React 学习笔记
这是一篇边学边记的 React 笔记,目标不是抄文档,而是把「什么时候该用什么」讲清楚。示例代码基于 React 18+,大部分写法在 React 19 同样适用,有差异的地方单独标注。
一、React 到底是什么
一句话:React 是一个用于构建用户界面的 JavaScript 库,它只负责「数据 → 界面」这一层,路由、请求、状态管理都交给生态。
三个核心观念,理解了它们就理解了 React 的一半:
| 观念 | 含义 | 带来的好处 |
|---|---|---|
| 声明式 | 你描述「界面应该长什么样」,而不是「一步步怎么改 DOM」 | 不用手写 DOM 操作,心智负担低 |
| 组件化 | UI 拆成独立、可复用的函数(组件) | 复用、隔离、好测试 |
| 单向数据流 | 数据从父组件流向子组件,子组件不能直接改父组件的数据 | 数据变化可追踪,出 bug 好定位 |
对比一下就明白了。同样是「点击按钮后把数字加一」:
1 | // 命令式(原生 JS)—— 需要自己管 DOM |
二、环境搭建
现在官方推荐用 Vite,create-react-app 已经不推荐在新项目里使用了。
1 | # 创建项目(选择 React + JavaScript/TypeScript) |
生成的关键文件:
1 | my-app/ |
main.jsx 里这几行是整个应用的起点:
1 | import React from 'react' |
三、JSX 语法要点
JSX 不是模板字符串,也不是 HTML,它会被编译成 React.createElement() 调用。和 HTML 的差异是新手最容易踩坑的地方:
| 场景 | HTML 写法 | JSX 写法 | 原因 |
|---|---|---|---|
| 类名 | class="box" |
className="box" |
class 是 JS 关键字 |
| 内联样式 | style="color:red" |
style={{ color: 'red' }} |
外层 {} 是表达式,内层 {} 是对象 |
| label | for="name" |
htmlFor="name" |
同上,for 是关键字 |
| 自闭合 | <br> |
<br /> |
JSX 要求标签闭合 |
| 注释 | <!-- x --> |
{/* x */} |
JSX 里只能写 JS 注释 |
三条规则记住就不会错:
- 只能返回一个根元素 —— 多个并列元素用
<> </>(Fragment)包起来 {}里可以放任何 JS 表达式,但不能放语句(如if、for)- 属性名用驼峰:
onclick→onClick,tabindex→tabIndex
1 | function UserCard({ user }) { |
四、组件与 Props
组件就是首字母大写的函数,返回一段 JSX。Props 是父组件传进来的参数,是个只读对象。
1 | // 子组件:通过 props 接收 |
Props 是只读的,永远不要在子组件里写 props.name = 'xxx'。组件内部要变的数据用 state,要通知父组件就调用父组件传下来的回调函数。
children 是个特殊的 prop,代表标签之间的内容:
1 | function Card({ title, children }) { |
五、State 与事件处理
useState 是组件内部的「可变数据」。调用 setXxx 会让组件重新渲染。
1 | import { useState } from 'react' |
state 更新是异步且批量的:调用 setCount 后立刻 console.log(count) 拿到的是旧值。要拿到更新后的值,用 useEffect 监听,或者直接用新值继续计算。
事件绑定的三种写法,注意括号:
1 | <button onClick={handleClick}>传函数引用</button> {/* ✅ 推荐 */} |
六、条件渲染与列表渲染
1 | // 1. 三元表达式:二选一 |
列表渲染用 map,必须写 key:
1 | {users.map(user => ( |
key 不要用数组下标(index)。删除中间某一项时下标会错位,导致 React 复用错误的 DOM 和组件状态 —— 表单输入框内容错位就是这么来的。用数据里稳定的 id。
七、表单:受控组件
React 里推荐「受控组件」:表单的值由 state 保管,value 与 onChange 成对出现。
1 | function Form() { |
八、Hooks 逐个拆解
Hooks 就是「在函数组件里使用状态等 React 特性」的函数。规则只有两条:只能在组件顶层调用(不能放进 if / for / 嵌套函数)、只能在组件或自定义 Hook 里调用。
useState
1 | const [state, setState] = useState(初始值) |
初始值可以传函数(懒初始化,只在首次渲染执行,适合开销大的计算):
1 | const [list, setList] = useState(() => JSON.parse(localStorage.getItem('list')) || []) |
useEffect —— 处理「副作用」
副作用 = 渲染之外的事情:请求数据、订阅、操作 DOM、定时器。
1 | useEffect(() => { |
依赖数组的三种情况,务必分清:
| 依赖数组 | 执行时机 | 典型场景 |
|---|---|---|
| 不写 | 每次渲染后都执行 | 几乎不用,容易死循环 |
[] |
只在挂载后执行一次 | 初始化请求、绑定全局事件 |
[a, b] |
挂载时 + a/b 变化时 |
按条件重新请求数据 |
依赖数组里必须写全 effect 中用到的所有会变化的外部变量(state、props、组件内定义的函数),否则会拿到过期的旧值 —— 这是 useEffect 最经典的坑。装上 eslint-plugin-react-hooks 可以自动提示。
useRef —— 拿 DOM 或存「不触发渲染的值」
1 | const inputRef = useRef(null) |
改 ref.current 不会触发重新渲染,所以它适合存定时器 ID、上一次的值这类不需要显示在界面上的数据。
useMemo / useCallback —— 缓存
1 | const expensive = useMemo(() => heavyCalc(list), [list]) // 缓存计算结果 |
别滥用。二者本身也有开销,只在这两种情况下用:① 计算确实很重;② 函数要传给用 React.memo 包过的子组件(否则每次渲染函数引用都变,memo 白做)。
useContext —— 跨层传递
解决「props 一层层往下传」的地狱:
1 | // 1. 创建 |
useReducer —— 复杂状态逻辑
state 结构复杂、更新逻辑多时,比 useState 清晰得多(Redux 的迷你版):
1 | function reducer(state, action) { |
自定义 Hook —— 逻辑复用
把重复逻辑抽出来,名字以 use 开头:
1 | function useLocalStorage(key, initial) { |
九、组件通信方式汇总
| 场景 | 方案 |
|---|---|
| 父 → 子 | props |
| 子 → 父 | 父传回调函数,子调用 |
| 兄弟组件 | 状态提升到共同的父组件 |
| 跨多层 | Context / 状态管理库 |
| 任意组件 | Zustand / Redux 等状态管理库 |
十、数据请求
推荐用 axios,比原生 fetch 好用(自动 JSON 解析、拦截器、超时控制)。
1 | useEffect(() => { |
正经项目建议直接用 TanStack Query(原 React Query),它把 loading / error / 缓存 / 重试 / 竞态全包了,能省掉上百行样板代码。
十一、路由(React Router v6)
1 | npm install react-router-dom |
1 | <Routes> |
1 | // 编程式跳转 |
十二、状态管理怎么选
组件不多、共享状态少 → Context + useReducer 就够用,别一上来就引入 Redux。
推荐 Zustand:API 极简(一个 create 搞定)、不需要 Provider 包裹、体积约 1KB。
1 | import { create } from 'zustand' |
Redux Toolkit:严格的单向数据流、时间旅行调试、完善的中间件体系,团队协作规范性强,代价是样板代码偏多。
十三、性能优化清单
- 列表项组件用
React.memo包裹,父组件重渲染时跳过未变化的子项 - 传给 memo 子组件的回调函数用
useCallback包住,避免引用变化让 memo 失效 - 长列表用虚拟滚动(
react-window/react-virtuoso),只渲染可视区域 - 路由级代码分割:
const Page = lazy(() => import('./Page'))配合<Suspense> - 图片懒加载 + CDN + 合适尺寸(可参考本站的封面缩放做法)
十四、常见坑速查
| 现象 | 原因 | 解法 |
|---|---|---|
| setState 后读到旧值 | 更新是异步批量的 | 用 useEffect 监听,或直接用新值 |
| 连续 setState 只生效一次 | 用了快照值而非函数式更新 | 改成 setCount(c => c + 1) |
| useEffect 死循环 | 依赖里放了每次渲染都变的对象/数组 | 用 useMemo 稳定引用,或调整依赖 |
| 列表删除后输入框错位 | 用 index 当 key | 换成数据里的稳定 id |
| 样式没生效 | 写了 class 而不是 className |
改 className |
| 事件里 e.target 变成 null | React 的事件对象会被回收 | 先解构 const { value } = e.target |
十五、React 与 Vue 对照
| 能力 | React | Vue 3 |
|---|---|---|
| 模板 | JSX(JS 里写 HTML) | template(HTML 里写 JS) |
| 状态 | useState |
ref / reactive |
| 副作用 | useEffect + 依赖数组 |
watch / watchEffect(自动收集依赖) |
| 计算属性 | useMemo |
computed |
| 组件通信 | props + 回调 | props + emit |
| 跨层传递 | Context | provide / inject |
两者思想高度相似(虚拟 DOM + 响应式 + 组件化),精通一个后学另一个的成本很低。
学习路径建议:JSX → 组件/Props → useState 与事件 → 条件与列表渲染 → useEffect → 表单 → 自定义 Hook → 路由 → 状态管理。每一步都配一个小 demo,比通读文档有效得多。


