前端开发

React Transform:从 HOC 到 Hooks 的组件演进之路

2024-09-01 wangjun 13 min read
<>

逻辑复用的永恒命题

React 组件开发的核心挑战之一是 逻辑复用。如何将状态管理、副作用、数据获取等 Transform 逻辑从组件中抽离,在不同组件间共享?React 的答案经历了三次范式迁移。

第一代:HOC (Higher-Order Components)

HOC 是一个接收组件、返回新组件的函数 -- 本质上是对组件做 Transform:

function withLoading(Component) {
  return function WithLoadingWrapper(props) {
    if (props.isLoading) return <Spinner />;
    return <Component {...props} />;
  };
}


const UserListWithLoading = withLoading(UserList);

问题:HOC 嵌套会产生"包装地狱"(Wrapper Hell),props 来源不透明,DevTools 中难以追踪。

第二代:Render Props

Render Props 通过一个返回 React 元素的函数 prop 来共享逻辑:

class MouseTracker extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (e) => {
    this.setState({ x: e.clientX, y: e.clientY });
  };

  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}


<MouseTracker render={({ x, y }) => (
  <h1>Mouse at ({x}, {y})</h1>
)} />

问题:嵌套使用时回调地狱严重,代码可读性差。

第三代:Hooks

Hooks 是 React 16.8 的革命性特性,允许在函数组件中"挂钩"状态和副作用:

useState -- 状态 Transform

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

useEffect -- 副作用 Transform

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(setUser);
  }, [userId]); // 仅当 userId 变化时重新执行

  if (!user) return <Spinner />;
  return <Profile data={user} />;
}

自定义 Hook -- 逻辑复用的终极方案

function useMousePosition() {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handler = (e) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener("mousemove", handler);
    return () => window.removeEventListener("mousemove", handler);
  }, []);

  return pos;
}


function App() {
  const { x, y } = useMousePosition();
  return <h1>Mouse at ({x}, {y})</h1>;
}

Hooks 的 Transform 本质

Hooks 之所以优于 HOC 和 Render Props,是因为它改变了逻辑组合的方式:

  • HOC:纵向组合 -- 组件层层包裹,props 透明度低
  • Render Props:横向组合 -- 回调嵌套,代码缩进深
  • Hooks:线性组合 -- 逻辑像管道一样串联,每个 Hook 是一个 Transform 步骤

Hooks 规则的背后逻辑

Hooks 必须在顶层调用,不能在条件分支或循环中。这是因为 React 内部用链表存储 Hooks 状态,调用顺序就是索引。如果顺序变化,状态映射就会错乱 -- 这是一种 顺序敏感的 Transform

从 HOC 到 Hooks,React 的每次演进都是在重新思考"如何 Transform 组件逻辑"。Hooks 的胜利,本质上是声明式组合战胜了命令式包装。