React交互动态UI

2017年2月11日更新

Interactivity and Dynamic UIs - React

2017年2月11日注:官网原文已删除。

概述

你已经学会了怎样用React展示数据。现在让我们来看一下怎样让我们的UI变得能互动吧。

从一个原型设计开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? 'like' : 'haven\'t liked';
return (
<p onClick={this.handleClick}>
You {text} this. Click to toggle.
</p>
);
}
});
ReactDOM.render(
<LikeButton />,
document.getElementById('example')
);

事件处理和自定义事件

通过React你只需要将事件处理器作为属性(驼峰命名规则)传递进去就行,就像在一般的HTML中所做的那样。React通过一个人工的事件系统(中间层),保证了在所有的浏览器中,所有的事件处理都一样。也就是说,React根据标准,知道怎么去处理事件冒泡和事件捕获,并且保证了传递到你的事件处理器中的事件是与W3C标准一致的,不管你用的什么浏览器都一样。

底层:自动绑定和事件委托

为了保证你的代码的高性能,React在底层做了一些很容易理解的工作。

自动绑定:当用JavaScript创建回调函数时,你经常需要明确地绑定一个方法给当前实例,这样this指针的值才能正确。用React,所有的方法都是自动绑定到了当前组件实例(除非用ES6的class)。React缓存了绑定方法,这样也就使得CPU和内存非常高效。同时敲的代码也少了。

事件委托:React实际上不附加事件处理器给节点本身。当React启动的时候,会通过一个单一的事件处理器开始监听所有顶级事件。当一个组件被挂载或者被卸载时,事件处理器只是在一个内部映射表中添加项或者删除项。当一个事件发生时,React知道怎么通过映射表去分派事件处理。如果在映射表中没有事件处理器时,React不作任何处理。想知道为什么这样做更快,可以阅读参考2

组件只是状态机

React认为UI跟状态机一样简单。认为UI处于不同状态中并渲染,通过这种理念很容易使你的UI持久化。

在React中,你只是更新一个组件的状态,然后基于这个新的状态渲染出了一个新的UI。React用最高效的方式为你处理了DOM的更新。

状态是怎么工作的

一般通知React数据改变了时调用setState(data, callback)方法。这个方法讲数据合并到了this.state中,并且重新渲染了组件。让组件完成渲染时,可选参数callback被调用。由于React会保证你的UI最新,所以大多数情况下你都不需要提供一个回调函数。

组件需要有些什么状态?

你的大多数组件应该只负责从属性中拿数据并且渲染。不过有时你需要对用户输入、服务器请求或者随时间变化而变化的因素做响应。这种情况下你就需要用状态。

尽量试着让你的组件无状态。通过这样做你会分离出状态到它符合逻辑地最应该在的位置并且使冗余性最低,使得更容易合理化你的应用。

一个常用的模式是创建几个渲染数据的无状态的组件,然后在层级结构中相对最顶层加一个富状态组件(stateful component),该组件通过属性传递自己的状态给其子层。含有多个状态的组件包含了所有的相互作用逻辑,而无状态组件负责通过声明式的方法渲染组件。

哪些数据应该存放在状态中?

组件的状态应该包含该组件的事件处理器可能改变触发UI更新的数据在真实的应用中,这种数据倾向于很小,并且都是JSON序列化的。当创建一个富状态组件时,考虑一下状态可能的最小集,然后仅存那些在this.state中的属性。在render()内部再基于状态去计算其他你需要的信息。你会发现用这种方法考虑和编写的应用是最正确的,因为在状态中添加冗余数据或者可计算值意味着你需要明确地保持这种数据的同步,而你不能指望React帮你计算这些数据。

哪些数据不应该存放在状态中?

this.state应该只包含需要表征你UI状态的最小数据量。也就是说,它不应该包含:

  • 可计算的数据:别担心通过状态去提前计算你需要的值 —— 如果你用render()方法做所有的逻辑计算处理,持久化UI会变得很容易。举个例子,如果你在状态中有一个数组(listItems)并且你需要把数组长度渲染成字符串,只要在你的render()方法中处理this.state.listItems.length + ‘list items’就可以,而不需要把它存储在状态中。
  • React组件:根据属性和状态在render()方法中创建组件。
  • 属性中的副本数据:可能的话试着用属性作为真实数据源。作为父组件的重新渲染的结果,属性可能会改变,所以知道属性在父组件或者更之前的值再把属性放到状态中的用法也是一种正确的用法。

参考

  1. Interactivity and Dynamic UIs - React
  2. How JavaScript Event Delegation Works - David Walsh