Vue3之 Composition API 对比Vue2.x用法及注意事项

前言

根据Vue官方 RFC文档来看,Vue3借鉴React Hook的思想,进行了更新。将 2.x 中与组件逻辑相关的选项以 API 函数的形式从新设计。我在看完RFC文档以后,也是感叹Vue3,真香!接下来和你们一块儿分享一下,Vue3的新API的用法,以及对比Vue2.x的优点。vue


基本例子

import { value, computed, watch, onMounted } from 'vue'

const App = {
  template: `
    <div>
      <span>count is {{ count }}</span>
      <span>plusOne is {{ plusOne }}</span>
      <button @click="increment">count++</button>
    </div>
  `,
  setup() {
    // reactive state
    const count = value(0)
    // computed state
    const plusOne = computed(() => count.value + 1)
    // method
    const increment = () => { count.value++ }
    // watch
    watch(() => count.value * 2, val => {
      console.log(`count * 2 is ${val}`)
    })
    // lifecycle
    onMounted(() => {
      console.log(`mounted`)
    })
    // expose bindings on render context
    return {
      count,
      plusOne,
      increment
    }
  }
}

复制代码



API说明

setup() -API入口

setup是一个新的组件选项,也是其余API的入口。setup是在一个组件实例被建立时,初始化了 props 以后调用,其实就是取代了Vue2.x的careted和beforeCreate。它接收porps做为参数。react

const MyComponent = {
  props: {
    name: String
  },
  setup(props) {
    console.log(props.name)
     return {      msg: `hello ${props.name}!` 
    }
}
 template: `<div>{{ msg }}</div>`}复制代码

setup返回一个对象,对象中的属性讲直接暴露给模板渲染的上下文。若是你不return,那么渲染的上下文将没法捕获到你定义的属性。而在Vue2.x中,你定义的属性都会被Vue内部无条件暴露给模板渲染上下文。从性能上来讲,你将能够有选择性地暴露你须要渲染的数据。数组

reactive -数据监听函数

reactive函数接收一个对象做为参数,返回这个对象的响应式代理,等价Vue2.x的Vue.observable()promise

用法:bash

setup() {
        let reactiveData = reactive({name: 'lin', age: 20})
        return {
          reactiveData 
        }
      }复制代码

等价于Vue2.x:app

data(){
    return {
        reactiveData :{name: 'lin', age: 20}    }
}复制代码

对比Vue2.x的observable(),只要是定义在this上的数据,都将进行监听,虽然给咱们带来的便利,可是在大型项目上来讲,性能开销就很大了。Vue3.0以后再也不将主动监听全部的数据,而是将选择权给你,只有经过reactive函数包装过的数据,才有被Vue监听。框架

ref

用法:

ref接收一个原始值,返回一个包装对象,包装对象具备.value属性。经过.value访问这个值。dom

import {ref} from vue

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1复制代码

模板访问

在渲染上下文中使用,Vue帮你自动展开,无须用.value访问。
异步

<template>
  <div>{{ count }}</div>
</template>

<script>
export default {
  setup() {
    return {
      count: ref(0)
    }
  }
}
</script>复制代码

将ref做为对象访问

若是做为对象访问,ref定义的值将自动展开,不用.value访问。async


const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) //  不用state.count.value

state.count = 1
console.log(count.value) // 做为值任然须要经过.value访问复制代码

isRef

检查一个对象是被ref包装过的对象

const unwrapped = isRef(foo) ? foo.value : foo复制代码

ref 和 reactive区别

其实ref至关于reactive的小弟,ref背后也是经过reactive实现的,惟一的区别是ref返回的是包装对象

const count = ref(0)  等价 const count = reactive({value:0})复制代码

为何ref要返回一个包装对象?

关于什么是包装对象,若是不懂的,请看这里

咱们知道在 JavaScript 中,原始值类型如 string 和 number 是只有值,没有引用的。若是在一个函数中返回一个字符串变量,接收到这个字符串的代码只会得到一个值,是没法追踪原始变量后续的变化的。所以,包装对象的意义就在于提供一个让咱们可以在函数之间以引用的方式传递任意类型值的容器。这有点像 React Hooks 中的 useRef —— 但不一样的是 Vue 的包装对象同时仍是响应式的数据源。有了这样的容器,咱们就能够在封装了逻辑的组合函数中将状态以引用的方式传回给组件。组件负责展现(追踪依赖),组合函数负责管理状态(触发更新):相似某act的自定义Hook

setup() {
  const valueA = useLogicA() // valueA 可能被 useLogicA() 内部的代码修改从而触发更新
  const valueB = useLogicB()
  return {
    valueA,
    valueB
  }
}复制代码

ref和reactive须要注意的点:

在setup函数中,若是经过结构返回ref和reactive,那么在模板渲染上下文中,获取不到他们的响应式变化。由于解构他们就意味着copy了他们的引用。因此尽可能不要用解构去返回一个你指望响应式的数据

var App = {  template: `    <div class="container">    <div>    {{name1}}    {{name2}}    </div>    <button @click="add1"> add count</button>      </div>`,  setup() {    const name1 = ref({name1:'我是name'})    const name2 = reactive({name2:'aa'})    const add1 = () => {      console.log(name1.value.name1 = 'test')      console.log(name2.name2 = 'test')    }    return {      count, add1, ...pop,...name1.value,...name2    }  }}复制代码

若是你非要经过解构来使用,你可使用toRefs()来使用

return{
    ...toRefs({name:'name'})
}复制代码



Props 

props对比Vue2.x主要要注意的地方

  1. Vue2.x中props绑定在this上,咱们能够经过this.props.propsName获取对应的值,Vue3.0后Props将变成setup的第一个参数,而setup也是在初始化props以后才被调用。有点像某act的感受。。。
  2. 类型定义的时候,任然能够像Vue2.x同样,同时也支持TS。     
  3. props 对象是响应式的 —— 它能够被看成数据源去观测,当后续 props 发生变更时它也会被框架内部同步更新。但对于用户代码来讲,它是不可修改的(会致使警告)。                                        

    interface IProps{
        name:string
    }
    const MyComponent = {
     
      setup(props:IProps) {
        return {
          msg: `hello ${props.name}!`
        }
      },
      template: `<div>{{ msg }}</div>`
    }复制代码

computed -计算

定义:

计算值的行为跟计算属性 (computed property) 同样:只有当依赖变化的时候它才会被从新计算。类型某act的useCallback useMemo 

computed() 返回的是一个包装对象,它能够和普通的包装对象同样在 setup() 中被返回 ,也同样会在渲染上下文中被自动展开。

用法:

computed能够传两种参数

第一种:直接传一个函数,返回你所依赖的值的计算结果,这个值是个包装对象,默认状况下,若是用户试图去修改一个只读包装对象,会触发警告,说白了就是你只能get没法set

第二种:传一个对象,对象包含get函数和set函数。

总的来讲这两点和Vue2.x相同。

import {computed,reactive} from vue

setup(){
    const count = reactive({count:0})
//第一种
    const computedCount1 = computed(()=>count.count++})
//第二种
    const computedCount2 = computed({
     get: () => count.value + 1,    set: val => { count.value = val - 1 }

computedCount2.value = 1
console.log(computedCount1.value) // 0
})
}复制代码

惟一不一样的是,3.0中,computed 被抽成一个API,直接从vue中获取,而Vue2.x中,computed是一个对象,在对象中定义一个个computed

Vue2.x
var vm = new Vue({
  data: { a: 1 },
  computed: {
    // 仅读取
    aDouble: function () {
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }
})

Vue3.0
import {ref,computed} from Vue
setup(){
    const a = ref(0)
    const b = ref(1)

    const a_computed = computed(()=>a.value++)复制代码
const b_computed = computed({
        get(){return a.value},
        set(val){return a.value+val }
)
}复制代码

readonly

接收一个ref或者reactive包装对象,返回一个只读的响应式对象。

const original = reactive({ count: 0 })

const copy = readonly(original)

watch(() => {
  // works for reactivity tracking
  console.log(copy.count)
})

// mutating original will trigger watchers relying on the copy
original.count++

// mutating the copy will fail and result in a warning
copy.count++ // warning!复制代码

watch

watch() API 提供了基于观察状态的变化来执行反作用的能力。

watch() 接收的第一个参数被称做 “数据源”,它能够是:

  • 一个返回任意值的函数
  • 一个包装对象
  • 一个包含上述两种数据源的数组

第二个参数是回调函数。回调函数只有当数据源发生变更时才会被触发:

这里有一些须要注意的点:

1.若是你不传数据源,只传一个回调函数,Vue会被动监听你回调函数中用到的每个响应式数据。

2.若是你不传数据源,回调函数参数中,没有监听函数的当前值和变化前一次的值

const count = ref(0)
    const count1 = ref(1)
    watch(() => console.log(count.value)) //监听count
    watch(()=>{
        console.log(count.value)
        console.log(count1.value)
}) //监听count和count1复制代码


  • 一个返回任意值的函数
const value = ref(0)   watch((newValue,oldValue)=>value.value,() => {
    
    //监听Value
      console.log(value.value, 'value')    })
复制代码
  • 一个包装对象

这里须要注意的点是:若是你监听reactive包装的数据,不能用这种方法,由于reactive返回的不是一个包装对象。你能够用第一种方法

const count = reactive({count:0})
watch(()=>count.count,()=>{....})   

 const value = ref(0)   
    watch(value,() => {        //监听Value
       console.log(value.value, 'value')   
     })复制代码
  • 一个包含上述两种数据源的数组

这种状况下,任意一个数据源的变化都会触发回调,同时回调会接收到包含对应值的数组做为参数:

const count = ref(0)
 const test = ref(0)
 watch([value,count,()=>test.value],([newValue,newCount,newTest],[oldValue,oldCount,oldTest]) => {    console.log(value.value, 'value') })
复制代码

中止watch

watch自动连接到组件的生命周期,在组件卸载的时候自动中止watch。不一样的是,Vue3.0的watch函数返回一个中止watch的函数,供你手动中止watch

const value = ref(0)
const stop = watch(value,(val,old)=>{.....})

stop()

清理反作用

其实,回调函数还有第三个参数,这个参数是用来注册清理反作用的函数的。熟悉react 的useEffect的同窗就知道,useEffect能够return 一个函数来清理自身的反作用,而Vue3.0是以参数的形式。通常状况下,在生命周期销毁阶段或是你手动stop这个监听函数的状况下,都会自动清理反作用,可是有时候,当观察的数据源变化后,咱们可能须要执行一些异步操做,如setTimeOut,fetch,当这些异步操做完成以前,监测的数据源又发生变化的时候,咱们可能要撤销还在等待的前一个操做,好比clearTimeOut。为了处理这种状况,watcher 的回调会接收到的第三个参数是一个用来注册清理操做的函数。调用这个函数能够注册一个清理函数。清理函数会在下属状况下被调用:
watch(value, (val, oldVal, onCleanup) => {  
    const token = setTimeout(() => console.log(val, '我更新了'), 2000)
    onCleanup(() => {  
         // id 发生了变化,或是 watcher 即将被中止.        
        // 取消还未完成的异步操做。        
        console.log('我是清理函数')        
        clearTimeout(token)      
    })    
})复制代码

那么为何Vue不像React那样,return一个清理反作用的函数,而是经过参数呢?

这是由于,咱们可能这么写watch:

watch(value, async (val, oldVal, onCleanup) => {       const token = await setTimeout(() => console.log(val, '我更新了'), 2000)             onCleanup(() => {        // id 发生了变化,或是 watcher 即将被中止.        // 取消还未完成的异步操做。        console.log('我是清理函数')        clearTimeout(token)      })    })复制代码

async函数隐性地返回一个promise,这样的状况下,咱们是没法返回一个须要被马上注册的清理函数的

控制watch回调调用时机

默认状况下,watch会在组件更新以后调用,若是你想在组件更新前调用,你能够传第三个参数,

第三个参数是个对象,有几个选项

flush  表示回调调用时机

post 默认值,在组件更新以后

pre 组件更新以前

sync 同步调用

deep 深度监听

类型: boolean  default :false

watch(
  () => state.foo,
  foo => console.log('foo is ' + foo),
  {    flush: 'post', // default, fire after renderer flush
    flush: 'pre', // fire right before renderer flush
    flush: 'sync' // fire synchronously
  })复制代码

和Vue2.x行为一致,都是对对象的深度监听

const count1 = reactive({count:{count:0}})    watch(()=>count1.count, (val,oldVal)=>{      console.log(count1,"count1")    },{      deep:true    })    const add1 = ()=>{      count1.count.count = Math.random()    }复制代码


Lazy - 和Vue2.ximmediate 正好相反

type:Boolean, default:false

const count1 = reactive({count:{count:0}})    watch(()=>count1.count, (val,oldVal)=>{      console.log(count1,"count1")    },{      lazy:true    })复制代码

onTrack和 onTrigger

debugger钩子函数,分别在依赖追踪和依赖发生变化时调用。

相关文章
相关标签/搜索