React Hooks简介

一.出发点

在 React 现有的组件模型下,存在很多难以解决的问题:

  • 难以跨组件复用状态逻辑

  • 组件复杂度高难以理解

  • Class 的诸多弊病

  • ……

而 Hooks,肩负着破局使命

组件间逻辑复用

组件间逻辑复用一直是个问题,Render PropsHigher-Order Components等常用套路模式都是为了分离横切关注点(Cross-cutting concern),复用诸如:

  • 日志

  • 缓存/同步/持久化

  • 数据校验

  • 错误捕获/异常处理

的逻辑,目的是将横切关注点与核心业务逻辑分离开,以便专注于业务逻辑

P.S.关于切面、关注点等 AOP 概念的更多信息,见AOP(Aspect-Oriented Programming)

然而,HOC 与 Render Props 虽然能以组件形式分离横切关注点,但也带来了一些新问题:

  • 扩展性限制

  • Ref 传递问题

  • Wrapper Hell

之所以会出现这些问题,根本原因在于:

细粒度代码复用不应该与组件复用捆绑在一起

而一直以来都缺少一种简单直接的组件行为扩展方式:

React doesn’t offer a way to “attach” reusable behavior to a component (for example, connecting it to a store).

提出 Hooks 的主要目的就是为了解决这个问题:

React needs a better primitive for sharing stateful logic.

P.S>关于组件间逻辑复用方式的更多信息,见React 组件间逻辑复用

组件复杂度问题

如你所见,React 组件正在变得越来越复杂:

Provider, Consumer, Higher-order Component, Render Props
// with Redux
Action, Reducer, Container
// with Reselect
Selector
// with xxx ...

等诸多抽象层缓解了状态逻辑的组织和复用问题,但随之而来的问题是组件复用成本更高了,不再是简单地引入组件就能获得完整的业务功能

诚然,这些细分抽象层能让代码职责变得更加清晰,但状态逻辑也被打散了,难以复用。因而组件复用程度大多停留在 View Component (View + UI State Logic)层面,而无法更进一步复用 Business Component (View + Business State Logic)。除非将这些业务状态逻辑(请求数据、订阅其它数据源、定时器等)全都收拢到单一组件里(比如改用mobxjs/mobx管理状态)。即便这样,也无法避免组件中掺杂着的副作用,以及生命周期方法中混在一起的不相干的逻辑,比如componentDidMount里含有数据请求、事件监听等……我们发现,真正有内在关联的代码被生命周期拆开了,而完全不相干的代码最终却凑到了一个方法里

Mutually related code that changes together gets split apart, but completely unrelated code ends up combined in a single method.

按组件生命周期拆分逻辑让组件体积迅速膨胀,而且这种巨大组件不容易拆成一堆小组件,因为状态逻辑到处都有,还难以测试

因此,需要一种更合理的、更容易复用的状态逻辑组织方式,让有内在关联的代码聚在一起,而不是被生命周期方法强制拆开

P.S.关于 MobX 的更多信息,见MobX

Class 弊病

Class 作为对象模具,是 OOP 中相当重要的一部分。然而,用 Class 来定义(视图与逻辑相结合的)组件却不那么理想:

  • 让代码难以组织/复用

  • 带来更高的学习成本

  • 阻碍编译优化

代码组织/复用方面,Class 的生命周期方法是典型的例子,一个组件只能存在一个特定的生命周期方法,因而只能将一些不相干的逻辑放在一起,并且这种模板式的划分让具有内在关联的代码被拆开了,不利于代码复用

学习成本上,主要体现在两点:

  • 要理解 JavaScript 中的this(与其它语言不一样),并记着bind(this)

  • 理解函数式组件与 Class 组件的区别,及各自的应用场景

编译优化方面,Class 让一些工具优化效果大打折扣,例如:

  • 组件提前编译(ahead-of-time compilation)效果不理想(React 团队已经在这方面做了一些尝试,发现 Class 组件不利于编译优化)

  • Class 不利于代码压缩

  • 难以正确热重载(hot reloading)

P.S.组件提前编译类似于GCC 的高级模式,对defaultProps常量等进行内联优化,并去除无用代码

因此,希望提供一套编译优化友好的 API:

We want to present an API that makes it more likely for code to stay on the optimizable path.

所以抛弃 Class,拥抱函数:

Hooks let you use more of React’s features without classes.

P.S.并非转投函数式编程,全面引入 FP 概念,而是提供了通向命令式编程的“逃生舱”,而不必掌握函数式编程、响应式编程等技术:

Hooks provide access to imperative escape hatches and don’t require you to learn complex functional or reactive programming techniques.

二.目标

为了解决以上种种问题,Hooks 应运而生,目标是:

  • 提供一种简单直接的代码复用方式

  • 提供一种更合理的代码组织方式

  • 提供一种 Class 的替代方案

一方面解决代码组织、复用的问题,另一方面,新的组件定义方式也是 React 未来愿景的一部分:

Hooks represent our vision for the future of React.

那么,Hooks 到底是个什么东西?

三.定位

Hooks 是一些能让函数式组件接入 React State 和生命周期等特性的函数

Hooks are functions that let you “hook into” React state and lifecycle features from function components.

一方面借助 Hooks 更合理地拆分/组织代码,解决复用问题,另一方面通过 Hooks 增强函数式组件,让其拥有与 Class 组件相同的表达力,进而成为一种替代选项,最终取而代之

四.作用

Hooks 主要解决了代码组织、逻辑复用方面的问题,例如:

  • 组织被生命周期拆开的关联逻辑,如数据源订阅/取消订阅、事件监听注册/注销等

  • 跨组件复用散落在生命周期中的重复逻辑,同时解决 HOC 和 Render Props 等基于组件组合的复用模式带来的组件嵌套问题(Wrapper Hell)

此外,对 React 自身而言,Hooks 还解决了大规模优化上的阻碍,比如内联组件的编译难题

代码组织

Hooks 方案下,最大的区别在于,可以将组件基于代码块的内在关联拆分成一些小函数,而不是强制按照生命周期方法去拆分

Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.

例如:

// 自定义Hook
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);
  // 注册/注销外部数据源
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

注册/注销外部数据源的代码紧密地联系在一起,而不用再关心调用时机(组件生命周期)的差异

逻辑复用

同时,如上面示例,这些状态逻辑和副作用能被轻松抽离到 Hooks 中,并组合成 Custom Hook,漂亮地解决了状态逻辑的复用问题:

With Hooks, you can extract stateful logic from a component so it can be tested independently and reused.

例如:

// View组件1
function FriendStatus(props) {
  // 使用自定义Hook
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
// View组件2
function FriendListItem(props) {
  // 使用自定义Hook
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

另一方面,这种复用方式是“无伤的”,不必调整组件树层级结构,即引即用

Hooks allow you to reuse stateful logic without changing your component hierarchy. This makes it easy to share Hooks among many components or with the community.

五.总结

单从形式上看,Hooks 是对函数式组件的增强,使之能与类组件平起平坐,甚至(期望)取而代之。实质意义在于进一步将更多的函数式思想引入到前端领域,比如 Effect、Monad 等。算是在提出v = f(d)的 UI 层函数式思路之后,在这条路上的进一步探索

(摘自React 16 Roadmap

从某种程度上来讲,这种思想风暴是比 Concurrent Mode 等核心特性更激动人心的

参考资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code