746 字
4 分钟
高阶组件(HOC)

它是什么#

高阶组件是一个接收组件、返回新组件的函数。它自己不是组件:组件收 props 返回 JSX,HOC 收组件返回组件。

它不是 React 的 API,而是基于组合形成的一种模式。当前的 react.dev 上没有它的专页,能查到的经典说明在已归档的旧站:Higher-Order Components。下面几条约定都出自那一页,本文会标出来。

一个例子#

function withAuth(WrappedComponent) {
function WithAuth({ role, ...rest }) {
if (role !== 'admin') {
return <p>没有访问权限</p>
}
return <WrappedComponent {...rest} />
}
WithAuth.displayName = `withAuth(${WrappedComponent.displayName || WrappedComponent.name})`
return WithAuth
}
const ProtectedPanel = withAuth(AdminPanel)

三点值得留意:它没有改 AdminPanel,只是包了一层;{...rest} 把与权限无关的 props 透传下去;displayName 让这层包装在 DevTools 里认得出名字。

四条约定#

不要修改被包装的组件本身。别去动它的原型,也别给它挂方法,那是污染而不是复用。

透传不相关的 props。HOC 只取自己需要的那几个,其余原样传给被包装组件,否则外面传的 props 会凭空消失。

不要在组件的渲染函数里创建包装后的组件:

// 不行:每次渲染都是一个新组件,内部的 state 会被丢掉
function Page() {
const Enhanced = withAuth(AdminPanel)
return <Enhanced />
}
// 可以:在模块顶层创建一次
const Enhanced = withAuth(AdminPanel)

给返回的组件设置 displayName。默认情况下 DevTools 里只会看到一层匿名的包装。

ref 这件事在 React 19 变了#

18 里 ref 是特殊 prop,HOC 包一层就会挡住它,必须用 forwardRef 显式转发:

// 18 的写法
function withLog(Wrapped) {
return forwardRef((props, ref) => <Wrapped {...props} ref={ref} />)
}

19 起 ref 就是普通 prop,HOC 只要把 props 透传,ref 自然跟着走。读旧代码时会看到 forwardRef 那层壳,它现在已标为弃用(见 useRef)。

今天更常用的替代#

逻辑复用首选自定义 Hook。同一件事,两种写法对比:

// HOC:包一层才能用
const UserListWithData = withData('/api/users')(UserList)
// Hook:组件内部直接调
function UserList() {
const { data, loading } = useData('/api/users')
// ...
}

Hook 版没有多出来的组件层级,props 从哪来一眼可见,类型也更好推。HOC 版在 DevTools 里多一层,props 的来源要翻包装函数才知道。

所以新代码里,逻辑复用走 自定义 Hook。

HOC 仍然合适的场景有两类:读旧代码(class 时代的组件库、部分路由与状态库的老 API),以及确实要「把一个组件换成另一个组件」的地方,比如权限包装、给一批组件统一加埋点、用类组件写错误边界再包出去。

什么时候不要用#

为了包一层而包一层。用 HOC 往下传数据(那是 Hook 的事)。嵌套超过两三层:每一层都可能改写 props,出问题时得一层层往回翻。

装饰器写法(@withLog)需要额外配置编译工具,新代码里不推荐用它来套 HOC。

第 8 篇,共 39 篇本文属于系列

高阶组件(HOC)
https://www.choria.top/posts/hoc/
作者
乔瑞雅·克林威尔
发布于
2026-05-03
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 189 天

部分内容可能已过时