React 19 新特性详解 & useMemo/useCallback 深度解析
React 19 新特性(2024 年发布)
1. React Compiler(自动优化)- 最大变革
核心价值:不再需要手动使用 memo/useMemo/useCallback
// React 18 之前(需要手动优化)
const TodoList = React.memo(({ todos, filter }) => {
const filtered = useMemo(
() => todos.filter((todo) => todo.status === filter),
[todos, filter]
)
const handleClick = useCallback((id) => {
console.log(id)
}, [])
return <List items={filtered} onClick={handleClick} />
})
// React 19 + Compiler(自动优化)
function TodoList({ todos, filter }) {
const filtered = todos.filter((todo) => todo.status === filter)
const handleClick = (id) => console.log(id)
return <List items={filtered} onClick={handleClick} />
}
// 编译器自动插入必要的缓存逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
工作原理:
- 编译时分析组件代码
- 识别可以缓存的计算和函数
- 自动生成优化后的代码
- 类似 Svelte/Solid.js 的编译时优化
启用方式:
// babel.config.js
module.exports = {
plugins: [
[
'babel-plugin-react-compiler',
{
target: '18' // 可向下兼容 React 18
}
]
]
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
2. Actions(表单处理增强)
新增 Hooks:
useActionState- 处理表单提交和 loading 状态useFormStatus- 获取表单状态useOptimistic- 乐观更新 UI
示例对比:
// React 18 之前(手动管理状态)
function AddTodo() {
const [pending, setPending] = useState(false)
const [error, setError] = useState(null)
const handleSubmit = async (e) => {
e.preventDefault()
setPending(true)
setError(null)
try {
const formData = new FormData(e.target)
await addTodo(formData)
} catch (err) {
setError(err.message)
} finally {
setPending(false)
}
}
return (
<form onSubmit={handleSubmit}>
<input name="title" />
<button disabled={pending}>{pending ? 'Adding...' : 'Add'}</button>
{error && <p className="error">{error}</p>}
</form>
)
}
// React 19(使用 Actions)
function AddTodo() {
const [state, submitAction, isPending] = useActionState(
async (prevState, formData) => {
try {
await addTodo(formData)
return { success: true }
} catch (err) {
return { error: err.message }
}
},
{ success: false }
)
return (
<form action={submitAction}>
<input name="title" />
<button disabled={isPending}>{isPending ? 'Adding...' : 'Add'}</button>
{state.error && <p className="error">{state.error}</p>}
</form>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
乐观更新示例:
function TodoList({ todos }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, { ...newTodo, pending: true }]
)
const handleAdd = async (formData) => {
// 立即显示新 todo(带 pending 标记)
addOptimisticTodo({
id: Date.now(),
title: formData.get('title')
})
// 后台提交
await addTodo(formData)
}
return (
<>
{optimisticTodos.map((todo) => (
<div key={todo.id} className={todo.pending ? 'pending' : ''}>
{todo.title}
</div>
))}
<form action={handleAdd}>
<input name="title" />
<button>Add</button>
</form>
</>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
3. use() API(统一资源读取)
功能:
- 在组件中读取 Promise
- 读取 Context(可在条件语句中)
- 自动触发 Suspense
示例:
// 读取 Promise
function UserProfile({ userPromise }) {
const user = use(userPromise) // 等待 Promise 完成
return <div>{user.name}</div>
}
// 条件读取 Context(React 19 新能力)
function ThemedComponent({ isDark }) {
const theme = use(isDark ? DarkThemeContext : LightThemeContext)
return <div style={theme}>Content</div>
}
// 与 Suspense 配合
;<Suspense fallback={<Loading />}>
<UserProfile userPromise={fetchUser(123)} />
</Suspense>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
4. ref 作为 prop(简化 ref 传递)
不再需要 forwardRef:
// React 18(需要 forwardRef)
const Input = React.forwardRef((props, ref) => {
return <input ref={ref} {...props} />
})
// React 19(直接使用)
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />
}
// 使用方式不变
function Form() {
const inputRef = useRef()
return <Input ref={inputRef} />
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
5. Document Metadata(内置 SEO 支持)
组件中直接管理 <title>、<meta>:
// React 18(需要 react-helmet)
import { Helmet } from 'react-helmet'
function BlogPost({ post }) {
return (
<>
<Helmet>
<title>{post.title} - My Blog</title>
<meta name="description" content={post.excerpt} />
<meta property="og:title" content={post.title} />
</Helmet>
<article>{post.content}</article>
</>
)
}
// React 19(原生支持)
function BlogPost({ post }) {
return (
<>
<title>{post.title} - My Blog</title>
<meta name="description" content={post.excerpt} />
<meta property="og:title" content={post.title} />
<article>{post.content}</article>
</>
)
}
// React 自动提升到 <head> 中,无需额外配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
6. 资源预加载 API
新增函数:
preload()- 预加载资源preinit()- 预初始化脚本/样式prefetchDNS()- DNS 预解析preconnect()- 预连接
import { preload, preinit, prefetchDNS, preconnect } from 'react-dom'
function ProductPage({ productId }) {
useEffect(() => {
// 预加载图片
preload(`/products/${productId}/hero.jpg`, { as: 'image' })
// 预初始化分析脚本
preinit('/analytics.js', { as: 'script' })
// DNS 预解析
prefetchDNS('https://api.example.com')
// 预连接 CDN
preconnect('https://cdn.example.com')
}, [productId])
return <div>Product {productId}</div>
}
// 自动生成 <link rel="preload"> 等标签
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
7. Suspense 增强
更好的错误处理:
<Suspense
fallback={<Loading />}
onError={(error) => {
// 错误回调
logError(error)
}}
>
<AsyncComponent />
</Suspense>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
8. Context 性能优化
自动选择性订阅:
// React 18:所有消费者都会重渲染
const ThemeContext = createContext()
// React 19:自动优化,只有使用的值变化才重渲染
function Component() {
const { color } = useContext(ThemeContext) // 只订阅 color
return <div style={{ color }}>Text</div>
}
// 当 ThemeContext 的其他属性变化时,此组件不会重渲染
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
为什么需要 useMemo 和 useCallback?(深度解析)
问题根源:JavaScript 的引用类型特性
1. 基本问题演示
function Component() {
const config = { theme: 'dark' } // ⚠️ 每次渲染创建新对象
return <Child config={config} />
}
// 问题:
// 每次 Component 渲染,config 都是新对象(新的内存地址)
// Child 收到的 props.config 引用不同
// 即使值相同,React.memo 也会认为 props 变了
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
2. JavaScript 对象比较
// JavaScript 中引用类型比较的是内存地址
{} === {} // false(不同引用)
[] === [] // false
() => {} === () => {} // false
// 相同值,不同引用
const obj1 = { a: 1 };
const obj2 = { a: 1 };
obj1 === obj2; // false ❌
// 相同引用
const obj1 = { a: 1 };
const obj2 = obj1;
obj1 === obj2; // true ✅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
useMemo 的必要性
场景 1:避免子组件无效重渲染
// ❌ 问题代码
function Parent() {
const [count, setCount] = useState(0)
const config = { theme: 'dark' } // 每次都是新对象
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ExpensiveChild config={config} />
</>
)
}
const ExpensiveChild = React.memo(({ config }) => {
console.log('Child render') // 每次 Parent 更新都会打印
// 昂贵的渲染逻辑
return <div>{config.theme}</div>
})
// 问题分析:
// 1. 点击按钮,count 变化
// 2. Parent 重新渲染
// 3. config 是新对象(新引用)
// 4. ExpensiveChild 的 props.config 变了
// 5. 即使有 React.memo,也会重渲染
// ✅ 解决方案
function Parent() {
const [count, setCount] = useState(0)
const config = useMemo(() => ({ theme: 'dark' }), []) // 稳定引用
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ExpensiveChild config={config} /> {/* 不会重渲染 ✅ */}
</>
)
}
// 工作原理:
// 1. 首次渲染:useMemo 执行,返回 { theme: 'dark' },缓存
// 2. 后续渲染:依赖项 [] 没变,返回缓存的对象(同一引用)
// 3. ExpensiveChild 的 props.config 引用相同,跳过渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
场景 2:避免昂贵计算重复执行
// ❌ 问题代码
function DataTable({ data, sortBy }) {
// 每次渲染都执行,即使 data 和 sortBy 没变
const processedData = data
.sort((a, b) => a[sortBy] - b[sortBy]) // 排序
.filter((item) => item.active) // 过滤
.map((item) => ({
...item,
formatted: expensiveFormat(item) // 昂贵的格式化
}))
return <Table data={processedData} />
}
// 问题:
// - 即使 data 和 sortBy 不变,每次渲染都会重新计算
// - 如果 data 有 10000 条记录,性能损耗巨大
// ✅ 解决方案
function DataTable({ data, sortBy }) {
const processedData = useMemo(() => {
console.log('计算中...') // 只在依赖变化时打印
return data
.sort((a, b) => a[sortBy] - b[sortBy])
.filter((item) => item.active)
.map((item) => ({
...item,
formatted: expensiveFormat(item)
}))
}, [data, sortBy]) // 只在 data 或 sortBy 变化时重新计算
return <Table data={processedData} />
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
场景 3:避免 useEffect 无限循环
// ❌ 问题代码
function Component({ userId }) {
const options = { userId } // 每次都是新对象
useEffect(() => {
fetchData(options) // 每次渲染都执行
}, [options]) // options 每次都不同,无限循环!
return <div>Loading...</div>
}
// 执行流程:
// 1. 首次渲染:options = { userId: 1 }(引用 A)
// 2. useEffect 执行,fetchData
// 3. fetchData 可能触发状态更新
// 4. 组件重新渲染:options = { userId: 1 }(引用 B,虽然值相同)
// 5. useEffect 检测到 options 变化(引用 A !== 引用 B)
// 6. 再次执行 fetchData
// 7. 无限循环 💥
// ✅ 解决方案
function Component({ userId }) {
const options = useMemo(() => ({ userId }), [userId])
useEffect(() => {
fetchData(options) // 只在 userId 变化时执行
}, [options])
return <div>Loading...</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
useCallback 的必要性
场景 1:优化 React.memo 组件
// ❌ 问题代码
function Parent() {
const [count, setCount] = useState(0)
const handleClick = () => {
// 每次都是新函数
console.log('clicked')
}
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<MemoButton onClick={handleClick} /> {/* 总是重渲染 */}
</>
)
}
const MemoButton = React.memo(({ onClick }) => {
console.log('Button render')
return <button onClick={onClick}>Click me</button>
})
// 问题:
// - 每次 Parent 渲染,handleClick 都是新函数
// - MemoButton 的 props.onClick 引用变化
// - React.memo 失效
// ✅ 解决方案
function Parent() {
const [count, setCount] = useState(0)
const handleClick = useCallback(() => {
console.log('clicked')
}, []) // 稳定引用
return (
<>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<MemoButton onClick={handleClick} /> {/* 不重渲染 ✅ */}
</>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
场景 2:避免 useEffect 重复执行
// ❌ 问题代码
function Component({ id }) {
const fetchData = () => {
// 每次都是新函数
fetch(`/api/${id}`).then((res) => res.json())
}
useEffect(() => {
fetchData() // 每次渲染都执行
}, [fetchData]) // fetchData 每次都不同
return <div>Data for {id}</div>
}
// ✅ 解决方案
function Component({ id }) {
const fetchData = useCallback(() => {
fetch(`/api/${id}`).then((res) => res.json())
}, [id]) // 只在 id 变化时创建新函数
useEffect(() => {
fetchData() // 只在 id 变化时执行
}, [fetchData])
return <div>Data for {id}</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
场景 3:自定义 Hook 返回稳定函数
// ❌ 问题代码
function useDebounce(value, delay) {
const [debounced, setDebounced] = useState(value)
// 每次都是新函数,使用此 Hook 的组件会有问题
const updateDebounced = (newValue) => {
setTimeout(() => setDebounced(newValue), delay)
}
return [debounced, updateDebounced]
}
function Component() {
const [debounced, update] = useDebounce('', 500)
useEffect(() => {
// update 每次都不同,这里会有问题
doSomething(update)
}, [update])
}
// ✅ 解决方案
function useDebounce(value, delay) {
const [debounced, setDebounced] = useState(value)
const updateDebounced = useCallback(
(newValue) => {
setTimeout(() => setDebounced(newValue), delay)
},
[delay]
) // 稳定的函数引用
return [debounced, updateDebounced]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
何时不需要 useMemo/useCallback
1. 简单值和原始类型
// ❌ 不需要(浪费性能)
const count = useMemo(() => 1 + 1, [])
const name = useMemo(() => 'John', [])
const isActive = useMemo(() => true, [])
// ✅ 直接使用
const count = 1 + 1
const name = 'John'
const isActive = true
// 原因:原始类型按值比较,不存在引用问题
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
2. 函数仅在当前组件使用
// ❌ 不需要
function Component() {
const handleClick = useCallback(() => {
console.log('click')
}, [])
// 直接在本组件使用,不传递给子组件
return <button onClick={handleClick}>Click</button>
}
// ✅ 直接定义
function Component() {
const handleClick = () => {
console.log('click')
}
return <button onClick={handleClick}>Click</button>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3. 子组件没有使用 React.memo
// ❌ 不需要
function Parent() {
const config = useMemo(() => ({ theme: 'dark' }), [])
return <Child config={config} /> // Child 没有 memo
}
function Child({ config }) {
return <div>{config.theme}</div>
}
// ✅ 不需要优化
function Parent() {
const config = { theme: 'dark' }
return <Child config={config} />
}
// 原因:Child 没有 memo,每次都会渲染,缓存 config 无意义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
性能成本对比
// 原始值比较(极快)
1 === 1 // 直接比较值
// 对象引用比较(快)
obj1 === obj2 // 比较内存地址
// useMemo 的成本
// 1. 调用 useMemo 函数
// 2. 对比依赖数组(浅比较每个依赖)
// 3. 决定是返回缓存还是重新计算
// 如果计算本身很简单,useMemo 的成本反而更高!
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
React 19 的改变
// React 18(需要手动优化)
function Component({ data }) {
const processed = useMemo(() => processData(data), [data])
const handler = useCallback(() => handleClick(), [])
return <Child data={processed} onClick={handler} />
}
// React 19 + Compiler(自动优化)
function Component({ data }) {
const processed = processData(data) // 编译器自动缓存
const handler = () => handleClick() // 编译器自动稳定引用
return <Child data={processed} onClick={handler} />
}
// 优势:
// ✅ 代码更简洁
// ✅ 无需考虑依赖数组
// ✅ 更少的 bug
// ✅ 编译器比手动优化更智能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
总结
为什么需要 useMemo/useCallback?
- JavaScript 特性:引用类型每次创建都是新引用
- React 特性:通过引用比较(
===)判断 props 是否变化 - 性能问题:新引用导致不必要的重渲染或副作用执行
何时使用?
- ✅ 传递给
React.memo组件的 props - ✅ 作为 Hook 的依赖项(
useEffect、useMemo等) - ✅ 昂贵的计算(循环、递归、复杂运算)
- ✅ 自定义 Hook 返回的值/函数
何时不用?
- ❌ 简单计算(加减乘除、字符串拼接)
- ❌ 仅内部使用的函数
- ❌ 非
React.memo组件的 props - ❌ 原始类型值(number、string、boolean)
未来趋势(React 19+)
→ 编译器自动优化,开发者无需手动处理!
决策树:
需要优化吗?
├─ 有性能问题吗?
│ ├─ 否 → 不要优化 ✋
│ └─ 是 → 继续
├─ 是引用类型吗?
│ ├─ 否(原始类型)→ 不需要 useMemo ✋
│ └─ 是 → 继续
├─ 传递给 memo 组件或作为依赖项吗?
│ ├─ 否 → 不需要优化 ✋
│ └─ 是 → 继续
├─ 计算昂贵吗(对象/数组)?
│ ├─ 是 → 使用 useMemo ✅
│ └─ 否(函数)→ 使用 useCallback ✅
└─ 等待 React 19 → 编译器自动处理 🎉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
