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

React路由子组件导航失效:URL变更但页面未跳转求助

解决React路由切换URL变化但页面不跳转的问题

核心问题梳理

  • 多Router实例冲突:Home组件内通过ReactDOM.render嵌套了第二个Router,导致路由上下文隔离——点击Link时更新的是内层Router状态,顶层负责页面渲染的Router未同步响应。
  • 路由配置不匹配:ItemList中Link指向rules/${item},但现有路由仅定义了rules,未包含动态参数路由;同时Home作为父路由,未使用Outlet渲染子路由内容。
  • 手动DOM操作:通过ReactDOM.createRoot直接修改DOM,脱离React状态驱动机制,组件无法响应路由变化。
  • 组件基础错误:ItemList未导入Link组件,且props接收方式错误(直接解构itemList而非props对象)。

修复步骤及代码修改

1. 修正顶层路由配置(App.js)

添加动态参数路由,用容器组件包裹Home并引入Outlet渲染子路由:

import './App.css';
import Home from './Componnent/Home';
import Test from './Componnent/List_item copy';
import {BrowserRouter as Router, Route, Routes, Outlet } from 'react-router-dom';

// 父路由容器:包含Home内容和子路由出口
function HomeContainer() {
  return (
    <div className="App">
      <Home />
      <Outlet />
    </div>
  );
}

function App() {
  return (
    <Router>
      <Routes>
        <Route path='/' element={<HomeContainer />}>
          <Route path="rules" element={<Test />} />
          <Route path="rules/:itemId" element={<Test />} /> {/* 新增动态参数路由 */}
        </Route>
      </Routes>
    </Router>
  );
}

export default App;

2. 重构Home组件(Home.js)

移除嵌套Router和手动DOM操作,改用state管理列表数据:

import { BsFillCloudUploadFill, BsCheckLg } from "react-icons/bs";
import { AiFillFileText } from "react-icons/ai";
import { useState } from "react";
import { Watch } from "react-loader-spinner";
import ItemList from './List_item';
import { useNavigate } from 'react-router-dom';

function Home() {
  const [file, setFile] = useState(null);
  const [loading, setLoading] = useState(true);
  const [msg, setMsg] = useState(null);
  const [itemList, setItemList] = useState(null); // 存储列表数据的state
  const navigate = useNavigate();

  function redirect() {
    navigate('/rules');
  }

  function handleUpload() {
    if (!file) {
      setMsg("No file Selected");
      return;
    }
    const fd = new FormData();
    fd.append('file', file);

    setMsg("Uploading...");
    setLoading(false);
    fetch("http://localhost:3500/upload", {
      method: "POST",
      body: fd,
    })
    .then(res => {
      if (!res.ok) throw res.statusText;
      setMsg("Upload successful");
      document.getElementById("check").style.display = "block";
      setLoading(true);
      return res.json();
    })
    .then(data => setItemList(data['data'])) // 用state存储数据触发渲染
    .catch(err => {
      setMsg("Upload failed");
      console.error(err);
      setLoading(true);
    });
  }

  return (
    <div className='wrapper'>
      <h1>Uploading Files</h1>
      <div onClick={() => document.querySelector("input").click()} className='wrapper2'>
        <BsFillCloudUploadFill style={{ fontSize: "50px", color: "black" }} />
        <input 
          hidden 
          onChange={(e) => {
            setFile(e.target.files[0]);
            document.querySelector("section").style.display = "block";
            if (e.target.files[0].name) document.querySelector(".name").innerText = e.target.files[0].name;
          }} 
          type="file" 
        />
      </div>
      <section hidden={!file}>
        <div id='span-list'>
          {/* 条件渲染ItemList,替代手动DOM操作 */}
          {itemList ? (
            <ItemList data={itemList} />
          ) : (
            <li className='rows'>
              <AiFillFileText style={{ fontSize: "30px" }} />
              <div className='content'>
                <div className='detail' style={{ overflowY: "scroll" }}>
                  <span className='name'>fikjdejefhjlfcjlchejklle_01</span>
                </div>
              </div>
              <div hidden id="check">
                <BsCheckLg style={{ fontSize: "30px", color: "green" }} />
              </div>
            </li>
          )}
        </div>
      </section>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <Watch height="80" width="80" radius="48" color='black' visible={!loading} />
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', margin: '10px 0' }}>
        <button className="upload" onClick={handleUpload}>Upload</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', margin: '10px 0' }}>
        <button className="upload" onClick={redirect}>redirect</button>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        {msg && <span style={{ marginTop: "15px", color: 'black' }}>{msg}</span>}
      </div>
    </div>
  );
}

export default Home;

3. 修正ItemList组件(List_item.js)

导入Link组件,修正props接收方式和路径:

import { Link } from 'react-router-dom';
import { AiFillFileText } from "react-icons/ai";
import { BsCheckLg } from "react-icons/bs";

// 正确解构props中的data属性
function ItemList({ data }) {
  const listItems = data.map((item) => (
    <li key={item} className='rows'>
      <AiFillFileText style={{ fontSize: '30px' }} />
      <div className='content'>
        <div className='detail' style={{ overflowY: 'scroll' }}>
          <span className='name'>{item}</span>
        </div>
      </div>
      <div hidden id='check'>
        <BsCheckLg style={{ fontSize: '30px', color: 'green' }} />
      </div>
      {/* 使用绝对路径匹配路由配置 */}
      <Link to={`/rules/${item}`}> Click me </Link>
    </li>
  ));

  return <ul>{listItems}</ul>;
}

export default ItemList;

关键修复说明

  1. 单一Router实例:整个应用仅保留顶层Router,确保所有路由操作共享同一上下文,URL变化能触发顶层路由的组件更新。
  2. 状态驱动渲染:用React state存储列表数据,通过条件渲染替代手动DOM操作,保证组件响应React的更新机制。
  3. 路由匹配与Outlet:父路由通过Outlet渲染子路由内容,动态参数路由rules/:itemId匹配带item的路径,确保URL变化时对应的组件能被渲染。
  4. 基础组件修正:补全Link组件导入,修正props接收方式,避免运行时错误。

内容的提问来源于stack exchange,提问作者a_confused_student_2

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 04:37:03