一、动机:关注分离
通过复用那些接口定义良好的组件来开发新的模块化组件,我们得到了与使用函数和类相似的好处。具体来说就是能够通过开发简单的组件把程序的不同关注面分离。如果为程序开发一套自定义的组件库,那么就能以最适合业务场景的方式来展示你的用户界面。
<div id="example"></div>
<script type="text/jsx">
//组合实例
var Avatar = React.createClass({
render: function () {
return (
<div>
<AvatarPic username = {this.props.username} />
<AvatarLink username = {this.props.username} />
</div>
);
}
});
var ProfilePic = React.createClass({
render: function () {
return (
<div className="avatar-pic">
<img width="100" height="100" alt="头像" src={'http://graph.facebook.com/'+ this.props.username + '/picture'} />
</div>
)
}
});
var ProfileLink = React.createClass({
render: function () {
return (
<div className="avatar-link">
<a href={'http://graph.facebook.com/'+ this.props.username}>
{this.props.username}
</a>
</div>
)
}
});
React.render(
<Avatar username = "qqli" />, //pwh
document.getElementById('example')
)
/**
* 注意事项:
* ProfilePic组件: 首字母需要大写
* {this.props.username}:调用属性值,需要使用{}包裹,作为属性值中有字符串的情况href={'http://graph.facebook.com/'+ this.props.username}
*/
</script>
二、从属关系
上面例子中,Avatar
拥有 ProfilePic
和 ProfileLink
的实例。拥有者 就是给其它组件设置 props
的那个组件。更正式地说, 如果组件 Y
在 render()
方法是创建了组件 X
,那么 Y
就拥有 X
。上面讲过,组件不能修改自身的 props
- 它们总是与它们拥有者设置的保持一致。这是保持用户界面一致性的关键性原则。
把从属关系与父子关系加以区别至关重要。从属关系是 React
特有的,而父子关系简单来讲就是DOM
里的标签的关系。在上一个例子中,Avatar
拥有 div
、ProfilePic
和 ProfileLink
实例,div
是 ProfilePic
和 ProfileLink
实例的父级(但不是拥有者)。
三、子级
实例化 React
组件时,你可以在开始标签和结束标签之间引用在React
组件或者Javascript
表达式:
<Parent><Child /></Parent>
Parent
能通过专门的 this.props.children props
读取子级。this.props.children
是一个不透明的数据结构: 通过 React.Children
工具类 来操作。
四、子级校正(Reconciliation)
校正就是每次 render
方法调用后 React
更新 DOM
的过程。 一般情况下,子级会根据它们被渲染的顺序来做校正。例如,下面代码描述了两次渲染的过程:
// 第一次渲染
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// 第二次渲染
<Card>
<p>Paragraph 2</p>
</Card>
直观来看,只是删除了<p>Paragraph 1</p>
。事实上,React
先更新第一个子级的内容,然后删除最后一个组件。React
是根据子级的顺序来校正的。
五、子组件状态管理
对于大多数组件,这没什么大碍。但是,对于使用 this.state
来自多次渲染过程中里维持数据的状态化组件,这样做潜在很多问题。
多数情况下,可以通过 隐藏组件而不是删除 它们来绕过这些问题。
// 第一次渲染
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// 第二次渲染
<Card>
<p style={{display: 'none'}}>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
六、动态子级
如果子组件位置会改变(如在搜索结果中)或者有新组件添加到列表开头(如在流中)情况会变得更加复杂。如果子级要在多个渲染阶段保持自己的特征和状态,在这种情况下,你可以通过给子级设置惟一标识的 key
来区分。
render: function() {
var results = this.props.results;
return (
<ol>
{results.map(function(result) {
return <li key={result.id}>{result.text}</li>;
})}
</ol>
);
}
当 React
校正带有 key
的子级时,它会确保它们被重新排序(而不是破坏)或者删除(而不是重用)。 务必 把 key
添加到子级数组里组件本身上,而不是每个子级内部最外层 HTML 上:
// 错误!
var ListItemWrapper = React.createClass({
render: function() {
return <li key={this.props.data.id}>{this.props.data.text}</li>;
}
});
var MyComponent = React.createClass({
render: function() {
return (
<ul>
{this.props.results.map(function(result) {
return <ListItemWrapper data={result}/>;
})}
</ul>
);
}
});
// 正确 :)
var ListItemWrapper = React.createClass({
render: function() {
return <li>{this.props.data.text}</li>;
}
});
var MyComponent = React.createClass({
render: function() {
return (
<ul>
{this.props.results.map(function(result) {
return <ListItemWrapper key={result.id} data={result}/>;
})}
</ul>
);
}
});
也可以传递 object
来做有 key
的子级。object
的 key
会被当作每个组件的 key
。但是一定要牢记 JavaScript
并不总是保证属性的顺序会被保留。实际情况下浏览器一般会保留属性的顺序,除了 使用 32位无符号数字做为 key
的属性。数字型属性会按大小排序并且排在其它属性前面。一旦发生这种情况,React
渲染组件的顺序就会混乱。可以在 key
前面加一个字符串前缀来避免:
render: function() {
var items = {};
this.props.results.forEach(function(result) {
// 如果 result.id 看起来是一个数字(比如短哈希),那么
// 对象字面量的顺序就得不到保证。这种情况下,需要添加前缀
// 来确保 key 是字符串。
items['result-' + result.id] = <li>{result.text}</li>;
});
return (
<ol>
{items}
</ol>
);
}
7、数据流
React
里,数据通过上面介绍过的 props
从拥有者流向归属者。这就是高效的单向数据绑定(one-way data binding)
:拥有者通过它的 props
或 state
计算出一些值,并把这些值绑定到它们拥有的组件的 props
上。因为这个过程会递归地调用,所以数据变化会自动在所有被使用的地方自动反映出来。
8、性能提醒
你或许会担心如果一个拥有者有大量子级时,对于数据变化做出响应非常耗费性能。值得庆幸的是执行 JavaScript
非常的快,而且 render()
方法一般比较简单,所以在大部分应用里这样做速度极快。此外,性能的瓶颈大多是因为 DOM
更新,而非 JS
执行,而且 React
会通过批量更新和变化检测来优化性能。
但是,有时候需要做细粒度的性能控制。这种情况下,可以重写 shouldComponentUpdate
() 方法返回 false
来让 React
跳过对子树的处理。参考 React reference docs 了解更多。
注意:
如果在数据变化时让shouldComponentUpdate
() 返回false
,React
就不能保证用户界面同步。当使用它的时候一定确保你清楚到底做了什么,并且只在遇到明显性能问题的时候才使用它。不要低估JavaScript
的速度,DOM
操作通常才是慢的原因。