pure render
首发于pure render
ReactEurope 2016 小记 - 上

ReactEurope 2016 小记 - 上

不知不觉间,2016 年已经过去了一半的时间。在这过去的六个月中,前端开发界又发生了许多激动人心的变化。与过去几年不同的是,各个前端社区讨论的重点已经从几个通用框架(React、Angular、Vue)的优劣转向了各个通用框架周边生态环境的建设,以及如何更好地使用这些通用框架。

作为国内最早实践 React 的团队,我们一直都在关注着 React 及其相关生态环境的发展,包括但不限于 Redux、Immutable、GraphQL、Relay 等。

两个星期前,ReactEurope 2016 在法国巴黎如期举行。与 ReactEurope 2015 不同的是,在这次的大会上,我们看到整个 React 的生态系统都呈现出了百花齐放的状态。

大会 Day 1 的第一位演讲者从去年 React 及 React Native 的开发者 Christopher Chedeau 换成了今年 Redux 的作者 Dan Abramov。从这一有趣的变化中,我们可以看出目前 React 本身已经广泛地被业界所接受,整个 React 社区的下一个重点是如何优雅地解决因为引入 React 而带来的一些 side effect(副作用)以及如何围绕 React 构建起一套完善的解决方案并提升广大开发者们的开发体验。

The Redux Journey - Dan Abramov

在 React 社区中,过去的一年是属于 Redux 的。

上线一年以来,Redux 在 npm 上的下载量已经超过了三百万次,使用 Redux 完成的项目总数正在以惊人的速度上升。Dan 在演讲中还特意提到了 Twitter 团队目前也已经引入了 Redux,这让 Dan 感到非常荣幸,因为他自己一直以来都是 Twitter 的忠实用户。

Dan 分享了自己在开发、推广 Redux 中的宝贵经验。不同于传统框架,Redux 并没有从 Features 及 APIs 的维度去定义自己,而是相应地设置了一些 Constraints 以及 Contracts。

Features => Constraints (Should be Useful)

  • Single state tree
  • Actions describe updates, no setters
  • Reducers apply updates

APIs => Contracts (Should be Social)

  • Reducers
  • Selectors
  • Middleware
  • Enhancers

从功能或特色上来讲,Redux 为开发者提供了时间旅行、数据持久化等功能,让开发者可以很方便地记录(方便收集数据)并复现(方便 debug)用户的所有操作。而为了获得以上提到的这些好处,开发者们又必须遵循 Redux 制定的一些规范。Redux 希望所有的使用者都可以理解 Redux 的思路,而不是去折腾一些毫无意义的配置文件然后实现一些简单的功能。

Redux 的思路:

Redux 是什么?

  • Redux 是一个拥有当前状态的状态变化发送机。

Redux 有什么功能?

  • 定义所有的事件监听器及当前状态
  • 增加或删除事件监听器
  • 读取当前状态

Redux 如何更改状态?

  • 发送一个 action 以更改状态
  • 传入 action 及当前状态至相应的 reducer,返回更改后的状态
  • 更新状态并再次调用所有的事件监听器

而从另一方面来讲,以 Contracts 的模式来定制 Redux 的 API,使得 Redux 具有了非常强大的扩展性。开发者不仅可以定义自己的 reducer、selector,也可以为 dispatch 函数增加特殊的行为(Middleware)甚至扩展 Redux 本身的 state store。

从我个人的角度来看,Redux 的火热一方面是因为它规范了 React 中 state 的使用方法并使得开发者获得上述的诸多好处,另一方面是因为它本身良好的扩展性又催生了一批优秀的 Redux 扩展,做到了即使其本身不完美也可以通过开源社区的力量去修正或扩展自己。

在演讲的最后,Dan 提到了 Redux 未来的发展方向,并提供了一些他认为在特定领域有希望解决问题的第三方库,有兴趣的朋友们可以提前加一下 star。

  • Declarative data fetching (relay)
  • Built-in optimistic mutations (relay)
  • Easy to test async control flow (redux-observable)
  • Combining local and server data (no idea yet)
  • Great developer experience (redux-devtools)

A cartoon guide to performance in React - Lin Clark

当整个前端开发界都在为 React 而疯狂时,到底又有多少人真正了解 React 本身的实现方式,并愿意继续去思考如何提升 React 在实际应用中的表现呢?

来自 Mozilla 的 Lin Clark 以动画的形式带我们重温了一遍 React 的核心思路并提出了以下四点可以提升 React 在实际应用中表现的方法。


Each child in an array or iterator should have a unique ‘key’ prop.
关于这点推荐大家阅读 @twobin 撰写的 React 源码剖析系列 - 不可思议的 react diff 中有关 element diff 的章节。
shouldComponentUpdate: if I give you next props and state, should I update you?

在 React 中,你可以通过让 shouldComponentUpdate 返回 false 的方式来跳过当前组件的 render 过程以提升渲染效率。

但盲目地相信或使用 shouldComponentUpdate 也会带来一些潜在的问题:

handleClick = () => {
  let nextItems = this.state.items;
  
  nextItems.push(msg);

  this.setState({
    items: nextItems,
  });
}

这是一段我们非常熟悉的使用 setState( ) 方法更新界面的代码,但实际运行的结果却是新的 msg 永远也不会更新到界面上去,原因就是因为 this.state.items 和 nextItems 永远都会指向同一个对象,也就是说 shouldComponentUpdate 永远都会返回 false。而如果你每次都为 nextItems 指定一个新的对象,这样即使 this.state.items 和 nextItems 的真实值是相同的,shouldComponentUpdate 也会返回 true 并执行一遍毫无意义的 render 函数。

关于这个问题的解决方案,让我们来看 Lin 提到的下一个优化点。

Immutability

在 React 中,Immutability 的思路就是如果当前 state 和 nextState 的真实值保持相同,那么他们都将会被指向同一个对象,这样 shouldComponentUpdate 可以快速地返回 false,减少不必要的渲染。而如果当前 state 和 nextState 的真实值不同,就会为 nextState 创建一个新的对象并赋予其真实值,这样 shouldComponentUpdate 就会返回 true 并执行相应的界面更新。

Using setState( ) or connect( ) at lower levels

假设我们有如下的组件:

<List values = {this.state.values} />

// List Component
<div>
  <button>Click</button>
  {
    this.props.values.map((v, i) =>
    <div key={i}>{v.name}</div>)
  }
</div>

如果我们是以上述的方法去架构这个 List 组件,即以数组的形式向子组件传递数据,那么只要这个数组中任意一个元素的值变化了,每次传下去的 props 的值都会是不同的,也就意味着我们需要重新渲染整个 List 组件。

换一种方式,如果我们只从 List 组件向下传递一组 uid,然后让每个 div 根据拿到的 uid 去渲染相应的数据,在上述的相同场景中就可以避免每次都重新渲染整个 List 组件,而只是重新渲染某一个特定的 div,从而加快整体界面的重绘速度。

上述方法总结起来,就是在数据真正变化的那层去 setState( ) 而不是在其上面的某层,但考虑到实际项目中的数据结构很有可能不能满足前端工程师每次都以这样的方式去架构组件,所以这个优化点还需要以后在业务中多加探索,以获得最大的收益成本比。

回到 React 本身,Lin 也为我们解释了 React 本身是如何去加速 DOM 更新的,那就是通过减少和批量处理 DOM 变化的方式。关于减少 DOM 变化,还是推荐大家阅读 @twobin 撰写的 React 源码剖析系列 - 不可思议的 react diff,而关于批量处理 DOM 变化,推荐大家看一下 @杨森 撰写的这篇 React 源码剖析系列 - 解密 setState


Recomposing your React application - Andrew Clark

熟悉 JavaScript 特性的朋友们应该都知道在 js 中可以使用高阶函数来简化一些常用的操作,即编写一个接受其他函数作为参数的函数。

举个例子:

function compose(f, g) {
  return function() {
    return f.call(this, g.apply(this, arguments));
  };
}

var square = function(x) { return x*x };
var sum = function(x,y) { return x+y };
var squareofSum = compose(square, sum);
squareofSum(2,3); // => 25

在 React 中,我们同样可以复用同样的思想来编写一些 higher-order components(高阶组件)为我们的基础组件增加一些新的功能,而 HOC 也就是 Andrew 演讲的主题。

高阶组件可以为输入的原始组件增加新的功能,也非常适合处理一些每个组件都需要去做的重复性工作,比如获取数据并将获得的数据传递下去。另一方面,为了使高阶组件具有更强的可复用性,Andrew 并不建议开发者去写一些本身非常复杂的高阶组件,而是建议将许多简单的高阶组件复合使用以达到实现复杂功能的目的。

举个例子:

const enhance = compose(
  withProps({
    object: { a: 'a', b: 'b' },
    c: 'c'
  }),
  flattenProp('object')
)
const Abc = enhance(BaseComponent)

// Base component receives props: { a: 'a', b: 'b', c: 'c' }

即先使用 withProps 高阶组件向 BaseComponent 传入 props,再使用 flattenProp 高阶组件将传入的 props 拍平,以达到方便 BaseComponent 使用的目的。

看到这里,我相信许多朋友应该会觉得以上的这个例子有些杀鸡焉用牛刀的感觉。的确是这样,从我个人的角度来看,高阶组件最大的意义应该是帮助开发者们去开拓一种新的解决问题的思路,而不是任何问题都盲目地去使用高阶组件解决。Andrew 在他的演讲中也提到,使用高阶组件最大的问题并不来源于技术,而是团队合作。如果你是团队中唯一喜欢经常写高阶组件的开发者,那么当别人来看你的代码时就会感到异常痛苦,因为他经常需要去翻阅许多文件来了解这个组件到底做了一件什么样的事情。

使用高阶组件是一件非常考验使用者架构能力的事情,所以在使用前需要三思后行。

在这一节的最后,还是替 Andrew 推荐一下他所撰写的高阶组件库 recompose,如果各位有时间的话,建议移步 Andrew 的 github 主页阅读下 recompose 的文档,说不定就能发现一个可以节省你许多开发时间的高阶组件呢。

小结

在这次的 ReactEurope Conf 上,共有超过二十位优秀的开发者为听众们带来了非常精彩的演讲。从中我们不仅可以学到许多实用的技巧,更重要的是可以了解到世界范围内的 React 开发者们正在思考并解决一些什么样的问题。因为他们今天所遇到的问题,很有可能就是我们在未来会踩到的坑,只有知道了正在使用的开源框架的优点及局限,我们才能更好地使用他们并避免因为框架本身的缺陷而对我们的项目造成重大影响。

因为这次大会上值得提及的点实在太多,我们不得不将这次大会的小记分为两篇文章,在《ReactEurope 2016 小记 - 下》中,我们将会提到更多关于 GraphQL、Relay 等其他方面的内容,敬请期待。

发布于 2016-06-23

文章被以下专栏收录

    分享关于 React, Flux 在实践中的经验与想法转载、引用前需联系作者,并署名作者且注明文章出处。未加允许,不得转载