React中NavLink切换路由时自定义钩子未触发问题求助
React路由切换时组件props更新但数据不更新问题解析
问题描述
我是React新手,跟着教程学习时遇到了一个问题:配置多个路由渲染同一个MovieList组件并传递不同sort props,使用NavLink导航时,组件未触发挂载/卸载,导致自定义钩子MovieService.useGet不执行,movies对象无法更新;但点击组件内调用setFilter的按钮时数据能正常更新,手动刷新页面后组件也能正确显示对应数据。使用版本:react@18.2.0、react-router-dom@6.16.0。
相关代码如下:
index.tsx
import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter } from "react-router-dom"; import './index.css'; import App from './App'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); root.render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> );
App.tsx
import React from 'react'; import { Header } from "./components"; import { Route, Routes } from "react-router-dom"; import { MovieList } from "./pages"; function App() { return ( <> <Header /> <Routes> <Route path="/" element={<MovieList />} /> <Route path="/movies/bottom" element={<MovieList sort='bottom' />} /> <Route path="/movies/unpopular" element={<MovieList sort='unpopular' />} /> <Route path="/movies/upcoming" element={<MovieList sort='upcoming' />} /> </Routes> </> ); } export default App;
Header.tsx(相关代码)
<li> <NavLink to="/" className={linkClass} aria-current="page" end>Recent</NavLink> </li> <li> <NavLink to="/movies/unpopular" className={linkClass}>Unpopular</NavLink> </li> <li> <NavLink to="/movies/bottom" className={linkClass}>Bottom Rated</NavLink> </li> <li> <NavLink to="/movies/upcoming" className={linkClass}>Upcoming</NavLink> </li>
MovieList.tsx
import { useState } from "react"; import { Card } from "../components"; import { MovieService } from "../services/Movie.service"; type MovieListProps = { sort?: MovieService.defaultSort; } export function MovieList({ sort }: MovieListProps = {}) { const [filter, setFilter] = useState({ defaultSort: sort }); const movies = MovieService.useGet(filter); return ( <main> <button onClick={() => setFilter({defaultSort: 'now_playing'})}>Recent</button> <button onClick={() => setFilter({defaultSort: 'unpopular'})}>unpopular</button> <button onClick={() => setFilter({defaultSort: 'bottom'})}>Bottom Ratted</button> <button onClick={() => setFilter({defaultSort: 'upcoming'})}>Upcoming</button> <section className="flex flex-wrap justify-center gap-4"> { movies.data?.results.map((movie, index) => ( <Card key={index} item={movie}/> )) } </section> </main> ); }
原因分析
核心问题是React Router的组件复用机制:当多个路由指向同一个组件时,React Router会复用已挂载的组件实例,而非卸载后重新创建。这导致:
useState的初始值仅在组件第一次挂载时生效,后续路由切换时,即使sortprops变化,filter状态也不会自动更新。- 自定义钩子
MovieService.useGet依赖filter状态,状态不更新就不会触发重新请求。
而组件内按钮调用setFilter是直接更新状态,会触发组件重渲染,进而让钩子感知到filter变化并请求数据;手动刷新页面时,组件重新挂载,useState会用新的sort props初始化状态,因此数据显示正常。
解决方案
方案1:监听props变化更新状态(推荐)
使用useEffect监听sort props的变化,当props更新时同步更新filter状态,触发钩子重新执行:
import { useState, useEffect } from "react"; import { Card } from "../components"; import { MovieService } from "../services/Movie.service"; type MovieListProps = { sort?: MovieService.defaultSort; } export function MovieList({ sort }: MovieListProps = {}) { const [filter, setFilter] = useState({ defaultSort: sort }); // 监听sort props变化,同步更新filter useEffect(() => { setFilter({ defaultSort: sort }); }, [sort]); const movies = MovieService.useGet(filter); return ( <main> <button onClick={() => setFilter({defaultSort: 'now_playing'})}>Recent</button> <button onClick={() => setFilter({defaultSort: 'unpopular'})}>unpopular</button> <button onClick={() => setFilter({defaultSort: 'bottom'})}>Bottom Ratted</button> <button onClick={() => setFilter({defaultSort: 'upcoming'})}>Upcoming</button> <section className="flex flex-wrap justify-center gap-4"> { movies.data?.results.map((movie, index) => ( <Card key={index} item={movie}/> )) } </section> </main> ); }
方案2:添加key属性强制组件重挂载
给每个路由的MovieList组件添加唯一key,让React认为是不同的组件实例,强制卸载/重新挂载:
// App.tsx修改部分 <Routes> <Route path="/" element={<MovieList key="/" />} /> <Route path="/movies/bottom" element={<MovieList key="/movies/bottom" sort='bottom' />} /> <Route path="/movies/unpopular" element={<MovieList key="/movies/unpopular" sort='unpopular' />} /> <Route path="/movies/upcoming" element={<MovieList key="/movies/upcoming" sort='upcoming' />} /> </Routes>
方案对比
- 方案1:保留组件复用,性能更优,能保留组件内的状态(比如用户临时筛选条件),适合大多数场景。
- 方案2:实现简单,但会丢失组件内所有状态,适合不需要保留状态的场景。
内容的提问来源于stack exchange,提问作者Taine
相关产品推荐
相关产品推荐

