这本字典怎么用
按用途分五组:JSX 与组件、内置组件、Hooks 速查、事件与表单、常见报错速查。每条四件事:签名、一句话语义(写”什么时候用、和谁的区别”)、最小示例、常见坑。字典是查的不是读的——先搜文末速查表定位,再跳回条目。概念的”为什么”在《React 语法要点》,上手过程见本系列的《环境搭建》与《第一个应用》两篇。条目默认运行在函数组件里。
一、JSX 与组件
组内共性:JSX 是 JavaScript 的语法糖,花括号里放的是表达式;能放进 {} 的东西与 .js 文件里能写的东西完全一样。
{ } 表达式插值
往 JSX 里嵌入任意表达式(会求值成一段内容的代码),不是模板变量。
<p>共 {items.length} 条,未完成 {items.filter(i => !i.done).length} 条</p>
<img src={user.avatar} alt={user.name} /> {/* 属性值同样是表达式 */}
坑:只能放表达式不能放语句(if/for 要换成三元与 map);渲染对象会直接报错——”Objects are not valid as a React child”,只能渲染字符串、数字等标量或元素。
条件渲染(&& / 三元 / 提前 return)
三选一:短路与渲染”可有可无”的东西,三元二选一,分支复杂时在组件体内提前 return。
{err && <p className="error">{err}</p>}
{loading ? <Spinner /> : <List />}
function Page({ user }) {
if (!user) return <Login /> // 早退分支
return <Dashboard user={user} />
}
坑:{count && …} 在 count 为 0 时渲染出字符串 “0”——布尔与 null 不渲染,数字会。防死写法 {count > 0 && …}。
列表渲染与 key
map 把数组变成元素数组;key 是每项的身份标识,React 靠它对账”哪个节点还是原来那个”。
{todos.map(t => (
<TodoItem key={t.id} todo={t} /> // key 用稳定业务 id
))}
坑:数组下标当 key,头部插入或删除后节点与数据错位,输入框、勾选状态会串到别的行;随机数 key 每次渲染都变,等于没有。没有 id 就造稳定 id(crypto.randomUUID())。
className 与 style
class 属性写 className;style 收对象不收字符串,数字自动补 px。
<p className="title">标题</p>
<p style={{ color: 'crimson', marginTop: 8 }}>内联样式</p>
坑:动态多个 class 用模板串或库(className={`btn ${active ? 'on' : ''}`}),别写成 class=——那是 HTML,JSX 里告警且部分场景行为不一致。
props 与默认值
组件的输入契约;父传什么子拿什么,子组件只读,改数据只能回调。
function Badge({ tone = 'gray', children }) { // 解构参数位给默认值
return <span className={`badge badge-${tone}`}>{children}</span>
}
坑:给 props 对象的嵌套字段赋值(props.item.done = true)看似生效,父组件下一次渲染就覆盖回去;传布尔简写 <Badge dark /> 等价 dark={true},但 dark="false" 是真值字符串——布尔属性别传字符串。
children
标签之间内容的容器,组件组装的原生通道,对应 Vue 的插槽。
function Card({ title, children }) {
return <section><header>{title}</header>{children}</section>
}
<Card title="标题"><p>内容由调用方决定</p></Card>
坑:children 是普通 prop,可以条件渲染、可以传任何东西(包括函数,渲染属性模式);但别在子组件里修改 children 里的元素——内容的主权在调用方。
二、内置组件
组内共性:React 顶层的特殊组件与 API,不带状态、只改变渲染行为或提供边界信息。
(…>)</h3>
不产生真实 DOM 的包裹器,满足 JSX 单根要求又不留多余节点。
return (
<>
<dt>{term}</dt>
<dd>{desc}</dd>
</> // 渲染结果没有多余的 wrapper,dl 结构合法
)
坑:需要传 key 时不能用短语法,必须写全 <Fragment key={id}>——短语法 <> 不接受任何属性。
开发模式下的检查器:故意双调用渲染与 Effect,把不纯的渲染、缺清理的副作用提前暴露。生产构建零影响。
<StrictMode>
<App />
</StrictMode>
// 挂在入口 createRoot 处即可,Vite 模板默认已带
坑:开发时”Effect 执行了两次”不是 bug,是 StrictMode 在帮你测清理函数;修法不是删 StrictMode,而是把清理写对(见 useEffect 条目)。
声明”这棵子树还没准备好”时的占位边界,配合 lazy 或异步数据使用。
<Suspense fallback={<Spinner />}>
<LazyDashboard />
</Suspense>
坑:fallback 只在”从无到有”与挂起时显示,已显示的内容更新时不会闪回占位;嵌套多个 Suspense 时占位粒度取最近的那个边界。
React.lazy
把组件的 import 变成”用到时才加载”,产出要放进 Suspense 里的组件。
const Settings = React.lazy(() => import('./Settings.jsx'))
// 路由级代码分割的标准姿势:每个页面组件一个 lazy
<Suspense fallback={<p>加载中…</p>}><Settings /></Suspense>
坑:lazy 接收”返回 Promise 的动态 import”,别写成 React.lazy(import('./x'))(立刻执行、签名不符);组件内部小段代码分割用动态 import + then 即可,不必上 lazy。
三、Hooks 速查
组内共性:Hook 让函数组件接入渲染周期;两条铁律——只在顶层调用、只在组件或自定义 Hook 里调用。执行模型(快照、闭包、依赖数组机制)的完整讲解在《React Hooks 心智模型》,这里只给速查形态。
useState
声明会触发重渲染的状态,返回 [当前值, 设置器]。
const [count, setCount] = useState(0)
setCount(count + 1) // 基于本次渲染快照的新值
setCount(v => v + 1) // 函数式更新:拿到队列里的最新值,连用不出错
const [list, setList] = useState(() => loadExpensive()) // 惰性初始化只跑一次
坑:同一事件里连续 setCount(count + 1) 三次只加 1——count 是快照;改对象/数组必须创建新引用(展开、map、filter),原地改界面不动。
useEffect
声明副作用:”依赖数组里的值变化后(含首次),执行这段代码”。
useEffect(() => {
const id = setInterval(tick, 1000)
return () => clearInterval(id) // 清理函数:下次执行前与卸载时都调用
}, [delay])
坑:漏写依赖是最常见 bug 源——闭包拿到的是旧渲染的值,lint 的 exhaustive-deps 警告当 error 对待;不写依赖数组等于每次渲染都执行,里面再 setState 就是死循环。
useRef
不触发重渲染的可变容器,.current 读写;也用于持有 DOM 引用。
const inputRef = useRef(null)
<input ref={inputRef} />
useEffect(() => inputRef.current?.focus(), []) // 挂载后聚焦
坑:改 ref.current 不触发渲染——要上界面的值用 state;初始值只在首次生效;渲染期间别读写 ref(保持渲染纯度)。
useMemo
缓存昂贵计算的结果:依赖不变就返回上次的值。
const stats = useMemo(
() => heavyCompute(rows), // rows 是几千行时才值得缓存
[rows]
)
坑:先测出慢再加(DevTools Profiler),缓存本身有比较开销;它解决”算得贵”,不解决”引用每次都变”——那是 useCallback 的事。React Compiler(当前为 RC 阶段)已可在新项目试用自动 memoization,新项目可考虑让编译器做。
useCallback
缓存函数引用:依赖不变时返回同一个函数,让子组件的 memo 或 Effect 的依赖比较能通过。
const onSave = useCallback(() => save(row), [row])
<MemoRow row={row} onSave={onSave} /> // 引用稳定,MemoRow 的 memo 才生效
坑:只传给”没被 memo 的子组件”或普通 DOM 时毫无收益,纯增加代码噪音;两层滥用(到处 memo + 到处 useCallback)是可读性反模式,何时值得做见渲染深潜文。
useContext
读取最近 Provider 提供的 context 值,跳过逐层传 props。
const ThemeContext = createContext('light') // 创建+默认值
<ThemeContext.Provider value={theme}><App /></ThemeContext.Provider>
function Button() {
const theme = useContext(ThemeContext) // 任意深度的后代都能读
}
坑:Provider 的 value 变化会让所有消费者重渲染,value 用对象字面量(每次新引用)等于全量广播——用 useMemo 稳住 value,或按更新频率拆成多个 context;只放主题、文案、当前用户这类真全局语义。
四、事件与表单
组内共性:事件名驼峰(onClick 不是 onclick),处理函数收到合成事件(SyntheticEvent)——跨浏览器统一包装的事件对象。
onClick / onChange 等基础事件
把事件处理函数作为 prop 传给元素。
<button onClick={() => setOpen(!open)}>切换</button>
<input onChange={e => setText(e.target.value)} />
坑:onClick={handle()} 立刻执行并把返回值当处理器——要传函数本身 onClick={handle} 或 onClick={() => handle(id)};合成事件是原生事件的轻量包装,React 17 起已移除事件池,异步回调里直接用 e.target.value 是安全的——旧教程里的 e.persist() 不再需要。
onSubmit 与表单提交
表单提交拦在 form 上,回车与提交按钮天然一致。
<form onSubmit={e => {
e.preventDefault() // 不拦会整页刷新,state 全丢
addTodo(text)
}}>
<input … /><button>添加</button>
</form>
坑:把提交绑在按钮 onClick 上——输入框里按回车不触发;移动端浏览器对 form 的”下一个/完成”也走 submit 路径,绑 form 才都接得住。
受控三件套(input / textarea / select)
表单值收编进 state:value 由 state 出,onChange 写回 state。
<input value={text} onChange={e => setText(e.target.value)} />
<textarea value={memo} onChange={e => setMemo(e.target.value)} />
<select value={lang} onChange={e => setLang(e.target.value)}>
<option value="zh">中文</option>
</select>
坑:checkbox 与 radio 用 checked 而不是 value:<input type="checkbox" checked={ok} onChange={e => setOk(e.target.checked)} />;只给 value 不给 onChange,输入框锁死打不了字。
文件输入(非受控)
文件路径浏览器不许脚本设置,无法受控,用 ref 直取。
const fileRef = useRef(null)
<input type="file" ref={fileRef} onChange={() => {
const f = fileRef.current?.files?.[0] // File 对象在 DOM 上
if (f) setUploading(f)
}} />
坑:给 file input 设 value 属性无效(只读);同名文件连续选择两次不触发 onChange 时,处理完后手动清 e.target.value = ''。
五、常见报错速查
组内共性:报错文本可直接搜索定位。八成源于三类反模式——状态改法不对、Hook 用法违反规则、Effect 依赖不完整。
| 症状 | 真实原因 | 修法 |
|---|---|---|
| Warning: Each child in a list should have a unique key | map 渲染没给 key,或 key 不稳定(下标、随机数) | key 用稳定业务 id;数据没 id 就生成并随数据持久化 |
| Warning: useEffect has a missing dependency | Effect 里用了组件内的值但没列进依赖数组,闭包是旧值 | 把值补进依赖;依赖变化太频繁就调整 Effect 粒度或把逻辑移进事件处理 |
| Too many re-renders. React limits the number of renders… | 渲染过程里直接调 setState(如函数体顶层)形成无限循环 | setState 移进事件处理或 Effect;初始化用惰性初始化而非”判断后 set” |
| Warning: Cannot update a component while rendering a different component | 渲染组件 A 时顺手改了组件 B 的 state | 改数据的动作移进事件处理;确需联动用 Effect 或状态提升 |
| Rendered more hooks than during the previous render | Hook 调用进了条件/循环/提前 return,两次渲染数量不一致 | Hook 全部放到函数顶层;条件逻辑放进 Hook 内部 |
| Objects are not valid as a React child | 花括号里渲染了对象或数组本身 | 渲染具体字段(obj.name)或先 map 成元素;数组渲染记得 key |
| 开发模式 Effect 执行了两次 | StrictMode 故意双调用以暴露缺失的清理 | 写对清理函数(return 清理逻辑),不要删 StrictMode |
速查表
| 条目 | 一句话用途 | 有什么坑 |
|---|---|---|
| { } 插值 | 往 JSX 嵌表达式 | 放不了语句;渲染对象报错 |
| 条件渲染 | && / 三元 / 提前 return | && 左侧为 0 时渲染 “0” |
| 列表与 key | map 出元素数组 | 下标/随机数当 key 会串位 |
| className / style | 类名与内联样式 | style 收对象;class= 是 HTML 写法 |
| props | 组件输入契约,只读 | 改嵌套字段会被下次渲染覆盖 |
| children | 内容分发通道 | 内容主权在调用方,别改它 |
| Fragment | 无节点的单根包裹 | 短语法不能带 key |
| StrictMode | 开发期双调用检查器 | 双执行是特性不是 bug |
| Suspense | 未就绪子树的占位边界 | 只管”从无到有”的挂起 |
| React.lazy | 按需加载组件 | 参数是返回动态 import 的函数 |
| useState | 触发重渲染的状态 | 快照语义;必须新引用替换 |
| useEffect | 依赖变化后执行副作用 | 漏依赖=旧值;无依赖数组=每次跑 |
| useRef | 不触发渲染的可变容器 | 改 .current 界面不动 |
| useMemo | 缓存昂贵计算 | 先测再加;解决”算得贵” |
| useCallback | 缓存函数引用 | 不配 memo 用基本没收益 |
| useContext | 跨层级读共享值 | value 不稳定=全量重渲染 |
| 基础事件 | onClick/onChange 驼峰绑定 | onClick={fn()} 是立刻执行 |
| onSubmit | 拦表单提交 | 不 preventDefault 整页刷新 |
| 受控三件套 | 表单值收编进 state | checkbox 用 checked;缺 onChange 锁死 |
| 文件输入 | ref 直取 File 对象 | 无法受控;value 只读 |
边界与延伸
本字典只收基础层:useReducer、useId、useOptimistic、Actions 等进阶 API 与服务端组件语境不在收录范围,以官方 API 索引为准。执行模型的深潜——快照、闭包陷阱、依赖数组机制——在《React Hooks 心智模型》;重渲染时机与 memo/Profiler 实战在《React 渲染与重渲染》;全部概念的系统讲解见开头链接的《React 语法要点》。
版本核实(撰写时检索,2025-07):React 当前稳定版为 19.1.x;本字典收录的组件与 Hooks 均为稳定 API;React Compiler 处于 RC 阶段(2025-04 发布候选版),可自动完成大部分手动 memoization,条目内已注明其对 useMemo/useCallback 的影响。具体以官方文档为准。