2020-07-11 Vue-composition-api 解讀(1)

@vue/composition-api 解讀1

vue-composition-api 是 vue 官方出得, 基于vue3的RFC來兼容Vue2.x的增強(qiáng)api,具體API和vue-next 是差不多的, 具體的API詳見 vue-composition-api-rfc, 本系列文章是基于你對vue2有所了解的情況

proxy

vue2的響應(yīng)式是基于Object.definePropertysetget方法來實(shí)現(xiàn)的,具體的在源碼中的實(shí)現(xiàn), 這也是組合式API響應(yīng)式的基礎(chǔ)

// src/utils/utils.ts
export function proxy(
  target: any,
  key: string,
  { get, set }: { get?: Function; set?: Function }
) {
  sharedPropertyDefinition.get = get || noopFn
  sharedPropertyDefinition.set = set || noopFn
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

其中sharedPropertyDefinition存在getset方法

reactive

reactive是組合式API提供的一個(gè)實(shí)現(xiàn)對象響應(yīng)式的API, 使用方法也很簡單,就是傳入一個(gè)object

setup() {
    const state = reactive({
        foo: 'foo'
    })

    return {
        state
    }
}

源碼中的實(shí)現(xiàn)我們一一來看

export function reactive<T extends object>(obj: T): UnwrapRef<T> {
  if (__DEV__ && !obj) {
    warn('"reactive()" is called without provide an "object".')
    // @ts-ignore
    return
  }

  if (
    !isPlainObject(obj) || // 非原始對象
    isReactive(obj) || // 已是響應(yīng)式
    isRaw(obj) || // 
    !Object.isExtensible(obj) // 對象不可擴(kuò)展
  ) {
    return obj as any
  }

  const observed = observe(obj)
  // def(obj, ReactiveIdentifierKey, ReactiveIdentifier);
  markReactive(obj)
  setupAccessControl(observed)
  return observed as UnwrapRef<T>
}

其中 observe是實(shí)現(xiàn)響應(yīng)式的關(guān)鍵, 我們打開看一看


function observe<T>(obj: T): T {
  const Vue = getVueConstructor()
  let observed: T
  if (Vue.observable) {
    observed = Vue.observable(obj)
  } else {
    const vm = defineComponentInstance(Vue, {
      data: {
        $$state: obj,
      },
    })
    observed = vm._data.$$state
  }

  return observed
}

你沒看錯(cuò),就這么簡單, 實(shí)際上就是用vue2現(xiàn)成的api來實(shí)現(xiàn)的 Vue.observable, else下面是用來兼容以前老版本vue沒有observable

markReactive 標(biāo)記 reactive中的對象, 實(shí)現(xiàn)方式其實(shí)也是使用Object.defineProperty來寫入標(biāo)記值,
其中標(biāo)記都是用Symbol來標(biāo)識唯一性的

ref

ref 是組合式API中另一個(gè)響應(yīng)式api,傳入一個(gè)基本類型參數(shù)

使用

setup() {
    const state = ref(1/"hello world"/false/null)

    return {
        state
    }
}

取值方式state.value, 如果使用模版方式就不需要使用.value方式,模版已經(jīng)做了這個(gè)工作,如果使用jsxrender函數(shù),就只能手加了

具體實(shí)現(xiàn)

export 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),
  })
}

其中在內(nèi)部實(shí)現(xiàn)了組裝了一個(gè)對象,還是用的reactive實(shí)現(xiàn)邏輯
關(guān)鍵在createRef

class RefImpl<T> implements Ref<T> {
  readonly [_refBrand]!: true
  public value!: T
  constructor({ get, set }: RefOption<T>) {
    proxy(this, 'value', {
      get,
      set,
    })
  }
}

export function createRef<T>(options: RefOption<T>) {
  // seal the ref, this could prevent ref from being observed
  // It's safe to seal the ref, since we really shouldn't extend it.
  // related issues: #79
  return Object.seal(new RefImpl<T>(options))
}

其中Object.seal 封閉了一個(gè)RefImpl對象,使其對象不能添加其他任何屬性,已有屬性可以更改,具體的去查Object.seal的文檔

RefImpl中 用我們上面寫的響應(yīng)式方法proxy, 傳入getset來重寫他的getset方法

官方用ref的方式來獲得dom實(shí)例

<template>
  <div ref="root"></div>
</template>

<script>
  import { ref, onMounted } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      onMounted(() => {
        // 在渲染完成后, 這個(gè) div DOM 會被賦值給 root ref 對象
        console.log(root.value) // <div/>
      })

      return {
        root,
      }
    },
  }
</script>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容