这是一本非常 campact 的 React 煮书,收集了在实践 React 时的一些问题和解决方法。
1Why not 2 way binding/为毛不用双向绑定
解释这个问题我们需要先看什么是双向绑定,什么是单向绑定
1.1双向绑定
也就是dom 上的 value 与 controller 或者 view controller 上的绑定,值保持一致。
1.2单向绑定
dom 上的值来源于 controller,但是 dom 上的值改变不会改变 controller 上的值。
1.3双向有什么不好1
- 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) },205)">render: 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。
2What's Virtual DOM,why should we care / 为毛要用 Vitual Dom
2.1以前是如何操作 DOM 的 (Mutable)
- query 到 DOM 上一个元素
- 改吧改吧
2.2Virtual 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
- 快
- 把紧耦合编程了高内聚
3Why 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?2
首先,函数式和面向对象并不冲突,两种编程范式分别有各自的方式解决问题。
其次:
3.1状态
如果状态只存在于 Component 中又并没有影响任何人,它还是状态吗?
ClojureScript 的 React 库 om,只有一个 app 级别的 state。因此所有的 component,其实并无状态。
3.2Class
想象一下使用一个 React Component 的时候
<AFancyHelloWord message="Good News Everyone!"/>
来想象一下
- 尖括号
<
往右移 - 尖括号变成圆括号
- 里面再加个大括号
- 等号变冒号
AFancyHelloWord({message:"Good News Everyone!"})
./images/futurama_August_26__2015_at_0617AM.gifok,如果把每个 Component 看成一个函数,为了我们的代码更好 reason about 而且更 loose couple,我们应该尽量要消除每一个 Component 的状态。 这样在 Component 的树中,我们可以随意切换 Component,以 Star Wars 为例,Anakin 有两样东西,Luke 和光剑:
当 Anakin 变成 Darth Vader,光剑的颜色变红时,Darth Vadar 有 Luke 和 红色光剑。
实际上我们需要尽量减少 Component 中的状态,而且对着少数的状态,由于他们是我们的 source of truth,并不希望他是 mutable 的,这样我很难知道谁动了我的 source of truth。
3.3让你的数据结构 immutable 的工具们
这是 react addon 中自带的工具,如果你并不想完整的 Immutable 数据结构,这个工具可以帮助 copy 一份来做改动
update = require('react-addons-update'); inc = x=>x+1 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 习惯一些, 不过跑分低一些。
4How to do Unit test React project / 如何单元测试
4.1Jest
4.2jasmine
jasmine 只是一个引擎,jest 也是用 jasmine 作为引擎。但是如果由于某种原因你不想用 jest 的话,可能你需要花更多的 effort 在:
- mock (rewire.js)
- runner (karma)
- headless browser for ci(phantomjs)
所以并不推荐花这么大 effort 去撘一个 jasmine 的测试环境,关键还会有一系列的问题
- phantomjs 怪怪的 issue
- karma 复杂的配置
- rewire 也有一些坑
4.3mocha
没试过用来测 React,不过 mocha 比 jasmine 好的一点是本身就可以跑在 node 上,使用 sinon(mock) 和 should.js(assert) 是个非常强大的一套测试工具。
5Modular and Components
5.1browserify
6How should I thinking in react way / 如何以 React 的方式解决问题
要以 react 的方式思考,其实跟思考 HTML 差不多http://facebook.github.io/react/docs/thinking-in-react.html
7What about Data Fetching / 只有 V 的话,数据 M 呢
7.1justrest
7.2relay/graphql
官方 data fetching 解决方案。
比起由 component 去发请求,再转换数据格式。relay/graphql 的思想是有 component 定义数据形状,由 relay 去发请求,有 graphql server 跟去根据定义返回相应形状的数据。
所以,对,会多一层 server layer。
view 层简单了,graphql 要做的事情却不少。
7.3falcor
netflix 的简单版的 graphql可以参考我的todo falcor思想大致相似,但是更为简单一些,没有什么 QL,schema 之类的
8What about Router / router 怎么办
建议使用 isomorphic router,就是 browser 与 node 都可以用的 router
8.1direactor
非常轻量级的通用 router,并不是专门为 react 准备的,但是 router 而已,为毛要跟 component 耦合。
client side
routes = { '/author': ()=>React.render(<Author/>,domNode),'/author/:id': (id)=>React.render(<Auther id={id}/>,domNode) }; router = Router(routes); router.init();
server side
只需要调用 router.dispatch 就好了,而且 server 端的 react 需要renderToString
router = new director.http.Router({ '/author': { get: function(){ this.res.end(React.renderToString(<Author/>)) } } }); server = http.createServer(function (req,res) { router.dispatch(req,res,err) { res.writeHead(200,{ 'Content-Type': 'text/html' }) if (err) { res.writeHead(404); res.end(); } }); });
8.2react 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)
9How to communicate between two components that don't have a parent-child relationship3/ 不是父子关系的 component 怎么交互
对于这个问题,我的问题是
如果不是父子关系或者兄弟或者伯父侄女,真的需要交互吗?
如果是在同一颗树上,那么一定能找到一个共同的 parent,把 parent 的回调传进来就好了
如果不在同一颗树上,你可能需要一个全局的一些东西
9.1event
使用随便一种 event emitter,比如backbon events。 在一个 componnet 中 trigger,另一个 component subscribe
9.2flux
flux 只是一个架构思想,你可以用任何自己喜欢的方式实现 其实跟 event emitter 差不多,只是针对和管理 state
dispatcher
作为action 的分发工作,决定哪些 action 引起哪些 store 的变化
store
状态与逻辑
9.3router
使用 router 传递信息也是可以的
9.4应用级别 state
跟 om 一样,全局应用级别 state
只有当出现一串一样的元素的时候,这个时候 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 成
11What's these Warnings / 这些黄黄的是神马
黄黄的东西(除了小黄人)请一定要除掉!
所有 react 的 warning 描述都非常详细,请一定务必要除掉。
12How to Profile Component Perfomance / 如何提升效率
当然不是咖啡!
12.1react profile
12.2PureRenderMixin
当你的 props 和 state 都是 immutable 的时候…
PureRenderMixin = require('react-addons-pure-render-mixin'); React.createClass({ mixins: [PureRenderMixin],139)">return <div className={this.props.className}>foo</div>; } });