VUE3.0基础入门笔记

时间:2024-10-22 11:30:47

一、响应式基础

1.ref():声明基本类型,引用类型,函数需接收参数,并将其包裹在一个带有 .value 属性的对象中,在模板中使用 ref 时,我们不需要附加 .value,当在模板中使用时,ref 会自动解包。

<template>
    <button @click="count++">
        {{ count }}
    </button>
</template>

<script setUp>
    import { ref } from 'vue'    
    const count = ref(0)
    console.log(count.value)
</script>

2.reactive():声明对象、数组等复杂类型。

<template>
    <button @click="state.count++">
          {{ state.count }}
    </button>
</tempalte>
<script setUp>
    import { reactive } from 'vue'
    const state = reactive({ count: 0 })
    console.log(state)
</script>


3. toRefs与toRef深度剖析

3.1 toRefs()函数用于将一个响应式对象的所有属性转换为单独的响应式引用。这个函数通常与解构赋值一起使用,以便我们可以轻松地访问响应式对象的每个属性。

<script setUp> 
import { reactive, toRefs  } from 'vue'
const state = reactive({
  name: 'Commas',
  age: 18
})
//现在解构这个对象,并保持每个属性的响应式,这时toRefs 就派上用场了
//这样,name 和 age 就成为了单独的响应式引用,我们可以直接使用它们,而不必担心失去响应式。
const { name, age } = toRefs(state);
</script> 

3.2 toRef()函数用于为源响应式对象的某个属性创建一个响应式引用。与 toRefs 不同的是,toRef 只能用于单个属性。

<script setUp>
//假设我们有一个响应式对象 state,包含name和age两个属性,现在我们只想为name属性创建一个响应式引用
//这样name就成为了state.name的响应式引用,我们可以直接修改name.value,这个修改会反映state.nam上。
 const name = toRef(state, 'name');
</script>

3.3 联系与区别

(1)toRefs 和 toRef 都用于创建响应式引用。

(2)toRefs 用于将整个响应式对象的所有属性转换为响应式引用,而 toRef 只用于单个属性。

(3)toRefs 通常与解构赋值一起使用,方便同时访问多个属性,而 toRef 用于为单个属性创建响应式引用。

二、计算属性 computed()

getter与setter的计算属性

 get()获取数据

 set()改变数据

<template>
  <div class="computed">
    姓: <input type="text" v-model="firstName" /> <br />
    名: <input type="text" v-model="lastName" /><br />
    姓名: <span>{{ fullName }}</span>
    <br />
    <button @click="changeName">更改姓名</button>
</template>

<script setUp>
import { computed, reactive } from "vue";
let firstName = ref("han");
let lastName = ref("召华");
//默认是 getter 方法
const fullName = computed(() => {
   return firstName.value + "_" + lastName.value;
});

//有getter与setter的计算属性
let fullName = computed({
  get() {
    return (
      firstName.value.slice(0, 1).toUpperCase() +
      firstName.value.slice(1) +
      "-" +
      lastName.value
    );
  },
  set(val) {
    let [str1, str2] = val.split("-");
    firstName.value = str1;
    lastName.value = str2;
  },
});
function changeName() {
  fullName.value = "huang-aaa";
}
</script>

三、watch与watchEffect

3.1watch 监听函数

 情况一:ref(监听不需要加.value)

 情况二:reactive(默认深度监听且deep不可变)

 情况三:getter函数监听 响应式对象的某个属性且是基本数据类型

 情况四:监视多个数据使用数组情况 [() => obj.value.a.b,() => obj.value.a]

<template>
  <h2>one:{{ one }} | two: {{ two.value }}</h2>
  <button @click="one++">改变 one 数据</button>
  <button @click="two.value++">改变 two 数据</button>
</template>

<script setup>
	import { watch, ref ,reactive} from 'vue'
	// 变量1
	const one = ref(0)
	// 变量2
	const two = reactive({
	  value: 10
	})
	// 监听多个变量
	// 第一个参数变为数组形式,每个下标对应一个要监听的变量
	// 第二个参数的函数传参改为每项数组形式,每个数组对应新旧值的参数
	watch([one, () => two.value], ([oneNew, twoNew], [oneOld, twoOld]) => {
		console.log(`one: ${oneNew}(新) ——— ${oneOld}(旧)`);
		console.log(`two: ${twoNew}(新) ——— ${twoOld}(旧)`);
	
	});
</script>

3.2 watchEffect 监听函数

 1.立即运行一个函数,同时响应式追踪其依赖,并在依赖更改时重新执行该函数

 2.watch与watchEffect区别

    2.1都能监听响应式数据变化,只不过见监听方式不同

    2.2watch:要明确指出监视数据

    2.3watchEffect:不用指出监视数据(函数使用哪些,就监视哪些属性)

<script setUp>
let obj = ref({
  a: {
    b: {
      watchName: "韩召华",
      watchAge: 28,
    },
  },
});
    watchEffect(() => {
         if (obj.value.a.b.watchAge > 23) {
            console.log("watchEffect--------", "监听到了");
          }
    });
</script>

四、ref获取DOM的使用

  1.用于修改HTML的DOM

  2.作用在组件上时,获取的是组件实例对象,子组件需通过defineExpose暴露数据,父组件才能使用

const presonDom = ref();
console.log("DOM实例", presonDom);

五、props使用

子组件接收

 1.defineProps 接收list + 限制类型

    defineProps(['a','b'])接收

    defineProps带默认值写法

 const props = defineProps({
  name: String,
    age:{
       type: Number,
       default: 23
    }
  })

 ts写法defineProp<{list?:Persons}>()

 2.withDefaults 接收list + 限制类型 + 限制必要性 + 指定默认值

 withDefaults(defineProps<{list?:Persons}>(),{list:()=>[{id:0,name:"张三",age:18}]})

六、生命周期

 1.创建 setUP

 2.挂载前 onBeforeMount(()=>{})

 3.挂载后 onMounted(()=>{ console.log(子组件挂载优先于父组件) })

 4.更新前 onBeforeUpdate(()=>{})

 5.更新后 onUpdate(()=>{})

 6.卸载前 onBeforeUnmount(()=>{})

 7.卸载后 onUnmounted(()=>{})

七、hooks

 1.use命名

 2.return 可用对象,数组,方法暴露数据且包含属性和方法

3.使用如下

八、routes路由

1.RouterLink 跳转路由

    >to的使用对象:name,path

    >active-class 激活样式

 2.RouterView 展示路由

 3.params query props 路由传参

 4.push推入,replace 替换(无路由记录,直接作用于RouterLink上)

 5.编程式导航 useRouter

 6.redirect 重定向

const router = useRouter();
router.push();
router.replace();

九、集中式状态数据管理 redux | vuex | pinia

1.npm i pinia

2.创建pinia(创建store文件-根据页面组件命名-用use命名store文件-defineStore定义store - 在使用组件引入该store文件 - 通过talkStore.value或者talkStore.$store.value)

3.pinia 修改的三种方式

  3.1第一种方式修改 countstore.sum 直接修改值

  3.2第二种方式修改 批量

 countstore.$patch ({
     sum: 100,
     xxx: 123
  })

  3.3第三种方式 使用actions

 //直接调用actions里的方法

  countStore.increment('参数集');

 4.storeToRefs响应式数据 只关注store中数据,不会对方法进行ref包裹

 5.getters 计算属性

 6.$subscribe 订阅 监听store中的数据变化

 countStore.$subscribe((mutate,satat)=>{
     console.log('监听',mutate,satat)
 }

 十、组件通信

 1.props

  父传子":data"

 子传父 子defineProps(['sentData']) 通过事件传参

 在父组件:sentData="getData"

 getData( value ) {
    console.log('接收子传数据',value);
  }

 2.自定义 defineEmits

  子组件 const emit = defineEmits(['eimtData'])

 emit('emitData',{name:'张三',age:18})

  父组件 @emitData="getData"

   getData( value ) {
       console.log('接收子传数据',value);
   }

  3._mitt工具-总线

 npm引入 mitt

 const emitter =mitt()

 emitter.emit() // 触发钩子

 emitter.on()   // 监听钩子

 emitter.off()  // 解绑钩子(一般在onUnmounted()里使用)

 emitter.all()  // 清空事件

 4.v-model方式

 5.$attrs 祖孙组件传值

  如下图所示,A 和 B、B 和 C/D 都是父子关系,C 和 D 是兄弟关系,A 和 C/D 是隔代关系。

$attrs: 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (包含class 和 style)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (包含class 和 style ),并且可以通过 v-bind=“$attrs” 传入内部组件。在Vue3.0版本中,$listeners / inheritAttrs已被移除掉,现在事件监听器是 $attrs 的一部分。

案例组件(父子孙)

同样,我们创建3个组件: 父组件:parent.vue,子组件:child.vue,孙组件:grandchild.vue

  • 父组件:parent.vue
<template>
  <div class="parent-root">
    <child
      class="child"
      :name="name"
      :age="age"
      @grandchildByValue="grandchildByValue"
    >
    </child>
  </div>
</template>

<script>
import { ref } from 'vue'
import child from '@/views/test/child.vue'
export default {
  name: 'parent',
  components: { child },
  props: {},
  setup() {
    const name = ref('小明'),
      age = ref(18),
      grandchildByValue = value => {
        console.log('孙组件传过来的值:', value)
      }
    return {
      name,
      age,
      grandchildByValue
    }
  }
}
</script>

<style scoped lang="scss"></style>
  • 子组件:child.vue
<template>
  <div class="child-root">
    <grandchild v-bind="$attrs"></grandchild>
  </div>
</template>

<script>
import { ref } from 'vue'
import grandchild from '@/views/test/grandchild.vue'
export default {
  name: 'child',
  components: { grandchild },
  props: {},
  setup(context) {
    console.log('attrs:', context.attrs) 
  }
}
</script>

<style scoped lang="scss"></style>

  • 孙组件:grandchild.vue
<template>
  <div class="grandchild-root">
    <p>name:{{ name }}</p>
    <p>age:{{ age }}</p>
    <el-button type="primary" @click="fn">孙子传值爷爷</el-button>
  </div>
</template>

<script>
export default {
  name: 'grandchild',
  components: {},
  props: {
    name: {
      type: String,
      default: ''
    },
    age: {
      type: Number,
      default: null
    }
  },
  setup(context) {
    const fn = () => {
      context.emit('grandchildByValue', '爷爷,您好')
    }
    return {
      fn
    }
  }
}
</script>

<style scoped lang="scss"></style>

6.$refs跟$parent

ref 需要结合defineExpose暴露数据使用

1、$refs用法

父组件(模板代码:参数$refs
 

<button @click="getAllChild($refs)">让所有孩子的书变多</button>
<Child1 ref="c1"/>
<Child2 ref="c2"/>

 按钮对应的函数代码
这里,就可以同时获取到c1和c2两个实例

	function getAllChild(refs:{[key:string]:any}){
		console.log(refs)
		for (let key in refs){
			refs[key].book += 3
		}
	}

两个子组件
暴露变量,这样父组件就可以操作该变量

// 把数据交给外部
defineExpose({book})

2、$parent用法

子组件(模板代码:参数$parent

<button @click="minusHouse($parent)">干掉父亲的一套房产</button>

按钮对应的函数代码

	function minusHouse(parent:any){
		parent.house -= 1
	}

 父组件(暴露变量

	// 向外部提供数据
	defineExpose({house})

  7.provide_inject依赖注入 祖孙之间直接通信

我们可以使用provide和inject使顶层组件向底层组件提供数据和方法。

Provide

语法格式: provide('key',顶层组件中的数据或方法)

顶层组件示例代码如下:

<template>
    顶层组件
    <Middle />
</template>
 
<script setup>
import { provide, ref } from 'vue'
import Middle from './components/middle.vue'
const count = ref('这是顶层组件提供的数据')
provide('count-key', count)		// 向底层组件提供数据
const sayHello = () => {
  console.log('你好,我是顶层组件提供的方法')
}
provide('sayHello', sayHello)   // 向底层组件提供方法
</script>

 中间组件示例代码如下:

<template>
  中间组件
  <Bottom />
</template>
 
<script setup>
import Bottom from './bottom.vue'
</script>

inject

底层组件通过inject函数获取数据并通过变量接收,其语法格式如下:

const 变量名=inject('key')

底层组件示例代码如下: 

<template>
  底层组件
  <button @click="sayHello">sayHello</button>
  <div>来自顶层组件的数据:{{ countData }}</div>
</template>
 
<script setup>
import { inject } from 'vue'
const sayHello = inject('sayHello')
const countData = inject('count-key')
</script>

十一、shallowReactive和shallowRef

shallowReactive

(1)作用:与reactive作用类似,但只处理对象最外层属性的响应式(浅响应式)

(2)使用场景:如果有一个对象数据,结构比较深(内嵌多层对象),但只需要最外层的属性进行响应式,则使用shallowReactive

shallowRef

(1)作用:与ref作用类型,但只处理基本数据类型的响应式,不处理对象类的响应式

(2)使用场景:如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换,则使用shallowRef

十二、readonly和shallowReadonly

(1)readonly:将包裹的对象变为只读,并且是深度只读
(2)shallowReadonly:浅层属性为只读,深层次属性可以修改

十三、toRaw和markRaw

     toRaw将代理对象变成普通对象,数据发生变化,不会更新
     markRaw标记的对象数据,从此以后再也不能成为代理对象了

两者区别:toRaw会将整个对象变成非响应式的,markRaw可以指定哪些属性值可以变化

<template>
  <div>
    <h2>toRaw与markRaw</h2>
    <h3>state:{{state}}</h3>
    <button @click="updateToRaw">测试toRaw</button>
    <button @click="updateMarkRaw">测试markRaw</button>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent,reactive,ref,toRaw,markRaw } from 'vue'
interface UserInfo {
  name:string,
  age:number,
  likes?:string[]
}
// 引入子集组件
import Child from './components/child.vue';
export default defineComponent({
    components:{
        Child
    },
    setup(){
      const state = reactive<UserInfo>({
        name:'小明',
        age:12,
      })
      const updateToRaw = () => {
        const user = toRaw(state)
        user.name += '-----'
        console.log('测试')
      }
      const updateMarkRaw = () => {
        // state.likes = ['吃','喝']
        // state.likes[0] += '-----'
        const likes = ['吃','喝']
        state.likes = markRaw(likes)

       setInterval(() => {
           console.log('定时器走起来')
           if (state.likes) {
             state.likes[0] += '----'
           }
         },1000)
        console.log('测试')
      }
      
      return {
        updateToRaw,
        updateMarkRaw,
        state
      }
    }
})
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;
}

nav a {
  font-weight: bold;
  color: #2c3e50;
}

nav a.router-link-exact-active {
  color: #42b983;
}
</style>

十四、自定义customRef

(1)customRef 用于自定义返回一个ref对象,可以显式地控制依赖追踪和触发响应,接受工厂函数
(2)两个参数分别是用于追踪的 track 与用于触发响应的 trigger,并返回一个一个带有 get 和 set 属性的对象

使用:
	import {customRef} from 'vue';
    function useDebouncedRef(value) {
	      return customRef((track, trigger) => {
	        return {
	          get() {
	            track()	追踪当前数据
	            return value
	          },
	          set(newValue) {
	            value=newValue
	            trigger() 触发响应,即更新界面
	          },
	        }
	      })
	 }
	
通过customRef返回的ref对象,和正常ref对象一样,通过x.value修改或读取值

类型声明:
	function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

	type CustomRefFactory<T> = (
	  track: () => void,
	  trigger: () => void
	) => {
	  get: () => T
	  set: (value: T) => void
	}