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鉤子函數,分別在依賴追蹤和依賴發生變化時調用。

相關文章
相關標籤/搜索