Since the release of Vue3
, the word composition API
has come into the vision of students who write Vue
, I believe Everyone has always heard that the composition API
is much better than the previous options API
. Now due to the release of the @vue/composition-api
plug-in, Vue2
students can also get on board. Next, we will mainly use the responsive ref
and reactive
to conduct an in-depth analysis of how this plug-in implements this function.
// 入口文件引入并注册 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 } } })
How about it? After reading it, do you feel exactly the same as vue3
? You may think:
This is vue2
Ah, my previous data
and methods
also have variables and methods in them, how can I do it the same as setup
The return values are merged together. [Related recommendations: vuejs video tutorial, web front-end development]
vue2
is not only defined in Will the data in data
be processed responsively? How do ref
and reactive
do it?
vue2
Constraints defined by responsive data (add attributes that are not assigned to the original object, modify array subscripts, etc.), use ref
instead Is it okay with reactive
?
Of course there are still a lot of doubts, because the plug-in provides quite a lot of API
, covering most of what Vue3
has, here Let’s analyze how it is done mainly from these questions.
Thanks to Vue
’s plug-in system, @vue/composition-api
is like vue-router
and vuex
are also injected through officially provided plug-ins.
// 这里只贴跟本章要讲的相关代码 funciton mixin (Vue) { Vue.mixin({ beforeCreate: functionApiInit } } function install (Vue) { mixin(Vue); } export const Plugin = { install: (Vue: VueConstructor) => install(Vue), }
Vue
The plug-in exposes a install
method to the outside. When use
is called, this method will be called and Vue
The constructor is passed in as a parameter, and then Vue.mixin
is called to handle the function when mixing in the corresponding hook.
The next step is to look at what functionApiInit
does
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 || {} }
because Vue
is in beforeCreated
and created
During the life cycle, the data will be processed by initState
. When processing data
, $options.data
will be called to obtain the defined data. , so the function is re-wrapped here. This is one of the reasons why beforeCreate
hook injection is chosen. It must be wrapped before the function is called.
Next, let’s see what initSetup
does
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 } }
This function is relatively long, and the code logic that is not on the main line to be explained this time has been deleted. This function mainly creates ctx
and convert the vm
instance into the instance
defined by the Vue3
data type, then execute the setup
function to get the return value, and then traverse For each property, call asVmProperty
to mount it on vm
. Of course, the mounting here is not by directly adding the properties and values to vm
. Doing so will There is a problem, that is, subsequent modifications to this attribute cannot be synchronized to vm
. The most common data proxy of Vue
is used here.
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 }, }) } }
Seeing this, I believe you have understood why the returned value defined in setup
can be used in template
, data
, methods
etc., because the returned things have been proxied to vm
.
ref
reactive
implementation)Next let’s talk about responsiveness and why ref
and reactive
can also make data reactive. The implementation of
ref
is actually a re-encapsulation of reactive
, mainly used for basic types.
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), }) }
Because reactive
must accept an object, so a constant is used here as the key
of ref
, which is
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
有了一定的了解,因为 API
相当多, 响应式相关的就还有 toRefs、toRef、unref、shallowRef、triggerRef
等等,这里就不一一分析,有兴趣的可以继续看源码的实现。
写 Vue2
的同学也可以不用羡慕写 Vue3
的同学了,直接引入到项目就可以使用起来,虽然没有 vue3
那么好的体验,但是绝大部分场景还是相同的,使用时注意 README
文档最后的限制章节,里面讲了一些使用限制。
The above is the detailed content of Analyze the principle of Vue2 implementing composition API. For more information, please follow other related articles on the PHP Chinese website!