react 剖析
使用脚手架搭建 react 项目
vite
1
| npm create vite@latest my-react-app
|
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
| const element = <h1 className="greeting">Hello, React!</h1>;
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 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> );
return ( <> <h1>标题</h1> <p>段落</p> </> );
|
所有标签必须闭合
1 2 3
| <img src="image.jpg" /> <br /> <input type="text" />
|
使用驼峰命名法
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 的两个主要用途:
- 访问 DOM 元素
- 存储不触发重渲染的可变值
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("");
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";
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);
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";
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> ); }
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 的使用场景
- 访问 DOM 元素
- 存储不触发重渲染的值(如定时器 ID、前一个 props 值等)
- 与第三方库集成(如视频播放器、图表库等)
1 2 3 4 5 6 7 8 9 10 11 12
| import { useRef, useEffect } from "react";
function App() { const ref = useRef(null);
useEffect(() => { console.log(ref.current); 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";
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 }) { const user = useUser(userId);
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";
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 36 37 38 39
| import { memo } from "react";
const User = memo( ({ user }) => { console.log("User 组件渲染"); return ( <div> <h3>{user.name}</h3> <p>{user.email}</p> </div> ); }, (prevProps, nextProps) => { 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 的使用场景
- 组件渲染开销大:组件包含复杂的计算或大量的 DOM 节点
- props 不经常变化:组件的 props 在多次渲染中保持不变
- 纯展示组件:组件只依赖 props,没有内部状态
注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { memo, useState } from "react";
function App() { const [count, setCount] = useState(0);
return ( <MemoChild data={{ value: count }} // 每次都是新对象 onClick={() => {}} // 每次都是新函数 /> ); }
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() { setCount((c) => c + 1); setFlag((f) => !f); }
setTimeout(() => { 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); });
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
| 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 29
| 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> ); }
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";
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 性能:
- 流式 SSR:服务器可以分块发送 HTML
- 选择性 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";
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
|
const SimpleButton = memo(({ label }) => <button>{label}</button>);
const ComplexList = memo(({ items }) => { return ( <ul> {items.map((item) => ( <ExpensiveItem key={item.id} data={item} /> ))} </ul> ); });
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
| 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
| 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
| 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
| 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
| 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); const count = useContext(StateContext).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
| 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; }
self.onmessage = (e) => { const result = heavyComputation(e.data); self.postMessage(result); };
|
总结
React 18 带来了并发渲染、自动批处理、Transitions 等强大特性,极大提升了应用性能和用户体验。在实际开发中,应该:
- 合理使用 Hooks:理解每个 Hook 的使用场景和最佳实践
- 性能优化:避免过早优化,先测量再优化
- 代码分割:使用 lazy 和 Suspense 减少初始加载时间
- 并发特性:利用 Transitions 和 useDeferredValue 优化用户体验
- 类型安全:使用 TypeScript 提高代码质量
- 测试:编写单元测试和集成测试确保代码可靠性
20260512 更新
react-compiler
官方中文文档:react-compiler
React-compiler是一个新的构建时工具,它可以自动优化你的 React 应用。在构建时分析你的组件代码,搞清楚「谁依赖谁」,然后在需要的地方自动插入 memoization,相当于帮你自动加 useMemo / useCallback / React.memo,不需要自己手写。
支持 react17/18/19
配置
配置文档:配置文档
1 2 3 4 5 6 7 8 9 10 11
| module.exports = { plugins: [ [ "babel-plugin-react-compiler", { }, ], ], };
|
scheduler 调度器
Scheduler的核心目标是 :在不阻塞浏览器主线程的前提下,按优先级有序推进任务执行 ,避免长任务导致页面掉帧。
- 双队列设计:内部维护两个任务队列,timerQueue 存放未到执行时间的延迟任务,taskQueue 存放已就绪的可执行任务,两个队列均基于最小堆实现,能快速取出优先级最高的任务。
- 优先级分级体系:内置 5 级任务优先级,对应不同的超时阈值:ImmediatePriority(-1ms)、UserBlockingPriority(250ms)、NormalPriority(5000ms)、LowPriority(10000ms)、IdlePriority(1073741823ms),优先级越高的任务越先被调度执行。
- 时间切片设计:默认单任务最大执行时长为 5ms,通过 shouldYieldToHost 判断是否超时,超时后立即中断任务,把主线程归还给浏览器完成 UI 渲染。
- 宏任务驱动循环:基于 MessageChannel 实现宏任务调度,替代不稳定的 requestIdleCallback,保证任务调度的可控性和兼容性。
reconciler 协调器
Reconciler是 React 更新流程的中枢,基于 Fiber 架构实现可中断的调和过程,负责完成新旧 UI 的差异计算。
- fiber 工作单元模型:把每个组件节点转化为独立的 Fiber 节点,用 child/sibling/return 链表结构替代传统递归遍历,将整个更新过程拆分为一个个可暂停、可恢复的小工作单元。
- 双缓存树机制:同时维护 current(当前已渲染到屏幕的 Fiber 树)和 workInProgress(正在后台计算的新 Fiber 树)两棵树,通过 alternate 指针双向关联,计算过程完全不影响当前页面显示,中断后可直接丢弃未完成的临时树。
- 可中断的 Render 阶段 :在内存中完成新旧 Fiber 树的 Diff 对比,为需要变更的节点打上 Placement/Update/Deletion 等副作用标记,整个过程是纯函数操作,不直接修改真实 DOM,支持被高优先级任务随时打断。
- 不可中断的 Commit 阶段 :将 Render 阶段标记的所有副作用一次性同步提交到宿主环境(如 DOM),这个过程无法中断,保证 UI 最终状态与组件状态完全一致。
调度器与协调器的关系
Scheduler 负责从宏观层面分配主线程时间,决定“什么时候执行任务、什么时候让出主线程”;Reconciler 负责在调度器分配的时间片内,完成 Fiber 树的遍历和 Diff 计算,二者配合让 React 实现了并发渲染能力,既保证了复杂场景下的页面流畅度,又能让用户交互等高优先级任务得到及时响应。
Fiber
Fiber 的本质是一个 普通的 JavaScript 对象 ,它是 React 内部用来表示组件节点、存储状态信息以及构建链表树结构的 工作单元(Work Unit)。
五大核心属性
基础身份属性
tag:标记 Fiber 的类型,如 HostRoot(根节点)、HostComponent(原生 DOM,如 div)、FunctionComponent(函数组件)、ClassComponent(类组件)等。React 根据 tag 决定如何处理该节点。
key:用于 Diff 算法中识别兄弟节点的身份,帮助 React 判断节点是移动、新增还是删除。
type:对应 JSX 中的元素类型。对于 DOM 节点是字符串(如 ‘div’),对于组件则是函数或类引用。
链表指针属性(核心)
这是 Fiber 能够替代递归栈、实现深度优先遍历(DFS)的关键。通过这三个指针,所有节点被串联成一个单向链表树。
child:指向第一个子 Fiber 节点。
sibling:指向下一个兄弟 Fiber 节点。
-
return:指向父 Fiber 节点。当子节点处理完毕或需要回溯时,通过 return 指针返回父级
遍历逻辑示例 :
React 从根节点开始,先处理 child;如果没有 child,则处理 sibling;如果也没有 sibling,则通过 return 回到父节点,继续寻找父节点的 sibling。这种结构使得遍历过程可以随时暂停(保存当前节点指针)和恢复。
双缓存与复用属性
为了实现“后台计算、前台显示”的双缓冲机制,Fiber 树有两棵:current 树(当前屏幕显示的)和 workInProgress 树(正在内存中计算的)。
-
alternate:这是一个双向指针。current 树中的节点通过 alternate 指向 workInProgress 树中对应的节点,反之亦然。如果节点在更新中没有变化,React 会直接复用 alternate 指向的旧 Fiber 对象,避免重新创建,极大节省内存。
-
pendingProps:即将应用的新 props。
memoizedProps:上一次渲染完成并已经应用到 DOM 上的 props。Diff 阶段会对比这两个值来判断是否需要更新。
状态与实例属性
stateNode:指向与该 Fiber 关联的真实实例。
- 对于 HostComponent(如 div),它指向真实的 DOM 节点。
- 对于 ClassComponent,它指向组件的类实例(this)。
- 对于 FunctionComponent,通常为 null(因为函数组件没有实例)。
- memoizedState:
- 对于类组件,存储当前的 state。
- 对于函数组件,存储 Hooks 链表(如 useState, useEffect 的状态)。
- updateQueue:存储待处理的更新队列(如 setState 产生的更新对象),在渲染阶段会被处理并合并到 state 中。
副作用与优先级属性
这些属性用于指导 Commit 阶段如何更新 DOM,以及 Scheduler 如何调度任务。
- flags (旧版本叫 effectTag):用位运算标记当前节点需要执行的操作,如 Placement(插入)、Update(更新)、Deletion(删除)。
- subtreeFlags:汇总子树中所有节点的 flags,方便快速判断子树是否有副作用,无需遍历整个子树。
- lanes:表示当前更新的优先级模型(React 18+)。不同的事件(如点击、输入、定时器)对应不同的 lane,高优先级的 lane 可以打断低优先级的渲染。
- childLanes:子树中所有未处理更新的优先级汇总,用于判断是否需要重新渲染子树。
简化版 Fiber 结构代码示意
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function FiberNode(tag, key, pendingProps) { this.tag = tag; this.key = key; this.type = null;
this.return = null; this.child = null; this.sibling = null;
this.alternate = null; this.pendingProps = pendingProps; this.memoizedProps = null; this.memoizedState = null; this.stateNode = null;
this.flags = 0; this.subtreeFlags = 0; this.lanes = 0; this.childLanes = 0; }
|
Fiber 结构的设计初衷就是为了 把庞大的递归任务拆解成一个个小的、可中断的对象 。
- 链表结构 (child/sibling/return) 让遍历不再依赖调用栈,可以随时暂停。
- 双缓存 (alternate) 让新旧树切换变得极快,且能复用节点。
- 优先级标记 (lanes/flags) 让 Scheduler 知道哪些任务紧急,哪些 DOM 操作需要执行。
react 性能优化
React 性能优化可以从减少不必要渲染、优化应用加载、利用新特性与工程手段这几个核心方向落地,覆盖从组件级到项目级的全链路优化方案。
减少不必要的组件渲染
- 使用记忆化高阶组件 :函数组件用
React.memo,类组件用 PureComponent 或自定义 shouldComponentUpdate,props 无变化时直接跳过重渲染。
- 稳定引用类型 props:用 useMemo 缓存耗时派生数据,用 useCallback 缓存事件函数,避免父组件更新时子组件因引用变化触发重渲染。
- 拆分大组件 :将复杂大组件拆分为多个职责单一的小组件,配合 memo 精确控制更新范围,避免父组件更新导致整树重绘。
- 合理使用 Context:拆分大体积 Context,避免单个 Context 更新触发所有消费者重渲染,传递稳定值给 memo 子组件。
- 避免内联对象/匿名函数 :渲染过程中不要直接创建内联对象、匿名函数作为 props,防止接收组件始终判定 props 变化而重渲染。
优化应用加载与列表性能
- 列表使用唯一 key:用数据 ID 作为列表 key,禁止使用数组索引,提升节点复用率,减少删除、重排场景下的 DOM 操作开销。
- 代码分割与懒加载 :通过 React.lazy 和 Suspense 按需加载路由或非首屏组件,大幅减少首屏 JS 体积。
- 开启包体积分析 :用 webpack-bundle-analyzer 或 vite-bundle-analyzer 可视化依赖占比,优先优化体积过大的第三方库。
- 避免请求瀑布流 :多个独立异步请求用 Promise.all 并行发起,减少串行等待带来的网络延迟。
- 优化第三方库导入 :避免桶文件全量导入,直接引入所需子模块,或通过框架配置开启自动优化,提升冷启动速度。
利用新特性与工程优化
- React 19 自动编译器 :开启后自动完成记忆化处理,无需手动编写大量 useMemo/useCallback/memo 代码。
- React 18 自动批处理 :将短时间内多次状态更新合并为一次渲染,无论在事件回调、异步场景下都能统一减少渲染次数。
- 生产环境构建 :部署时使用 React 生产版本,开启 JS/CSS 代码压缩,禁用调试相关代码。
- 延迟状态初始化 :将耗时的状态初始化逻辑包装成回调函数,确保仅在组件首次挂载时执行一次。
- 内存泄漏清理 :组件卸载时及时清除定时器、事件监听器等副作用,避免内存占用持续升高拖慢应用。
- 状态管理优化 :避免过度嵌套状态,选择轻量化状态管理方案,减少不必要的数据传递和跨组件重渲染。
- 性能瓶颈定位 :使用 React Profiler 工具,精准定位渲染耗时过长的组件,查看触发组件更新的具体原因。