DrillLab
第 69 / 105 道69 / 105 · #335

什么是 HOC

What is HOC

先自己答,再往下看Answer it yourself first

一句话:高阶组件 ——接收一个组件、返回一个增强后的新组件的函数。它是「复用组件逻辑」的老方案。

withRouterconnect(Redux)、withStyles 都是 HOC。 本质就是 #292 的高阶函数用在组件上。

三个必须注意的点(考点在这):

  • 要透传 props——<Comp {...props} />, 不然把原来的 props 吞了。
  • 要拷贝静态方法—— 包一层之后原组件的静态属性丢了 (hoist-non-react-statics 干这事)。
  • ref 传不进去—— 要用 forwardRef

为什么现在少用了(这才是重点):

  • wrapper 地狱—— 叠三四层之后 DevTools 里全是嵌套, 难调试。
  • props 来源不明—— 组件里出现一个 user prop, 你不知道是哪个 HOC 注进来的。
  • 命名冲突—— 两个 HOC 都注入 data 就打架了。

自定义 hook 解决了全部三条: 平铺、来源显式 (const user = useUser()一眼看出来)、 名字由你决定。所以现在优先写 hook。

会追问:「那 HOC 还有用吗?」—— 有两个 hook 替代不了的场合:需要「包裹」渲染结果(比如给所有页面套一层错误边界或布局)、 以及要改写 props 后再传给一个你无法修改的组件

In one line: a higher-order component — a function that takes a component and returns an enhanced one. It is the old answer to reusing component logic.

withRouter, connect (Redux) and withStyles are all HOCs. It is nothing more than the higher-order function from #292 applied to components.

Three things you must get right — the marks are here:

  • Forward the props <Comp {...props} />, or you swallow the ones the component already had.
  • Copy the statics — wrapping loses the original component’s static properties (hoist-non-react-statics exists for this).
  • Refs do not pass through — you need forwardRef.

Why it fell out of favour — this is the real point:

  • Wrapper hell — stack three or four and DevTools is nothing but nesting; debugging hurts.
  • Props of unknown origin — a user prop shows up in the component and you cannot tell which HOC injected it.
  • Name collisions — two HOCs both injecting data fight each other.

Custom hooks fix all three: they stay flat, the origin is explicit (const user = useUser() says it out loud), and you choose the name. So hooks come first now.

Follow-up: “Is there still a use for HOCs?” — two places hooks cannot cover: when you need to wrap the rendered output (putting an error boundary or a layout around every page), and when you have to rewrite props before handing them to a component you cannot modify.

JSXHOC 与 hook 的对比示意Illustrative
1// HOC
2function withAuth(Comp) {
3 return function Wrapped(props) {
4 const user = useUser();
5 if (!user) return <Login />;
6 return <Comp {...props} user={user} />; // 记得透传 props
7 };
8}
9
10// 同一件事用 hook:平的,而且来源一眼看得出
11function Page() {
12 const user = useUser(); // ← 明确知道 user 从哪来
13 if (!user) return <Login />;
14 return <Content user={user} />;
15}
1// HOC
2function withAuth(Comp) {
3 return function Wrapped(props) {
4 const user = useUser();
5 if (!user) return <Login />;
6 return <Comp {...props} user={user} />; // remember to pass props through
7 };
8}
9
10// The same thing with a hook: flat, and you can see where the value came from
11function Page() {
12 const user = useUser(); // ← it is obvious where user comes from
13 if (!user) return <Login />;
14 return <Content user={user} />;
15}