HOME | EDIT | RSS | INDEX | ABOUT | GITHUB

A Compact React Cookbook

这是一本非常 campact 的 React 煮书,收集了在实践 React 时的一些问题和解决方法。

1. Why not 2 way binding/为毛不用双向绑定

解释这个问题我们需要先看什么是双向绑定,什么是单向绑定

multi-recur.gif

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行 是单向绑定值 messageinput 元素上,第setstate行 是把 input 元素的值绑定回来,但是 注意看 这里绑定回来需要通过 setState 来完成,这就保证了 React Component 的 source of truth 还是只有 state。

2. What's Virtual DOM, why should we care / 为毛要用 Vitual Dom

2.1. 以前是如何操作 DOM 的 (Mutable)

  1. query 到 DOM 上一个元素
  2. 改吧改吧

2.2. Virtual DOM (Immutable)

  1. 想好要往 DOM 上放什么东西
  2. 把它给 Virtual DOM
  3. Virtual DOM 决定哪些应该修改 DOM 哪些不用

    为什么说前者是 Mutable 后者是 Immutable,这是相对你的业务逻辑来说的。

DOM 本身是 Mutable 的东西,把它柔和到你的业务上给你的逻辑加上了不少 mutable 的因素,而 Virtual DOM 成功的屏蔽掉了 mutable 的 DOM,每次 render 的 Component 其实都是新的,并不是以前 Component 的修改。

所以使用 Virutal DOM

  • 容易 reason about, 因为 immutable
  • 把紧耦合编程了高内聚

3. Why Immutable / 为毛要不可变

Immutable 是函数式的概念之一,一旦创建出来之后,就不能再改变。因此,当你想对其做修改,就得弄一个新的。

zoidberg-die.gif

好奇的同学要问了,但是 React 看起来是面向对象的啊。 createClassstate ,函数式有状态和 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,其实并无状态。

https://youtu.be/5yHFTN-_mOo

3.2. Class

想象一下使用一个 React Component 的时候

<AFancyHelloWord message="Good News Everyone!"/>

来想象一下

  1. 尖括号 < 往右移
  2. 尖括号变成圆括号
  3. 里面再加个大括号
  4. 等号变冒号
AFancyHelloWord({message:"Good News Everyone!"})

futurama_August_26__2015_at_0617AM.gif 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
  }
react-tree.png

当 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
  }
react-tree-swap.png

实际上我们需要尽量减少 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 文件。

  • babel 官网就说明了如何使用 browserify babelify 你的模块们
  • 如果使用 gulp ,需要参考 gulp 这篇文档
  • grunt 用户请使用 grunt-browserify 插件(非官方)
  • broccoli 用户插件在 这里

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。

came-out.gif

官网文档的这个例子

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 / 如何提升效率

当然不是咖啡!

coffee.gif

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

Footnotes: