React 核心知识点系统总结:从基础语法到高级 API,一篇文章梳理完整学习路线
摘要
React 是当前前端开发中非常重要的 UI 库,它的核心思想并不是"记住一堆 API",而是理解 组件化开发、数据驱动视图、单向数据流、状态管理和前端路由 这一整套开发模式。
本文将按照 React 的学习路线,对 React 中的核心知识点进行系统总结,内容包括:
文章目录
- [React 核心知识点系统总结:从基础语法到高级 API,一篇文章梳理完整学习路线](#React 核心知识点系统总结:从基础语法到高级 API,一篇文章梳理完整学习路线)
- [一、初识 React](#一、初识 React)
-
- [1. React 是什么?](#1. React 是什么?)
- [2. React 的核心思想](#2. React 的核心思想)
- [二、React 基础语法](#二、React 基础语法)
- 三、JSX
-
- [1. JSX 是什么?](#1. JSX 是什么?)
- [2. JSX 的基本规则](#2. JSX 的基本规则)
-
- [2.1 必须有一个根节点](#2.1 必须有一个根节点)
- [2.2 class 要写成 className](#2.2 class 要写成 className)
- [2.3 style 要写成对象](#2.3 style 要写成对象)
- [2.4 JSX 中可以写表达式,不能直接写语句](#2.4 JSX 中可以写表达式,不能直接写语句)
- 四、组件
-
- [1. 什么是组件?](#1. 什么是组件?)
- [2. 函数组件](#2. 函数组件)
- [3. 组件命名规则](#3. 组件命名规则)
- 五、事件绑定
-
- [1. 基本事件绑定](#1. 基本事件绑定)
- [2. 事件传参](#2. 事件传参)
- 六、条件渲染
-
- [1. 三元表达式](#1. 三元表达式)
- [2. && 写法](#2. && 写法)
- [3. 提前 return](#3. 提前 return)
- 七、列表渲染
-
- [1. 使用 map 渲染列表](#1. 使用 map 渲染列表)
- [2. key 的作用](#2. key 的作用)
- [八、props 与 state](#八、props 与 state)
-
- [1. props](#1. props)
- [2. state](#2. state)
- [3. state 不能直接修改](#3. state 不能直接修改)
- [4. props 和 state 的区别](#4. props 和 state 的区别)
- 九、组件通信
-
- [1. 父传子](#1. 父传子)
- [2. 子传父](#2. 子传父)
- [3. 兄弟组件通信](#3. 兄弟组件通信)
- [4. 跨层级通信](#4. 跨层级通信)
- 十、Hooks
-
- [1. useState](#1. useState)
- [2. useEffect](#2. useEffect)
- [3. useEffect 的依赖数组](#3. useEffect 的依赖数组)
-
- [3.1 空数组](#3.1 空数组)
- [3.2 不传依赖数组](#3.2 不传依赖数组)
- [3.3 有依赖项](#3.3 有依赖项)
- [4. 清理副作用](#4. 清理副作用)
- [5. useRef](#5. useRef)
-
- [5.1 获取 DOM](#5.1 获取 DOM)
- [5.2 保存不会触发重新渲染的数据](#5.2 保存不会触发重新渲染的数据)
- [6. useMemo](#6. useMemo)
- [7. useCallback](#7. useCallback)
- [8. 自定义 Hook](#8. 自定义 Hook)
- 十一、Redux
-
- [1. Redux 核心概念](#1. Redux 核心概念)
- [2. Redux Toolkit](#2. Redux Toolkit)
- [3. 什么数据适合放 Redux?](#3. 什么数据适合放 Redux?)
- [十二、React Router](#十二、React Router)
-
- [1. 基础路由](#1. 基础路由)
- [2. Link 跳转](#2. Link 跳转)
- [3. 编程式导航](#3. 编程式导航)
- [4. 动态路由](#4. 动态路由)
- [5. 嵌套路由](#5. 嵌套路由)
- [6. 路由权限控制](#6. 路由权限控制)
- [十三、高级 API 和性能优化](#十三、高级 API 和性能优化)
-
- [1. React.memo](#1. React.memo)
- [2. useMemo](#2. useMemo)
- [3. useCallback](#3. useCallback)
- [4. forwardRef](#4. forwardRef)
- [5. useImperativeHandle](#5. useImperativeHandle)
- [6. lazy 和 Suspense](#6. lazy 和 Suspense)
- [7. Portal](#7. Portal)
- [8. Error Boundary](#8. Error Boundary)
- [十四、React 学习重点总结](#十四、React 学习重点总结)
-
- [1. React 的核心不是 API,而是思想](#1. React 的核心不是 API,而是思想)
- [2. 状态应该放在哪里?](#2. 状态应该放在哪里?)
- [3. 不要滥用 Redux](#3. 不要滥用 Redux)
- [4. Hooks 不是越多越好](#4. Hooks 不是越多越好)
- [5. React 项目开发流程](#5. React 项目开发流程)
- 十五、结语
本文适合正在学习 React,或者已经学过但知识点比较零散,想系统复盘 React 技术体系的同学阅读。
一、初识 React
1. React 是什么?
React 是一个用于构建用户界面的 JavaScript 库。
它主要负责把数据渲染成页面,并在数据发生变化时,让页面自动更新。
在传统前端开发中,我们经常需要手动操作 DOM:
js
const title = document.getElementById("title");
title.innerText = "新的标题";
这种方式在简单页面中没有问题,但当页面交互变复杂、状态变多、DOM 节点变多之后,代码会越来越难维护。
React 的思想是:
text
UI = f(state)
也就是说:
页面是状态的结果,状态发生变化后,页面会自动重新渲染。
所以,React 的核心不是"手动修改 DOM",而是"通过数据描述页面"。
2. React 的核心思想
学习 React,最重要的是理解下面几个思想:
| 核心思想 | 说明 |
|---|---|
| 组件化 | 把页面拆分成一个个独立组件 |
| 声明式开发 | 只描述页面应该长什么样,不直接操作 DOM |
| 数据驱动视图 | 数据变化后,页面自动更新 |
| 单向数据流 | 数据通常从父组件传递到子组件 |
| 状态不可变 | 修改状态时创建新数据,而不是直接改原数据 |
这几个思想贯穿整个 React 学习过程。
二、React 基础语法
React 基础部分是后面学习组件通信、Hooks、Redux、Router 的前提。
React 基础语法主要包括:
- JSX
- 组件
- 事件绑定
- 条件渲染
- 列表渲染
- props
- state
其中最核心的是 JSX、组件、props、state。
三、JSX
1. JSX 是什么?
JSX 是 React 中最常见的语法形式。
它允许我们在 JavaScript 中写类似 HTML 的结构。
示例:
jsx
function App() {
const name = "React";
return <h1>Hello, {name}</h1>;
}
export default App;
这里的:
jsx
{name}
表示在 JSX 中插入 JavaScript 表达式。
需要注意的是,JSX 看起来像 HTML,但它本质上并不是 HTML,而是 JavaScript 的语法扩展,最终会被编译成 JavaScript 代码。
2. JSX 的基本规则
2.1 必须有一个根节点
错误写法:
jsx
return (
<h1>标题</h1>
<p>内容</p>
);
正确写法:
jsx
return (
<div>
<h1>标题</h1>
<p>内容</p>
</div>
);
如果不想额外生成一个 div,可以使用 Fragment:
jsx
return (
<>
<h1>标题</h1>
<p>内容</p>
</>
);
2.2 class 要写成 className
因为 class 是 JavaScript 的关键字,所以 JSX 中要写成 className。
jsx
<div className="container">内容</div>
2.3 style 要写成对象
jsx
<div style={{ color: "red", fontSize: "20px" }}>
红色文字
</div>
注意:
CSS 中的 font-size,在 JSX 中要写成小驼峰形式:
js
fontSize
2.4 JSX 中可以写表达式,不能直接写语句
可以写:
jsx
{age >= 18 ? "成年人" : "未成年人"}
不能直接写:
jsx
{if (age >= 18) { return "成年人" }}
因为 if 是语句,不是表达式。
在 JSX 中,常见可以写的内容包括:
jsx
{name}
{count + 1}
{isLogin ? "已登录" : "未登录"}
{list.map(item => <li>{item.name}</li>)}
四、组件
1. 什么是组件?
组件是 React 中最核心的单位。
一个页面可以拆分成多个组件:
text
App
├── Header
├── Sidebar
├── Main
│ ├── ArticleList
│ └── ArticleDetail
└── Footer
每个组件负责页面中的一部分功能。
组件化的好处是:
- 代码结构更清晰
- 功能更容易复用
- 页面更容易维护
- 复杂页面可以拆分成多个小模块
2. 函数组件
现在 React 开发中,主流写法是函数组件。
jsx
function Header() {
return <h1>React 学习笔记</h1>;
}
export default Header;
在其他组件中使用:
jsx
function App() {
return (
<div>
<Header />
<p>这是页面内容</p>
</div>
);
}
3. 组件命名规则
React 组件名必须首字母大写。
例如:
jsx
<Header />
这是自定义组件。
而:
jsx
<header />
这是原生 HTML 标签。
React 会通过首字母是否大写,判断这是普通 HTML 标签还是自定义组件。
五、事件绑定
1. 基本事件绑定
React 中事件使用小驼峰命名。
例如:
jsx
function App() {
const handleClick = () => {
console.log("按钮被点击了");
};
return <button onClick={handleClick}>点击</button>;
}
需要注意:
jsx
onClick={handleClick}
表示点击时执行函数。
而:
jsx
onClick={handleClick()}
表示组件渲染时立即执行函数。
这是初学者非常容易写错的地方。
2. 事件传参
如果事件函数需要传参,可以使用箭头函数包一层。
jsx
function App() {
const handleClick = (id) => {
console.log("当前 id:", id);
};
return <button onClick={() => handleClick(100)}>点击</button>;
}
不能直接写成:
jsx
<button onClick={handleClick(100)}>点击</button>
因为这样会在页面渲染时立即执行。
六、条件渲染
条件渲染就是根据不同条件显示不同内容。
1. 三元表达式
jsx
function App() {
const isLogin = true;
return (
<div>
{isLogin ? <span>已登录</span> : <span>未登录</span>}
</div>
);
}
三元表达式适合"二选一"的场景。
2. && 写法
如果只需要在条件成立时显示内容,可以使用 &&。
jsx
{isLogin && <button>退出登录</button>}
含义是:
text
isLogin 为 true,显示按钮
isLogin 为 false,不显示按钮
这种写法适合"满足条件才显示"的场景。
3. 提前 return
如果条件逻辑比较复杂,可以提前 return。
jsx
function UserInfo({ user }) {
if (!user) {
return <div>请先登录</div>;
}
return <div>欢迎你,{user.name}</div>;
}
这种写法比在 JSX 中写一堆复杂判断更清晰。
七、列表渲染
1. 使用 map 渲染列表
React 中通常使用 map 渲染列表。
jsx
function App() {
const list = [
{ id: 1, name: "React" },
{ id: 2, name: "Vue" },
{ id: 3, name: "Angular" },
];
return (
<ul>
{list.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
2. key 的作用
在列表渲染中,key 非常重要。
key 的作用是帮助 React 识别每一个列表项,从而提高更新效率。
推荐使用唯一 id:
jsx
<li key={item.id}>{item.name}</li>
不推荐使用数组下标:
jsx
<li key={index}>{item.name}</li>
如果列表会新增、删除、排序,使用 index 作为 key 可能会导致渲染错误或性能问题。
八、props 与 state
props 和 state 是 React 中非常重要的两个概念。
1. props
props 用于父组件向子组件传递数据。
jsx
function Child(props) {
return <div>{props.name}</div>;
}
function App() {
return <Child name="React" />;
}
也可以使用解构写法:
jsx
function Child({ name }) {
return <div>{name}</div>;
}
props 的特点是:
- 由父组件传入
- 子组件只读
- 子组件不能直接修改 props
2. state
state 是组件内部自己的状态。
函数组件中使用 useState 定义状态。
jsx
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
当前数量:{count}
</button>
);
}
这里:
jsx
const [count, setCount] = useState(0);
含义是:
| 内容 | 说明 |
|---|---|
| count | 当前状态值 |
| setCount | 修改状态的方法 |
| 0 | 初始值 |
3. state 不能直接修改
错误写法:
jsx
count++;
正确写法:
jsx
setCount(count + 1);
如果 state 是对象,也不能直接修改原对象。
错误写法:
jsx
user.name = "张三";
setUser(user);
正确写法:
jsx
setUser({
...user,
name: "张三",
});
React 强调状态不可变。
修改状态时,应该创建一个新数据,而不是直接修改原数据。
4. props 和 state 的区别
| 对比项 | props | state |
|---|---|---|
| 来源 | 父组件传入 | 组件内部定义 |
| 是否可修改 | 子组件不能修改 | 组件自己可以修改 |
| 作用 | 组件之间传值 | 管理组件内部状态 |
| 更新方式 | 父组件重新传入 | setState / setXxx 修改 |
简单理解:
props 是外部传进来的数据,state 是组件自己管理的数据。
九、组件通信
React 是单向数据流,数据通常从父组件流向子组件。
常见组件通信方式如下:
| 通信场景 | 实现方式 |
|---|---|
| 父传子 | props |
| 子传父 | 父组件传函数,子组件调用 |
| 兄弟组件通信 | 状态提升 |
| 跨层级通信 | Context |
| 全局通信 | Redux |
1. 父传子
父组件通过 props 把数据传给子组件。
jsx
function Child({ title }) {
return <h2>{title}</h2>;
}
function App() {
return <Child title="React 学习" />;
}
2. 子传父
子组件不能直接修改父组件的数据。
如果子组件想把数据传给父组件,需要父组件先传一个函数给子组件。
jsx
function Child({ onSend }) {
return (
<button onClick={() => onSend("来自子组件的数据")}>
发送数据
</button>
);
}
function App() {
const handleReceive = (data) => {
console.log("父组件接收到:", data);
};
return <Child onSend={handleReceive} />;
}
这个过程可以理解为:
text
父组件把函数传给子组件
子组件调用这个函数
调用时把数据作为参数传回父组件
3. 兄弟组件通信
兄弟组件之间不能直接通信。
通常需要把共享状态提升到共同的父组件中。
jsx
function A({ setMsg }) {
return (
<button onClick={() => setMsg("来自 A 组件的数据")}>
发送
</button>
);
}
function B({ msg }) {
return <div>B 组件接收到:{msg}</div>;
}
function App() {
const [msg, setMsg] = useState("");
return (
<div>
<A setMsg={setMsg} />
<B msg={msg} />
</div>
);
}
这种方式叫做状态提升。
4. 跨层级通信
如果组件层级很深,一级一级传 props 会非常麻烦。
例如:
text
App → A → B → C → D
如果 App 的数据要传给 D,中间组件都要帮忙传递。
这种情况可以使用 Context。
jsx
import { createContext, useContext } from "react";
const UserContext = createContext();
function App() {
return (
<UserContext.Provider value="张三">
<Child />
</UserContext.Provider>
);
}
function Child() {
return <GrandChild />;
}
function GrandChild() {
const name = useContext(UserContext);
return <div>当前用户:{name}</div>;
}
Context 适合传递:
- 用户信息
- 主题颜色
- 国际化语言
- 权限信息
- 全局配置
但是,如果状态变化频繁,或者业务逻辑复杂,更推荐使用 Redux 等状态管理工具。
十、Hooks
Hooks 是现代 React 开发的核心。
Hooks 让函数组件也可以拥有状态、副作用和逻辑复用能力。
常见 Hooks 包括:
| Hook | 作用 |
|---|---|
| useState | 定义组件状态 |
| useEffect | 处理副作用 |
| useRef | 获取 DOM 或保存可变数据 |
| useMemo | 缓存计算结果 |
| useCallback | 缓存函数 |
| useContext | 使用 Context 数据 |
| 自定义 Hook | 复用组件逻辑 |
1. useState
useState 用于定义组件状态。
jsx
const [count, setCount] = useState(0);
完整示例:
jsx
function App() {
const [count, setCount] = useState(0);
const add = () => {
setCount(count + 1);
};
return <button onClick={add}>{count}</button>;
}
如果新的状态依赖上一次状态,推荐使用函数式写法:
jsx
setCount(prev => prev + 1);
这种写法更加稳定。
2. useEffect
useEffect 用来处理副作用。
副作用包括:
- 请求接口
- 设置定时器
- 绑定事件监听
- 操作 DOM
- 修改页面标题
- 订阅数据
基本写法:
jsx
useEffect(() => {
console.log("组件渲染完成");
}, []);
3. useEffect 的依赖数组
3.1 空数组
jsx
useEffect(() => {
console.log("组件挂载时执行一次");
}, []);
表示组件挂载时执行一次。
3.2 不传依赖数组
jsx
useEffect(() => {
console.log("每次组件渲染都会执行");
});
表示每次组件渲染都会执行。
3.3 有依赖项
jsx
useEffect(() => {
console.log("count 变化了");
}, [count]);
表示只有 count 变化时才执行。
4. 清理副作用
如果在组件中使用定时器,需要在组件卸载时清理。
jsx
useEffect(() => {
const timer = setInterval(() => {
console.log("定时器执行");
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return 中的函数就是清理函数。
它会在组件卸载时执行,也会在下一次 effect 执行前执行。
5. useRef
useRef 有两个常见用途。
5.1 获取 DOM
jsx
function App() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>聚焦</button>
</div>
);
}
5.2 保存不会触发重新渲染的数据
jsx
const countRef = useRef(0);
countRef.current++;
修改 ref.current 不会导致组件重新渲染。
6. useMemo
useMemo 用来缓存计算结果。
如果某个计算过程比较耗时,可以使用 useMemo 避免每次渲染都重新计算。
jsx
const total = useMemo(() => {
return list.reduce((sum, item) => sum + item.price, 0);
}, [list]);
只有当 list 变化时,total 才会重新计算。
注意:
useMemo 不是必须到处使用,只有计算成本较高时才有意义。
7. useCallback
useCallback 用来缓存函数引用。
jsx
const handleClick = useCallback(() => {
console.log("点击");
}, []);
它通常和 React.memo 配合使用,减少子组件不必要的重新渲染。
8. 自定义 Hook
自定义 Hook 用于复用逻辑。
例如,封装一个计数器逻辑:
jsx
function useCounter() {
const [count, setCount] = useState(0);
const add = () => {
setCount(prev => prev + 1);
};
const minus = () => {
setCount(prev => prev - 1);
};
return {
count,
add,
minus,
};
}
使用:
jsx
function App() {
const { count, add, minus } = useCounter();
return (
<div>
<button onClick={minus}>-</button>
<span>{count}</span>
<button onClick={add}>+</button>
</div>
);
}
自定义 Hook 的本质是:
把组件中的可复用逻辑抽离出来。
十一、Redux
当项目变复杂后,多个组件之间共享状态会变得困难。
例如:
- 用户登录信息多个页面都要用
- 权限信息多个组件都要判断
- 购物车数据多个页面都要展示
- 后台管理系统中多个页面共享筛选条件
这时可以使用 Redux。
Redux 是一种全局状态管理方案。
它的核心思想是:
把多个组件都要使用的数据,统一放到一个 store 中管理。
1. Redux 核心概念
Redux 有几个核心概念:
| 概念 | 说明 |
|---|---|
| Store | 全局状态仓库 |
| State | 仓库中的状态数据 |
| Action | 描述要做什么事情 |
| Reducer | 根据 action 修改 state |
| Dispatch | 触发 action |
Redux 的数据流如下:
text
组件 dispatch action
↓
reducer 根据 action 修改 state
↓
store 更新
↓
组件重新渲染
2. Redux Toolkit
现在开发中更推荐使用 Redux Toolkit。
它可以减少传统 Redux 中大量模板代码。
示例:
jsx
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: {
count: 0,
},
reducers: {
add(state) {
state.count++;
},
minus(state) {
state.count--;
},
},
});
export const { add, minus } = counterSlice.actions;
export default counterSlice.reducer;
组件中使用:
jsx
import { useSelector, useDispatch } from "react-redux";
import { add, minus } from "./counterSlice";
function App() {
const count = useSelector(state => state.counter.count);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(minus())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(add())}>+</button>
</div>
);
}
3. 什么数据适合放 Redux?
并不是所有状态都应该放进 Redux。
适合放 Redux 的数据:
- 用户信息
- 登录状态
- 权限信息
- 购物车数据
- 全局配置
- 多页面共享的业务状态
不适合放 Redux 的数据:
- 输入框临时内容
- 弹窗开关
- 某个组件内部的小状态
- 局部 loading
- 鼠标悬浮状态
简单判断:
text
只在当前组件使用:useState
少量跨层级共享:Context
复杂全局共享状态:Redux
十二、React Router
React Router 用于实现前端路由。
在传统网站中,不同页面通常对应不同 HTML 文件。
而在 React 单页应用中,页面切换本质上是组件切换。
例如:
text
/ → Home 组件
/about → About 组件
/detail/1 → Detail 组件
1. 基础路由
jsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
2. Link 跳转
jsx
import { Link } from "react-router-dom";
function Home() {
return <Link to="/about">跳转到关于页面</Link>;
}
Link 不会刷新整个页面,只会切换前端路由。
3. 编程式导航
有些场景需要在函数中跳转页面,比如登录成功后跳转首页。
jsx
import { useNavigate } from "react-router-dom";
function Login() {
const navigate = useNavigate();
const handleLogin = () => {
navigate("/home");
};
return <button onClick={handleLogin}>登录</button>;
}
4. 动态路由
详情页通常需要动态参数。
jsx
<Route path="/detail/:id" element={<Detail />} />
获取参数:
jsx
import { useParams } from "react-router-dom";
function Detail() {
const params = useParams();
return <div>当前 ID:{params.id}</div>;
}
访问:
text
/detail/100
可以拿到:
text
id = 100
5. 嵌套路由
嵌套路由适合后台管理系统布局。
jsx
<Route path="/layout" element={<Layout />}>
<Route path="home" element={<Home />} />
<Route path="user" element={<User />} />
<Route path="setting" element={<Setting />} />
</Route>
在 Layout 组件中使用:
jsx
import { Outlet } from "react-router-dom";
function Layout() {
return (
<div>
<aside>侧边栏</aside>
<main>
<Outlet />
</main>
</div>
);
}
Outlet 表示子路由渲染的位置。
6. 路由权限控制
实际项目中,经常需要判断用户是否登录。
如果没有登录,就跳转到登录页。
jsx
import { Navigate } from "react-router-dom";
function AuthRoute({ children }) {
const token = localStorage.getItem("token");
if (!token) {
return <Navigate to="/login" />;
}
return children;
}
使用:
jsx
<Route
path="/home"
element={
<AuthRoute>
<Home />
</AuthRoute>
}
/>
这是前端项目中非常常见的路由鉴权写法。
十三、高级 API 和性能优化
当掌握 React 基础、Hooks、Redux、Router 之后,就需要进一步学习 React 的高级 API。
高级 API 主要解决两个问题:
text
1. 性能优化
2. 复杂组件场景
1. React.memo
React.memo 用于避免组件不必要的重新渲染。
jsx
const Child = React.memo(function Child({ name }) {
console.log("Child 渲染了");
return <div>{name}</div>;
});
如果父组件重新渲染,但传给 Child 的 props 没有变化,那么 Child 不会重新渲染。
适合使用场景:
- 子组件比较复杂
- 子组件渲染成本较高
- props 不经常变化
2. useMemo
useMemo 用于缓存计算结果。
jsx
const total = useMemo(() => {
return list.reduce((sum, item) => sum + item.price, 0);
}, [list]);
适合计算量较大的场景。
但不要滥用 useMemo。
如果计算本身很简单,使用 useMemo 反而会增加代码复杂度。
3. useCallback
useCallback 用于缓存函数引用。
jsx
const handleClick = useCallback(() => {
console.log("click");
}, []);
它常用于把函数传给被 React.memo 包裹的子组件。
4. forwardRef
forwardRef 可以让父组件获取子组件内部的 DOM。
jsx
const MyInput = forwardRef((props, ref) => {
return <input ref={ref} />;
});
function App() {
const inputRef = useRef(null);
return (
<div>
<MyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>
聚焦
</button>
</div>
);
}
5. useImperativeHandle
useImperativeHandle 可以控制子组件暴露给父组件的方法。
jsx
const MyInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus() {
inputRef.current.focus();
},
}));
return <input ref={inputRef} />;
});
这样父组件只能调用子组件暴露出来的方法,而不是随意操作子组件内部细节。
6. lazy 和 Suspense
lazy 和 Suspense 用于组件懒加载。
jsx
import { lazy, Suspense } from "react";
const Home = lazy(() => import("./Home"));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Home />
</Suspense>
);
}
适合用于路由懒加载,可以减少首屏加载体积,提高页面打开速度。
7. Portal
Portal 可以把组件渲染到当前 DOM 层级之外。
常见使用场景:
- 弹窗
- 抽屉
- Tooltip
- 全局提示
示例:
jsx
import { createPortal } from "react-dom";
function Modal() {
return createPortal(
<div className="modal">弹窗内容</div>,
document.body
);
}
虽然 Modal 写在当前组件中,但它实际会被渲染到 document.body 下。
8. Error Boundary
Error Boundary 用于捕获组件渲染错误,防止整个页面崩溃。
大型项目中,如果某个组件发生错误,可以只显示局部错误提示,而不是整个页面白屏。
需要注意:
Error Boundary 主要使用类组件实现。
十四、React 学习重点总结
1. React 的核心不是 API,而是思想
React 的核心思想包括:
text
组件化
声明式开发
数据驱动视图
单向数据流
状态不可变
如果只背 API,而不理解这些思想,写项目时很容易混乱。
2. 状态应该放在哪里?
React 项目中最重要的问题之一是:
这个数据应该放在哪里?
可以按照下面的规则判断:
| 数据使用范围 | 推荐方案 |
|---|---|
| 只有当前组件使用 | useState |
| 父子组件共享 | props |
| 兄弟组件共享 | 状态提升 |
| 多层组件共享 | Context |
| 多页面复杂共享 | Redux |
很多 React 项目写乱,不是因为 JSX 不会写,而是因为状态位置设计不合理。
3. 不要滥用 Redux
Redux 很强,但不是所有状态都应该放 Redux。
例如:
jsx
const [keyword, setKeyword] = useState("");
这种输入框临时内容,直接放组件内部即可。
Redux 更适合全局共享、跨页面使用、业务复杂的数据。
4. Hooks 不是越多越好
useMemo、useCallback 这些 Hook 是优化工具,不是必须到处使用。
更合理的顺序是:
text
先写清楚
再考虑复用
最后考虑性能优化
如果组件本身很简单,过度使用优化 Hook 反而会降低代码可读性。
5. React 项目开发流程
一个 React 项目的开发流程可以概括为:
text
1. 分析页面结构
2. 拆分组件
3. 设计数据结构
4. 确定状态放在哪里
5. 编写组件
6. 处理组件通信
7. 接入接口请求
8. 接入路由
9. 接入状态管理
10. 做性能优化和代码整理
在真正写代码之前,应该先想清楚:
- 页面怎么拆
- 数据从哪里来
- 状态放在哪里
- 组件之间如何通信
这比直接上来写 JSX 更重要。
十五、结语
React 的知识点看起来很多,但主线其实非常清楚:
text
JSX 是写页面结构的方式
组件是组织页面的单位
props 是父子传值的方式
state 是组件内部状态
Hooks 是函数组件的核心能力
Redux 解决复杂全局状态
React Router 解决页面路由切换
高级 API 解决性能优化和复杂组件场景
如果用一句话总结 React:
React 是一个通过组件化组织页面,通过数据驱动视图,通过单向数据流管理状态的 UI 库。
学习 React 不能只停留在"这个 API 怎么写",更应该理解:
- 为什么要组件化?
- 为什么状态不能直接修改?
- 为什么数据要单向流动?
- 为什么有些状态适合放 Redux,有些状态只适合放 useState?
- 为什么需要 useMemo、useCallback 这类优化工具?
当这些问题理解清楚后,React 的知识体系就不会再显得零散。
React 的学习路线可以最终总结为:
text
基础语法
↓
组件思想
↓
数据流
↓
Hooks
↓
状态管理
↓
路由系统
↓
高级 API
↓
项目实践
只要沿着这条路线学习,就可以从"会写 React"逐渐过渡到"会设计 React 项目"。