You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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会复用已挂载的组件实例,而非卸载后重新创建。这导致:

  1. useState的初始值仅在组件第一次挂载时生效,后续路由切换时,即使sort props变化,filter状态也不会自动更新。
  2. 自定义钩子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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 17:08:13