@vue/composition-api 解讀1
vue-composition-api 是 vue 官方出得, 基于vue3的RFC來(lái)兼容Vue2.x的增強(qiáng)api,具體API和vue-next 是差不多的, 具體的API詳見 vue-composition-api-rfc, 本系列文章是基于你對(duì)vue2有所了解的情況
proxy
vue2的響應(yīng)式是基于Object.defineProperty
的 set
和 get
方法來(lái)實(shí)現(xiàn)的,具體的在源碼中的實(shí)現(xiàn), 這也是組合式API響應(yīng)式的基礎(chǔ)
// src/utils/utils.ts
export function proxy(
target: any,
key: string,
{ get, set }: { get?: Function; set?: Function }
) {
sharedPropertyDefinition.get = get || noopFn
sharedPropertyDefinition.set = set || noopFn
Object.defineProperty(target, key, sharedPropertyDefinition)
}
其中sharedPropertyDefinition
存在get
和set
方法
reactive
reactive
是組合式API提供的一個(gè)實(shí)現(xiàn)對(duì)象響應(yīng)式的API, 使用方法也很簡(jiǎn)單,就是傳入一個(gè)object
setup() {
const state = reactive({
foo: 'foo'
})
return {
state
}
}
源碼中的實(shí)現(xiàn)我們一一來(lái)看
export function reactive<T extends object>(obj: T): UnwrapRef<T> {
if (__DEV__ && !obj) {
warn('"reactive()" is called without provide an "object".')
// @ts-ignore
return
}
if (
!isPlainObject(obj) || // 非原始對(duì)象
isReactive(obj) || // 已是響應(yīng)式
isRaw(obj) || //
!Object.isExtensible(obj) // 對(duì)象不可擴(kuò)展
) {
return obj as any
}
const observed = observe(obj)
// def(obj, ReactiveIdentifierKey, ReactiveIdentifier);
markReactive(obj)
setupAccessControl(observed)
return observed as UnwrapRef<T>
}
其中 observe
是實(shí)現(xiàn)響應(yīng)式的關(guān)鍵踱阿, 我們打開看一看
function observe<T>(obj: T): T {
const Vue = getVueConstructor()
let observed: T
if (Vue.observable) {
observed = Vue.observable(obj)
} else {
const vm = defineComponentInstance(Vue, {
data: {
$$state: obj,
},
})
observed = vm._data.$$state
}
return observed
}
你沒(méi)看錯(cuò),就這么簡(jiǎn)單黎比, 實(shí)際上就是用vue2現(xiàn)成的api來(lái)實(shí)現(xiàn)的 Vue.observable
, else
下面是用來(lái)兼容以前老版本vue沒(méi)有observable
的
markReactive
標(biāo)記 reactive
中的對(duì)象, 實(shí)現(xiàn)方式其實(shí)也是使用Object.defineProperty
來(lái)寫入標(biāo)記值,
其中標(biāo)記都是用Symbol
來(lái)標(biāo)識(shí)唯一性的
ref
ref
是組合式API中另一個(gè)響應(yīng)式api,傳入一個(gè)基本類型參數(shù)
使用
setup() {
const state = ref(1/"hello world"/false/null)
return {
state
}
}
取值方式state.value
颓帝, 如果使用模版方式就不需要使用.value
方式,模版已經(jīng)做了這個(gè)工作瘪板,如果使用jsx
或render
函數(shù)厢拭,就只能手加了
具體實(shí)現(xiàn)
export function ref(raw?: unknown) {
if (isRef(raw)) {
return raw
}
const value = reactive({ [RefKey]: raw })
return createRef({
get: () => value[RefKey] as any,
set: (v) => ((value[RefKey] as any) = v),
})
}
其中在內(nèi)部實(shí)現(xiàn)了組裝了一個(gè)對(duì)象,還是用的reactive
實(shí)現(xiàn)邏輯
關(guān)鍵在createRef
class RefImpl<T> implements Ref<T> {
readonly [_refBrand]!: true
public value!: T
constructor({ get, set }: RefOption<T>) {
proxy(this, 'value', {
get,
set,
})
}
}
export function createRef<T>(options: RefOption<T>) {
// seal the ref, this could prevent ref from being observed
// It's safe to seal the ref, since we really shouldn't extend it.
// related issues: #79
return Object.seal(new RefImpl<T>(options))
}
其中Object.seal
封閉了一個(gè)RefImpl
對(duì)象,使其對(duì)象不能添加其他任何屬性颤殴,已有屬性可以更改,具體的去查Object.seal
的文檔
RefImpl
中 用我們上面寫的響應(yīng)式方法proxy
, 傳入get
和set
來(lái)重寫他的get
和set
方法
官方用ref
的方式來(lái)獲得dom實(shí)例
<template>
<div ref="root"></div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// 在渲染完成后, 這個(gè) div DOM 會(huì)被賦值給 root ref 對(duì)象
console.log(root.value) // <div/>
})
return {
root,
}
},
}
</script>