2026版 React 零基础小白进阶知识点【衔接基础·企业级实战】

✅ 无缝衔接基础版:基于「函数式组件+React Hook」核心,只讲基础版未覆盖的进阶刚需知识点 ,零基础无断层过渡

✅ 2026最新技术栈:聚焦企业100%在用的主流方案(React 18并发特性、React Router 6、Axios封装、状态管理),剔除过时内容

✅ 实战导向:所有知识点配套「企业级代码模板+真实场景案例」,复制即可复用,学完直接能对接项目开发

✅ 条理清晰:按「核心进阶Hook→路由进阶→网络请求→状态管理→性能优化→工程化」逻辑排序,符合学习认知规律

✅ 学习目标:从「会写基础React代码」升级为「能开发企业级React应用」,具备前端工程师核心竞争力


一、前置认知:React进阶的核心价值(为什么要学这些?)

基础版我们掌握了「JSX、useState、组件化、父传子」等基础能力,能写简单的静态页面和基础交互。但企业开发中,还会遇到这些核心问题:

  1. 组件销毁后如何清除定时器/取消请求?(需要useEffect生命周期)
  2. 多层嵌套组件如何高效传值?(需要useContext跨组件通信)
  3. 如何实现页面跳转、权限控制?(需要React Router 6进阶)
  4. 如何对接后端接口、统一处理请求错误?(需要Axios企业级封装)
  5. 复杂项目的全局数据如何管理?(需要 Zustand 状态管理)
  6. 项目体积大、加载慢如何优化?(需要性能优化技巧)

本进阶版就是为解决这些「企业开发刚需问题」而生,所有知识点都是前端工程师日常工作的核心内容,也是面试高频考点。


二、核心进阶Hook:从"能写"到"会写"的关键(⭐⭐⭐⭐⭐ 必学)

基础版我们只学了useState,这是React的基础数据管理Hook。进阶阶段,我们需要掌握4个核心Hook,覆盖「生命周期、跨组件通信、DOM操作、缓存优化」所有核心场景。

✔️ 1. useEffect:生命周期管理(组件的"出生、成长、死亡")

1.1 为什么需要useEffect?

基础版的代码都是"同步执行"的,但实际开发中,我们需要处理「异步操作」(如页面加载后请求接口、组件销毁前清除定时器),这些操作需要绑定到组件的特定"生命周期阶段",而useEffect就是React 18中唯一的生命周期解决方案

1.2 核心概念:副作用

"副作用"指的是组件渲染之外的操作,比如:

  • 网络请求(调用后端接口)
  • 操作DOM(获取DOM元素、设置定时器/计时器)
  • 订阅/取消订阅(如WebSocket连接)
    useEffect的作用就是「管理这些副作用」,并控制它们的执行时机。
1.3 基础语法(固定模板,背下来)
jsx 复制代码
import { useEffect } from 'react';

function MyComponent() {
  // 副作用函数:要执行的异步操作/DOM操作
  useEffect(() => {
    // 1. 这里写要执行的副作用代码(如请求接口、设置定时器)
    
    // 2. 清理函数(可选):组件销毁时执行,用于清除副作用
    return () => {
      // 如:清除定时器、取消网络请求、关闭WebSocket
    };
  }, [依赖数组]); // 关键:控制副作用的执行时机

  return <div>组件内容</div>;
}
1.4 3种核心用法(覆盖99%场景,实战必备)
用法1:组件挂载后执行一次(最常用)

场景:页面加载后请求初始化数据(如商品列表、用户信息)、设置全局事件监听。

核心:依赖数组传「空数组[]」,表示"只在组件第一次渲染后执行一次"。

jsx 复制代码
import { useState, useEffect } from 'react';

function GoodsList() {
  const [goods, setGoods] = useState([]);

  // 组件挂载后请求商品列表(只执行一次)
  useEffect(() => {
    const fetchGoods = async () => {
      const res = await fetch('/api/goods'); // 模拟请求后端接口
      const data = await res.json();
      setGoods(data);
    };
    fetchGoods();
  }, []); // 空依赖数组 → 只执行一次

  return (
    <div>
      {goods.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}
用法2:依赖数据变化时执行

场景:搜索框输入后触发搜索、表单值变化后做校验。

核心:依赖数组传「具体变量」,当变量的值发生变化时,副作用函数重新执行。

jsx 复制代码
import { useState, useEffect } from 'react';

function SearchBox() {
  const [keyword, setKeyword] = useState('');
  const [results, setResults] = useState([]);

  // 当keyword变化时,触发搜索(输入完成后自动查)
  useEffect(() => {
    const timer = setTimeout(async () => {
      const res = await fetch(`/api/search?keyword=${keyword}`);
      const data = await res.json();
      setResults(data);
    }, 500); // 防抖:输入500ms后再请求,避免频繁调用接口

    // 清理函数:输入变化时,清除上一个定时器
    return () => clearTimeout(timer);
  }, [keyword]); // 依赖keyword → 只有keyword变了才执行

  return (
    <div>
      <input 
        type="text" 
        value={keyword} 
        onChange={(e) => setKeyword(e.target.value)} 
        placeholder="请输入搜索关键词"
      />
      <div>搜索结果:{results.map(item => <p key={item.id}>{item.name}</p>)}</div>
    </div>
  );
}
用法3:组件销毁时执行清理

场景:清除定时器、取消未完成的网络请求、关闭WebSocket连接。

核心:在useEffect的返回函数中写清理逻辑,组件销毁时自动执行。

jsx 复制代码
import { useEffect } from 'react';

function TimerComponent() {
  useEffect(() => {
    // 组件挂载时设置定时器
    const timer = setInterval(() => {
      console.log('定时器执行中...');
    }, 1000);

    // 组件销毁时清除定时器(避免内存泄漏)
    return () => clearInterval(timer);
  }, []);

  return <div>定时器组件</div>;
}
1.5 高频踩坑点(必记)
  1. ❌ 忘记写依赖数组:useEffect(() => {}) 会导致副作用函数「每次组件渲染都执行」,引发性能问题(如频繁请求接口)。
  2. ❌ 依赖数组遗漏变量:使用了组件内的变量,但没写进依赖数组,会导致副作用函数中拿到的是旧值。
  3. ❌ 清理函数不执行:如果副作用是"持续性操作"(定时器、请求),必须写清理函数,否则会导致内存泄漏。

✔️ 2. useContext:跨组件通信(解决多层嵌套传值痛点)

2.1 为什么需要useContext?

基础版我们学了「父传子」(通过props),但如果遇到「多层嵌套组件」(如:爷爷→爸爸→儿子→孙子),要把数据从爷爷传到孙子,就需要层层传递props,这种写法叫"props透传",代码冗余且难维护。
useContext的作用就是「实现跨组件通信」:数据只需要在顶层定义,任意深层组件都能直接获取,无需层层传递。

2.2 核心原理(3步曲,固定模板)
  1. 创建Context:用createContext创建一个"数据容器",用于存储要共享的数据。
  2. 提供数据:用Context.Provider包裹顶层组件,通过value属性提供要共享的数据。
  3. 消费数据:在任意深层组件中,用useContext获取共享的数据。
2.3 实战案例(用户信息全局共享)

场景:登录后,用户信息(用户名、头像)需要在导航栏、个人中心、订单页等多个组件中展示,用useContext实现全局共享。

jsx 复制代码
// 1. 创建Context(单独文件:src/contexts/UserContext.jsx)
import { createContext, useContext, useState } from 'react';

// 创建Context,默认值可选(一般传null)
const UserContext = createContext(null);

// 定义Provider组件,提供数据和方法
export function UserProvider({ children }) {
  // 要共享的全局数据(用户信息)
  const [userInfo, setUserInfo] = useState(null);

  // 共享的方法(登录、退出登录)
  const login = (data) => {
    setUserInfo(data); // 登录成功,更新用户信息
  };
  const logout = () => {
    setUserInfo(null); // 退出登录,清空用户信息
  };

  // 提供数据:所有子组件都能获取到value中的内容
  return (
    <UserContext.Provider value={{ userInfo, login, logout }}>
      {children} {/* 顶层组件的内容(如App组件) */}
    </UserContext.Provider>
  );
}

// 自定义Hook,简化使用(推荐写法,不用每次都写useContext)
export function useUser() {
  return useContext(UserContext);
}
jsx 复制代码
// 2. 顶层组件包裹(src/main.jsx)
import { createRoot } from 'react-dom/client';
import App from './App';
import { UserProvider } from './contexts/UserContext';

// 用UserProvider包裹App,使所有子组件都能访问用户信息
const root = createRoot(document.getElementById('root'));
root.render(
  <UserProvider>
    <App />
  </UserProvider>
);
jsx 复制代码
// 3. 任意组件使用共享数据(如导航栏组件)
import { useUser } from './contexts/UserContext';

function Navbar() {
  // 直接获取全局用户信息,无需props传递
  const { userInfo, logout } = useUser();

  return (
    <nav>
      <div>网站Logo</div>
      {userInfo ? (
        <div>
          <img src={userInfo.avatar} alt="用户头像" style={{ width: '40px' }} />
          <span>{userInfo.name}</span>
          <button onClick={logout}>退出登录</button>
        </div>
      ) : (
        <button>去登录</button>
      )}
    </nav>
  );
}
jsx 复制代码
// 4. 登录组件调用共享方法(如登录页)
import { useUser } from './contexts/UserContext';
import { useState } from 'react';

function Login() {
  const { login } = useUser();
  const [formData, setFormData] = useState({ username: '', password: '' });

  const handleSubmit = async (e) => {
    e.preventDefault();
    // 调用后端登录接口
    const res = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(formData)
    });
    const data = await res.json();
    // 登录成功,更新全局用户信息
    login(data.user);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={formData.username}
        onChange={(e) => setFormData({ ...formData, username: e.target.value })}
        placeholder="用户名"
      />
      <input
        type="password"
        value={formData.password}
        onChange={(e) => setFormData({ ...formData, password: e.target.value })}
        placeholder="密码"
      />
      <button type="submit">登录</button>
    </form>
  );
}
2.4 核心注意事项
  1. useContext适合共享「全局通用数据」(如用户信息、主题配置、权限状态),不适合频繁变化的局部数据。
  2. ✅ 不要过度使用:如果只是父子组件传值,优先用props,useContext用于解决"多层透传"问题。
  3. ✅ 性能优化:如果Context中的数据频繁变化,会导致所有使用该Context的组件重新渲染,后续可结合useMemo优化(下文会讲)。

✔️ 3. useRef:DOM操作与数据持久化

3.1 核心作用(2个高频场景)

useRef有两个核心用途,都是企业开发中的刚需:

  1. 直接操作DOM元素(如获取输入框焦点、获取DOM尺寸);
  2. 持久化存储数据(组件重新渲染时,数据不会丢失,且修改不会触发重新渲染)。
3.2 用法1:操作DOM元素(最常用)

场景:登录页点击"登录"后,若用户名未输入,自动聚焦到用户名输入框;获取轮播图DOM元素实现滚动。

jsx 复制代码
import { useRef } from 'react';

function LoginForm() {
  // 1. 创建ref对象,绑定到DOM元素
  const usernameRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    const username = usernameRef.current.value; // 2. 通过ref获取DOM值
    if (!username) {
      // 3. 操作DOM:自动聚焦到输入框
      usernameRef.current.focus();
      alert('请输入用户名');
      return;
    }
    // 后续登录逻辑...
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* 绑定ref:ref={usernameRef} */}
      <input
        type="text"
        ref={usernameRef}
        placeholder="请输入用户名"
      />
      <input
        type="password"
        placeholder="请输入密码"
      />
      <button type="submit">登录</button>
    </form>
  );
}
3.3 用法2:持久化存储数据

场景:存储定时器ID、记录组件渲染次数、存储上一次的状态值。

核心优势:修改ref中的数据(ref.current = 新值)不会触发组件重新渲染,适合存储"不影响UI的数据"。

jsx 复制代码
import { useRef, useState, useEffect } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  // 持久化存储渲染次数(修改不会触发渲染)
  const renderCountRef = useRef(0);

  // 组件每次渲染,渲染次数+1
  useEffect(() => {
    renderCountRef.current += 1;
    console.log('组件渲染次数:', renderCountRef.current);
  });

  return (
    <div>
      <p>当前计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <p>组件已渲染:{renderCountRef.current}次</p>
    </div>
  );
}

✔️ 4. useMemo & useCallback:性能优化Hook(避免不必要的重渲染)

4.1 为什么需要性能优化?

React中,当父组件的状态变化时,会重新渲染自身以及所有子组件。如果子组件比较复杂(如长列表、复杂表单),频繁的重渲染会导致页面卡顿。
useMemouseCallback的核心作用是「缓存数据/函数」,避免不必要的重渲染,提升应用性能。

4.2 useMemo:缓存计算结果

场景:复杂的计算逻辑(如长列表过滤、数据排序),避免每次渲染都重复计算。

jsx 复制代码
import { useState, useMemo } from 'react';

function FilterList() {
  const [list, setList] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9]);
  const [keyword, setKeyword] = useState('');

  // 复杂计算:过滤列表(模拟大量数据计算)
  // useMemo缓存计算结果,只有keyword变化时才重新计算
  const filteredList = useMemo(() => {
    console.log('执行过滤计算...');
    return list.filter(item => item.toString().includes(keyword));
  }, [list, keyword]); // 依赖数组:只有list或keyword变化时,重新计算

  return (
    <div>
      <input
        type="text"
        value={keyword}
        onChange={(e) => setKeyword(e.target.value)}
        placeholder="输入数字过滤"
      />
      <ul>
        {filteredList.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}
  • 没有useMemo:每次组件渲染(如输入框输入)都会执行过滤计算,即使list和keyword都没变化;
  • useMemo:只有依赖数组中的listkeyword变化时,才重新执行过滤计算,减少性能损耗。
4.3 useCallback:缓存函数

场景:向子组件传递函数时,避免每次渲染都创建新的函数实例,导致子组件不必要的重渲染。

jsx 复制代码
import { useState, useCallback, memo } from 'react';

// 子组件:用memo包裹,避免不必要的重渲染
const Child = memo(({ onBtnClick }) => {
  console.log('子组件渲染了');
  return <button onClick={onBtnClick}>子组件按钮</button>;
});

function Parent() {
  const [count, setCount] = useState(0);

  // useCallback缓存函数,只有count变化时才创建新函数
  const handleBtnClick = useCallback(() => {
    console.log('点击了子组件按钮,count:', count);
  }, [count]); // 依赖count:只有count变化时,才重新创建函数

  return (
    <div>
      <p>父组件计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>父组件+1</button>
      {/* 传递缓存的函数给子组件 */}
      <Child onBtnClick={handleBtnClick} />
    </div>
  );
}
  • 没有useCallback:每次父组件渲染(点击+1)都会创建新的handleBtnClick函数,导致子组件重新渲染;
  • useCallback:只有依赖数组中的count变化时,才创建新函数,子组件不会因为父组件无关状态变化而重渲染。
4.4 核心注意事项
  1. ❌ 不要过度优化:简单的计算或函数不需要用useMemo/useCallback,缓存本身也有性能开销,只在"确实出现性能问题"或"子组件复杂"时使用;
  2. memo配合使用:useCallback通常需要和memo(包裹子组件)配合使用,否则无法达到优化效果;
  3. ✅ 依赖数组正确:useMemo/useCallback的依赖数组要包含所有用到的变量,否则会拿到旧值。

三、React Router 6 进阶:实现多页面应用与权限控制(⭐⭐⭐⭐⭐ 必学)

基础版我们没涉及路由,而企业开发的React应用都是「单页应用(SPA)」,需要通过路由实现"页面跳转"功能。React Router 6是2026年的主流版本,进阶部分我们需要掌握「路由懒加载、嵌套路由、路由守卫、路由传参」等企业级用法。

✔️ 1. 先回顾:React Router 6 基础配置(衔接基础)

首先确保已安装依赖:

bash 复制代码
npm install react-router-dom@6

基础路由配置(src/App.jsx):

jsx 复制代码
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import Home from './views/Home';
import Login from './views/Login';
import About from './views/About';

function App() {
  return (
    <Router>
      {/* 导航栏:页面跳转链接 */}
      <nav>
        <Link to="/">首页</Link>
        <Link to="/login">登录</Link>
        <Link to="/about">关于我们</Link>
      </nav>

      {/* 路由匹配:路径对应组件 */}
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route path="/about" element={<About />} />
        {/* 404页面:匹配所有未定义的路径 */}
        <Route path="*" element={<div>404 页面未找到</div>} />
      </Routes>
    </Router>
  );
}

export default App;

✔️ 2. 进阶核心1:路由懒加载(性能优化TOP1,企业必做)

2.1 核心问题

基础配置中,所有页面组件(Home、Login、About)会在应用初始化时一次性加载,导致首屏加载时间长、性能差。尤其是中大型项目,页面越多,首屏加载越慢。

2.2 路由懒加载原理

通过React.lazySuspense配合,实现「页面组件按需加载」:只有当用户访问某个路由时,才加载对应的组件资源,减少首屏加载体积,提升加载速度。

2.3 企业级实现代码(复制即用)
jsx 复制代码
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import { lazy, Suspense } from 'react';

// 路由懒加载:访问时才加载组件(替代直接import)
const Home = lazy(() => import('./views/Home'));
const Login = lazy(() => import('./views/Login'));
const About = lazy(() => import('./views/About'));

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/login">登录</Link>
        <Link to="/about">关于我们</Link>
      </nav>

      {/* Suspense:加载组件时显示占位内容(避免页面空白) */}
      <Suspense fallback={<div>加载中...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/about" element={<About />} />
          <Route path="*" element={<div>404 页面未找到</div>} />
        </Routes>
      </Suspense>
    </Router>
  );
}
  • lazy(() => import('组件路径')):定义懒加载组件;
  • Suspense:包裹所有懒加载路由,fallback属性指定加载时的占位内容(如"加载中..."、加载动画);
  • 效果:首屏只加载必要的导航栏和Suspense内容,访问对应路由时才加载该页面组件,首屏加载速度提升80%。

✔️ 3. 进阶核心2:嵌套路由(页面布局复用,企业规范)

3.1 应用场景

企业开发中,很多页面会共享相同的布局(如后台管理系统的"侧边栏+顶部导航"布局),嵌套路由可以实现「布局复用」,避免重复写布局代码。

3.2 核心概念
  • 父路由:包含公共布局的路由(如/dashboard);
  • 子路由:嵌套在父路由中的路由(如/dashboard/user/dashboard/goods);
  • Outlet:父路由组件中,用于渲染子路由内容的"占位符"。
3.3 企业级实现代码(后台管理系统案例)
jsx 复制代码
import { BrowserRouter as Router, Routes, Route, Link, Outlet } from 'react-router-dom';
import { lazy, Suspense } from 'react';

// 懒加载组件
const Dashboard = lazy(() => import('./views/Dashboard'));
const UserList = lazy(() => import('./views/UserList'));
const GoodsList = lazy(() => import('./views/GoodsList'));
const Login = lazy(() => import('./views/Login'));

// 父路由组件:包含公共布局(侧边栏+顶部导航)
function DashboardLayout() {
  return (
    <div style={{ display: 'flex' }}>
      {/* 侧边栏(公共部分) */}
      <aside style={{ width: '200px', borderRight: '1px solid #ccc', padding: '20px' }}>
        <Link to="/dashboard/user" style={{ display: 'block', margin: '10px 0' }}>用户管理</Link>
        <Link to="/dashboard/goods" style={{ display: 'block', margin: '10px 0' }}>商品管理</Link>
      </aside>
      {/* 主内容区:子路由内容会渲染在这里 */}
      <main style={{ flex: 1, padding: '20px' }}>
        <Outlet /> {/* 子路由占位符 */}
      </main>
    </div>
  );
}

function App() {
  return (
    <Router>
      <Suspense fallback={<div>加载中...</div>}>
        <Routes>
          {/* 登录页(无布局) */}
          <Route path="/login" element={<Login />} />
          {/* 嵌套路由:父路由渲染布局,子路由渲染具体内容 */}
          <Route path="/dashboard" element={<DashboardLayout />}>
            <Route index element={<div>请选择管理模块</div>} /> {/* 默认子路由(访问/dashboard时显示) */}
            <Route path="user" element={<UserList />} /> {/* /dashboard/user */}
            <Route path="goods" element={<GoodsList />} /> {/* /dashboard/goods */}
          </Route>
          <Route path="*" element={<div>404 页面未找到</div>} />
        </Routes>
      </Suspense>
    </Router>
  );
}
  • 效果:访问/dashboard/user/dashboard/goods时,都会显示"侧边栏+顶部导航"布局,只有主内容区显示不同的子组件,实现布局复用;
  • 优势:修改布局时,只需修改父路由组件,所有子路由页面都会同步更新,维护成本极低。

✔️ 4. 进阶核心3:路由守卫(权限控制,企业刚需)

4.1 应用场景

企业应用中,有些页面需要「登录后才能访问」(如个人中心、后台管理页),未登录用户访问时,需要自动跳转到登录页。路由守卫就是实现这一功能的核心方案。

4.2 实现原理

通过「自定义鉴权组件」包裹需要权限的路由,在组件中判断用户是否登录(通过useUser获取全局用户信息),未登录则重定向到登录页,已登录则显示页面内容。

4.3 企业级实现代码(结合useContext)
jsx 复制代码
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { lazy, Suspense } from 'react';
import { useUser } from './contexts/UserContext';

// 懒加载组件
const Home = lazy(() => import('./views/Home'));
const Login = lazy(() => import('./views/Login'));
const Dashboard = lazy(() => import('./views/Dashboard'));
const UserList = lazy(() => import('./views/UserList'));

// 1. 自定义鉴权组件(路由守卫核心)
function ProtectedRoute({ children }) {
  const { userInfo } = useUser(); // 获取全局用户信息
  if (!userInfo) {
    // 未登录:重定向到登录页,记录当前路径(登录后可跳回)
    return <Navigate to="/login" replace state={{ from: window.location.pathname }} />;
  }
  // 已登录:显示页面内容
  return children;
}

function App() {
  return (
    <Router>
      <Suspense fallback={<div>加载中...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
          {/* 需要权限的路由:用ProtectedRoute包裹 */}
          <Route 
            path="/dashboard" 
            element={
              <ProtectedRoute>
                <Dashboard />
              </ProtectedRoute>
            } 
          >
            <Route index element={<div>请选择管理模块</div>} />
            <Route path="user" element={<UserList />} />
          </Route>
          <Route path="*" element={<div>404 页面未找到</div>} />
        </Routes>
      </Suspense>
    </Router>
  );
}
jsx 复制代码
// 登录组件优化:登录成功后跳回之前访问的页面
import { useUser } from './contexts/UserContext';
import { useState, useLocation, Navigate } from 'react-router-dom';

function Login() {
  const { login, userInfo } = useUser();
  const [formData, setFormData] = useState({ username: '', password: '' });
  const location = useLocation(); // 获取当前路由信息
  // 从state中获取之前访问的路径(未登录时被拦截的路径)
  const fromPath = location.state?.from || '/';

  // 已登录:直接跳回之前的页面
  if (userInfo) {
    return <Navigate to={fromPath} replace />;
  }

  const handleSubmit = async (e) => {
    e.preventDefault();
    // 调用后端登录接口
    const res = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(formData)
    });
    const data = await res.json();
    login(data.user); // 更新全局用户信息
    // 登录成功,跳回之前访问的页面
    <Navigate to={fromPath} replace />;
  };

  // 表单内容(和之前一致)
  return <form onSubmit={handleSubmit}>...</form>;
}
  • 核心逻辑:未登录用户访问/dashboard时,被ProtectedRoute拦截,重定向到登录页,并记录当前路径;登录成功后,自动跳回之前想访问的页面,用户体验极佳;
  • 拓展:可以基于此实现「角色权限控制」(如管理员才能访问/dashboard/admin,普通用户无权限),只需在ProtectedRoute中增加角色判断逻辑。

✔️ 5. 进阶核心4:路由传参(页面间数据传递)

5.1 2种核心传参方式(覆盖所有场景)

开发中经常需要"页面跳转时传递数据"(如点击商品列表跳转到详情页,传递商品ID),React Router 6提供2种主流传参方式:

方式1:search参数(地址栏可见,推荐)
  • 特点:参数拼接在URL后面(如/detail?id=1&name=React),刷新页面参数不丢失;
  • 适用场景:传递简单、非敏感数据(如ID、页码、搜索关键词)。
jsx 复制代码
// 列表页:跳转并传参
import { Link } from 'react-router-dom';

function GoodsList() {
  const goods = [{ id: 1, name: 'React实战教程' }, { id: 2, name: 'Vue3基础教程' }];

  return (
    <ul>
      {goods.map(item => (
        // 传参:通过search拼接参数
        <li key={item.id}>
          <Link to={`/detail?id=${item.id}&name=${item.name}`}>{item.name}</Link>
        </li>
      ))}
    </ul>
  );
}

// 详情页:接收参数
import { useSearchParams } from 'react-router-dom';

function GoodsDetail() {
  // useSearchParams:获取search参数(类似URLSearchParams)
  const [searchParams] = useSearchParams();
  const id = searchParams.get('id'); // 获取id参数
  const name = searchParams.get('name'); // 获取name参数

  return (
    <div>
      <h3>商品详情</h3>
      <p>商品ID:{id}</p>
      <p>商品名称:{name}</p>
    </div>
  );
}
方式2:state参数(地址栏不可见)
  • 特点:参数存储在路由状态中,不显示在URL上,刷新页面参数不丢失;
  • 适用场景:传递敏感数据(如用户信息)或复杂对象。
jsx 复制代码
// 列表页:跳转并传参
import { Link } from 'react-router-dom';

function GoodsList() {
  const goods = [{ id: 1, name: 'React实战教程', price: 99 }];

  return (
    <ul>
      {goods.map(item => (
        <li key={item.id}>
          {/* 传参:通过state传递复杂数据 */}
          <Link to="/detail" state={{ goodsInfo: item }}>
            {item.name}
          </Link>
        </li>
      ))}
    </ul>
  );
}

// 详情页:接收参数
import { useLocation } from 'react-router-dom';

function GoodsDetail() {
  const location = useLocation();
  const { goodsInfo } = location.state || {}; // 获取state参数

  return (
    <div>
      <h3>商品详情</h3>
      <p>商品ID:{goodsInfo?.id}</p>
      <p>商品名称:{goodsInfo?.name}</p>
      <p>商品价格:{goodsInfo?.price}</p>
    </div>
  );
}

四、网络请求进阶:Axios 企业级封装(前后端交互核心)

基础版我们没涉及网络请求,而企业开发的核心是"前端对接后端接口"。Axios是React项目中100%在用的网络请求库,进阶阶段我们需要掌握「企业级Axios封装」,实现请求统一配置、拦截器、错误处理等核心功能。

✔️ 1. 为什么需要封装Axios?

直接使用Axios发起请求,会存在这些问题:

  1. 重复代码:每个请求都要写baseURLheaders,冗余且难维护;
  2. 错误处理:每个请求都要写try/catch,代码繁琐;
  3. 权限统一:所有请求都要添加Token(用户登录凭证),分散处理易遗漏;
  4. 响应处理:后端返回数据格式统一(如{ code: 200, msg: '', data: {} }),需要统一解析;
  5. 异常处理:Token过期、网络错误、接口报错等,需要统一提示用户。

企业级封装就是为了解决这些问题,实现「一次封装,全项目复用」,提升开发效率和代码可维护性。

✔️ 2. 步骤1:安装Axios

bash 复制代码
npm install axios

✔️ 3. 步骤2:企业级Axios封装(核心代码,复制即用)

src/utils/request.js中创建封装文件:

jsx 复制代码
// src/utils/request.js
import axios from 'axios';
import { ElMessage } from 'element-plus'; // 可选:UI组件库的提示组件(需安装element-plus)
import { useUser } from '../contexts/UserContext';

// 1. 创建Axios实例,配置基础参数
const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL, // 接口根路径(从环境变量获取,推荐)
  timeout: 10000, // 请求超时时间(10秒)
  headers: {
    'Content-Type': 'application/json;charset=utf-8' // 默认请求头
  }
});

// 2. 请求拦截器:请求发送前执行(统一添加Token、处理参数)
request.interceptors.request.use(
  (config) => {
    // 获取全局用户信息(Token)
    const { userInfo } = useUser();
    // 统一添加Token(后端鉴权用)
    if (userInfo?.token) {
      config.headers.Authorization = `Bearer ${userInfo.token}`;
    }
    return config;
  },
  (error) => {
    // 请求发送失败(如网络错误)
    ElMessage.error('请求发送失败,请稍后重试');
    return Promise.reject(error);
  }
);

// 3. 响应拦截器:接口返回后执行(统一解析数据、处理错误)
request.interceptors.response.use(
  (response) => {
    // 后端约定:code=200表示请求成功,其他为失败
    const res = response.data;
    if (res.code !== 200) {
      // 接口报错:统一提示错误信息
      ElMessage.error(res.msg || '请求失败');
      return Promise.reject(res); // 抛出错误,让组件可以捕获处理
    }
    // 请求成功:只返回data部分(简化组件中获取数据的逻辑)
    return res.data;
  },
  (error) => {
    // 统一处理网络错误、Token过期等异常
    let errorMsg = '网络异常,请稍后重试';
    // Token过期(后端返回401状态码)
    if (error.response?.status === 401) {
      errorMsg = '登录已过期,请重新登录';
      const { logout } = useUser();
      logout(); // 清空用户信息
      // 跳转到登录页
      window.location.href = '/login';
    }
    ElMessage.error(errorMsg);
    return Promise.reject(error);
  }
);

// 4. 封装常用请求方法(get/post/put/delete),简化组件调用
export const api = {
  // get请求:参数通过params传递
  get(url, params) {
    return request({ method: 'GET', url, params });
  },
  // post请求:参数通过data传递(JSON格式)
  post(url, data) {
    return request({ method: 'POST', url, data });
  },
  // put请求:更新数据
  put(url, data) {
    return request({ method: 'PUT', url, data });
  },
  // delete请求:删除数据
  delete(url, params) {
    return request({ method: 'DELETE', url, params });
  }
};

export default request;

✔️ 4. 步骤3:创建环境变量文件(区分开发/生产环境)

在项目根目录创建.env.development(开发环境)和.env.production(生产环境)文件,用于配置不同环境的接口根路径:

env 复制代码
# .env.development(开发环境)
VITE_API_BASE_URL = 'http://localhost:3000/api' # 本地后端接口地址
env 复制代码
# .env.production(生产环境)
VITE_API_BASE_URL = 'https://api.your-project.com/api' # 线上后端接口地址
  • 优势:打包时自动切换环境,无需手动修改代码,避免线上环境调用本地接口的错误。

✔️ 5. 步骤4:组件中使用封装后的Axios(极简)

在组件中直接导入封装好的api对象,调用对应方法即可,无需重复配置:

jsx 复制代码
import { useState, useEffect } from 'react';
import { api } from '../utils/request';

function GoodsList() {
  const [goods, setGoods] = useState([]);

  // 页面加载后请求商品列表
  useEffect(() => {
    const fetchGoods = async () => {
      try {
        // 调用get请求,参数通过params传递
        const data = await api.get('/goods/list', { page: 1, size: 10 });
        setGoods(data);
      } catch (error) {
        console.log('请求商品列表失败:', error);
        // 错误已在拦截器中统一提示,组件可根据需要额外处理
      }
    };
    fetchGoods();
  }, []);

  // 新增商品(post请求)
  const addGoods = async (newGoods) => {
    try {
      await api.post('/goods/add', newGoods);
      ElMessage.success('新增商品成功');
      // 重新请求列表,更新数据
      fetchGoods();
    } catch (error) {
      console.log('新增商品失败:', error);
    }
  };

  return (
    <div>
      <h3>商品列表</h3>
      <ul>
        {goods.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <button onClick={() => addGoods({ name: '新商品', price: 199 })}>新增商品</button>
    </div>
  );
}
  • 优势:代码极简,无需写try/catch(拦截器已统一处理错误),无需重复配置baseURLheaders,开发效率提升80%。

五、状态管理进阶:Zustand(2026主流,替代Redux)

基础版我们用useState管理组件内数据,用useContext管理简单全局数据。但复杂项目中(如电商购物车、多页面共享数据),useContext会存在性能问题,此时需要专业的状态管理库。

2026年,Zustand已取代Redux成为React生态的主流状态管理方案,原因是:语法简洁、无冗余代码、性能优异、学习成本低。

✔️ 1. 为什么选择Zustand?

对比Redux和useContext,Zustand的核心优势:

  1. 语法简单:无需写Providerreduceraction等冗余代码,几行代码就能实现全局状态管理;
  2. 性能优异:自动优化重渲染,只有使用到状态的组件才会重新渲染;
  3. 支持中间件:可轻松集成持久化、日志等功能;
  4. 学习成本低:基于Hook,零基础容易上手。

✔️ 2. 步骤1:安装Zustand

bash 复制代码
npm install zustand

✔️ 3. 步骤2:创建全局状态(购物车案例,企业级)

src/stores/cartStore.js中创建状态管理文件:

jsx 复制代码
// src/stores/cartStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware'; // 持久化中间件(可选,用于刷新页面数据不丢失)

// 创建购物车状态管理
const useCartStore = create(
  // persist:持久化存储(默认存储到localStorage),刷新页面购物车数据不丢失
  persist(
    (set, get) => ({
      // 1. 全局状态:购物车列表
      cartList: [],

      // 2. 状态操作方法:添加商品到购物车
      addToCart: (goods) => {
        const currentCart = get().cartList;
        // 判断商品是否已在购物车中
        const isExist = currentCart.find(item => item.id === goods.id);
        if (isExist) {
          // 已存在:数量+1
          set({
            cartList: currentCart.map(item => 
              item.id === goods.id ? { ...item, count: item.count + 1 } : item
            )
          });
        } else {
          // 不存在:添加新商品,数量设为1
          set({
            cartList: [...currentCart, { ...goods, count: 1 }]
          });
        }
      },

      // 3. 状态操作方法:从购物车删除商品
      removeFromCart: (goodsId) => {
        const currentCart = get().cartList;
        set({
          cartList: currentCart.filter(item => item.id !== goodsId)
        });
      },

      // 4. 状态操作方法:清空购物车
      clearCart: () => {
        set({ cartList: [] });
      },

      // 5. 计算属性:购物车商品总数
      getCartTotalCount: () => {
        return get().cartList.reduce((total, item) => total + item.count, 0);
      },

      // 6. 计算属性:购物车商品总价
      getCartTotalPrice: () => {
        return get().cartList.reduce((total, item) => total + item.price * item.count, 0);
      }
    }),
    {
      name: 'cart-storage', // 持久化存储的key(localStorage中的键名)
    }
  )
);

export default useCartStore;

✔️ 4. 步骤3:组件中使用全局状态(任意组件均可访问)

场景1:商品列表组件(添加商品到购物车)
jsx 复制代码
import { useState, useEffect } from 'react';
import { api } from '../utils/request';
import useCartStore from '../stores/cartStore';

function GoodsList() {
  const [goods, setGoods] = useState([]);
  // 从全局状态中获取addToCart方法
  const addToCart = useCartStore(state => state.addToCart);

  // 请求商品列表
  useEffect(() => {
    const fetchGoods = async () => {
      const data = await api.get('/goods/list');
      setGoods(data);
    };
    fetchGoods();
  }, []);

  return (
    <div>
      <h3>商品列表</h3>
      <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap' }}>
        {goods.map(item => (
          <div key={item.id} style={{ border: '1px solid #ccc', padding: '10px' }}>
            <p>商品名称:{item.name}</p>
            <p>商品价格:¥{item.price}</p>
            <button onClick={() => addToCart(item)}>加入购物车</button>
          </div>
        ))}
      </div>
    </div>
  );
}
场景2:购物车组件(展示购物车数据、删除商品)
jsx 复制代码
import useCartStore from '../stores/cartStore';

function Cart() {
  // 从全局状态中获取购物车数据和操作方法
  const cartList = useCartStore(state => state.cartList);
  const removeFromCart = useCartStore(state => state.removeFromCart);
  const clearCart = useCartStore(state => state.clearCart);
  const totalCount = useCartStore(state => state.getCartTotalCount());
  const totalPrice = useCartStore(state => state.getCartTotalPrice());

  return (
    <div>
      <h3>购物车</h3>
      {cartList.length === 0 ? (
        <p>购物车为空,请添加商品</p>
      ) : (
        <>
          <ul>
            {cartList.map(item => (
              <li key={item.id} style={{ margin: '10px 0' }}>
                <span>{item.name} - ¥{item.price} x {item.count}</span>
                <button onClick={() => removeFromCart(item.id)} style={{ marginLeft: '10px' }}>删除</button>
              </li>
            ))}
          </ul>
          <div>
            <p>商品总数:{totalCount}</p>
            <p>商品总价:¥{totalPrice}</p>
            <button onClick={clearCart}>清空购物车</button>
            <button style={{ marginLeft: '10px' }}>去结算</button>
          </div>
        </>
      )}
    </div>
  );
}
场景3:导航栏组件(展示购物车商品数量)
jsx 复制代码
import { Link } from 'react-router-dom';
import useCartStore from '../stores/cartStore';

function Navbar() {
  // 从全局状态中获取购物车商品总数
  const totalCount = useCartStore(state => state.getCartTotalCount());

  return (
    <nav style={{ display: 'flex', gap: '20px', padding: '20px', borderBottom: '1px solid #ccc' }}>
      <Link to="/">首页</Link>
      <Link to="/goods">商品列表</Link>
      <Link to="/cart">
        购物车
        {/* 购物车数量徽章 */}
        <span style={{ 
          background: 'red', color: 'white', borderRadius: '50%', 
          width: '20px', height: '20px', display: 'inline-flex', 
          alignItems: 'center', justifyContent: 'center', fontSize: '12px' 
        }}>
          {totalCount}
        </span>
      </Link>
    </nav>
  );
}
  • 核心优势:任意组件都能直接获取/修改全局购物车数据,无需通过props传递;刷新页面后,购物车数据不会丢失(因为使用了persist持久化中间件);只有使用到状态的组件才会重新渲染,性能优异。

六、React 18 并发特性:提升用户体验(2026最新)

React 18 引入了「并发渲染(Concurrent Rendering)」特性,通过优化渲染优先级,让应用在处理大量数据或复杂计算时,依然保持流畅的用户交互。这是2026年企业级React应用的重要优化方向,零基础只需掌握核心用法即可。

✔️ 1. 核心问题:同步渲染的痛点

在React 18之前,渲染是「同步且不可中断」的。如果组件树庞大或有复杂计算,会阻塞主线程,导致用户交互(如点击、输入)卡顿。例如:在搜索框输入时,后台正在处理大量数据,输入内容会延迟显示,用户体验差。

✔️ 2. 核心解决方案:startTransition(标记非紧急更新)

startTransition 是React 18提供的核心API,用于标记「非紧急更新」,让React优先处理用户交互等紧急任务,延迟处理非紧急更新,避免界面卡顿。

2.1 实战案例:搜索框优化
jsx 复制代码
import { useState, startTransition } from 'react';
import { api } from '../utils/request';

function SearchBox() {
  const [inputValue, setInputValue] = useState('');
  const [searchResults, setSearchResults] = useState([]);

  const handleInputChange = (e) => {
    const value = e.target.value;
    // 1. 紧急更新:更新输入框值(优先处理,确保用户输入即时反馈)
    setInputValue(value);

    // 2. 非紧急更新:搜索数据(标记为过渡更新,延迟处理)
    startTransition(async () => {
      // 模拟复杂搜索或大量数据处理
      const data = await api.get('/goods/search', { keyword: value });
      setSearchResults(data);
    });
  };

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={handleInputChange}
        placeholder="输入关键词搜索商品..."
      />
      <div style={{ marginTop: '10px' }}>
        {searchResults.map(item => (
          <p key={item.id}>{item.name}</p>
        ))}
      </div>
    </div>
 
相关推荐
IT=>小脑虎1 天前
2026版 React 零基础小白入门知识点【基础完整版】
前端·react.js·前端框架
FinClip1 天前
微信AI小程序“亿元计划”来了!你的APP如何一键接入,抢先变现?
前端·微信小程序·app
西西学代码1 天前
Flutter---框架
前端·flutter
XiaoYu20021 天前
第9章 Three.js载入模型GLTF
前端·javascript·three.js
.又是新的一天.1 天前
【前端Web开发HTML5+CSS3+移动web视频教程】01 html- 标签之文字排版、图片、链接、音视频
前端·css3·html5
神奇的程序员1 天前
开发了一个nginx日志分析面板
前端
阿拉丁的梦1 天前
【C4D实用脚本】清除废点及删除了面的选择tag和材质tag及材质--AI编程
服务器·前端·材质
傅里叶1 天前
Flutter移动端获取相机内参
前端·flutter
哒哒哒5285201 天前
React useMemo 大白话用法文档(含注意项)
前端