原文连接:reactjs.org/docs/thinki…html
在咱们看来React是构建大型快速反应的Web应用的首选方式。咱们已经在Facebook和Instagram里面证明了React可以运行地很是完美。react
React最棒的部分之一是引导咱们思考如何构建一个应用。在本章中,咱们将会带领你领略使用React构建产品搜索应用的全过程。数组
想象咱们已经有了JSON的API和设计手稿。设计手稿以下:bash
[
{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"}
];
复制代码
第一件须要作的事是在设计稿上画出组件(包括子组件)并给他们命名。若是你有一个设计师,那么他们可能早就已经作好了这步工做,只要去问他们就能够了。可能PS上的图层名就是最终的组件名哦。模块化
可是咱们怎么知道什么才是组件呢?就像你决定是否建立一个函数或者对象同样,根据单一功能原则
来决定。组件应该只作一件事,若是组件包含了许多功能那么就须要将它再次分解成更小的组件。函数
因为咱们常常向用户展现JSON数据模型,若是你的模型构建的准确,你都UI(或者说组件结构)就会与数据模型一一对应。这是由于UI与数据模型倾向于遵循相同的信息结构。将UI分红组件,其中组件须要与数据模型中的某一部分向匹配。post
如今咱们看向ProductTable
,咱们会发现表头(包含“Name”和“Price”)不是一个组件,但这能够根据我的喜爱来决定是否要把它提成一个组件。在本例中,咱们把它做为ProductTable
的一部分,由于渲染数据集合本就是ProductTable
的工做。可是若是表头是复杂的(好比包含有排序功能),那么将他提取成ProductTableHeader
就变得有必要了。测试
如今咱们就定义好了咱们的组件,让咱们把它们按照层级排好。在设计稿中出如今其余组件内的组件在层级上应该做为该组件的子组件显式:ui
class ProductCategoryRow extends React.Component {
render() {
const category = this.props.category;
return (
<tr>
<th colSpan="2">
{category}
</th>
</tr>
);
}
}
class ProductRow extends React.Component {
render() {
const product = this.props.product;
const name = product.stocked ?
product.name :
<span style={{color: 'red'}}>
{product.name}
</span>;
return (
<tr>
<td>{name}</td>
<td>{product.price}</td>
</tr>
);
}
}
class ProductTable extends React.Component {
render() {
const rows = [];
let lastCategory = null;
this.props.products.forEach((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>
);
}
}
class SearchBar extends React.Component {
render() {
return (
<form>
<input type="text" placeholder="Search..." />
<p>
<input type="checkbox" />
{' '}
Only show products in stock
</p>
</form>
);
}
}
class FilterableProductTable extends React.Component {
render() {
return (
<div>
<SearchBar />
<ProductTable products={this.props.products} />
</div>
);
}
}
const 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')
);
复制代码
如今咱们已经有了组件的层级图了,接下来就该实现咱们的应用了。最简单的方法是建立一版没有交互仅仅渲染了数据的UI。由于建立静态版本须要编写许多代码并且过程缺乏思考,因此在这个过程当中咱们最好将他们解耦。而添加交互的过程不须要过多的编码,须要的是思考。接下来咱们就来看看为何须要这样作。this
为了建立一个可以渲染数据模型的静态版本的应用,咱们须要建立可以复用其余组件而且将数据做为props传递下去的组件。props是将数据从父组件传递给子组件的一种数据传递方式。若是你熟悉state的概念,请不要在构建静态版本时使用state,由于state存储的数据是变化的而这正适合交互时数据变化的特性,因此state须要为交互设计预留。因此在构建静态版本时咱们不须要使用state。
你能够自上而下或者自下而上地构建应用,换而言之,就是你能够从构建组件层级中最高层开始(如FilterableProductTable
)或者从层级中较低层开始(如ProductRow
)。在简单的应用中,一般来讲自上而下构建应用更加简单,但在大型应用中,自下而上是更好的方法,你能够直接测试你编写的部分。
在这步的最后,咱们将会拥有用来构建应用的组件库。由于如今只是构建静态版本因此每一个组件都只有render()
方法。最高层级组件FilterableProductTable
会将数据模型做为prop传递给其余组件。若是你修改了数据而且再次调用ReactDOM.render()
方法,那么UI将会被更新,你能够看到UI是如何根据数据来变化的。React的单向数据流(也成为单向绑定)使得组件模块化更加易于开发。
理解React中的两种数据模型props和state是很是重要的。若是对于这二者的区别还不是很了解,能够查看state&生命周期,也能够查看FAQ:props和state的区别。
为了是你的UI具备交互能力,就须要具备对基本数据进行修改的能力。React经过state达到这个目的。
为了正确地构建你的应用,首先咱们须要肯定应用须要的最小可修改state集合。肯定集合的关键是:不要重复数据
。确保你的应用能够经过这个数据集计算出其余全部须要的数据。好比,你如今证实构建一个待办事项清单,只须要保存待办事项的数组就能够了,不须要保存待办事项的数量,若是你想要知道待办事项的数目,只须要经过数组的length就能够知道了。
如今咱们来看看应用中须要的数据,咱们有:
如今让咱们看看哪个数据能够做为state保存。在判断的时候问本身如下三个问题:
产品列表是经过props传递的,因此它不是一个state。用户输入的搜索文本和单选框的值是能够随着时间改变的而且不发根据其余值计算出来,因此它们是state。最后,筛选后的产品列表不是state,由于它能够经过原始产品列表,用户输入的搜索文本和单选框的值计算出来。
因此,应用的state是:
class ProductCategoryRow extends React.Component {
render() {
const category = this.props.category;
return (
<tr>
<th colSpan="2">
{category}
</th>
</tr>
);
}
}
class ProductRow extends React.Component {
render() {
const product = this.props.product;
const name = product.stocked ?
product.name :
<span style={{color: 'red'}}>
{product.name}
</span>;
return (
<tr>
<td>{name}</td>
<td>{product.price}</td>
</tr>
);
}
}
class ProductTable extends React.Component {
render() {
const filterText = this.props.filterText;
const inStockOnly = this.props.inStockOnly;
const rows = [];
let lastCategory = null;
this.props.products.forEach((product) => {
if (product.name.indexOf(filterText) === -1) {
return;
}
if (inStockOnly && !product.stocked) {
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;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
class SearchBar extends React.Component {
render() {
const filterText = this.props.filterText;
const inStockOnly = this.props.inStockOnly;
return (
<form>
<input
type="text"
placeholder="Search..."
value={filterText} />
<p>
<input
type="checkbox"
checked={inStockOnly} />
{' '}
Only show products in stock
</p>
</form>
);
}
}
class FilterableProductTable extends React.Component {
constructor(props) {
super(props);
this.state = {
filterText: '',
inStockOnly: false
};
}
render() {
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>
);
}
}
const 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')
);
复制代码
如今咱们已经肯定了应用的最小状态集,接下来咱们须要肯定哪一个组件能够修改或者拥有这些state。
注意:React是单向数据流而且根据组件层级自上而下传递的。对于初次使用React的开发者来讲可能没法快速清晰地了解什么组件应该拥有什么state,因此让咱们按照下面的步骤来肯定:
对于应用中的每一个state:
如今让咱们将上述策略使用在咱们的应用上:
ProductTable
须要根据state筛选产品列表,SearchBar
须要展现用户输入和单选框FilterableProductTable
是共同全部者FilterableProductTable
上是合理的最终,咱们把state放在FilterableProductTable
组件中。首先,在FilterableProductTable
的构造函数中添加属性this.state = {filterText: '', inStockOnly: false}
来展现应用的初始状态。以后将filterText
和inStockOnly
做为props传递给ProductTable
和SearchBar
。最后根据props在ProductTable
筛选出对应的产品,在SearchBar
中设置相应的值。
如今咱们能够来看看应用是怎么表现的了,将filterText
的值设为ball
,而后刷新页面,你就能够看到产品列表更新了。
class ProductCategoryRow extends React.Component {
render() {
const category = this.props.category;
return (
<tr>
<th colSpan="2">
{category}
</th>
</tr>
);
}
}
class ProductRow extends React.Component {
render() {
const product = this.props.product;
const name = product.stocked ?
product.name :
<span style={{color: 'red'}}>
{product.name}
</span>;
return (
<tr>
<td>{name}</td>
<td>{product.price}</td>
</tr>
);
}
}
class ProductTable extends React.Component {
render() {
const filterText = this.props.filterText;
const inStockOnly = this.props.inStockOnly;
const rows = [];
let lastCategory = null;
this.props.products.forEach((product) => {
if (product.name.indexOf(filterText) === -1) {
return;
}
if (inStockOnly && !product.stocked) {
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;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
this.handleInStockChange = this.handleInStockChange.bind(this);
}
handleFilterTextChange(e) {
this.props.onFilterTextChange(e.target.value);
}
handleInStockChange(e) {
this.props.onInStockChange(e.target.checked);
}
render() {
return (
<form>
<input
type="text"
placeholder="Search..."
value={this.props.filterText}
onChange={this.handleFilterTextChange}
/>
<p>
<input
type="checkbox"
checked={this.props.inStockOnly}
onChange={this.handleInStockChange}
/>
{' '}
Only show products in stock
</p>
</form>
);
}
}
class FilterableProductTable extends React.Component {
constructor(props) {
super(props);
this.state = {
filterText: '',
inStockOnly: false
};
this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
this.handleInStockChange = this.handleInStockChange.bind(this);
}
handleFilterTextChange(filterText) {
this.setState({
filterText: filterText
});
}
handleInStockChange(inStockOnly) {
this.setState({
inStockOnly: inStockOnly
})
}
render() {
return (
<div>
<SearchBar
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
onFilterTextChange={this.handleFilterTextChange}
onInStockChange={this.handleInStockChange}
/>
<ProductTable
products={this.props.products}
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
</div>
);
}
}
const 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')
);
复制代码
到目前为止,咱们已经根据自上而下传递的props和state正确地渲染了一个应用了。接下来让咱们来使用另外一种数据传递方式:在组件层级深处的表单组件须要在FilterableProductTable
处更新它的staet。
React显式地声明数据以使咱们可以更好地了解程序是如何运行的,但这的确比传统的双向绑定繁琐一些。
若是你如今点击单选框或者在输入框内输入,你会发现页面不会有任何改变,React无视了你的操做。React是故意这么作的,由于咱们设置了传递的prop值与FilterableProductTable
中的state值是相同的。
那么让咱们来想一想咱们须要让程序如何运行。咱们想要在用户输入时,可以更新state以反应用户的输入。由于组件只能更新自身的state,因此FilterableProductTable
须要向SearchBar
传递一个回调函数来实时更新state。咱们可使用<input>
标签上的onChange
方法来通知状态更新。在用户输入时,FilterableProductTable
传递的回调函数将会调用setState()
方法来更新应用状态。
但愿本章可以为你使用React构建组件和应用提供思路。尽管这相较之前来讲须要写更多的代码,但请记住,阅读代码的时间每每比写代码的时间多得多。这样模块化,显式地编写代码能让你更加容易地阅读代码。当你开始编写大型的组件库时会清晰地意识到代码模块化,显式化的重要性。随着代码复用的增多,你的应用的行数也会逐渐减小。