一.出发点
在 React 现有的组件模型下,存在很多难以解决的问题:
难以跨组件复用状态逻辑
组件复杂度高难以理解
Class 的诸多弊病
……
而 Hooks,肩负着破局使命
组件间逻辑复用
组件间逻辑复用一直是个问题,Render Props、Higher-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 等核心特性更激动人心的