📟Vue3最全 Composition API

128 阅读5分钟

Composition API

1.判断API

  • isProxy

    • 检查对象是否由reactive或readonly创建的proxy
  • isReactive

    • 检查对象是否是由reactive创建的响应式代理;
    • 如果该代理是readonly,但包裹了由reactive创建另一个代理,它也会返回true
  • isReadonly

    • 检查对象是否由readonly创建的只读代理
  • toRaw

    • 返回reactive或readonly代理的原始对象(不建议保留对原始对象的持久引用,请谨慎使用!!!)
  • shallowReactive

    • 创建一个响应式代理,它跟踪其自身property的响应式,但不执行嵌套对象的深层响应式转换(深层还是原生对象)
  • shallowReadonly

    • 创建一个proxy,使其自身的property为只读,但不执行嵌套对象的深度只读转换(深层还是可读可写)

2.toRefs

  • 如果使用对象结构对reactive返回的对象进行获取值,然后再进行修改,数据就不是响应式了
  • 那么toRefs就是Vue给我们提供的函数,可以将reactive返回的对象中的属性都转成ref;
  • 那么再次进行结构的话就是ref
  • 这种做法其实相当于在数据之间创立立链接,任何一个修改都会引起另外一个变化
<template>
  <div>
    <h2>{{info.name}}</h2>
    <button @click="changeAge">点我修改</button>
  </div>
</template>
<script>
  import {reactive,toRefs} from "vue";
  export default {
    setup() {
      //直接进行结构不是响应式的咯
      //let {name,age} = reactive({name:"Tim",age:18})
      
      const info = reactive({name:"阿基米德",age:100})
      let {name,age} = toRefs(info)
      //它其实是这样的
      //toRefs({name:ref,age:ref}) 所以结构出来就可以是响应式的用了toRefs之后。
      const changeAge = () => {
        age.value++
      }
      //如果修改了info的age也是会使结构出来的值改变
      const changeAge = () => {
        info.age++
      }
      
      
      
      return {
        info,
        age,
        name
      }
    }
  }
</script>

3.toRef

  • 如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法:
<template>
  <div>
    <h2>{{name}}</h2>
    <button @click="changeAge">点我修改</button>
  </div>
</template>
<script>
  import {reactive,toRef} from "vue";
  export default {
    setup() {
      const info = reactive({name:"阿基米德",age:100})
      
      let name = toRef(info,"name")
      
      const changeAge = () => {
        info.name = "伽利略"
      }
      
      return {
        info,
        name
      }
    }
  }
</script>

4.补充API

  • unref

  • 如果我们想要获取一个ref引用中的value,那么可以通过unref方法:

    • 如果参数是一个ref,则返回内部值,否则返回参数本身
    • 这是val = isRef(val) ? val.value : val 的语法糖函数;
<script>
  export default {
    setup() {
      const name = ref("tim")
      foo(name)
      function foo(bar) {
        unref(bar) //val = isRef(val) ? val.value : val
      }
    }
  }
</script>
  • shalloRef与triggerRef
<script>
  export default {
    setup() {
      const info = shallowRef({name:"Tim"}) //浅层的ref
      
      const changeInfo = () => {
        info.value.name = "Yang" //直接修改shallowRef的值是不行滴🙅
        triggerRef(info);//手动触发 传入的值是ref
      }
    }
  }
</script>

5.customRef

customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 tracktrigger 两个函数作为参数,并返回一个带有 getset 方法的对象。

一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。

  • 下面用一个案例来学习学习
<template>
  <input v-model="message" />
  <p>{{message}}</p>
</template>
<script>
  import debounceRef from "./hook/cstdebounceRef"
  export default {
    setup() {
      const message = debounceRef("hello world")
      
      return {
        message
      }
    }
  }
</script>
<style scoped></style>
  • 定义hook
//自定义ref
import {customRef} from "vue"
export default function(value,delay=1000) {
  let timer = null
  return customRef((track,trigger) => {
    return {
      get() {
        track(); //收集对应依赖
        return value;
      },
      set(newValue) {
        clearTimeout(timer)
        timer = setTimeout(()=> {
            value = newValue
            trigger()
        },delay)
      }
    }
  })
}

6.computed

  • 在前面我们讲解过计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理

    • 我们可以在 setup 函数中使用 computed 方法来编写一个计算属性;
  • 如何使用computed呢?

  1. 接收一个getter函数,并为getter函数返回值,返回一个不变的ref对象
  2. 接收一个具有get和set的对象,返回一个可变(可读写)ref对象;
<template>
  <div>
    <h2>{{fullName}</h2>
    <button @click="changeName">修改firstName</button>
  </div>
</template><script>
  import {ref,computed} from "vue"
  export default {
    setup() {
      const firstName = ref("yang")
      condt lastName = ref("tim")
      //第一种方式传入一个getter函数
      //返回值是一个ref对象
      //const fullName = computed(()=>firstName.value + lastName.value)
      //第二种传入对象设置get和set
      const fullName = computed({
        get:() => fristName.value + " " + lastName.value,
        set(newValue){
          const names = newValue.split(" ")
          firstName.value = names[0],
          lastName.value = names[1]
          
        }
      })
      
      const changeName = () => {
        fullName.value = "kobe Bryant"
      }
      
      
      return {
       fullName
      }
    }
  }
</script>

7.watchEffect

  • watchEffect和watch都是用来完成响应式数据监听的

    • watchEffect用于自动收集响应式数据依赖
    • watch需要手动指定侦听的数据
  • watchEffect传入的函数会被立即执行一次,并且收集执行过程中的依赖

  • watchEffect只有在收集的依赖发生变化时,watchEffect传入的函数才会被再次执行

  • watchEffect的停止侦听(了解)

    • 如果要停止监听,就用stop接收,watchEffect返回函数任何进行判断
  • watchEffect清除副作用(了解)

    • 如果在开发中需要侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了
    • 那么上一次的网络请求需要被取消,那么就要清除上一次的副作用
    • 在给watchEffect传入的函数被回调时,可以获取到一个参数:onInvalidate(随便起的名字)
    • 可以在这个函数中操作清除的副作用
<template>
  <div>
    <h2>{{name}}---{{age}}</h2>
    <button @click="changeName"></button>
    <button @click="changeAge"></button>
  </div>
</template>
<script>
  import {ref,watchEffect} from "vue"
  export default {
    setup() {
      const name = ref("tim")
      const age = ref(21)
      
      const changeName =() => {
        name.value = "kobe"
      }
     
      //立即执行一次
      const stop = watchEffect((onInvalidate)=>{
        const timer = setTimeout(() => {
          console.log("网络请求成功")
        },2000)
        //清除副作用
        onInvalidate(()=> {
          clearTimeout(timer)
        })
        console.log(name.value,age.value)
      })
      
      //停止监听,如果岁数超过三十岁,就停止监听
      const changeAge =() => {
        age.value++
        if(age.value > 30) {
          stop()
        }
      }
      
      return {
        name,age,changeName,changeAge
      }
    }
  }
</script>

8.watchEffect执行机制

  • 在默认情况下,组件的更新会在副作用函数执行之前

    • 如果我们下午在副作用函数中获取到元素,是否可行
<template>
  <div>
    <h2 ref="title">执行机制</h2>
  </div>
</template><script>
  import { ref, watchEffect } from 'vue';
​
  export default {
    setup() {
      const title = ref(null);
​
      watchEffect(() => {
        console.log(title.value);
      })
​
      return {
        title
      }
    }
  }
</script><style scoped></style>
  • 打印出来会有两次,一次是null一次是元素

    • 因为setup函数在执行时就会立即执行传入的副作用函数,这时,DOM并没有被挂载,所以打印出来为null
    • 而当DOM挂载时,会给title的ref对象赋值新的值,副作用函数会再次执行,打印出来对应的元素
  • 如果希望第一次时就打印出来对应的元素而不是null应该咋办啊

    • 需要改变副作用函数的执行时机,
    • 它的默认值是pre,会在元素挂载或者更新之前执行
    • 改一下这个默认值就可以了有pre,post,sync但是最好不要使用sync
<template>
  <div>
    <h2 ref="title">哈哈哈</h2>
  </div>
</template><script>
  import { ref, watchEffect } from 'vue';
​
  export default {
    setup() {
      const title = ref(null);
​
      watchEffect(() => {
        console.log(title.value);
      }, {
        //改变这个默认值:pre为post挂载后使用watchEffect
        flush: "post"
      })
​
      return {
        title
      }
    }
  }
</script><style scoped></style>

9.watch

  • watch的API完全等同于组件watch选项的Property:

    • watch需要侦听特定的数据源,并在回调函数中执行副作用;

    • 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;

      • 与watchEffect的比较:

        • 懒执行副作用(第一次不会直接执行)
        • 更具体的说明当哪些状态发生变化时,触发侦听器的执行;
        • 访问侦听状态变化前后的值
  • 监听单个数据源:

    • watch侦听函数的数据源有两种类型:

      • 一个getter函数:但是该getter函数必须引用可响应式对象(reactive|ref)
      • 直接写入一个可响应式对象,ref|reactive
<template>
  <button @click="changeData">
    监听watch修改
  </button>
</template><script>
  import {ref,reactive,watch} from "vue";
  export default {
    setup() {
      const info = reactive({name:"Tim",age:21})
      
      // 1.侦听watch时,传入一个getter函数
      watch(()=>info.name,(newValue,oldValue) => {
        console.log(newValue)
        console.log(oldValue)
      })
      
      // 2.传入一个可响应式对象: reactive对象/ref对象
      // 情况一: reactive对象获取到的newValue和oldValue本身都是reactive对象
      // watch(info, (newValue, oldValue) => {
      //   console.log("newValue:", newValue, "oldValue:", oldValue);
      // })
      // 如果希望newValue和oldValue是一个普通的对象
      
      // 情况二: ref对象获取newValue和oldValue是value值的本身
      // const name = ref("why");
      // watch(name, (newValue, oldValue) => {
      //   console.log("newValue:", newValue, "oldValue:", oldValue);
      // })
      
      // 如果希望newValue和oldValue是一个普通的对象
      watch(() => {
        return {...info}
      }, (newValue, oldValue) => {
        console.log("newValue:", newValue, "oldValue:", oldValue);
      })
      
      
      const changeData = () => {
        info.name = "Yang"
      }
      
      return {
        info,changeData
      }
      
      
    }
  }
</script>
  • 监听多个数据源

    • 侦听器还可以使用数组同时侦听多个源:
<template>
  <div>
    <h2 ref="title">{{info.name}}</h2>
    <button @click="changeData">修改数据</button>
  </div>
</template><script>
  import { ref, reactive, watch } from 'vue';
​
  export default {
    setup() {
      // 1.定义可响应式的对象
      const info = reactive({name: "tim", age: 18});
      const name = ref("tim");
​
      // 2.侦听器watch
      watch([info, name], ([newInfo, newName], [oldInfo, oldName]) => {
        console.log(newInfo, newName, oldInfo, oldName);
      })
​
      const changeData = () => {
        info.name = "kobe";
      }
​
      return {
        changeData,
        info
      }
    }
  }
</script><style scoped></style>

10.watch选项

  • 如果我们希望侦听一个深层的侦听,那么依然需要设置 deep 为true:

    • 也可以传入 immediate 立即执行;
<template>
  <div>
    <h2 ref="title">{{info.name}}</h2>
    <button @click="changeData">修改数据</button>
  </div>
</template><script>
  import { ref, reactive, watch } from 'vue';
​
  export default {
    setup() {
      // 1.定义可响应式的对象
      const info = reactive({
        name: "tim", 
        age: 18,
        friend: {
          name: "kobe"
        }
      });
​
      // 2.侦听器watch
      watch(() => ({...info}), (newInfo, oldInfo) => {
        console.log(newInfo, oldInfo);
      }, {
        deep: true,
        immediate: true
      })
​
      const changeData = () => {
        info.friend.name = "james";
      }
​
      return {
        changeData,
        info
      }
    }
  }
</script><style scoped></style>

11.总结

  • 这篇文章是续写上一篇文章的东西,主要还是总结了一些选项式API的东西,vue官网的一些不经常使用的也在这里说明如何使用这些不常用的点。