目錄
擴展學習資料
State進階知識點
狀態更新擴展
shouldComponentUpdate
PureComponent
為何使用不變數據【保證數據引用不會出錯】
?單一數據源
?@/src/App.js
@/src/components/listItem.jsx
狀態提升
@/src/components/listPage.jsx
@src/App.js
有狀態組件&無狀態組件
Stateful【有狀態】和Stateless【無狀態】的區別
Stateful
Stateless
小結
練習
擴展學習資料
預習資料名稱? | 鏈接 | 備注 |
不可變數據 | https://github.com/immutable-js/immutable-js | 無 |
JS內存管理 | 內存管理 - JavaScript | MDN | 無 |
狀態提升 | mangojuice.top?-?該網站正在出售!?-?mangojuice 資源和信息。 | 擴展閱讀 |
context管理狀態 | http://react.html.cn/docs/context.html ? 聊一聊我對 React Context 的理解以及應用 - 掘金 | 擴展閱讀 |
State進階知識點
- 通過條件判斷優化渲染
- 使用不可變數據
- 狀態提升
- 使用無狀態組件
狀態更新擴展
阻止不必要的render方法執行
shouldComponentUpdate
// render渲染執行前調用的函數,返回false,可以有效的阻止不必要的render方法執行shouldComponentUpdate(nextProps, nextState) {console.log('props', this.props, nextProps);console.log('state', this.state, nextState);if(this.state.count === nextState.count) {return false}if(this.props.id === nextProps.id) {return false}return true}
PureComponent
import React, { PureComponent } from 'react';
class ListItem extends PureComponent {}
為何使用不變數據【保證數據引用不會出錯】
// ...
handleDelete = (id) => {// 使用不可變數據, filter返回一個新數組const listData = this.state.listData.filter((item) => item.id !== id);this.setState({listData,});};handleAmount = () => {// 如不使用新的數組【沒有使用不可變數據】, state變化,不會重新渲染UI//const _list = this.state.listData.concat([]);/* pop() 方法用于刪除數組的最后一個元素并返回刪除的元素。注意:此方法改變數組的長度!提示: 移除數組第一個元素,請使用 shift() 方法。*/_list.pop();this.setState({listData: _list,});};
// ...
如下圖,如果沒有創建新的引用,在PureComponent中,不會調用render
?如下圖,使用不可變數據,可以避免引用帶來的副作用,使得整個程序數據變的易于管理
?單一數據源
handleReset = () => {// 使用map方法創建一個新的數組const _list = this.state.listData.map((item) => {// ... 解構符const _item = { ...item };_item.value = 0;return _item;});this.setState({listData: _list,});// 此時props數據變化,子組件state.count沒變化// 原因出在沒有使用單一數據源};
?@/src/App.js
import React, { PureComponent } from 'react';
import ListItem from './components/listItem';
import ListItemFunc from './components/listItemFunc';
import style from './components/listitem.module.css';// eslint-disable-next-line no-unused-vars
class App extends PureComponent {constructor(props) {super(props);this.state = {listData: [{id: 1,name: 'sony 65寸高清電視',price: 4000,stock: 1,value: 4,},{id: 2,name: '華為 Meta30',price: 6000,stock: 12,value: 2,},{id: 3,name: '華碩 玩家國度筆記本',price: 10000,stock: 11,value: 1,}],};}renderList() {return this.state.listData.map((item) => {return (<ListItemkey={item.id}data={item}onDelete={this.handleDelete}onDecrease={this.handleDecrease}onAdd={this.handleAdd}/>);});}handleDelete = (id) => {// 使用不可變數據, filter返回一個新數組const listData = this.state.listData.filter((item) => item.id !== id);this.setState({listData,});};handleDecrease = (id) => {// 使用不可變數據, filter返回一個新數組const _data = this.state.listData.map((item) => {if (item.id === id) {const _item = { ...item };_item.value--;if (_item.value < 0) _item.value = 0;return _item;}return item;});this.setState({listData: _data,});};handleAdd = (id) => {// 使用不可變數據, filter返回一個新數組console.log(id);const _data = this.state.listData.map((item) => {if (item.id === id) {const _item = { ...item };_item.value++;return _item;}return item;});this.setState({listData: _data,});};handleAmount = () => {// 如不使用新的數組【沒有使用不可變數據】, state變化,不會重新渲染UI//const _list = this.state.listData.concat([]);/* pop() 方法用于刪除數組的最后一個元素并返回刪除的元素。注意:此方法改變數組的長度!提示: 移除數組第一個元素,請使用 shift() 方法。*/_list.pop();this.setState({listData: _list,});};handleReset = () => {// 使用map方法創建一個新的數組const _list = this.state.listData.map((item) => {// ... 結構符const _item = { ...item };_item.value = 0;return _item;});this.setState({listData: _list,});// 此時props數據變化,子組件state.count沒變化// 原因出在沒有使用單一數據源};render() {return (<div className='container'><button onClick={this.handleAmount} className='btn btn-primary'>減去最后一個</button><button onClick={this.handleReset} className='btn btn-primary'>重置</button>{this.state.listData.length === 0 && (<div className='text-center'>購物車是空的</div>)}{this.renderList()}</div>);}
}export default App;
@/src/components/listItem.jsx
// import React, { Component } from 'react';
import React, { PureComponent } from 'react';
import style from './listitem.module.css';
import classnames from 'classnames/bind';
const cls = classnames.bind(style);
class ListItem extends PureComponent {// 類的構造函數// eslint-disable-next-line no-useless-constructorconstructor(props) {super(props);}?render() {console.log('item is rendering');return (<div className='row mb-3'><div className='col-4 themed-grid-col'><span style={{ fontSize: 22, color: '#710000' }}>{this.props.data.name}</span></div><div className='col-1 themed-grid-col'><span className={cls('price-tag')}>¥{this.props.data.price}</span></div><divclassName={`col-2 themed-grid-col${this.props.data.value ? '' : '-s'}`}><buttononClick={() => {this.props.onDecrease(this.props.data.id);}}type='button'className='btn btn-primary'>-</button><span className={cls('digital')}>{this.props.data.value}</span><buttononClick={() => {this.props.onAdd(this.props.data.id);}}type='button'className='btn btn-primary'>+</button></div><div className='col-2 themed-grid-col'>¥ {this.props.data.price * this.props.data.value}</div><div className='col-1 themed-grid-col'><buttononClick={() => {this.props.onDelete(this.props.data.id);}}type='button'className='btn btn-danger btn-sm'>刪除</button></div></div>);}
}
export default ListItem;
狀態提升
處理組件和子組件數據傳遞,自頂向下單向流動
?@/src/components/navbar.jsx
import React, { PureComponent } from 'react';
class Nav extends PureComponent {render() {return (<nav className='navbar navbar-expand-lg navbar-light bg-light'><div className='container'><div className='wrap'><span className='title'>NAVBAR</span><span className='badge badge-pill badge-primary ml-2 mr-2'>{this.props.itemNum}</span><buttononClick={this.props.onReset}className='btn btn-outline-success my-2 my-sm-0 fr'type='button'>Reset</button></div></div></nav>);}
}
export default Nav;
@/src/components/listPage.jsx
import React, { PureComponent } from 'react';
import ListItem from './listItem.jsx';
// 商品列表渲染
class ListPage extends PureComponent {renderList() {return this.props.data.map((item) => {return (<ListItemkey={item.id}data={item}onDelete={this.props.handleDelete}onDecrease={this.props.handleDecrease}onAdd={this.props.handleAdd}/>);});}render() {return (<div className='container'>{this.props.data.length === 0 && (<div className='text-center'>購物車是空的</div>)}{this.renderList()}</div>);}
}
export default ListPage;
@src/App.js
import React, { PureComponent } from 'react';
import Nav from './components/navbar';
import ListPage from './components/listPage';
const listData = [{id: 1,name: 'sony 65寸高清電視',price: 4000,stock: 1,value: 4,},{id: 2,name: '華為 Meta30',price: 6000,stock: 12,value: 2,},{id: 3,name: '華碩 玩家國度筆記本',price: 10000,stock: 11,value: 1,},
];
// eslint-disable-next-line no-unused-vars
class App extends PureComponent {constructor(props) {super(props);this.state = {listData: listData,};}handleDelete = (id) => {// 使用不可變數據, filter返回一個新數組const listData = this.state.listData.filter((item) => item.id !== id);this.setState({listData,});};handleDecrease = (id) => {// 使用不可變數據, filter返回一個新數組const _data = this.state.listData.map((item) => {if (item.id === id) {const _item = { ...item };_item.value--;if (_item.value < 0) _item.value = 0;return _item;}return item;});this.setState({listData: _data,});};handleAdd = (id) => {// 使用不可變數據, filter返回一個新數組console.log(id);const _data = this.state.listData.map((item) => {if (item.id === id) {const _item = { ...item };_item.value++;return _item;}return item;});this.setState({listData: _data,});};handleAmount = () => {// 如不使用新的數組【沒有使用不可變數據】, state變化,不會重新渲染UI//const _list = this.state.listData.concat([]);/* pop() 方法用于刪除數組的最后一個元素并返回刪除的元素。注意:此方法改變數組的長度!提示: 移除數組第一個元素,請使用 shift() 方法。*/_list.pop();this.setState({listData: _list,});};handleReset = () => {// 使用map方法創建一個新的數組const _list = this.state.listData.map((item) => {// ... 結構符const _item = { ...item };_item.value = 0;return _item;});this.setState({listData: _list,});// 此時props數據變化,子組件state.count沒變化// 原因出在沒有使用單一數據源};render() {return (<><Nav itemNum={this.state.listData.length} onReset={this.handleReset} /><ListPagedata={this.state.listData}handleAdd={this.handleAdd}handleAmount={this.handleAmount}handleDecrease={this.handleDecrease}handleDelete={this.handleDelete}handleReset={this.handleReset}/></>);}
}
export default App;
有狀態組件&無狀態組件
Stateful【有狀態】和Stateless【無狀態】的區別
Stateful
- 類組件
- 有狀態組件
- 容器組件
Stateless
- 函數組件
- 無狀態組件
- 展示組件
盡可能通過狀態提升原則,將需要的狀態提取到父組件中,而其他的組件使用無狀態組件編寫【父組件有狀態,子組件無狀態】
無狀態組件簡單好維護,單一從上而下的數據流
小結
- 優化渲染
- 使用不可變數據
- 單一數據源以及狀態提升
- 無狀態組件寫法
練習
【題目1】?用單一數據源原則和狀態提升原則改造購物車工程
【題目2】 目前Header中顯示的是商品種類數量,改造成商品的總數目