React实战指南

React 实战指南

React 是前端两大框架之一,也是 CSDN 前端板块流量最大的话题。本文从核心思想讲起,覆盖 JSX、组件、props/state、Hooks(useState/useEffect/useMemo/useCallback)、组件通信、React Router、状态管理、性能优化、与 Vue 对比,全程带可运行代码。


一、React 核心思想

1.1 声明式 UI

命令式(传统 DOM 操作):

js 复制代码
// 手动操作 DOM:增删改都要自己写
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
    document.getElementById('count').textContent = '1';
});

声明式(React):

jsx 复制代码
// 只描述"界面应该长什么样",React 负责更新
const [count, setCount] = useState(0);
<button onClick={() => setCount(count + 1)}>{count}</button>

核心 :数据驱动视图------数据变了,React 自动重新渲染。

1.2 组件化

页面拆成独立组件,每个组件管自己的 UI + 逻辑:

复制代码
App
├── Header
├── ProductList
│    └── ProductCard × N
└── Footer

1.3 单向数据流

数据从父组件流向子组件(props),子组件通过事件回调通知父组件------方向明确,好排查。


二、JSX 与渲染

2.1 JSX 是什么

JSX = JavaScript + XML,在 JS 里写类似 HTML 的语法。不是模板字符串,会被编译成 React.createElement 调用。

jsx 复制代码
// JSX
const element = <h1>你好,{name}</h1>;

// 编译后等价于
const element = React.createElement('h1', null, '你好,', name);

2.2 JSX 语法要点

jsx 复制代码
// 表达式用 {}(不是字符串模板)
const age = 25;
<p>年龄:{age}</p>
<p>{age > 18 ? '成年' : '未成年'}</p>

// 条件渲染
{isLogin && <span>欢迎回来</span>}
{isLogin ? <LogoutButton /> : <LoginButton />}

// 列表渲染(必须加 key)
<ul>
    {products.map(p => <li key={p.id}>{p.name}</li>)}
</ul>

// 内联样式(驼峰命名,值是对象)
<div style={{ color: 'red', fontSize: '16px' }}>文字</div>

// class 用 className(class 是保留字)
<div className="card">卡片</div>

2.3 事件处理

jsx 复制代码
function Button() {
    const handleClick = (e) => {
        e.preventDefault();          // 阻止默认行为
        console.log('点击了', e.target);
    };
    return <button onClick={handleClick}>点击</button>;
}

三、组件与 props

3.1 函数组件(现代标准)

jsx 复制代码
// 函数组件:接收 props,返回 JSX
function Greeting({ name, age = 18 }) {    // 解构 + 默认值
    return <h1>你好,{name}({age}岁)</h1>;
}

// 使用
<Greeting name="张三" age={25} />

3.2 props 不可变

props 是只读的------组件不能改自己的 props。要变的数据用 state。

3.3 children 插槽

jsx 复制代码
function Card({ title, children }) {
    return (
        <div className="card">
            <h3>{title}</h3>
            <div>{children}</div>   {/* 子元素 */}
        </div>
    );
}

<Card title="订单信息">
    <p>订单号:12345</p>   {/* 作为 children 传入 */}
</Card>

四、State:组件状态

4.1 useState 基础

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

function Counter() {
    const [count, setCount] = useState(0);   // 初始值 0

    return (
        <div>
            <p>当前:{count}</p>
            <button onClick={() => setCount(count + 1)}>+1</button>
        </div>
    );
}

useState 规则(必考):

  • 返回 [当前值, 更新函数];
  • 更新函数触发重新渲染;
  • 值变了才重渲染(Object.is 比较)。

4.2 setState 的异步性

jsx 复制代码
const [count, setCount] = useState(0);

// 错误:连续 set 只生效一次(闭包里的 count 是旧值)
setCount(count + 1);
setCount(count + 1);   // 结果还是 1

// 正确:用函数式更新
setCount(prev => prev + 1);
setCount(prev => prev + 1);   // 结果 2

原因:React 批处理更新,函数式更新拿到的是最新值。

4.3 对象/数组 state 更新(不可变更新)

jsx 复制代码
const [user, setUser] = useState({ name: '张三', age: 25 });
const [list, setList] = useState([1, 2, 3]);

// 错误:直接改对象(不触发渲染)
// user.age = 26;

// 正确:创建新对象
setUser({ ...user, age: 26 });

// 数组增删改
setList([...list, 4]);                    // 追加
setList(list.filter(x => x !== 2));       // 删除
setList(list.map(x => x * 2));            // 变换

原则:state 不可变更新------永远创建新值,不修改旧值。


五、Hooks 详解

5.1 useEffect:副作用(请求、订阅、DOM 操作)

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

function UserList() {
    const [users, setUsers] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        // 组件挂载后执行(模拟 componentDidMount)
        fetch('/api/users')
            .then(res => res.json())
            .then(data => {
                setUsers(data);
                setLoading(false);
            })
            .catch(err => console.error(err));

        // 返回清理函数(组件卸载时执行)
        // return () => { ... };
    }, []);   // 依赖数组:空 = 只执行一次

    if (loading) return <p>加载中...</p>;
    return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

useEffect 依赖数组(重点):

写法 执行时机
无依赖数组 每次渲染都执行
[] 挂载时执行一次
[a, b] a 或 b 变化时执行

无限循环坑:

jsx 复制代码
// 错误:setState 触发渲染 → 渲染触发 effect → 无限循环
useEffect(() => {
    setCount(count + 1);   // count 变化 → effect 再执行 → 再 set...
}, [count]);

5.2 useRef:引用(不触发渲染)

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

function InputFocus() {
    const inputRef = useRef(null);

    const focusInput = () => {
        inputRef.current.focus();    // 直接操作 DOM
    };

    return (
        <>
            <input ref={inputRef} />
            <button onClick={focusInput}>聚焦</button>
        </>
    );
}

用途:

  • 操作 DOM(focus、滚动);
  • 存可变值(定时器 ID)------改 ref 不触发渲染;
  • 保存上一次的值。

5.3 useMemo:缓存计算结果

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

// 计算量大时才用(普通计算用不上)
const total = useMemo(() => {
    return orders.reduce((s, o) => s + o.amount, 0);
}, [orders]);   // orders 变才重算

5.4 useCallback:缓存函数

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

// 传给子组件的函数要缓存(防止子组件每次重渲染)
const handleDelete = useCallback((id) => {
    setList(list => list.filter(x => x.id !== id));
}, []);   // 依赖不变,函数引用不变

useMemo/useCallback 什么时候用 :子组件用 React.memo 时,父组件的函数不缓存会让 memo 失效。普通组件别滥用(有开销)。

5.5 自定义 Hook

jsx 复制代码
// useWindowSize:监听窗口大小
import { useState, useEffect } from 'react';

function useWindowSize() {
    const [size, setSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight
    });

    useEffect(() => {
        const handleResize = () =>
            setSize({ width: window.innerWidth, height: window.innerHeight });
        window.addEventListener('resize', handleResize);
        return () => window.removeEventListener('resize', handleResize);
    }, []);

    return size;
}

// 组件里使用
const { width, height } = useWindowSize();

命名规范 :以 use 开头;内部可以调其他 Hooks;不同组件共享同一 Hook 时状态独立。


六、组件通信

6.1 父子:props + 回调

jsx 复制代码
// 父组件
function Parent() {
    const [count, setCount] = useState(0);

    return (
        <Child
            count={count}
            onIncrement={() => setCount(count + 1)}   // 传回调
        />
    );
}

// 子组件
function Child({ count, onIncrement }) {
    return (
        <button onClick={onIncrement}>当前:{count}</button>
    );
}

6.2 跨层级:Context

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

// 1. 创建 Context
const ThemeContext = createContext('light');

// 2. 提供者(顶层)
function App() {
    const [theme, setTheme] = useState('light');
    return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
            <Page />
        </ThemeContext.Provider>
    );
}

// 3. 任意后代使用
function Button() {
    const { theme } = useContext(ThemeContext);
    return <button className={theme}>按钮</button>;
}

注意:Context 值变化会让所有消费它的组件重渲染------别塞频繁变化的数据。

6.3 全局状态:Redux / Zustand

Zustand(轻量推荐):

js 复制代码
import { create } from 'zustand';

const useStore = create((set) => ({
    user: null,
    login: (user) => set({ user }),
    logout: () => set({ user: null })
}));

// 组件里
function Profile() {
    const user = useStore(s => s.user);
    const logout = useStore(s => s.logout);
    return <button onClick={logout}>{user?.name},退出</button>;
}

Redux Toolkit(大型项目):生态成熟、DevTools 强,但样板代码多。


七、React Router

7.1 安装与配置

bash 复制代码
npm install react-router-dom
jsx 复制代码
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function App() {
    return (
        <BrowserRouter>
            <nav>
                <Link to="/">首页</Link>
                <Link to="/products">商品</Link>
            </nav>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/products" element={<ProductList />} />
                <Route path="/products/:id" element={<ProductDetail />} />
                <Route path="*" element={<NotFound />} />
            </Routes>
        </BrowserRouter>
    );
}

7.2 动态路由参数

jsx 复制代码
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';

function ProductDetail() {
    const { id } = useParams();            // /products/1001 → id = "1001"
    const navigate = useNavigate();
    const [params] = useSearchParams();    // ?page=2

    return (
        <div>
            <p>商品 ID:{id}</p>
            <button onClick={() => navigate('/products')}>返回</button>
        </div>
    );
}

7.3 路由守卫(登录拦截)

jsx 复制代码
function RequireAuth({ children }) {
    const isLogin = useStore(s => s.user !== null);

    if (!isLogin) {
        return <Navigate to="/login" replace />;
    }
    return children;
}

// 使用
<Route path="/orders" element={
    <RequireAuth><Orders /></RequireAuth>
} />

八、性能优化

8.1 React.memo:组件跳过重渲染

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

// 父组件重渲染时,props 没变的子组件跳过
const ProductCard = memo(function ProductCard({ product, onBuy }) {
    return (
        <div>
            <p>{product.name}</p>
            <button onClick={() => onBuy(product.id)}>购买</button>
        </div>
    );
});

配合 useCallback :onBuy 不缓存的话,每次都是新函数引用,memo 失效。

8.2 key 的重要性

jsx 复制代码
// 错误:用 index 做 key(删除/排序时状态错乱)
{items.map((item, i) => <Row key={i} item={item} />)}

// 正确:用稳定唯一 id
{items.map(item => <Row key={item.id} item={item} />)}

8.3 懒加载(代码分割)

jsx 复制代码
import { lazy, Suspense } from 'react';

// 路由级懒加载:进入页面才加载对应 chunk
const Orders = lazy(() => import('./pages/Orders'));

function App() {
    return (
        <Suspense fallback={<div>加载中...</div>}>
            <Routes>
                <Route path="/orders" element={<Orders />} />
            </Routes>
        </Suspense>
    );
}

8.4 常见优化清单

  • 列表渲染加稳定 key;
  • 大列表用虚拟滚动(react-window);
  • 防抖搜索输入;
  • 图片懒加载(loading="lazy");
  • 函数/计算用 useCallback/useMemo(配合 memo 才有意义);
  • 打包分析(vite-bundle-visualizer)。

九、React vs Vue(面试常问)

维度 React Vue3
模板 JSX(JS 里写 UI) 模板语法(HTML 扩展)
数据流 单向(props 下传) 单向 + v-model 双向
状态管理 Redux/Zustand Pinia
响应式 setState 触发 自动追踪依赖
上手 心智模型稍难 模板友好,上手快
生态 最大 大
适用 复杂交互、大团队 中小项目、快速开发

选型建议:都不是玄学------看团队熟悉度和项目类型。


十、常见坑速查

现象 原因 解决
setState 不生效 直接修改对象 不可变更新
连续 set 只加 1 闭包旧值 函数式更新
无限循环 effect 里 setState 依赖自身 检查依赖数组
useEffect 执行多次 依赖没写/写错 明确依赖
memo 不生效 函数没缓存 useCallback
key 用 index 列表重排状态错 用稳定 id
组件重渲染很多 数据/引用变了 memo + useCallback

本章小结

React 的核心心智:声明式 UI + 单向数据流 + 组件化。日常开发 80% 时间在 useState/useEffect/props 三个工具上,10% 在路由和状态管理,10% 在性能优化。把 Hooks 的规则(不可变更新、依赖数组、函数式更新)吃透,React 就入门了。

下一篇讲计算机网络与 HTTP------面试必考的地基话题。

(全文完)

相关推荐
v:ychya20181 小时前
独立站外贸建站公司怎么选?建站就要建营销型网站
前端·php
春涧草茶1 小时前
慢就是快12-12手动抛出异常
java·linux·前端
三小河2 小时前
从 Markdown 到 Generative UI:AI 如何从“生成答案”进化到“生成界面”?
前端·人工智能·后端
请为小H留灯2 小时前
Windows 本地前端项目一键运行指南:Volta+Node+pnpm + 阿里镜像完整初始化流程
前端·pnpm·node·js
J船长2 小时前
从 App 开发到 Unity 游戏开发:建立认知地图,结合 AI 开始实践
前端
牛奔2 小时前
mise 管理多版本 Go 项目
开发语言·前端·chrome·后端·golang
能源革命3 小时前
Vue 网格拖拽布局组件全景指南:7 大方案深度对比
前端·javascript·vue.js
PC2005_cloud4 小时前
AI 汉字漂移实录:它为什么总把「学习」写成日语「学習」
前端·后端