React18入门(第三篇)——React Hooks详解,React内置Hooks、自定义Hooks使用

news2025/4/22 2:18:02

文章目录

    • 概述
    • 一、内置 Hook——useState
        • 1.1 响应式数据更新
        • 1.2 什么是 state
        • 1.3 state 特点(一)——异步更新
        • 1.4 state 特点(二)——可能会被合并
        • 1.5 state 特点(三)——不可变数据(重要!!!)
        • 1.6 使用 immer 修改 state
    • 二、内置 Hook——useEffect
        • 2.1 作用及使用
        • 2.2 执行时机
        • 2.3 useEffect 执行两次
    • 三、其他内置 Hooks
        • 3.1 useRef —— 用于Dom节点,用于JS变量
        • 3.2 useMemo —— 缓存变量
        • 3.3 useCallback—— 缓存函数
    • 四、自定义 Hook
        • 4.1 React Hooks 的正确打开方式
        • 4.2 React 组件公共逻辑的抽离和复用
        • 4.3 自定义 Hook —— 修改网页标题
    • 五、第三方 Hooks
        • 5.1 常见的第三方 Hooks
        • 5.2 ahooks 的使用
    • 六、Hooks 使用规则

概述

React Hooks 可以说是 React 最重要 的内容之一。常见的 React Hooks 命名 通常 以 use 开头,比如 useState、useEffect 等。
本文将采用图文详解的方式,带你快速了解:React 内置 Hooks、自定义 Hooks(复用代码)、第三方 Hooks 的使用。


一、内置 Hook——useState

1.1 响应式数据更新
import React, { useState } from 'react'

function App() {
  // let count = 0  // 普通的 js 变量,无法触发组件的更新
  const [count, setCount] = useState(0) // useState 可以触发组件的更新

  function add() {
    setCount(count + 1)
    console.log(count, 'count')
  }
  return (
    <>
      <div>
        <button onClick={add}>add {count}</button>
      </div>
    </>
  )
}
export default App


1.2 什么是 state
  • props 是父组件传递过来的信息
  • state 是组件内部的状态信息,不对外
  • state 变化,触发组件更新,重新渲染 rerender 页面
1.3 state 特点(一)——异步更新
  • 写一个简单的累加方法,打印出来的 count 永远是累加之前的 count 。并非是同步更新
import React, { FC, useState } from 'react'

const StateDemo: FC = () => {
  const [count, setCount] = useState(0) // useState 可以触发组件的更新
  const [name, setName] = useState('张三')

  function add() {
    // 写法一:
    setCount(count + 1)
    // 写法二:
    // setCount(count => count + 1)
    
    /** 打印出来的 count 永远是累加之前的 count */
    console.log(count, 'cur count') // 异步更新,无法直接拿到最新的 state 值 
  }

  return (
    <>
       <button onClick={add}>add {count}</button>
    </>
  )
}
export default StateDemo
  • 如果说一个变量不用于 JSX 中显示,那就不要用 setState 来管理它,用 useRef
import React, { FC, useState } from 'react'

const StateDemo: FC = () => {
  const [name, setName] = useState('张三')

  function add() {
    setName('李四')
    console.log(name) // 如果说一个变量不用于 JSX 中显示,那就不要用 setState 来管理它,用 useRef
  }
}
export default StateDemo
1.4 state 特点(二)——可能会被合并
import React, { FC, useState } from 'react'

const StateDemo: FC = () => {
  const [count, setCount] = useState(0) // useState 可以触发组件的更新

  function add() {
    /** 方法一:看代码觉得可能会实现 count + 5,但由于是异步更新,每次执行的时候,count 任然是0。所以最后仍然是 count + 1 */
    setCount(count + 1)
    setCount(count + 1)
    setCount(count + 1)
    setCount(count + 1)
    setCount(count + 1)
    
    /** 方法二:可以实现 count + 5 */
    setCount(count => count + 1)
    setCount(count => count + 1)
    setCount(count => count + 1)
    setCount(count => count + 1)
    setCount(count => count + 1)
    
    console.log(count, 'cur count')
  }

  return (
    <>
      <div>
        <button onClick={add}>add {count}</button>
      </div>
    </>
  )
}
export default StateDemo
1.5 state 特点(三)——不可变数据(重要!!!)

注:编辑 state 的数据只能是传入一个的数据进行覆盖,而不能修改原数据

import React, { FC, useState } from 'react'
import QuestionCard from './components/QuestionCard'

const List2: FC = () => {
  // 问卷列表数据
  const [questionList, setQuestionList] = useState([
    { id: 'q1', title: '问卷1', isPublished: false },
    { id: 'q2', title: '问卷2', isPublished: true },
    { id: 'q3', title: '问卷3', isPublished: true },
    { id: 'q4', title: '问卷4', isPublished: false },
  ])

  // 新增问卷
  function handleAdd() {
    // 生成三位随机数
    const r = Math.random().toString().slice(-3)
    setQuestionList(
      questionList.concat({
        id: 'q' + r,
        title: '问卷' + r,
        isPublished: false,
      })
    )
  }

  // 删除问卷
  function deleteQuestion(id: string) {
    setQuestionList(questionList.filter(item => item.id != id))
  }

  // 编辑问卷
  function publishedQuestion(id: string) {
    setQuestionList(
      questionList.map(item => {
        if (item.id !== id) return item
        return {
          ...item,
          isPublished: true,
        }
      })
    )
  }
}
export default List2
1.6 使用 immer 修改 state
  • state 是不可变数据
  • state 操作成本高,有很大不稳定性
  • 使用 immer 可避免这一个问题

安装 immer

npm install immer --save

使用 immer

import React, { FC, useState } from 'react'
import { produce } from 'immer'

const Demo: FC = () => {
  const [userInfo, setUserInfo] = useState({ name: '张三', age: 24 })

  // 修改个人信息
  const handleChangeUser = () => {
    setUserInfo(
      // 通过 immer,即可让我们便捷的修改数据
      produce(draft => {
        draft.name = '李四'
      })
    )
  }
  return (
    <div>
      <div>{JSON.stringify(userInfo)}</div>
      <button onClick={handleChangeUser}>change age</button>
    </div>
  )
}
export default Demo

二、内置 Hook——useEffect

2.1 作用及使用
import React, { FC, useState, useEffect } from 'react'
import { produce } from 'immer'
import QuestionCard from './components/QuestionCard'

const List2: FC = () => {
  /** 方式一:直接发起 Ajax 请求。不行,state值改变时会触发请求,造成重复发送冗余请求 */
  // console.log('方式一加载 Ajax 网络请求')

  /** 方式二:此方法即可 */
  useEffect(() => {
    console.log('方式二加载 Ajax 网络请求')
  }, []) // 第二个参数为依赖项, 数组中可以放多个 state 值,当 state 值改变时,会触发 函数(第一个参数)的执行。若为空,
  const [questionList, setQuestionList] = useState([
    { id: 'q1', title: '问卷1', isPublished: false },
    { id: 'q2', title: '问卷2', isPublished: true },
    { id: 'q3', title: '问卷3', isPublished: true },
    { id: 'q4', title: '问卷4', isPublished: false },
  ])
}
export default List2
2.2 执行时机

组件创建、销毁以及依赖的 state 数据改变时,会执行

import React, { FC, useState, useEffect } from 'react'
import { produce } from 'immer'
import QuestionCard from './components/QuestionCard'

const List2: FC = () => {
  /** 方式一:直接发起 Ajax 请求。不行,会频繁的重复请求 */
  // console.log('方式一加载 Ajax 网络请求')

  /** 方式二:此方法即可 */
  useEffect(() => {
    // 组件挂载时执行
    console.log('component mounted')

    return () => {
      // 组件销毁时执行
      console.log('component unmounted')
    }
  }, [questionList]) // 如果此数组中依赖数据,则依赖数据更新时会执行
  const [questionList, setQuestionList] = useState([
    { id: 'q1', title: '问卷1', isPublished: false },
    { id: 'q2', title: '问卷2', isPublished: true },
  ])
}
export default List2
2.3 useEffect 执行两次
  • React18 开始,useEffect 在开发环境下会执行两次
  • 目的是模拟左键创建、销毁、再创建的完整流程、及早暴露问题
  • 生产环境下会执行一次

三、其他内置 Hooks

3.1 useRef —— 用于Dom节点,用于JS变量
  • 一般用于操作 DOM
  • 也可以传入普通的 JS 变量,但更新不会触发 rerender
  • 要和 Vue3 ref 区分开(如果你用过 Vue3)

示例代码一:操作 DOM

import React, { FC, useRef } from 'react'

const UseRefDemo: FC = () => {
  const inputRef = useRef<HTMLInputElement>(null)

  function handelSelect() {
    const element = inputRef.current  // 可以拿到 DOM 节点,进行 DOM 操作
    element?.select()
  }
  return (
    <div>
      <input ref={inputRef} defaultValue="Hello Word" />
      <button onClick={handelSelect}>select button</button>
    </div>
  )
}
export default UseRefDemo

示例代码二:不会触发 rerender

import React, { FC, useRef } from 'react'

const UseRefDemo: FC = () => {
  const name = useRef('张三')

  function handleChangeName() {
    name.current = '王小虎' // 修改 ref 的值,不会触发 rerender(修改 state 的值,会触发 rerender)
    console.log(name.current, 'now name') // 此时页面仍然是 张三,但是此处打印出来的是 王小虎
  }
  return (
    <div>
      <div>{name.current}</div>
      <button onClick={handleChangeName}>select button</button>
    </div>
  )
}
export default UseRefDemo
3.2 useMemo —— 缓存变量
  • 函数组件,每次 state 更新都会重新执行函数
  • useMemo 可以缓存数据,不用每次执行函数都重新生成
  • 可用于计算量较大的场景,缓存提高性能

使用示例代码:

import React, { FC, useMemo, useState } from 'react'

const Demo: FC = () => {
  const [num1, setNum1] = useState(10)
  const [num2, setNum2] = useState(20)
  const [test, setTest] = useState('测试')  // 更新组件,导致组件 rerender

  const sum = useMemo(() => {
    return num1 + num2
  }, [num1, num2])
  return (
    <>
      <p> {sum} </p>
      <p> {num1}{' '}<button onClick={() => { setNum1(num1 + 1) }}>add num1</button></p>
      
      <p> {num2}{' '} <button onClick={() => {setNum2(num2 + 2)}}>add num2</button></p>
      <div>
        {/* form组件,受控组件 */}
        <input value={test} onChange={e => setTest(e.target.value)} />
      </div>
    </>
  )
}
export default Demo
3.3 useCallback—— 缓存函数
  • 和 useMemo 作用类似
  • 专门用于 **缓存函数 **
  • 使用时根据业务而定,不能为了优化二优化
import React, { FC, useState, useCallback } from 'react'

const Demo: FC = () => {
  const [text, setText] = useState('Hello')

  // fn1 是只要组件重新更新、重新执行,那么 fn1 就会被重新定义 -- 无缓存
  const fn1 = () => console.log('执行 fn1 ')

  // fn2 用了 useCallback ,就会根据依赖项去重新定义 -- 有缓存
  const fn2 = useCallback(() => {
    console.log('执行 fn2')
  }, [text]) // 依赖项,与 useEffect,useMemo,useCallback 用法类似

  // 缓存,为了性能优化,提升时间效率
  // 但是不能为了优化,要根据业务而定

  return (
    <>
      <div>
        <button onClick={fn1}>fn 1</button> &nbsp; <button onClick={fn2}>fn 2</button>
      </div>
      {/* form组件,受控组件 */}
      <input value={text} onChange={e => setText(e.target.value)} />
    </>
  )
}
export default Demo

四、自定义 Hook

4.1 React Hooks 的正确打开方式
  • 内置 Hooks 保证基础功能
  • 内置 Hooks 灵活配合,实现业务功能
  • 抽离功能部分,自定义 Hooks 或者第三方 Hooks —— 复用嗲马
4.2 React 组件公共逻辑的抽离和复用
  • 之前是 class 组件,现在是函数组件
  • class 组件:Mixin(混合)、 HOC(高阶组件)、 render-props(渲染属性) 来复用公共逻辑
  • 函数组件:使用 Hooks —— 当前最完美的解决方案,Vue3也在参考
4.3 自定义 Hook —— 修改网页标题

4.3.1 定一个 hooks

// /src/hooks/useTitle.ts
import { useEffect } from 'react'

function useTitle(title: string) {
  useEffect(() => {
    // 修改网页标题
    document.title = title
  }, [])
}
export default useTitle

4.3.2 使用 自定义 hooks

import React from 'react'
// 引入 自定的 hooks
import useTitle from './hooks/useTitle'

function App() {
  ... 其他代码
  
  // 使用自定义的 hooks
  useTitle('React 自定义 Hook 学习')
 
 ... 其他代码
}
export default App

4.3.3 页面效果
在这里插入图片描述


五、第三方 Hooks

5.1 常见的第三方 Hooks
  • ahooks – 国内常用 官网地址:https://ahooks.js.org/
  • react-use – 国外常用
5.2 ahooks 的使用

安装 ahooks

npm install --save ahooks

使用

import React from 'react';
import { useTitle } from 'ahooks';

export default () => {
  useTitle('Page Title');

  return (
    <div>
      <p>Set title of the page.</p>
    </div>
  );
};

六、Hooks 使用规则

  • 必须用 useXxx 格式来命名
  • 只能在两个地方调用 Hook (*组件内、其他 Hook 内 *)
  • 必须保证每次调用顺序一致(不能将 Hook 放在 if for 内部)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1087828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

看一下链表结构

序、慢慢来才是最快的方法。 背景 链表(Linked List) 链表是一种常见的基础数据结构&#xff0c;是一种线性表。与顺序表不同的是&#xff0c;链表中的每个节点不是顺序存储的&#xff0c;而是通过节点的指针域指向到下一个节点。 1.链表的优缺点 2.链表的类型 单链表、双链表…

UDP 的报文结构

UDP的报文结构&#xff1a; 其中前面的源端口号和目的端口号&#xff0c;UDP长度和UDP检验和&#xff0c;它们都是2个字节。 那么什么是UDP长度呢&#xff0c;它指的是后面的数据的长度&#xff0c;换算单位也就是64kb&#xff0c;因此一个数据报&#xff08;数据&#xff09;最…

idea 启动项目报错 Command line is too long

1.idea 启动报错 Command line is too long&#xff0c;启动报错信息&#xff1a;Error running ‘Application‘: Command line is too long. 2.如何解决&#xff1f; 1&#xff09;idea打开一个项目。 2.打开项目下的*.idea* 文件夹下的 workspace.xml 文件。 3.在<co…

【高阶数据结构】图详解第三篇:最小生成树(Kruskal算法+Prim算法)

文章目录 最小生成树1. 最小生成树概念2. Kruskal算法算法思想代码实现测试 3. Prim算法算法思想代码实现测试 4. 源码 最小生成树 1. 最小生成树概念 在了解最小生成树之前&#xff0c;我们先来回顾一下生成树的概念&#xff0c;这是我们之前文章提到过的&#xff1a; 无向图…

QMA6100P 姿态传感器使用

QMA6100P 姿态传感器使用 项目用途&#xff1a;分析和记录设备位置信息变化&#xff08;设备安装在车辆内部&#xff09; 通讯接口&#xff1a;I2C&#xff0c;地址0x13&#xff0c;标准I2C通讯采用IO模拟 功能需求&#xff1a;读取三轴加速度 芯片初始化设置 参考手册说明和…

STM32 芯片怎么选型?

SMT32概览&#xff1a; STM32F051R8T6x xx 代表的含义 STM32MPU产品型号含义 STM32芯片封装&#xff1a;

浮点数运算以及溢出问题

一、浮点数加减运算的步骤 对阶&#xff1a;小阶向大阶对齐&#xff0c;阶小的那个数尾数右移&#xff0c;对于IEEE754标准表示的浮点数来说&#xff0c;右移时要注意将隐含的一位1右移到小数部分尾数加减&#xff1a;注意要先还原隐藏位尾数规格化&#xff1a;直到将第一位1移…

外汇天眼:外汇投资出入金难?教你一个快速到账的方法!

对于每一位投资者来说&#xff0c;在外汇市场中盈利赚钱才是最重要的事&#xff01;而对盈利来说最重要的事就是能够顺利地出入金&#xff01; 但在复杂的外汇市场中&#xff0c;摒弃黑平台&#xff0c;在正规平台出入金很多时候也并没有那么顺利&#xff0c;所以很多外汇投资…

MySQL常用脚本

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《ELement》。&#x1f3af;&#x1f3af; &#x1…

Godot2D角色导航-自动寻路教程(Godot实现角色随鼠标移动)

文章目录 运行结果2D导航概述开始前的准备2D导航创建导航网格创建角色 其他文章 运行结果 2D导航概述 Godot为2D和3D游戏提供了多个对象、类和服务器&#xff0c;以便于基于网格或基于网格的导航和路径查找。 说到导航&#xff0c;就得说一下导航网格&#xff0c;导航网格定义…

Design patterns--策略模式

设计模式之策略模式 笔者经常使用Modbus TCP和Modbus RTU通信协议&#xff0c;而两种的请求数据的格式不一样&#xff0c;故而采用策略模式来健壮整个工程项目。 代码示例 #ifndef MODBUS_H #define MODBUS_H #include <string>std::string convertToHex(unsigned char…

Spring framework Day09:JSR250注入注解

前言 JSR 250 是 Java Specification Request 的缩写&#xff0c;它定义了一组用于依赖注入和生命周期管理的注解。这些注解是在 Java EE 5 规范中引入的&#xff0c;用于简化和标准化开发企业级应用程序的依赖注入和生命周期管理。 一、开始学习 本次需要学习的依然是注解&…

淘宝商品品牌数据采集接口,淘宝商品详情数据接口,淘宝API接口

采集淘宝商品品牌数据&#xff0c;可以按照以下步骤进行&#xff1a; 确定采集目标&#xff1a;确定要采集的淘宝商品品牌和具体的产品类型。选择采集工具&#xff1a;可以选择爬虫、数据抓取工具等采集工具进行数据采集。设置采集参数&#xff1a;设置采集参数&#xff0c;包…

ROS中的图像数据

无论是USB摄像头还是RGBD摄像头&#xff0c;发布的图像数据格式多种多样&#xff0c;在处理这些数据之前&#xff0c;我们首先需要了解这些数据的格式。 二维图像数据 连接USB摄像头到PC端的USB接口&#xff0c;通过以下命令启动摄像头&#xff1a; roslaunch usb_cam usb_ca…

PHP 员工工资管理系统mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP 员工工资管理系统是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 php员工工资管理系统 代码 https://download.csdn.net/download/qq_41221322/884215…

睿趣科技:未来抖音开网店还有前景吗

随着科技的快速发展&#xff0c;电商平台已经成为了人们生活中不可或缺的一部分。在中国&#xff0c;抖音作为一个短视频平台&#xff0c;近年来迅速崛起&#xff0c;吸引了大量的用户和商家。那么&#xff0c;在未来&#xff0c;抖音是否还能为商家提供一个有效的电商平台呢?…

【LeetCode】2319. 判断矩阵是否是一个X矩阵

难度&#xff1a;简单 题目 如果一个正方形矩阵满足下述 全部 条件&#xff0c;则称之为一个 X 矩阵 &#xff1a; 矩阵对角线上的所有元素都 不是 0矩阵中所有其他元素都是 0 给你一个大小为 n x n 的二维整数数组 grid &#xff0c;表示一个正方形矩阵。如果 grid 是一个…

小谈设计模式(28)—解释器模式

小谈设计模式&#xff08;28&#xff09;—解释器模式 专栏介绍专栏地址专栏介绍 解释器模式角色分析抽象表达式&#xff08;Abstract Expression&#xff09;终结符表达式&#xff08;Terminal Expression&#xff09;非终结符表达式&#xff08;Non-terminal Expression&…

opencv图像卷积操作和常用的图像滤波函数

文章目录 opencv图像卷积操作原理&#xff0c;opencv中常用的图像滤波函数一、图像卷积操作原理&#xff1a;1、卷积操作原理图&#xff1a; 二、opencv常用的图像滤波函数&#xff1a;这些函数的主要作用是对图像进行平滑处理或去除噪声(核心目的是减少图像中的噪声&#xff0…

PG14归档失败解决办法archiver failed on wal_lsn

问题描述 昨晚RepmgrPG14主备主库因wal日志撑爆磁盘&#xff0c;删除主库过期wal文件重做备库后上午进行主备状态巡查&#xff0c;主库向备库发送wal文件正常&#xff0c;但是查主库状态时发现显示有1条归档失败的记录。 postgres: archiver failed on 000000010000006F000000…