您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息
三六零分类信息网 > 四平分类信息网,免费分类信息发布

解析Vue2实现composition API的原理

2025/8/23 7:24:52发布3次查看
自从 vue3 发布之后,composition api 这个词走入写 vue 同学的视野之中,相信大家也一直听到 composition api 比之前的 options api 有多好多强,如今由于 @vue/composition-api 插件的发布,vue2 的同学也可以上车咯,接下来我们主要以响应式的 ref 和 reactive 来深入分析一下,这个插件是怎么实现此功能的。
如何使用// 入口文件引入并注册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 返回值打通合并在一起的。【相关推荐:vuejs视频教程、web前端开发】
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 有了一定的了解,因为 api 相当多, 响应式相关的就还有 torefs、toref、unref、shallowref、triggerref 等等,这里就不一一分析,有兴趣的可以继续看源码的实现。
写 vue2 的同学也可以不用羡慕写 vue3 的同学了,直接引入到项目就可以使用起来,虽然没有 vue3 那么好的体验,但是绝大部分场景还是相同的,使用时注意 readme 文档最后的限制章节,里面讲了一些使用限制。
(学习视频分享:vuejs入门教程、编程基础视频)
以上就是解析vue2实现composition api的原理的详细内容。
四平分类信息网,免费分类信息发布

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product