React 16.8 正式推出了 Hooks
的特性,这期间也从一些方面了解尝试过 Hooks,谨参考多篇文章来谈谈 Hooks
。
Hooks
可以让你不需要通过类来使用React
的state
以及其它特性。你也可以自定义自己的Hooks
来在组件之间共享可复用的状态逻辑 。
起源#
当下,组件以及自顶向下的数据流可以帮助我们将大型 UI 组织成小型、独立可复用的部分。但是很多时候,我们的逻辑是带有状态的,因此不能进一步打破复杂的组件,也不能拆分出一个函数或是另一个组件。这些情况包括动画、表单控件、连接外部数据源 等等是非常普遍的 。
虽然现在对于 React
应用我们已经有很多种方式来复用这些逻辑,比如写简单的函数来调用,或是通过组件(函数或类的形式),但是对于非可视化的逻辑它就不是那么方便了,所以我们又提出了一些复杂的模式,比如 render props
和 高阶组件 。
如果 React
只有一种通用的方式来复用逻辑是不是会更简单呢?
对于代码复用,函数似乎是一种完美的机制,但是函数并不能包含本地 React
的状态,我们不能简单从类组件提取一些行为,比如 依据 window 的大小来更新状态 或是 让一个值随时间变化。我们通常都需要重构这部分代码或者是采用抽象的模式比如观察者。这些都破坏了 React
的简洁性 。
基于以上的思考,React
开发团队提出了 Hooks
。它可以让我们通过函数来使用 React
的特性(比如状态),并且 React
官方也提供了一些内置的与 React
构建模块相关的 Hooks
: 状态、生命周期、上下文,因为Hooks
就是普通的 JavaScript
函数,因此我们也可以自定义 Hooks
来使用 。
Demo#
让我们看一个例子:随 window 大小的调整来更新 React
的状态 。
import { useState, useEffect } from 'react'
function useWindowWidth () {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
})
return width
}
上述代码用到了 React
自带最常用的两个 Hooks
: useState
和 useEffect
:
React
基础的Hooks
还有一个 useContext 。
同时我们还有一个基于内置 Hooks
的自定义 Hooks
: useWindowWidth
。
我们来看看 useWindowWidth
做了什么:传入 window 的初始大小,返回状态值 width
和可以更新 width
的 setWidth
,在 useEffect
内部我们定义了一个通过 setWidth
设置 window 大小的 handleResize
函数,并且监听了 window resize 事件来更新 window 的大小,最终返回取消监听的函数 。
这就是一个可以随 window 大小来调整布局的 Hooks
。
试想一下如果我们用类组件的方式来实现会是怎样?
class setWidth extends React.Component {
constructor (props) {
super(props)
this.state = {
width: window.innerWidth
}
}
handleResize = () => this.setState({ width: window.innerWidth })
componentDidMount () {
window.addEventListener('resize', handleResize)
}
componentWillUnmount () {
window.removeEventListener('resize', handleResize)
}
render () {
return <SomeUI />
}
}
对比之下我们很容易感觉到类组件的写法我们要设置 state
、结合繁琐的生命周期以及必须要渲染 UI (当然可以是 null
),并且更大的问题是如果我们需要复用这个组件同时 <SomeUI />
又不能相同,我们往往就会采用高阶组件的写法,这又引入了抽象的模式。所以对比下来, Hooks
让开发者可以有更小的学习成本,同时也更贴合 React
简洁的理念 。
这里有一个类组件与
Hooks
更直观的对比的视频 : https://twitter.com/i/status/1056960391543062528 。
同时我们也能感受到自定义 Hooks
的魅力,随着 Hooks
的正式提出,之后将会有越来越多的 Hooks
npm 包来更好的帮助开发者 。
这里有一个各类自定义
Hooks
实现的网站 : https://usehooks.com/ 。
深入#
作为一个 React
开发者,Hooks
刚提出时我是感到很惊艳的,React
从一开始的定位就是为了更好地构建 UI,这就带给了我两个疑问 :
Hooks
的出现代表了什么?Hooks
是怎么实现的?
在 对 React Hooks 的一些思考 中有一句话 : "有状态的组件没有渲染,有渲染的组件没有状态"。React
团队想要推行的正确理念就是 "状态与 UI 分开"。而这其实也正与 React Hooks
的特性相合,我们来看一下使用 Hooks
的 两个规则 :
- 只在函数顶部调用
Hooks
。 - 只在
React
函数中调用Hooks
。
Hooks
必须集中在函数顶部来写,这其实很容易养成书写无状态 UI 组件的习惯,因此也会更容易践行 "状态与 UI 分开" 这个理念。个人觉得这也是 React
团队将 Hooks
视为 状态共享问题 长久以来最完美的解决方案的原因之一 。
那么为什么 Hooks
必须在函数顶部被调用?是为了防止我们用条件判断来包裹 Hooks
。为什么不能用条件判断包裹就涉及到 Hooks
的实现问题,简单解释一下就是 : Hooks
并不是通过 Proxy
或者 getters
实现的,而是通过类似数组的实现方式,因此 Hooks
的正常调用与下标顺序有关,每次 useState
都会改变下标,如果在条件判断中使用了 Hooks
,可能就会影响下标造成 Hooks
调用出错,因此官方也建议在 Hooks
内部进行条件判断是更正确的做法 。
具体原因可以见 官方文档 。
Hooks
的原理可以见 React hooks: not magic, just arrays 。如果你想更深入地了解
Hooks
体系,可以见 Under the hood of React’s hooks system 。
可能有人会感觉到 Hooks
是带有一些限制的,但是更正确的理解是我们应该遵循 Hooks
的约定,它也是 React
官方第一次将 "约定优先" 的理念引入 React
框架当中,有了限制,但也有了更好的便利性 。
在
Next.js
和Umi
中都有 "约定路由" 的功能,这大大降低了路由配置的复杂度,而Hooks
就像代码级别的约定,大大降低了代码的复杂度,因此我们可以考虑这是不是未来很重要的一个趋势 。
更多思考#
Hooks
的设计并不是绑定在 React
上的,现在也已经有了 Vue
、Web Components
甚至是原生 JavaScript
函数的实现(实验性的 API 设计)。
你可能会有这样的疑问 : Vue
需要 Hooks
吗? Vue
确实没有像 React
类组件一样的问题,如果是需要复用逻辑的话,Mixins
也可以解决。但是从本质上看的话,Vue
的确需要 Hooks
来解决 状态共享 的问题,相比于 Mixins
,Hooks
可以帮助 Vue
解决两个主要问题 :
- 实现状态的共享 。
- 更清晰地展示逻辑的来向 。
目前 Vue
团队也计划在 Vue
3.0 中集成 Hooks
,但是会偏离 React
的 API 而贴合 Vue
的思想去设计,并且很有可能会成为 Mixins
的替代品,因此也是十分值得探索的 。
Vue 实验性质的实现 : https://github.com/yyx990803/vue-hooks
参考 :