将 Context 拆分为状态并更新以提高性能(减少渲染)?
Split up Context into state and update to improve performance (reduce renders)?
一些博客文章在使用 React 上下文 时将上下文拆分为 两个 单独的更新和状态上下文。我想知道这是否真的会提高性能或会导致减少渲染周期。
所以最终的解决方案将有:
- 两个不同的上下文对象
- 两个不同的挂钩 1.) 访问状态 和 2.) 修改状态
- 两个独立的供应商(像
AuthProvider
一样打包成一个供应商)
拆分 Update 和 State 上下文的解决方案:
const authContext = React.createContext<AuthUser | null | undefined>(undefined)
const authUpdateContext = React.createContext<Dispatch<SetStateAction<AuthUser | null>> | null>(null)
export function useAuth() {
const authUser = React.useContext(authContext);
if (authUser === undefined) throw new Error(`useAuth must be used within a ContextAuthProvider`);
return authUser;
}
export const useAuthUpdate = () => {
const setAuthUser = React.useContext(authUpdateContext);
if (!setAuthUser) throw new Error(`useAuthUpdate must be used within a AuthProvider`);
return setAuthuser;
}
export const AuthProvider: React.FC = ({children}) => {
const [authUser, setAuthUser] = useState<AuthUser | null>(null)
return (
<authUpdateContext.Provider value={setAuthUser}>
<authContext.Provider value={authUser}>
{children}
</authContext.Provider>
</authUpdateContext.Provider>
)
}
// Usage only in components where needed (one of those two or both combined)
const authUpdate = useAuthUpdate()
const auth = useAuth()
您需要了解的重要一点是,当上下文的值发生变化时,订阅特定上下文的任何组件都会重新呈现。
回到你的问题,使用不同的钩子来更新和检索状态值没有意义,因为两者都连接到上下文提供者。我希望这是有道理的。更多详情 here
上下文的动作往往不会改变,而那些动作控制的状态却会改变。如果您创建一个同时提供状态值和状态更新方法的上下文,则每次状态值更改时,都会在订阅上下文的所有组件上触发渲染,即使它们只访问操作(可能没有更改) .
您可以通过将上下文拆分为两个单独的上下文来避免这种情况,并在仅订阅操作上下文的组件上保存渲染(以及相关的副作用,例如 useEffect() 调用)。
这是一个相关问题:Avoid runnning an effect hook when Context get updated 清楚地说明了这一点。如果在该示例中只使用了一个上下文,则 ActionsComponent 中的 useEffect 将在 ValueComponent 中的计数器的每个滴答时触发,但它不会因为每个组件都订阅了不同的上下文。
这里是 React 存储库上的一个问题中您的选项的简要概述:Preventing rerenders with React.memo and useContext hook。 #15156:Option 1 (Preferred): Split contexts that don't change together
一些博客文章在使用 React 上下文 时将上下文拆分为 两个 单独的更新和状态上下文。我想知道这是否真的会提高性能或会导致减少渲染周期。
所以最终的解决方案将有:
- 两个不同的上下文对象
- 两个不同的挂钩 1.) 访问状态 和 2.) 修改状态
- 两个独立的供应商(像
AuthProvider
一样打包成一个供应商)
拆分 Update 和 State 上下文的解决方案:
const authContext = React.createContext<AuthUser | null | undefined>(undefined)
const authUpdateContext = React.createContext<Dispatch<SetStateAction<AuthUser | null>> | null>(null)
export function useAuth() {
const authUser = React.useContext(authContext);
if (authUser === undefined) throw new Error(`useAuth must be used within a ContextAuthProvider`);
return authUser;
}
export const useAuthUpdate = () => {
const setAuthUser = React.useContext(authUpdateContext);
if (!setAuthUser) throw new Error(`useAuthUpdate must be used within a AuthProvider`);
return setAuthuser;
}
export const AuthProvider: React.FC = ({children}) => {
const [authUser, setAuthUser] = useState<AuthUser | null>(null)
return (
<authUpdateContext.Provider value={setAuthUser}>
<authContext.Provider value={authUser}>
{children}
</authContext.Provider>
</authUpdateContext.Provider>
)
}
// Usage only in components where needed (one of those two or both combined)
const authUpdate = useAuthUpdate()
const auth = useAuth()
您需要了解的重要一点是,当上下文的值发生变化时,订阅特定上下文的任何组件都会重新呈现。 回到你的问题,使用不同的钩子来更新和检索状态值没有意义,因为两者都连接到上下文提供者。我希望这是有道理的。更多详情 here
上下文的动作往往不会改变,而那些动作控制的状态却会改变。如果您创建一个同时提供状态值和状态更新方法的上下文,则每次状态值更改时,都会在订阅上下文的所有组件上触发渲染,即使它们只访问操作(可能没有更改) .
您可以通过将上下文拆分为两个单独的上下文来避免这种情况,并在仅订阅操作上下文的组件上保存渲染(以及相关的副作用,例如 useEffect() 调用)。
这是一个相关问题:Avoid runnning an effect hook when Context get updated 清楚地说明了这一点。如果在该示例中只使用了一个上下文,则 ActionsComponent 中的 useEffect 将在 ValueComponent 中的计数器的每个滴答时触发,但它不会因为每个组件都订阅了不同的上下文。
这里是 React 存储库上的一个问题中您的选项的简要概述:Preventing rerenders with React.memo and useContext hook。 #15156:Option 1 (Preferred): Split contexts that don't change together