React 长列表修改时避免全体渲染
长列表的问题
React 会在数据更新时,触发所有子组件的 Render。
但当数据量变多时,每个子组件都更新就会产生性能问题,导致卡顿。
场景
基于如下场景时,此时在任何一个input修改,都会导致所有子组件更新,导致输入卡顿。
// 深拷贝
const deepCopy = (v) => JSON.parse(JSON.stringify(v))
// 父组件,拥有1000个元素的数组
class Parent extends React.Component {
constructor() {
super()
const list = []
list.length = 1000
list.fill(1)
this.state = { list: list.map((e, i) => ({ v: i, id: Math.random() })) }
}
onChange = (value, index) => {
const list = deepCopy(this.state.list)
list[index] = { ...list[index], v: value }
this.setState({ list })
}
render() {
return this.state.list.map((e, i) => (
<Input
onChange={this.onChange}
key={e.id}
e={e}
i={i} />
))
}
}
// 子组件 当触发 componentDidUpdate 时会输出 1
class Input extends React.Component {
componentDidUpdate() {
console.log('update', this.props.e.v)
}
render() {
return (
<input value={this.props.e.v} onChange={e => this.props.onChange(e.target.value, this.props.i)} />
)
}
}
解决方案
利用 shouldComponentUpdate 钩子,在更新前进行对比,避免无意义渲染。
// 子组件
class Input extends React.Component {
componentDidUpdate() {
console.log('update', this.props.e.v)
}
// 对比上一个props的 e.v 和当前的 e.v,当不相同,才触发渲染。
shouldComponentUpdate(nextProps, nextState) {
return nextProps.e.v !== this.props.e.v
}
render() {
return (
<input value={this.props.e.v} onChange={e => this.props.onChange(e.target.value, this.props.i)} />
)
}
}
完整demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
const deepCopy = (v) => JSON.parse(JSON.stringify(v))
class Parent extends React.Component {
constructor() {
super()
const list = []
list.length = 1000
list.fill(1)
this.state = { list: list.map((e, i) => ({ v: i, id: Math.random() })) }
}
onChange = (value, index) => {
const list = deepCopy(this.state.list)
list[index] = { ...list[index], v: value }
this.setState({ list })
}
add = index => {
const list = deepCopy(this.state.list)
const item = { v: list.length + 1, id: Math.random() }
list.splice(index, 0, item)
this.setState({ list })
}
render() {
return this.state.list.map((e, i) => (
<Input
add={this.add}
onChange={this.onChange}
key={e.id}
e={e}
i={i} />
))
}
}
class Input extends React.Component {
componentDidMount() {
console.log('mount', this.props.e.v)
}
componentDidUpdate() {
console.log('update', this.props.e.v)
}
shouldComponentUpdate(nextProps, nextState) {
return nextProps.e.v !== this.props.e.v
}
render() {
return (
<input value={this.props.e.v} onChange={e => this.props.onChange(e.target.value, this.props.i)} onKeyDown={(e) => e.key === 'Enter' && this.props.add(this.props.i)} />
)
}
}
ReactDOM.render(
<Parent />,
document.getElementById('example')
)
</script>
</body>
</html>