Vue2如何支持composition API示例詳解
前言
自從 Vue3
發(fā)布之后,composition API
這個(gè)詞走入寫 Vue
同學(xué)的視野之中,相信大家也一直聽(tīng)到 composition API
比之前的 options API
有多好多強(qiáng),如今由于 @vue/composition-api
插件的發(fā)布,Vue2
的同學(xué)也可以上車咯,接下來(lái)我們主要以響應(yīng)式的 ref
和 reactive
來(lái)深入分析一下,這個(gè)插件是怎么實(shí)現(xiàn)此功能的。
如何使用
// 入口文件引入并注冊(cè) import Vue from 'vue' import VueCompositionAPI from '@vue/composition-api' Vue.use(VueCompositionAPI)
// vue文件使用 import { defineComponent, ref, reactive } from '@vue/composition-api' export default defineComponent({ setup () { const foo = ref('foo'); const obj = reactive({ bar: 'bar' }); return { foo, obj } } })
怎么樣,看完是不是感覺(jué)和 vue3
一模一樣,你可能會(huì)想:
- 這是
vue2
啊,我之前的data
、methods
里面也有變量和方法,怎么做到跟setup
返回值打通合并在一起的。 vue2
不是只有定義在data
里面的數(shù)據(jù)才會(huì)被處理成響應(yīng)式的嗎?ref
和reactive
是怎么做到的呢?vue2
響應(yīng)式數(shù)據(jù)定義的約束(添加賦值原對(duì)象沒(méi)有的屬性,數(shù)組下標(biāo)修改等),改用ref
和reactive
就沒(méi)問(wèn)題嗎?
當(dāng)然還有很多的疑惑,因?yàn)椴寮峁┑?API
相當(dāng)多,覆蓋了絕大部分 Vue3
所擁有的,這里主要從這幾個(gè)問(wèn)題來(lái)分析一下是如何做到的。
原理解析
得益于 Vue
的插件系統(tǒng),@vue/composition-api
像 vue-router
、vuex
一樣也是通過(guò)官方提供的插件式來(lái)注入。
// 這里只貼跟本章要講的相關(guān)代碼 funciton mixin (Vue) { Vue.mixin({ beforeCreate: functionApiInit } } function install (Vue) { mixin(Vue); } export const Plugin = { install: (Vue: VueConstructor) => install(Vue), }
Vue
插件就是向外面暴露一個(gè) install
的方法,當(dāng)調(diào)用 use
的時(shí)候會(huì)調(diào)用該方法,并把 Vue
構(gòu)造函數(shù)作為參數(shù)傳入,然后調(diào)用 Vue.mixin
混入對(duì)應(yīng)鉤子時(shí)要處理的函數(shù)。
接下來(lái)主要看下 functionApiInit
做了什么
function functionApiInit(this: ComponentInstance) { const vm = this const $options = vm.$options const { setup, render } = $options // render 相關(guān) const { data } = $options $options.data = function wrappedData() { initSetup(vm, vm.$props) return isFunction(data) ? ( data as (this: ComponentInstance, x: ComponentInstance) => object ).call(vm, vm) : data || {} }
因?yàn)?Vue
在 beforeCreated
和 created
生命周期之間,會(huì) initState
對(duì)數(shù)據(jù)進(jìn)行處理,其中對(duì) data
的處理時(shí)就會(huì)調(diào)用 $options.data
拿到定義的數(shù)據(jù),所以這里重新對(duì)該函數(shù)其包裹一層,這也是為什么要選擇 beforeCreate
鉤子注入的一個(gè)原因,必須在該函數(shù)調(diào)用前進(jìn)行包裹。 接下來(lái)看 initSetup
都做了什么
function initSetup(vm: ComponentInstance, props: Record<any, any> = {}) { const setup = vm.$options.setup! const ctx = createSetupContext(vm) const instance = toVue3ComponentInstance(vm) instance.setupContext = ctx def(props, '__ob__', createObserver()) resolveScopedSlots(vm, ctx.slots) let binding: ReturnType<SetupFunction<Data, Data>> | undefined | null activateCurrentInstance(instance, () => { binding = setup(props, ctx) }) // setup返回是函數(shù)的情況 需要重寫render函數(shù) const bindingObj = binding Object.keys(bindingObj).forEach((name) => { let bindingValue: any = bindingObj[name] // 數(shù)據(jù)處理 asVmProperty(vm, name, bindingValue) }) return } }
這個(gè)函數(shù)比較長(zhǎng),不在本次要講解的主線上代碼邏輯都刪除了,這個(gè)函數(shù)主要是創(chuàng)建了 ctx
和把 vm
實(shí)例轉(zhuǎn)換成 Vue3
數(shù)據(jù)類型定義的 instance
,然后執(zhí)行 setup
函數(shù)得到返回值,然后遍歷每個(gè)屬性,調(diào)用 asVmProperty
掛載到 vm
上面,當(dāng)然這里的掛載不是直接通過(guò)把屬性和值添加到 vm
上面,這么做會(huì)有一個(gè)問(wèn)題,就是后續(xù)對(duì)該屬性的修改不能同步到 vm
中,這里采用的還是 Vue
最常見(jiàn)的數(shù)據(jù)代理。
export function asVmProperty( vm: ComponentInstance, propName: string, propValue: Ref<unknown> ) { const props = vm.$options.props if (!(propName in vm) && !(props && hasOwn(props, propName))) { if (isRef(propValue)) { proxy(vm, propName, { get: () => propValue.value, set: (val: unknown) => { propValue.value = val }, }) } else { proxy(vm, propName, { get: () => { if (isReactive(propValue)) { ;(propValue as any).__ob__.dep.depend() } return propValue }, set: (val: any) => { propValue = val }, }) } }
看到這里,相信你已經(jīng)明白了在 setup
中定義返回的為什么能夠在 template
、 data
、 methods
等之中去使用了,因?yàn)榉祷氐臇|西都已經(jīng)被代理到 vm
之上了。
響應(yīng)式( ref reactive 的實(shí)現(xiàn))
接下來(lái)我們來(lái)說(shuō)說(shuō)響應(yīng)式相關(guān)的,為什么 ref
和 reactive
也可以讓數(shù)據(jù)成為響應(yīng)式的。
ref
的實(shí)現(xiàn)其實(shí)是對(duì) reactive
再次封裝,主要用來(lái)給基本類型使用。
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), }) }
因?yàn)?reactive
接受的必須是一個(gè)對(duì)象,所有這里使用了一個(gè)常量作為 ref
的 key
, 也就是
const value = reactive({ "composition-api.refKey": row })
export function createRef<T>( options: RefOption<T>, isReadonly = false, isComputed = false ): RefImpl<T> { const r = new RefImpl<T>(options) const sealed = Object.seal(r) if (isReadonly) readonlySet.set(sealed, true) return sealed } export class RefImpl<T> implements Ref<T> { readonly [_refBrand]!: true public value!: T constructor({ get, set }: RefOption<T>) { proxy(this, 'value', { get, set, }) } }
通過(guò) new RefImpl
實(shí)例,該實(shí)例上有一個(gè) value
的屬性,對(duì) value
做代理,當(dāng)取值的時(shí)候返回 value[RefKey]
,賦值的時(shí)候賦值給 value[RefKey]
, 這就是為什么 ref
可以用在基本類型,然后對(duì)返回值的 .value
進(jìn)行操作。調(diào)用 object.seal
是把對(duì)象密封起來(lái)(會(huì)讓這個(gè)對(duì)象變的不能添加新屬性,且所有已有屬性會(huì)變的不可配置。屬性不可配置的效果就是屬性變的不可刪除,以及一個(gè)數(shù)據(jù)屬性不能被重新定義成為訪問(wèn)器屬性,或者反之。但屬性的值仍然可以修改。)
我們主要看下 reactive
的實(shí)現(xiàn)
export function reactive<T extends object>(obj: T): UnwrapRef<T> { const observed = observe(obj) setupAccessControl(observed) return observed as UnwrapRef<T> } export function observe<T>(obj: T): T { const Vue = getRegisteredVueOrDefault() let observed: T if (Vue.observable) { observed = Vue.observable(obj) } else { const vm = defineComponentInstance(Vue, { data: { $$state: obj, }, }) observed = vm._data.$$state } return observed }
我們通過(guò) ref
或者 reactive
定義的數(shù)據(jù),最終還是通過(guò)了變成了一個(gè) observed
實(shí)例對(duì)象,也就是 Vue2
在對(duì) data
進(jìn)行處理時(shí),會(huì)調(diào)用 observe
返回的一樣,這里在 Vue2.6+
把 observe
函數(shù)向外暴露為 Vue.observable
,如果是低版本的話,可以通過(guò)重新 new
一個(gè) vue
實(shí)例,借助 data
也可以返回一個(gè) observed
實(shí)例,如上述代碼。
因?yàn)樵?reactive
中定義的數(shù)據(jù),就如你在 data
中定義的數(shù)據(jù)一樣,都是在操作返回的 observed
,當(dāng)你取值的時(shí)候,會(huì)觸發(fā) getter
進(jìn)行依賴收集,賦值時(shí)會(huì)調(diào)用 setter
去派發(fā)更新, 只是定義在 setup
中,結(jié)合之前講到的 setup
部分,比如當(dāng)我們?cè)?template
中訪問(wèn)一個(gè)變量的值時(shí),vm.foo
-> proxy
到 setup
里面的 foo
-> observed
的 foo
,完成取值的流程,這會(huì)比直接在 data
上多代理了一層,因此整個(gè)過(guò)程也會(huì)有額外的性能開(kāi)銷。
因此使用該 API
也不會(huì)讓你可以直接規(guī)避掉 vue2
響應(yīng)式數(shù)據(jù)定義的約束,因?yàn)樽罱K還是用 Object.defineProperty
去做對(duì)象攔截,插件同樣也提供了 set API
讓你去操作對(duì)象新增屬性等操作。
總結(jié)
通過(guò)上面的了解,相信你一定對(duì)于 Vue2
如何使用 composition API
有了一定的了解,因?yàn)?API
相當(dāng)多, 響應(yīng)式相關(guān)的就還有 toRefs、toRef、unref、shallowRef、triggerRef
等等,這里就不一一分析,有興趣的可以繼續(xù)看源碼的實(shí)現(xiàn)。
寫 Vue2
的同學(xué)也可以不用羨慕寫 Vue3
的同學(xué)了,直接引入到項(xiàng)目就可以使用起來(lái),雖然沒(méi)有 vue3
那么好的體驗(yàn),但是絕大部分場(chǎng)景還是相同的,使用時(shí)注意 README
文檔最后的限制章節(jié),里面講了一些使用限制。
以上就是Vue2如何支持composition API示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Vue 支持composition API的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue實(shí)現(xiàn)復(fù)制文字復(fù)制圖片實(shí)例詳解
這篇文章主要為大家介紹了vue實(shí)現(xiàn)復(fù)制文字復(fù)制圖片實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02vue中使用moment設(shè)置倒計(jì)時(shí)的方法
這篇文章給大家介紹了vue中使用moment設(shè)置倒計(jì)時(shí)的方法,文中通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-02-02vue中實(shí)現(xiàn)拖拽排序功能的詳細(xì)教程
在業(yè)務(wù)中列表拖拽排序是比較常見(jiàn)的需求,下面這篇文章主要給大家介紹了關(guān)于vue中實(shí)現(xiàn)拖拽排序功能的詳細(xì)教程,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08如何利用vue實(shí)現(xiàn)css過(guò)渡和動(dòng)畫
過(guò)渡Vue在插入、更新或者移除 DOM 時(shí),提供多種不同方式的應(yīng)用過(guò)渡效果這篇文章主要給大家介紹了關(guān)于如何利用vue實(shí)現(xiàn)css過(guò)渡和動(dòng)畫的相關(guān)資料,需要的朋友可以參考下2021-11-11