您好,登錄后才能下訂單哦!
本篇內容主要講解“Vue2實現composition API的原理是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Vue2實現composition API的原理是什么”吧!
// 入口文件引入并注冊
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
}
}
})
怎么樣,看完是不是感覺和 vue3
一模一樣,你可能會想:
這是 vue2
啊,我之前的 data
、methods
里面也有變量和方法,怎么做到跟 setup
返回值打通合并在一起的。
vue2
不是只有定義在 data
里面的數據才會被處理成響應式的嗎? ref
和 reactive
是怎么做到的呢?
vue2
響應式數據定義的約束(添加賦值原對象沒有的屬性,數組下標修改等),改用 ref
和 reactive
就沒問題嗎?
當然還有很多的疑惑,因為插件提供的 API
相當多,覆蓋了絕大部分 Vue3
所擁有的,這里主要從這幾個問題來分析一下是如何做到的。
得益于 Vue
的插件系統,@vue/composition-api
像 vue-router
、vuex
一樣也是通過官方提供的插件式來注入。
// 這里只貼跟本章要講的相關代碼
funciton mixin (Vue) {
Vue.mixin({
beforeCreate: functionApiInit
}
}
function install (Vue) {
mixin(Vue);
}
export const Plugin = {
install: (Vue: VueConstructor) => install(Vue),
}
Vue
插件就是向外面暴露一個 install
的方法,當調用 use
的時候會調用該方法,并把 Vue
構造函數作為參數傳入,然后調用 Vue.mixin
混入對應鉤子時要處理的函數。
接下來主要看下 functionApiInit
做了什么
function functionApiInit(this: ComponentInstance) {
const vm = this
const $options = vm.$options
const { setup, render } = $options
// render 相關
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 || {}
}
因為 Vue
在 beforeCreated
和 created
生命周期之間,會 initState
對數據進行處理,其中對 data
的處理時就會調用 $options.data
拿到定義的數據,所以這里重新對該函數其包裹一層,這也是為什么要選擇 beforeCreate
鉤子注入的一個原因,必須在該函數調用前進行包裹。
接下來看 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返回是函數的情況 需要重寫render函數
const bindingObj = binding
Object.keys(bindingObj).forEach((name) => {
let bindingValue: any = bindingObj[name]
// 數據處理
asVmProperty(vm, name, bindingValue)
})
return
}
}
這個函數比較長,不在本次要講解的主線上代碼邏輯都刪除了,這個函數主要是創建了 ctx
和把 vm
實例轉換成 Vue3
數據類型定義的 instance
,然后執行 setup
函數得到返回值,然后遍歷每個屬性,調用 asVmProperty
掛載到 vm
上面,當然這里的掛載不是直接通過把屬性和值添加到 vm
上面,這么做會有一個問題,就是后續對該屬性的修改不能同步到 vm
中,這里采用的還是 Vue
最常見的數據代理。
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
},
})
}
}
看到這里,相信你已經明白了在 setup
中定義返回的為什么能夠在 template
、 data
、 methods
等之中去使用了,因為返回的東西都已經被代理到 vm
之上了。
ref
reactive
的實現)接下來我們來說說響應式相關的,為什么 ref
和 reactive
也可以讓數據成為響應式的。
ref
的實現其實是對 reactive
再次封裝,主要用來給基本類型使用。
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),
})
}
因為 reactive
接受的必須是一個對象,所有這里使用了一個常量作為 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,
})
}
}
通過 new RefImpl
實例,該實例上有一個 value
的屬性,對 value
做代理,當取值的時候返回 value[RefKey]
,賦值的時候賦值給 value[RefKey]
, 這就是為什么 ref
可以用在基本類型,然后對返回值的 .value
進行操作。調用 object.seal
是把對象密封起來(會讓這個對象變的不能添加新屬性,且所有已有屬性會變的不可配置。屬性不可配置的效果就是屬性變的不可刪除,以及一個數據屬性不能被重新定義成為訪問器屬性,或者反之。但屬性的值仍然可以修改。)
我們主要看下 reactive
的實現
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
}
我們通過 ref
或者 reactive
定義的數據,最終還是通過了變成了一個 observed
實例對象,也就是 Vue2
在對 data
進行處理時,會調用 observe
返回的一樣,這里在 Vue2.6+
把observe
函數向外暴露為 Vue.observable
,如果是低版本的話,可以通過重新 new
一個 vue
實例,借助 data
也可以返回一個 observed
實例,如上述代碼。
因為在 reactive
中定義的數據,就如你在 data
中定義的數據一樣,都是在操作返回的 observed
,當你取值的時候,會觸發 getter
進行依賴收集,賦值時會調用 setter
去派發更新,
只是定義在 setup
中,結合之前講到的 setup
部分,比如當我們在 template
中訪問一個變量的值時,vm.foo
-> proxy
到 setup
里面的 foo
-> observed
的 foo
,完成取值的流程,這會比直接在 data
上多代理了一層,因此整個過程也會有額外的性能開銷。
因此使用該 API
也不會讓你可以直接規避掉 vue2
響應式數據定義的約束,因為最終還是用 Object.defineProperty
去做對象攔截,插件同樣也提供了 set API
讓你去操作對象新增屬性等操作。
到此,相信大家對“Vue2實現composition API的原理是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。