React进阶笔记【4_React-Hooks 简介】

2022/4/5 ReactJS

React-Hooks 自 React 16.8 以来才真正被推而广之,它的背后涉及对 类组件函数组件 两种组件形式的思考和侧重。

# 类组件

class Demo extends React.Component {
  state = {
    text: ""
  }
  
  componentDidMount(){}
  
  render(){
    return (
    	<div className='demo'>
        {this.state.text}
      </div>
    )
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 函数组件(无状态组件)

function DemoFunction(props){
  const { text } = props
  return (
  	<div className='demo'>
      {text}
    </div>
  )
}
1
2
3
4
5
6
7
8

# 对比

类组件和函数组件的区别大致有以下几点:

  • 类组件需要继承 class,函数组件不需要
  • 类组件可以访问生命周期方法,函数组件不能
  • 类组件中可以获得实例化后的 this,并基于 this 做很多事情,函数组件不能
  • 类组件可以定义并维护 state,函数组件不能

在 Reack-Hooks 出现之前,类组件的能力明显强于函数组件,类组件固然强大,但它绝非万能。而 函数组件会捕获 render 内部的状态,这是两类组件最大的不同。

React 组件本身的定位是函数:一个吃数据,吐 UI 的函数。开发者编写声明式代码,而 React 把声明式代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中,也就是说 React 的数据应该总是和渲染绑定在一起,而类组件做不到这一点,函数组件真正地把数据和渲染绑定到了一起。

为什么类组件不可以?

因为虽然 props 本身是不可变的,但是 this 是可变的,this 上的数据是可以被修改的,this.props 每次都会获取最新的 props

React-Hooks 给函数组件提供了丰富的零件,从而让函数组件发挥到极致。

# useState

useState 为函数组件引入状态,同样逻辑的函数组件相比类组件而言,复杂度要低得多。

import React, { useState } from 'react'
export default function Demo(){
  const [text, setText] = useState('初始文本')
  
  function changeText(){
    return setText('修改后的文本')
  }
  return (
  	<div className='demo'>
      <p>{text}</p>
      <button onClick={changeText}>点击修改</button>
    </div>
    
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

useState 返回的是一个数组,第一个元素对应 state 变量,第二个元素对应 能够修改这个变量的 API。

在调用 React.useState 的时候,实际上是给这个组件关联了 一个 状态。

# useEffect

useEffect 允许函数组件执行副作用操作,相当于它是一个为函数组件引入副作用的钩子,在一定程度上弥补了生命周期的缺席。

import React, { useState, useEffect } from 'react'
function Increase(){
  const [count, setCount] = useState(0)
  
  useEffect(() => {
    const list = document.getElementById('list')
    const item = document.createElement('li')
    item.innerHTML = '哈哈'
    list.append(item)
  })
  
  return (
  	<div>
    	<p>count为:{count}</p>
      <ul id='list'></ul>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 调用规则

  1. 每一次渲染后都执行的副作用:传入回调函数,不传依赖数组

    useEffect(callback)
    
    1
  2. 仅在挂载阶段执行一次副作用:传入回调函数,且该函数的返回值不是一个函数,同时传入一个空数组

    useEffect(() => {
      // 业务逻辑
    }, [])
    
    1
    2
    3
  3. 仅在挂载阶段和卸载阶段执行的副作用:传入回调函数,且这个函数的返回值是一个函数,同时传入一个空数组

    // 将在挂载阶段执行A,卸载阶段执行B
    useEffect(() => {
      // 业务逻辑A
      
      // 返回一个函数记为B
      return () => {
        
      }
    }, [])
    
    // 此处的B被叫做 “清除函数”,类似于 componentWillUnmount 的逻辑
    // 只要在 useEffect 回调中返回了一个函数,它就会被作为清除函数来处理
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  4. 每次渲染都触发,且卸载阶段也会被触发的副作用:传入回调函数,且这个函数的返回值是一个函数,同时不传第二个参数

    useEffect(() => {
      // 业务逻辑A
      
      // 返回一个函数记为B
      return (() => {
        
      })
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
  5. 根据一定的依赖条件来触发副作用:传入回调函数,同时传入一个非空的数组

    useEffect(() => {
      // 业务逻辑
      
      // 若 xxx 是一个函数,则 xxx 会在组件卸载时被触发
      return xxx
    }, [n1, n2])
    
    // n1, n2 一般来源于组件本身的数据,若数组不为空,那么 react 会在一次
    // 新的渲染中对比前后两次数组内的数据是否有变化,并且在有更新的前提下,
    // 去触发 useEffect 里的副作用逻辑
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

# 为什么需要 React-Hooks?

  1. 告别难以理解的 Class

    Class 有两大痛点:this生命周期

    • this 使用不当会有很大的问题(于是用 bind、箭头函数等解决)
    • 生命周期增加了学习成本,同时也存在不合理的逻辑规划方式:逻辑曾经一度与生命周期耦合在一起。
  2. 解决业务逻辑难以拆分的问题

  3. 使状态逻辑复用变得简单可行

  4. 函数组件从设计思想上来看,更加契合 React 的理念:UI = render(data)

React-Hooks 的好处:

  1. 更好的逻辑拆分,按照逻辑上的关联拆分进不同的函数组件里,从而帮助实现业务逻辑的聚合,避免复杂的组价和冗余的代码。
  2. 状态复用,之前靠的是 HOC(高阶组件)和 Render Props 这些组件设计模式,不过这些设计模式同时也会破坏组件的结构,最常见的就是 “嵌套地域”。而 Hooks 可以看作是 React 未解决状态逻辑复用这个问题所提供的一个原生途径,现在可以通过自定义 Hook 来达到既不破坏组件结构、又能够实现逻辑复用的效果。

# 使用原则

  1. 只在 React 函数中调用 Hook
  2. 不要在循环、条件或嵌套函数中调用 Hook
  3. 要确保 Hooks 在每次渲染时都保持同样的执行顺序

# 原理

Hooks 的正常运作,在底层依赖于顺序链表。

# 调用链路

useState 为例,下图是首次渲染:

下图为更新渲染:

以上两个链路,区别就在于一个是 mountState,一个是 updateState

  • mountState(首次渲染)构建链表并渲染
  • updateState 依次遍历链表并渲染

hooks 的渲染是通过 “依次遍历” 来定位每个 hooks 内容的。如果前后两次读到的链表在顺序上出现差异,那么渲染的结果是不可控的(比如你想改 name 字段,但是它却改成了 age 字段)。

hooks 的本质是链表