React 版本:18.x 难度:高级
考察要点:
- Concurrent Mode 工作原理
- useTransition 使用场景
- 可中断渲染策略
- 用户体验优化
解答:
1. 概念解释
基本定义:
- Concurrent Mode:新的渲染模式,支持可中断更新
- useTransition:用于标记非紧急更新的 Hook
- startTransition:将更新标记为可中断
工作原理:
- 将渲染工作分解成小单元
- 支持更新的优先级排序
- 允许中断和恢复渲染
应用场景:
- 大量数据渲染
- 复杂计算处理
- 页面切换动画
- 搜索建议实现
2. 代码示例
基础示例:
import React, { useState, useTransition } from 'react';
interface ListProps {
items: string[];
}
// 👉 模拟耗时计算
const expensiveCalculation = (items: string[]) => {
console.log('Performing expensive calculation...');
return items.map(item => {
let result = item;
for (let i = 0; i < 1000000; i++) {
result += 'x';
}
return result.slice(0, 10);
});
};
// 👉 列表组件
const ExpensiveList: React.FC<ListProps> = ({ items }) => {
const processedItems = expensiveCalculation(items);
return (
<ul>
{processedItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
};
// 👉 主组件
export const TransitionExample: React.FC = () => {
const [isPending, startTransition] = useTransition();
const [searchTerm, setSearchTerm] = useState('');
const [items, setItems] = useState<string[]>([]);
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
// 立即更新搜索框
setSearchTerm(value);
// 将列表更新标记为非紧急
startTransition(() => {
const newItems = Array.from({ length: 1000 }, (_, i) =>
`Item ${i} ${value}`
);
setItems(newItems);
});
};
return (
<div>
<input
type="text"
value={searchTerm}
onChange={handleSearch}
placeholder="Search..."
/>
{isPending ? (
<div>Loading...</div>
) : (
<ExpensiveList items={items} />
)}
</div>
);
};
进阶示例:
import React, {
Suspense,
useState,
useTransition,
useDeferredValue
} from 'react';
// 👉 数据获取包装器
interface Resource<T> {
read(): T;
}
function createResource<T>(promise: Promise<T>): Resource<T> {
let status = 'pending';
let result: T;
let error: Error;
const suspender = promise.then(
(data) => {
status = 'success';
result = data;
},
(e) => {
status = 'error';
error = e;
}
);
return {
read() {
switch (status) {
case 'pending':
throw suspender;
case 'error':
throw error;
case 'success':
return result;
default:
throw new Error('Impossible state');
}
}
};
}
// 👉 数据组件
interface User {
id: number;
name: string;
email: string;
}
const UserProfile: React.FC<{ resource: Resource<User> }> = ({ resource }) => {
const user = resource.read();
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
};
// 👉 主应用组件
export const ConcurrentApp: React.FC = () => {
const [userId, setUserId] = useState<number>(1);
const [isPending, startTransition] = useTransition();
const deferredUserId = useDeferredValue(userId);
// 模拟数据获取
const fetchUser = (id: number) => {
return new Promise<User>((resolve) =>
setTimeout(() => {
resolve({
id,
name: `User ${id}`,
email: `user${id}@example.com`
});
}, 1000)
);
};
const userResource = createResource(fetchUser(deferredUserId));
const handleUserChange = (newId: number) => {
startTransition(() => {
setUserId(newId);
});
};
return (
<div>
<div>
<button
onClick={() => handleUserChange(userId - 1)}
disabled={userId <= 1}
>
Previous
</button>
<span>User ID: {userId}</span>
<button
onClick={() => handleUserChange(userId + 1)}
>
Next
</button>
</div>
{isPending && <div>Loading...</div>}
<Suspense fallback={<div>Loading profile...</div>}>
<UserProfile resource={userResource} />
</Suspense>
</div>
);
};
3. 注意事项与最佳实践
❌ 常见错误示例:
// ❌ 错误示范:在 transition 中更新紧急状态
const BadComponent: React.FC = () => {
const [isPending, startTransition] = useTransition();
const [urgentState, setUrgentState] = useState('');
const handleClick = () => {
startTransition(() => {
// 错误:不应在 transition 中更新紧急状态
setUrgentState('new value');
});
};
};
// ❌ 错误示范:不必要的 transition
const AnotherBadComponent: React.FC = () => {
const [isPending, startTransition] = useTransition();
const handleSimpleClick = () => {
startTransition(() => {
// 错误:简单更新不需要 transition
console.log('clicked');
});
};
};
✅ 正确实现方式:
// ✅ 正确示范:合理使用 transition
const GoodComponent: React.FC = () => {
const [isPending, startTransition] = useTransition();
const [urgentState, setUrgentState] = useState('');
const [heavyState, setHeavyState] = useState<string[]>([]);
const handleComplexUpdate = () => {
// 立即更新紧急状态
setUrgentState('new value');
// 将耗时更新放入 transition
startTransition(() => {
setHeavyState(generateLargeDataSet());
});
};
};
// ✅ 正确示范:结合 Suspense 使用
const AnotherGoodComponent: React.FC = () => {
const [resource, setResource] = useState<Resource<Data>>(initialResource);
const [isPending, startTransition] = useTransition();
const handleRefresh = () => {
startTransition(() => {
setResource(createResource(fetchData()));
});
};
return (
<Suspense fallback={<Spinner />}>
<DataComponent resource={resource} />
</Suspense>
);
};
4. 性能优化
import React, { useMemo, useTransition, useDeferredValue } from 'react';
// 👉 优化的搜索实现
const OptimizedSearch: React.FC = () => {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const [isPending, startTransition] = useTransition();
// 使用 useMemo 缓存搜索结果
const searchResults = useMemo(() => {
return performExpensiveSearch(deferredQuery);
}, [deferredQuery]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<div style={{ opacity: isPending ? 0.5 : 1 }}>
{searchResults.map(result => (
<ResultItem key={result.id} data={result} />
))}
</div>
</div>
);
};
5. 测试策略
import { render, screen, fireEvent, act } from '@testing-library/react';
describe('TransitionExample', () => {
it('should show loading state during transition', async () => {
render(<TransitionExample />);
const input = screen.getByPlaceholderText('Search...');
await act(async () => {
fireEvent.change(input, { target: { value: 'test' } });
});
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('should update search results after transition', async () => {
render(<TransitionExample />);
const input = screen.getByPlaceholderText('Search...');
await act(async () => {
fireEvent.change(input, { target: { value: 'test' } });
});
// 等待 transition 完成
await screen.findByText(/Item 0 test/);
});
});
这个实现展示了 Concurrent Mode 和 useTransition 的完整使用方案,包括:
- 基础和进阶的实现方式
- 错误处理和最佳实践
- 性能优化策略
- 测试方法
关键是要理解何时使用 transition,以及如何正确处理可中断更新来提升用户体验。