• React Hooks


    React Hooks

    前言

    • React中组件分为两大类:Class类式组件、函数式组件

    React v16.8以前:

    Class类式组件实例的三个核心属性:state、props、refs (主流)

    函数式组件:props (没有state状态,功能有限)

    React v16.8以后:

    Hooks的出现,自此,函数组件成为 React 的新宠儿。


    一、Hooks 是什么

    Hooks(钩子)React v16.8 中的新增功能 ,只能在函数组件中使用

    作用:

    • 函数组件提供状态、生命周期等原本 class 组件中提供的 React 功能

    • 可以理解为通过 Hooks 为函数组件钩入 class 组件的特性

    React v16.8 版本前后,组件开发模式的对比:

    • React v16.8 以前: class 组件(提供状态) + 函数组件(展示内容)
    • React v16.8 及其以后:
      1. class 组件(提供状态) + 函数组件(展示内容)
      2. Hooks(提供状态) + 函数组件(展示内容)
      3. 混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件

    注意1:虽然有了 Hooks,但 React 官方并没有计划从 React 库中移除 class。

    注意2:有了 Hooks 以后,不能再把函数组件称为无状态组件了,因为 Hooks 为函数组件提供了状态。

    二、Hooks的产生

    两个角度:组件的状态逻辑复用、class 组件自身的问题

    1. 组件的状态逻辑复用:
      • 在 Hooks 之前,组件的状态逻辑复用经历了:mixins(混入)、HOCs(高阶组件)、render-props 等模式。
      • mixins 的问题:1 数据来源不清晰 2 命名冲突。(早已废弃)
      • HOCs、render-props 的问题:重构组件结构,导致组件形成 JSX 嵌套地狱问题。
    2. class 组件自身的问题:
      • 需要理解 class 中的 this 指向
      • class组件和生命周期息息相关,一个功能相互关联且需要对照修改的代码被拆分到不同生命周期函数中
        • componentDidMount -> window.addEventListener('resize', this.fn)
        • componentWillUnmount -> window.addEventListener('resize', this.fn)

    三、hooks的优势

    • 两大优点:同一个功能的代码集中、不需要考虑this指向(函数式组件里面没有this)

    由于原来 React 中存在的问题,促使 React 需要一个更好的自带机制来实现组件状态逻辑复用。

    从开发者角度:

    1. 语法更简单。
    2. 组件的解构更加清晰明了,按功能进行代码分区。
    3. 没有class类式组件中的生命周期。
    4. 避免了class类式组件的class语法和相关API,纯函数,要求更低。
    5. 复用组件状态逻辑,而无需更改组件层次结构。

    从项目角度:

    1. 具有更好的 TS 类型推导。
    2. tree-shaking 友好,打包时去掉未引用的代码。
    3. 更好的压缩。

    项目开发中,Hooks 的采用策略:

    • 不推荐直接使用 Hooks 大规模重构现有组件
    • 推荐:新功能用 Hooks,复杂功能实现不了的,也可以继续用 class
    • 找一个功能简单、非核心功能的组件开始使用 hooks

    useState Hook

    前言

    问题:Hook 是什么? 一个 Hook 就是一个特殊的函数,让你在函数组件中获取状态等 React 特性
    使用模式:函数组件 + Hooks
    特点:从名称上看,Hook 都以 use 开头,useState Hook


    一、useState Hook 的基本使用

    • 使用场景:当你想要在函数组件中,使用组件状态时,就要使用 useState Hook 了
    • 作用:为函数组件提供状态(state)
    • 使用步骤:
      1. 导入 useState 函数
      2. 调用 useState 函数,并传入状态的初始值(注意useState 的初始值(参数)只会在组件第一次渲染时生效。)
      3. useState 函数的返回值中,拿到状态和修改状态的函数
      4. 在 JSX 中展示状态
      5. 在按钮的点击事件中调用修改状态的函数,来更新状态

    • 参数:状态初始值。比如,传入 0 表示该状态的初始值为 0
      • 注意:此处的状态可以是任意值(比如,数值、字符串、对象等),而 class 组件中的 state 必须是对象
    • 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)

    二、class类式组件 对比 useState Hook

    class类式组件:

    函数式组件useState Hook:

    三、解构

    • es6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称为解构

    3.1、 使用数组解构简化

    比如,要获取数组中的元素:

    1. 原始方式:索引访问
    const arr = ['aaa', 'bbb']
    
    const a = arr[0]  // 获取索引为 0 的元素
    const b = arr[1]  // 获取索引为 1 的元素
    
    • 1
    • 2
    • 3
    • 4
    1. 简化方式:数组解构
      • 相当于创建了两个变量(可以是任意的变量名称)分别获取到对应索引的数组元素
    const arr = ['aaa', 'bbb']
    
    const [a, b] = arr
    // a => arr[0]
    // b => arr[1]
    
    const [state, setState] = arr
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 使用数组解构简化 useState 的使用
      • 约定:修改状态的函数名称以 set 开头,后面跟上状态的名称
    // 解构出来的名称可以是任意名称
    
    const [state, setState] = useState(0)
    const [age, setAge] = useState(0)
    const [count, setCount] = useState(0)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.2、 对象解构

    // 1.
    const {foo,bar}={foo:'hello',bar:"world"};
    console.log(foo,bar);
      
    // 2.如果变量名和属性名不一致,需要重命名
    const {foo:baz}={foo:"hello",bar:"world"};   baz:hello
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.3、 字符串解构

    // 1.可以使用对象解构或者是数组解构,使用数组结构可以获取指定字符;使用对象结构可以获取实例属性方法;
    const [a,b,c]='hello'; 
    console.log(a,b,c);h e l
    
    • 1
    • 2
    • 3

    四、useState的初值

    useState的初值可以是任意数据类型。比如,数值、字符串、对象等。

    const [user, setUser] = useState({
        name: 'zs',
        age: 18,
    })
    
    • 1
    • 2
    • 3
    • 4

    五、useState hook 的使用规则

    规则一:

    • 注意:React Hooks 只能直接出现在 函数组件 中,不能嵌套在 if/for/其他函数中

    规则二:

    • React Hooks 必须要每次组件渲染时,按照相同的顺序来调用所有的 Hooks。

      • 为什么会有这样的规则? 因为 React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个 Hook

      • 通过开发者工具可以查看到。



    useEffect Hook

    前言

    副作用(side effect)是什么?

    • 在计算机科学中,如果一个函数或其他操作修改了其局部环境之外的状态变量值,那么它就被称为有副作用。

    对于 React 组件来说:

    主作用:就是根据数据(state/props)渲染 UI

    副作用:比如,数据(Ajax)请求、手动修改 DOM、localStorage(token) 操作等等

    当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook 了

    • 函数式组件除了渲染,其他操作都要通过useEffect
    • class类式组件除了render,其他操作都要在各种生命周期函数钩子

    一、useEffect Hook 的基本使用

    • 使用场景:当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook
    • 作用:处理函数组件中的副作用(类似Vue里面的watch)

    注意:在实际开发中,副作用是不可避免的。因此,react 专门提供了 useEffect Hook 来处理函数组件中的副作用

    • 使用步骤:
      1. 导入 useEffect 函数
      2. 调用 useEffect 函数,并传入参数(回调函数)
      3. 使用回调函数处理副作用

    解释:

    • 参数:回调函数(称为 effect),就是在该函数中写副作用代码
    • 执行时机:该 effect 会在组件渲染后以及组件更新后执行
    • 相当于生命周期钩子函数componentDidMount + componentDidUpdate

    二、useEffect 的参数

    参数一:回调函数

    参数二:依赖项(可选)
    • 依赖项(用于作状态的范围限制,只有设置的状态变化,才会触发)

    1. 不设置依赖项目:默认只要函数组件内有任意状态发生变化,就会触发useEffect

    2. 依赖项为有元素的数组:可以为多个状态元素,只要其中一个状态元素发生变化,就会触发useEffect

    3. 依赖项为空数组:相当于componentDidMount,只有组件加载完毕会执行一次

    注意

    不要对useEffect的依赖项撒谎:

    在useEffect回调函数中使用到的状态,必须在依赖项的数组中写明,useEffect才会生效(调用谁,依赖谁

    三、class类式组件 对比 useEffect

    • 监听状态:实现count变化,标题也跟着变化。

    class类式组件:

    函数式组件useState Hook:

    四、useEffect 清理副作用

    有时候,我们只想在 React 更新 DOM 之后运行一些额外的代码。

    • 无需清楚的effect:比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。
    • 需要清楚的effect:例如订阅外部数据源, 开启定时器,注册事件。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!

    内存泄漏:

    内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

    比如,组件开了计时器,组件已经销毁了,但是计时器没有销毁,占用内存,造成内存泄漏

    在组件卸载时,要用到 effect 的返回值return

    解释:

    1. effect 的返回值return也是可选的,可省略。也可以返回一个清理函数,用来执行事件解绑等清理操作
    2. 清理函数的执行时机:
      • 组件卸载时 (此时,相当于 class 组件的 componentWillUnmount 钩子函数的作用)
      • effect 重新执行前
    3. 推荐:一个 useEffect 只处理一个功能,有多个功能时,使用多次 useEffect
    4. 优势:根据业务逻辑来拆分,相同功能的业务逻辑放在一起,而不是根据生命周期方法名称来拆分代码
    5. 编写代码时,关注点集中;而不是上下翻滚来查看代码

    五、将事件处理程序放在 useEffect 内部

    • 在给 window 绑定事件时,将 事件处理程序放在 useEffect 内部。
    // 1 将 resize 事件处理程序放在 effect 回调中,当前这个代码是没有问题的
    useEffect(() => {
      const handleResize = () => {
        console.log('window 窗口大小改变了')
      }
      window.addEventListener('resize', handleResize)
    
      return () => {
        window.removeEventListener('resize', handleResize)
      }
    }, [])
    
    // 2 将 resize 事件处理程序拿到 useEffect 的外部,当前这个代码是没有问题的
    const handleResize = () => {
      console.log('window 窗口大小改变了')
    }
    
    useEffect(() => {
      window.addEventListener('resize', handleResize)
    
      return () => {
        window.removeEventListener('resize', handleResize)
      }
    }, [])
    
    // 3 有依赖项的情况:
    useEffect(() => {
      // resize 事件的处理程序
      const handleResize = () => {
        console.log('window 窗口大小改变了', count)
      }
    
      window.addEventListener('resize', handleResize)
    
      return () => {
        window.removeEventListener('resize', handleResize)
      }
    }, [count])
    
    
    // 注意:此处的代码,会给一些警告!!! 不要按照这种方式写代码!!!
    // 4 如果将 handleResize 放到了 useEffect 外部,React 会给以警告:
    //   要么将 handleResize 放到 useEffect 中
    //   要么使用 useCallback 这个 hook 来包裹 handleResize
    // resize 事件的处理程序
    const handleResize = () => {
      console.log('window 窗口大小改变了', count)
    }
    useEffect(() => {
      console.log('useeffect 执行了')
      window.addEventListener('resize', handleResize)
    
      return () => {
        window.removeEventListener('resize', handleResize)
      }
    }, [handleResize])
    
    // 总结以上几种情况,推荐:在给 window 绑定事件时,将 事件处理程序放在 useEffect 内部。
    
    • 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
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    六、useEffect 发送请求

    在组件中,使用 useEffect Hook 发送请求获取数据(side effect):

    useEffect(() => {
      //在内部定义一个发请求的方法
      const loadData = async () => {}
      loadData()
    }, [])
    
    • 1
    • 2
    • 3
    • 4
    • 5

    解释:

    • 注意:effect 只能是一个同步函数,不能使用 async
    • 因为 effect 的返回值return要求是一个清理函数,React 会在组件卸载或者 effect 的依赖项变化时重新执行
    • 但如果 effect 是 async 的,此时返回值是 Promise 对象。这样的话,就无法保证清理函数被立即调用
    • 如果延迟调用清理函数,也就没有机会忽略过时的请求结果或取消请求
    • 为了使用 async/await 语法,可以在 effect 内部创建 async 函数,并调用
    // 错误演示:
    
    // 不要给 effect 添加 async
    useEffect(async () => {}, [])
    
    • 1
    • 2
    • 3
    • 4
    // https://github.com/facebook/react/issues/14326#issuecomment-441680293
    
    useEffect(() => {
      // 是否取消本次请求
      let didCancel = false
      //在内部定义一个发请求的方法
      async function fetchMyAPI() {
        let url = 'http://something/' + productId
        let config = {}
        const response = await myFetch(url)
        // 如果开启其他请求,就忽略本次(过时)的请求结果
        if (!didCancel) {
          console.log(response)
        }
      }
    
      fetchMyAPI()
      return () => { didCancel = true } // 取消本次请求
    }, [productId])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    七、useEffect循环报错问题

    • 依赖了list,只要list发生变化,就会重新请求数据。
    • 数组之间比较的下标地址,两个数据相同的数组不相等,会导致死循环,一直请求数据
    useEffect(() => {
      const getList = async () => {
        const res = await axios.get('http://geek.itheima.net/v1_0/channels')
        setList(res.data.data.channels)
      }
      console.log(list)
      getList()
    }, [list])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    Flutter 开关和切换高级指南
    什么叫等响曲线?有什么用?
    【Java】Java中的零拷贝
    π162E61 Pai162E61 5.0kVrms 200Mbps 六通道数字隔离芯片 兼容代替Si8662ED-B-IS
    MYSQL 查看SQL执行计划
    【C++】C++从入门到精通教程(持续更新...)
    python爬虫:爬虫的简单介绍及requests模块的简单使用
    视频压缩技术—H264
    @alilclowcode-engine-ext@1.0.5 不支持安装react@^16.3.0
    offsetWidth、clientWidth、scrollWidth的区别
  • 原文地址:https://blog.csdn.net/m0_55990909/article/details/126458135