写在前面的话
根据React官方文档介绍,Hook是React 16.8的新增特性,它可以让你在不编写class的情况下使用state以及其他的React特性。
那么在有class组件的情况下,我们为什么还要用Hook呢?在什么情况下应该用Hook组件呢?
React组件
React可以通过class和function两种方式创建组件。
class组件
class组件实例化要经过几个过程:
- 初始化组件
- 执行构造函数
- 继承父类属性
- 绑定事件
- 初始化state
这样做会带来一定负作用:
- 大量组件实例化过程会占用内存,对性能造成一定影响
- 如果在构造函数里通过bind绑定事件,代码可读性较差;而用箭头函数声明事件处理函数又容易造成子组件的重复渲染
函数组件
函数组件相当于声明了一个可以返回UI的函数,这样做的好处是没有了实例化过程,不会对内存造成负担。相应的,由于没有实例化,函数组件也就没有自己的this,没有自身的state,更不能访问生命周期钩子函数。函数组件又被成为无状态组件。
函数组件渲染需要的数据来自父组件传递的props
什么时候用函数式声明组件?
实际项目开发中的组件一般不会是简单的一层结构,通常是父组件嵌套几个子组件。我们可以把需要用到生命周期处理逻辑、需要维护自身状态的父组件用class声明,简单的只需要接收父组件props的子组件用function声明。
React Hook
随着“函数式编程”思想的深入,React开发了用于函数组件中挂钩状态和生命周期的Hook。有了Hook,组件中的状态不再是聚合成一个对象,而是分开独立管理;而且不再需要在开发时在意生命周期的使用,这给了开发者很大的自由。
Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState
是允许你在 React 函数组件中添加 state 的 Hook.
useState
上图中通过函数式声明了一个叫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.count
和 this.setState
类似,唯一区别就是你需要成对的获取它们。
经过以上操作,我们可以在UI中拿到count作为渲染数据,呈现在视图上
useEffect
在上面的实例中,如果我们想每点击一次按钮控制台就打印一次信息,应该怎么做呢
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 变得更便捷。
复杂组件变得难以理解
我们经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount
和 componentDidUpdate
中获取数据。但是,同一个 componentDidMount
中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount
中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。
在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。
为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。
真正拥抱函数式编程
Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。