学习React的原由是面试的时候出了一个测验。给四天时间要求用react写个todo-list的demo,还要使用ant-design作为视图界面。emmm...这些我都没有用过,还是学起来吧。刚开始的时候是在慕课网上看了一个React入门的视频,跟着视频做了个todo-list;接着看了React的中文教程,跟着教程做了一个下棋的小游戏;昨天晚上及今天上午在看React文档中主要概念一节,顺便跟着React理念那一小节做了一个可搜索的产品数据表格,理顺一下如何使用React的思路。
本问主要记录React理念一小节中,做一个可搜索的产品数据表格。
可搜索的产品数据表格原型图如下:
//JSON数据
[
{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"}
];
Step1:将页面分成若干组件
用方框划分出每一个组件或者子组件,并且给这些组件命名。
什么是组件???一些将UI分切成一些独立的、可复用的部件,这样就只需专注于构建每一个单独的部件。
遵循单一功能原则。
FilterableProductTable
(橙色): 包含了整个例子SearchBar
(蓝色): 接受所有的用户输入ProductTable
(绿色): 根据用户输入过滤并展示数据集合ProductCategoryRow
(绿松石色): 展示每个分类的标题ProductRow
(红色): 用行来展示每个产品
确定原型图中的组件并且将他们整理为层级结构
Step 2:用React创建一个静态版本
先创建一个静态版本:传入数据模型,渲染 UI 但没有任何交互。最好把这些过程解耦,因为创建一个静态版本更多需要的是码代码,不太需要逻辑思考,而添加交互则更多需要的是逻辑思考,不是码代码。要构建一个用于呈现数据模型的静态版本的应用程序,你需要创建能够复用其他组件的组件,并通过 props 来传递数据。props 是一种从父级向子级传递数据的方法。可以选择自顶向下或者自底向上的方式构建应用。
在较为简单的例子中,通常自顶向下更容易,而在较大的项目中,自底向上会更容易并且在你构建的时候有利于编写测试。
class ProductCategoryRow extends React.Component {
//渲染 合并种类行的列 并展示种类的名称
render() {
return <tr><th colSpan="2">{this.props.category}</th></tr>;
}
}
class ProductRow extends React.Component {
//ProductRow 列出product的名字和价格 库存为false的name要为红色
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>
);
}
}
//ProductTable 表格 返回JSX对象 一个table里有thead tbody
//tbody里包括分类行及product行
class ProductTable extends React.Component {
render() {
var rows = [];
//分类
var lastCategory = null;
this.props.products.forEach(function(product) {
if (product.category !== lastCategory) {
//在行内添加 ProductCategoryRow 组件
rows.push(<ProductCategoryRow category={product.category} key={product.category} />);
}
//在行内添加 ProductRow 组件
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>
);
}
}
//SearchBar 组件
class SearchBar extends React.Component {
render() {
//返回的一个JSX对象 一个表单里有一个输入框和复选框
return (
<form>
<input type="text" placeholder="Search..." />
<p>
<input type="checkbox" />
{' '}
Only show products in stock
</p>
</form>
);
}
}
//FilterableProductTable 组件
class FilterableProductTable extends React.Component {
render() {
//渲染 返回一个JSX对象 里面包含SearchBar 和ProductTable组件
//以JSX属性的方式 向ProductTable里传入json数据
return (
<div>
<SearchBar />
<ProductTable products={this.props.products} />
</div>
);
}
}
//JSON数据
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'}
];
//将FilterableProductTable放在 html中id为container的节点下面
//向FilterableProductTable里传入JSON数据
ReactDOM.render(
<FilterableProductTable products={PRODUCTS} />,
document.getElementById('container')
);
这些组件只会有 render()
方法,因为这只是你的应用的静态版本。层级最高的组件(FilterableProductTable
)会把数据模型作为 prop 传入。如果你改变你的基础数据模型并且再次调用 ReactDOM.render()
, UI 会更新。
在React中的单向数据流保证保证了一切是模块化并且是快速的。
Step3:定义UI状态的最小完整表示
为了使你的 UI 交互,你需要能够触发对底层数据模型的更改。React 使用 state,让这变的更容易。你需要考虑你的应用所需要的最小可变状态集。
实例应用中所有数据
- 原产品列表
- 用户输入的搜索文本
- 复选框的值
- 产品的筛选列表
找出哪一个是 state。每个数据只要考虑三个问题:
- 不是通过props传过来的
- 随着时间推移会发生改变
- 不能够根据组件中任何其他的 state 或 props 把它计算出来
最后筛选出的state
- 用户输入的搜索文本
- 复选框的值
Step4:确定你的State位于哪里?
步骤
- 确定每一个需要这个 state 来渲染的组件
- 找到一个公共所有者组件
- 这个公共所有者组件或另一个层级更高的组件应该拥有这个 state
- 如果你没有找到可以拥有这个 state 的组件,创建一个仅用来保存状态的组件并把它加入比这个公共所有者组件层级更高的地方
用户输入的搜索文本应该在FilterableProductTable里
复选框的值应该在FilterableProductTable里
Step5:添加反向数据流
到目前为止,我们已经创建了一个可以正确渲染的应用程序,它的数据在层级中通过函数的 props 和 state 向下流动。现在是时候支持其他方式的数据流了:层级结构中最底层的表单组件需要去更新在 FilterableProductTable
中的 state。
最终代码如下:
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import FilterableProductTable from './FilterableProductTable';
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('root'));
//FilterableProductTable
import React from 'react';
import SearchBar from './SearchBar'
import ProductTable from './ProductTable'
class FilterableProductTable extends React.Component{
constructor(props){
super(props);
this.state={
filterText:'',
inStockOnly: false
};
this.handlerChange = this.handlerChange.bind(this);
this.handlerClick = this.handlerClick.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}
handleFilterTextInput ={this.state.onFilterTextInput}
handleInStockInput={this.onInStockInput}
/>
<ProductTable
filterText={this.state.filterText}
inStockOnly={this.state.inStockOnly}
products={this.props.products}
/>
</div>
)
}
}
//导出组件
export default FilterableProductTable;
//searchBar
import React from 'react';
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(){
var filterText = this.props.filterText;
var inStockOnly = this.props.inStockOnly;
return(
<form>
<input type='text' placeholder='Search..' onChange={this.handleFilterTextInputChange} value={filterText}/>
<p>
<input type="checkbox" onChange={this.handleInStockInputChange} checked={inStockOnly}/>
{''}
Only show Products in stock
</p>
</form>
)
}
}
//导出组件
export default SearchBar;
//ProductTable
import React from 'react';
import ProductRow from './ProductRow'
import ProductCategoryRow from './ProductCategoryRow'
class ProductTable extends React.Component{
render(){
var rows=[];
var lastCategory = null;
this.props.products.forEach((product) => {
//这里使用正则表达式来匹配 i 忽略大小写
if (product.name.search(new RegExp(this.props.filterText,'i')) === -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>
)
}
}
export default ProductTable;
import React from 'react';
class ProductCategoryRow extends React.Component{
render(){
return(
<tr><th colSpan='2'>
{this.props.category}
</th></tr>
)
}
}
export default ProductCategoryRow;
import React from 'react';
class ProductRow extends React.Component{
render(){
var stocked =this.props.product.stocked;
var product=this.props.product;
var name = '';
name= stocked ?
product.name:
<span style={{color:'red'}}>
{product.name}
</span>
return(
//返回一行product name由是否有库存改变状态
<tr>
<td>{name}</td>
<td>{this.props.product.price}</td>
</tr>
)
}
}
export default ProductRow;
小结:构建React app步骤
1 把UI划分出组件层级
2 用React创建一个静态版本
3 定义UI状态的最小(但完整)表示
4 确定你的state应该位于哪里
5 添加反向数据流
下面会接着学习ant-design。。。在这个代码基础上使用ant-design。我会写另一篇文章来记录。