A Compact React Cookbook
这是一本非常 campact 的 React 煮书,收集了在实践 React 时的一些问题和解决方法。
1. Why not 2 way binding/为毛不用双向绑定
解释这个问题我们需要先看什么是双向绑定,什么是单向绑定
1.1. 双向绑定
也就是dom 上的 value 与 controller 或者 view controller 上的绑定,值保持一致。
1.2. 单向绑定
dom 上的值来源于 controller,但是 dom 上的值改变不会改变 controller 上的值。
1.3. 双向有什么不好 https://www.quora.com/Why-is-the-two-way-data-binding-being-dropped-in-Angular-2
- perfomance
- 我们真的需要吗?实际上有多少值是真的需要双向绑的
- 到底谁动了我的值?too many sources of truth
1.4. 单向有什么好
- 只有一个 source of truth, 代码好 reason about
- 更快
- 需要的时候自己绑一把,也并不是多麻烦的事
var TwoWayBindingInput = React.createClass({
getInitialState: function() {
return {message: 'Hello!'};
},
handleChange: function(event) {
this.setState({message: event.target.value}); // <= (setstate)
},
render: function() {
var message = this.state.message;
return <input type="text" value={message} onChange={this.handleChange} />; // <= (value)
}
});
注意看这个双向绑定,第value行 是单向绑定值 message
到 input
元素上,第setstate行 是把 input
元素的值绑定回来,但是 注意看 这里绑定回来需要通过 setState
来完成,这就保证了 React Component 的 source of truth 还是只有 state。
2. What's Virtual DOM, why should we care / 为毛要用 Vitual Dom
2.1. 以前是如何操作 DOM 的 (Mutable)
- query 到 DOM 上一个元素
- 改吧改吧
2.2. Virtual DOM (Immutable)
- 想好要往 DOM 上放什么东西
- 把它给 Virtual DOM
Virtual DOM 决定哪些应该修改 DOM 哪些不用
为什么说前者是 Mutable 后者是 Immutable,这是相对你的业务逻辑来说的。
DOM 本身是 Mutable 的东西,把它柔和到你的业务上给你的逻辑加上了不少 mutable 的因素,而 Virtual DOM 成功的屏蔽掉了 mutable 的 DOM,每次 render 的 Component 其实都是新的,并不是以前 Component 的修改。
所以使用 Virutal DOM
- 容易 reason about, 因为 immutable
- 快
- 把紧耦合编程了高内聚
3. Why Immutable / 为毛要不可变
Immutable 是函数式的概念之一,一旦创建出来之后,就不能再改变。因此,当你想对其做修改,就得弄一个新的。
好奇的同学要问了,但是 React 看起来是面向对象的啊。 createClass
, state
,函数式有状态和 class 吗?
If a tree falls in a forest and no one is around to hear it, does it make a sound? https://en.wikipedia.org/wiki/If_a_tree_falls_in_a_forest
首先,函数式和面向对象并不冲突,两种编程范式分别有各自的方式解决问题。
其次:
3.1. 状态
如果状态只存在于 Component 中又并没有影响任何人,它还是状态吗?
ClojureScript 的 React 库 om,只有一个 app 级别的 state。因此所有的 component,其实并无状态。
3.2. Class
想象一下使用一个 React Component 的时候
<AFancyHelloWord message="Good News Everyone!"/>
来想象一下
- 尖括号
<
往右移 - 尖括号变成圆括号
- 里面再加个大括号
- 等号变冒号
AFancyHelloWord({message:"Good News Everyone!"})
ok, 如果把每个 Component 看成一个函数,为了我们的代码更好 reason about 而且更 loose couple,我们应该尽量要 消除 每一个 Component 的状态。
这样在 Component 的树中,我们可以随意切换 Component,以 Star Wars 为例,Anakin 有两样东西,Luke 和光剑:
digraph component {
Luke [label="Luke Skywalker"]
Anakin [label="Anakin Skywalker"]
Darth [label="Darth Vader", color=gray]
Lightsaber [label="Lightsaber"]
Anakin -> Luke
Anakin -> Lightsaber
}
当 Anakin 变成 Darth Vader,光剑的颜色变红时,Darth Vadar 有 Luke 和 红色光剑。
digraph component {
Luke [label="Luke Skywalker"]
Anakin [label="Anakin Skywalker", color=gray]
Darth [label="Darth Vader"]
Lightsaber [label="Red Lightsaber", color=red]
Darth -> Luke [xlabel="i’m your father!"]
Darth -> Lightsaber
}
实际上我们需要尽量减少 Component 中的状态,而且对着少数的状态,由于他们是我们的 source of truth,并不希望他是 mutable 的,这样我很难知道谁动了我的 source of truth。
3.3. 让你的数据结构 immutable 的工具们
Immutablility helper
这是 react addon 中自带的工具,如果你并不想完整的 Immutable 数据结构,这个工具可以帮助 copy 一份来做改动
var update = require('react-addons-update');
var inc = x=>x+1
var fancyPropsForChild=update(this.state, {
x: {y: {z: {$set: 7}}},
a: {b: {$push: [9]}},
h: {$merge: {i: "j"}},
e: {$apply: inc}
});
mori
更为彻底的选择是,使用 ClojureScript 的 Immutable 数据结构。benchmark 要比 facebook 的 Immutable.js 好上许多,但是使用上跟 ClojureScript 一致, 用惯JavaScript的人可能不太能习惯,alternative 是使用我 fork 的 mori 版本conjs。
Immutable.js
facebook 实现的 immutable 数据结构,使用上比较符合 JavaScript 习惯一些, 不过跑分低一些。
4. How to do Unit test React project / 如何单元测试
4.1. Jest
总的来说,jest 的测试理念解决了非常多的前端测试的棘手问题,我做过一个关于 jest 的 session, 文章在 这里。 文章可能写得有点早,非常知道高兴的是终于支持最新的 nodejs 了,而且 重要的是 facebook 使用 jest 测试 react,有一些非常方便的 mock component 的方法。
recap 一下主要是
- automock/ manual mock
- jsdom
- 并行测试
4.2. jasmine
jasmine 只是一个引擎,jest 也是用 jasmine 作为引擎。但是如果由于某种原因你不想用 jest 的话,可能你需要花更多的 effort 在:
- mock (rewire.js)
- runner (karma)
- headless browser for ci(phantomjs)
所以并不推荐花这么大 effort 去撘一个 jasmine 的测试环境,关键还会有一系列的问题
- phantomjs 怪怪的 issue
- karma 复杂的配置
- rewire 也有一些坑
4.3. mocha
没试过用来测 React,不过 mocha 比 jasmine 好的一点是本身就可以跑在 node 上,使用 sinon(mock) 和 should.js(assert) 是个非常强大的一套测试工具。
5. Modular and Components
5.1. browserify
简单的 modular bundler, 推荐 , 因为职责单一的工具更不容易遇到奇怪的问题。
使用 browserify 使用 babel transformer 就可以把所有的 component 以 node 的方式模块化的组织,最后 bundle 成一个 js 文件。
5.2. webpack
以 grunt 的方式 browserify 你的代码,非常强大的 bundler。但是个人并不喜欢 grunt,karma,webpack 这种基于配置的工具,原因很简单,配置不是代码!配置不是代码!配置不是代码! 配对了当然简单,但是配错了怎么办,没法 debug。
虽然不喜欢,我还是要告诉你怎么用,就这么一行配置就好了
module: {
loaders: [
{ test: /\.jsx?$/, exclude: /node_modules/, loader: "babel-loader"}
]
}
6. How should I thinking in react way / 如何以 React 的方式解决问题
要以 react 的方式思考,其实跟思考 HTML 差不多 http://facebook.github.io/react/docs/thinking-in-react.html
7. What about Data Fetching / 只有 V 的话,数据 M 呢
7.1. just rest
简单,rest 请求回来一个 Promise,你还可以用 when 获得更多的 promise 和 monad 用法。
无需 model 在 componentDidMount 发出 rest 请求,then 直接扔给 setState。最多 setState 前加些 map filter 把数据改改格式。
7.2. relay/graphql
官方 data fetching 解决方案。
比起由 component 去发请求,再转换数据格式。relay/graphql 的思想是有 component 定义数据形状,由 relay 去发请求,有 graphql server 跟去根据定义返回相应形状的数据。
所以,对,会多一层 server layer。
view 层简单了,graphql 要做的事情却不少。
7.3. falcor
netflix 的简单版的 graphql可以参考我的 todo falcor 思想大致相似,但是更为简单一些,没有什么 QL,schema 之类的
8. What about Router / router 怎么办
建议使用 isomorphic router,就是 browser 与 node 都可以用的 router
8.1. direactor
非常轻量级的通用 router,并不是专门为 react 准备的,但是 router 而已,为毛要跟 component 耦合。
client side
var routes = {
'/author': ()=>React.render(<Author/>, domNode),
'/author/:id': (id)=>React.render(<Auther id={id}/>, domNode)
};
var router = Router(routes);
router.init();
server side
只需要调用 router.dispatch 就好了, 而且 server 端的 react 需要 renderToString
var router = new director.http.Router({
'/author': {
get: function(){
this.res.end(React.renderToString(<Author/>))
}
}
});
var server = http.createServer(function (req, res) {
router.dispatch(req, res, function (err) {
res.writeHead(200, { 'Content-Type': 'text/html' })
if (err) {
res.writeHead(404);
res.end();
}
});
});
8.2. react router
非常 非轻量级 的 router,而且只能给 react component用。
概念上就是使用 Route 把你的 Component 包起来,让 router 决定到底哪个 componet 上
render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About}/>
<Route path="users" component={Users}>
<Route path="/user/:userId" component={User}/>
</Route>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
), document.body)
9. How to communicate between two components that don't have a parent-child relationship http://facebook.github.io/react/tips/communicate-between-components.html / 不是父子关系的 component 怎么交互
对于这个问题,我的问题是
如果不是父子关系或者兄弟或者伯父侄女,真的需要交互吗?
如果是在同一颗树上,那么一定能找到一个共同的 parent,把 parent 的回调传进来就好了
如果不在同一颗树上,你可能需要一个全局的一些东西
9.1. event
使用随便一种 event emitter,比如 backbon events。 在一个 componnet 中 trigger,另一个 component subscribe
9.2. flux
flux 只是一个架构思想,你可以用任何自己喜欢的方式实现 其实跟 event emitter 差不多,只是针对和管理 state
dispatcher
作为action 的分发工作,决定哪些 action 引起哪些 store 的变化
store
状态与逻辑
9.3. router
使用 router 传递信息也是可以的
9.4. 应用级别 state
跟 om 一样,全局应用级别 state
10. When should I use "key" / 什么时候该用 key
只有当出现一串一样的元素的时候 ,这个时候 Virtual DOM 去 reconciliate(搞) DOM 的时候会傻傻分不清楚。
别的时候不要用 key,key 已经出现在 virtual dom diff/reconciliation 的阶段,效率要更低于 shouldComponentUpdate,所以尽量通过 shouldComponentUpdate 来决定是否要 render component。
官网文档的这个例子
renderA: <div><span>first</span></div>
renderB: <div><span>second</span><span>first</span></div>
=> [replaceAttribute textContent 'second'], [insertNode <span>first</span>]
其实是往第一个位置插入了一个 span,但是会被 diff 成
- 替换内容 first 到 second
插入内容为 first 的 span
不光是这样会更慢的问题,如果你在 first 上绑有事件的话,重新 render 后因为是 replace 了内容,因此这是原来的事件会变成 second 的事件,这样就 完全错乱 了。
11. What's these Warnings / 这些黄黄的是神马
黄黄的东西(除了小黄人)请一定要除掉!
所有 react 的 warning 描述都非常详细,请一定 务必 要除掉。
12. How to Profile Component Perfomance / 如何提升效率
当然不是咖啡!
12.1. react profile
12.2. PureRenderMixin
当你的 props 和 state 都是 immutable 的时候…
var PureRenderMixin = require('react-addons-pure-render-mixin');
React.createClass({
mixins: [PureRenderMixin],
render: function() {
return <div className={this.props.className}>foo</div>;
}
});
12.3. shouldComponentUpdate
可以通过这个方法对于 component 到底什么情况下应该重新 render 调优
所有图片来源于 giphy.com, copyright @Futurama