- 原文地址: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、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。