精讀《React 的多型性》

黃子毅發表於2019-02-25

1 引言

本週精讀的文章是:surprising-polymorphism-in-react-applications,看看作者是如何解釋這個多型性含義的。

讀完文章才發現,文章標題改為 Redux 的多型性更妥當,因為整篇文章都在說 Redux,而 Redux 使用場景不侷限於 React。

2 概述

Redux immutable 特性可能產生瀏覽器無法優化的效能問題,也就是瀏覽器無法做 shapes 優化,也就是上一篇精讀《JS 引擎基礎之 Shapes and Inline Caches》 裡提到的。

先看看普通的 redux 的 reducer:

const todo = (state = {}, action) => {
  switch (action.type) {
    case "ADD_TODO":
      return {
        id: action.id,
        text: action.text,
        completed: false
      };
    case "TOGGLE_TODO":
      if (state.id !== action.id) {
        return state;
      }

      return Object.assign({}, state, {
        completed: !state.completed
      });

    default:
      return state;
  }
};
複製程式碼

我們簡化一下使用場景,假設基於這個 reducer todo,生成了兩個新 store s1 s2

const s1 = todo(
  {},
  {
    type: "ADD_TODO",
    id: 1,
    text: "Finish blog post"
  }
);

const s2 = todo(s1, {
  type: "TOGGLE_TODO",
  id: 1
});
複製程式碼

看上去很常見,也的確如此,我們每次 dispatch 都會根據 reducer 生成新的 store 樹,而且是一個新的物件。然而對 js 引擎而言,這樣的程式碼可能做不了 Shapes 優化(關於 Shapes 優化建議閱讀上一期精讀 Shapes 優化),也就是最需要做優化的全域性 store,在生成新 store 時無法被瀏覽器優化,這個問題很容易被忽視,但的確影響不小。

至於為什麼會阻止 js 引擎的 shapes 優化,看下面的程式碼:

// transition-trees.js
let a = {x:1, y:2, z:3};

let b = {};
b.x = 1;
b.y = 2;
b.z = 3;

console.log("a is", a);
console.log("b is", b);
console.log("a and b have same map:", %HaveSameMap(a, b));
複製程式碼

通過 node --allow-natives-syntax test.js 執行,通過呼叫 node 原生函式 %HaveSameMap 判斷這種情況下 ab 是否共享一個 shape(v8 引擎的 Shape 實現稱為 Map)。

image

結果是 false,也就是 js 引擎無法對 a b 做 Shapes 優化,這是因為 ab 物件初始化的方式不同。

同樣,在 Redux 程式碼中常用的 Object.assign 也有這個問題:

image

因為新的物件以 {} 空物件作為最初狀態,js 引擎會為新物件建立 Empty Shape,這與原物件的 Shape 一定不同。

順帶一提 es6 的解構語法也存在同樣的問題,因為 babel 將解構最終解析為 Object.assign

image

對這種尷尬的情況,作者的建議是對所有物件賦值時都是用 Object.assign 以保證 js 引擎可以做 Shapes 優化:

let a = Object.assign({}, {x:1, y:2, z:3});

let b = Object.assign({}, a);

console.log("a is", a);
console.log("b is", b);
console.log("a and b have same map:", %HaveSameMap(a, b)); // true
複製程式碼

3 精讀

這篇文章需要與上一篇 精讀《JS 引擎基礎之 Shapes and Inline Caches》 連起來看容易理解。

作者描述的效能問題是引擎級別的 Shapes 優化問題,讀過上篇精讀就很容易知道,只有相同初始化方式的物件才被 js 引擎做優化,而 Redux 頻繁生成的 immutable 全域性 store 是否能被優化呢?答案是“往往不能”,因為 immutable 賦值問題,我們往往採用 Object.assign 或者解構方式賦值,這種方式產生的新物件與原物件的 Shape 不同,導致 Shape 無法複用。

這裡解釋一下疑惑,為什麼說 immutable 物件之間也要優化呢?這不是兩個不同的引用嗎?這是因為 js 引擎級別的 Shapes 優化就是針對不同引用的物件,將物件的結構:Shape 與資料分離開,這樣可以大幅優化儲存效率,對陣列也一樣,上一篇精讀有詳細介紹。

所以筆者更推薦使用比如 immutable-js 這種庫操作 immutable 物件,而不是 Object.assign,因為封裝庫內部是可能通過統一物件初始化方式利用 js 引擎進行優化的。

4 總結

原文提到的多型是指多個相同結構物件,被拆分成了多個 Shape;而單態是指這些物件可以被一個 Shape 複用。

筆者以前也經歷過從 Object.assign 到 Immutablejs 庫,最後又回到解構新語法的經歷,覺得在層級不深情況下解構語法可以代替 Immutablejs 庫。

通過最近兩篇精讀的分析,我們需要重新思考這樣做帶來的優缺點,因為在 js 環境中,Object.assign 的優化效率比 Immutablejs 庫更低。

最後,也完全沒必要現在就開始重構,因為這只是 js 執行環境中很小一部分影響因素,比如為了引入 Immutablejs 讓你的網路延時增加了 100%?所以僅在有必要的時候優化它。

5 更多討論

討論地址是:精讀《React 的多型性》 · Issue #92 · dt-fe/weekly

如果你想參與討論,請點選這裡,每週都有新的主題,週末或週一釋出。

相關文章