完整迁移案例
约 743 字大约 2 分钟
布欧-Lewyon
2026-05-15
通过一个完整案例演示如何将复杂的类组件逐步迁移为 Hooks。
迁移前:类组件版本
// UserDashboard.tsx —— 原有的类组件
interface Props { userId: number }
interface State {
user: User | null
loading: boolean
error: string | null
editing: boolean
formData: Partial<User>
isSaving: boolean
}
class UserDashboard extends React.Component<Props, State> {
state: State = {
user: null,
loading: true,
error: null,
editing: false,
formData: {},
isSaving: false,
}
componentDidMount() {
this.fetchUser()
}
componentDidUpdate(prevProps: Props) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser()
}
}
fetchUser = async () => {
this.setState({ loading: true, error: null })
try {
const user = await api.getUser(this.props.userId)
this.setState({ user, loading: false })
} catch (e) {
this.setState({ error: e.message, loading: false })
}
}
handleEdit = () => {
this.setState({
editing: true,
formData: { name: this.state.user!.name, email: this.state.user!.email }
})
}
handleSave = async () => {
this.setState({ isSaving: true })
try {
await api.updateUser(this.props.userId, this.state.formData)
this.setState({
isSaving: false,
editing: false,
user: { ...this.state.user!, ...this.state.formData },
})
} catch (e) {
this.setState({ isSaving: false, error: e.message })
}
}
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({
formData: { ...this.state.formData, [e.target.name]: e.target.value }
})
}
render() {
const { user, loading, error, editing, formData, isSaving } = this.state
if (loading) return <Loading />
if (error) return <Error message={error} onRetry={this.fetchUser} />
return (
<div>
{editing ? (
<EditForm
data={formData}
onChange={this.handleChange}
onSave={this.handleSave}
isSaving={isSaving}
/>
) : (
<UserInfo user={user!} onEdit={this.handleEdit} />
)}
</div>
)
}
}迁移后:Hooks 版本
// 1. 先提取自定义 Hook —— 数据获取逻辑
function useUser(userId: number) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const fetchUser = useCallback(async () => {
setLoading(true)
setError(null)
try {
const data = await api.getUser(userId)
setUser(data)
} catch (e: any) {
setError(e.message)
} finally {
setLoading(false)
}
}, [userId])
useEffect(() => { fetchUser() }, [fetchUser])
return { user, loading, error, refetch: fetchUser }
}
// 2. 自定义 Hook —— 编辑表单逻辑
function useEditForm(initialUser: User | null) {
const [editing, setEditing] = useState(false)
const [formData, setFormData] = useState<Partial<User>>({})
const [isSaving, setSaving] = useState(false)
const startEdit = useCallback(() => {
if (!initialUser) return
setFormData({ name: initialUser.name, email: initialUser.email })
setEditing(true)
}, [initialUser])
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setFormData(prev => ({ ...prev, [e.target.name]: e.target.value }))
}, [])
const save = useCallback(async (userId: number) => {
setSaving(true)
try {
await api.updateUser(userId, formData)
setEditing(false)
return true
} catch (e: any) {
setSaving(false)
throw e
}
}, [formData])
return { editing, formData, isSaving, startEdit, handleChange, save }
}
// 3. 最终组件 —— 简洁清晰
function UserDashboard({ userId }: Props) {
const { user, loading, error, refetch } = useUser(userId)
const { editing, formData, isSaving, startEdit, handleChange, save } = useEditForm(user)
if (loading) return <Loading />
if (error) return <Error message={error} onRetry={refetch} />
const handleSave = async () => {
try {
await save(userId)
refetch() // 重新获取最新数据
} catch (e: any) {
alert(e.message)
}
}
return (
<div>
{editing ? (
<EditForm
data={formData}
onChange={handleChange}
onSave={handleSave}
isSaving={isSaving}
/>
) : (
<UserInfo user={user!} onEdit={startEdit} />
)}
</div>
)
}迁移前后对比
| 维度 | 类组件(~90 行) | Hooks(~80 行分 3 个 Hook) |
|---|---|---|
| this | ✅ 需要 | ❌ 不需要 |
| 生命周期 | componentDidMount/Update | useEffect |
| setState 对象 | 单一大对象 | 多个小 state |
| 逻辑复用 | 需要 HOC 或 Render Props | 自定义 Hook 直接复用 |
| 可测试性 | 一般 | ✅ 单独测试 Hook |
| 类型推断 | ⚠️ | ✅ 自动推断 |
迁移步骤
1. 提取数据获取 → useUser Hook
2. 提取表单逻辑 → useEditForm Hook
3. 类组件改为函数组件,调用 Hook
4. 移除 constructor、this、生命周期
5. 删除类组件文件
6. 测试(注意闭包陷阱)小结
- 迁移的核心策略:按逻辑拆分为多个自定义 Hook。
- Hook 之间可组合(
useEditForm接受user参数)。 - 每个 Hook 单一职责,可独立测试。
- 迁移后代码量减少 ~20%,可读性大幅提升。
- 建议从叶子组件开始逐步迁移,不要求全量重写。
上一节:HOC 到自定义 Hook 重构 下一节:无(完结)- 迁移专题至此结束
