react入门练习

React实现实时搜索查询,快速选择

效果查看

思路分析

原型(mock)

拆分原型为一个组件树

遵循单一功能原则,指的是,理想状态下一个组件应该只做一件事,假如它功能逐渐变大就需要被拆分成更小的子组件。
原型界面和数据模型在 信息构造 方面都要一致 (JSON数据模型与UI组件结构一致性)

将原型划分为5个组件

  1. FilterableProductTable 包含整个例子的容器
  2. SearchBar 接受所有 用户输入( user input )
  3. ProductTable 根据 用户输入( user input ) 过滤和展示 数据集合( data collection )
  4. ProductCategoryRow 为每个 分类( category ) 展示一列表头
  5. ProductRow 为每个 产品( product ) 展示一列

创建静态版本

通过组件树,简单的将数据模型渲染到UI上。
构建一个静态的版本仅需要大量的输入,而不需要思考。
创建一个渲染数据模型的应用的静态版本,你将会构造一些组件,这些组件重用其它组件,并且通过 props 传递数据。
props 是一种从父级向子级传递数据的方式。

识别最小(最完整)代表UI的state

为了使 UI 可交互,需要能够触发底层数据模型的变化。 React 通过 state 使这变得简单。
state 是:

  • 用户输入的搜索文本
  • 复选框的值

确认state生命周期

React 中数据是沿着组件树从上到下单向流动的。
我们在应用中应用这个策略:

  • ProductTable 需要基于 state 过滤产品列表,SearchBar 需要显示搜索文本和复选框状态。
  • 共同拥有者组件是 FilterableProductTable 。
  • 理论上,过滤文本和复选框值位于 FilterableProductTable 中是合适的。

给 FilterableProductTable 添加 getInitialState() 方法,该方法返回 {filterText: ‘’, inStockOnly: false} 来反映应用的初始化状态。
传递 filterText 和 inStockOnly 给 ProductTable 和 SearchBar 作为 prop 。
使用这些 props 来过滤 ProductTable 中的行,设置在 SearchBar 中表单字段的值。

添加反向数据流

无论何时用户改变了表单,都要更新 state 来反映用户的输入。
由于组件只能更新自己的 state , FilterableProductTable 将会传递一个回调函数给 SearchBar ,此函数将会在 state 应该被改变的时候触发。
使用 input 的 onChange 事件来监听用户输入,从而确定何时触发回调函数。
FilterableProductTable 传递的回调函数将会调用 setState() ,然后应用将会被更新。

源代码

相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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"}
];

// FilterProductTable
// - SearchBar
// - ProductTable
// = ProductCategoryRow
// = ProductRow

var SearchBar = React.createClass({
// onChange 事件来监听用户输入此函数,触发回调函数
handleChange: function() {
this.props.onUserInput(this.refs.filterTextInput.value, this.refs.filterStockInput.checked);
},
render: function() {
return (
<form>
<input type="text" onChange={this.handleChange} placeholder="Search..." ref="filterTextInput" value={this.props.filterText}/>
<p>
<input type="checkbox" onChange={this.handleChange} ref="filterStockInput" checked={this.props.stockOnly}/>
Only show products in stock
</p>
</form>
);
}
});

var ProductCategoryRow = React.createClass({
render: function() {
return (
<tr>
<th>{this.props.category}</th>
</tr>
);
}
});

var ProductRow = React.createClass({
render: function() {
var name = this.props.product.stocked ? <td>{this.props.product.name}</td>
: <td className="red">{this.props.product.name}</td>
return (
<tr>
{name}
<td>{this.props.product.price}</td>
</tr>
);
}
});

var ProductTable = React.createClass({
render: function() {
var rows = [];
var categories = [];
//循环将所有分类放在categories数组
this.props.products.forEach(function(product) {
if( categories.indexOf(product.category) < 0 ) {
categories.push(product.category);
}
});
for(var i=0; i<categories.length;i++) {
//分类行插入
rows.push(<ProductCategoryRow category={categories[i]} key={categories[i]}/>);
var filterText = this.props.filterText;
var stockOnly = this.props.stockOnly
this.props.products.forEach(function(product) {
//所搜filterText无法与产品名称匹配,跳出当前循环;
//!product.stocked 产品库存false,并且stockOnly为true只查询有库存产品时,跳出当前循环;
if( product.name.indexOf(filterText) === -1 || (stockOnly && !product.stocked) ) {
return ;
}
if(product.category == categories[i]) {
//产品行插入
rows.push(<ProductRow product={product} key={product.name}/>);
}
});
}
return (
<table>
<thead>
<tr>
<td>Name</td>
<td>Price</td>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
);
}
});

var FilterProductTable = React.createClass({
//初始化state
getInitialState: function() {
return {
filterText:'',
stockOnly:false
};
},
//回调函数,state传值即时更新
handleUserInput: function(filterText, stockOnly) {
this.setState({
filterText: filterText,
stockOnly: stockOnly
});
},
render: function() {
return (
<div>
<SearchBar filterText={this.state.filterText} stockOnly={this.state.stockOnly} onUserInput={this.handleUserInput}/>
<ProductTable filterText={this.state.filterText} stockOnly={this.state.stockOnly} products={this.props.products}/>
</div>
);
}
});

ReactDOM.render(
<FilterProductTable products={PRODUCTS} />,
document.getElementById('content')
);

相关知识总结

  1. 组件划分遵循单一功能原则,由上往下,由大到小依次组件化;
  2. React应用搭建三部曲
    • 通过props搭建基于数据结构和UI的静态版本;
    • 识别最小、最完整state,确认state生命周期(getInitialState() 初始化state,更新部分由state代替props);
    • 添加反向数据流(回调,数据更新setState())。
  3. props 是一种从父级向子级传递数据的方式。
  4. 无论何时用户改变了表单,都要更新 state 来反映用户的输入