学习目标
- 理解React 18并发特性
- 掌握useTransition和useDeferredValue
- 学会使用Suspense改进
- 理解自动批处理
- 掌握React 18最佳实践
学习时间安排
总时长:8-9小时
- React 18新特性概述:1小时
- 并发特性深入:2.5小时
- Suspense改进:1.5小时
- 自动批处理:1小时
- 最佳实践:1.5小时
- 实践项目:2-3小时
第一部分:React 18新特性概述 (1小时)
1.1 React 18主要变化
版本升级说明(详细注释版)
javascript
// React 18的主要变化包括:
// 1. 并发渲染(Concurrent Rendering)
// 2. 自动批处理(Automatic Batching)
// 3. Suspense改进
// 4. 新的Hooks:useTransition、useDeferredValue、useId、useSyncExternalStore
// 5. 服务端渲染改进
// 升级到React 18的步骤
// 1. 安装React 18
// npm install react@18 react-dom@18
// 2. 更新入口文件
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// React 18使用createRoot API替代render
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// 3. 更新TypeScript类型(如果使用TypeScript)
// npm install --save-dev @types/react@18 @types/react-dom@18
1.2 并发渲染基础
并发渲染概念(详细注释版)
javascript
// 并发渲染是React 18的核心特性
// 它允许React中断、暂停、恢复渲染工作
// 这使得React能够优先处理更重要的更新
// 传统渲染(同步)
// React会一次性完成所有更新,无法中断
// 如果更新耗时较长,会阻塞用户交互
// 并发渲染(异步)
// React可以将更新分解为多个小任务
// 可以在任务之间暂停,处理用户交互
// 然后继续完成剩余的更新
// 示例:传统渲染的问题
function TraditionalComponent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
// 这个更新会阻塞UI
const handleUpdate = () => {
setCount(count + 1);
// 大量数据更新会阻塞
setItems(generateLargeArray(10000));
};
return (
<div>
<button onClick={handleUpdate}>Update</button>
<p>Count: {count}</p>
<List items={items} />
</div>
);
}
// 示例:并发渲染的解决方案
function ConcurrentComponent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
const [isPending, startTransition] = useTransition();
// 使用startTransition标记非紧急更新
const handleUpdate = () => {
setCount(count + 1);
startTransition(() => {
// 这个更新可以被中断
setItems(generateLargeArray(10000));
});
};
return (
<div>
<button onClick={handleUpdate} disabled={isPending}>
{isPending ? 'Updating...' : 'Update'}
</button>
<p>Count: {count}</p>
<List items={items} />
</div>
);
}
第二部分:并发特性深入 (2.5小时)
2.1 useTransition Hook
useTransition基础使用(详细注释版)
javascript
// src/hooks/useTransitionExample.js
// 导入React和useTransition
import React, { useState, useTransition } from 'react';
// 定义使用useTransition的组件
function SearchResults({ query }) {
// 使用useState Hook管理结果
const [results, setResults] = useState([]);
// 使用useTransition Hook
// isPending表示是否有待处理的过渡更新
// startTransition用于标记非紧急更新
const [isPending, startTransition] = useTransition();
// 搜索函数
const performSearch = (searchQuery) => {
// 使用startTransition包装非紧急更新
startTransition(() => {
// 这个更新可以被中断
// 如果用户继续输入,React会中断这个更新
const filteredResults = expensiveSearch(searchQuery);
setResults(filteredResults);
});
};
// 当查询变化时执行搜索
React.useEffect(() => {
if (query) {
performSearch(query);
}
}, [query]);
return (
<div>
{isPending && <div>Searching...</div>}
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}
// 定义主搜索组件
function SearchApp() {
const [query, setQuery] = useState('');
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<SearchResults query={query} />
</div>
);
}
// 模拟昂贵的搜索操作
function expensiveSearch(query) {
// 模拟大量数据处理
const allItems = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`
}));
return allItems.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}
// 导出组件
export default SearchApp;
useTransition高级用法(详细注释版)
javascript
// src/components/TransitionExample.js
// 导入React和useTransition
import React, { useState, useTransition, useDeferredValue } from 'react';
// 定义使用过渡的列表组件
function TransitionList({ items }) {
// 使用useTransition Hook
const [isPending, startTransition] = useTransition();
// 使用useState Hook管理过滤后的项目
const [filteredItems, setFilteredItems] = useState(items);
// 处理过滤
const handleFilter = (filterValue) => {
// 使用startTransition标记非紧急更新
startTransition(() => {
const filtered = items.filter(item =>
item.name.toLowerCase().includes(filterValue.toLowerCase())
);
setFilteredItems(filtered);
});
};
return (
<div>
<input
type="text"
placeholder="Filter items..."
onChange={(e) => handleFilter(e.target.value)}
/>
{isPending && <div>Filtering...</div>}
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
// 定义使用多个过渡的组件
function MultipleTransitions() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
const [isPending, startTransition] = useTransition();
// 处理紧急更新
const handleUrgentUpdate = () => {
// 这个更新是紧急的,不使用startTransition
setCount(count + 1);
};
// 处理非紧急更新
const handleNonUrgentUpdate = () => {
// 使用startTransition标记非紧急更新
startTransition(() => {
// 这个更新可以被中断
const newItems = generateLargeArray(10000);
setItems(newItems);
});
};
return (
<div>
<div>
<p>Count: {count}</p>
<button onClick={handleUrgentUpdate}>Increment (Urgent)</button>
</div>
<div>
<button onClick={handleNonUrgentUpdate} disabled={isPending}>
{isPending ? 'Loading...' : 'Load Items (Non-Urgent)'}
</button>
<p>Items: {items.length}</p>
</div>
</div>
);
}
// 生成大量数据
function generateLargeArray(count) {
return Array.from({ length: count }, (_, i) => ({
id: i,
name: `Item ${i}`,
value: Math.random() * 100
}));
}
// 导出组件
export { TransitionList, MultipleTransitions };
2.2 useDeferredValue Hook
useDeferredValue基础使用(详细注释版)
javascript
// src/components/DeferredValueExample.js
// 导入React和useDeferredValue
import React, { useState, useDeferredValue, useMemo } from 'react';
// 定义使用useDeferredValue的组件
function DeferredSearchResults({ query }) {
// 使用useDeferredValue延迟更新值
// 这个值会延迟更新,允许更紧急的更新先完成
const deferredQuery = useDeferredValue(query);
// 使用useMemo记忆化搜索结果
// 只有当deferredQuery变化时才重新计算
const results = useMemo(() => {
if (!deferredQuery) return [];
// 模拟昂贵的搜索操作
return expensiveSearch(deferredQuery);
}, [deferredQuery]);
// 检查值是否过时
const isStale = query !== deferredQuery;
return (
<div>
{isStale && <div>Searching for "{query}"...</div>}
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}
// 定义主搜索组件
function DeferredSearchApp() {
const [query, setQuery] = useState('');
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<DeferredSearchResults query={query} />
</div>
);
}
// 模拟昂贵的搜索操作
function expensiveSearch(query) {
const allItems = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`
}));
return allItems.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}
// 定义使用useDeferredValue的列表组件
function DeferredList({ items }) {
const [filter, setFilter] = useState('');
// 延迟过滤值
const deferredFilter = useDeferredValue(filter);
// 使用useMemo记忆化过滤结果
const filteredItems = useMemo(() => {
if (!deferredFilter) return items;
return items.filter(item =>
item.name.toLowerCase().includes(deferredFilter.toLowerCase())
);
}, [items, deferredFilter]);
// 检查值是否过时
const isStale = filter !== deferredFilter;
return (
<div>
<input
type="text"
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Filter items..."
/>
{isStale && <div>Filtering...</div>}
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
// 导出组件
export { DeferredSearchApp, DeferredList };
2.3 useId Hook
useId基础使用(详细注释版)
javascript
// src/components/UseIdExample.js
// 导入React和useId
import React, { useId } from 'react';
// 定义使用useId的表单组件
function FormWithId() {
// 使用useId生成唯一ID
// 这个ID在服务端和客户端渲染时保持一致
const nameId = useId();
const emailId = useId();
const passwordId = useId();
return (
<form>
<div>
<label htmlFor={nameId}>Name:</label>
<input type="text" id={nameId} name="name" />
</div>
<div>
<label htmlFor={emailId}>Email:</label>
<input type="email" id={emailId} name="email" />
</div>
<div>
<label htmlFor={passwordId}>Password:</label>
<input type="password" id={passwordId} name="password" />
</div>
</form>
);
}
// 定义使用useId的复选框组件
function CheckboxWithId({ label, checked, onChange }) {
// 使用useId生成唯一ID
const id = useId();
return (
<div>
<input
type="checkbox"
id={id}
checked={checked}
onChange={onChange}
/>
<label htmlFor={id}>{label}</label>
</div>
);
}
// 定义使用useId的多个输入组件
function MultipleInputs() {
// 使用useId生成基础ID
const baseId = useId();
return (
<div>
<div>
<label htmlFor={`${baseId}-first`}>First Name:</label>
<input type="text" id={`${baseId}-first`} />
</div>
<div>
<label htmlFor={`${baseId}-last`}>Last Name:</label>
<input type="text" id={`${baseId}-last`} />
</div>
</div>
);
}
// 导出组件
export { FormWithId, CheckboxWithId, MultipleInputs };
2.4 useSyncExternalStore Hook
useSyncExternalStore基础使用(详细注释版)
javascript
// src/hooks/useSyncExternalStoreExample.js
// 导入React和useSyncExternalStore
import React, { useSyncExternalStore } from 'react';
// 定义外部存储类
class ExternalStore {
constructor(initialState = {}) {
this.state = initialState;
this.listeners = new Set();
}
// 获取当前状态
getSnapshot() {
return this.state;
}
// 订阅状态变化
subscribe(listener) {
this.listeners.add(listener);
// 返回取消订阅函数
return () => {
this.listeners.delete(listener);
};
}
// 更新状态
setState(newState) {
this.state = { ...this.state, ...newState };
// 通知所有订阅者
this.listeners.forEach(listener => listener());
}
}
// 创建全局存储实例
const globalStore = new ExternalStore({ count: 0, name: 'Initial' });
// 定义使用useSyncExternalStore的组件
function SyncExternalStoreComponent() {
// 使用useSyncExternalStore Hook
// 这个Hook用于订阅外部数据源
const state = useSyncExternalStore(
// 订阅函数
(listener) => globalStore.subscribe(listener),
// 获取快照函数
() => globalStore.getSnapshot(),
// 服务端快照函数(可选)
() => globalStore.getSnapshot()
);
// 处理增加计数
const handleIncrement = () => {
globalStore.setState({ count: state.count + 1 });
};
// 处理更新名称
const handleNameChange = (newName) => {
globalStore.setState({ name: newName });
};
return (
<div>
<h2>External Store Component</h2>
<p>Count: {state.count}</p>
<p>Name: {state.name}</p>
<button onClick={handleIncrement}>Increment</button>
<input
type="text"
value={state.name}
onChange={(e) => handleNameChange(e.target.value)}
placeholder="Enter name"
/>
</div>
);
}
// 定义使用多个外部存储的组件
function MultipleStoresComponent() {
// 创建多个存储实例
const store1 = React.useMemo(() => new ExternalStore({ value: 0 }), []);
const store2 = React.useMemo(() => new ExternalStore({ value: 0 }), []);
// 订阅多个存储
const state1 = useSyncExternalStore(
(listener) => store1.subscribe(listener),
() => store1.getSnapshot()
);
const state2 = useSyncExternalStore(
(listener) => store2.subscribe(listener),
() => store2.getSnapshot()
);
return (
<div>
<div>
<p>Store 1: {state1.value}</p>
<button onClick={() => store1.setState({ value: state1.value + 1 })}>
Increment Store 1
</button>
</div>
<div>
<p>Store 2: {state2.value}</p>
<button onClick={() => store2.setState({ value: state2.value + 1 })}>
Increment Store 2
</button>
</div>
</div>
);
}
// 导出组件和存储类
export { SyncExternalStoreComponent, MultipleStoresComponent, ExternalStore };
第三部分:Suspense改进 (1.5小时)
3.1 Suspense基础
Suspense基础使用(详细注释版)
javascript
// src/components/SuspenseExample.js
// 导入React和Suspense
import React, { Suspense, lazy } from 'react';
// 使用lazy函数动态导入组件
// 这些组件会被代码分割,只在需要时加载
const LazyComponent = lazy(() => import('./LazyComponent'));
const AnotherLazyComponent = lazy(() => import('./AnotherLazyComponent'));
// 定义加载中组件
function LoadingFallback() {
return (
<div className="loading-fallback">
<div className="spinner"></div>
<p>Loading component...</p>
</div>
);
}
// 定义使用Suspense的组件
function SuspenseApp() {
const [showComponent, setShowComponent] = React.useState(false);
return (
<div>
<button onClick={() => setShowComponent(!showComponent)}>
{showComponent ? 'Hide' : 'Show'} Lazy Component
</button>
{/* 使用Suspense包装懒加载组件 */}
{/* fallback属性指定加载时显示的组件 */}
<Suspense fallback={<LoadingFallback />}>
{showComponent && <LazyComponent />}
</Suspense>
</div>
);
}
// 定义嵌套Suspense组件
function NestedSuspense() {
return (
<div>
<Suspense fallback={<div>Loading main content...</div>}>
<MainContent />
<Suspense fallback={<div>Loading sidebar...</div>}>
<Sidebar />
</Suspense>
</Suspense>
</div>
);
}
// 定义主内容组件
function MainContent() {
return <div>Main Content</div>;
}
// 定义侧边栏组件
function Sidebar() {
return <div>Sidebar</div>;
}
// 导出组件
export { SuspenseApp, NestedSuspense };
3.2 Suspense与数据获取
Suspense数据获取(详细注释版)
javascript
// src/components/SuspenseDataFetching.js
// 导入React和Suspense
import React, { Suspense, useState, useTransition } from 'react';
// 定义数据获取函数
// 这个函数会抛出Promise,Suspense会捕获它
function fetchUserData(userId) {
let status = 'pending';
let result;
const promise = fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(data => {
status = 'success';
result = data;
})
.catch(error => {
status = 'error';
result = error;
});
return {
read() {
if (status === 'pending') {
throw promise;
} else if (status === 'error') {
throw result;
} else {
return result;
}
}
};
}
// 定义使用Suspense的用户组件
function UserProfile({ userId }) {
// 读取用户数据
// 如果数据还在加载,会抛出Promise,Suspense会捕获
const userData = fetchUserData(userId).read();
return (
<div>
<h2>{userData.name}</h2>
<p>Email: {userData.email}</p>
<p>Phone: {userData.phone}</p>
</div>
);
}
// 定义使用Suspense的主组件
function SuspenseDataApp() {
const [userId, setUserId] = useState(1);
const [isPending, startTransition] = useTransition();
// 处理用户ID变化
const handleUserIdChange = (newUserId) => {
startTransition(() => {
setUserId(newUserId);
});
};
return (
<div>
<div>
<button onClick={() => handleUserIdChange(1)}>User 1</button>
<button onClick={() => handleUserIdChange(2)}>User 2</button>
<button onClick={() => handleUserIdChange(3)}>User 3</button>
</div>
{isPending && <div>Loading user...</div>}
{/* 使用Suspense包装数据获取组件 */}
<Suspense fallback={<div>Loading user profile...</div>}>
<UserProfile userId={userId} />
</Suspense>
</div>
);
}
// 定义错误边界组件
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong</h2>
<p>{this.state.error?.message}</p>
</div>
);
}
return this.props.children;
}
}
// 定义带错误边界的Suspense组件
function SuspenseWithErrorBoundary() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userId={1} />
</Suspense>
</ErrorBoundary>
);
}
// 导出组件
export { SuspenseDataApp, SuspenseWithErrorBoundary };
第四部分:自动批处理 (1小时)
4.1 自动批处理概念
自动批处理示例(详细注释版)
javascript
// src/components/AutomaticBatching.js
// 导入React和useState
import React, { useState, useEffect } from 'react';
// 定义使用自动批处理的组件
function AutomaticBatchingExample() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
const [name, setName] = useState('');
// 在React 18中,所有状态更新都会自动批处理
// 无论它们是在事件处理函数、Promise、setTimeout等中
const handleClick = () => {
// 这些更新会被批处理,只会触发一次重新渲染
setCount(count + 1);
setFlag(!flag);
setName('Updated');
// 在React 18之前,这可能会触发多次重新渲染
// 在React 18中,只会触发一次重新渲染
};
// 在Promise中的更新也会被批处理
const handleAsyncUpdate = () => {
fetch('/api/data')
.then(() => {
// 这些更新会被批处理
setCount(count + 1);
setFlag(!flag);
setName('Async Updated');
});
};
// 在setTimeout中的更新也会被批处理
const handleTimeoutUpdate = () => {
setTimeout(() => {
// 这些更新会被批处理
setCount(count + 1);
setFlag(!flag);
setName('Timeout Updated');
}, 1000);
};
// 如果需要立即应用更新,可以使用flushSync
const handleImmediateUpdate = () => {
// 导入flushSync
const { flushSync } = require('react-dom');
// 立即应用这个更新
flushSync(() => {
setCount(count + 1);
});
// 这个更新会在下一个渲染周期应用
setFlag(!flag);
};
return (
<div>
<h2>Automatic Batching Example</h2>
<p>Count: {count}</p>
<p>Flag: {flag ? 'true' : 'false'}</p>
<p>Name: {name}</p>
<button onClick={handleClick}>Batch Update</button>
<button onClick={handleAsyncUpdate}>Async Batch Update</button>
<button onClick={handleTimeoutUpdate}>Timeout Batch Update</button>
<button onClick={handleImmediateUpdate}>Immediate Update</button>
</div>
);
}
// 定义使用useEffect的批处理示例
function BatchingWithEffect() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
// useEffect中的更新也会被批处理
useEffect(() => {
// 这些更新会被批处理
setCount(1);
setFlag(true);
}, []);
return (
<div>
<p>Count: {count}</p>
<p>Flag: {flag ? 'true' : 'false'}</p>
</div>
);
}
// 导出组件
export { AutomaticBatchingExample, BatchingWithEffect };
第五部分:最佳实践 (1.5小时)
5.1 组件设计最佳实践
组件设计模式(详细注释版)
javascript
// src/components/BestPractices.js
// 导入React
import React, { memo, useMemo, useCallback } from 'react';
// 最佳实践1:单一职责原则
// 每个组件应该只负责一个功能
function UserCard({ user }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
// 最佳实践2:使用memo优化组件
// 只有当props变化时才重新渲染
const OptimizedUserCard = memo(function OptimizedUserCard({ user, onEdit }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
});
// 最佳实践3:使用useMemo记忆化计算结果
function ExpensiveCalculation({ data }) {
// 使用useMemo记忆化计算结果
const result = useMemo(() => {
// 昂贵的计算
return data.reduce((sum, item) => sum + item.value, 0);
}, [data]);
return <div>Result: {result}</div>;
}
// 最佳实践4:使用useCallback记忆化函数
function ParentComponent({ items }) {
// 使用useCallback记忆化函数
const handleItemClick = useCallback((itemId) => {
console.log('Item clicked:', itemId);
}, []);
return (
<div>
{items.map(item => (
<ChildComponent
key={item.id}
item={item}
onClick={handleItemClick}
/>
))}
</div>
);
}
// 最佳实践5:提取自定义Hook
function useUserData(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]);
return { user, loading, error };
}
// 最佳实践6:使用组合而非继承
function Card({ children, title }) {
return (
<div className="card">
{title && <h2>{title}</h2>}
<div className="card-content">
{children}
</div>
</div>
);
}
function CardHeader({ children }) {
return <div className="card-header">{children}</div>;
}
function CardBody({ children }) {
return <div className="card-body">{children}</div>;
}
function CardFooter({ children }) {
return <div className="card-footer">{children}</div>;
}
// 使用组合
function ComposedCard() {
return (
<Card title="User Profile">
<CardHeader>
<h3>Header</h3>
</CardHeader>
<CardBody>
<p>Body content</p>
</CardBody>
<CardFooter>
<button>Action</button>
</CardFooter>
</Card>
);
}
// 导出组件
export {
UserCard,
OptimizedUserCard,
ExpensiveCalculation,
ParentComponent,
useUserData,
Card,
CardHeader,
CardBody,
CardFooter,
ComposedCard
};
5.2 状态管理最佳实践
状态管理模式(详细注释版)
javascript
// src/hooks/useStateManagement.js
// 导入React和useState
import React, { useState, useReducer, useCallback } from 'react';
// 最佳实践1:使用useReducer管理复杂状态
const initialState = {
items: [],
filter: 'all',
sortBy: 'name',
loading: false,
error: null
};
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
items: [...state.items, action.payload]
};
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.payload)
};
case 'SET_FILTER':
return {
...state,
filter: action.payload
};
case 'SET_SORT':
return {
...state,
sortBy: action.payload
};
case 'SET_LOADING':
return {
...state,
loading: action.payload
};
case 'SET_ERROR':
return {
...state,
error: action.payload
};
default:
return state;
}
}
function TodoApp() {
const [state, dispatch] = useReducer(todoReducer, initialState);
const addItem = useCallback((item) => {
dispatch({ type: 'ADD_ITEM', payload: item });
}, []);
const removeItem = useCallback((id) => {
dispatch({ type: 'REMOVE_ITEM', payload: id });
}, []);
const setFilter = useCallback((filter) => {
dispatch({ type: 'SET_FILTER', payload: filter });
}, []);
return (
<div>
{/* 组件内容 */}
</div>
);
}
// 最佳实践2:状态提升
function ParentComponent() {
const [sharedState, setSharedState] = useState('');
return (
<div>
<ChildComponent1
value={sharedState}
onChange={setSharedState}
/>
<ChildComponent2
value={sharedState}
onChange={setSharedState}
/>
</div>
);
}
// 最佳实践3:使用Context共享状态
const AppContext = React.createContext();
function AppProvider({ children }) {
const [state, setState] = useState({});
const value = {
state,
setState,
updateState: (updates) => {
setState(prev => ({ ...prev, ...updates }));
}
};
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
// 导出
export { TodoApp, ParentComponent, AppProvider, AppContext };
第六部分:实践项目(详细注释版)
项目:高性能搜索应用
主应用组件(详细注释版)
javascript
// src/App.js
// 导入React和Suspense
import React, { Suspense, useState, useTransition } from 'react';
// 导入组件
import SearchInput from './components/SearchInput';
import SearchResults from './components/SearchResults';
import LoadingFallback from './components/LoadingFallback';
// 导入样式
import './App.css';
// 定义主应用组件
function App() {
// 使用useState Hook管理搜索查询
const [query, setQuery] = useState('');
// 使用useTransition Hook管理过渡状态
const [isPending, startTransition] = useTransition();
// 处理搜索查询变化
const handleQueryChange = (newQuery) => {
// 使用startTransition标记非紧急更新
startTransition(() => {
setQuery(newQuery);
});
};
return (
<div className="App">
<header className="App-header">
<h1>High Performance Search App</h1>
</header>
<main className="App-main">
{/* 搜索输入组件 */}
<SearchInput
value={query}
onChange={handleQueryChange}
isPending={isPending}
/>
{/* 使用Suspense包装搜索结果 */}
<Suspense fallback={<LoadingFallback />}>
<SearchResults query={query} />
</Suspense>
</main>
</div>
);
}
// 导出App组件
export default App;
搜索输入组件(详细注释版)
javascript
// src/components/SearchInput.js
// 导入React和useDeferredValue
import React, { useDeferredValue, useState, useEffect } from 'react';
// 定义SearchInput组件
function SearchInput({ value, onChange, isPending }) {
// 使用useState Hook管理本地输入值
const [localValue, setLocalValue] = useState(value);
// 使用useDeferredValue延迟更新
const deferredValue = useDeferredValue(localValue);
// 当延迟值变化时,调用onChange
useEffect(() => {
if (deferredValue !== value) {
onChange(deferredValue);
}
}, [deferredValue, onChange, value]);
// 处理输入变化
const handleChange = (e) => {
setLocalValue(e.target.value);
};
// 检查值是否过时
const isStale = localValue !== deferredValue;
return (
<div className="search-input-container">
<input
type="text"
className="search-input"
value={localValue}
onChange={handleChange}
placeholder="Search..."
/>
{isPending && (
<div className="search-loading">
Searching...
</div>
)}
{isStale && !isPending && (
<div className="search-stale">
Updating results...
</div>
)}
</div>
);
}
// 导出SearchInput组件
export default SearchInput;
搜索结果组件(详细注释版)
javascript
// src/components/SearchResults.js
// 导入React和useMemo
import React, { useMemo, useTransition } from 'react';
// 定义搜索结果组件
function SearchResults({ query }) {
// 使用useTransition Hook管理过渡状态
const [isPending, startTransition] = useTransition();
// 使用useState Hook管理结果
const [results, setResults] = React.useState([]);
// 使用useMemo记忆化搜索结果
const filteredResults = useMemo(() => {
if (!query.trim()) {
return [];
}
// 模拟昂贵的搜索操作
return performSearch(query);
}, [query]);
// 当过滤结果变化时,使用startTransition更新状态
React.useEffect(() => {
startTransition(() => {
setResults(filteredResults);
});
}, [filteredResults]);
if (!query.trim()) {
return (
<div className="search-results-empty">
<p>Enter a search query to see results</p>
</div>
);
}
if (isPending) {
return (
<div className="search-results-loading">
<p>Loading results...</p>
</div>
);
}
if (results.length === 0) {
return (
<div className="search-results-empty">
<p>No results found for "{query}"</p>
</div>
);
}
return (
<div className="search-results">
<h2>Search Results ({results.length})</h2>
<ul className="results-list">
{results.map(result => (
<li key={result.id} className="result-item">
<h3>{result.title}</h3>
<p>{result.description}</p>
<span className="result-meta">
{result.category} - {result.date}
</span>
</li>
))}
</ul>
</div>
);
}
// 模拟搜索函数
function performSearch(query) {
// 模拟大量数据
const allItems = Array.from({ length: 10000 }, (_, i) => ({
id: i,
title: `Item ${i}`,
description: `Description for item ${i}`,
category: ['Category A', 'Category B', 'Category C'][i % 3],
date: new Date(Date.now() - Math.random() * 10000000000).toISOString()
}));
// 执行搜索
return allItems.filter(item =>
item.title.toLowerCase().includes(query.toLowerCase()) ||
item.description.toLowerCase().includes(query.toLowerCase())
);
}
// 导出SearchResults组件
export default SearchResults;
加载回退组件(详细注释版)
javascript
// src/components/LoadingFallback.js
// 导入React
import React from 'react';
// 定义LoadingFallback组件
function LoadingFallback() {
return (
<div className="loading-fallback">
<div className="spinner"></div>
<p>Loading search results...</p>
</div>
);
}
// 导出LoadingFallback组件
export default LoadingFallback;
样式文件(详细注释版)
css
/* src/App.css */
/* 应用主容器样式 */
.App {
min-height: 100vh;
font-family: Arial, sans-serif;
background-color: #f5f5f5;
}
/* 应用头部样式 */
.App-header {
background-color: #333;
color: white;
padding: 2rem;
text-align: center;
}
.App-header h1 {
margin: 0;
font-size: 2rem;
}
/* 主要内容区域 */
.App-main {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
/* 搜索输入容器样式 */
.search-input-container {
margin-bottom: 2rem;
position: relative;
}
.search-input {
width: 100%;
padding: 1rem;
font-size: 1.2rem;
border: 2px solid #ddd;
border-radius: 8px;
box-sizing: border-box;
}
.search-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
}
.search-loading {
position: absolute;
top: 50%;
right: 1rem;
transform: translateY(-50%);
color: #007bff;
font-size: 0.9rem;
}
.search-stale {
position: absolute;
top: 50%;
right: 1rem;
transform: translateY(-50%);
color: #6c757d;
font-size: 0.9rem;
}
/* 搜索结果样式 */
.search-results {
background-color: white;
border-radius: 8px;
padding: 2rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.search-results h2 {
margin-top: 0;
color: #333;
font-size: 1.5rem;
}
.results-list {
list-style: none;
padding: 0;
margin: 0;
}
.result-item {
padding: 1.5rem;
border-bottom: 1px solid #eee;
transition: background-color 0.2s;
}
.result-item:hover {
background-color: #f8f9fa;
}
.result-item:last-child {
border-bottom: none;
}
.result-item h3 {
margin: 0 0 0.5rem 0;
color: #333;
font-size: 1.2rem;
}
.result-item p {
margin: 0 0 0.5rem 0;
color: #666;
line-height: 1.6;
}
.result-meta {
font-size: 0.9rem;
color: #999;
}
/* 空状态样式 */
.search-results-empty {
text-align: center;
padding: 4rem 2rem;
color: #666;
}
.search-results-empty p {
font-size: 1.2rem;
margin: 0;
}
/* 加载状态样式 */
.search-results-loading {
text-align: center;
padding: 4rem 2rem;
color: #666;
}
.search-results-loading p {
font-size: 1.2rem;
margin: 0;
}
/* 加载回退样式 */
.loading-fallback {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 2rem;
min-height: 400px;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-fallback p {
color: #666;
font-size: 1.1rem;
margin: 0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.App-main {
padding: 1rem;
}
.search-input {
font-size: 1rem;
padding: 0.75rem;
}
.search-results {
padding: 1rem;
}
.result-item {
padding: 1rem;
}
}