目录
扩展学习资料
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 (
<ListItem
key={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-constructor
constructor(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>
<div
className={`col-2 themed-grid-col${
this.props.data.value ? '' : '-s'
}`}>
<button
onClick={() => {
this.props.onDecrease(this.props.data.id);
}}
type='button'
className='btn btn-primary'>
-
</button>
<span className={cls('digital')}>{this.props.data.value}</span>
<button
onClick={() => {
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'>
<button
onClick={() => {
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>
<button
onClick={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 (
<ListItem
key={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} />
<ListPage
data={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中显示的是商品种类数量,改造成商品的总数目