讓Vue3 Composition API 存在於你 Vue 以外的專案中

陳大魚頭發表於2020-12-14

作為新特性 Composition API ,在 Vue3 正式釋出之前一段時間就釋出過了。

距文件介紹, Composition API 是一組低侵入式的、函式式的 API,使得我們能夠更靈活地「組合」元件的邏輯。

不僅在 Vue 中,在其他的框架或原生 JS 也可以很好地被使用,下面我們就選取幾個比較重要的 Composition API ,通過一些簡單的例子來看看如何在其他專案中使用。

注:本文僅列出各個分類下比較重要的 API,想要檢視全部可以點選下方連結進行檢視:

https://github.com/vuejs/vue-...

reactive API

createReactiveObject

createReactiveObject 函式是 reactive API 的核心,用於建立 reactive 物件 。

在分享 API 之前,我們先看看其核心實現,由於篇幅有限,本文僅展示出理解後的簡化版程式碼,程式碼如下:

function createReactiveObject(
  target, // 要監聽目標
  isReadonly, // 是否只讀
  baseHandlers, // target 為 Object 或 Array 時的處理器,支援對資料的增刪改查
  collectionHandlers // target 為 Map/WeakMap 或 Set/WeakSet 時的處理器,支援對資料的增刪改查
) {
    if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) {
      // 當 target 已經是一個 Proxy 時,直接返回
      // 例外情況:在 Proxy 裡呼叫 readonly()
        return target
    }
    // 當前物件已被監聽過時,就直接返回被監聽的物件
    if (existingProxy) {
      return existingProxy
    }
    // 如果是 Object Array Map/WeakMap Set/WeakSet 以外只能監聽到值的資料,直接返回
    if (targetType === TargetType.INVALID) {
      return target
    }
    // 根據引數型別生成對應的 Proxy 物件,以及新增對應的處理器
    const proxy = new Proxy(
      target,
      targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
    )
    proxyMap.set(target, proxy)
    return proxy
}

reactive

接收一個普通物件然後返回該普通物件的響應式代理。等同於 2.x 的 Vue.observable()

示例如下:

import {
  reactive,
  isReactive // 判斷是否是 reactive 物件
} from 'https://unpkg.com/@vue/reactivity/dist/reactivity.esm-browser.js'
const obj = {
  nested: {
    foo: 1
  },
  array: [{ bar: 2 }]
}
const value = 10

const observedObj = reactive(obj)
const observedValue = reactive(value)

console.log(isReactive(observedObj)) // true
console.log(isReactive(observedValue)) // true

shallowReactive

只為某個物件的私有(第一層)屬性建立淺層的響應式代理,不會對“屬性的屬性”做深層次、遞迴地響應式代理,而只是保留原樣。

示例如下:

const obj = {
  nested: {
    foo: 1
  },
  array: [{ bar: 2 }]
}
const value = 10

const unobservedObj = shallowReactive(obj)
const unobservedValue = shallowReactive(value)

console.log(isReactive(observedObj)) // false
console.log(isReactive(observedValue)) // false

effect API

createReactiveEffect

createReactiveEffect 是 effect API 的核心,用於建立監聽使用者自定義的 reactive 物件的函式

在分享 API 之前,我們先看看其核心實現,由於篇幅有限,本文僅展示出理解後的簡化版程式碼,程式碼如下:

function createReactiveEffect(
    fn, // 使用者建立的 reactive 物件變動執行回撥
  options = {
    lazy, // 是否執行使用者函式
    scheduler, // 收集資料記錄
    onTrack, // 追蹤使用者資料的回撥
    onTrigger, // 追蹤變更記錄
    onStop, // 停止執行
    allowRecurse // 是否允許遞迴
  }
) {
    const effect = function reactiveEffect() {
      if (!effectStack.includes(effect)) {
        cleanup(effect) // 清空 effect
        try {
          enableTracking() // 往追蹤使用者資料的棧內新增當前 effect
          effectStack.push(effect) // 往 effect 棧內新增 effect
          activeEffect = effect // 將活動 effect 變成當前 effect
          return fn() // 執行回撥
        } finally { // 刪除當前記錄
          effectStack.pop()
          resetTracking()
          activeEffect = effectStack[effectStack.length - 1]
        }
      }
    }
        effect.id = uid++
    effect._isEffect = true
    effect.active = true
    effect.raw = fn
    effect.deps = []
    effect.options = options
    return effect
}

effect

effect 函式是 effect API 的核心。以 WeakMap 為資料型別,是一個用於儲存使用者自定義函式的 訂閱者

示例如下:

let dummy
const counter = reactive({ num: 0 })
effect(() => (dummy = counter.num))
console.log(dummy === 0) // true
counter.num = 7
console.log(dummy === 7) // true

ref API

RefImpl

RefImpl 是 ref API 的核心,用於建立 ref 物件

在分享 API 之前,我們先看看其核心實現,程式碼如下:

class RefImpl {
  private _value // 儲存當前 ref 物件的值

  public __v_isRef = true // 確定是否為 ref 物件的變數 (只讀)

  constructor(
    private _rawValue, // 使用者傳入的原始值
    public readonly _shallow = false // 當前 ref 物件是否為 shallowRef
  ) {
    // convert:如果傳入的原始值為物件,則會被 convert 函式轉換為 reactive 物件
    this._value = _shallow ? _rawValue : convert(_rawValue)
  }

  get value() {
    // 用於追蹤使用者輸入的值變化
    // track:effect api 的 track 函式,用於追蹤使用者行為,當前則是追蹤使用者的 get 操作
    // toRaw:effect api 的 toRaw 函式,將 this 轉化為使用者輸入值
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      // 當前 ref 物件有變化時
      // _rawValue / _value 變更
      // trigger:effect api 的 trigger 函式,根據使用者傳入的值與操作型別來進行操作,當前則為將使用者傳入的值新增到值 map 裡
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
    }
  }
}

ref

接受一個引數值並返回一個響應式且可改變的 ref 物件。ref 物件擁有一個指向內部值的單一屬性 .value。如果傳入 ref 的是一個物件,將呼叫 reactive 方法進行深層響應轉換。

示例如下:

const count = ref({
  name: '魚頭',
  type: '帥哥'
})
console.log(count.value.type) // 帥哥
count.value.type = '超級大帥哥'
console.log(count.value.type) // 超級大帥哥

shallowRef

建立一個 ref ,將會追蹤它的 .value 更改操作,但是並不會對變更後的 .value 做響應式代理轉換(即變更不會呼叫 reactive

示例如下:

const __shallowRef = shallowRef({ a: 1 })
let dummy
effect(() => {
  dummy = __shallowRef.value.a
})
console.log(dummy) // 1

__shallowRef.value.a = 2
console.log(dummy) // 1
console.log(isReactive(__shallowRef.value)) // false

customRef

customRef 用於自定義一個 ref,可以顯式地控制依賴追蹤和觸發響應,接受一個工廠函式,兩個引數分別是用於追蹤的 track 與用於觸發響應的 trigger,並返回一個帶有 getset 屬性的物件。

示例如下:

let value = 1
let _trigger

const custom = customRef((track, trigger) => ({
  get() {
    track()
    return value
  },
  set(newValue) {
    value = newValue
    _trigger = trigger
  }
}))

let dummy
effect(() => {
  dummy = custom.value
})
console.log(dummy) // 1

custom.value = 2
console.log(dummy) // 1

_trigger()
console.log(dummy) // 2

triggerRef

triggerRef 用於主動觸發 shallowRef

示例如下:

const __shallowRef = shallowRef({ a: 1 })
let dummy
effect(() => {
  dummy = __shallowRef.value.a
})
console.log(dummy) // 1

__shallowRef.value.a = 2
console.log(dummy) // 1
console.log(isReactive(__shallowRef.value)) // false

triggerRef(__shallowRef)
console.log(dummy) // 2

computed API

ComputedRefImpl

ComputedRefImpl 是 ref API 的核心,用於建立 computed 物件

在分享 API 之前,我們先看看其核心實現,由於篇幅有限,本文僅展示出理解後的簡化版程式碼,程式碼如下:

class ComputedRefImpl {
  private _value // 當前值
  private _dirty = true // 當前值是否發生過變更

  public effect // effect 物件

  public __v_isRef = true; // 指定為 ref 物件
  public [ReactiveFlags.IS_READONLY]: boolean // 是否只讀

  constructor(
    getter, // getter
    private _setter, // setter
    isReadonly // 是否只讀
  ) {
    this.effect = effect(getter, {
      lazy: true,
      scheduler: () => {
        if (!this._dirty) {
          // 將變更狀態變為 true
          // trigger:effect api 的 trigger 函式,根據使用者傳入的值與操作型別來進行操作,當前則為將使用者傳入的值新增到值 map 裡
          this._dirty = true
          trigger(toRaw(this), TriggerOpTypes.SET, 'value')
        }
      }
    })

    this[ReactiveFlags.IS_READONLY] = isReadonly
  }

  get value() {
    if (this._dirty) {
      // 返回當前值
      // 將變更狀態變為 false
      this._value = this.effect()
      this._dirty = false
    }
       // track:effect api 的 track 函式,用於追蹤使用者行為,當前則是追蹤使用者的 get 操作
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newValue) {
    this._setter(newValue)
  }
}

computed

傳入一個 getter 函式,返回一個預設不可手動修改的 ref 物件。或者傳入一個擁有 getset 函式的物件,建立一個可手動修改的計算狀態。

示例如下:

const count1 = ref(1)
const plus1 = computed(() => count1.value + 1)
console.log(plus1.value) // 2
plus1.value++ // Write operation failed: computed value is readonly

const count2 = ref(1)
const plus2 = computed({
  get: () => count2.value + 1,
  set: val => {
    count2.value = val - 1
  }
})
console.log(plus2.value) // 2
plus2.value = 0
console.log(plus2.value) // 0

相關文章