Vue3入门笔记

本文详述Vue3的setup生命周期、异步组件、数据代理、ref与reactive、计算属性、watch与watchEffect、props与context、hooks、响应式转换函数等内容,通过实例代码帮助读者快速掌握Vue3核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前端时间一直在研究Vue3,最近总算把笔记整理完了,在此记录下来,以备查阅。
虽然这篇内容不多,但是私以为看完这篇,对着代码写写,基本上也就入门Vue3了。

Vue3学习笔记:

1. setup生命周期等

在Vue3中,有一个setup函数,是新的组件选项,作为composition API的入口。

1. setup的调用时机:
  1. 创建组件实例,然后初始化props,接着调用setup函数。从生命周期来看,它是在beforeCreate之前被调用的。所有的组合API函数都在此使用,并且只被执行一次。

  2. setup中的this是undefined,不能通过this来访问data/computed/methods/props,包括所有的组合式API中的相关回调函数都不可以

2. setup函数返回的是一个对象,对象中的属性或方法,可以直接使用。

如下代码中,setup返回了两个属性,一个是count变量,一个是updateCount函数,可以直接使用。

<template>
   {{count}}
 <br/>
 <button @click="updateCount">更新数据</button>
</template>
<script lang='ts'>
import {defineComponent, ref} from 'vue';
export default defineComponent ({
 name: 'App',
 setup(){
   // html 模板中不需要.value属性的写法
   let count = ref(11);
   function updateCount(){
     count.value++ ;
   }
   return {
     count, // 返回的count属性可以直接使用
     updateCount,  // 函数也在此返回
   }
 }
})
</script>

另外,vue2中的data返回的配置项,也可以用在vue3中,用法与在Vue2中一致。但是并不推荐这样做。
同样的,vue2中定义method的方式,也可以用在vue3中,官方也不推荐这样使用。

methods中可以访问setup提供的属性和方法,但是setup中 无法访问data和methods。

setup不能是一个async函数,因为返回值不再是return对象,而是promise,模板看不到return对象中的属性数据。

3. setup参数:

写法:setup(props,context){} 或 setup(props,{attrs,slots,emit}){}

props: 包含了props配置中声明并传入的所有属性对象

attrs:包含没有在props中声明的所有属性对象,相当于this.$attrs

slots:包含所有传入的插槽对象,相当于this.$slots

emit:用来分发自定义事件函数,相当于this.$emit

2. 异步引入组件

异步引入组件的方法很多,在这里记录下3个:

  1. defineAsyncComponent异步加载组件
<template>
	<AsyncCom />
</template>
import {defineComponent, defineAsyncComponent} from 'vue';
// Vue3中静态引入组件的方法
const AsyncCom = defineAsyncComponent(()=>import('./AsyncCom.vue'));
export default defineComponent({
  components:{ AsyncCom },
  setup(){
    return {}
  },
});
  1. setup中返回promise对象
export default defineComponent({
    setup(){
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
            resolve({
                msg:'Hello world!'
            });
        },2000);
        })
    }
});
  1. setup和async/await结合使用
    前面说过,setup不能是async函数,那是因为返回的是一个promise对象,模板看不到返回对象中的数据结构,所以需要把结果处理成模板可识别的结构,方能使用。
    如下代码所示:
export default defineComponent({
    // setup(){
    //     return axios.get('/data/address.json').then(response=>{
    //         return {
    //             data: response.data
    //         }
    //     });
    // }
    async setup(){
        const result = await axios.get('/data/address.json');
        return {
            data: result.data
        }
    }
})

3. 数据代理源码分析

Vue2中数据代理用的是Object.defineProperty方法,Vue3中是通过Proxy和Reflect结合,实现数据代理的。proxy比Object.defineProperty高效,响应也更快。
代码见下:

// 模拟Vue3中的数据代理
	let user = {
        name: '路飞',
        age: 20,
        wife:{
            name: '小樱',
            age: 19,
        };
	const proxyUser = new Proxy(user,{
            get(target,prop){
                console.log('get方法调用了!');
                return Reflect.get(target,prop);
            },
            // 修改目标对象的属性值/为目标对象添加新的属性
            set(target,prop,value){
                return Reflect.set(target,prop, value);
            },
            // 删除目标对象上的某个属性
            deleteProperty(target,prop){
                console.log('delete方法被调用了!');
                return Reflect.deleteProperty(target, prop);
            }
        });
	
        proxyUser.name = '路飞'; 
        proxyUser.gender = '男';
        delete proxyUser.gender;
        proxyUser.wife.name = '小红';

4. ref和reactive的基本使用

Vue3中有两个新的API,ref和reactive都是用来把数据转换成响应式数据,ref用来处理基本数据,reactive用来处理对象(递归深度响应式数据)。
ref也可以传入对象类型的数据,其内部会自动调用reactive,把对象转为响应式数据。
ref:内部会给value添加getter和setter来实现数据劫持,在js/ts中,ref对象的值,需通过.value操作,而在模板中则不需要添加.value。
reactive:内部通过Proxy来实现对象内部所有属性的劫持,并通过Reflect操作对象内部数据。

<template>
  <h1>用户1</h1>
  <h2>姓名:{{ user.name }}</h2>
  <h2>年龄:{{user.age}}</h2>
  <h2>性别:{{user.gender}}</h2>
  <h2>wife: {{user.wife}}</h2>
  <button @click="updateUser">更改数据</button>
  <h2>App父级组件</h2>
  <h3>{{msg}}</h3>
  <button @click="msg+='='">Update</button> // 模板中不用调用msg.value即可操作ref中的数据
</template>
import { defineComponent,reactive,ref } from 'vue';
export default defineComponent({
  name: 'App',
  setup(){
    const obj: any = { // 为了在使用obj.gender = "男"的时候不出现这种错误提示
      name:"香吉士",
      age: 25,
      wife:{
        age: 22,
        gender: 'female',
        name: '娜美',
        car: ['法拉利','奔驰'],
      }
    };
    const user = reactive(obj);
    const msg = ref('How are you,today?');
    const updateUser = ()=>{
      // 通过当前的代理对象找到该对象中的某个属性,更改该属性中的某个数组的数据
      user.wife.age=23;
      user.wife.car[1]='玛莎拉蒂';
      user.wife.car[2]='奥拓';
      // 总结:如果操作代理对象,目标对象中的数据也会随之变化,同时如果想要在操作数据的时候,界面也要跟着重新更新渲染,那么也是操作代理对象
    };
    return {
      user,
      msg,
      updateUser 
    }
  }
});

5. computed计算属性

Vue3中的计算属性依然依赖于缓存,有getter和setter函数,也有简写形式,但是不用像Vue2中那样写在computed配置项中,而是写在setup中,作为组合式API之一返回,如下所示:

<template>
  <div class="demo">
  <h1>我是computed演示组件</h1>
  用户1FirstName:<input v-model="person1.firstName" /><br />
  用户1LastName:<input v-model="person1.lastName" />
  全名<h1>{{person1.fullName}}</h1>
  <hr/>
  用户2FirstName:<input v-model="person2.firstName" /><br />
  用户2LastName:<input v-model="person2.lastName" />
  全名<h1>{{person2.fullName}}</h1>
</div>
</template>

<script>
import {reactive,computed} from 'vue';
export default {
  name: 'Demo',
  props:['msg','school'],
  emits:['hello'],
  setup(props, context){
    let person1 = reactive({
      firstName:'尼古拉斯',
      lastName:'赵四',
      age: 18,
    });
    let person2 = reactive({
      firstName:'郭达',
      lastName:'斯坦森',
      age: 19,
    });
    // 1.计算属性 简写形式的computed
    person1.fullName = computed(()=>{
       return person1.firstName + person1.lastName;
     });
    // 2.计算属性 完整形式的computed
    person2.fullName = computed({
      get(){
        return person2.firstName +'-'+ person2.lastName;
      },
      set(value){
        const nameArr = value.split('-');
        person2.firstName = nameArr[0];
        person2.lastName = nameArr[1];

      }
    });
    return {person1,person2}
  },
}
</script>

6. watch和watchEffect

Vue3中的 watch,也是写在setup中,监视输出对象。
watch在页面首次渲染的时候不会调用,需指定immediate属性的值,才能执行首次监听。
watch可以取到更新前后的值。
另外,当watch监听reactive定义的数据时,会强制开启深度监听,也就是说,此时设置deep参数无效,无论是true或false,都会进行深度监听。
上代码:

<template>
  <div class="demo">
  <h1>sum:{{sum}}</h1><button @click="sum++">加一</button>
  <hr/>
  <h1>消息:{{msg}}</h1><button @click="msg+='!'">修改信息</button>
  <hr/>
  <h1>姓名:{{person.name}}</h1><button @click="person.name+='~'">修改姓名</button>
  <h1>年龄:{{person.age}}</h1><button @click="person.age+=1">修改年龄</button>
  <h1>薪资:{{person.job.j1.salary}}K</h1><button @click="person.job.j1.salary+=1">修改薪资</button>
  <hr/>
  <h1>{{person1.name}}</h1>
  <h1>{{person1.age}}</h1>
  <h1>{{person1.job.j2.salary}}</h1>
  <button @click="person1.job.j2.salary++">加薪</button>
</div>
</template>
<script >
import {reactive,ref,watch} from 'vue';
export default {
  name: 'Demo',
  setup(props, context){
    let sum = ref(0);
    let msg = ref('你好啊');
    let person = reactive({
      name: '张三',
      age: 18,
      job:{
        j1:{salary: 20}
      }
    });
    let person1 = ref({
      name:'李四',
      age:25,
      job:{j2: {salary: 20}}
    })
    // 情况一:监视ref所定义的一个响应式数据
     watch(sum,(newValue,oldValue)=>{
       console.log('sum变了',newValue, oldValue);
     },{immediate: true})

    // 情况二:监视ref所定义的多个响应式数据
     watch([sum,msg],(newValue, oldValue)=>{
       console.log('修改了sum 或 msg',newValue, oldValue);
     },{immediate: true})

    // 情况三:监视reactive所定义的一个响应式数据,
    //  注意:
    // 1.这里无法正确得获取oldValue
    // 2.强制开启了深度监视(deep配置无效)
     watch(person,(newValue, oldValue)=>{
       console.log('修改了person',newValue, oldValue);
     },{deep: false}); //此处的深度监视无效

    // 情况四:监视reactive所定义的一个响应式数据中的某个属性
       watch(()=>person.name, (newValue, oldValue)=>{
         console.log('修改了person中的name属性',newValue,oldValue);
       })

    // 情况五:监视reactive所定义的一个响应式数据中的某些属性
     watch([()=>person.name,()=>person.age],(newValue, oldValue)=>{
       console.log('修改了person中的name或age属性',newValue, oldValue);
     });
    
    // 特殊情况
    watch(()=>person.job,(newValue, oldValue)=>{
      console.log('person的job变化了,',newValue, oldValue);
    }, {deep: true}) // 此处用于深度监视的是reactive定义的对象中的某个属性,所以deep配置有效

    // watch
    watch(person1,(newValue, oldValue)=>{
      console.log('person1发生了变化',newValue, oldValue);
    },{deep: true})
    return {sum,msg,person,person1}
  },
}
</script>

watchEffect不需要添加immediate参数即可实现首次监听,只要数据有更新便执行,但其无法取到更新前的原值。用法如下代码所示:

import {reactive,computed,ref,watchEffect} from 'vue';
export default {
  name: 'Demo',
  emits:['hello'],
  setup(){
    let sum = ref(0);
    let msg = ref('你好啊');
    let person = reactive({
      name: '张三',
      age: 18,
      job:{
        j1:{salary: 20}
      }
    });
    
    watchEffect(()=>{
      let x = person.name;
      let y = person.job.j1.salary;
    })
    return {sum,msg,person}
  },
}

此处有watch和watchEffect的详细解析

7. props和context

props属性传值和context上下文,与Vue2中用法略有不同,props须在setup里作为参数传递给组合式API,上代码:

// Parent组件
<template>
  <div class="app">
    <h1>Computed</h1>
    <Demo msg="你好啊" school="南加州大学" @hello="sayHello">
    </Demo>
  </div>
</template>
<script>
import Demo from './components/Demo.vue';
export default {
  name: 'App',
  components:{Demo},
  setup(){
    function sayHello(value){
      alert(`你好啊,你触发了hello事件,收到的参数是${value}`);
    }
    return{
      sayHello
    }
  }
}
</script>
// Child组件
<template>
  <div class="demo">
  <h1>我是Demo组件</h1>
  <h1>{{person.name}}</h1>
  <h1>{{person.age}}</h1>
  <h1>{{props.school}}</h1>
  <button @click="testEmit">触发hello事件</button>
</div>
</template>
<script>
import {reactive} from 'vue';
export default {
  name: 'Child',
  props:['msg','school'], 
  emits:['hello'],
  setup(props, context){  // 此处需声明props,context,下文才能引用
    let person = reactive({
      name:'张三',
      age: 18,
      msg: props.msg
    });
    console.log(context.slots);
    console.log(context.attrs);
    console.log(context.emits);
    function testEmit(value){
      context.emit('hello',6666); // 触发父级传来的自定义事件
    }
    return {person, testEmit}
  },
}
</script>

8. hooks钩子

hooks钩子就是使用Vue3中组合式API封装的可复用的功能函数,类似于Vue2中的mixins。钩子的命名通常以use+‘XXX’开头,所以更方便识别其用途。

<template>
  <h2>自定义hook函数操作</h2>
  <h2>x:{{x}},y:{{ y}}</h2>
  <h3 v-if="loading">正在加载中……</h3>
  <h3 v-else-if="errorMsg">错误信息:{{errorMsg}}</h3>
  <!-- <ul v-else>
    <li>id:{{data.id}}</li>
    <li>address:{{data.address}}</li>
    <li>distance:{{data.distance}}</li>
  </ul>
  <hr /> -->
  <!--数组数据-->
  <ul v-for="item in data" :key="item.id">
    <li>id:{{item.id}}</li>
    <li>title:{{item.title}}</li>
    <li>price:{{item.price}}</li>
  </ul>
</template>
<script lang="ts">
import { defineComponent, onMounted,onBeforeUnmount, ref} from 'vue';
import useMousePosition from './hooks/useMousePosition';
import useRequest from './hooks/useRequest';
// 定义接口,约束对象的类型
interface AddressData{
  id: number,
  address: string,
  distance: string,
}
interface ProductData{
  id: string,
  title: string,
  price: number,
}
export default defineComponent({
  name: 'App',
  // 需求1:用户在页面中点击页面,把点击的位置的横纵坐标收集起来并展示出来

  setup(){
    const {x,y} = useMousePosition();

    // 发送请求
    const {loading, data, errorMsg} = useRequest<AddressData>('/data/address.json'); // 获取对象数据
    const {loading, data, errorMsg} = useRequest<ProductData[]>('/data/products.json'); // 获取对象数据
    return {
      x,y,
      loading, data, errorMsg
    }
  }
});
</script>

钩子 useRequest,用于通过axios请求数据,并把数据传递返回出来。

import {ref} from 'vue';
import axios from 'axios';
// 发送ajax请求
export default function<T> (url: string){
    // 加载的状态
    const loading = ref(true);
    // 请求成功的数据
    const data = ref<T | null>(null);
    // 错误信息
    const errorMsg = ref('');

    axios.get(url).then(response =>{
        // 改变加载状态
        loading.value  = false;
        data.value = response.data;
    }).catch((error:any)=>{
        // 改变加载状态
        loading.value =false;
        errorMsg.value = error.msg || '未知错误';
    });
    return {
        loading,
        data,
        errorMsg
    }
}

钩子userMousePosition,用于获取鼠标点击时的屏幕坐标。

import {onMounted,onBeforeUnmount, ref} from 'vue';

export default function() {
  // 需求1:用户在页面中点击页面,把点击的位置的横纵坐标收集起来并展示出来
    const x = ref(0);
    const y = ref(0);
    const clickHandler = (event:MouseEvent)=>{
      x.value = event.pageX;
      y.value = event.pageY;
    };
    // 页面已经加载完成了,再进行点击的操作
    // 页面加载完毕的声明周期组合API
    onMounted(()=>{
      window.addEventListener('click', clickHandler);
    })
    // 页面卸载之前的声明周期组合API
    onBeforeUnmount(()=>{
      window.removeEventListener('click',clickHandler);
    })
    return {
      x,y
    }
  }

9. toRefs和toRef

  1. toRefs的作用是,把一个响应式对象转换成普通对象,该对象的每一个property都是ref。
    应用场景:当从合成函数返回响应式对象的时候,toRefs非常有用,可以在不丢失响应式的情况下对返回的对象进行分解应用。
    如下代码:state返回一个包含name和age的对象,如果直接同reactive解构,数据是无法实现动态响应的,但是toRefs可以。
<template>
  <h2>reactive解构</h2>
  <h3>name:{{name}}</h3>
  <h3>age: {{age}}</h3>
  <h3>toRefs返回的</h3>
  <h3>name1:{{name1}}</h3>
  <h3>age1:{{age1}}</h3>
</template>

<script lang="ts">
import { defineComponent, toRefs, reactive} from 'vue';
export default defineComponent({
  name: 'App',
  setup(){
    const state = reactive({
      name: '小龙人',
      age: 500
    });   
    const {name, age} = toRefs(state);
    // 定时器,更新数据,如果数据变化了,界面也会随之变化,肯定是响应式的数据
    setInterval(()=>{
       state.name += '==='
    },2000);
    return {
      // state
       ...state  // 不是响应式的数据了
       name1:name,
       age1:age,
    }
  }
});
</script>

进一步对比,toRefs和reactive。toRefs可以生成属性也是响应式的对象,而reactive本身是响应式,当其属性解构以后就不再是响应式了。

<template>
  <h2>toRefs的使用</h2>
  <h3>name:{{name}}</h3>
  <h3>age: {{age}}</h3>
  <hr/>
  <h3>name2:{{name2}}</h3>
  <h3>age2: {{age2}}</h3>
</template>

<script lang="ts">
import { defineComponent, toRefs, reactive} from 'vue';

function useFeatureX(){
  const state =reactive({
    name2: '小飞侠',
    age2:23
  });
  return {
    ...toRefs(state)
  }
}

export default defineComponent({
  name: 'App',
  setup(){
    const state = reactive({
      name: '小龙人',
      age: 500
    });
    // toRef可以把一个响应式对象转换成普通的对象,该普通对象的每个property都是一个ref
    // const state2 = toRefs(state)
    const {name, age} = toRefs(state);
    // 定时器,更新数据,如果数据变化了,界面也会随之变化,肯定是响应式的数据
    setInterval(()=>{
      // state.name += '==='
      // state2.name.value += '=='
      name.value += '='
    },2000);

    const {name2, age2} =useFeatureX();
    return {
      // state
      // ...state  // 不是响应式的数据了
      // ...state2
      name,
      age,
      name2,
      age2
    }
  }
});
</script>
  1. toRef
    toRef顾名思义,就是把对象里的属性转为ref对象,即保留了响应式也保留了引用。代码如下所示:
<template>
  <h2>toRaw 和 markRow</h2>
  <hr/>
  {{ state }}<br/>
  <hr/>
  年龄:{{age}}<br/>
  存款:{{money}}<br/>
  <button @click="update">更新数据</button>
</template>

<script lang="ts">
import { defineComponent, ref, reactive, toRef} from 'vue';
export default defineComponent({
  name: 'App',
  setup(){
    const state = reactive({
      age:5,
      money: 20
    });
    // 把响应式数据state对象中的属性age变成了ref对象了
    const age = toRef(state,'age');
    // 把响应式对象中的属性使用ref进行包装,变成了一个ref对象
    const money = ref(state.money);
    const update = ()=>{
      // 更新数据
      age.value += 2;  // 效果等同于 state.age += 2; 
      money.value +=4;
    }
    return {
      state,
      age,
      money,
      update
    }
  }
});
</script>

10. shallowReactive和shallowRef

shallow意为浅层,shallowReactive和shallowRef顾名思义,就是只监听对象的浅层属性,浅层属性为响应式数据,深层数据非响应式。
shallowReactive只处理对象的最外层属性。
shallowRef只处理.value的响应式,不进行对象的reactive处理。
什么情况下使用shallowReactive和shallowRef呢?
如果有一个对象数据,结构较深,但是变化只是外层属性变化,可以使用shallowReactive。
如果一个对象数据,后面会产生新的对象来替换,可以使用shallowRef。

看下面的代码:

<template>
  <h2>shallowReactive 和 shallowRef</h2>
  <h3>m1:{{m1}}</h3>
  <h3>m2:{{m2}}</h3>
  <h3>m3:{{m3}}</h3>
  <h3>m4:{{m4}}</h3>
  <hr/>
  <button @click="update">更新数据</button>
</template>

<script lang="ts">
import { defineComponent, reactive, ref,shallowReactive, shallowRef} from 'vue';
export default defineComponent({
  name: 'App',
  setup(){
    // 深度劫持(深监视)---深度响应式
    const m1 = reactive({
      name: '小红',
      age: 20,
      car:{name: '奔驰', color: 'red' }
    });
    // 浅劫持(浅监视)--- 浅响应式
    const m2 = shallowReactive({
      name: '小黄',
      age: 22,
      car: { name: '奔驰', color: 'yellow'}
    });
    // 深度劫持(深监视)---深度响应式---做了reactive的处理
    const m3 = ref({
      name: '小兰',
      age: 22,
      car: {name: '宝马',color:'blue'}
    });
    // 浅接触(浅监视)---浅响应式
    const m4 = shallowRef({
      name: '小白',
      age: 22,
      car:{name: '宝马', color:'white'}
    });

    const update=()=>{
      // 更改m1数据 --- reactive方式
       m1.name +='==';
       m1.car.name += '=='
      // 更改m2的数据---shallowReactive
       m2.name += '==';
       m2.car.name += '=='      // 此处无法改变其值
      // 更改m3的方式---ref方式
       m3.value.name+= '='
      // 更改m4的方式---shallowRef方式
       m4.value.name +='='
       m4.value.car.name += '='; // 此处无法改变其值。
      console.log(m3,m4);
    }
    return {
      m1,
      m2,
      m3,
      m4,
      update
    }
  }
});
</script>

11. readonly和shallowReadonly

readonly顾名思义为只读,读取一个对象(响应式或纯对象)或ref并返回原始代理的只读代理。只读代理是深层的,嵌套的任何property都是只读的。
shallowReadonly则是把浅层属性转为只读,对于深层数据仍然可修改。

应用场景:
在某些特定情况下,我们可能不希望对数据进行更新的操作,那就可以包装生成一个只读代理对象来读取数据,而不能修改或删除

<template>
  <h2>readonly 和 shallowReadonly</h2>
  <h3>m1 原数据:{{m1}}</h3>
  <h3>m2 readonly:{{m2}}</h3>  
  <h3>m3 shallowReadonly:{{m3}}</h3>
  <hr/>
  <button @click="update">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, readonly,shallowReadonly, reactive, ref} from 'vue';
export default defineComponent({
  name: 'App',
  setup(){
     const m1 = reactive({
       name: '小红',
       age: 20,
       car:{name: '奔驰', color: 'red' }
     });
     const m2 = readonly(m1);
    const m3 = shallowReadonly(m1);
    const update=()=>{
      m1.name += '=='
      m2.value.car.name += '==='
    }
    return {
      m1,
      m2,
      m3
      update
    }
  }
});
</script>

12. toRaw和markRaw

toRaw和markRaw的作用,都是把响应式代理数据,转为普通对象,对于这个对象的所有操作,不会引起页面的更新。
toRaw用于接收一个由reactive创建的响应式对象,并将这个响应式数据转为普通对象。
toRaw()可以返回由reactive(),readonly(),shallowReactive()或shallowReadonly()创建的代理对应的原始对象
适用于用于临时读取,改变数据,但不引起界面更新的场景。

markRaw 标记一个对象,使其永远不会成为响应式对象,当其中的属性发生改变时,不会引起界面更新。
实际上markRaw是把代理对象又返回了普通对象本身。
适用于:
1.有些值不应该被设置为响应式的对象,例如复杂的第三方类库或Vue组件对象
2.当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
以下为代码示例:

<template>
 <h2>toRaw 和 markRow</h2>
 <h3>{{state}}</h3>
 <hr>
 <button @click="testToRaw">测试toRaw</button>
 <button @click="testMarkRaw">测试MarkRaw</button>
</template>

<script lang="ts">
import { defineComponent, markRaw, reactive, toRaw} from 'vue';
interface userInfo{
 name: string;
 age: number;
 hobby?: string[];
};
export default defineComponent({
 name: 'App',
 setup(){
   const state = reactive<userInfo>({
     name: '小花',
     age: 20
   });
   
   const testToRaw = ()=>{
     const user = toRaw(state);
     user.age ++;
   }

   const testMarkRaw = ()=>{
     const hobby = ['吃','喝'];
     state.hobby = markRaw(hobby);
     setInterval(()=>{
       state &&state.hobby &&state.hobby[0]+= '=='
     },1000);
   }
   return {
    state,
    testToRaw,
    testMarkRaw
   }
 }
});
</script>

在此可查看更详细的toRaw和markRaw的详解

13. customRef的使用

customRef用于创建一个自定义ref,并对其依赖跟踪和更新触发进行显示控制。
接收一个工厂函数,其中两个参数:trace用于追踪,trigger用于触发,并返回一个带有get和set属性的对象。
上代码:

<template>
  <h2>CustomRef的使用</h2>
  <input type="text" v-model="keyword"/>
  <p>{{ keyword }}</p>
</template>

<script lang="ts">
import { customRef, defineComponent} from 'vue';
// 自定义hook防抖函数
// value传入的数据,将来数据的类型不确定,所以,用泛型,delay防抖的间隔时间是,200毫秒。
function useDebouncedRef<T>(value:T, delay=200){
  console.log(value,delay);
  let timeOutId: number;
  return customRef((track, trigger)=>{
    return {
      // 返回数据
      get(){
        // 告诉数据追踪
        track();
        return value;
      },
      // 设置数据
      set(newValue:T){
        // 清理定时器
        clearTimeout(timeOutId);
        // 开启定时器
        timeOutId =setTimeout(()=>{
          value = newValue;
          // 告诉界面更新
          trigger();
        }, delay)
      }
    }
  })
}
export default defineComponent({
  name: 'App',
  setup(){
    const keyword = useDebouncedRef('abc',500);
    return {
      keyword
    }
  }
});
</script>

14. isRef、isReactive、isReadonly、isProxy

isRef:检查一个值是否为ref,
isReactive:检查一个对象是否是由reactive创建的响应式代理
isReadonly:检查一个对象是否是readonly创建的只读代理
isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理

15. provide和inject

provide和inject需配合使用,可以不分级别传递参数,类似于Vue2中的全局事件总线Bus。具体方法为:通过provide提供数据输出,然后由inject接收。
话不多说,上代码:
祖级组件 App.vue

// App.vue
<template>
  <h2>provide 和 inject</h2>
  <p>当前的颜色:{{ color }}</p>
  <button @click="color= 'red'">红色</button>
  <button @click="color= 'yellow'">黄色</button>
  <button @click="color= 'green'">绿色</button>
  <hr>
  <Child />
</template>

<script lang="ts">
import { defineComponent, ref,provide} from 'vue';
import Child from './components/Child.vue';
export default defineComponent({
  name: 'App',
  components:{ Child },
  setup(){
    const color = ref('red');
    provide('color',color);  // 此处的provide函数把color传递出去
    return {
      color,
    }
  }
});
</script>

父级组件:Child.vue

// Child.vue
<template>
  <h3>Child 子级组件</h3>
  <hr>
  <Grandchild :color="color"/>
</template>

<script lang="ts">
import {defineComponent} from 'vue';
import Grandchild from './Grandchild.vue';
export default defineComponent({ // 此处并未传递任何props
    name: 'Child',
    components:{Grandchild},
})
</script>

子级组件:Grandchild

// GrandChild.vue
<template>
  <h3 :style="{color}">Grandchild 子级组件</h3>
  <hr>
</template>

<script lang="ts">
import {defineComponent, inject} from 'vue';
export default defineComponent({
    name: 'Grandchild',
    setup(){
       const color = inject('color');  // 通过inject接收到的函数,可以直接使用
       return {
        color
       }
    }
})
</script>

16. teleport

teleport译为“瞬移”,是Vue3中一个很使用的方法,可以把组件转移到在父组件外想要插入的标签(通常是body)内。
话不多说,上代码:
父组件:

// APP.vue
<template>
  <h2>响应式数据的判断</h2>
  <h2>响应式数据</h2>
  <hr/>
  <Child />
</template>

<script lang="'ts">
import {defineComponent, isReactive, isRef, reactive,ref,readonly,isReadonly,isProxy} from 'vue';
import Child from './Child.vue';
export default defineComponent({
  components:{Child},
});

子组件:标签teleport中的内容将出现在body里

<template>
    <button @click="modalOpen = true">Open full screen modal</button>
    <teleport to="body">
        <div class='modal' v-if="modalOpen">
            <div>这是modal对话框
            <button @click="modalOpen =false">关闭对话框</button>
        </div>
        </div>
    </teleport>
</template>
<script langt="ts">
import {defineComponent, ref} from 'vue';
export default defineComponent({
    name:'Modal',
    setup(){
        const modalOpen = ref(false);
        return {
            modalOpen,
        }
    }
});
</script>

17. Fragment

fragment名为碎片,在Vue3中提供了一种虚拟的fragment元素,使得在template中不再需要用一个根节点把所有元素都包裹起来。减少了渲染时的内存消耗。
仔细观察上面的所有代码实例里,是不是大多数template里都没有包含根节点?那就是fragment的作用。

18.写在最后:这篇文章内容不多,但是我写了快一个星期,期间翻了很多之前学习的视频和代码, 为的是每个点都附带上一段代码。有些内容看完即忘,但是如果之前写过,再次看到就会立马想起来。正所谓:纸上得来终觉浅,绝知此事要躬行。希望以后能有更多案例可以记录下来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值