URL变更时Apollo查询触发useContext重渲染问题咨询
这不是预期行为,出现该问题的核心原因有两个:
- AuthContext的value不稳定:每次AuthProvider组件渲染时,都会生成新的value对象,导致所有消费该Context的组件(比如Header)被迫重渲染。
- 查询执行时机不受控:路由切换时
CURRENT_USER查询会重新执行,触发onCompleted里的customer查询,此时旧的data会被重置为undefined,直到新查询结果返回,从而出现数据闪烁的现象。
下面逐个给出解决办法:
1. 稳定Context的Value(解决不必要重渲染)
你提到试过useMemo但没生效,大概率是依赖项配置有误。需要用useMemo包裹Context的value,确保只有当依赖项变化时才生成新的value对象:
修改auth.context中的Provider部分:
export const AuthProvider = ({ children }) => { // ... 其他代码保持不变 // 用useMemo稳定value,依赖项包含所有需要监听变化的状态 const contextValue = useMemo(() => ({ isAuthenticated, data, authenticate, auth, signout, }), [isAuthenticated, data, authenticate, auth, signout]); return ( <AuthContext.Provider value={contextValue}> {children} </AuthContext.Provider> ); };
这里的依赖项要包含value里的所有属性,确保只有这些值变化时,value才会更新,避免无意义的重渲染。
2. 控制查询的执行时机(解决数据闪烁)
目前每次CURRENT_USER查询完成都会调用customer查询,不管用户ID有没有变化,而且路由切换时CURRENT_USER可能重新执行,导致重复触发查询。我们需要添加判断,只在必要时执行GET_LOGGED_IN_CUSTOMER:
修改auth.context中的useQuery(CURRENT_USER)部分:
const { data: auth } = useQuery(CURRENT_USER, { onCompleted: (authData) => { // 只有当当前没有用户数据,或者用户ID变化时才执行查询 if (!data || authData.currentUser.id !== data?.user?.id) { customer({ variables: { where: { id: authData.currentUser.id }, }, }); } }, ssr: true, // 添加fetchPolicy,优先使用缓存,避免路由切换时重复请求 fetchPolicy: 'cache-first', });
同时,给useLazyQuery也设置缓存策略,避免重复请求:
const [customer, { data, loading, error }] = useLazyQuery( GET_LOGGED_IN_CUSTOMER, { ssr: true, fetchPolicy: 'cache-first', } );
3. 优化isAuthenticated的状态更新
当前isValidToken仅在组件初始化时调用一次,后续token或data变化时无法及时更新。可以把isValidToken改成依赖data的useMemo,再用useEffect同步isAuthenticated状态:
// 用useMemo计算是否有效,依赖data、error和localStorage的token const isValidToken = useMemo(() => { if (!isBrowser) return false; const token = localStorage.getItem("token"); return !error && token && !!data; }, [data, error]); // 用useEffect同步isAuthenticated状态 useEffect(() => { makeAuthenticated(isValidToken); }, [isValidToken]);
这样isAuthenticated会随着data和token的变化自动更新,同时保留authenticate方法用于主动触发验证。
4. 进一步优化Header组件的重渲染
修改完Context后,Header组件应该不会再因为Context无意义更新而重渲染。如果还有问题,可以用React.memo包裹Header组件,避免props未变化时的重渲染:
const Header: React.FC<Props> = ({ className, userData }) => { // ... 组件内容不变 }; // 用React.memo包裹,浅比较props export default React.memo(Header);
总结
通过稳定Context的value、控制查询执行时机、优化状态更新这几个步骤,就能解决URL变更时的重渲染和数据闪烁问题,让Context中的查询只在用户数据不存在或变化时触发。
内容的提问来源于stack exchange,提问作者realhat

