React 状态提升、Context 使用及带来的问题分析与解决
在我们平时开发中,避免不了一种场景就是组件状态共享,A 和 B 两个组件有公共的状态,常见的处理方法:
状态提升
将公共的 state 提升至它们的公共祖先,由祖先提供 state 和修改 state 的逻辑,这样有可能导致重新渲染一颗巨大的组件树。
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
| const A = (props) => { console.log("child1 更新了"); return <div>A组件 {props.value.name}</div>; };
const B = (props) => { console.log("child2 更新了"); return <div>B组件 {props.value.age}</div>; };
function App() { const [value, setValue] = useState({ name: "chenjiang", age: 26 }); return ( <> <A value={value} /> <B value={value} />
{/* 不依赖value的子组件 */} <C xxxProps /> <D xxxProps /> <button onClick={() => { setValue({ ...value, name: "chenjiang666" }); }} > 修改name </button> </> ); }
|
当我们更新 value 时候,父组件会重新渲染,由于 A 和 B 两个组件都依赖了也会随着重新渲染,但是父组件还有一些不依赖的 value 的子组件也进行不必要的渲染。
如何减少不必要的渲染
不依赖 value 的子组件:C、D 这两个组件
我们对 C、D 组件用 React.memo 包裹,保证它的 props 没有发生变化的时候不会重新渲染,但是由于父组件更新了导致函数重新执行,那么 C、D 组件放在父组件的 props 引用发生变化,也会导致重新渲染,所以说并不是组件被 React.memo 包裹了就万事俱备了,还需要对放在父组件的 props 用 useMemo 或者 useCallback 包裹。
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
| const C = React.memo(function test() { return <div>123213</div>; });
function App() { const [value, setValue] = useState({ name: "chenjiang", age: 26 });
const cState = useMemo(() => {}, []); const handleC = useCallback(() => {}, []); const handleD = useCallback(() => {}, []);
return ( <> <A value={value} /> <B value={value} />
<C handleC={handleC} cState={cState} /> <D handleD={handleD} />
<button onClick={() => { setValue({ ...value, name: "chenjiang666" }); }} > 修改name </button> </> ); }
|
注:一般来说 React.memo 都是要和 useMemo 或者 useCallback 配合来使用的。
使用 Context
将公共状态放到 Provider 组件的 value 里面,然后包裹需要此状态的组件,可能引起不必要的渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const AppContext = createContext();
function App() { const [value, setValue] = useState({ theme: "dark", name: "chenjiang", age: 26 }); return ( <> <AppContext.Provider value={value}> <Theme /> <Person /> </AppContext.Provider>
<button onClick={() => { setValue({ ...value, theme: value.theme === "dark" ? "light" : "dark" }); }} > 修改主题色 </button> </> ); }
|
当前 value 更新,Provider 就会重新渲染,其包裹的组件也随着重新渲染,但是有组件所依赖的状态并没有改变,导致了不必要的渲染。
例如:Theme 组件依赖 value 的 theme 属性,Person 组件依赖 value 的 age、name 属性,有一次更新了 value.theme 导致 value 引用发生了改变,就导致 Provider 及其子组件都更新了,Person 组件其实并不依赖 theme 属性但是也更新了,这就造成了不必要的渲染。
如何减少不必要的渲染
为什么存在不必要的渲染,其实本质就是用了自己本不需要的 props,需要做的就是拆分,各取所需。
拆分 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
| const AppContext = createContext(); const InfoContext = createContext();
function App() { const [theme, setTheme] = useState({ theme: "dark" }); const [info, setInfo] = useState({ name: "chenjiang", age: 26 });
const cState = useMemo(() => {}, []); const handleC = useCallback(() => {}, []);
const handleD = useCallback(() => {}, []); return ( <> <AppContext.Provider value={theme}> <A /> </AppContext.Provider>
<InfoContext.Provider value={info}> <B /> </InfoContext.Provider>
<button onClick={() => { setTheme({ theme: theme.theme === "dark" ? "light" : "dark" }); }} > 修改主题色 </button> </> ); }
|
提取独有的状态 React.memo
memo 允许你的组件在 props 没有改变的情况下跳过重新渲染。
把当前组件所需要的状态,单独从 Context 提取出来,以 props 形式传递,然后对组件 React.memo 包裹,但是如果组件多了话,每个都需要单独拎出来进行处理。
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
| const Person = React.memo(function P(props) { return <div>{props.theme}</div>; });
function App() { const [value, setValue] = useState({ theme: "dark", name: "chenjiang", age: 26 }); return ( <> <AppContext.Provider value={value}> {/* 所需要的状态单独提取出来 */} <Theme theme={value.theme} />
<Person /> </AppContext.Provider>
<button onClick={() => { setValue({ ...value, theme: value.theme === "dark" ? "light" : "dark" }); }} > 修改主题色 </button> </> ); }
|
提取独有的状态 useMemo
useMemo 是一个 Hook,可以缓存计算的结果,只有依赖发生变化了,才会重新计算。我们可以利用这个特性,对组件进行缓存,只有它所需要的状态发生变化,才去更新组件。
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
| function App() { const [value, setValue] = useState({ theme: "dark", name: "chenjiang", age: 26 }); return ( <> <AppContext.Provider value={value}> {/* 所需要的状态单独提取出来 */} {useMemo( () => ( <UserPage theme={value.theme} /> ), [value.theme] )}
<Person /> </AppContext.Provider>
<button onClick={() => { setValue({ ...value, theme: value.theme === "dark" ? "light" : "dark" }); }} > 修改主题色 </button> </> ); }
|