React - 性能优化

360 阅读4分钟

React 性能优化

本文参考 React官网如何对 React 函数式组件进行优化 - 掘金 - 桃翁

以下是整理的正文


React 内部已经使用了很多技巧优化了 UI 的更新和 DOM 操作。大部分情况下,我们不需要特意去做针对 React 的性能优化。尽管如此,以下依然是一些可以提升速度的方法。

使用生产版本

React 默认包含了很多警告信息,以便在开发过程中提示一些有用的反馈。然而这些信息包的体积并不算小。我们部署到服务器的包,一定要使用生产环境的 React

通过安装 React DevTools 可以帮助我们判断,当前使用的是生产环境还是开发环境。

虚拟化长列表

参考 react-window

使用这个组件库,可以很方便的减少长列表中 UI 的更新。

React.lazy()

懒加载组件

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
    return (
        <div>
            <Suspense fallback={<div>Loading...</div>}>
                <section>
                    <OtherComponent />
                    <AnotherComponent />
                </section>
            </Suspense>
        </div>
    );
}

跳过渲染

React 通过 diff 算法,已经尽可能的减少了需要更新的 DOM节点 的数量。不过如果我们知道何时可以不用更新 DOM ,我们可以通过以下这些方法来跳过渲染。

shouldComponentUpdate

shouldComponentUpdate 生命周期钩子函数会在每次 props 或者 state 更新时执行。其默认返回 true,即需要渲染。

通过比较 this.props 和 next.propsthis.state 和 next.state。如果当前 state或者props 的更改确实不需要更新 DOM ,那么便可以返回 fasle 去避免不必要的 render 和 后续的 ComponentDidUpdate 的执行。(说实话,确定不是需要将这个 state 的属性不要定义在 state 里吗???)

React 默认提供了 PureComponent 来内置了这个功能。但是其只会对更改前的值和更改后的值做浅比较。使用拓展运算符能够比较好的解决这一问题。(当然任何返回新对象的方法都可以).

不建议在 shouldComponentUpdate 里做深度比较或者执行深克隆。这样将会损害性能。

React.memo()

React.memo 其实就是一个高阶组件。其接收一个组件作为参数,返回一个新组件。

这个新组件只有在其 props 更改时,才会重新渲染这个组件,否则将直接返回“记忆”中的上一次的结果。

// Child
function Child(props) {
    return <div>{props.name}</div>;
}
// App
class App extends React.Component() {
    state = {
        title: '',
    };
    changeTitle = () => {
        this.setState({
            title: 'i am changed!',
        });
    };
    render() {
        return (
            <div>
                <span>{this.state.title}</span>
                <button onClick={this.changeTitle}>Click Me</button>
                <Child name="zxl" />
            </div>
        );
    }
}

上述代码中,我只是更改了父组件的 title,并没有更新 Child 组件所接收的 name。但是在 title 更新后,不仅 App 组件会重新渲染,Child 组件也会重新渲染。

我们如果想避免这种情况,可以使用 React.memo()

function Child(props) {
    return <div>{props.name}</div>;
}

export default React.memo(Child);

React.memo 默认只会对 props 做浅比较。如果想做深度比较,更加精准的控制,可以传入第二个参数

React.memo(Child, (preProps, nextProps) => {});

UseCallback

这个 Hook 用于给 Clild 传递了函数的情况下的跳过渲染。什么意思呢?

// Child
function Child(props) {
    return (
        <div>
            <span>{props.name}</span>
            <button onClick={props.changeTitle}>Click Me</button>
        </div>
    );
}

export default React.memo(Child);
// App
class App extends React.Component() {
    state = {
        title: '',
        subTitle: '',
    };
    changeTitle = () => {
        this.setState({
            title: 'i am changed!',
        });
    };
    changeSubTitle = () => {
        this.setState({
            subTitle: 'subTitle Changed!',
        });
    };
    render() {
        return (
            <div>
                <span>{this.state.title}</span>
                <button onClick={this.changeSubTitle}>Click Me</button>
                <Child name="zxl" changeTitle={this.changeTitle} />
            </div>
        );
    }
}

无论当我们点击 App 中的按钮改变 subTitle ,还是点击 Child 中的按钮改变 title。都会触发 Child 的更新。为什么呢?

注意到 App 中的 ChageTitle 是我们传入 Childprops 的属性。而当 App 更新的时候,会重新创建一个 changeTitle 函数。这就是弊病所在。

useCallback 就是为了解决这个问题。

// App
import React, { useCallback } from 'react';
class App extends React.Component() {
    state = {
        title: '',
        subTitle: '',
    };
    changeTitle = () => {
        this.setState({
            title: 'i am changed!',
        });
    };
    changeSubTitle = () => {
        this.setState({
            subTitle: 'subTitle Changed!',
        });
    };
    render() {
        const memoizedCallback = useCallback(this.changeTitle, []);
        return (
            <div>
                <span>{this.state.title}</span>
                <button onClick={this.changeSubTitle}>Click Me</button>
                <Child name="zxl" changeTitle={memoizedCallback} />
            </div>
        );
    }
}

参考:useCallback

useEffect 的第二个参数

参考 通过跳过 Effect 进行性能优化

跳过计算

useMemo

当有一个函数需要很长的执行时间,而其值影响到了 DOM 的渲染,但并不是唯一因素的时候,就需要用到这个 hook了。什么意思呢?

function Cal() {
    const [count, setCount] = useState(0);
    const longTimeCal = () => {
        let result = 0;
        for (let i = 0; i < 1000000; i++) {
            result += i;
        }
        return result;
    };
    const value = longTimeCal();
    return (
        <div>
            <span>{count + value}</span>
            <button onClick={(count) => setCount(count + 1)} />
        </div>
    );
}

每次重新渲染的时候,都需要计算一遍这个函数的值。这就是弊病所在。

我们使用 useMemo 做优化。

function Cal() {
    const [count, setCount] = useState(0);
    const longTimeCal = () => {
        let result = 0;
        for (let i = 0; i < 1000000; i++) {
            result += i;
        }
        return result;
    };
    const value = useMemo(longTimeCal, []);
    return (
        <div>
            <span>{count + value}</span>
            <button onClick={(count) => setCount(count + 1)} />
        </div>
    );
}

key 的使用

养成好习惯~

Profiler

React DevTools 提供的分析性能的工具。详细用法请看React Profiler 介绍