写在前面的话

根据React官方文档介绍,Hook是React 16.8的新增特性,它可以让你在不编写class的情况下使用state以及其他的React特性。

那么在有class组件的情况下,我们为什么还要用Hook呢?在什么情况下应该用Hook组件呢?

React组件

React可以通过class和function两种方式创建组件。

class组件

截屏2020-07-26 下午5.13.24.png
class组件实例化要经过几个过程:

  • 初始化组件
  • 执行构造函数
  • 继承父类属性
  • 绑定事件
  • 初始化state

这样做会带来一定负作用:

  • 大量组件实例化过程会占用内存,对性能造成一定影响
  • 如果在构造函数里通过bind绑定事件,代码可读性较差;而用箭头函数声明事件处理函数又容易造成子组件的重复渲染

函数组件

截屏2020-07-26 下午5.21.48.png
函数组件相当于声明了一个可以返回UI的函数,这样做的好处是没有了实例化过程,不会对内存造成负担。相应的,由于没有实例化,函数组件也就没有自己的this,没有自身的state,更不能访问生命周期钩子函数。函数组件又被成为无状态组件。

函数组件渲染需要的数据来自父组件传递的props

什么时候用函数式声明组件?

实际项目开发中的组件一般不会是简单的一层结构,通常是父组件嵌套几个子组件。我们可以把需要用到生命周期处理逻辑、需要维护自身状态的父组件用class声明,简单的只需要接收父组件props的子组件用function声明。

React Hook

随着“函数式编程”思想的深入,React开发了用于函数组件中挂钩状态和生命周期的Hook。有了Hook,组件中的状态不再是聚合成一个对象,而是分开独立管理;而且不再需要在开发时在意生命周期的使用,这给了开发者很大的自由。

Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook.

useState

截屏2020-07-26 下午5.53.22.png
上图中通过函数式声明了一个叫Counter的组件,并调用useState给组件声明了一个count的state。

调用useState时做了什么?
它定义一个 “state 变量”。这里的变量叫 count, 但是我们可以叫他任何名字,比如 banana。这是一种在函数调用时保存变量的方式 —— useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。

useState需要哪些参数?
useState() 方法里面唯一的参数就是初始 state。不同于 class 的是,我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。在示例中,只需使用数字来记录用户点击次数,所以传了 0 作为变量的初始 state。(如果我们想要在 state 中存储两个不同的变量,只需调用 useState() 两次即可。)

useState返回什么?
返回值为:当前 state 以及更新 state 的函数。这就是我们写 const [count, setCount] = useState() 的原因。这与 class 里面 this.state.countthis.setState 类似,唯一区别就是你需要成对的获取它们。

经过以上操作,我们可以在UI中拿到count作为渲染数据,呈现在视图上

useEffect

在上面的实例中,如果我们想每点击一次按钮控制台就打印一次信息,应该怎么做呢
截屏2020-07-26 下午6.10.17.png
Effect Hook可以让你在函数组件中执行副作用操作。

useEffect做了什么?
通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。

为什么在组件内部调用useEffect?
useEffect 放在组件内部让我们可以在 effect 中直接访问 count state 变量(或其他 props)。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。

useEffect会在每次渲染后执行吗?
是的,默认情况下,它在第一次渲染之后_和_每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

可以说useEffect可以替代class组件中的componentDidMount、componentDidUpdate、componentWillUnmount生命周期,每次组件渲染完后组件都会执行useEffect的操作。

为什么useEffect能拿到每次更新的值?
组件每次渲染后都会生成新的effect,覆盖之前的值,这时拿到的count也是最新的

useEffect传递的参数
useEffect中传递的第一个参数是一个函数,表示组件渲染后要执行的操作。我们还可以在组件中传第二个参数——这个参数表示useEffect执行的依赖,也就是说,只有这个参数发生变化时useEffect才会执行,这就实现了useEffect的优化

如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。

如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

React会在渲染完成后延迟一段时间执行useEffect,这使得额外操作很方便。

useEffect返回值
每个effect都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于effect的一部分。可以认为返回的函数会在组件卸载时执行,用来清理定时器等工作

React何时清楚effect?
React 会在组件卸载的时候执行清除操作。正如之前学到的effect在每次渲染的时候都会执行。这就是为什么React会在执行当前 effect 之前对上一个 effect 进行清除。

为什么用Hook组件?

在组件之间复用状态逻辑很难

React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)。如果你使用过 React 一段时间,你也许会熟悉一些解决此类问题的方案,比如 render props高阶组件。但是这类方案需要重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。如果你在 React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。

你可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。

复杂组件变得难以理解

我们经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMountcomponentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。

在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。

为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。

真正拥抱函数式编程

Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。


菜菜的电冰箱
15 声望0 粉丝

程序媛