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>
posted @ 2019-08-13 16:12  _NKi  阅读(643)  评论(0编辑  收藏  举报