vue3 composition 注意点总结

131 阅读6分钟

「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战

setup()

setup()接受两个参数:

  1. props

  2. context:context中包含三个属性

    attrs:所有的非prop的attribute;

    slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用);

    emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);

setup()的返回值:

setup()可以放回一个对象,该对象中的属性可以在template中使用,相当于options API 中的data属性。

注意:在 setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。

setup中,如果我们想要访问组件实例,我们需要vue中导出的getCurrentInstance。

 import { getCurrentInstance} from "vue";
 setup(props,context) {
 ​
     const instance = getCurrentInstance();
     console.log(instance.appContext.config.globalProperties.$name);
    }

reactive

我们直接定义的对象不是响应式的,界面中的使用不会随着数据改变而响应式的更新。因此,我们可以使用reactive(),将我们的对象进行包裹,此时我们对象中的值,就是响应式的了。

注意:reative()中只能传入对象或数组,不能传入基本数据类型。

使用:

 <template>
   <div class="home">
     <button @click="fn">点击</button>
     <h2>home:{{ info3.age }}</h2>
   </div>
 </template>
 ​
 <script>
    //导入reactive
 import { reactive } from "vue";
 export default {
   props: ['lz'],
   components: {
     detail
   },
   setup(props, context) {
       //使用reactive
     let info3 = reactive({ age: 10 });
     function fn() {
       console.log('fn函数被触发');
       info3.age = 100;
     }
     return {
       fn,
       info3,
     }
   }
 ​
 }
 </script>
 ​

ref

由于reactive中只能传入对象类型,因此我们可以使用ref来传入一个基本数据类型,ref()会给我们返回一个可变的响应式对象。我们传入的值是保存在该对象的value属性中的。

 <template>
   <div class="home">
     <button @click="fn">点击</button>
     <h1>{{ age }}</h1>
   </div>
 </template>
 ​
 <script>
 import { reactive, ref, readonly } from "vue";
 import detail from './Detail.vue'
 export default {
   props: ['lz'],
   components: {
     detail
   },
   setup(props, context) {
     //定义响应式变量age
     let age = ref(25);
     
     function fn() {
       console.log('fn函数被触发');
       age.value = 19
     }
 ​
     return {
       fn,
       age
     }
   }
 ​
 }
 </script>
 ​

需要注意的是,我们在template模板中使用,我们不需要[.value],vue会帮助我们自动解包。但是在setup()中,我们还是需要[.value]的,因为不会自动的解包。

模板中的解包是浅层解包,如果ref对象嵌入到对象中了,模板是不会正确解包的。

     //这种情况,我们需要写.value
     <h1>{{ info.age.value }}</h1>
 ​
     let age = ref(19);
     let info = {
       name: 'wang',
       age
     }

如果将ref对象,放入到reactive()中,作为一个属性。此时ref对象是会自动解包的。无论是在template中,还是setup()函数中。

readonly

readonly可以控制对象只读,readonly会返回原生对象的只读代理。

参数:

  1. 普通对象
  2. ref对象
  3. reactive对象
 <template>
   <div class="home">
     <button @click="fn">点击</button>
     <h1>{{ address }}</h1>
     <h1>只读:{{ Raddress }}</h1>
   </div>
 </template>
 ​
 <script>
 import { reactive, ref, readonly } from "vue";
 export default {
   components: {
   },
   setup(props, context) {
      
     let address = ref('北京')
 ​
     let Raddress = readonly(address);
     
     const fn = () => {
       console.log('fn被执行');
       //address.value='南京';  //可以修改
       Raddress.value = '上海'; //不可以修改,Raddress为只读属性
     }
 ​
     return {
       fn,
       address,
       Raddress
     }
   }
 ​
 }
 </script>
 ​

应用:对于向子组件传值,如果我们传递的基本数据类型,当我们在子组件中改变props时,会跑出警告,该值为只读属性。但是如果我们传递的是引用类型的数据,改变其中的某一个属性,vue是检查不到的,因此我们可以使用readonly,对我们的对象进行代理,不允许在子组件中改变props。

如果试图修改,会抛出这样的警告。

image.png

toRefs

当我们使用reactive()创建出来响应式对象时,为了使用方便,我们想通过es6中的解构来解构对象,但是这样解构reactive返回的对象时,我们再来修改对象时,无论是修改解构出来的变量还是修改该对象,值都不会改变。因此我们可以使用toRefs来将reactive返回的对象中的属性都转成ref; 那么我们再次进行结构出来的 name 和 age 本身都是 ref的;

这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化;

示例:

 setup(props, context) {
 ​
     const info = reactive({
       name: '张三',
       age: 21
     });
     
     let { name, age } = toRefs(info);
 ​
     //修改info.name或者name.value,都是响应式改变的
     const fn = () => {
       console.log('fn被执行');
       // name.value = 'lisi';
       info.name = '王五';
       console.log(name);
     }
     return {
       fn,
       info,
       name,
       age
     }

toRef

如果我们指向转换某一个对象属性,可以使用toRef()。

 //参数为:【对象,对象中的属性】
 let name = toRef(info, 'name')

shallowRef

创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。

 const info = shallowRef({
     name: '张三',
     age: 21,
     friends: {
         name: 'lisi'
     }
 });
 const fn = () => {
     console.log('fn被执行');
     //响应式
     info.value = {
         name: '李四'
     };
     //不是响应式
     info.value.name = '王五'
 }

shallowReactive

创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值)。

    const info = shallowReactive({
       name: '张三',
       age: 21,
       friends: {
         name: 'lisi'
       }
     });
     const fn = () => {
       console.log('fn被执行');
       //响应式
       info.name = '李四'
       //不是响应式
       info.friends.name = '王五'
     }

triggerRef

手动执行与 shallowRef 关联的任何作用 (effect)。

意思是:由于shalowRef只会监听.value是否改变,而不会监听.value中的属性的变化。

例如:

 const shallow = shallowRef({
     greet: 'Hello, world'
 })
 ​
 //点击事件执行函数
 const fn = () => {
     console.log('fn被执行');
     //修改greet值,界面不会刷新
     shallow.value.greet = 'feawfe';
     //打印的值会变为 feawfe
     console.log(shallow.value.greet);
     //手动触发对象内部的变化,但是界面中的值不更新的副作用
     triggerRef(shallow);
 }

customRef

创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 tracktrigger 函数作为参数,并且应该返回一个带有 getset 的对象。

实现简单节流的响应数据:

 #userDebounceRef.js文件
 ​
 import {customRef} from "vue";
 export default function (value) {
   let timer = null;
   return customRef((track, trigger) => {
     return {
       get() {
         track();
         return value;
       },
       set(newValue) {
         if (timer) clearTimeout(timer);
         timer = setTimeout(() => {
           value = newValue;
           trigger();
         }, 1000);
       },
     };
   });
 }
 ​
 //组件中使用
 const info = DebounceRef('Hello, world');

watchEffect

规则:

  • 首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖;
  • 其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行;
 //会侦听name和info的变化,name或info变化都会执行该函数
 const stop = watchEffect(() => {
     console.log('watchEffect' + name.value);
     console.log('watchEffect' + info.value);
 })
 //改变info事件函数
 const fn = () => {
     console.log('fn被执行');
     info.value++;
     //info==20时,停止侦听
     if (info.value == 20) {
         stop();
     }
 }
 //改变name事件函数
 const foo = () => {
     name.value = 'lisi'
 }

watchEffect的返回值是一个函数,执行该函数可以停止侦听。

清除副作用

案例:当数据改变时,我们想用清除之前的请求,可以使用回调函数中的onInvalidate函数,清除之前的请求。

    const stop = watchEffect((onInvalidate) => {
       console.log('watchEffect' + name.value);
       console.log('watchEffect' + info.value);
       //清除副作用,当数据改变时,清除之前的请求
       onInvalidate(() => {
         clearTimeout(timer);
       })
       //请求数据
       let timer = setTimeout(() => {
         console.log('请求数据成功');
       }, 1000);
     })

watchEffect执行时机

默认情况下,副作用函数会在组件更新前执行。但是如果我们想要在组件更新之后执行副作用函数,可以使用flush属性,将其设置为post,默认是 pre

例如:在示例中,

默认情况:

默认情况下,titleRef.value会打印两次,分别为null和

这是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null;

而当DOM挂载时,会给titleRef对象赋值新的值,副作用函数会再次执行,打印出来对应的元素;

设为post后:

由于执行setup函数时应该执行一次,但是这次会检测到我们想要它在DOM挂载完之后再执行,因此它推迟了执行,开始渲染DOM。由于渲染DOM时,会给titleRef对象赋值新的值,因此再次触发了副作用函数,由于(Vue 的响应性系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick” 中多个状态改变导致的不必要的重复调用)vue的特性,第一次的副作用函数别覆盖,因此只会执性一次。

 <template>
   <div class="home">
     <p ref="titleRef">年后</p>
   </div>
 </template>
 ​
 <script>
 import { reactive, computed, ref, watchEffect } from "vue";
 import DebounceRef from '../plugins/userDebounceRef.js'
 ​
 export default {
   setup(props, context) {
     //setup函数中使用[ref](相当于this.$refs.titleRef),
     //定义ref对象,导出,然后在标签中使用该变量
     const titleRef = ref(null);
     //
     const stop = watchEffect((onInvalidate) => {
       console.log(titleRef.value);
     }, {
       flush: 'post'
     })
 ​
     return {
       titleRef,
     }
   }
 ​
 }
 </script>
 ​

watch

可以监听的类型:

  1. ref对象
  2. reactive对象
  3. getter函数(必须引用的是可响应式的对象,如reative和ref)
  4. 使用数组同时侦听多个数据源

基本使用:

 let info = reactive({
     name: 'wagn'
 })
 //监听的属性 回调
 watch(info, () => {
     console.log('count改变了');
 })