react18剖析

react剖析

使用脚手架搭建 react 项目

vite

1
npm create vite@latest my-react-app --template react-ts

create-react-app

1
npx create-react-app my-react-app --template typescript

JSX

什么是 JSX

JSX(JavaScript XML)是一种 JavaScript 的语法扩展,允许我们在 JavaScript 代码中编写类似 HTML 的结构。它使得组件的结构更加直观和易于维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// JSX 示例
const element = <h1 className="greeting">Hello, React!</h1>;

// 带表达式的 JSX
const name = 'World';
const element = <h1>Hello, {name}!</h1>;

// 嵌套结构
const component = (
<div className="container">
<h1>标题</h1>
<p>这是一段文本</p>
</div>
);

JSX 编译原理

JSX 通过编译器(Babel 的 @babel/preset-react 或 plugin-transform-react-jsx)进行转换。

React 17 之前的转换方式:

1
2
3
4
5
6
7
8
9
// 源代码
const element = <h1 className="greeting">Hello, React!</h1>;

// 编译后(旧)
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, React!'
);

React 17+ 的新 JSX 转换:

1
2
3
4
5
6
7
8
9
// 源代码
const element = <h1 className="greeting">Hello, React!</h1>;

// 编译后(新)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', {
className: 'greeting',
children: 'Hello, React!'
});

在线转换地址:Babel 在线转换工具

JSX 规则

  1. 必须返回单个根元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 错误
    return (
    <h1>标题</h1>
    <p>段落</p>
    );

    // 正确 - 使用父元素包裹
    return (
    <div>
    <h1>标题</h1>
    <p>段落</p>
    </div>
    );

    // 正确 - 使用 Fragment
    return (
    <>
    <h1>标题</h1>
    <p>段落</p>
    </>
    );
  2. 所有标签必须闭合

    1
    2
    3
    <img src="image.jpg" />
    <br />
    <input type="text" />
  3. 使用驼峰命名法

    1
    2
    3
    <div className="container" onClick={handleClick}>
    <label htmlFor="input">标签</label>
    </div>

Hooks

Hooks 是 React 16.8 引入的特性,让函数组件能够使用状态和其他 React 特性。

useState

用于在函数组件中添加状态管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setCount(prev => prev - 1)}>减少</button>
</div>
);
}

重点:

  • 状态更新是异步的
  • 使用函数式更新可以获取最新状态:setCount(prev => prev + 1)
  • 初始值只在首次渲染时使用

useReducer

适合管理复杂的状态逻辑,类似于 Redux 的 reducer。

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
import { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error('Unknown action type');
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<div>
<p>计数: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</div>
);
}

useRef

创建一个可变的引用对象,用于访问 DOM 元素或存储不触发重渲染的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useRef, useEffect } from 'react';

function TextInput() {
const inputRef = useRef(null);
const renderCount = useRef(0);

useEffect(() => {
renderCount.current += 1;
inputRef.current.focus();
});

return (
<div>
<input ref={inputRef} type="text" />
<p>组件渲染次数: {renderCount.current}</p>
</div>
);
}

useRef 的两个主要用途:

  1. 访问 DOM 元素
  2. 存储不触发重渲染的可变值

useMemo

用于缓存计算结果,避免在每次渲染时进行昂贵的计算。

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
import { useMemo, useState } from 'react';

function ExpensiveComponent({ items }) {
const [filter, setFilter] = useState('');

// 只有当 items 或 filter 改变时才重新计算
const filteredItems = useMemo(() => {
console.log('过滤计算执行');
return items.filter(item =>
item.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]);

return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="搜索..."
/>
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}

useCallback

返回一个记忆化的回调函数,避免子组件不必要的重渲染。

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
import { useCallback, useState, memo } from 'react';

// 子组件使用 memo 包裹
const Button = memo(({ onClick, children }) => {
console.log('Button 渲染');
return <button onClick={onClick}>{children}</button>;
});

function Parent() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);

// 只有 count 改变时才创建新函数
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);

return (
<div>
<p>Count: {count}</p>
<p>Other: {other}</p>
<Button onClick={increment}>增加 Count</Button>
<button onClick={() => setOther(o => o + 1)}>增加 Other</button>
</div>
);
}

useMemo vs useCallback:

  • useMemo(() => fn) 缓存函数的返回值
  • useCallback(fn) 缓存函数本身

useContext

用于在组件树中共享数据,避免 props 层层传递。

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
import { createContext, useContext, useState } from 'react';

// 创建 Context
const ThemeContext = createContext();

function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');

const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};

return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}

// 使用 Context
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);

return (
<button
onClick={toggleTheme}
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
>
当前主题: {theme}
</button>
);
}

function App() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}

useEffect

处理副作用(数据获取、订阅、DOM 操作等)。

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
import { useEffect, useState } from 'react';

function DataFetcher({ userId }) {
const [data, setData] = useState(null);

useEffect(() => {
let cancelled = false;

async function fetchData() {
const response = await fetch(`/api/user/${userId}`);
const json = await response.json();

if (!cancelled) {
setData(json);
}
}

fetchData();

// 清理函数
return () => {
cancelled = true;
};
}, [userId]); // 依赖项数组

return <div>{data ? data.name : '加载中...'}</div>;
}

useLayoutEffect

与 useEffect 类似,但在所有 DOM 变更后同步触发,用于需要同步测量布局的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useLayoutEffect, useRef, useState } from 'react';

function MeasureComponent() {
const ref = useRef(null);
const [height, setHeight] = useState(0);

useLayoutEffect(() => {
// 在浏览器绘制前执行
setHeight(ref.current.offsetHeight);
}, []);

return (
<div ref={ref}>
<p>组件高度: {height}px</p>
</div>
);
}

ref

ref 不仅可以作为 DOM 的引用,还可以作为不需要引起视图更新的数据存储。

ref 的使用场景

  1. 访问 DOM 元素
  2. 存储不触发重渲染的值(如定时器 ID、前一个 props 值等)
  3. 与第三方库集成(如视频播放器、图表库等)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useRef, useEffect } from 'react';

function App() {
const ref = useRef(null);

useEffect(() => {
console.log(ref.current); // 输出 DOM 元素
ref.current.focus(); // 聚焦输入框
}, []);

return (
<div ref={ref}>
Ref demo
</div>
);
}

ref 的常见用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function VideoPlayer({ src }) {
const videoRef = useRef(null);

const play = () => {
videoRef.current.play();
};

const pause = () => {
videoRef.current.pause();
};

return (
<div>
<video ref={videoRef} src={src} />
<button onClick={play}>播放</button>
<button onClick={pause}>暂停</button>
</div>
);
}

forwardRef

forwardRef 允许组件将 ref 转发给子组件,使父组件能够访问子组件的 DOM 节点。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { forwardRef, useRef } from 'react';

// 子组件使用 forwardRef 包裹
const CustomInput = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});

// 父组件
function Form() {
const inputRef = useRef(null);

const handleFocus = () => {
inputRef.current.focus();
};

return (
<div>
<CustomInput ref={inputRef} placeholder="输入文本" />
<button onClick={handleFocus}>聚焦输入框</button>
</div>
);
}

结合 useImperativeHandle

使用 useImperativeHandle 可以自定义暴露给父组件的实例值。

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
import { forwardRef, useRef, useImperativeHandle } from 'react';

const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef(null);

useImperativeHandle(ref, () => ({
// 只暴露特定的方法
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
},
getValue: () => {
return inputRef.current.value;
}
}));

return <input ref={inputRef} {...props} />;
});

function App() {
const fancyInputRef = useRef(null);

const handleClick = () => {
fancyInputRef.current.focus();
console.log(fancyInputRef.current.getValue());
fancyInputRef.current.clear();
};

return (
<div>
<FancyInput ref={fancyInputRef} placeholder="自定义输入框" />
<button onClick={handleClick}>操作输入框</button>
</div>
);
}

Suspense

Suspense 允许你在组件树中指定加载状态,通常用于代码分割和数据获取。

与 lazy 配合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Suspense, lazy } from 'react';

// 懒加载组件
const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
return (
<div>
<h1>我的应用</h1>
<Suspense fallback={<div>加载中...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}

嵌套 Suspense

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Suspense, lazy } from 'react';

const Profile = lazy(() => import('./Profile'));
const Posts = lazy(() => import('./Posts'));

function App() {
return (
<Suspense fallback={<div>加载页面...</div>}>
<div>
<Suspense fallback={<div>加载个人资料...</div>}>
<Profile />
</Suspense>
<Suspense fallback={<div>加载文章...</div>}>
<Posts />
</Suspense>
</div>
</Suspense>
);
}

React 18 中的数据获取

React 18 支持在 Suspense 中进行数据获取(需要配合支持 Suspense 的数据获取库)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Suspense } from 'react';

function UserProfile({ userId }) {
// 使用支持 Suspense 的数据获取库(如 React Query、SWR 等)
const user = useUser(userId); // 这会在数据未准备好时 suspend

return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}

function App() {
return (
<Suspense fallback={<div>加载用户信息...</div>}>
<UserProfile userId={1} />
</Suspense>
);
}

lazy

React.lazy 用于动态导入组件,实现代码分割,减少初始加载时间。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { lazy, Suspense } from 'react';

// 动态导入组件
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));

function App() {
const [page, setPage] = useState('dashboard');

return (
<div>
<nav>
<button onClick={() => setPage('dashboard')}>仪表板</button>
<button onClick={() => setPage('settings')}>设置</button>
</nav>

<Suspense fallback={<div>加载中...</div>}>
{page === 'dashboard' && <Dashboard />}
{page === 'settings' && <Settings />}
</Suspense>
</div>
);
}

路由级别的代码分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));

function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>页面加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}

错误处理

结合 Error Boundary 处理加载失败的情况。

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
import { Component, lazy, Suspense } from 'react';

class ErrorBoundary extends Component {
state = { hasError: false };

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, info) {
console.error('组件加载失败:', error, info);
}

render() {
if (this.state.hasError) {
return <div>组件加载失败,请刷新页面重试</div>;
}
return this.props.children;
}
}

const LazyComponent = lazy(() => import('./LazyComponent'));

function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>加载中...</div>}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
);
}

memo

React.memo 是一个高阶组件,用于优化函数组件的性能,避免不必要的重渲染。

基本用法

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
import { memo, useState } from 'react';

// 使用 memo 包裹组件
const ExpensiveComponent = memo(({ value }) => {
console.log('ExpensiveComponent 渲染');
return <div>值: {value}</div>;
});

function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');

return (
<div>
<ExpensiveComponent value={count} />
<button onClick={() => setCount(count + 1)}>增加计数</button>

<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="输入不会触发 ExpensiveComponent 重渲染"
/>
</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
31
32
33
34
35
import { memo } from 'react';

const User = memo(
({ user }) => {
console.log('User 组件渲染');
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
},
(prevProps, nextProps) => {
// 返回 true 表示不需要重渲染
// 返回 false 表示需要重渲染
return prevProps.user.id === nextProps.user.id;
}
);

function App() {
const [user, setUser] = useState({ id: 1, name: '张三', email: 'zhang@example.com' });
const [count, setCount] = useState(0);

return (
<div>
<User user={user} />
<button onClick={() => setCount(count + 1)}>
计数: {count} (不会触发 User 重渲染)
</button>
<button onClick={() => setUser({ ...user, name: '李四' })}>
改变用户 (会触发 User 重渲染)
</button>
</div>
);
}

memo 的使用场景

  1. 组件渲染开销大:组件包含复杂的计算或大量的 DOM 节点
  2. props 不经常变化:组件的 props 在多次渲染中保持不变
  3. 纯展示组件:组件只依赖 props,没有内部状态

注意事项

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
import { memo, useState } from 'react';

// 错误示例:每次渲染都会创建新对象,导致 memo 失效
function App() {
const [count, setCount] = useState(0);

return (
<MemoChild
data={{ value: count }} // 每次都是新对象
onClick={() => {}} // 每次都是新函数
/>
);
}

// 正确示例:配合 useMemo 和 useCallback
function App() {
const [count, setCount] = useState(0);

const data = useMemo(() => ({ value: count }), [count]);
const handleClick = useCallback(() => {}, []);

return (
<MemoChild
data={data}
onClick={handleClick}
/>
);
}

React 18 新特性

React 18 引入了多项重大更新,提升了性能和用户体验。

并发渲染(Concurrent Rendering)

React 18 的核心特性,允许 React 同时准备多个版本的 UI,并在需要时中断渲染。

关键概念:

  • 可中断的渲染:React 可以暂停渲染工作,处理更高优先级的更新
  • 自动批处理:多个状态更新会自动批处理成一次重渲染
  • 过渡更新:区分紧急更新和非紧急更新

自动批处理(Automatic Batching)

React 18 会自动批处理所有状态更新,无论它们在哪里触发。

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
import { useState } from 'react';

function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);

function handleClick() {
// React 18: 自动批处理,只触发一次重渲染
setCount(c => c + 1);
setFlag(f => !f);
// 在 React 17 中,如果在 Promise、setTimeout 中,会触发两次渲染
}

setTimeout(() => {
// React 18: 这里也会批处理
setCount(c => c + 1);
setFlag(f => !f);
}, 1000);

console.log('渲染'); // 只输出一次

return (
<div>
<button onClick={handleClick}>
Count: {count}, Flag: {flag.toString()}
</button>
</div>
);
}

退出批处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { flushSync } from 'react-dom';

function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// 此时 DOM 已更新

flushSync(() => {
setFlag(f => !f);
});
// 又更新了一次
}

Transitions API

用于标记非紧急的状态更新,让 React 知道某些更新可以被中断。

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
import { useState, useTransition } from 'react';

function SearchResults() {
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);

function handleChange(e) {
const value = e.target.value;

// 紧急更新:立即更新输入框
setQuery(value);

// 非紧急更新:搜索结果可以延迟
startTransition(() => {
const filtered = largeList.filter(item =>
item.includes(value)
);
setResults(filtered);
});
}

return (
<div>
<input value={query} onChange={handleChange} />
{isPending ? (
<div>搜索中...</div>
) : (
<ul>
{results.map(item => (
<li key={item}>{item}</li>
))}
</ul>
)}
</div>
);
}

useDeferredValue

延迟更新某个值,让紧急更新优先处理。

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
import { useState, useDeferredValue, memo } from 'react';

function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);

return (
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
/>
{/* 输入框立即响应 */}
<p>当前输入: {text}</p>

{/* 列表渲染使用延迟的值 */}
<SlowList text={deferredText} />
</div>
);
}

const SlowList = memo(({ text }) => {
const items = [];
for (let i = 0; i < 250; i++) {
items.push(<li key={i}>{text} - {i}</li>);
}
return <ul>{items}</ul>;
});

useId

生成唯一的 ID,适用于服务端渲染。

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
import { useId } from 'react';

function NameFields() {
const id = useId();

return (
<div>
<label htmlFor={`${id}-firstName`}>名字:</label>
<input id={`${id}-firstName`} type="text" />

<label htmlFor={`${id}-lastName`}>姓氏:</label>
<input id={`${id}-lastName`} type="text" />
</div>
);
}

// 即使有多个组件实例,ID 也不会冲突
function App() {
return (
<>
<NameFields />
<NameFields />
</>
);
}

useSyncExternalStore

用于订阅外部数据源,确保在并发渲染中的一致性。

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
import { useSyncExternalStore } from 'react';

// 外部 store
const store = {
state: { count: 0 },
listeners: new Set(),

subscribe(listener) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
},

getSnapshot() {
return this.state;
},

increment() {
this.state = { count: this.state.count + 1 };
this.listeners.forEach(listener => listener());
}
};

function Counter() {
const state = useSyncExternalStore(
store.subscribe.bind(store),
store.getSnapshot.bind(store)
);

return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => store.increment()}>增加</button>
</div>
);
}

useInsertionEffect

专门用于 CSS-in-JS 库,在 DOM 变更前同步触发。

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
import { useInsertionEffect } from 'react';

function useCSS(rule) {
useInsertionEffect(() => {
// 在浏览器重新计算布局前插入样式
const style = document.createElement('style');
style.textContent = rule;
document.head.appendChild(style);

return () => {
document.head.removeChild(style);
};
}, [rule]);
}

function App() {
useCSS(`
.dynamic-class {
color: red;
font-size: 20px;
}
`);

return <div className="dynamic-class">动态样式</div>;
}

新的服务端渲染架构

React 18 改进了 SSR 性能:

  1. 流式 SSR:服务器可以分块发送 HTML
  2. 选择性 Hydration:优先水化用户正在交互的部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { renderToPipeableStream } from 'react-dom/server';

// 服务端
function handleRequest(req, res) {
const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
res.setHeader('Content-Type', 'text/html');
pipe(res);
}
});
}

// 客户端
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root'), <App />);

Strict Mode 的变化

React 18 的严格模式会在开发环境中双重调用某些函数,帮助发现副作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { StrictMode } from 'react';

// 在开发环境中,以下会被调用两次:
// - 组件函数体
// - useState、useMemo、useReducer 的初始化函数
// - useEffect、useLayoutEffect、useInsertionEffect 的 setup 函数

function App() {
return (
<StrictMode>
<MyComponent />
</StrictMode>
);
}

性能优化最佳实践

1. 合理使用 memo、useMemo、useCallback

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
// 避免过度优化
// 错误:对简单组件使用 memo
const SimpleButton = memo(({ label }) => <button>{label}</button>);

// 正确:对复杂组件使用 memo
const ComplexList = memo(({ items }) => {
return (
<ul>
{items.map(item => (
<ExpensiveItem key={item.id} data={item} />
))}
</ul>
);
});

// 正确:配合使用 useCallback
function Parent() {
const [count, setCount] = useState(0);

const handleClick = useCallback(() => {
console.log('Clicked');
}, []);

return <ComplexList items={items} onClick={handleClick} />;
}

2. 代码分割和懒加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { lazy, Suspense } from 'react';

// 路由级别分割
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));

// 组件级别分割
const HeavyChart = lazy(() => import('./HeavyChart'));

function App() {
const [showChart, setShowChart] = useState(false);

return (
<div>
<button onClick={() => setShowChart(true)}>显示图表</button>
{showChart && (
<Suspense fallback={<div>加载中...</div>}>
<HeavyChart />
</Suspense>
)}
</div>
);
}

3. 虚拟化长列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);

return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}

4. 避免不必要的渲染

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
// 使用 key 优化列表渲染
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
// 使用稳定的 ID 作为 key
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}

// 提取不变的内容到组件外
const STATIC_OPTIONS = [
{ value: '1', label: '选项 1' },
{ value: '2', label: '选项 2' }
];

function Select() {
return (
<select>
{STATIC_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
);
}

5. 使用 Transitions 优化用户体验

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
import { useState, useTransition } from 'react';

function App() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('home');

function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}

return (
<div>
<TabButton
isActive={tab === 'home'}
onClick={() => selectTab('home')}
>
首页
</TabButton>
<TabButton
isActive={tab === 'posts'}
onClick={() => selectTab('posts')}
>
文章 {isPending && '(加载中...)'}
</TabButton>

{tab === 'home' && <HomePage />}
{tab === 'posts' && <PostsPage />}
</div>
);
}

6. 图片优化

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
// 使用懒加载
function LazyImage({ src, alt }) {
return (
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
/>
);
}

// 响应式图片
function ResponsiveImage({ src, alt }) {
return (
<picture>
<source
media="(max-width: 600px)"
srcSet={`${src}-small.jpg`}
/>
<source
media="(max-width: 1200px)"
srcSet={`${src}-medium.jpg`}
/>
<img src={`${src}-large.jpg`} alt={alt} />
</picture>
);
}

7. 状态管理优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 使用 Context 时避免不必要的重渲染
const StateContext = createContext();
const DispatchContext = createContext();

function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}

// 组件只订阅需要的部分
function Component() {
const dispatch = useContext(DispatchContext); // 不会因 state 变化而重渲染
const count = useContext(StateContext).count; // 只在 count 变化时重渲染

return <div onClick={() => dispatch({ type: 'INCREMENT' })}>{count}</div>;
}

8. 使用 Web Workers 处理复杂计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { useEffect, useState } from 'react';

function useWorker(workerFn) {
const [result, setResult] = useState(null);

useEffect(() => {
const worker = new Worker(
new URL('./worker.js', import.meta.url)
);

worker.onmessage = (e) => setResult(e.data);

return () => worker.terminate();
}, []);

return result;
}

// worker.js
self.onmessage = (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
};

总结

React 18 带来了并发渲染、自动批处理、Transitions 等强大特性,极大提升了应用性能和用户体验。在实际开发中,应该:

  1. 合理使用 Hooks:理解每个 Hook 的使用场景和最佳实践
  2. 性能优化:避免过早优化,先测量再优化
  3. 代码分割:使用 lazy 和 Suspense 减少初始加载时间
  4. 并发特性:利用 Transitions 和 useDeferredValue 优化用户体验
  5. 类型安全:使用 TypeScript 提高代码质量
  6. 测试:编写单元测试和集成测试确保代码可靠性

react18剖析
https://zouhualu.github.io/20250321/react18剖析/
作者
花鹿
发布于
2025年3月21日
许可协议