@vue/composition-api 解讀1
vue-composition-api 是 vue 官方出得, 基于vue3的RFC來兼容Vue2.x的增強(qiáng)api,具體API和vue-next 是差不多的, 具體的API詳見 vue-composition-api-rfc, 本系列文章是基于你對vue2有所了解的情況
proxy
vue2的響應(yīng)式是基于Object.defineProperty 的 set 和 get方法來實(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)對象響應(yīng)式的API, 使用方法也很簡單,就是傳入一個(gè)object
setup() {
const state = reactive({
foo: 'foo'
})
return {
state
}
}
源碼中的實(shí)現(xiàn)我們一一來看
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) || // 非原始對象
isReactive(obj) || // 已是響應(yīng)式
isRaw(obj) || //
!Object.isExtensible(obj) // 對象不可擴(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
}
你沒看錯(cuò),就這么簡單, 實(shí)際上就是用vue2現(xiàn)成的api來實(shí)現(xiàn)的 Vue.observable, else下面是用來兼容以前老版本vue沒有observable的
markReactive 標(biāo)記 reactive中的對象, 實(shí)現(xiàn)方式其實(shí)也是使用Object.defineProperty來寫入標(biāo)記值,
其中標(biāo)記都是用Symbol來標(biāo)識唯一性的
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è)對象,還是用的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對象,使其對象不能添加其他任何屬性,已有屬性可以更改,具體的去查Object.seal的文檔
RefImpl中 用我們上面寫的響應(yīng)式方法proxy, 傳入get和set來重寫他的get和set方法
官方用ref的方式來獲得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 會被賦值給 root ref 對象
console.log(root.value) // <div/>
})
return {
root,
}
},
}
</script>