學習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。我會寫另一篇文章來記錄。