欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

15 分鐘掌握vue-next響應(yīng)式原理

 更新時(shí)間:2019年10月13日 10:49:50   作者:littleLyon  
這篇文章主要介紹了15 分鐘掌握vue-next響應(yīng)式原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

寫(xiě)在前面

最新 vue-next 的源碼發(fā)布了,雖然是 pre-alpha 版本,但這時(shí)候其實(shí)是閱讀源碼的比較好的時(shí)機(jī)。在 vue 中,比較重要的東西當(dāng)然要數(shù)它的響應(yīng)式系統(tǒng),在之前的版本中,已經(jīng)有若干篇文章對(duì)它的響應(yīng)式原理和實(shí)現(xiàn)進(jìn)行了介紹,這里就不贅述了。在 vue-next 中,其實(shí)現(xiàn)原理和之前還是相同的,即通過(guò)觀察者模式和數(shù)據(jù)劫持,只不過(guò)對(duì)其實(shí)現(xiàn)方式進(jìn)行了改變。

對(duì)于解析原理的文章,我個(gè)人是比較喜歡那種“小白”風(fēng)格的文章,即不要摘錄特別多的代碼,也不要闡述一些很深?yuàn)W的原理與概念。在我剛接觸 react 的時(shí)候,還記得有一篇利用 jquery 來(lái)介紹 react 的文章,從簡(jiǎn)入繁,面面俱到,其背后闡述的知識(shí)點(diǎn)對(duì)我后來(lái)學(xué)習(xí) react 起到很多的幫助。

因此,這篇文章我也打算按這種風(fēng)格來(lái)寫(xiě)一下利用最近空閑時(shí)間閱讀 vue-next 響應(yīng)式模塊的源碼的一些心得與體會(huì),算是拋磚引玉,同時(shí)實(shí)現(xiàn)一個(gè)極簡(jiǎn)的響應(yīng)式系統(tǒng)。

如有錯(cuò)誤,還望指正。

預(yù)備知識(shí)

無(wú)論是閱讀這篇文章,還是閱讀 vue-next 響應(yīng)式模塊的源碼,首先有兩個(gè)知識(shí)點(diǎn)是必備的:

  • Proxy:es6 中新的代理內(nèi)建工具類(lèi)
  • Reflect:es6 中新的反射工具類(lèi)

由于篇幅有限,這里也不詳細(xì)贅述這兩個(gè)類(lèi)的用途與使用方法了,推薦三篇我認(rèn)為不錯(cuò)的文章,僅供參考:

接口

對(duì)于 vue-next 響應(yīng)式系統(tǒng)的 RFC,可以參考這里。雖然距離現(xiàn)在有一段時(shí)間了,但是通過(guò)閱讀源碼,可以發(fā)現(xiàn)一些影子。

我們大體要實(shí)現(xiàn)的效果如下面的代碼所示:

// 實(shí)現(xiàn)兩個(gè)方法 reactive 和 effect

const state = reactive({
  count: 0
})

effect(() => {
  console.log('count: ', state.count)
})

state.count++ // 輸入 count: 1

可以發(fā)現(xiàn)我們熟悉的依賴(lài)收集階段(同時(shí)也是觀察者模式的訂閱過(guò)程),是在 effect 中進(jìn)行的,依賴(lài)收集的準(zhǔn)備工作(即數(shù)據(jù)劫持邏輯),是在 reactive 中進(jìn)行的,而數(shù)據(jù)變化的觸發(fā)響應(yīng)的邏輯在后面的 state.count++ 代碼執(zhí)行時(shí)進(jìn)行(同時(shí)也是觀察者模式的發(fā)布過(guò)程),之后便會(huì)執(zhí)行之前傳入 effect 內(nèi)部的回調(diào)函數(shù)并輸入 count: 1。

類(lèi)型與公共變量

由于 vue-next 用 ts 進(jìn)行了重寫(xiě),這里我也使用 ts 來(lái)實(shí)現(xiàn)這個(gè)極簡(jiǎn)版本的響應(yīng)式系統(tǒng)。主要涉及到的類(lèi)型和公共變量如下:

type Effect = Function;
type EffectMap = Map<string, Effect[]>;

let currentEffect: Effect;
const effectMap: EffectMap = new Map();
  • currentEffect:用來(lái)儲(chǔ)存當(dāng)前正在收集依賴(lài)的 effect
  • effectMap:代表目標(biāo)對(duì)象每個(gè) key 所對(duì)應(yīng)的依賴(lài)于它的 effect 數(shù)組,也可以把它理解為觀察者模式中的訂閱者字典

利用 Proxy 實(shí)現(xiàn)數(shù)據(jù)劫持

在之前的版本中,vue 利用 Object.defineProperty 中的 setter 和 getter 來(lái)對(duì)數(shù)據(jù)對(duì)象進(jìn)行劫持,vue-next 則通過(guò) Proxy。眾所周知,Object.defineProperty 所實(shí)現(xiàn)的數(shù)據(jù)劫持是有一定限制的,而 Proxy 就會(huì)強(qiáng)大很多。

首先,我們?cè)谀X后中,設(shè)想一下如何使用 Proxy 來(lái)實(shí)現(xiàn)數(shù)據(jù)劫持呢?很簡(jiǎn)單,大體結(jié)構(gòu)如下所示:

export function reactive(obj) {
 const proxied = new Proxy(obj, handlers);

 return proxied;
}

這里的 handlers 是聲明如何處理各個(gè) trap 的邏輯,比如:

const handlers = {
  get: function(target, key, receiver) {
    ...
  },
  set: function(target, key, value, receiver) {
    ...
  },
  deleteProperty(target, key) {
    ...
  }
  // ...以及其他 trap
 }

由于這里是極簡(jiǎn)版本的實(shí)現(xiàn),那么我們就僅僅實(shí)現(xiàn) get 和 set 兩個(gè) trap 就可以了,分別對(duì)應(yīng)依賴(lài)收集和觸發(fā)響應(yīng)的邏輯。

依賴(lài)收集

對(duì)于依賴(lài)收集的實(shí)現(xiàn),由于是極簡(jiǎn)版本,實(shí)現(xiàn)的前提如下:

  • 不考慮對(duì)象的嵌套
  • 不考慮集合類(lèi)型
  • 不考慮基礎(chǔ)類(lèi)型
  • 不考慮對(duì)代理對(duì)象的處理

哈哈,基本這四點(diǎn)排除之后,這個(gè)依賴(lài)收集函數(shù)就會(huì)很輕很薄,如下:

function(target, key: string, receiver) {
    // 僅僅在某個(gè) effect 內(nèi)部進(jìn)行依賴(lài)收集
    if (currentEffect) {
     if (effectMap.has(key)) {
      const effects = effectMap.get(key);
      if (effects.indexOf(currentEffect) === -1) {
       effects.push(currentEffect);
      }
     } else {
      effectMap.set(key, [currentEffect]);
     }
    }

   return Reflect.get(target, key, receiver);
}

實(shí)現(xiàn)的邏輯很簡(jiǎn)單,其實(shí)就是觀察者模式中注冊(cè)訂閱者的實(shí)現(xiàn)邏輯,值得注意的是,這里對(duì)于 target 的賦值邏輯,我們委托給 Reflect 來(lái)完成,雖然 target[key] 也是可以工作的,但是使用 Reflect 是更提倡的方式。

觸發(fā)響應(yīng)

觸發(fā)響應(yīng)的邏輯就比較簡(jiǎn)單了,其實(shí)是對(duì)應(yīng)觀察者模式中,發(fā)布事件的邏輯,如下:

function(target, key: string, value, receiver) {
    const result = Reflect.set(target, key, value, receiver);
    
    if (effectMap.has(key)) {
     effectMap.get(key).forEach(effect => effect());
    }

    return result;
}

同樣,這里使用 Reflect 來(lái)對(duì) target 進(jìn)行賦值操作,因?yàn)樗鼤?huì)返回一個(gè) boolean 值代表是否成功,而 set 這個(gè) trap 也需要代表相同含義的值。

通過(guò) reactive 方法來(lái)初始化代理對(duì)象

實(shí)現(xiàn)了數(shù)據(jù)劫持的代理邏輯之后,我們只需要在 reactive 這個(gè)方法中,返回一個(gè)代理對(duì)象的實(shí)例即可,還記的上文中我們?cè)趯?shí)現(xiàn)之前腦海中浮現(xiàn)的大致代碼框架嗎?

如下:

export function reactive(obj: any) {
 const proxied = new Proxy(obj, {
  get: function(target, key: string, receiver) {
   if (currentEffect) {
    if (effectMap.has(key)) {
     const effects = effectMap.get(key);
     if (effects.indexOf(currentEffect) === -1) {
      effects.push(currentEffect);
     }
    } else {
     effectMap.set(key, [currentEffect]);
    }
   }

   return Reflect.get(target, key, receiver);
  },
  set: function(target, key: string, value, receiver) {
   const result = Reflect.set(target, key, value, receiver);

   if (effectMap.has(key)) {
    effectMap.get(key).forEach(effect => effect());
   }

   return result;
  }
 });

 return proxied;
}

依賴(lài)收集的準(zhǔn)備工作

上文中提到了,對(duì)于依賴(lài)收集的工作,我們是有條件地進(jìn)行的,即在一個(gè) effect 中,我們才會(huì)進(jìn)行收集,其他情況下的取值邏輯,我們則不會(huì)進(jìn)行依賴(lài)收集,因此,effect 方法正式為了實(shí)現(xiàn)這點(diǎn)而存在的,如下:

export function effect(fn: Function) {
 const effected = function() {
  fn();
 };

 currentEffect = effected;
 effected();
 currentEffect = undefined;

 return effected;
}

之所以實(shí)現(xiàn)如此簡(jiǎn)單,是因?yàn)槲覀冞@里是極簡(jiǎn)版本,不需要考慮諸如 readOnly 、異常以及收集時(shí)機(jī)等因素??梢园l(fā)現(xiàn),就是將傳入的回調(diào)函數(shù)包裹在另一個(gè)方法中,然后將這個(gè)方法用 currentEffect 這個(gè)變量暫存,之后嘗試運(yùn)行一下即可。當(dāng) effect 運(yùn)行完畢之后,再將 currentEffect 置空,這樣就可以達(dá)到只在 effect 下進(jìn)行依賴(lài)收集的目的。

運(yùn)行效果

我在 codepen 上簡(jiǎn)單寫(xiě)了一個(gè)計(jì)數(shù)器 demo,鏈接如下:
https://codepen.io/littlelyon1/pen/mddVPgo

寫(xiě)在最后

這個(gè)極簡(jiǎn)的響應(yīng)式系統(tǒng)雖然能用,但是有很多未考慮的因素,其實(shí)就是在上文中被我們忽略的那些前提條件,這里再列舉一下,并給出源代碼中的解法:

  • 基礎(chǔ)數(shù)據(jù)類(lèi)型的處理:可以將基礎(chǔ)數(shù)據(jù)類(lèi)型封裝為一個(gè) ref 對(duì)象,其 value 指向基礎(chǔ)數(shù)據(jù)類(lèi)型的值
  • 嵌套對(duì)象:遞歸進(jìn)行執(zhí)行代理過(guò)程即可
  • 集合對(duì)象:編寫(xiě)專(zhuān)門(mén)的 trap 處理邏輯
  • 代理實(shí)例:緩存這些代理實(shí)例,下次遇到直接返回即可

但我仍然推薦你直接去閱讀一下源碼,因?yàn)槟銜?huì)發(fā)現(xiàn),源碼會(huì)在這個(gè)極簡(jiǎn)版本基礎(chǔ)上,利用了更加復(fù)雜數(shù)據(jù)結(jié)構(gòu)以及流程,來(lái)控制依賴(lài)收集和觸發(fā)響應(yīng)的流程,同時(shí)各種特殊情況也有更加明細(xì)的考慮。

另外,這僅僅是 vue-next 響應(yīng)式系統(tǒng)的簡(jiǎn)易實(shí)現(xiàn),諸如其他功能模塊,比如指令、模板解析、vdom 等,我也準(zhǔn)備利用最近的空閑時(shí)間再去看看,有時(shí)間的話,最近也整理出來(lái),分享給大家。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論