大家好,我是鱼樱!!!
关注公众号【鱼樱AI实验室】
持续每天分享更多前端和AI辅助前端编码新知识~~喜欢的就一起学反正开源至上,无所谓被诋毁被喷被质疑文章没有价值~
一个城市淘汰的自由职业-农村前端程序员(虽然不靠代码挣钱,写文章就是为爱发电),兼职远程上班目前!!!热心坚持分享~~
1. 组件定义方式演变
1.1 类组件与函数组件
React 16:
jsx
// 类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 函数组件(无状态)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
React 16.8+:
jsx
// 函数组件(有状态)
function Welcome(props) {
const [count, setCount] = useState(0);
return <h1>Hello, {props.name}, Count: {count}</h1>;
}
最佳实践:
- React 16-19: 优先使用函数组件和 Hooks,类组件仅用于维护旧代码
- React 18+: 利用并发特性时,建议使用函数组件,类组件不支持某些新功能
2. 生命周期详细对比
2.1 类组件生命周期变化
React 16.0-16.2:
jsx
class Component extends React.Component {
constructor(props) {
super(props);
// 初始化状态
}
componentWillMount() {
// 组件挂载前 (已弃用)
}
componentDidMount() {
// 组件挂载后
}
componentWillReceiveProps(nextProps) {
// 组件接收新props前 (已弃用)
}
shouldComponentUpdate(nextProps, nextState) {
// 控制是否重新渲染
return true;
}
componentWillUpdate(nextProps, nextState) {
// 组件更新前 (已弃用)
}
componentDidUpdate(prevProps, prevState) {
// 组件更新后
}
componentWillUnmount() {
// 组件卸载前
}
}
React 16.3:
jsx
class Component extends React.Component {
// 新增
static getDerivedStateFromProps(props, state) {
// 在render前调用,用于根据props更新state
// 替代componentWillReceiveProps
return null; // 返回null表示不更新状态
}
// 新增
getSnapshotBeforeUpdate(prevProps, prevState) {
// 在DOM更新前调用,返回值会传递给componentDidUpdate
// 替代componentWillUpdate
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// snapshot是getSnapshotBeforeUpdate的返回值
}
}
React 16.3-16.8 弃用的生命周期:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
这些方法被标记为 UNSAFE_
前缀版本:
- UNSAFE_componentWillMount
- UNSAFE_componentWillReceiveProps
- UNSAFE_componentWillUpdate
React 17+ 完整生命周期:
jsx
class Component extends React.Component {
constructor(props)
static getDerivedStateFromProps(props, state)
shouldComponentUpdate(nextProps, nextState)
render()
getSnapshotBeforeUpdate(prevProps, prevState)
componentDidMount()
componentDidUpdate(prevProps, prevState, snapshot)
componentWillUnmount()
// 错误处理
static getDerivedStateFromError(error)
componentDidCatch(error, info)
}
2.2 函数组件生命周期(Hooks)
React 16.8+ Hooks:
jsx
function Component(props) {
// 相当于constructor和componentDidMount, componentDidUpdate, componentWillUnmount的组合
useEffect(() => {
// 相当于componentDidMount和componentDidUpdate
console.log('组件挂载或更新后');
return () => {
// 相当于componentWillUnmount
console.log('组件卸载前');
};
}, [/* 依赖数组 */]);
// 仅在挂载时执行一次
useEffect(() => {
console.log('仅在组件挂载后');
return () => {
console.log('组件卸载前清理');
};
}, []);
// 每次渲染后都执行
useEffect(() => {
console.log('每次渲染后执行');
});
// 根据特定依赖更新
useEffect(() => {
console.log('props.id变化时执行');
}, [props.id]);
// 相当于componentDidMount, componentDidUpdate和getSnapshotBeforeUpdate
useLayoutEffect(() => {
// DOM更新后、浏览器绘制前同步执行
}, []);
}
React 18+ 新添加的 Hook:
jsx
function Component() {
// 仅服务端渲染期间执行一次
useInsertionEffect(() => {
// 用于CSS-in-JS库注入样式
// 在DOM创建后,useLayoutEffect前执行
}, []);
// 延迟执行,不阻塞渲染
const deferredValue = useDeferredValue(value);
// 过渡更新,标记非紧急更新
const [isPending, startTransition] = useTransition();
// React 18.2+,组件ID
const id = useId();
// React 18+,Suspense缓存
useCacheRefresh();
}
React 19+ Hooks:
jsx
function Component() {
// React 19 新增action hook管理表单状态
const formAction = useFormAction({
// 表单状态管理
});
// 新的effect优化API,减少重渲染
useEffectEvent(() => {
// 执行不需要重新渲染的逻辑
});
}
2.3 类组件与函数组件生命周期对比表
类组件 | 函数组件(Hooks) | 执行时机 |
---|---|---|
constructor | useState, useRef初始化 | 组件创建时 |
getDerivedStateFromProps | 在渲染过程中通过计算获取 | render前 |
shouldComponentUpdate | React.memo, useMemo | 决定是否重新渲染 |
render | 函数本体 | 渲染组件 |
getSnapshotBeforeUpdate | useLayoutEffect(有限接近) | DOM更新前 |
componentDidMount | useEffect([], ...) | 组件挂载后 |
componentDidUpdate | useEffect([deps], ...) | 组件更新后 |
componentWillUnmount | useEffect返回的函数 | 组件卸载前 |
getDerivedStateFromError | 无直接对应,需使用错误边界 | 子组件出错时 |
componentDidCatch | 无直接对应,需使用错误边界 | 捕获子组件错误后 |
3. 状态管理演变
3.1 类组件状态管理
React 16:
jsx
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
// 或使用函数式更新
this.setState(state => ({ count: state.count + 1 }));
}
render() {
return (
<button onClick={this.increment}>
Count: {this.state.count}
</button>
);
}
}
3.2 函数组件状态管理
React 16.8+:
jsx
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
React 18: 自动批处理更新 - 多个状态更新合并为一次渲染
jsx
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
// React 18中这些会被自动批处理为一次更新
setCount(c => c + 1); // 不会触发重新渲染
setFlag(f => !f); // 只有这后会触发一次重新渲染
}
// ...
}
React 19: 引入更细粒度的状态管理,useActionState帮助管理表单状态
jsx
function Form() {
const [state, action] = useActionState({
initialState: { name: "" },
action: (state, newValue) => {
return { ...state, ...newValue }
}
});
return <form action={action}>
<input name="name" defaultValue={state.name} />
</form>;
}
3.3 状态管理最佳实践
- React 16-19:
- 使用函数式更新处理依赖前一状态的更新
- 对象状态更新时保持不可变性
- React 18+:
- 利用自动批处理优化性能
- 使用 useDeferredValue 和 useTransition 处理大型状态更新
- React 19+:
- 使用新的表单状态管理API简化表单处理
4. Props 处理与组件通信
4.1 Props 默认值
类组件:
jsx
class Button extends React.Component {
static defaultProps = {
color: 'blue'
};
render() {
return <button className={this.props.color}>{this.props.children}</button>;
}
}
函数组件:
jsx
function Button({ color = 'blue', children }) {
return <button className={color}>{children}</button>;
}
// 或者
Button.defaultProps = {
color: 'blue'
};
4.2 Props 类型检查
React 16-19:
jsx
import PropTypes from 'prop-types';
function Button({ color, children }) {
return <button className={color}>{children}</button>;
}
Button.propTypes = {
color: PropTypes.string,
children: PropTypes.node.isRequired
};
TypeScript (推荐):
tsx
interface ButtonProps {
color?: string;
children: React.ReactNode;
}
function Button({ color = 'blue', children }: ButtonProps) {
return <button className={color}>{children}</button>;
}
4.3 Context API 演变
React 16.0:
jsx
// 创建Context
const ThemeContext = React.createContext('light');
// 提供Context
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}
}
// 消费Context
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return <button theme={this.context}>Themed Button</button>;
}
}
// 函数组件使用Consumer
function ThemedButton() {
return (
<ThemeContext.Consumer>
{value => <button theme={value}>Themed Button</button>}
</ThemeContext.Consumer>
);
}
React 16.8+ (Hooks):
jsx
const ThemeContext = React.createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button theme={theme}>Themed Button</button>;
}
5. 渲染优化各版本对比
5.1 类组件优化
React 16:
jsx
class ExpensiveComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 手动控制重新渲染
return this.props.value !== nextProps.value;
}
render() {
return <div>{this.props.value}</div>;
}
}
// 或使用PureComponent
class OptimizedComponent extends React.PureComponent {
// 自动浅比较props和state
render() {
return <div>{this.props.value}</div>;
}
}
5.2 函数组件优化
React 16.8+:
jsx
// React.memo - 类似PureComponent的函数组件版本
const MemoizedComponent = React.memo(function MyComponent(props) {
return <div>{props.value}</div>;
});
// 自定义比较函数
const MemoizedCustomComponent = React.memo(
function MyComponent(props) {
return <div>{props.value}</div>;
},
(prevProps, nextProps) => {
// 返回true表示不重新渲染
return prevProps.value === nextProps.value;
}
);
function App() {
// useMemo - 缓存计算结果
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// useCallback - 缓存函数引用
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
}
React 18+:
jsx
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
// 输入时立即更新query,但延迟更新结果,提升用户体验
const deferredQuery = useDeferredValue(query);
useEffect(() => {
fetchResults(deferredQuery).then(setResults);
}, [deferredQuery]);
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
{/* 可能很慢的渲染 */}
<ResultsList query={deferredQuery} results={results} />
</>
);
}
React 19+:
jsx
// React 19中的自动记忆
function ResultItem({item}) {
return <div className="item">{item.title}</div>;
}
// React会自动优化类似上述纯渲染组件
function ResultsList({items}) {
return (
<div>
{items.map(item => <ResultItem key={item.id} item={item} />)}
</div>
);
}
6. 副作用处理
6.1 类组件
React 16:
jsx
class DataFetcher extends React.Component {
state = { data: null, loading: true };
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.fetchData();
}
}
componentWillUnmount() {
// 清理工作
this.isUnmounted = true;
}
fetchData = async () => {
this.setState({ loading: true });
const data = await fetchAPI(this.props.id);
// 防止组件卸载后设置状态
if (!this.isUnmounted) {
this.setState({ data, loading: false });
}
}
render() {
// ...
}
}
6.2 函数组件
React 16.8+:
jsx
function DataFetcher({ id }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
setLoading(true);
fetchAPI(id).then(data => {
if (isMounted) {
setData(data);
setLoading(false);
}
});
return () => {
isMounted = false; // 清理函数
};
}, [id]); // 依赖数组,id变化时重新执行
// ...
}
React 18+: 使用useTransition处理副作用
jsx
function SearchResults() {
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleChange(e) {
// 立即更新输入值
setQuery(e.target.value);
// 标记结果更新为非紧急
startTransition(async () => {
const results = await fetchResults(e.target.value);
setResults(results);
});
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending ? (
<div>Loading...</div>
) : (
<ResultsList results={results} />
)}
</>
);
}
7. 错误处理
7.1 错误边界
React 16+:
jsx
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
// 更新状态,下次渲染显示备用UI
return { hasError: true, error };
}
componentDidCatch(error, info) {
// 记录错误信息
logErrorToService(error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
注意: 截至React 19,仍然没有函数组件版本的错误边界,必须使用类组件。
7.2 React 18+ 新错误处理能力
jsx
// React 18新增全局API
function App() {
return (
<React.StrictMode>
<ErrorBoundary
fallback={<p>Application Error</p>}
onError={(error, errorInfo) => {
// 记录到服务
logError(error, errorInfo);
}}
>
<AppContent />
</ErrorBoundary>
</React.StrictMode>
);
}
8. React 16 到 19 的重要 API 变更
8.1 新增API
API | 版本 | 描述 |
---|---|---|
React.lazy | 16.6 | 组件代码分割 |
React.Suspense | 16.6 | 等待加载组件 |
React.memo | 16.6 | 函数组件性能优化 |
Hooks系列API | 16.8 | 函数组件状态和生命周期 |
React.createRoot | 18.0 | 新并发渲染器入口 |
useTransition | 18.0 | 标记非紧急更新 |
useDeferredValue | 18.0 | 延迟低优先级更新 |
useId | 18.0 | 生成唯一ID |
useInsertionEffect | 18.0 | CSS-in-JS库专用Hook |
useFormState | 19.0 | 表单状态管理 |
useFormStatus | 19.0 | 表单提交状态 |
useEffectEvent | 19.0 | 分离事件逻辑和渲染依赖 |
useOptimistic | 19.0 | 乐观UI更新 |
8.2 弃用API
API | 弃用版本 | 替代方案 |
---|---|---|
componentWillMount | 16.3 | componentDidMount |
componentWillReceiveProps | 16.3 | getDerivedStateFromProps |
componentWillUpdate | 16.3 | getSnapshotBeforeUpdate |
ReactDOM.render | 18.0 | ReactDOM.createRoot().render() |
ReactDOM.hydrate | 18.0 | ReactDOM.hydrateRoot() |
findDOMNode | 17.0 | Refs |
8.3 重大变更
React 17:
- 事件委托变更 - 从document改为挂载React树的根DOM
- 移除事件池
- useEffect清理函数变为异步
- 一致的错误边界行为
React 18:
- 自动批处理state更新
- 并发渲染
- 服务器组件
- Suspense支持服务端渲染
- transitions用于区分紧急和非紧急更新
React 19:
- 更好的表单API
- 自动化记忆优化
- 服务器Actions
- 资产加载优化
- 错误边界改进
9. 代码组织最佳实践
9.1 组件设计原则
单一职责:
jsx
// 不推荐
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
useEffect(() => {
fetchUser(userId).then(setUser);
fetchPosts(userId).then(setPosts);
}, [userId]);
// 渲染用户信息和帖子列表...
}
// 推荐 - 分离关注点
function UserProfile({ userId }) {
return (
<div>
<UserInfo userId={userId} />
<UserPosts userId={userId} />
</div>
);
}
function UserInfo({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
// 渲染用户信息...
}
function UserPosts({ userId }) {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetchPosts(userId).then(setPosts);
}, [userId]);
// 渲染帖子列表...
}
9.2 自定义Hook封装
jsx
// 封装数据获取逻辑
function useFetch(url, options) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
setLoading(true);
fetch(url, options)
.then(res => res.json())
.then(data => {
if (isMounted) {
setData(data);
setError(null);
setLoading(false);
}
})
.catch(err => {
if (isMounted) {
setError(err);
setData(null);
setLoading(false);
}
});
return () => {
isMounted = false;
};
}, [url, JSON.stringify(options)]);
return { data, loading, error };
}
// 使用
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error loading user</p>;
return <div>{user.name}</div>;
}
9.3 React 18+ 并发特性最佳实践
jsx
function SearchPage() {
const [searchTerm, setSearchTerm] = useState('');
const [isPending, startTransition] = useTransition();
function handleSearch(e) {
// 立即更新输入值
setSearchTerm(e.target.value);
// 非紧急更新 - 搜索结果计算
startTransition(() => {
// 重量级操作...
});
}
return (
<>
<input value={searchTerm} onChange={handleSearch} />
{isPending ? <Spinner /> : <SearchResults term={searchTerm} />}
</>
);
}
9.4 React 19 表单处理最佳实践
jsx
'use client';
import { useFormState, useFormStatus } from 'react';
// 服务端Action
async function signup(prevState, formData) {
try {
const user = await createUser(formData);
return { success: true, user };
} catch (error) {
return { success: false, error: error.message };
}
}
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button disabled={pending}>
{pending ? 'Signing up...' : 'Sign up'}
</button>
);
}
function SignupForm() {
const [state, formAction] = useFormState(signup, {
success: false,
error: null
});
return (
<form action={formAction}>
{state.error && <p className="error">{state.error}</p>}
{state.success && <p className="success">Account created!</p>}
<input name="username" required />
<input name="email" type="email" required />
<input name="password" type="password" required />
<SubmitButton />
</form>
);
}
10. 性能优化策略
10.1 React 16-17 优化
- 虚拟列表渲染
jsx
import { FixedSizeList } from 'react-window';
function List({ items }) {
const renderRow = ({ index, style }) => (
<div style={style}>
{items[index].text}
</div>
);
return (
<FixedSizeList
height={500}
width={300}
itemCount={items.length}
itemSize={35}
>
{renderRow}
</FixedSizeList>
);
}
- 使用React.memo避免不必要渲染
jsx
const ExpensiveComponent = React.memo(function ExpensiveComponent({ value }) {
// 昂贵的渲染操作
return <div>{value}</div>;
});
- 使用useMemo缓存计算值
jsx
function FilteredList({ items, filterTerm }) {
const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(filterTerm.toLowerCase())
);
}, [items, filterTerm]);
return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
10.2 React 18 优化
- 使用useDeferredValue
jsx
function SearchResults({ query }) {
// 延迟处理搜索结果更新
const deferredQuery = useDeferredValue(query);
// 昂贵的过滤操作使用deferredQuery
const results = useMemo(() => {
return computeFilteredResults(deferredQuery);
}, [deferredQuery]);
return (
<div>
<p>Searching for: {query}</p>
<ul>
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
</div>
);
}
- useTransition分离紧急和非紧急更新
jsx
function TabContainer() {
const [selectedTab, setSelectedTab] = useState('home');
const [isPending, startTransition] = useTransition();
function selectTab(tab) {
// 标记为非紧急更新
startTransition(() => {
setSelectedTab(tab);
});
}
return (
<div>
<TabButton
isSelected={selectedTab === 'home'}
onClick={() => selectTab('home')}
>
Home
</TabButton>
<TabButton
isSelected={selectedTab === 'about'}
onClick={() => selectTab('about')}
>
About
</TabButton>
{isPending ? <Spinner /> : <TabContent tab={selectedTab} />}
</div>
);
}
10.3 React 19 优化
- 使用自动记忆机制
jsx
// React 19能自动优化这样的纯渲染组件
function Item({ text }) {
return <div>{text}</div>;
}
// 在父组件中不需要额外缓存处理
function ItemsList({ items }) {
return (
<div>
{items.map(item => <Item key={item.id} text={item.text} />)}
</div>
);
}
- 使用useOptimistic实现乐观UI更新
jsx
function TodoList() {
const [todos, setTodos] = useState([]);
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, { ...newTodo, pending: true }]
);
async function addTodo(text) {
const newTodo = { id: Date.now(), text };
// 立即显示乐观结果
addOptimisticTodo(newTodo);
try {
// 执行实际API调用
const savedTodo = await saveTodoToServer(newTodo);
// 成功后更新实际状态
setTodos([...todos, savedTodo]);
} catch (error) {
// 处理错误...
}
}
return (
<div>
<AddTodoForm onAdd={addTodo} />
<ul>
{optimisticTodos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
isPending={todo.pending}
/>
))}
</ul>
</div>
);
}
11. 路由与代码分割
11.1 React Router 演变
React Router 5 (React 16-17):
jsx
import { BrowserRouter, Route, Switch } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/users/:id" component={UserProfile} />
<Route component={NotFound} />
</Switch>
</BrowserRouter>
);
}
React Router 6 (React 18+):
jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserProfile />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
11.2 代码分割
React 16.6+:
jsx
import React, { Suspense } from 'react';
// 懒加载组件
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
React Router 6 与代码分割:
jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import React, { Suspense } from 'react';
const Home = React.lazy(() => import('./routes/Home'));
const About = React.lazy(() => import('./routes/About'));
const UserProfile = React.lazy(() => import('./routes/UserProfile'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserProfile />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
11.3 React 18+ 服务端组件
jsx
// app/page.js - 服务器组件
export default async function Page() {
// 直接在服务器上获取数据,不需要useEffect
const data = await fetchData();
return (
<div>
<h1>Server Component</h1>
<ServerRenderedContent data={data} />
<ClientInteractiveComponent />
</div>
);
}
// ClientInteractiveComponent.js
'use client'; // 标记为客户端组件
import { useState } from 'react';
export default function ClientInteractiveComponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
12. 测试策略
12.1 组件测试
使用Jest和React Testing Library:
jsx
// Button.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
test('renders button with correct text', () => {
render(<Button>Click me</Button>);
const buttonElement = screen.getByText(/click me/i);
expect(buttonElement).toBeInTheDocument();
});
test('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText(/click me/i));
expect(handleClick).toHaveBeenCalledTimes(1);
});
12.2 Hook测试
jsx
// useCounter.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from './useCounter';
test('should increment counter', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
12.3 React 18+ 并发模式测试
jsx
// 测试useTransition
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import TransitionComponent from './TransitionComponent';
test('shows loading state during transition', async () => {
render(<TransitionComponent />);
// 点击触发transition
await userEvent.click(screen.getByText('Load Data'));
// 检查是否显示加载状态
expect(screen.getByText('Loading...')).toBeInTheDocument();
// 等待transition完成
await screen.findByText('Data loaded');
// 检查加载状态是否消失
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});
13. 版本迁移指南
13.1 从React 16到React 17
-
移除不安全生命周期方法
- 重构
componentWillMount
、componentWillReceiveProps
、componentWillUpdate
- 重构
-
修复事件系统变化
- 检查事件处理依赖于冒泡到document的代码
- 事件池被移除,不再需要e.persist()
-
JSX转换
- 不再需要
import React from 'react'
(如果使用新JSX转换)
- 不再需要
13.2 从React 17到React 18
-
更新渲染API
jsx// 旧版 import ReactDOM from 'react-dom'; ReactDOM.render(<App />, rootNode); // 新版 import ReactDOM from 'react-dom/client'; const root = ReactDOM.createRoot(rootNode); root.render(<App />);
-
处理自动批处理变化
- 检查依赖于单独状态更新的代码
-
利用并发特性
- 利用useTransition和useDeferredValue优化用户体验
13.3 从React 18到React 19
-
更新表单处理
- 迁移到新表单APIs (
useFormState
,useFormStatus
)
- 迁移到新表单APIs (
-
优化事件处理
- 使用
useEffectEvent
分离事件逻辑
- 使用
-
重构服务端组件
- 更好地区分服务端和客户端组件
14. 综合最佳实践
14.1 组件设计
- 使用函数组件和Hooks
- 将复杂逻辑和状态抽离为自定义Hooks
- 通过Props向下传递,通过Context共享全局状态
- 避免过深的组件层级,考虑组合代替继承
14.2 状态管理
- 局部状态使用useState和useReducer
- 全局状态考虑使用Context或Redux
- 使用不可变数据更新模式
- React 18+中使用useDeferredValue和useTransition优化更新
14.3 副作用处理
- 使用useEffect分类管理副作用
- 正确设置依赖数组,避免遗漏
- 使用清理函数防止内存泄漏
- 使用fetch包装器如SWR或React Query优化数据获取
14.4 渲染优化
- 使用React.memo包装纯组件
- 使用useMemo缓存计算值
- 使用useCallback稳定回调函数引用
- 代码分割减少初始包大小
- React 18+中使用startTransition标记非紧急更新
14.5 错误处理
- 使用错误边界捕获组件树错误
- 为Promise错误添加try/catch
- 降级渲染处理错误状态
15. 总结
React 从16到19版本的演进展现了框架的不断成熟与优化。主要变化集中在:
-
编程模式变革: 从类组件到函数组件+Hooks的范式转变
-
响应式更新: 引入并发模式,更精细的更新优先级控制
-
服务器集成: 服务器组件的引入与完善
-
开发体验: 自动批处理、更好的工具和API设计
-
性能优化: 从手动优化到框架层自动优化
每个React版本都为开发者提供了更强大的工具来构建高性能、可维护的UI。通过理解这些变化和最佳实践,开发者可以充分利用React的强大能力,同时避免常见的陷阱。