React hooks之useCallback的使用与性能分析

使用useCallback优化代码

useCallback是对传过来的回调函数优化,返回的是一个函数;useMemo返回值可以是任何,函数,对象等都可以。
简单来说就是返回一个函数,只有在依赖项发生变化的时候才会更新(返回一个新的函数)。

1.原理分析

useCallback是React Hooks中的一个函数,用于优化函数组件的性能。它的作用是返回一个memoized(记忆化的)函数,这个函数只有在依赖项发生变化时才会重新计算,否则会直接返回上一次计算的结果。
useCallback是对传过来的回调函数优化,返回的是一个函数;useMemo返回值可以是任何,函数,对象等都可以。
简单来说就是返回一个函数,只有在依赖项发生变化的时候才会更新(返回一个新的函数)。

2.案例分析:

父组件定义一个请求函数fetchData,和一个状态query,将query当作fetchData的参数,将该函数传递进子组件,当父组件query发生变化时,让子组件调用该函数发起请求。

class Parent extends Component {state = {query: 'react'};fetchData = () => {    const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;    // ... Fetch data and do something ...  };  render() {return <Child fetchData={this.fetchData} />;}
}class Child extends Component {state = {data: null};componentDidMount() {this.props.fetchData();}componentDidUpdate(prevProps) {// 🔴 This condition will never be trueif (this.props.fetchData !== prevProps.fetchData) {this.props.fetchData();}}render() {// ...}
}

在本代码中,fetchData是一个class方法!(或者你也可以说是class属性)它不会因为状态的改变而不同,所以this.props.fetchData和 prevProps.fetchData始终相等,因此不会重新请求。

2.1旧思维–优化该案例:

子组件使用:

componentDidUpdate(prevProps) {this.props.fetchData();
}

这样可以发起请求,但是会在每次渲染后都去请求。
或者改变父组件:

render() {return <Child fetchData={this.fetchData.bind(this, this.state.query)} />;
}

但这样一来,this.props.fetchData !== prevProps.fetchData 表达式永远是true,即使query并未改变。这会导致我们总是去请求。(bind() 方法会创建一个新的函数对象)
唯一现实可行的办法是把query本身传入 Child 组件。 Child 虽然实际并没有直接使用这个query的值,但能在它改变的时候触发一次重新请求:

class Parent extends Component {state = {query: 'react'};fetchData = () => {const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;// ... Fetch data and do something ...};render() {return <Child fetchData={this.fetchData} query={this.state.query} />;  }
}class Child extends Component {state = {data: null};componentDidMount() {this.props.fetchData();}componentDidUpdate(prevProps) {if (this.props.query !== prevProps.query) {      this.props.fetchData();    }  }render() {// ...}
}

在class组件中,函数属性本身并不是数据流的一部分。使用useCallback,函数完全可以参与到数据流中。我们可以说如果一个函数的输入改变了,这个函数就改变了。如果没有,函数也不会改变。

2.2开始使用hooks:

场景一: 使用函数组件
但父组件不使用useCallback处理函数

import React, { useCallback, useState, useEffect } from 'react';
import './App.css';function App() {const [query, setQuery] = useState(1);const [queryOther, setQueryOther] = useState(1);const fecthData = () => {console.log('新的fetch');return query;}const add = () => {console.log('点击add');setQuery(query + 1);}const addOther = () => {console.log('点击addOther');setQueryOther(queryOther + 1);}return (<><Child fecthData={fecthData} /><button onClick={add}>+1</button><button onClick={addOther}>other+1</button><div>{ query }</div></>);
}function Child({ fecthData }: { fecthData: any }) {console.log('子组件相关内容');useEffect(() => {const querN = fecthData();console.log('子组件调用该函数获取到相关内容', querN);}, [fecthData])return <div>123</div>
}export default App;

初始化的时候:
在这里插入图片描述
点击按钮:

在这里插入图片描述
但是从图里面可以看到,点击addOther时,并没有使得query发生变化,但是子组件仍然调用了该函数发起请求。可以看到这种方法需求可以使得子组件在父组件的状态query发生变化时,成功发起了请求,但是还是存在副作用。
问题的原因在于状态queryOther的改变,使得父组件重新渲染,重新生成了fecthData函数,并返回了该函数新的地址,导致子组件刷新。

场景二:父组件使用useCallback处理函数

import React, { useCallback, useState, useEffect } from 'react';
import './App.css';function App() {const [query, setQuery] = useState(1);const [queryOther, setQueryOther] = useState(1);const fecthData = useCallback(() => {console.log('新的fetch');return query;}, [query])const add = () => {console.log('点击add');setQuery(query + 1);}const addOther = () => {console.log('点击addOther');setQueryOther(queryOther + 1);}return (<><Child fecthData={fecthData} /><button onClick={add}>+1</button><button onClick={addOther}>other+1</button><div>{ query }</div></>);
}function Child({ fecthData }: { fecthData: any }) {console.log('子组件相关内容');useEffect(() => {const querN = fecthData();console.log('子组件调用该函数获取到相关内容', querN);}, [fecthData])return <div>123</div>
}export default App;

初始状态
在这里插入图片描述
点击按钮:
在这里插入图片描述

可以看到只有点击+1按钮改变query才会使得子组件发起请求,点击other+1已经没有处罚上文副作用。

原因分析:
使用了useCallback,useCallback的工作原理是什么?useCallBack的本质工作不是在依赖不变的情况下阻止函数创建,而是在依赖不变的情况下不返回新的函数地址而返回旧的函数地址。不论是否使用useCallBack都无法阻止组件render时函数的重新创建。在本例子中点击按钮other+1并没有使得query发生变化,所以并没有返回新的fetchData函数地址,又因为在子组件中使用useEffect对fetchData监听时,所以子组件不会发起请求。但是,点击按钮other+1时,子组件虽然没发起请求,但是还是刷新了,这是什么原因呢?这是因为子组件直接在父组件中挂载,没有做过任何优化,当父组件重新渲染时,会导致子组件也跟着渲染。所以单纯的使用useCallback可以监听到相应变化,使得子组件做出变化,但是并不能优化性能。所以当我们不用监听某个状态使得函数发生改变时,不要轻易使用useCallback,因为使用 useCallBack后每次执行到这里内部比对是否变化,还有存一下之前的函数,消耗更大了。

场景三:优化上述问题,搭配React.memo使用

import React, { useCallback, useState, useEffect } from 'react';
import './App.css';function App() {const [query, setQuery] = useState(1);const [queryOther, setQueryOther] = useState(1);const fecthData = useCallback(() => {console.log('新的fetch');return query;}, [query])const add = () => {console.log('点击add');setQuery(query + 1);}const addOther = () => {console.log('点击addOther');setQueryOther(queryOther + 1);}return (<><Child fecthData={fecthData} /><button onClick={add}>+1</button><button onClick={addOther}>other+1</button><div>{ query }</div>  ,,mconst Child = React.memo(({ fecthData }: { fecthData: any }) => {console.log('子组件相关内容');useEffect(() => {const querN = fecthData();console.log('子组件调用该函数获取到相关内容', querN);}, [fecthData])return <div>123</div>
})export default App;

初始状态:
在这里插入图片描述
点击按钮:
在这里插入图片描述
一切问题都解决了。点击other+1按钮,没有使得子组件发起请求,也没有使得子组件因为这个无关变量的变化,导致重新渲染。

原因分析:

  • 使用useCallback使得无关变量变化时,阻止了新创建的fetchData的新地址返回,传给子组件的还是原本的函数地址(useCallBack的本质工作不是在依赖不变的情况下阻止函数创建,而是在依赖不变的情况下不返回新的函数地址而返回旧的函数地址)
  • React.memo 这个方法,此方法内会对 props 做一个浅层比较,如果如果 props 没有发生改变(useCallback的存在使得props没变化),则不会重新渲染此组件。

场景四:单纯使用React.memo会发生什么

import React, { useCallback, useState, useEffect } from 'react';
import './App.css';function App() {const [query, setQuery] = useState(1);const [queryOther, setQueryOther] = useState(1);const fecthData = () => {console.log('新的fetch');return query;}const add = () => {console.log('点击add');setQuery(query + 1);}const addOther = () => {console.log('点击addOther');setQueryOther(queryOther + 1);}return (<><Child fecthData={fecthData} /><button onClick={add}>+1</button><button onClick={addOther}>other+1</button><div>{ query }</div></>);
}const Child = React.memo(({ fecthData }: { fecthData: any }) => {console.log('子组件相关内容');useEffect(() => {const querN = fecthData();console.log('子组件调用该函数获取到相关内容', querN);}, [fecthData])return <div>123</div>
})export default App;

初始状态:
在这里插入图片描述
点击按钮
在这里插入图片描述
React.memo检测的是props中数据的栈地址是否改变。而父组件重新构建的时候,会重新构建父组件中的所有函数(旧函数销毁,新函数创建,等于更新了函数地址),新的函数地址传入到子组件中被props检测到栈地址更新。也就引发了子组件的重新渲染。所以,在上面的代码示例里面,子组件是要被重新渲染的。上文中的fetchData因为失去了useCallback的保护使得子组件的props发生了变化,从而React.memo也失去了作用,而且因为fetchData因为失去了useCallback的保护,使得点击other+1按钮改变无关的变量时,子组件也调用了请求函数。

3.useCallback使用总结:

  • 可以使用useCallback可以监听到相应状态变化,使得父/子组件做出响应。
  • 但是滥用useCallback会影响性能,需搭配React.memo进行使用,否则适得其反。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《项目实战》构建SpringCloud alibaba项目(二、构建微服务鉴权子工程store-authority-service)

系列文章目录 构建SpringCloud alibaba项目&#xff08;一、构建父工程、公共库、网关&#xff09; 构建SpringCloud alibaba项目&#xff08;二、构建微服务鉴权子工程store-authority-service&#xff09; 文章目录 系列文章目录前言1、在公共库增加 UserInfo类2、微服务鉴权…

电脑文件怎么加密?哪个文件加密软件好用?

不少人的电脑中都存放着一些重要文件&#xff0c;这些文件需要使用专业的方式进行加密保护。那么电脑文件该怎么加密呢&#xff1f;下面我们就通过本文来一起了解一下吧。 超级加密3000 作为一款备受好评的文件加密软件&#xff0c;超级加密3000在安全性、便捷性、全面性等方面…

记录征战Mini开发板从无到有

前言 我们店铺的开发板目前主要有Altera,Xilinx以及国产安路&#xff0c;高云。Xilinx只有Spartan6系列&#xff0c;这个系列的芯片只支持ISE软件&#xff0c;但是很多客户用的是VIVADO软件&#xff0c;所以导致我们无法满足客户的需求。基于此原因&#xff0c;我们经过几个月…

AST-抽象语法树

js加密解混淆首先想到的是AST语法树&#xff0c;那么什么是AST呢&#xff0c;学习AST过程的一些笔记 1.AST是JS执行的第一步是读取 js 文件中的字符流&#xff0c;然后通过词法分析生成令牌流Tokens&#xff0c;之后再通过语法分析生成 AST&#xff08;Abstract Syntax Tree&a…

读取摄像机的内参和畸变系数并对畸变图像进行去畸变

这个程序的目标是读取摄像机的参数(内参和畸变系数),并对畸变图像进行去畸变操作,然后进行一些特征点和矩形框的绘制。 #include 语句引入所需的库。using namespace std; 和 using namespace cv; 语句是在代码中使用std和opencv命名空间,这样就不用在每次使用这些库的函数…

ChatGPT 有什么新奇的使用方式?

先来看看ChatGPT对此问题如何作答 ChatGPT对此问题如何作答 ChatGPT是什么 ChatGPT是一种基于自然语言处理的语言模型&#xff0c;由OpenAI开发。它是建立在GPT&#xff08;Generative Pre-trained Transformer&#xff09;架构的基础上的&#xff0c;采用了深度学习技术。GP…

Todo-List案例版本一

初级使用e.target.value 记得安装npm i nanoid与UUID类似 快捷键ctrlH替换内容 src/components/MyHeader.vue <template><div class"todo-header"><input type"text" placeholder"请输入你的任务名称&#xff0c;按回车键确认&quo…

浅谈RPC协议

RPC协议 RPC简介为啥需要RPCRPC的调用过程gRPCProtoBuffergRPC实战 RPC简介 RPC&#xff08;Remote Procedure Call Protocol&#xff09;远程过程调用协议&#xff0c;目标就是让远程服务调用更加简单、透明。RPC 框架负责屏蔽底层的传输方式&#xff08;TCP 或者 UDP&#x…

《动手学深度学习》——线性神经网络

参考资料&#xff1a; 《动手学深度学习》 3.1 线性回归 3.1.1 线性回归的基本元素 样本&#xff1a; n n n 表示样本数&#xff0c; x ( i ) [ x 1 ( i ) , x 2 ( i ) , ⋯ , x d ( i ) ] x^{(i)}[x^{(i)}_1,x^{(i)}_2,\cdots,x^{(i)}_d] x(i)[x1(i)​,x2(i)​,⋯,xd(i)​…

特征选择算法 | Matlab实现基于ReliefF特征选择算法的分类数据特征选择 ReliefF

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 特征选择算法 | Matlab实现基于ReliefF特征选择算法的分类数据特征选择 ReliefF 部分源码 %--------------------

写一个starter(spring boot)

前置知识 自动装配 自动装配的一个重要注解就是SpringBootApplication。它是一个复合注解&#xff0c;由四个元注解和另外三个注解组成。这三个注解是&#xff1a; ConfigurationEnableAutoConfigurationComponentScan Configuration Configuration 是 JavaConfig 形式的…

神经网络之VGG

目录 1.VGG的简单介绍 1.2结构图 3.参考代码 VGGNet-16 架构&#xff1a;完整指南 |卡格尔 (kaggle.com) 1.VGG的简单介绍 经典卷积神经网络的基本组成部分是下面的这个序列&#xff1a; 带填充以保持分辨率的卷积层&#xff1b; 非线性激活函数&#xff0c;如ReLU&a…