下面是React官方文档中的Thinking inReact文章的翻译,第一次翻译英文的文章,肯定有非常多不对的地方,还望多多包涵。
原文地址:https://facebook.github.io/react/docs/thinking-in-react.html
原文開始
---------------------------------------------我是分隔符------------------------------------------
Thinking in React
by Pete Hunt
React是什么?我的看法是。它是用javascript创建大型。高速web应用的首要方式。Fackbook和Instagram为我们做了非常好的測试。
React的当中一个很厉害的部分就是它是让你在构建你的应用时如何去思考的。接下来,我会带你体验一下如何使用React去构建一个有搜索功能的产品数据表。
Start with a mock
想象一下我们已经有了JSON数据的API和设计师给的站点模型。我们的设计师明显不怎么样由于他给的模型是这种:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
我们的JSON接口返回的数据是这种:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
Step 1: Break the UI into a component hierarchy(把UI切割成组件层)
你要做的第一件事就是在组件周围画上盒子而且给它们取一个名字。有过你和网页设计师一起工作。那他们可能已经做过这个工作了,去和他们谈谈。
他们的
photoshop layer的名字,最后应该就会成为你的React组件的名字。
可是你怎么知道谁应该成为一个单独组件呢?用这种方法就能够了:依据你是否须要去创建一个新的方法或者对象就能够决定是否是一个组件。这就是单一职责原则,原则就是:一个组件在理想状态下仅仅做一件事情。假设之后须要扩展。那么就把它切割成子组件。
假设你常常向用户展示JSON数据模型。你就会发现假设你的模型建立正确的话。你的UI(也就是你的组件结构)的布局就不会有问题。由于UI和数据模型都是依靠同样的信息结构,这也就以为这把你的UI分隔成组件是非常寻常的。
去把UI分隔成代表单块数据模型的组件吧。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
你能够看到在我们简单地应用中有五个组件,
1.FilterableProductTable(橙色): 包括了应用这个总体。
2.SearchBar(蓝色):接收了用户输入
3.ProductTable(绿色):根据用户输入展示和过滤数据
4.ProductCategoryRow(天蓝色)区域标题
5.ProductRow(红色):展示每一个产品的行
看一下ProductTable,你就会发现表头(包括‘Names’和‘Price’标签)不是它自己的组件。这是一种偏好,对此有非常多的争论。在这个样例中。我把它放在了ProductTable里边,是由于它是渲染ProductTable数据集中的一部分。然而,假设这个表头变复杂了(比如:我们增加了排序功能),它就得建立自己的ProductTableHeader元素。如今我们已经确认了我们用例中的组件,那就把它们层次化。组件中的小组件在层次化中应该表现成子类。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
Step 2: Build a static version in React(在React中建立一个静态版本号)
如今我们的组件已经层次化了。是时间去实现我们的应用了。最简单的方式是建立一个UI是依靠你的数据模型渲染的可是并没有交互的版本号。最好要拆解这些步骤。由于建立一个静态的版本号须要在没有想法的情况下写非常多的代码,加入交互就须要思考非常多而且写非常少的代码。
以下会知道为什么。
建立一个渲染你的数据模型的应用的静态版本号,你会想去建立能复用其它组建的组件,而且使用props传递数据。props是父辈向下传递数据的方式。假设你非常熟悉state的概念。不要在这个静态版本号里面用state。state不过为了交互而存在,里边的数据是一直在变的。尽然这是一个静态版本号,那就不要用state。
你能够从上往下或反向建立。也就是你能够根据应用的层级从高层向底层(比如从FilterableProductTable開始)或反向实现(从ProductRow開始)。再简单的样例中。从上至下更easy,在大型项目中,从下向上地建立和測试应用更简单。
在这一步的最后,你会用一个可复用的组件库去渲染你的数据模型。
既然这是一个应用的静态版本号,那么组件就仅仅会拥有render()方法。在组件层顶端的组件就会把数据模型作为prop。
假设你改动底层的数据模型。然后再次调用ReactDOM.render(),UI会被更新。
非常easy就能够看到你的UI如何更新,哪里做了改变,之后就没有什么复杂的东西了。React是依靠单向数据流使得全部事情模块化和高速。
var ProductCategoryRow = React.createClass({
render: function() {
return (<tr><th colSpan="2">{this.props.category}</th></tr>);
}
}); var ProductRow = React.createClass({
render: function() {
var name = this.props.product.stocked ?
this.props.product.name :
<span style={{color: 'red'}}>
{this.props.product.name}
</span>;
return (
<tr>
<td>{name}</td>
<td>{this.props.product.price}</td>
</tr>
);
}
}); var ProductTable = React.createClass({
render: function() {
var rows = [];
var lastCategory = null;
this.props.products.forEach(function(product) {
if (product.category !== lastCategory) {
rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}); var SearchBar = React.createClass({
render: function() {
return (
<form>
<input type="text" placeholder="Search..." />
<p>
<input type="checkbox" />
{' '}
Only show products in stock
</p>
</form>
);
}
}); var FilterableProductTable = React.createClass({
render: function() {
return (
<div>
<SearchBar />
<ProductTable products={this.props.products} />
</div>
);
}
}); var PRODUCTS = [
{category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
{category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
{category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
{category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
{category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
{category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
]; ReactDOM.render(
<FilterableProductTable products={PRODUCTS} />,
document.getElementById('container')
);
Step 3: Identify the minimal (but complete) representation of UI state(找到最小的UI state的代表)
为了实现你的UI的交互性。你须要去触发你的底层数据模型的改变。
React通过运用state使这变得非常easy。
为了正确的创建你的应用。第一步应该考虑的是你的应用须要的最小单位的可变state。
关键技巧就是:不要反复。找出你的应用须要的state绝对最小代表,然后去推算你须要的全部其它东西。
比如:假设你在建一个TODO列表。那就只在这TODO items周围保存一个数组;不要为了数组长度去保存一个state,假设须要,就去取数组长度。
想一下我们应用中的全部数据片段。我们有:
1.最原始的产品列表
2.用户输入的搜索文本
3.checkbox的值
4.被筛选的产品
我们挨个看一下,找出哪一个是state。对于每个数据片段提三个问题:
1.这是从父辈传过来的props吗?
假设是。则它不是state。
2.它实时在改变吗?假设不是,则不是state。
3.你能在组件中通过其它的state或者props得出它来吗?假设能,则不是state。
最原始的产品清单是被当做props传递的,它不是state。搜索文本和checkbox看起来像是state,由于它们实时在变并且不能通过其他数据得出。
最后,被筛选的产品列表不是state,由于它可以通过搜索文本,checkbox和原始产品清单得出。
所以,我们的state就是:搜索文本和checkbox的值。
Step 4: Identify where your state should live(确认你的state应该放在哪)
好的。这样我们就确认了应用中state的最小单位(set)。下一步 ,我们须要确认哪一个组件改变或者拥有这个state。
请记住,react在我们的组件层次中是单向数据流动的。
可能不会一下就看明确哪个组件拥有哪个state。这一点对新手来说常常是最有挑战性的部分,所以根据下面步骤能够弄明确:
对于你的应用中每个state:
-确认每个依靠这个state去渲染sth的组件
-找到一个组件共同拥有者(在组件层次中,位于全部组件上层的单独组件也须要这个state)
-无论是组件共同拥有者还是组件层中的上层组件都应该拥有这个state
-假设你找不到一个拥有这个state的组件,那就创建一个功能仅仅是保存这个state的组件,把它放到组件共同拥有者的上方。
我们在当前的应用中试一下上边的策略。
-ProductTable须要根据state去筛选产品列表。SearchBar展示搜索文本和checkbox状态也须要state。
-组件共同拥有者是FilterableProductTable.
-把filter text和checked value放在FilterableProductTable里边从概念上来说是行的通的。
Cool,我们就这样愉快地决定了把state放在FilterableProductTable里边。首先,在FilterableProductTable里加入一个getInitialState()方法。返回值是{filterText: '', inStockOnly: false},反映了应用的初始state。
然后,把filterText和inStockOnly当做prop传入ProductTable和SearchBar中。
最后,用这些props过滤ProductTable中的rows,设置SearchBar中的
表单字段的值。
如今你就能看到你的应用是怎样执行的:将filterText设为“ball”然后刷新应用。你就能看到数据表正确更新了。
var ProductCategoryRow = React.createClass({
render: function() {
return (<tr><th colSpan="2">{this.props.category}</th></tr>);
}
}); var ProductRow = React.createClass({
render: function() {
var name = this.props.product.stocked ? this.props.product.name :
<span style={{color: 'red'}}>
{this.props.product.name}
</span>;
return (
<tr>
<td>{name}</td>
<td>{this.props.product.price}</td>
</tr>
);
}
}); var ProductTable = React.createClass({
render: function() {
var rows = [];
var lastCategory = null;
this.props.products.forEach(function(product) {
if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
return;
}
if (product.category !== lastCategory) {
rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
}.bind(this));
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}); var SearchBar = React.createClass({
render: function() {
return (
<form>
<input type="text" placeholder="Search..." value={this.props.filterText} />
<p>
<input type="checkbox" checked={this.props.inStockOnly} />
{' '}
Only show products in stock
</p>
</form>
);
}
}); var FilterableProductTable = React.createClass({
getInitialState: function() {
return {
filterText: '',
inStockOnly: false
};
}, render: function() {
return (
<div>
<SearchBar
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
<ProductTable
products={this.props.products}
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
</div>
);
}
}); var PRODUCTS = [
{category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
{category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
{category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
{category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
{category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
{category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
]; ReactDOM.render(
<FilterableProductTable products={PRODUCTS} />,
document.getElementById('container')
);
STEP 5:Add inverse data flow(加入反向数据流)
到这里,我们建立了一个可以依据state和prop在模块层间的流动去正确渲染的应用。
如今是时候去支持数据的还有一种方式流动:在深层次中的表单组件须要在FilterableProductTable里去更新state。React中的这样的数据流动很明白清晰,所以可以很easy得理解你的应用是如何工作的。可是这须要比通常的双向数据绑定写很多其它的代码。React提供了额外的一个叫做ReactLink的工具让这样的模式变得像双向数据绑定一样的舒服,这样的目的就是,让全部的事情变得更明白。
假设你尝试着去在近期版本号例子里去写代码或检查checkbox,你会看到React忽略了你的输入。这是有意为之的,由于我们设置了input的prop一直是和FilterableProductTable传过来的state同样的。 我们来想一下我们想要什么样的事情发生。我们想确保不管用户什么时候改变表单。我们就去更新state以便反映出用户的输入。
既然组件应该只更新他们自己的state,FilterableProductTable会传递给SearchBar回调函数,在state须要更新的时候就会调用。
我们能够在input上使用onchange事件去通知调用callback。
被FilterableProductTable传递过来的callback会调用setState(),然后应用就会被更新了。
虽然这听起来非常复杂,可是真的仅仅是须要几行代码。而且你的应用中数据的流动过程会非常的清晰。And that's it。
但愿这篇文章可以帮助你弄清晰怎么用React去建立组件和应用。虽然这要比平时多写一些代码,可是请记住代码的可读性的重要性要远远超过代码的书写,阅读这些模块化和很清晰的代码是很easy的。当你開始建立大型的组件库。你就会感谢这样的明白性和模块化,随着代码的复用。你的代码量就会開始降低了。
var ProductCategoryRow = React.createClass({
render: function() {
return (<tr><th colSpan="2">{this.props.category}</th></tr>);
}
}); var ProductRow = React.createClass({
render: function() {
var name = this.props.product.stocked ?
this.props.product.name :
<span style={{color: 'red'}}>
{this.props.product.name}
</span>;
return (
<tr>
<td>{name}</td>
<td>{this.props.product.price}</td>
</tr>
);
}
}); var ProductTable = React.createClass({
render: function() {
var rows = [];
var lastCategory = null;
this.props.products.forEach(function(product) {
if (product.name.indexOf(this.props.filterText) === -1 || (!product.stocked && this.props.inStockOnly)) {
return;
}
if (product.category !== lastCategory) {
rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
}.bind(this));
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}); var SearchBar = React.createClass({
handleChange: function() {
this.props.onUserInput(
this.refs.filterTextInput.value,
this.refs.inStockOnlyInput.checked
);
},
render: function() {
return (
<form>
<input
type="text"
placeholder="Search..."
value={this.props.filterText}
ref="filterTextInput"
onChange={this.handleChange}
/>
<p>
<input
type="checkbox"
checked={this.props.inStockOnly}
ref="inStockOnlyInput"
onChange={this.handleChange}
/>
{' '}
Only show products in stock
</p>
</form>
);
}
}); var FilterableProductTable = React.createClass({
getInitialState: function() {
return {
filterText: '',
inStockOnly: false
};
}, handleUserInput: function(filterText, inStockOnly) {
this.setState({
filterText: filterText,
inStockOnly: inStockOnly
});
}, render: function() {
return (
<div>
<SearchBar
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
onUserInput={this.handleUserInput}
/>
<ProductTable
products={this.props.products}
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
</div>
);
}
}); var PRODUCTS = [
{category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
{category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
{category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
{category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
{category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
{category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
]; ReactDOM.render(
<FilterableProductTable products={PRODUCTS} />,
document.getElementById('container')
);
------------------------------------------------我是分隔符--------------------------------------------------
原文结束。
本文主要是根据一个产品标的样例来介绍React的单向数据流的思想,从一開始如何分析站点模型的结构,如何划分组件。组件中的数据。数据之间的关系,根据这些关系,有固定的方法来帮助我们决定哪一些数据属于state,哪一些是prop,确定了state之后,就能够给state依存的组件加入监听方法。监听input输入的变化。从而实时更新数据模型。又一次渲染界面。