前言
◼ Hook 是 React 16.8 的新增特性,它可以让我们在不编写class的情况下使用state以及其他的React特性(比如生命周期)。
◼ 我们先来思考一下class组件相对于函数式组件有什么优势?比较常见的是下面的优势:
◼ class组件可以定义自己的state,用来保存组件自己内部的状态;
函数式组件不可以,因为函数每次调用都会产生新的临时变量;
◼ class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑;
比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次;
函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求;
◼ class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等;
函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次;
◼ 所以,在Hook出现之前,对于上面这些情况我们通常都会编写class组件。
在react中组件分为类组件和函数组件
类组件:
- 通过class类去定义组件
- 类组件有内部state状态,可以通过setState控制组件自身更新渲染
- 类组件拥有自己的生命周期
class App extends React.Componnet {
constructor(props) {
super(props);
}
click = () => {
}
render() {
return (
<div onClick={this.click}>类组件</div>
)
}
}
Class组件存在的问题
◼ 复杂组件变得难以理解:
我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂;
比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在componentWillUnmount中移除);
而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度;
◼ 难以理解的class:
很多人发现学习ES6的class是学习React的一个障碍。
比如在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this;
虽然我认为前端开发人员必须掌握this,但是依然处理起来非常麻烦;
◼ 组件复用状态很难:
在前面为了一些状态的复用我们需要通过高阶组件;
像我们之前学习的redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用;
或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套;
这些代码让我们不管是编写和设计上来说,都变得非常困难;
函数组件
- 使用函数编写组件
- 函数组件没有内部state,无法控制自身更新渲染,只能接收外部props数据来更新渲染
- 函数组件没有生命周期
- 函数式组件的命名:第一个字母必须大写
function App() {
function click() {
}
return (
<div onClick={click}>函数组件</div>
)
}
Hooks概述
随着react版本的更迭,在react16.8版本,新增了hook特性,可以通过hook函数去开发函数组件,使函数组件内部拥有了state内部状态,以及模拟生命周期
Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用!!!
Hooks
:钩子、钓钩、钩住Hooks
是 React v16.8 中的新增功能- 作用:为函数组件提供状态、生命周期等原本 class 组件中提供的 React 功能
-
- 可以理解为通过 Hooks 为函数组件钩入 class 组件的特性
- 注意:Hooks 只能在函数组件中使用,自此,函数组件成为 React 的新宠儿
React v16.8 版本前后,组件开发模式的对比:
- React v16.8 以前: class 组件(提供状态) + 函数组件(展示内容)
- React v16.8 及其以后:
-
- class 组件(提供状态) + 函数组件(展示内容)
- Hooks(提供状态) + 函数组件(展示内容)
- 混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件
注意1:虽然有了 Hooks,但 React 官方并没有计划从 React 库中移除 class。
注意2:有了 Hooks 以后,不能再把函数组件称为无状态组件了,因为 Hooks 为函数组件提供了状态。
为什么要有 Hooks
两个角度:1 组件的状态逻辑复用 2 class 组件自身的问题
- 组件的状态逻辑复用:
-
- 在 Hooks 之前,组件的状态逻辑复用经历了:mixins(混入)、HOCs(高阶组件)、render-props 等模式。
- (早已废弃)mixins 的问题:1 数据来源不清晰 2 命名冲突。
- HOCs、render-props 的问题:重构组件结构,导致组件形成 JSX 嵌套地狱问题。
- class 组件自身的问题:
-
- 选择:函数组件和 class 组件之间的区别以及使用哪种组件更合适
- 需要理解 class 中的 this 是如何工作的
- 相互关联且需要对照修改的代码被拆分到不同生命周期函数中
-
-
- componentDidMount -> window.addEventListener('resize', this.fn)
- componentWillUnmount -> window.addEventListener('resize', this.fn)
-
- 相比于函数组件来说,不利于代码压缩和优化,也不利于 TS 的类型推导
- 语法上来说,函数组件学习成本会低一点,函数组件少了this指向和继承
- 从组件的开发难易程度来说,函数组件相对于类组件来说难度低一点,函数组件少了this指向和继承
- 类组件的开发基于面向对象方式开发,类与类之间通过继承来达到了一种依赖关系,这种继承的开发方式并不是最好的解决方案
正是由于 React 原来存在的这些问题,才有了 Hooks 来解决这些问题
hooks的优势
由于原来 React 中存在的问题,促使 React 需要一个更好的自带机制来实现组件状态逻辑复用。
- Hooks 只能在函数组件中使用,避免了 class 组件的问题
- 复用组件状态逻辑,而无需更改组件层次结构
- 根据功能而不是基于生命周期方法强制进行代码分割
- 抛开 React 赋予的概念来说,Hooks 就是一些普通的函数
- 具有更好的 TS 类型推导
- tree- - shaking 友 好,打包时去掉未引用的代码
- 更好的压缩
项目开发中,Hooks 的采用策略:
- 不推荐直接使用 Hooks 大规模重构现有组件
- 推荐:新功能用 Hooks,复杂功能实现不了的,也可以继续用 class
- 找一个功能简单、非核心功能的组件开始使用 hooks
class 组件相关的 API 不用了,比如:
class Hello extends Component
componentDidMount
、componentDidUpdate
、componentWillUnmount
this
相关的用法
原来学习的内容还是要用的,比如:
- JSX:
{}
、onClick={handleClick}
、条件渲染、列表渲染、样式处理等 - 组件:函数组件、组件通讯
- 路由
- React 开发理念:
单向数据流
、状态提升
等 - 解决问题的思路、技巧、常见错误的分析等上
hook函数使用规则
- 只在 React 函数中调用 Hook,不要在普通的 JavaScript 函数中调用 Hook
- 只能在最顶层使用 Hook
- 不要在循环,条件或嵌套函数中调用 Hook,确保总是在你的 React 函数的最顶层调用他们
- 遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用
- 这让 React 能够在多次的
useState
和useEffect
调用之间保持 hook 状态的正确 - 不要在class类组件中使用hook函数
hooks 的缺陷
计数器示例
类组件实现
import React, { PureComponent } from 'react'
export default class demoClassComponent extends PureComponent {
constructor(props) {
super(props)
this.state = {
counter: 0,
}
}
increment() {
this.setState({
counter: this.state.counter + 1,
})
}
decrement() {
this.setState({
counter: this.state.counter - 1,
})
}
render() {
const { counter } = this.state
return (
<div>
<p>当前计数:{counter}</p>
<button onClick={(e) => this.increment()}>+1</button>
<button onClick={(e) => this.decrement()}>-1</button>
</div>
)
}
}
函数组件实现
import { memo, useState } from 'react'
function DemoFuncClassComponent() {
const [counter, setCounter] = useState(0)
return (
<div>
<p>当前计数:{counter}</p>
<button onClick={(e) => setCounter(counter + 1)}>+1</button>
<button onClick={(e) => setCounter(counter - 1)}>-1</button>
</div>
)
}
export default memo(DemoFuncClassComponent)