5.3 KiB
5.3 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-04-29 22:17 |
迁移与升级
概述
大型 React 项目不可避免地面临技术债务和版本升级。本文档提供从 Class 组件到函数组件、从旧版 Router 到 v7、以及应对 React Major Version 变更的系统化迁移策略。
Class → Function + Hooks 迁移
逐步对照表
| Class 特性 | Hook 等效写法 |
|---|---|
this.state |
useState |
componentDidMount |
useEffect(() => { ... }, []) |
componentDidUpdate(prev) |
useEffect(() => { ... }, [dep]) |
componentWillUnmount |
useEffect(() => { return () => { cleanup } }) |
this.setState(fn) |
setState(prev => ({ ... })) |
ref = React.createRef() |
useRef |
shouldComponentUpdate |
React.memo / useMemo / useCallback |
| Context.Consumer | useContext |
实际迁移示例
// ❌ Before: Class Component
class UserProfile extends React.Component<{ userId: string }> {
state = { user: null, loading: true };
componentDidMount() {
fetchUser(this.props.userId).then(user => {
this.setState({ user, loading: false });
});
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.setState({ loading: true });
fetchUser(this.props.userId).then(user => this.setState({ user, loading: false }));
}
}
componentWillUnmount() {
// nothing to clean up here
}
render() {
const { user, loading } = this.state;
return loading ? <Spinner /> : <div>{user.name}</div>;
}
}
// ✅ After: Functional Component + Hooks
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetchUser(userId).then(u => {
if (!cancelled) setUser(u);
}).finally(() => {
if (!cancelled) setLoading(false);
});
return () => { cancelled = true; }; // cleanup
}, [userId]); // userId 变化时自动重新 fetch
if (loading) return <Spinner />;
return <div>{user?.name}</div>;
}
[!warning] 迁移 Checklist
- 所有
componentDidMount/Update/Unmount转译为 useEffect- State 合并:将互不相关的 state 拆分,减少不必要的重渲染
- 移除
this.—— 检查所有闭包中的变量引用- Props 类型从 class props 改为 interface 解构
React Router v6 → v7 迁移
graph LR
A["v6 BrowserRouter"] -->|"Switch → Routes"| B["v7 Routes"]
C["v6 <Route path='/' element={} />"] -->|"path prop 简化"| D["v7 { path: '/', element: {} }"]
E["v6 <Redirect/>"] -->|"navigate(-1)/replace"| F["v7 <Navigate replace />"]
G["v6 Outlet"] -->|"保留"| H["v7 Outlet — 位置不变"]
style A fill:#F4DBD6,color:#000
style B fill:#61DAFB,color:#000
API 映射速查
| v6 | v7 | 备注 |
|---|---|---|
<BrowserRouter> |
<RouterProvider> + createBrowserRouter |
data router 模式 |
Switch |
Routes |
v6 已有 |
<Redirect to="/x" /> |
<Navigate to="/x" /> |
语义更清晰 |
useHistory |
useNavigate |
名称统一 |
<Route component={Page}> |
<Route element={<Page />} > |
JSX 方式 |
match.params |
useParams() |
hook 方式 |
React 17 → 18 兼容性
| 注意事项 | 说明 |
|---|---|
ReactDOM.render 已废弃 |
改用 createRoot().render() |
ReactDOM.unmountComponentAtNode 已废弃 |
改用 root.unmount() |
| 事件处理在微任务中执行 | event.persist() 已移除 |
| StrictMode 双重渲染 | 开发环境 effect 先 mount → unmount → remount(用于检测) |
// React 17
import ReactDOM from "react-dom";
ReactDOM.render(<App />, document.getElementById("root"));
// React 18
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root"));
root.render(<App />);
Next.js Pages → App Router 迁移
| Pages Router | App Router |
|---|---|
pages/ 目录 |
app/ 目录 |
_document.tsx |
app/layout.tsx |
_app.tsx |
app/layout.tsx(根布局) |
getServerSideProps |
async Page 组件 |
getStaticProps |
async Page + revalidate |
next/router |
next/navigation |
_middleware.ts |
middleware.ts |
// Pages Router
export async function getServerSideProps() {
const data = await api.getData();
return { props: { data } };
}
// App Router
async function Page() {
const data = await api.getData();
return <View data={data} />;
}
Major Version 升级通用流程
flowchart TD
A["发现新版本发布"] --> B["阅读 Changelog / Breaking Changes"]
B --> C{"影响范围评估"}
C --> D["仅 minor version bump"]
C --> E["API 变更 / 新配置项"]
C --> F["架构性改动"]
D --> G["直接升级 + CI 验证"]
E --> H["逐个文件修复"]
F --> I["分阶段迁移 + 并行验证"]
G --> J["更新依赖 + 运行测试"]
H --> J
I --> J
J --> K["Staging 环境验证"]
K --> L["灰度发布 / Feature Flag"]
L --> M["全量上线"]
style F fill:#F5A87D,color:#000
style I fill:#F5A87D,color:#000