React子组件在Apollo Client useQuery完成后偶尔不重渲染问题
问题描述
我正在开发一个基于React、GraphQL(Apollo Client)的应用。应用加载时通过useQuery钩子发起查询获取当前用户,查询依赖localStorage中保存的Web Token做身份验证,返回的用户数据会作为props传给导航栏组件NavBar。
但现在的问题是:查询返回用户数据后,导航栏仅偶尔会更新。刷新应用10次,只有2-3次能正确重渲染导航栏。
主App组件代码
const App = () => { if (!window.Promise) { window.Promise = PromisePolyfill } const [token, setToken] = useState(null) const currentUserQuery = useQuery(GET_CURRENT_USER, { onCompleted: (data) => { console.log("user query completed", data) } }, []) return ( <div id="page-container"> {currentUserQuery.loading || currentUserQuery.error || currentUserQuery.data === null ? <NavBar currentUser={null} /> : <NavBar currentUser={currentUserQuery.data.me} />} <div id="content-wrap"> <Link to="/users/63f6c389dadc96cc795852ab">test</Link> <div> <Routes> <Route path="/" element={<Home />} /> <Route path="/login" element={<LoginForm setToken={setToken} />} /> <Route path="/signup" element={<h2>Sign Up</h2>} /> <Route path="/users/:id" element={<UserProfile />} /> <Route path="*" element={<h2>Page Not Found</h2>} /> </Routes> </div> </div> <Footer /> </div> ) }
NavBar组件代码
const NavBar = ({currentUser}) => { console.log("current user prop in navbar", currentUser) if (currentUser) { return ( <Navbar collapseOnSelect className="navbar" key={currentUser}> <Navbar.Toggle aria-controls="responsive-navbar-nav" /> <Navbar.Collapse id="responsive-navbar-nav"> <Nav className="me-auto"> <Nav.Link href="#" as="span"> <Link className='link' to="/"><img className="logo" src={ccLogo} alt="Chef's Club" /></Link> </Nav.Link> </Nav> <Nav> <NavDropdown title="Dropdown" id="basic-nav-dropdown"> <NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item> <NavDropdown.Item href="#action/3.2"> Another action </NavDropdown.Item> <NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item> <NavDropdown.Divider /> <NavDropdown.Item href="#action/3.4"> Separated link </NavDropdown.Item> </NavDropdown> </Nav> </Navbar.Collapse> </Navbar> ) } return ( <Navbar collapseOnSelect className="navbar" key={currentUser}> <Navbar.Toggle aria-controls="responsive-navbar-nav" /> <Navbar.Collapse id="responsive-navbar-nav"> <Nav className="me-auto"> <Nav.Link href="#" as="span"> <Link className='link' to="/"><img className="logo" src={ccLogo} alt="Chef's Club" /></Link> </Nav.Link> </Nav> <Nav> <Nav.Link href="#" as="span"> <Link className='link' to="/login">Login</Link> </Nav.Link> </Nav> </Navbar.Collapse> </Navbar> ) }
补充信息
- 从
onCompleted的控制台输出可以确认,服务器每次都返回了用户数据,但导航栏有时不更新。 - 成功场景:应用初始加载时给
NavBar传null,查询返回后props变化触发重渲染。 - 失败场景:服务器返回用户数据,但
NavBar未收到更新后的props,也没有重渲染。
分析与解决方案
1. useQuery参数错误导致Hook行为不稳定
Apollo Client的useQuery钩子不接受第三个依赖数组参数(这是React自定义Hook的写法,并非useQuery的官方API)。你在调用时传递了空数组[],这违反了React Hook的规则,导致组件渲染时useQuery的状态更新逻辑偶尔失效,无法触发App组件重渲染,进而NavBar无法收到新的props。
修复方式: 移除useQuery的第三个参数,正确调用如下:
const currentUserQuery = useQuery(GET_CURRENT_USER, { onCompleted: (data) => { console.log("user query completed", data) } })
2. NavBar的key属性使用不当
你给Navbar组件设置了key={currentUser},但currentUser是对象类型。React判断key是否变化时采用引用比较,如果Apollo Client缓存返回的用户对象引用出现复用(或渲染时机问题),可能导致key的变化检测失效,组件无法触发重渲染。
优化方式: 使用currentUser的唯一标识(如id)作为key,或者直接移除该key(条件渲染的两个分支React会自动处理):
// 有currentUser时用id作为key,无用户时用固定字符串 <Navbar collapseOnSelect className="navbar" key={currentUser?.id || 'anonymous-user'}>
3. 可选验证:检查Apollo Client缓存策略
如果上述修改后问题仍存在,检查Apollo Client的fetchPolicy配置。默认的cache-first策略会优先读取缓存数据,若缓存中的用户数据未及时更新,可能导致useQuery不触发网络请求或状态更新。可根据业务需求调整为network-only强制从服务器获取最新数据:
const currentUserQuery = useQuery(GET_CURRENT_USER, { onCompleted: (data) => { console.log("user query completed", data) }, fetchPolicy: 'network-only' // 按需使用 })
总结
最核心的问题是useQuery错误传递了依赖数组,破坏了Hook的正常执行逻辑,导致组件偶尔无法感知查询状态变化。先移除该参数,再调整key属性,基本可以解决导航栏偶尔不更新的问题。
内容的提问来源于stack exchange,提问作者jelder96

