Hook
是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。
一、简介:
请记住 Hook 是:
完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。
100% 向后兼容的。 Hook 不包含任何破坏性改动。
现在可用。 Hook 已发布于 v16.8.0。
没有计划从 React 中移除 class。
Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。
i、组件之间复用状态逻辑很难:
React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)。React 需要为共享状态逻辑提供更好的原生途径。
你可以
使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。
Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。
ii、复杂组件变得难以理解:
组件起初很简单,但是逐渐会被
状态逻辑和副作用
充斥。每个生命周期常常包含一些不相关的逻辑。
相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。
在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。
为了解决这个问题,
Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
,而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。
iii、class 是一个障碍:
理解 JavaScript 中
this
的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。
class 不能很好的压缩,并且会使热重载出现不稳定的情况。
为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。
例子:计数器,记录按键次数
在这里,
useState
就是一个
Hook
。通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。
useState
会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的
this.setState
,但是它不会把新的 state 和旧的 state 进行合并。
useState
唯一的参数就是初始 state
。在上面的例子中,我们的计数器是从零开始的,所以初始 state 就是
0
。值得注意的是,不同于
this.state
,
这里的 state 不一定要是一个对象 —— 如果你有需要,它也可以是
。
这个初始 state 参数只有在第一次渲染时会被用到
。
我们声明了一个叫
count
的 state 变量,然后把它设为
0
。React 会在重复渲染时记住它当前的值,并且提供最新的值给我们的函数。我们可以通过调用
setCount
来更新当前的
count
。
(2)等价的class 示例:
state 初始值为
{ count: 0 }
,当用户点击按钮后,我们通过调用
this.setState()
来增加
state.count
。
对比class和hook两个例子:
i、
在 class 中,我们通过在构造函数中设置
this.state
为
{ count: 0 }
来初始化
count
state 为
0;
ii、在函数组件中,我们没有
this
,所以我们不能分配或读取
this.state
。我们直接在组件中调用
useState
Hook。
(const [count, setCount] = useState(0);)
方括号是数组解构
。
🌟(3)关于useState使用:
i、
调用 useState
方法的时候做了什么? --
它定义一个 “state 变量”。我们的变量叫
count
, 但是我们可以叫他任何名字,比如
banana
。这是一种在函数调用时保存变量的方式 ——
useState
是一种新方法,
它与 class 里面的
this.state
提供的功能完全相同
。一般来说,在函数退出后变量就就会”消失”,
而 state 中的变量会被 React 保留。
ii、
useState
需要哪些参数?
useState()
方法里面
唯一的参数就是初始 state
。不同于 class 的是,我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。在示例中,只需使用数字来记录用户点击次数,所以我们传了
0
作为变量的初始 state。(如果我们想要在 state 中存储
两个不同的变量,只需调用
useState()
两次
即可。)
iii、
useState
方法的返回值是什么?
返回值为:
当前 state 以及更新 state 的函数
。这就是我们写
const [count, setCount] = useState()
的原因。这与 class 里面
this.state.count
和
this.setState
类似
,唯一区别就是你需要成对的获取它们。
(4)为什么叫useState:
“Create” 可能不是很准确,因为 state 只在组件首次渲染的时候被创建。在下一次重新渲染时,
useState
返回给我们当前的 state。否则它就不是 “state”了!这也是 Hook 的名字
总是
以
use
开头的一个原因。
🌟(5)如何读取state?
i、class中: { this.state.count }
ii、hook中: { count }
🌟(6)更新 state?
i、class中,用
this.setState
( { count : this.state.count + 1 } )
ii、hook中,已经有setCount 和count 变量: setCount( count + 1 )
(7)为什么react知道这个hook对应哪个组件?
React 保持对当先渲染中的组件的追踪。
当你用
useState()
调用一个 Hook 的时候,它会
读取当前的单元格(或在首次渲染时将其初始化),然后把指针移动到下一个。
这就是多个
useState()
调用会得到各自独立的本地 state 的原因。
(8)用函数组件和hook 取代clss:
函数组件,曾经称为“无状态组件”,但现在为他们引入使用React state(hook造成的)。
三、使用 Effect Hook:(两种常见副作用操作:需要清除的和不需要清除的)
(1)Effect Hook: { useEffect }
可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。
useEffect
就是一个 Effect Hook
,给函数组件增加了操作副作用的能力。它跟 class 组件中的
componentDidMount
、
componentDidUpdate
和
componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。
举例1: 上面例子添加功能--更新DOM后设置一个页面标题:(运行“副作用”)
当你调用
useEffect
时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。
由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
(2)不需清除的effect:
在 React 更新 DOM 之后运行一些额外的代码。比如发送
网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作
。因为我们在执行完这些操作之后,就可以忽略他们了。
class中的副作用实现:
render
函数是不应该有任何副作用的
。我们基本上都
希望在 React 更新 DOM 之后才执行我们的操作
。这就是为什么在 React class 中,我们把副作用操作放到
componentDidMount
和
componentDidUpdate
函数中。
对比hook和class 在副作用中的区别:
i、
useEffect
做了什么?
--
通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。在这个 effect 中,我们设置了 document 的 title 属性,不过我们也可以执行数据获取或调用其他命令式的 API。
ii、
为什么在组件内部调用
useEffect
?
--
将
useEffect
放在组件内部让我们
可以在 effect 中直接访问
count
state 变量(或其他 props)
。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。
Hook 使用了 JavaScript 的闭包机制
,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。
iii、
useEffect
会在每次渲染后都执行吗?
--
是的,默认情况下,它在第一次渲染之后
和
每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
与
componentDidMount
或
componentDidUpdate
不同,使用
useEffect
调度的 effect
不会阻塞浏览器更新屏幕
,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。
(3)需要清楚的 effect:
例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以
防止引起内存泄露
!
举例2: (清除副作用)订阅好友的在线状态,并通过取消订阅来清除操作:
React 会在组件销毁时取消对
ChatAPI
的订阅,然后在后续渲染时重新执行副作用函数。
通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里。
在class中会这样做:
componentDidMount
和
componentWillUnmount
之间相互对应。使用生命周期函数迫使我们拆分这些逻辑代码,即使这两部分代码都作用于相同的副作用。
对比hook和class 的副作用写法:
为什么要在 effect 中返回一个函数?
--
这是 effect 可选的清除机制
。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
React 何时清除 effect?
--
React 会
在组件卸载的时候执行清除操作
。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React
会
在执行当前 effect 之前对上一个 effect 进行清除。
(4)使用Effect 的提示:
i、使用多个 Effect 实现关注点分离:
使用 Hook 其中一个目的就是要
解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题
。
上面hook,effect事例就是使用多个effect的例子。
Hook 允许我们按照代码的用途分离他们, 而不是像生命周期函数那样。
React 将按照 effect 声明的顺序依次调用组件中的
每一个
effect。
ii、为什么每次更新的时候都要运行 Effect:
并不需要特定的代码来处理更新逻辑(class中的
componentDidUpdate
),因为
useEffect
默认
就会处理。它会在调用一个新的 effect 之前对前一个 effect 进行清理。
这是很常见的需求,所以它被内置到了
useEffect
的 Hook API 中。如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为
useEffect
的第二个可选参数即可:
如果
count
的值是
5
,而且我们的组件重渲染的时候
count
还是等于
5
,React 将对前一次渲染的
[5]
和后一次渲染的
[5]
进行比较。因为数组中的所有元素都是相等的(
5 === 5
),React 会跳过这个 effect,这就实现了性能的优化。
对于清除操作的effect(return 即清除)同样:
如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以
传递一个空数组(
[]
)
作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。请记得 React 会等待浏览器完成画面渲染之后才会延迟调用
useEffect
,因此会使得额外操作很方便。
只能在函数最外层调用 Hook。
不要在循环、条件判断或者子函数中调用。
只能在 React 的函数组件中调用 Hook。
不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。)
同时,我们提供了
linter 插件
来自动执行这些规则。这些规则乍看起来会有一些限制和令人困惑,但是要让 Hook 正常工作,它们至关重要。