Reactjs入门官方文档(十一)【Thinking in React】
React 的编程思想在我们看来,React 是 JavaScript 构建大型,高性能 Web 应用的首选。在 Facebook 和 Instagram 中都能很好的应用。 React 中许多重要部分之一是思考如何构建应用程序。 在本文档中,我们将引导你完成用 React 构建一个可搜索的产品数据表的思考过程。 从一个线框图开始设想你已经根据我们的设计有一个模拟JSON API。这个Mock 看上去如下: 我们的JSON API 返回的数据如下: [
{category: "Sporting Goods",price: "$49.99",stocked: true,name: "Football"},{category: "Sporting Goods",price: "$9.99",name: "Baseball"},price: "$29.99",stocked: false,name: "Basketball"},{category: "Electroincs",price: "$99.99",name: "iPod Touch"},price: "$399.99",name: "iPhone 5"},price: "$199.99",name: "Nexus 7"}
]
步骤1:将 UI 拆解到组件层次结构中第一件事在mock 中给每一个组件(子组件)画框并进行命名。他们可能已经做了这些工作,所以去和他们交流一下!他们的 Photoshop 图层名称可能最终成为你的 React 组件名称(愚人码头注:意思是可以和设计师交流一下,约定图层命名规范)! 但是你该如何拆分组件呢?其实只需要像拆分一个新方法或新对象一样的方式即可。一个常用的技巧是单一职责原则single responsibility principle,也就是说,理论上一个组件只做一件事。如果组件变大,它应该被拆分成几个小组件。 由于你经常展示一个JSON 数据模型给用户,你会发现如果你的模型被正确构建,那么你的UI(和你的组件框架)会非常好的匹配。那是因为UI 和数据模型总是依附相同的信息架构(information architecture),也就是意味着分离你的UI 成为组件是非常容易的。只要正确地的将你的每一个数据模型对应组件就可以了。 你会发现在我们这个简单的app中有5 个组件。我们使用斜体数据来表示每一个组件
如果你看 现在,我们已经在线框图中做了标识,让我们对它们进行层级结构排列。这很简单。在线框图中,出现在其它组件内的组件,应该在层次结构中显示为子组件即可:
步骤2: 用 React 构建一个静态版本在CodePen 上尝试 class ProductCategoryRow extends React.Component {
render() {
return <tr><th colSpan="2">{this.props.category}</th></tr>;
}
}
class ProductRow extends React.Component {
render() {
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>
);
}
}
class ProductTable extends React.Component {
render() {
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>
);
}
}
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>
);
}
}
var PRODUCTS = [
{category: 'Sporting Goods',price: '$49.99',stocked: true,name: 'Football'},{category: 'Sporting Goods',price: '$9.99',name: 'Baseball'},price: '$29.99',stocked: false,name: 'Basketball'},{category: 'Electronics',price: '$99.99',name: 'iPod Touch'},price: '$399.99',name: 'iPhone 5'},price: '$199.99',name: 'Nexus 7'}
];
ReactDOM.render(
<FilterableProductTable products={PRODUCTS} />,document.getElementById('container')
);
现在你有了自己的组件系统,是时候实现你的app 了。最简单的方式的是个构建一个没有交互的只是将数据渲染成UI。最好解耦这些过程,因为构建一个静态版本需要许多代码不需要思考,添加交互需要更多思考和少量的代码。我们会看到为什么。 为了构建一个静态的app 去渲染你的数据模型(data model),你想要构建组件并复用其他的组件同使用props 去传递数据。props 是从父组件传递给子组件数据的一种方式。如果你了解state 的概念,在构建静态版本时,一点也不需要使用state(don’t use state at all)。State 只用于交互,也就是说,数据可以随时被改变。因为是静态版本,所以你不需要state。 在构建时你可以自上而下(top-down)或自下而上(down-top)。也就是说,你可以从位于高层级的组件(例如, 在此最后,你有了许多可复用的组件来渲染你的数据模型。因为是静态版本的应用,所以这些组件中仅有 如果你在执行这一步的时候需要帮助,可以简单浏览React doc。 小插曲: Props(属性) vs State(状态)在 React 中有两种类型的“模型”数据:props(属性) 和 state(状态)。理解这两者的差异非常重要;如果你不确定它们之间的区别,请参阅官方 React 文档the offical React docs 步骤3: 确定 UI state(状态) 的最小(但完整)表示为了你的 UI 可以交互,你需要能够触发更改底层的数据模型。React 通过 要正确的构建应用程序,你首先需要考虑你的应用程序需要的可变 state(状态) 的最小集合。这里的关键是:不要重复你自己 (DRY,don’t repeat yourself)。找出你的应用程序所需 state(状态) 的绝对最小表示,并且可以以此计算出你所需的所有其他数据内容。例如,如果你正在构建一个 TODO 列表,只保留一个 TODO 元素数组即可;不需要为元素数量保留一个单独的 state(状态) 变量。相反,当你要渲染 TODO 计数时,只需要获取 TODO 数组的长度即可。 考虑我们在实例应用程序中所有的数据块。我们有:
让我们仔细分析每一个数据,弄清楚哪一个是 state(状态) 。请简单地提出有关每个数据的 3 个问题:
所以最终,我们的state 是:
步骤4:确定 state(状态) 的位置在CodePen 上尝试 class ProductCategoryRow extends React.Component {
render() {
return (<tr><th colSpan="2">{this.props.category}</th></tr>);
}
}
class ProductRow extends React.Component {
render() {
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>
);
}
}
class ProductTable extends React.Component {
render() {
var rows = [];
var lastCategory = null;
this.props.products.forEach((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;
});
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..." value={this.props.filterText} />
<p>
<input type="checkbox" checked={this.props.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>
);
}
}
var PRODUCTS = [
{category: 'Sporting Goods',document.getElementById('container')
);
OK,现在我们已经确认了app state 的最小集合。Next,我们需要确认一个组件改变或拥有这些 state。 记住:React 单向数据流在层级中自上而下进行。它可能并不立即清楚那一个组件应该拥有什么state。这个通常对新手去了解是充满挑战的,所以根据接下来的步骤来搞清楚它: 对于你应用中的每一个state :
我们在我们的应用中贯穿这个策略: 那么我们已经决定 state(状态) 保存在 FilterableProductTable 中。首先,添加一个实例属性 this.state = {filterText: ”,inStockOnly: false} 到 FilterableProductTable 的constructor 来反映你应用的初始 state(状态) 。然后,将 你可以看你的应用的行为:设置 步骤5:添加反向数据流在CodePen 上尝试 class ProductCategoryRow extends React.Component {
render() {
return (<tr><th colSpan="2">{this.props.category}</th></tr>);
}
}
class ProductRow extends React.Component {
render() {
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>
);
}
}
class ProductTable extends React.Component {
render() {
var rows = [];
var lastCategory = null;
console.log(this.props.inStockOnly)
this.props.products.forEach((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;
});
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.handleFilterTextInputChange = this.handleFilterTextInputChange.bind(this);
this.handleInStockInputChange = this.handleInStockInputChange.bind(this);
}
handleFilterTextInputChange(e) {
this.props.onFilterTextInput(e.target.value);
}
handleInStockInputChange(e) {
this.props.onInStockInput(e.target.checked);
}
render() {
return (
<form>
<input
type="text"
placeholder="Search..."
value={this.props.filterText}
onChange={this.handleFilterTextInputChange}
/>
<p>
<input
type="checkbox"
checked={this.props.inStockOnly}
onChange={this.handleInStockInputChange}
/>
{' '}
Only show products in stock
</p>
</form>
);
}
}
class FilterableProductTable extends React.Component {
constructor(props) {
super(props);
this.state = {
filterText: '',inStockOnly: false
};
this.handleFilterTextInput = this.handleFilterTextInput.bind(this);
this.handleInStockInput = this.handleInStockInput.bind(this);
}
handleFilterTextInput(filterText) {
this.setState({
filterText: filterText
});
}
handleInStockInput(inStockOnly) {
this.setState({
inStockOnly: inStockOnly
})
}
render() {
return (
<div>
<SearchBar
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
onFilterTextInput={this.handleFilterTextInput}
onInStockInput={this.handleInStockInput}
/>
<ProductTable
products={this.props.products}
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
/>
</div>
);
}
}
var PRODUCTS = [
{category: 'Sporting Goods',document.getElementById('container')
);
到现在为止,我们已经建立了一个应用程序,进行正确渲染将props 和state 作为功能在层级中向下流动。现在,是时候添加支持数据从另一个方法流动:在层级中最深出的form 组件去更新位于 Reack 为了使数据流明确,使它能够容易的了解你的程序是如何工作的,但是它需要更多的数据相比传统的双线数据绑定(two-way data binding) 如果在这个例子版本中进行键入信息或操作多选框,你会发现React 忽略了你的输入。这是趋势,通过设置 想想我们希望发生什么。我们期望当用户改变表单输入的时候,我们更新 state(状态) 来反映用户的输入。由于组件只能更新它们自己的 state(状态) ,FilterableProductTable 将传递回调到 SearchBar,然后在 state(状态) 被更新的时候触发。我们可以使用 input 的 onChange 事件来接收通知。而且通过 FilterableProductTable 传递的回调调用 setState(),然后应用被更新。 尽管这听起来很复杂,但真的只需要简单的几行代码即可实现。同时清晰的表达数据在应用中的流动。 就这么简单希望,他能够给你一个思路,在React 中如何思考构建组件和应用。虽然它可能比你之前使用更多的代码,记住代码可读性远超过它的编写, 模块化、结构清晰的代码最利于阅读。当你开始构建大量的组件库,你将感激模块化、结构清晰和可以重用的代码,同时你的代码行数会慢慢减少。:) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |