react hooks讲解--通俗易懂版

news2025/1/18 6:59:42

面试必备!

useState:状态管理

useState有两个状态,一个是status,一个是setStatus

setStatus修改数据后,会触发<App/>的re-render

什么是re-render?

re-render:重新渲染,re-render并不意味着dom会更新,re-render是react自身执行一系列的组件自身生命周期,render,虚拟dom之间diff之后,如果dom发生变化,则更新新的dom,如果没有变化则不更新。

以下情况会导致re-render:

  1. 组件内部state变化:无论这个声明的state在组件的render中是否使用,state的变化都会导致组件自身及其子节点的re-render;
  2. 组件的props变化:这是站在子组件的角度上得出的结论,而子组件props变化来源于父组件的state,究其根本,还是组件内部state发生了变化;
  3. 用户交互事件触发或者接口请求数据响应;
  4. 组件订阅的context发生变化:组件订阅的context发生变化,是由于provider中的state发生了变化,同理,还是因为state发生变化导致的re-render
  5. 父组件的render:父组件render,子组件就会re-render.除非子组件是一个纯组件(纯组件:即他的props没有发生变化,那么他就不会更新,可以使用React.memo(),React.PureComponent将组件变为纯组件。)

如何避免不必要的re-render?

  1. 使用类组件PureComponent:引入PureComponent,在需要用到Component的地方修改为PureComponent
    import {PureComponent} from 'react';
    export class Root extends PureComponent{
    ...
    }
    
    
    
    //或者直接继承
    //export class Root extends React.PureComponent{...}
  2. 使用函数组件Memo:父组件中修改state时,下面这个子组件如果没有用到这个state,则不会re-render。换言之,react.memo()是校验props中数据的内存地址是否改变来决定是否重新渲染
    
    import { memo } from "react"
    const Memodemo=({props,children})=>{
            console.log('Memodemo')
            return <>
            <p>这里用的是memo</p>
            </>
        }
        export default memo(Memodemo)
  3. 利用高阶组件shouldComponentUpdate:组件在重新渲染之前(也就是虚拟dom对比完毕并生成最终dom之后),会调用该函数,该函数将是否重新渲染的权限交给开发者,若该函数返回true,则更新dom
    //下面这个示例,仅在value变化时发生dom更新,否则跳过dom更新
    class MyComponent extends React.Copmonent{
        shouldComponentUpdate(nextProps,nextState){
            return nextProps.value!===this.props.value
        }
    }
    //nextProps:即将传入的新的props
    //nextState:即将传入的新的state

useEffect:

useEffect接受两个参数,第一个参数是一个函数,必传,可以看作componentDidMount、componentDidUpdate、componentWillUnmount这三个函数的组合

useEffect中第一个参数如果添加了return返回一个函数,切换路由时,就会执行这个return返回的函数,相当于componentWillUnmount。

第二个参数可以不传或者传一个数组

那么useEffect会出现以下四种使用情况:

  1. 不传递第二个参数,当前组件每次渲染都会执行,包括state的每次更新都会触发useEffect
    useEffect(
        ()=>{console.log('useEffect')}
    )
  2. 第二个参数传递空数组:只在挂载和卸载的时候执行
    useEffect(()=>{
    console.log('useEffect[]')
    },[])
  3. 第二个参数传递一个值
    useEffect(
    ()=>{
    console.log('当page发生修改的时候,会输出此行');
    },[page])
  4. 第二个参数传递多个值:数组中的任意一个或者多个发生变化,useEffect都会重新运行一次;
    useEffect(()=>{
        console.log('useEffect');
    },[level,level1])

componentDidMount :react在组件添加到屏幕上(挂在)后调用它,一般用于进入页面后,数据初始化

componentDidUpdate:页面中的state或者model中的state定义的变量发生了变化,这个方法就会执行;

componentWillUnmount:组件被移除屏幕(卸载)之前调用

这三个类生命周期和useEffect等同方式,看下面代码:

//类生命周期
class Example extends React.Component{
    constructor(props){
        super(props);
        this.state={
            count:0,
            dateNow:''
        }
    }
    componentDidMount(){    
        console.log('当前count=',this.state.count)
        this.timeFlag=setInterval(()=>{
            this.setState.dateNow=new Date()
        })
    }
    componentDidUpdate(){
        console.log('当前更新为count=',this.state.count)
    }
    componentWillUnmount(){
        clearInterval(this.timeFlag)
    }
    render(){
        return(
            <div>
                <p>当前count={this.state.count}</P>
                <p>当前时间={this.state.dateNow}</P>
                <button onClick={()=>{this.state.count=this.state.count+1}}>count+1</button>
            </div>
        )    
    }
}
//上面的类生命周期代码,等同于下面的代码
import {useState,useEffect} from 'react';
const Example=()=>{
    const [count,setCount]=useState(0);
    const [dateNow,setDateNow]=useState('');
//下面这一行没有写第二个参数,所以,当state中的变量发生变化时,这里的useEffect就会调用
    useEffect(()=>{
        console.log('当前count=',count)
    })
//这一行只有count发生变化,才会调用useEffect
    useEffect(()=>{ console.log('当前更新为count=',count)},[count])
    uesEffect(()=>{
        const timeFlag=setInterval(()=>{
            setDateNow(new Date())        
        })
        return()=>{clearInterval(timeFlag)}
    },[])
    return(
        <div>
            <p>当前count={count}</P>
            <p>当前时间={dateNow}</P>
                <button onClick={()=>{setCount(count+1)}}>count+1</button>
        </div>
    )
}

useMemo:性能优化工具,主要解决使用react hooks产生的无用渲染的性能问题,类似于vue的computed,可以缓存计算结果,用于跳过昂贵的计算

useMemo使用方式为:useMemo(fn,arr);

第二个参数arr分以下几种情况:

  1. 不传,即useMemo(fn):每次更新都会重新计算
  2. arr=空数组[]:只会计算一次;
  3. arr=[a]:当a发生变化的时候,会重新执行fn;

useMemo的作用时跳过昂贵的计算,那我们看以下几个例子:

//当x或者y发生变化,组件re-render时,a都会进行重新计算,可以看到time的计算时间都是很长的
import { useMemo, useState } from "react"
const MemoPage=()=>{
    const [x,setX]=useState(1);
    const [y,setY]=useState(1);
    console.time('time')
let a=0;
for(var i=0;i<100000000;i++){
    a=a+i+y
}
    console.timeEnd('time')
    return(<>
        <div>x={x}</div>
        <div>y={y}</div>
        <div>x={a}</div>
        <div>
            <button onClick={()=>setX(x+1)}>x++</button>
            <button onClick={()=>setY(y+1)}>y++</button>
        </div>
    </>)
}
export default MemoPage

//用useMemo返回一个a,无论x还是y发生了修改, x={a}取的都是缓存中的a的值,不会进行重新计算,可以看到下图,time的计算时间变短
import { useMemo, useState } from "react"
const MemoPage=()=>{
    const [x,setX]=useState(1);
    const [y,setY]=useState(1);
    console.time('time')
    
   let a= useMemo(()=>{
        let a=0;
        for(var i=0;i<100000000;i++){
            a=a+i
        }
        return a
    },[])
//上面的[]必须写,如果不写,当x或者y发生修改,则a还是会重新计算
   
    console.timeEnd('time')
    return(<>
        <div>x={x}</div>
        <div>y={y}</div>
        <div>x={a}</div>
        <div>
            <button onClick={()=>setX(x+1)}>x++</button>
            <button onClick={()=>setY(y+1)}>y++</button>
        </div>
    </>)
}
export default MemoPage

//上面的代码a是固定不变的,假如,a是变化的呢?
//下面代码,当x发生变化时,a仍然取缓存,当y发生变化时,则重新计算
import { useMemo, useState } from "react"
const MemoPage=()=>{
    const [x,setX]=useState(1);
    const [y,setY]=useState(1);
    console.time('time')
    
   let a= useMemo(()=>{
        let a=0;
        for(var i=0;i<100000000;i++){
            a=a+i+y
        }
        return a
    },[y])
   
    console.timeEnd('time')
    return(<>
        <div>x={x}</div>
        <div>y={y}</div>
        <div>x={a}</div>
        <div>
            <button onClick={()=>setX(x+1)}>x++</button>
            <button onClick={()=>setY(y+1)}>y++</button>
        </div>
    </>)
}
export default MemoPage

上面是useMemo的主要作用

还有一些扩展作用 ,这些只是能达到效果,不是useMemo的主要作用

import { memo, useMemo, useState ,useRef} from "react";
const Child=memo(({a})=>{

    console.log('子组件')
    return <><div>子组件</div></>
})
const MemoPage=()=>{
    const [x,setX]=useState(1);
    const [y,setY]=useState(1);
    // let a=['a','b','c'];//直接写这一行,当x或者y发生变化时,子组件还是会重新渲染
    let a=useMemo(()=>['a','b','c'],[])//这一行子组件不会重新渲染
//    const [a,setA]=useState(['a','b','c'])//这一行子组件也不会重新渲染
// let a=useRef(['a','b','c']);//下面的子组件修改为<Child a={a.current}/>,也能达到一样效果


    console.log('父组件')
  
    return(<>
        <div>x={x}</div>
        <div>y={y}</div>
        {/* <div>x={a}</div> */}
        <div>
            <button onClick={()=>setX(x+1)}>x++</button>
            <button onClick={()=>setY(y+1)}>y++</button>
        </div>
        <Child a={a}/>
    </>)
}
export default MemoPage

memo和useMemo的区别和相同点是什么呢?

区别:

  1. 用途:useMemo是一个hook,用于记忆计算结果,可以在组件重新渲染时避免重新计算某些值,从而提升性能;react.memo是一个高阶组件,用于记忆组件的渲染结果,可以在父组件re-render时避免重新渲染子组件,从而提升性能。
  2. 使用场景:假设有一个昂贵的计算(简易理解为超大数字计算),可以用useMemo缓存计算结果,当依赖的变量发生变化时,再从新计算;react.memo用于当一个纯函数组件,渲染结果仅仅依赖于props时,react.memo可以避免不必要的重新渲染;
  3. 语法方面也有所不同;
  4. 返回值:useMemo返回计算结果值,用户缓存计算后的状态;react.memo翻译一个新的组件。

    总结:useMemo用于记忆计算结果,避免重复计算,适用于需要缓存计算结果的场景;
    react.memo用于记忆组件的渲染结果,避免重复渲染,适用于纯函数组件,避免不必要的重新渲染。

相同点:

  1. 两者都是为了性能优化,减少不必要的计算或者渲染;
  2. 两者都接受一个依赖项数组,用于决定是否需要重新计算或者渲染。

useCallback:主要作用是缓存一个回调函数,确保组件在re-render时不会重新创建新的回调函数,优化函数性能。需要搭配React.memo()使用。

重点:useCallback的作用不是阻止函数创建,而是依赖不变的情况下,返回旧函数地址。

import { useCallback, useState } from "react"
const CallPage=()=>{
    const [state,setState]=useState(1)
    const a=useCallback(()=>{
        console.log('a',state)
    },[])
    const b=()=>{
        console.log('b',state)
    }
    return <>
    <button onClick={()=>{
        a();
        b();
        setState(state+1)
    }}>btn</button>
    </>
}
export default CallPage

比如上面的代码,点击 btn的时候,a(),返回的count不变,但是b()返回的count永远+1

每一个被useCallback的函数都会被加入到useCallback内部管理队列,一个使用了useCallback的函数在组件重新渲染时,会去useCallback内部管理队列中寻找校验依赖是否改变并校验。在这个过程中,寻找指定函数和校验都需要性能,如果滥用useCallback会增加寻找指定函数和校验依赖是否改变两个功能,为项目增加不必要的负担。

什么时候使用useCallback?

import { memo, useCallback, useState } from "react"
const Child1=memo(()=>{
    console.log('Child1')
    return<></>
})
const Child2=memo((props)=>{
    console.log('Child2')
    return<></>
})
const Child3=memo((props)=>{
    console.log('Child3')
    return<></>
})
const CallPage=()=>{
    const [state,setState]=useState(1)
      const fn=()=>{
         console.log('点击btn')
     }
    const fns=useCallback(()=>{
        console.log('点击btn')
    },[])
    return<>
    <button onClick={()=>{
        setState(state+1)
    }}>btn</button>
    <Child1 />
    <Child2 fn={fn}/>
    <Child3 fn={fns}/>
    </>
}
export default CallPage

上面的代码,child1没有传参,当父组件state更新的时候,child1对比到props没有发生变化,于是不更新;

child2,不行,会重新渲染;因为父组件re-render的时候,会重构父组件中的所有函数,也就是所有函数的内存地址发生了变化,在child2中,react.memo检测到props中的数据的栈的地址发生了变化,所以child2也会重新渲染。

child3,传参fns,被useCallback保护着,父组件re-render时,从useCallback的内部管理队列返回旧的内存地址,因此,子组件不会检测到地址变化,也就不会重新渲染。

useRef:创建一个容器

用途:

  1. 访问dom节点
    import { useEffect, useRef } from "react"
    
    const Refpage=()=>{
        const ref1=useRef(null)
        useEffect(()=>{
            console.log(ref1)
        },[])
        return<>
        <div ref={ref1}>ref</div>
        </>
    }
    export default Refpage
  2. 存储任意可变的值

useContext:

还没有写完,慢慢写~~

有需要补充的,麻烦私信我

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

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

相关文章

MongoDB集群的介绍与搭建

MongoDB集群的介绍与搭建 一.MongoDB集群的介绍 注意&#xff1a;Mongodb是一个比较流行的NoSQL数据库&#xff0c;它的存储方式是文档式存储&#xff0c;并不是Key-Value形式&#xff1b; 1.1集群的优势和特性 MongoDB集群的优势主要体现在以下几个方面&#xff1a; (1)高…

基于 Python、OpenCV 和 PyQt5 的人脸识别上课打卡系统

大家好&#xff0c;我是Java徐师兄&#xff0c;今天为大家带来的是基于 Python、OpenCV 和 PyQt5 的人脸识别上课签到系统。该系统采用 Python 语言开发&#xff0c;开发过程中采用了OpenCV框架&#xff0c;Sqlite db 作为数据库&#xff0c;系统功能完善 &#xff0c;实用性强…

element Plus中 el-table表头宽度自适应,不换行

在工作中&#xff0c;使用el-table表格进行开发后&#xff0c;遇到了小屏幕显示器上显示表头文字会出现换行展示&#xff0c;比较影响美观&#xff0c;因此需要让表头的宽度变为不换行&#xff0c;且由内容自动撑开。 以下是作为工作记录&#xff0c;用于demo演示教程 先贴个…

GitLab基础环境部署:Ubuntu 22.04.5系统在线安装GitLab 17.5.2实操手册

文章目录 GitLab基础环境部署&#xff1a;Ubuntu 22.04.5系统在线安装GitLab 17.5.2实操手册一、环境准备1.1 机器规划1.2 环境配置1.2.1 设置主机名1.2.2 停止和禁用防火墙1.2.3 更新系统 二、GitLab安装配置2.1 安装GitLab所需的依赖包2.2 添加GitLab存储库2.2.1 将GitLab存储…

1.3.3 存储系统

目录 存储器分类存储器的层次结构主存储器高速缓存的特点及组成外存储器的种类和特点 存储器分类 存储器按照所处位置、制作材料、访问方式、寻址方式、工作方式可以分成多种类型。 位置&#xff1a;在主机或主板上的是内存&#xff0c;否则是外存。材料&#xff1a;磁存储器&…

【PyQt5教程 一】Qt Designer 安装及其使用方法说明,附程序源码

目录 一、PyQt5介绍&#xff1a; &#xff08;1&#xff09;PyQt简介&#xff1a; &#xff08;2&#xff09;PyQt API&#xff1a; &#xff08;3&#xff09;支持的环境&#xff1a; &#xff08;4&#xff09;安装&#xff1a; &#xff08;5&#xff09;配置环境变量…

SPT: Revisiting the Power of Prompt for Visual Tuning

方法简介 方法很简单&#xff0c;作者通过实验发现prompt拥有一个良好的初始化是VPT的关键&#xff0c;于是作者就通过在MAE/MoCo进行预训练来得到一个良好的prompt的初始化来提供微调阶段的prompt。 这么简单的方法是怎么催成一篇顶会的呢&#xff1f;值得我们去学习&#xf…

基于事件驱动的websocket简单实现

websocket的实现 什么是websocket&#xff1f; WebSocket 是一种网络通信协议&#xff0c;旨在为客户端和服务器之间提供全双工、实时的通信通道。它是在 HTML5 规范中引入的&#xff0c;可以让浏览器与服务器进行持久化连接&#xff0c;以便实现低延迟的数据交换。 WebSock…

基于协同过滤算法的宠物用品商城的设计与实现(计算机毕业设计)Java Spring 衍生为任何商城系统 毕业论文

系统合集跳转 源码获取链接 一、系统环境 运行环境: 最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以 tomcat环境&#xff1a; Tomcat 7.x,8.x,9.x版本均可 操作系统…

文本预处理介绍

文本预处理 文本预处理1.认识文本预处理2.文本处理的基本方法2.1.什么是分词2.2 什么是命名实体化2.3词性标注 3.文本张量的表示方法3.1文本张量表示3.2 one-hot词向量表示 4.Word2vec模型4.1模型介绍4.2word2vec的训练和使用 5.词嵌入word embedding 介绍6.文本数据分析1.文本…

力扣3381.长度可被K整除的子数组的最大元素和

力扣3381.长度可被K整除的子数组的最大元素和 题目 题目解析及思路 题目要求返回一段长度为K的倍数的最大子数组和 同余前缀和 代码 class Solution { public:long long maxSubarraySum(vector<int>& nums, int k) {int n nums.size();vector<long long>…

第三节、电机定速转动【51单片机-TB6600驱动器-步进电机教程】

摘要&#xff1a;本节介绍用定时器定时的方式&#xff0c;精准控制脉冲时间&#xff0c;从而控制步进电机速度 一、计算过程 1.1 电机每一步的角速度等于走这一步所花费的时间&#xff0c;走一步角度等于步距角&#xff0c;走一步的时间等于一个脉冲的时间 w s t e p t … ……

【数学建模】线性规划问题及Matlab求解

问题一 题目&#xff1a; 求解下列线性规划问题 解答&#xff1a; 先将题目中求最大值转化为求最小值&#xff0c;则有 我们就可以得到系数列向量: 我们对问题中所给出的不等式约束进行标准化则得到了 就有不等式约束条件下的变系数矩阵和常系数矩阵分别为&#xff1a; 等式…

云计算对定制软件开发的影响

在当代数字世界中&#xff0c;云计算是改变许多行业&#xff08;包括定制软件开发&#xff09;的最伟大的革命性趋势之一。由于这些公司努力寻求更好、更多不同的方式来履行职责&#xff0c;因此云计算与传统的内部部署基础设施相比具有许多不可否认的优势。这种范式转变对定制…

3D 生成重建020-Gaussian Grouping在场景中分割并编辑一切

3D 生成重建020-Gaussian Grouping在场景中分割并编辑一切 文章目录 0 论文工作1 方法2 实验结果 0 论文工作 最近提出的高斯Splatting方法实现了高质量的实时三维场景新视角合成。然而&#xff0c;它仅仅关注外观和几何建模&#xff0c;缺乏细粒度的物体级场景理解。为了解决…

GUI的最终选择:Tkinter

Tkinter是Python默认的GUI库&#xff0c;因此使用时直接导入即可&#xff1a;import tkinter 17.1 Tkinter之初体验 代码分析&#xff1a; tkinter.mainloop()通常是程序的最后一行代码&#xff0c;执行后程序进入主事件循环。 17.2 进阶版本 将代码封装成类&#xff1a; 运…

电子商务人工智能指南 3/6 - 聊天机器人和客户服务

介绍 81% 的零售业高管表示&#xff0c; AI 至少在其组织中发挥了中等至完全的作用。然而&#xff0c;78% 的受访零售业高管表示&#xff0c;很难跟上不断发展的 AI 格局。 近年来&#xff0c;电子商务团队加快了适应新客户偏好和创造卓越数字购物体验的需求。采用 AI 不再是一…

嵌入式软件C语言面试常见问题及答案解析(一)

本文中题目列表 1. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)2. 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。3. 预处理器标识#error的目的是什么?4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?5. …

从失败中学习:如何将错误转化为学习机会

失败是人生的一部分&#xff0c;无论是在个人生活还是职业生涯中&#xff0c;我们都难免会遇到挫折和错误。然而&#xff0c;失败并不意味着终结&#xff0c;而是一个潜在的学习机会。通过正确的态度和方法&#xff0c;我们可以从失败中汲取经验&#xff0c;转化为成长的动力。…

HarmonyOS NEXT的Navigation,跳转子页面后底部Tab不隐藏问题解决

问题复现 一直以来&#xff0c;首页的Tabs是这么用的&#xff1a; import Home from "../pages/home/Home" import ZhiHu from "../pages/song/Song" import Mine from "../pages/mine/Mine"Entry Component struct Index {State currentIndex…