HOC 到自定义 Hook 重构
约 570 字大约 2 分钟
布欧-Lewyon
2026-05-15
高阶组件(HOC)是类组件时代代码复用的主要方式。Hooks 提供了更简洁的替代——自定义 Hook。
withAuth → useAuth
// HOC 版本
function withAuth<P>(WrappedComponent: React.ComponentType<P>) {
return class extends React.Component<P> {
render() {
const user = getCurrentUser()
if (!user) return <Navigate to="/login" />
return <WrappedComponent {...this.props} user={user} />
}
}
}
const ProtectedDashboard = withAuth(Dashboard)
// <ProtectedDashboard /> 直接使用// Custom Hook 版本
function useAuth() {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
getCurrentUser()
.then(setUser)
.finally(() => setLoading(false))
}, [])
return { user, loading }
}
// 使用
function Dashboard() {
const { user, loading } = useAuth()
if (loading) return <Loading />
if (!user) return <Navigate to="/login" />
return <div>Welcome, {user.name}</div>
}withLoading → useLoading
// HOC 版本
function withLoading<P>(Component: React.ComponentType<P>) {
return class extends React.Component<P & { loading: boolean }> {
render() {
const { loading, ...rest } = this.props
if (loading) return <Spinner />
return <Component {...rest as P} />
}
}
}
// Hook 版本 —— 更灵活:可在任意位置使用
function useLoading(initial = false) {
const [loading, setLoading] = useState(initial)
const withLoading = async <T,>(fn: () => Promise<T>): Promise<T> => {
setLoading(true)
try { return await fn() }
finally { setLoading(false) }
}
return { loading, setLoading, withLoading }
}
// 使用
function UserList() {
const { loading, withLoading } = useLoading()
const [data, setData] = useState([])
useEffect(() => {
withLoading(() =>
fetch('/api/users').then(r => r.json()).then(setData)
)
}, [])
if (loading) return <Spinner />
return <List data={data} />
}withWindowSize → useWindowSize
// HOC
function withWindowSize(Component) {
return class extends React.Component {
state = { width: window.innerWidth, height: window.innerHeight }
onResize = () => this.setState({
width: window.innerWidth,
height: window.innerHeight,
})
componentDidMount() { window.addEventListener('resize', this.onResize) }
componentWillUnmount() { window.removeEventListener('resize', this.onResize) }
render() { return <Component {...this.props} {...this.state} /> }
}
}// Custom Hook
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
})
useEffect(() => {
const onResize = () => setSize({
width: window.innerWidth,
height: window.innerHeight,
})
window.addEventListener('resize', onResize)
return () => window.removeEventListener('resize', onResize)
}, [])
return size
}
// 使用 —— 任意组件直接调用
function ResponsiveChart() {
const { width } = useWindowSize()
return <Chart width={width} />
}常见 HOC 到 Hook 对照
| HOC 模式 | Hook 替代 |
|---|---|
withAuth(Component) | const { user } = useAuth() |
withLoading(Component) | const { loading, withLoading } = useLoading() |
withWindowSize(Component) | const { width } = useWindowSize() |
withRouter(Component) | useParams(), useNavigate() |
withTranslation(Component) | const { t } = useTranslation() |
connect(mapStateToProps)(Component) | useSelector(state => state.xxx) |
重构清单
1. 确定 HOC 注入的 props(如 user、loading、width)
2. 创建 useXxx Hook,返回这些值
3. 移除 HOC 包裹层
4. 在组件内直接调用 useXxx
5. 删除 HOC 文件小结
- HOC 用
withXxx(Component)注入 props,Hook 用useXxx()返回数据。 - Hook 更灵活:可在组件内任意位置使用,可组合多个 Hook。
- Hook 没有 wrapper 嵌套问题(HOC 嵌套过多形成 wrapper hell)。
- Hook 的类型推断比 HOC 更好。
- 迁移简单:HOC 注入的 props → Hook 返回值。
上一节:this 绑定与闭包 下一节:完整迁移案例
