• React18入门(第三篇)——React Hooks详解,React内置Hooks、自定义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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 如果说一个变量不用于 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    1.6 使用 immer 修改 state
    • state 是不可变数据
    • state 操作成本高,有很大不稳定性
    • 使用 immer 可避免这一个问题

    安装 immer

    npm install immer --save
    
    • 1

    使用 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    二、内置 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    示例代码二:不会触发 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    四、自定义 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.3.2 使用 自定义 hooks

    import React from 'react'
    // 引入 自定的 hooks
    import useTitle from './hooks/useTitle'
    
    function App() {
      ... 其他代码
      
      // 使用自定义的 hooks
      useTitle('React 自定义 Hook 学习')
     
     ... 其他代码
    }
    export default App
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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


    五、第三方 Hooks

    5.1 常见的第三方 Hooks
    5.2 ahooks 的使用

    安装 ahooks

    npm install --save ahooks
    
    • 1

    使用

    import React from 'react';
    import { useTitle } from 'ahooks';
    
    export default () => {
      useTitle('Page Title');
    
      return (
        <div>
          <p>Set title of the page.</p>
        </div>
      );
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    六、Hooks 使用规则

    • 必须用 useXxx 格式来命名
    • 只能在两个地方调用 Hook (*组件内、其他 Hook 内 *)
    • 必须保证每次调用顺序一致(不能将 Hook 放在 if for 内部)
  • 相关阅读:
    vscode开发高频、通用插件集合(精选15个)
    Vue3的响应式更新是由什么实现的
    使用NetFlow Analyzer简化网络带宽监控
    java EE初阶 — 线程的状态
    dhrystone和coremark测试比较
    Hive 之拉链表
    python面试题
    吴恩达学习笔记(五)——优化算法
    计算机毕业设计Java校园社团信息管理(系统+源码+mysql数据库+lw文档)
    中成药数据图谱可视化与知识问答平台研究
  • 原文地址:https://blog.csdn.net/qq_61402485/article/details/133542470