React基础篇(三):项目中 React 基础核心知识点实战

目录

[第三章 用个案例回顾](#第三章 用个案例回顾)

[第四章 跨域处理](#第四章 跨域处理)

[4.1 react解决跨域的两种方式](#4.1 react解决跨域的两种方式)

[4.1.1 方案二:(18/19版本)](#4.1.1 方案二:(18/19版本))

[4.1.2 方案二:(旧版本)](#4.1.2 方案二:(旧版本))

[第五章 React的状态管理](#第五章 React的状态管理)

[5.1 Redux Toolkit](#5.1 Redux Toolkit)

[5.1.1 准备](#5.1.1 准备)

[5.1.2 基本使用](#5.1.2 基本使用)

[5.2 Zustand](#5.2 Zustand)

[5.2.1 准备](#5.2.1 准备)

[5.2.2 基本使用](#5.2.2 基本使用)

[第六章 React 路由](#第六章 React 路由)

[6.1 Router 6 版本核心](#6.1 Router 6 版本核心)

[6.2 Router 5 版本核心](#6.2 Router 5 版本核心)

[6.3 区别](#6.3 区别)


基础回顾:

React基础篇(一)
React 基础篇(二):使用vite搭建react项目

第三章 用个案例回顾

  • 结合小编React基础篇(一)所了解的知识,接下来通过一个案例,更深刻的了解知识点(注:小编目前使用语法的基本上是官方推荐函数式+hook写法)
  • App.jsx
javascript 复制代码
import { Link, Route, Routes } from "react-router-dom";
import { useState } from "react";
import "./App.css";
import Header from "./components/Header/index";
import List from "./components/List/index";
import Footer from "./components/Footer/index";
import "./App.css";

function App() {
  const [todos, setTodos] = useState([
    {
      id: 1,
      title: "学习 React",
      completed: false,
    },
    {
      id: 2,
      title: "学习 CSS",
      completed: true,
    },
    {
      id: 3,
      title: "学习 HTML",
      completed: false,
    },
  ]);

  // 添加任务
  const handleAdd = (todoObj) => {
    setTodos([...todos, todoObj]);
  };

  // 切换任务状态
  const handleToggleChange = (todoObj) => {
    setTodos((prev) =>
      prev.map((todo) => (todo.id === todoObj.id ? todoObj : todo)),
    );
  };

  // 删除任务
  const handleDelete = (id) => {
    setTodos((prev) => prev.filter((todo) => todo.id !== id));
  };

  // 全选/取消全选
  const handleToggleChangeAll = (checked) => {
    setTodos((prev) =>
      prev.map((todo) => ({
        ...todo,
        completed: checked,
      })),
    );
  };
  return (
    <>
      <div>
        <div className="todo-container">
          <div className="todo-wrap">
            <Header onAdd={handleAdd} />
            <List
              todos={todos}
              onToggleChange={handleToggleChange}
              onDelete={handleDelete}
            />
            <Footer todos={todos} onToggleChangeAll={handleToggleChangeAll} />
          </div>
        </div>
      </div>
    </>
  );
}

export default App;
  • Header组件
javascript 复制代码
import React, { useRef } from "react";
import { nanoid } from "nanoid";
import "./index.css";

export default function Header(props) {
  const { onAdd } = props;
  const inputRef = useRef(null);

  // 添加任务
  const handleKeyUp = (e) => {
    if (e.key === "Enter") {
      const title = inputRef.current.value;
      if (title.trim() === "") {
        return;
      }
      onAdd({
        id: nanoid(),
        title,
        completed: false,
      });
      inputRef.current.value = "";
    }
  };

  return (
    <div className="todo-header">
      <input
        onKeyUp={handleKeyUp}
        ref={inputRef}
        type="text"
        placeholder="请输入你的任务名称,按回车键确认"
      />
    </div>
  );
}
  • List组件
javascript 复制代码
import PropTypes from "prop-types";

import "./index.css";
import Item from "../Item/index";

function List(props) {
  // 检查 props 是否符合 propTypes 定义
  PropTypes.checkPropTypes(List.propTypes, props, "prop", "List");
  // 解构 props 中的属性
  const { todos, onToggleChange, onDelete } = props;

  return (
    <ul className="todo-main">
      {todos.map((todo) => (
        <Item
          key={todo.id}
          todo={todo}
          onToggleChange={onToggleChange}
          onDelete={onDelete}
        />
      ))}
    </ul>
  );
}

// propTypes 和 defaultProps 必须定义在函数外部
List.propTypes = {
  todos: PropTypes.array.isRequired,
  onToggleChange: PropTypes.func.isRequired,
};

List.defaultProps = {
  todos: 1,
  onToggleChange: () => {},
};

export default List;
  • Item组件
javascript 复制代码
import { useState } from "react";
import "./index.css";

export default function Item(props) {
  const { todo, onToggleChange, onDelete } = props;
  const { id, title, completed } = todo;
  const [showDelete, setShowDelete] = useState(false);

  // 切换任务状态
  const handleToggleChange = (id) => {
    onToggleChange({
      id,
      title,
      completed: !completed,
    });
  };


  // 删除任务
  const handleDelete = (id) => {
    if (!confirm("确定删除吗?")) {
      return;
    }
    onDelete(id);
  };

  return (
    <li
      onMouseEnter={() => setShowDelete(true)}
      onMouseLeave={() => setShowDelete(false)}
    >
      <label>
        <input
          type="checkbox"
          checked={completed}
          onChange={() => handleToggleChange(id)}
        />
        <span>{title}</span>
      </label>
      <button
        className="btn btn-danger"
        style={{ display: showDelete ? "block" : "none" }}
        onClick={() => handleDelete(id)}
      >
        删除
      </button>
    </li>
  );
}
  • Footer组件
javascript 复制代码
import React from "react";
import "./index.css";

export default function Footer(props) {
  const { todos, onToggleChangeAll } = props;

  // 计算已完成任务数量
  // const completedCount = todos.filter((todo) => todo.completed).length;
  const completedCount = todos.reduce(
    (prev, cur) => prev + (cur.completed ? 1 : 0),
    0,
  );
  // 计算总任务数量
  const totalCount = todos.length;

  // 全选/取消全选
  const handleToggleChange = (e) => {
    onToggleChangeAll(e.target.checked);
  };

  // 清除已完成任务
  const handleClearCompleted = () => {
    onToggleChangeAll(false);
  };

  return (
    <>
      <div className="todo-footer">
        <label>
          <input
            type="checkbox"
            checked={completedCount === totalCount && totalCount !== 0}
            onChange={handleToggleChange}
          />
        </label>
        <span>
          <span>已完成{completedCount}</span> / 全部{totalCount}
        </span>
        <button
          className="btn btn-danger"
          onClick={() => handleClearCompleted()}
        >
          清除已完成任务
        </button>
      </div>
    </>
  );
}
  • 效果
  • 相关解释:
  1. 按功能拆分为Header(添加任务)、List(任务列表容器)、Item(单个任务项)、Footer(任务统计 / 操作),符合 React 组件化 "单一职责" 原则。
  2. 父子组件通信:通过 props 传递数据(如todos)和回调函数(如onAdd/onToggleChange/onDelete),子组件触发事件后调用父组件回调完成状态更新。(目前通信方面都是在App父/祖先组件上维护,向下级传数据使用的式props,子/孙组件想要给上级组件数据只有通过方法调用,)
  3. 添加任务:Header通过useRef获取输入框值,监听回车事件,校验非空后调用onAdd添加任务,清空输入框。
  4. PropTypes.checkPropTypes调用:React 15.5+ 后PropTypes从 React 剥离,且checkPropTypes仅需在开发环境才需要引出校验,才能看到传参错误控制台给提示

第四章 跨域处理

再不清楚,可以看小编小面这篇文章:(虽然是vue的,但是她们只有组件的区别,原理是相同的)

利用代理服务器解决跨域问题

4.1 react解决跨域的两种方式

  • 方案一:在package.json中追加如下配置
javascript 复制代码
```json
"proxy":"http://localhost:5000"
```
  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

4.1.1 方案二:(18/19版本)

  1. 在vite.config.js中配置代理,与vue相似
javascript 复制代码
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      // 代理 1:/api -> http://localhost:5000
      '/api': {
        target: 'http://localhost:5000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
        configure: (proxy) => {
          proxy.on('proxyReq', (proxyReq, req) => {
            console.log(`📡 [PROXY] ${req.method} ${req.url} -> http://localhost:5000${proxyReq.path}`)
          }) 
        }
      },

      // 代理 2:/api2 -> http://localhost:5001
      '/api2': {
        target: 'http://localhost:5001',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api2/, ''),
        configure: (proxy) => {
          proxy.on('proxyReq', (proxyReq, req) => {
            console.log(`📡 [PROXY] ${req.method} ${req.url} -> http://localhost:5001${proxyReq.path}`)
          })
        }
      }
    }
  }
})

4.1.2 方案二:(旧版本)

  1. 创建代理配置文件 ` 在src下创建配置文件:src/setupProxy.js `
  2. 编写setupProxy.js配置具体代理规则:
javascript 复制代码
const { createProxyMiddleware } = require('http-proxy-middleware')

module.exports = function(app) {
    app.use(
        createProxyMiddleware('/api1', { // 遇见/api1前缀的请求,就会触发该该代理配置
            target: 'http://localhost:5000', // 代理服务器请求转发给谁
            changeOrigin: true, // 控制服务器收到的响应头中的Host字段的指
            pathRewrite: {'^/api1': ''} // 重写请求路径(必须)
        }),
        createProxyMiddleware('/api2', {
            target: 'http://localhost:5001',
            changeOrigin: true,
            pathRewrite: {'^/api2': ''}
        })
    )
}
  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置稍微繁琐,前端请求资源时必须加前缀。
  • 注意事项

有的时候我们在使用下面这段代码时,会发现控制台调了两次接口,是因为我们开了严格模式导致的!在main.jsx中,如果不想要注释了就好;还有这种重复调用 只在开发模式下 发生,生产环境不会有这个问题。

javascript 复制代码
useEffect(() => {
    // 使用代理路径 /api,会被转发到 http://localhost:5000
    // pathRewrite 会将 /api 去掉,所以实际请求是 http://localhost:5000/students
    axios
      .get("http://localhost:5173/api/students")
      .then((response) => {
        console.log(response.data);
        setStudents(response.data);
        setLoading(false);
      })
      .catch((err) => {
        setError(err.message);
        setLoading(false);
      });
}, []);

第五章 React的状态管理

5.1 Redux Toolkit

5.1.1 准备

javascript 复制代码
npm install react-redux @reduxjs/toolkit

5.1.2 基本使用

  • Redux 核心模块划分(src/store)

Redux 遵循「单一数据源 + 状态不可变(RTK 内部已封装可变写法)+ 纯函数修改」的原则,代码中按「功能模块」拆分 store,符合模块化最佳实践:

javascript 复制代码
src/store
├── index.js          # 根 store 配置(组合所有子模块 reducer)
└── modules/
    ├── counterStore.js  # 计数器功能模块(同步状态)
    └── channelStore.js  # 频道列表模块(异步请求 + 状态管理)
  • 核心 API 解析及使用(Redux Toolkit):
  • createSlice: 是 RTK 的核心简化工具,最核心的 API,整合了「初始状态、reducer 函数、action 创建函数」,避免原生 Redux 手动写 action type、action creator、switch reducer 的繁琐
javascript 复制代码
import { createSlice } from "@reduxjs/toolkit";

const countStore = createSlice({
    name: 'counter', // 命名空间(action type 前缀,如 "counter/inscrement")
    initialState: { count: 0 }, // 初始状态
    reducers: { // 同步 reducer 函数(RTK 内置 Immer,支持「直接修改状态」,底层自动转不可变操作)
        inscrement (state) {
            state.count++ // 看似直接修改,实际 Immer 处理为不可变更新
        },
        descrement (state) {
            state.count--
        },
        addToNum (state, actions) {
            state.count += actions.payload // payload 是 action 携带的参数
        }
    }
})

// 解构生成的 action creator 函数(直接调用返回标准 action 对象,如 {type: "counter/inscrement"})
const { inscrement, descrement, addToNum } = countStore.actions
// 导出 reducer(供根 store 组合)
const reducer = countStore.reducer

export {inscrement, descrement, addToNum}
export default reducer
  1. name:作为 action type 的前缀,避免多个模块 action type 冲突;
  2. reducers 中的函数:第一个参数是「当前状态」,第二个是「action 对象」(payload 为传参);
  3. Immer 库:RTK 内置 Immer,允许在 reducer 中「直接修改 state」(如 state.count++),无需手动写 {...state, count: state.count + 1}。
  • configureStore:根 store 配置,store/index.js 中用 configureStore 替代原生 Redux 的 createStore,自动集成中间件(如 redux-thunk 处理异步)、DevTools 调试工具,无需手动配置
javascript 复制代码
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from './modules/counterStore'
import channelReducer from './modules/channelStore'

const store = configureStore({
    reducer: { // 组合子模块 reducer,最终 state 结构为 { counter: {...}, channel: {...} }
        counter: counterReducer,
        channel: channelReducer
    }
})
console.log('store', store, store.getState())

export default store

输出内容查看

  • 异步 action 处理:RTK 内置 redux-thunk 中间件,允许 action creator 返回「函数」(而非仅 action 对象),从而处理异步逻辑:(注意写法)
javascript 复制代码
import { createSlice } from "@reduxjs/toolkit";
import axios from "axios";

const channelStore = createSlice({
    name: 'channel',
    initialState: {
        channelList: []
    },
    reducers: {
        setChannels(state, actions) {
            state.channelList = actions.payload
        }
    }
})

const { setChannels } = channelStore.actions

// 异步 action creator
const fetchChannelList = () => {
    // 返回一个接收 dispatch 的函数(thunk 函数)
    return async (dispatch) => {
        const res = await axios.get('http://geek.itheima.net/v1_0/channels') // 异步请求
        dispatch(setChannels(res.data.data.channels)) // 请求成功后,dispatch 同步 action 修改状态
    }
}

export { fetchChannelList }

const reducer = channelStore.reducer

export default reducer

核心逻辑:

  1. 异步 action creator 不直接修改状态,而是通过「请求数据 → dispatch 同步 action」的方式修改;
  2. dispatch 由 thunk 中间件注入,用于触发同步 reducer 函数。
  • Provider:全局注入 store(index.js)
javascript 复制代码
import { Provider } from 'react-redux';
import store from './store';

root.render(
  <Provider store={store}> {/* 把 store 注入整个 React 应用 */}
    <App />
  </Provider>
);
  1. Provider 是 react-redux 的核心组件,接收 store 属性,使所有子组件能通过 useSelector/useDispatch 访问 store;
  2. 必须包裹在根组件外层,否则组件无法获取 store。
  • React 组件中使用 Redux(App.js)
javascript 复制代码
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import {
  inscrement,
  descrement,
  addToNum,
  subtractFromNum,
} from "./store/modules/counterStore";
import { fetchChannelList } from "./store/modules/channelStore";
import "./App.css";

function App() {
  const { count } = useSelector((state) => state.counter);
  const { channelList } = useSelector((state) => state.channel);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchChannelList());
  }, [dispatch]);

  return (
    <div className="App">
      <button onClick={() => dispatch(descrement())}>-</button>
      {count}
      <button onClick={() => dispatch(inscrement())}>+</button>
      <button onClick={() => dispatch(addToNum(10))}>+10</button>
      <button onClick={() => dispatch(addToNum(20))}>+20</button>
      <button onClick={() => dispatch(subtractFromNum(10))}>-10</button>
      <button onClick={() => dispatch(subtractFromNum(20))}>-20</button>

      <h2>Channel List</h2>
      <ul>
        {channelList.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

React 组件通过 react-redux 提供的 Hooks 连接 Redux:

1、useSelector:读取 Redux 状态

javascript 复制代码
import { useSelector, useDispatch } from "react-redux";

// 从根 state 中解构 counter 模块的 count,channel 模块的 channelList
const { count } = useSelector(state => state.counter)
const { channelList } = useSelector(state => state.channel)
  • useSelector 接收一个函数,参数是 Redux 根 state,返回需要的状态;
  • 组件会订阅对应状态,状态更新时组件自动重渲染。

2、useDispatch:触发 action

javascript 复制代码
const dispatch = useDispatch() // 获取 dispatch 函数

// 触发同步 action
<button onClick={()=>dispatch(descrement())}>-</button>
// 触发带参数的同步 action
<button onClick={()=>dispatch(addToNum(10))}>+10</button>
// 触发异步 action(在 useEffect 中初始化请求)
useEffect(() => {
  dispatch(fetchChannelList())
}, [dispatch])
  • useDispatch 返回 Redux store 的 dispatch 方法,用于触发 action(同步 / 异步);
  • 异步 action 触发后,thunk 中间件执行异步逻辑,最终通过 dispatch 同步 action 修改状态。
维度 旧版 Redux Redux Toolkit
核心 API createStore createSlice + configureStore
状态修改 必须手动返回新对象,不能直接改 state 可以直接写 state.num++,自动处理
Action 需要手动定义、手动导出 createSlice 自动生成
异步支持 需要手动安装 redux-thunk、配置中间件 内置 thunk,开箱即用
开发体验 大量重复样板代码 极度精简,代码量少一半以上
官方态度 已废弃,不维护 官方强力推荐,唯一标准
配套生态 老旧、兼容性差 适配 React18/19、TS、现代项目

5.2 Zustand

5.2.1 准备

javascript 复制代码
npm install zustand axios

5.2.2 基本使用

  • 模块划分:
javascript 复制代码
src/store
├── index.js          # 根 store 配置
└── modules/
    ├── counterStore.js  # 计数器功能模块(同步状态)
    └── channelStore.js  # 频道列表模块(异步请求 + 状态管理)
  • modules/***.js
javascript 复制代码
/**
 * 计数器模块
 * @param {Function} set - 更新状态的函数
 * @param {Function} get - 获取当前状态的函数
 * @param {Object} api - store 的 API 对象
 */
const counterSlice = (set, get, api) => ({
  count: 0,
  
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  addToNum: (num) => set((state) => ({ count: state.count + num })),
  subtractFromNum: (num) => set((state) => ({ count: state.count - num }))
})

export default counterSlice
javascript 复制代码
import axios from 'axios'

/**
 * 频道列表模块
 * @param {Function} set - 更新状态的函数
 * @param {Function} get - 获取当前状态的函数
 * @param {Object} api - store 的 API 对象
 */
const channelSlice = (set, get, api) => ({
  channelList: [],
  
  fetchChannelList: async () => {
    try {
      const res = await axios.get('http://geek.itheima.net/v1_0/channels')
      set({ channelList: res.data.data.channels })
    } catch (error) {
      console.error('Failed to fetch channels:', error)
    }
  }
})

export default channelSlice
  • store/index.js
javascript 复制代码
import { create } from 'zustand'
import counterSlice from './modules/counter'
import channelSlice from './modules/channel'

// 参数说明:
// - set: 用于更新状态
// - get: 用于获取当前状态
// - api: store 的 API 对象(包含 subscribe、destroy 等方法)
const useStore = create((set, get, api) => ({
  ...counterSlice(set, get, api),
  ...channelSlice(set, get, api)
}))

export default useStore
  • 组件使用(App.jsx):极简的消费方式
javascript 复制代码
import { useEffect } from 'react'
import useStore from './store'
import './App.css'

function App() {
  const {
    count,
    channelList,
    increment,
    decrement,
    addToNum,
    subtractFromNum,
    fetchChannelList
  } = useStore()

  useEffect(() => {
    fetchChannelList()
  }, [fetchChannelList])

  return (
    <div className="App">
      <button onClick={decrement}>-</button>
      {count}
      <button onClick={increment}>+</button>
      <button onClick={() => addToNum(10)}>+10</button>
      <button onClick={() => addToNum(20)}>+20</button>
      <button onClick={() => subtractFromNum(10)}>-10</button>
      <button onClick={() => subtractFromNum(20)}>-20</button>
      
      <h2>Channel List</h2>
      <ul>
        {channelList.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  )
}

export default App
  • 注意:组件中解构多个状态时,Zustand 推荐「精准选择」避免不必要的重渲染:
javascript 复制代码
// 优化前:解构所有,任意状态变化都会重渲染
const { count, channelList } = useStore()

// 优化后:仅监听需要的状态,粒度更细
const count = useStore(state => state.count)
const channelList = useStore(state => state.channelList)

这里 3 个超级重要参数:

  1. set:修改状态,等价于Redux 里的 dispatch + reducer 直接更新状态
  2. get:获取当前最新状态
  3. ap:i高级用法,一般很少用,暂时不用管。
  • 常用语法总结:
javascript 复制代码
// 1. 创建 store
const useStore = create((set) => ({
  状态名: 值,
  方法名: () => set({}),
}));

// 2. 组件使用
const 状态 = useStore(state => state.状态名)
const 方法 = useStore(state => state.方法名)

// 3. 修改状态
set({ 状态名: 新值 })

// 4. 异步
方法: async () => {
  const res = await axios.get()
  set({ 数据: res.data })
}


// 模块文件
const 模块 = (set) => ({ 状态、方法 })

// 合并
...模块(set, get, api)

第六章 React 路由

6.1 Router 6 版本核心

  • 路由模式:集中式配置 + useRoutes(核心)
javascript 复制代码
import { NavLink, useRoutes } from "react-router-dom";

// src/routes/index.jsx - 集中式路由配置数组
const routes = [
  { path: "/", element: <Navigate to="/home" /> }, // 默认重定向
  { path: "/home", element: <Home />, children: [...] }, // 嵌套路由
  { path: "*", element: <Navigate to="/home" /> } // 404 兜底
];

// src/App.jsx - 组件内使用 useRoutes 渲染路由
const element = useRoutes(routes);
return <Suspense fallback={<div>Loading...</div>}>{element}</Suspense>;
  1. 替代 Router 5 的 <Switch> + 零散 <Route> 写法,代码更易维护;
  2. 结合 React.lazy() 实现路由组件懒加载,配合 Suspense 处理加载态。(如 const Home = lazy(() => import("../pages/Home/index")))
  • 嵌套路由:Outlet 作为子路由出口
javascript 复制代码
// Home 组件(父路由)中渲染子路由出口
<Outlet /> 
// 路由配置中定义子路由(src/routes/index.jsx)
{
  path: "/home",
  element: <Home />,
  children: [
    { path: "news", element: <News /> }, // /home/news
    { path: "message", element: <Message /> } // /home/message
  ]
}
  1. Router 6 强制要求嵌套路由通过 Outlet 声明渲染位置,Router 5 则是手动嵌套 <Route> 标签;
  2. 子路由路径无需写全路径(如 news 而非 /home/news),更简洁。
  • 导航链接:NavLink 激活态重构
javascript 复制代码
// 激活态通过函数式 className 判断(替代 Router 5 的 activeClassName)
<NavLink
  to="/home"
  className={({ isActive }) => `list-group-item ${isActive ? "active" : ""}`}
>Home</NavLink>
  1. Router 6 移除 activeClassName/activeStyle,改为通过回调函数获取 isActive 状态;
  • 路由传参方式1:Params 传参

需要routes.js中配置:

javascript 复制代码
      {
        path: "detail/:id/:type?", // 支持 params 传参,type 可选
        // path: "detail", // 支持 search 传参,id 可选,title 可选,content 可选
        element: <Detail />,
      },
javascript 复制代码
// 跳转(URL 格式:/home/detail/1001/news)
<NavLink to="/home/detail/1001/news">查看新闻详情 (Params)</NavLink>
// 接收参数
import { useParams } from "react-router-dom";
const params = useParams(); 

<p>ID: {params.id}</p>
<p>类型: {params.type}</p>

Router 6 默认精确匹配,但 /home/detail/:id/:type 这种写法会开启模糊匹配;

  • 路由传参方式2:Search 传参
javascript 复制代码
      {
        // path: "detail/:id/:type?", // 支持 params 传参,type 可选
        path: "detail", // 支持 search 传参,id 可选,title 可选,content 可选
        element: <Detail />,
      },
javascript 复制代码
// 跳转(URL 格式:/home/detail?id=2001&title=搜索传参示例)
<NavLink to="/home/detail?id=2001&title=搜索传参示例">查看详情 (Search)</NavLink>

import { useSearchParams } from "react-router-dom";
// 接收参数(Router 6 新增 useSearchParams)
const [searchParams] = useSearchParams();

<p>ID: {searchParams.get('id')}</p>
<p>标题: {searchParams.get('title')}</p>
  • 路由传参方式3:State 传参
javascript 复制代码
// 跳转(参数不暴露在 URL 中)
<NavLink to="/home/detail" state={{ id: 3001, content: "state 传参内容" }}>查看详情 (State)</NavLink>
// 接收参数
import { useLocation } from "react-router-dom";
const location = useLocation();

const state = location.state;
<p>ID: {state.id}</p>
<p>内容: {state.content}</p>
  • 编程式导航:useNavigate 替代 useHistory
javascript 复制代码
const navigate = useNavigate();

// Params 跳转
onClick={() => navigate("/home/detail/4001/navigate")}
// Search 跳转
onClick={() => navigate("/home/detail?id=5001&title=navigate搜索")}
// State 跳转
onClick={() => navigate("/home/detail", { state: { id: 6001 } })}
// 回退上一页
onClick={() => navigate(-1)}
// 跳转到 About
onClick={() => navigate("/about")}

第二个对象传参,还可以控制是replace还是push

javascript 复制代码
navigate(路径, {
  replace: true,  // true=替换,false=push(默认)
  state: { id: 1, name: 'xxx' } // 路由传参
})
功能 Router 5(history) Router 6(navigate)
跳转(可返回) push("/home") navigate("/home")
替换(不可返回) replace("/home") navigate("/home", {replace:true})
返回 goBack() navigate(-1)
前进 goForward() navigate(1)
跳多级 go(-2) navigate(-2)
带 state 跳转 push("/a", {id:1}) navigate("/a", {state:{id:1}})
  • 重定向:Navigate 替代 Redirect
javascript 复制代码
// 默认路由重定向到 /home
{ path: "/", element: <Navigate to="/home" /> }
// 404 兜底重定向
{ path: "*", element: <Navigate to="/home" /> }
  • 路由 Hooks 组合使用
javascript 复制代码
// About 组件中获取路由全量信息
const navigate = useNavigate();
const location = useLocation();
const params = useParams();

// Detail 组件中拆分使用
const params = useParams(); // Params 传参
const [searchParams] = useSearchParams(); // Search 传参
const location = useLocation(); // State 传参

6.2 Router 5 版本核心

  • 路由模式,Router 5 必须这样写(Switch + Route)
javascript 复制代码
import { Switch, Route, Redirect } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import NotFound from './pages/NotFound'

function App() {
  return (
    <div>
      <Switch>

        {/* 重定向 / → /home */}
        <Route exact path="/" render={() => <Redirect to="/home" />} />

        {/* 普通路由 */}
        <Route path="/home" component={Home} />
        <Route path="/about" component={About} />

        {/* 404 */}
        <Route component={NotFound} />

      </Switch>
    </div>
  )
}
  1. <Switch>:只匹配第一个路由
  2. <Route exact path="/">:精确匹配
  3. <Redirect to="/home">:重定向
  4. <Route component={Home}>:挂载组件
  • 嵌套路由(Router5:必须在组件内部再写 <Switch> + <Route>)
javascript 复制代码
// pages/Home/index.js
import { Switch, Route } from 'react-router-dom'
import News from './News'
import Message from './Message'

const Home = () => {
  return (
    <div>
      <h1>Home 页面</h1>

      {/* 子路由 */}
      <Switch>
        <Route path="/home/news" component={News} />
        <Route path="/home/message" component={Message} />
      </Switch>
    </div>
  )
}
  1. 必须写完整路径 /home/news
  2. 必须自己包 <Switch>
  3. 没有 <Outlet>
  • NavLink 导航高亮
javascript 复制代码
<NavLink activeClassName="active" to="/home">
  首页
</NavLink>
  1. activeClassName="active" 自动激活
  2. 不用写函数判断
  • 编程式导航useHistory()
javascript 复制代码
import { useHistory } from 'react-router-dom'

const Home = () => {
  const history = useHistory()

  return (
    <button onClick={() => history.push('/about')}>
      跳转到关于
    </button>
  )
}
功能 Router 5(history) Router 6(navigate)
跳转(可返回) push("/home") navigate("/home")
替换(不可返回) replace("/home") navigate("/home", {replace:true})
返回 goBack() navigate(-1)
前进 goForward() navigate(1)
跳多级 go(-2) navigate(-2)
带 state 跳转 push("/a", {id:1}) navigate("/a", {state:{id:1}})
  • 路由传参方式一:params 传参
javascript 复制代码
to="/detail/1001"
const params = useParams() // 一样
  • 路由传参方式二:search 传参
javascript 复制代码
import { useLocation } from 'react-router-dom'

const location = useLocation()
const search = new URLSearchParams(location.search)
const id = search.get('id')
  • 路由传参方式三:state 传参
javascript 复制代码
// 跳转
history.push('/detail', { id: 1001 })

// 获取
const location = useLocation()
location.state

注意:上面说的都是支持hooks之后,支持旧版本,也就是react还在使用类式组件时,当时是用props接收参数的,如下:

javascript 复制代码
// Detail.js 【Router5 纯 props 写法】
function(props){
    // 1. params 参数
    console.log(props.match.params)

    // 2. search 参数
    console.log(props.location)

    // 3. 跳转方法
    console.log(props.history)

    return ...
}

6.3 区别

功能 Router 6 Router 5
路由容器 useRoutes() / <Routes> <Switch>
重定向 <Navigate to="/home" /> <Redirect to="/home" />
嵌套路由 <Outlet /> 组件内写 <Route>
导航高亮 className={({isActive}) => ...} activeClassName="active"
编程导航 useNavigate() useHistory()
跳转 navigate('/a') history.push('/a')
Search 参数 useSearchParams() 手动解析 location.search
路径匹配 默认为精确匹配 需要加 exact 才精确
404 path="*" 最后一个 <Route>
相关推荐
Hello--_--World1 小时前
React 的核心设计理念是什么?并列举三大核心特性。
javascript·react.js·ecmascript
Momo__1 小时前
contenteditable 深度剖析:让网页元素「活」起来
前端·html
淸湫1 小时前
前端JavaScript:NaN、undefined、null详解
javascript
Hello--_--World2 小时前
React:描述UI 官网笔记
笔记·react.js·ui
栀栀栀栀栀栀2 小时前
强迫症犯了(゚∀゚) 2026/4/26
前端·javascript·vue.js
RPGMZ2 小时前
RPGMakerMZ 获取敌人攻击时属性 用于画UI或属性克制
javascript·游戏引擎·rpgmz·rpgmakermz
Lucas_coding2 小时前
【xiaozhi-客户端】xiaozhi-web-client 连接客户端 6位有效码
前端
谪星·阿凯2 小时前
电商系统Web渗透测试实战指南
前端·网络·安全·web安全·网络安全
Ruihong2 小时前
Vue 的 :deep/:global/:slotted 怎么转成 React ?一份对照指南?
vue.js·react.js·面试