如何在不触发重渲染的情况下修改React Context状态?
嘿,我来帮你理清楚这个问题!首先先解答你第一个疑问:为啥两个组件都会重渲染?
当你调用setBoo修改Context里的boo状态时,Context Provider的value会更新——因为你之前是直接返回{ boo, setBoo },每次Provider重渲染时这个对象都是新的引用。而所有通过useContext订阅这个Context的组件(不管是用了boo还是setBoo),都会因为Context的value更新而触发重渲染,这就是为啥Thing明明只用到setBoo,也跟着重新渲染了。
另外还要提一句,你代码里用全局变量localBoo来同步状态是个坏主意!React的状态应该完全由useState来管理,直接修改全局变量很容易导致状态不一致,后面用函数式更新就好,比如setBoo(prev => !prev),不用依赖外部变量。
接下来解决核心问题:怎么让Thing在触发状态更新时不重渲染?给你两个实用方案:
方案一:拆分Context,把状态和更新函数分开
这是最清晰也最推荐的方式——把boo状态和setBoo更新函数拆成两个独立的Context:
import { createContext, useContext, useState, PropsWithChildren, Dispatch, SetStateAction } from 'react'; let initialBoo = false; // 专门存放boo状态的Context type BooContextType = boolean; const BooContext = createContext<BooContextType | undefined>(undefined); // 专门存放setBoo函数的Context type SetBooContextType = Dispatch<SetStateAction<boolean>>; const SetBooContext = createContext<SetBooContextType | undefined>(undefined); const MyContextProvider = ({ children }: PropsWithChildren) => { const [boo, setBoo] = useState(initialBoo); return ( <BooContext.Provider value={boo}> <SetBooContext.Provider value={setBoo}> {children} </SetBooContext.Provider> </BooContext.Provider> ); }; const Thing = () => { const setBoo = useContext(SetBooContext)!; console.log("render Thing"); const handleOnClick = () => { // 用函数式更新,直接基于前状态反转,不用全局变量 setBoo(prev => !prev); }; return <button onClick={handleOnClick}>Toggle Boo</button>; }; const Thing2 = () => { const boo = useContext(BooContext)!; console.log("render Thing2"); return <div>Current Boo value: {boo.toString()}</div>; };
为啥这样能行?因为SetBooContext的value是useState返回的setBoo,这个函数的引用在组件生命周期里是稳定的——只要Provider不卸载,它就不会变。所以当boo状态变化时,只有订阅BooContext的Thing2会重渲染,Thing因为只订阅了不会变化的SetBooContext,完全不会触发重渲染。
方案二:用useMemo缓存Context的value(适合不想拆分Context的场景)
如果你不想拆分成两个Context,可以用useMemo把Provider的value缓存起来,但这个方案没法完全避免Thing重渲染,除非结合其他优化,不如拆分直接,不过还是说一下:
const MyContextProvider = ({ children }: PropsWithChildren) => { const [boo, setBoo] = useState(initialBoo); // 用useMemo缓存value,只有boo变化时才生成新对象 const contextValue = useMemo(() => ({ boo, setBoo }), [boo]); return <MyContext.Provider value={contextValue}>{children}</MyContext.Provider>; };
但这个方案里,当boo变化时,contextValue还是会更新,所以Thing还是会重渲染。如果要让Thing不重渲染,还得用React.memo包裹Thing并自定义比较函数,这就有点复杂了,不如拆分Context来得干净。
总结一下,最推荐的就是拆分Context的方案,既清晰又能彻底解决你的问题,还能避免全局变量带来的状态不一致风险。
备注:内容来源于stack exchange,提问作者Jeff Tillwick

