首頁  >  文章  >  web前端  >  解析Vue2實作composition API的原理

解析Vue2實作composition API的原理

青灯夜游
青灯夜游轉載
2023-01-13 08:30:012711瀏覽

解析Vue2實作composition API的原理

自從Vue3 發布之後,composition API 這個字走入寫Vue 同學的視野之中,相信大家也一直聽到composition API 比之前的options API 有多好多強,如今由於@vue/composition-api 外掛程式的發布, Vue2 的同學也可以上車咯,接下來我們主要以響應式的refreactive 來深入分析一下,這個插件是怎麼實現此功能的。

如何使用

// 入口文件引入并注册
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 啊,我之前的datamethods 裡面也有變數和方法,怎麼做到跟setup 傳回值打通合併在一起的。 【相關推薦:vuejs影片教學web前端開發

  • vue2 不是只有定義在 data 裡面的資料才會被處理成響應式的嗎? refreactive 是怎麼做到的呢?

  • vue2 響應式資料定義的約束(新增賦值原始物件沒有的屬性,陣列下標修改等),改用refreactive 就沒問題嗎?

當然還有很多的疑惑,因為插件提供的API 相當多,覆蓋了絕大部分Vue3 所擁有的,這裡主要從這幾個問題來分析一下是如何做到的。

原理解析

得益於Vue 的外掛系統,@vue/composition-api 像是 vue-routervuex 一樣也是透過官方提供的外掛程式來注入。

// 这里只贴跟本章要讲的相关代码

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 || {}
  }

因為VuebeforeCreatedcreated 生命週期之間,會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, &#39;__ob__&#39;, 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 中定義返回的為什麼能夠在templatedata methods 等之中去使用了,因為傳回的東西都已經被代理到vm 之上了。

回應式( ref  reactive 的實作)

接下來我們來說說響應式相關的,為什麼refreactive 也可以讓資料成為響應式的。

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, &#39;value&#39;, {
      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 -> proxysetup 里面的 foo -> observedfoo ,完成取值的流程,这会比直接在 data 上多代理了一层,因此整个过程也会有额外的性能开销。

因此使用该 API 也不会让你可以直接规避掉 vue2 响应式数据定义的约束,因为最终还是用 Object.defineProperty 去做对象拦截,插件同样也提供了 set API 让你去操作对象新增属性等操作。

总结

通过上面的了解,相信你一定对于 Vue2 如何使用 composition API 有了一定的了解,因为 API 相当多, 响应式相关的就还有 toRefs、toRef、unref、shallowRef、triggerRef 等等,这里就不一一分析,有兴趣的可以继续看源码的实现。

Vue2 的同学也可以不用羡慕写 Vue3 的同学了,直接引入到项目就可以使用起来,虽然没有 vue3 那么好的体验,但是绝大部分场景还是相同的,使用时注意 README 文档最后的限制章节,里面讲了一些使用限制。

(学习视频分享:vuejs入门教程编程基础视频

以上是解析Vue2實作composition API的原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除