- 作者:陳大魚頭
- github: KRISACHAN
作為新特性 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
,並返回一個帶有get
和set
屬性的物件。
示例如下:
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 物件。或者傳入一個擁有get
和set
函式的物件,建立一個可手動修改的計算狀態。
示例如下:
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