- 原文地址:Surprising polymorphism in React applications
- 原文做者:Benedikt Meurer
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者: Candy Zheng
- 校對者:goldEli,老教授
基於 React 框架的現代 web 應用常常經過不可變數據結構來管理它們的狀態。好比使用比較知名的 Redux 狀態管理工具。這種模式有許多優勢而且即便在 React/Redux 生態圈外也愈來愈流行。html
這種機制的核心被稱做爲 reducers
。 它們是一些能根據一個特定的映射行爲 action
(例如對用戶交互的響應)把應用從一個狀態映射到下一個狀態的函數。經過這種核心抽象的概念,複雜的狀態和 reducers 能夠由一些更簡單狀態和 reducers 組成,這使得它易於對各部分代碼隔離作單元測試。咱們仔細分析一下 Redux 文檔 中的例子。前端
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
}
}
複製代碼
這個名叫 todo
的 reducer 根據給定的 action
把一個已有的 state
映射到了一個新的狀態。這個狀態就是一個普通的 JavaScript 對象。咱們單從性能角度來看這段代碼,他彷佛是符合單態法則的,好比這個對象的形狀(key/value)保持一致。react
const s1 = todo({}, {
type: 'ADD_TODO',
id: 1,
text: "Finish blog post"
});
const s2 = todo(s1, {
type: 'TOGGLE_TODO',
id: 1
});
function render(state) {
return state.id + ": " + state.text;
}
render(s1);
render(s2);
render(s1);
render(s2);
複製代碼
表面上來看, render
中訪問屬性應該是單態的,好比說 state
對象應該有相同的對象形狀- map 或者 V8 概念中的 hidden class 形式 — 無論何時, s1
和 s2
都擁有 id
, text
和 completed
屬性而且它們有序。然而,當經過 d8
運行這段代碼並跟蹤代碼的 ICs
(內聯緩存) 時,咱們發現那個 render
表現出來的對象形狀不相同, state.id
和 state.text
的獲取變成了多態形式:android
那麼問題來了,這個多態是從哪裏來的?它確實表面看上去一致但其實有微小差別,咱們得從 V8 是如何處理對象字面量着手分析。V8 裏,每一個對象字面量 (好比 {a:va,...,z:vb}
形式的表達形式 ) 定義了一個初始的map
(map 在 V8 概念中特指對象的形狀)這個 map
會在以後屬性變更時遷移成其餘形式的 map
。因此,若是你使用一個空對象字面量 {} 時,這棵遷移樹(transition tree)的根是一個不包含任何屬性的 map
,但若是你使用 {id:id, text:text, completed:completed}
形式的對象字面量,那麼這個遷移樹(transition tree)的根就會是一個包含這三個屬性,讓咱們來看一個精簡過的例子:ios
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.js
運行命令後面加上 --allow-natives-syntax
跑這段代碼(開啓便可應用內部方法 %HaveSameMap
),舉個例子:git
儘管 a
and b
這兩個對象看上去是同樣的 —— 依次擁有相同類型的屬性,它們 map 結構並不同。緣由是它們的遷移樹(transition tree)並不相同,咱們能夠看如下的示例來解釋:github
因此當對象初始化期間被分配不一樣的對象字面量時,遷移樹(transition tree)就不一樣,map
也就不一樣,多態就隱含的造成了。這一結論對你們廣泛用的 Object.assign
也適用,好比:web
let a = {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));
複製代碼
這段代碼仍是產生了不一樣的 map
,由於對象 b
是從一個空對象( {}
字面量) 建立的,而屬性是等到Object.assign
纔給他分配。redux
這也代表,當你使用 spread
(拓展運算符)處理屬性,而且經過 Babel 來語法轉譯,就會遇到這個多態的問題。由於 Babel (其餘轉譯器可能也同樣), 對 spread
語法使用了 Object.assign
處理。後端
有一種方法能夠避免這個問題,就是始終使用 Object.assign
,而且全部對象從一個空的對象字面量開始。可是這也會致使這個狀態管理邏輯存在性能瓶頸:
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));
複製代碼
不過,當一些代碼變成多態也不意味着一切完了。對大部分代碼而言,單態仍是多態並沒啥關係。你應該在決定優化時多思考優化的價值。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。