【react】react 重复渲染及解决方案

react 重复渲染及解决方案

相关文章

react 重复渲染及解决方案


一、哪些情况导致组件重复渲染

状态变化导致重复渲染

当组件的状态(state)发生变化时,React 会重新渲染组件,以便将新的状态反映在 UI 上。这是 React 中组件更新的内部机制,也是组件重复渲染的根本原因之一。

父组件导致重复渲染

当父组件重新渲染时,它的子组件也会跟着重新渲染。这是因为 React 默认情况下会比较新旧虚拟 DOM 树,以确定需要重新渲染的组件。

Context 变化导致重复渲染

当使用 Context API 时,如果 Context Provider 提供的 value 发生变化,使用该 Context 的所有组件都会重新渲染。即使组件只使用了 Context 中的部分数据,也会触发重新渲染。

二、如何避免不必要的重渲染

状态变化导致重复渲染

组件内部的状态改变会触发组件重新渲染,因此我们应该谨慎使用状态。只有在必要时才使用状态。如果某些值的变化不会影响页面渲染,我们可以将这些值保存在函数组件外部,或者使用 useRef。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { useState, useEffect, useRef } from 'react';

function ExampleComponent() {
// 使用 useState 来存储需要影响 UI 的值
const [count, setCount] = useState(0);

// 使用 useRef 来存储不需要影响 UI 的值
const timerRef = useRef(null);

// 每秒更新计时器的值
useEffect(() => {
timerRef.current = setInterval(() => {
// 不需要影响 UI 的值可以存储在 useRef 中
console.log('Current count:', count);
}, 1000);

// 组件卸载时清除计时器
return () => {
clearInterval(timerRef.current);
};
}, []); // 依赖项为空数组,表示只在组件挂载和卸载时执行一次

return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button>
</div>
);
}

export default ExampleComponent;

父组件导致重复渲染

  • state影响最小化原则

    尽量将状态的影响范围限制在最小的组件范围内。

    优化前

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const Component = () => {
    const [isOpen, setOpen] = useState(false)
    return (
    <div>
    <button onClick={() => setOpen(!isOpen)}>open</button>
    { isOpen && <ModalDialog />}
    {/* 状态的变化会引起 SlowComponent 重复渲染 */}
    <SlowComponent />
    </div>
    )
    }

    优化后

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const Component = () => {
    return (
    <div>
    <ButtonWithDialog />
    <SlowComponent />
    </div>
    )
    }

    const ButtonWithDialog = () => {
    const [isOpen, setOpen] = useState(false)
    return (
    <>
    <button onClick={() => setOpen(!isOpen)}>open</button>
    { isOpen && <ModalDialog />}
    </>
    )
    }
  • 使用React.memo缓存组件

    优化前

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import React from 'react';

    const ComponentWithoutMemo = ({ data }) => {
    console.log('Rendering ComponentWithoutMemo');
    return <div>{data}</div>;
    };

    function App() {
    const data = 'Hello, World!';
    return (
    <div>
    {/* 状态的变化会引起 ComponentWithoutMemo 重复渲染 */}
    <ComponentWithoutMemo data={data} />
    </div>
    );
    }

    export default App;

    优化后

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import React from 'react';

    const ComponentWithoutMemo = React.memo(({ data }) => {
    console.log('Rendering ComponentWithoutMemo');
    return <div>{data}</div>;
    });

    function App() {
    const data = 'Hello, World!';
    return (
    <div>
    {/* 使用 React.memo 缓存组件,当 data 没有变化时,不会触发重复渲染 */}
    <ComponentWithoutMemo data={data} />
    </div>
    );
    }

    export default App;
  • 使用useMemo缓存props

    如果props是引用类型,则依然会发生重复渲染。因为这些引用类型的变量在每次父组件渲染时都会更新,此时则需要使用 useMemo 或 useCallback 缓存 props。

    优化后

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import React from 'react';

    const ComponentWithoutMemo = React.memo(({ data }) => {
    console.log('Rendering ComponentWithoutMemo');
    return <div>{data}</div>;
    });

    function App() {
    const data = useMemo(() => ({ message : 'Hello, World!'}), []);
    return (
    <div>
    {/* 使用 React.memo 缓存组件,当 data 没有变化时,不会触发重复渲染 */}
    <ComponentWithoutMemo data={data} />
    </div>
    );
    }

    export default App;

Context 变化导致重复渲染

  • 使用useMemo缓存content

    优化前

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import React from 'react';
    import Home from './home'

    const MyContext = React.createContext({});

    const App = () => {
    const value = {
    data: 'Hello, World!',
    count: 0
    };
    return <MyContext.Provider value={value}>{Home}</MyContext.Provider>;
    };

    export default App

    优化后

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import React from 'react';
    import Home from './home'

    const MyContext = React.createContext({});

    const App = () => {
    const value = useMemo(() => ({
    data: 'Hello, World!',
    count: 0
    }), []);
    return <MyContext.Provider value={value}>{Home}</MyContext.Provider>;
    };

    export default App
  • context读写分离

    和state类似,将context的影响范围限制在最小的组件范围内。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    import React, { createContext, useContext, useState } from 'react';

    // 创建两个独立的 Context
    const ReadDataContext = createContext();
    const WriteDataContext = createContext();

    // 读取数据的 Context Provider
    const ReadDataProvider = ({ children }) => {
    const [data, setData] = useState('');
    const value = { data }
    return <ReadDataContext.Provider value={value}>{children}</ReadDataContext.Provider>;
    };

    // 写入数据的 Context Provider
    const WriteDataProvider = ({ children }) => {
    const [data, setData] = useState('');
    const value = { setData };
    return <WriteDataContext.Provider value={value}>{children}</WriteDataContext.Provider>;
    };

    // 读取数据的子组件
    const ReadDataComponent = () => {
    const data = useContext(ReadDataContext);
    return (
    <div>
    <p>{data}</p>
    </div>
    );
    };

    // 写入数据的子组件
    const WriteDataComponent = () => {
    const { setData } = useContext(WriteDataContext);
    return (
    <div>
    <button onClick={setData}></button>
    </div>
    );
    };

    // 应用程序根组件
    const App = () => (
    <ReadDataProvider>
    <WriteDataProvider>
    <div>
    <ReadDataComponent />
    <WriteDataComponent />
    </div>
    </WriteDataProvider>
    </ReadDataProvider>
    );

    export default App;

    多个数据场景

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    import { createContext, useContext, useReducer } from 'react'

    const WriteContext = createContext(null)
    const ReadContext = createContext(null)

    const useWriteContext = () => useContext(WriteContext)
    const useReadContext = () => useContext(ReadContext)

    class initialState {
    navHeight = 62
    groupTabIndex = 0
    errorData = {
    show: false,
    retcode: 9999,
    }
    }

    /**
    * 全局的 Reducer
    */
    function pageReducer(state, action) {
    switch (action.type) {
    case '__setStore':
    state = Object.assign(state, action.payload)
    break
    default:
    console.error('reducer error', action)
    return state
    }
    return { ...state }
    }

    export default function Context(props) {
    const [state, dispatch] = useReducer(pageReducer, new initialState())

    return (
    <WriteContext.Provider value={dispatch}>
    <ReadContext.Provider value={{ state }}>{props.children}</ReadContext.Provider>
    </WriteContext.Provider>
    )
    }

    export { useWriteContext, useReadContext }

【react】react 重复渲染及解决方案
https://www.cccccl.com/20231102/react/react 重复渲染及解决方案/
作者
Jeffrey
发布于
2023年11月2日
许可协议