React 从类组件到 Hooks:为什么 Hooks 改变了 React?

学习 React 时,经常会看到两种写法:

scala 复制代码
class App extends React.Component {}

csharp 复制代码
function App() {}

很多新手会疑惑:

复制代码
为什么 React 有两种组件?

为什么现在项目几乎都在使用函数组件?

Hooks 又是什么?

想搞懂这些问题,就需要了解 React 的发展历史。


React 16.8 之前:类组件时代

在 React 16.8 之前:

swift 复制代码
函数组件
=
UI展示

类组件
=
真正开发业务

原因很简单:

复制代码
只有类组件拥有状态和生命周期

因此绝大部分业务代码都写在类组件中。


什么是类组件?

类组件本质上是一个 JavaScript Class。

例如:

scala 复制代码
import React from 'react'

class App extends React.Component {

  render() {
    return (
      <h1>Hello React</h1>
    )
  }

}

export default App

React 会创建类的实例:

scss 复制代码
new App()

然后渲染页面。


什么是状态(State)?

React 中最重要的概念之一:

复制代码
State(状态)

状态可以理解为:

复制代码
数据发生变化

组件会重新渲染

例如:

复制代码
计数器数字

用户信息

购物车数量

主题颜色

这些都属于状态。


类组件中的状态

类组件使用:

kotlin 复制代码
this.state

保存状态。

例如:

scala 复制代码
class App extends React.Component {

  state = {
    count: 0
  }

  render() {
    return (
      <h1>{this.state.count}</h1>
    )
  }
}

为什么不能直接修改 state?

错误写法:

kotlin 复制代码
this.state.count++

React 不会更新页面。

因为:

复制代码
React无法感知变化

setState

React 提供:

kotlin 复制代码
this.setState()

修改状态。

例如:

kotlin 复制代码
this.setState({
  count: 1
})

执行过程:

复制代码
修改状态

↓

触发重新渲染

↓

更新页面

因此:

ini 复制代码
setState
=
修改状态

+
触发更新

生命周期

类组件最大的特点之一:

复制代码
生命周期(Lifecycle)

组件从出生到销毁:

复制代码
创建

↓

运行

↓

更新

↓

销毁

React 在这些阶段提供对应钩子函数。


componentDidMount

组件挂载完成后执行。

javascript 复制代码
componentDidMount() {

}

触发时机:

复制代码
页面首次渲染完成

常见用途:

复制代码
发送请求

获取数据

订阅事件

开启定时器

例如:

scss 复制代码
componentDidMount(){

  fetch('/api/user')

}

componentDidUpdate

组件更新后执行。

javascript 复制代码
componentDidUpdate() {

}

触发时机:

perl 复制代码
state变化

props变化

之后执行。


例如:

复制代码
数据更新

↓

组件重新渲染

↓

componentDidUpdate

componentWillUnmount

组件销毁前执行。

javascript 复制代码
componentWillUnmount() {

}

常见用途:

复制代码
清除定时器

解绑事件

取消订阅

例如:

scss 复制代码
componentWillUnmount(){

  clearInterval(timer)

}

类组件的问题

随着项目越来越大:

kotlin 复制代码
生命周期逻辑分散

this容易丢失

代码复用困难

逻辑组织混乱

例如:

复制代码
获取数据

↓

componentDidMount

处理更新

↓

componentDidUpdate

清理资源

↓

componentWillUnmount

同一个功能被拆散到多个地方。

维护成本越来越高。


React 16.8:Hooks 诞生

2019年 React 发布:

复制代码
React Hooks

这是 React 历史上最重要的更新之一。

目标:

复制代码
让函数组件拥有类组件的能力

从此:

diff 复制代码
函数组件

=

状态

+

生命周期

+

逻辑复用

函数组件时代

现代 React 项目几乎全部采用:

csharp 复制代码
function App() {

}

写法。

例如:

javascript 复制代码
function App() {

  return (
    <h1>Hello React</h1>
  )

}

Hooks 是什么?

官方定义:

复制代码
Hook 是特殊函数

允许函数组件使用 React 特性

最常用的两个 Hook:

复制代码
useState

useEffect

useState

作用:

复制代码
给函数组件定义状态

例如:

javascript 复制代码
import { useState } from 'react'

function App() {

  const [count, setCount] =
    useState(0)

  return (
    <button
      onClick={() =>
        setCount(count + 1)
      }
    >
      {count}
    </button>
  )
}

useState 结构

scss 复制代码
const [state, setState] =
  useState(initialValue)

例如:

scss 复制代码
const [count, setCount] =
  useState(0)

对应:

复制代码
count
↓
当前状态

setCount
↓
修改状态

useEffect

作用:

复制代码
处理副作用

例如:

复制代码
发送请求

操作DOM

监听事件

定时器

这些都属于副作用。


useEffect 默认执行时机

scss 复制代码
useEffect(() => {

})

触发:

diff 复制代码
首次渲染

+

每次重新渲染

只执行一次

scss 复制代码
useEffect(() => {

}, [])

触发:

复制代码
仅组件首次加载

等价于:

复制代码
componentDidMount

监听某个变量

scss 复制代码
useEffect(() => {

}, [count])

触发:

diff 复制代码
首次渲染

+

count变化时

例如:

复制代码
count更新

↓

useEffect执行

useEffect 返回函数

scss 复制代码
useEffect(() => {

  return () => {

  }

}, [])

返回函数称为:

javascript 复制代码
Cleanup Function
清理函数

执行时机:

复制代码
组件卸载前

例如:

scss 复制代码
useEffect(() => {

  const timer =
    setInterval(() => {},1000)

  return () => {

    clearInterval(timer)

  }

}, [])

作用:

复制代码
释放资源

避免内存泄漏

生命周期与 Hooks 对应关系

类组件:

复制代码
componentDidMount

componentDidUpdate

componentWillUnmount

函数组件:

scss 复制代码
useEffect(() => {

  // mount

  return () => {

    // unmount

  }

}, [])

再配合依赖数组:

scss 复制代码
useEffect(() => {

  // update

}, [state])

基本覆盖生命周期需求。


为什么 Hooks 更优秀?

1. 没有 this

类组件:

kotlin 复制代码
经常出现this指向问题

函数组件:

kotlin 复制代码
没有this

逻辑更简单。


2. 更容易复用逻辑

多个组件共享逻辑:

复制代码
自定义Hook

即可实现。


3. 代码更集中

类组件:

复制代码
一个功能

分散在多个生命周期

Hooks:

复制代码
一个功能

集中在一个useEffect

维护成本更低。


面试高频题

什么是 State?

复制代码
状态

数据变化会导致组件重新渲染

类组件如何定义状态?

kotlin 复制代码
this.state

如何修改状态?

kotlin 复制代码
this.setState()

useState 的作用?

复制代码
为函数组件定义状态

useEffect 默认什么时候执行?

diff 复制代码
首次渲染

+

每次重新渲染

useEffect(\[\]) 什么时候执行?

复制代码
仅首次加载

useEffect(x) 什么时候执行?

diff 复制代码
首次加载

+

x变化时

useEffect 返回函数有什么作用?

复制代码
组件卸载前执行

用于资源清理

一张图记忆

markdown 复制代码
React

│

├── React16.8之前
│
│    └── 类组件
│          │
│          ├── state
│          ├── setState
│          └── 生命周期
│
└── React16.8之后
      │
      └── Hooks
            │
            ├── useState
            └── useEffect

生命周期

componentDidMount
        ↓

componentDidUpdate
        ↓

componentWillUnmount

对应

useEffect

核心口诀

diff 复制代码
React16.8之前:

类组件

state

setState

生命周期

----------------

React16.8之后:

函数组件

Hooks

----------------

useState:

定义状态

setState:

修改状态

----------------

useEffect():

首次加载
+
每次更新

useEffect([],):

仅首次加载

useEffect([x]):

首次加载
+
x变化

----------------

return函数:

组件卸载前执行
用于清理资源
相关推荐
程序员黑豆1 天前
Java包装类:基本类型与对象的桥梁
java·前端·ai编程
bugcome_com1 天前
JWT 知识小课堂:从入门到精通,一文吃透 JSON Web Token
前端·json
To_OC1 天前
写了三天 Next.js,我的 React 世界观被拆了又装回去
前端·全栈·next.js
独泪了无痕1 天前
viewer.js 安装与配置指南:实现图片预览功能
前端·vue.js
起床学FPGA1 天前
AI回答问题之后,会自动跳到最下面的问题
开发语言·javascript·ecmascript
Fluxart.ai1 天前
电商商品图审核怎么自动化?规则引擎、人工复核与发布门禁
java·前端·自动化
why技术1 天前
AI 写的文章,可能都带着手敲一遍都去不掉的“隐形水印”。
前端·人工智能·后端
愚公搬代码1 天前
【愚公系列】《Android应用案例开发大全》016-LBS类应用掌上杭州(辅助工具类的开发)
android·前端
CodeSheep1 天前
又一个华为天才少年,离职了!
前端·后端·程序员
kyriewen1 天前
面试官说"打开你的AI工具"——我才发现,他考的根本不是写代码
前端·人工智能·面试