React Context中解构与非解构值的重渲染差异及优化咨询
现象对比
两段几乎一致的React Native代码,表现却截然不同:
示例1:点击TRUE/FALSE按钮,控制台仅打印
'Getter is rendered',只有依赖状态的Getter组件重渲染,符合预期。import { createContext, useState, useContext } from 'react'; import { Button, Text } from 'react-native'; const SetterContext = createContext<any>(() => undefined); const GetterContext = createContext<boolean>(false); const App = () => { return ( <Provider> <> <Getter /> <Setter /> </> </Provider> ); }; const Provider = ({ children }: { children: any }) => { const [isFoo, setIsFoo] = useState(false); return ( <GetterContext.Provider value={isFoo}> {/* 直接传递setIsFoo */} <SetterContext.Provider value={setIsFoo}> {children} </SetterContext.Provider> </GetterContext.Provider> ); }; const Getter = () => { console.log('Getter is rendered'); const getter = useContext(GetterContext); return <Text>{getter ? "TRUE":"FALSE"}</Text> } const Setter = () => { const setIsFoo = useContext(SetterContext); console.log('Setter is rendered') return ( <> <Button title="True" onPress={() => setIsFoo(true)}></Button> <Button title="False" onPress={() => setIsFoo(false)}></Button> </> ) } export default App;示例2:点击按钮时,控制台同时打印
'Getter is rendered'和'Setter is rendered',Setter组件也发生了不必要的重渲染。import { createContext, useState, useContext } from 'react'; import { Button, Text } from 'react-native'; const SetterContext = createContext<any>(() => undefined); const GetterContext = createContext<boolean>(false); const App = () => { return ( <Provider> <> <Getter /> <Setter /> </> </Provider> ); }; const Provider = ({ children }: { children: any }) => { const [isFoo, setIsFoo] = useState(false); return ( <GetterContext.Provider value={isFoo}> {/* 传递包含setIsFoo的新对象 */} <SetterContext.Provider value={{ setIsFoo }}> {children} </SetterContext.Provider> </GetterContext.Provider> ); }; const Getter = () => { console.log('Getter is rendered'); const getter = useContext(GetterContext); return <Text>{getter ? "TRUE":"FALSE"}</Text> } const Setter = () => { const { setIsFoo } = useContext(SetterContext); console.log('Setter is rendered') return ( <> <Button title="True" onPress={() => setIsFoo(true)}></Button> <Button title="False" onPress={() => setIsFoo(false)}></Button> </> ) } export default App;
核心原因
React Context的触发逻辑是:当Provider的value发生引用变化时,所有消费该Context的组件都会重渲染。
- 示例1中,
SetterContext.Provider的value是useState返回的setIsFoo函数——React保证useState的setter函数是稳定引用,无论组件重渲染多少次,这个函数的引用都不会变。所以当isFoo变化导致Provider重渲染时,SetterContext的value没有变化,Setter组件不会触发重渲染。 - 示例2中,
SetterContext.Provider的value是{ setIsFoo }——每次Provider重渲染(因为isFoo变化),都会创建一个全新的对象,这个对象的引用和上一次完全不同。React会认为SetterContext的value发生了变化,因此触发消费它的Setter组件重渲染。
而Getter组件的重渲染是正常的,因为它依赖的GetterContext的value(isFoo)确实发生了变化。
解决方法
要实现仅在状态变化时让Getter组件重渲染,关键是保证SetterContext的value引用稳定:
方法1:直接传递setter函数(推荐)
就像示例1那样,直接把setIsFoo作为Context的value传递,因为它的引用天然稳定,不需要额外处理:
// Provider里的写法 <SetterContext.Provider value={setIsFoo}> {children} </SetterContext.Provider> // Setter组件里的写法 const setIsFoo = useContext(SetterContext);
方法2:用useMemo缓存对象(需传递多个值时)
如果需要在Context中传递多个函数或数据,用useMemo包裹对象,确保只有当依赖项变化时,对象的引用才会更新:
const Provider = ({ children }: { children: any }) => { const [isFoo, setIsFoo] = useState(false); // 用useMemo缓存对象,依赖项只有稳定的setIsFoo const setterContextValue = useMemo(() => ({ setIsFoo }), [setIsFoo]); return ( <GetterContext.Provider value={isFoo}> <SetterContext.Provider value={setterContextValue}> {children} </SetterContext.Provider> </GetterContext.Provider> ); };
这样即使Provider重渲染,只要setIsFoo不变(它本来就不会变),setterContextValue的引用就不会变,Setter组件就不会触发不必要的重渲染。
方法3:拆分独立Context
如果需要传递多个不相关的状态或方法,可以拆分成多个独立的Context,比如把状态和setter分别放在不同的Context里,这样每个Context的Provider只会在对应value变化时触发重渲染,避免无关组件受到影响。
内容的提问来源于stack exchange,提问作者Justin

