前端开发
React Transform:从 HOC 到 Hooks 的组件演进之路
<>
逻辑复用的永恒命题
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 的胜利,本质上是声明式组合战胜了命令式包装。