一、React 基础三要素:JSX、组件、Props
1. JSX
使用方法
- 用类似 HTML 的语法写界面,在
{}中写 JavaScript 表达式。 - 属性用驼峰命名:
className、htmlFor。 - 必须有根标签,可用空标签
<></>。
js
const name = "Alice";
const el = <h1 className="title">Hello, {name}</h1>;
底层原理
JSX 会被 Babel 编译为 React.createElement(type, props, ...children) 调用,返回一个 虚拟 DOM 对象(描述 UI 的普通 JS 对象)
js
// 编译后
React.createElement('h1', { className: 'title' }, 'Hello, ', name);
2. 函数组件与 Props
使用方法
- 组件名首字母大写。
- 通过参数接收
props(只读对象)。
js
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 使用:<Welcome name="Bob" />
原理
组件是一个函数,返回虚拟 DOM。React 调用该函数,得到虚拟 DOM 并渲染。
二、State:组件自己的记忆
1. useState
使用方法
js
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
原理
useState在组件 fiber 节点上维护一个 Hook 链表,存储状态。setCount会创建一个更新对象,触发调度更新。- 批量更新 :React 18 中,同一事件处理函数内的多次
setState会被合并,只重渲染一次。 - 用函数形式更新可避免闭包陷阱:
setCount(prev => prev + 1)。
三、事件处理
使用方法
js
function App() {
function handleClick(e) {
e.preventDefault();
console.log('clicked');
}
return <button onClick={handleClick}>Click</button>;
}
原理
React 使用合成事件(SyntheticEvent) ,包裹原生事件,统一跨浏览器行为。事件挂载在根节点,通过事件委托触发。
四、条件渲染与列表渲染
使用方法
- 条件:
{isShow && <Comp />}、{isShow ? <A /> : <B />}。 - 列表:
arr.map(item => <li key={item.id}>{item.name}</li>)。
key 的作用和原理
- key 帮助 React 在 Diff 时识别节点是否可复用。
- 使用
index做 key 在顺序变化时会导致状态混乱(Diff 失败)。 - 底层:Diff 算法通过对比新旧虚拟 DOM 的 key 决定移动、复用或重建。
五、表单:受控与非受控
使用方法
受控组件:
js
const [value, setValue] = useState('');
<input value={value} onChange={e => setValue(e.target.value)} />
非受控组件:
js
const inputRef = useRef();
<input ref={inputRef} />
// 取值: inputRef.current.value
原理
- 受控:React state 作为"唯一数据源",输入值完全由 state 控制。
- 非受控:DOM 自身维护状态,通过 ref 读取。
六、Hooks 全面详解
1. useEffect ------ 处理副作用
使用方法
js
useEffect(() => {
const timer = setInterval(() => {...}, 1000);
return () => clearInterval(timer); // 清理函数
}, [deps]);
- 依赖数组:
[]只挂载执行;[count]count 变化执行;不传则每次渲染都执行。
原理
- React 在渲染完成后(绘制屏幕后)异步执行 effect。
- 清理函数在下一次 effect 执行前或组件卸载时运行。
- React 18 严格模式会故意挂载两次,帮助发现副作用问题。
2. useLayoutEffect ------ 同步执行
使用方法
js
useLayoutEffect(() => {
// 在浏览器绘制之前执行
}, []);
原理
- 执行时机:DOM 更新后,浏览器绘制前,同步执行。
- 通常用于读取布局信息并同步重绘,避免闪烁。
3. useContext ------ 跨组件数据
使用方法
js
const ThemeCtx = createContext('light');
function Child() {
const theme = useContext(ThemeCtx);
return <div>{theme}</div>;
}
function App() {
return (
<ThemeCtx.Provider value="dark">
<Child />
</ThemeCtx.Provider>
);
}
原理
- Provider 将值记录在 fiber 节点上,子组件通过
useContext直接读取,并自动订阅更新。 - 性能注意 :Provider value 变化会导致所有消费组件重渲染,可用
memo或拆分 Context 优化。
4. useReducer ------ 复杂状态
使用方法
js
const reducer = (state, action) => {
switch (action.type) {
case 'inc': return { count: state.count + 1 };
default: return state;
}
};
const [state, dispatch] = useReducer(reducer, { count: 0 });
// 调用:dispatch({ type: 'inc' });
原理
- 和 Redux 思想一致:派发 action,reducer 返回新状态。
dispatch是稳定引用,不会随渲染变化。- 底层
useState实际上就是用useReducer实现的。
5. useMemo / useCallback ------ 缓存
使用方法
js
const memoizedValue = useMemo(() => heavyCompute(a, b), [a, b]);
const memoizedFn = useCallback(() => { doSth(a, b); }, [a, b]);
原理
- 依赖不变时返回缓存的值/函数,避免子组件无意义重渲染(需配合
React.memo)。 useCallback是useMemo的语法糖:useMemo(() => fn, deps)。
6. useRef ------ 穿透渲染周期
使用方法
- 访问 DOM:
<input ref={ref} /> - 保存可变值:
ref.current = value,改变不会触发重渲染。
原理
ref对象在组件的整个生命周期保持不变。- 底层 fiber 节点上保存 ref 对象。
7. useImperativeHandle ------ 限制暴露的 ref
使用方法
js
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus() }));
return <input ref={inputRef} />;
});
// 父组件:childRef.current.focus()
8. 自定义 Hook ------ 逻辑复用
示例:useFetch
js
function useFetch(url) {
const [data, setData] = useState(null);
useEffect(() => {
let ignore = false;
fetch(url).then(res => res.json()).then(d => {
if (!ignore) setData(d);
});
return () => { ignore = true; };
}, [url]);
return data;
}
规则
- 命名以
use开头。 - 内部可以使用其他 Hooks。
七、虚拟 DOM 与 Diff 算法
原理
-
虚拟 DOM:用 JS 对象描述真实 DOM(类型、属性、子节点)。
-
Diff:对比新旧两棵虚拟 DOM 树,找出最小更新。
-
算法前提:
- 同层比较(O(n) 复杂度)。
- 不同类型节点直接替换。
key标识列表节点。
流程
- 根节点类型不同 → 销毁重建。
- 同类型元素 → 复用 DOM,仅更新属性。
- 同类型组件 → 实例不变,更新 props。
- 子节点:使用 key 进行双端比较(Vue 类似),React 也用类似策略。
八、Fiber 架构(React 16+)
为什么需要 Fiber?
- React 15 的 Stack Reconciler 递归不可中断,长任务会卡顿。
- Fiber 将工作拆分为小单元,可暂停、继续、放弃。
Fiber 节点
- 每个元素/组件都有对应的 fiber 节点,形成链表(child, sibling, return)。
- 双缓冲 :
current树(当前屏幕)和workInProgress树(在内存中构建)。
渲染过程
- 协调阶段(可中断) :构建 workInProgress 树,Diff 对比,标记副作用(增/删/改)。
- 提交阶段(不可中断) :同步将变更应用到真实 DOM。
时间切片
- 通过
requestIdleCallback(或 Scheduler 模拟)在空闲时执行工作单元。 - 每个单元执行后检查是否还有时间,无则让出主线程。
九、React 18 并发特性
1. 自动批处理
- 在事件、setTimeout、Promise 等所有更新中都默认批处理,减少渲染次数。
2. useTransition / startTransition
js
const [isPending, startTransition] = useTransition();
startTransition(() => {
setSearchQuery(input);
});
- 标记非紧急更新,可被更高优先级的更新中断(如用户输入)。
3. useDeferredValue
js
const deferredValue = useDeferredValue(value);
- 延迟渲染不紧急的值,让出主线程。
4. Suspense 增强
- 可配合数据获取,组件在数据准备好前显示 fallback。
十、状态管理
1. Redux Toolkit
使用方法
js
import { createSlice, configureStore } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
incremented: state => { state.value += 1; }
}
});
const store = configureStore({ reducer: counterSlice.reducer });
// 组件中使用 useSelector、useDispatch
原理
- 单一 store,状态不可变,通过 dispatch action → reducer 生成新状态。
- 内部用
useSyncExternalStore订阅 store 变化(React 18)。
2. Zustand
js
import { create } from 'zustand';
const useStore = create(set => ({
count: 0,
inc: () => set(state => ({ count: state.count + 1 }))
}));
// 组件中:const count = useStore(s => s.count);
- 无 Provider,直接选择状态,按需渲染。
十一、React Router v6
使用方法
js
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Link to="/user/123">User</Link>
<Routes>
<Route path="/user/:id" element={<User />} />
</Routes>
</BrowserRouter>
);
}
function User() {
const { id } = useParams();
return <div>{id}</div>;
}
- 编程式导航:
useNavigate()。
十二、Next.js 与 SSR
基本概念
- Server Component(默认):在服务器运行,无客户端 JS,可直接访问数据库。
- Client Component :
'use client'标记,可使用 hooks 和交互。
数据获取
js
// app/page.js (Server Component)
export default async function Page() {
const data = await fetch('https://...', { next: { revalidate: 60 } });
return <div>{data.title}</div>;
}
水合(Hydration)
- 服务端返回 HTML,客户端加载 JS 后,React 将事件绑定到已有 DOM,不重新生成。
十三、React 19 新特性
- use() :读取 Promise 或 Context,可中断渲染。
- Actions :
<form action={serverAction}>直接处理表单。 - useOptimistic:乐观更新。
- ref 作为普通 prop ,无需
forwardRef。 - 简化 Context 写法 :
<Context>即 Provider。
十四、性能优化
- React.memo:浅比较 props,无变化跳过渲染。
- useMemo / useCallback:缓存值/函数。
- 虚拟列表 :
react-window只渲染可视区。 - 代码分割 :
React.lazy(() => import('./Comp'))+<Suspense>。 - 避免内联对象/函数作为 props。
- 使用 Profiler 和 React DevTools 分析。
十五、测试
- Vitest + React Testing Library
js
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('button click', async () => {
render(<Counter />);
await userEvent.click(screen.getByText('+1'));
expect(screen.getByText('1')).toBeInTheDocument();
});