IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> [React Hooks]性能调优与自定义钩子 -> 正文阅读

[JavaScript知识库][React Hooks]性能调优与自定义钩子

前言

这一节,将会主要介绍两个钩子:useCallbackuseMemo,以及对自定义hooks的理解。

useCallback,反复渲染解决之道

无限请求的"怪象"

在介绍这个钩子之前,先来看一段react初学者很容易写出来的代码:

const [detail, setDetail] = useState();

// 通过后台接口请求数据,并设置到state中
const fetchDetail = async () => {try {const res = await xxxx;setDetail(res?.data || {});} catch(err) {// xxx}
};

// 期望类似于在componentDidMount生命周期中调一次接口获取详情
useEffect(() => {fetchDetail();
},[fetchDetail]) 

这段代码其实会引起严重的bug,打开network面板,我们将看到浏览器在疯狂向后台发请求。

这是因为重渲染的时候,上述的fetchDetail函数会被重新创建一遍,并放到deps依赖数组中,由于我们上文中说过,react对比deps里面的内容本质是通过Object.is来做的。两次渲染创建了两个函数,比较两个函数的引用,自然会得到不相等的结果。

用法

const memoFn = useCallback(fn,deps); 

比如

const showUA = useCallback(function (){console.log(navigator.userAgent);
},[]); 

原理

其本质就是将一个函数保存到组件外面,并将指向这个函数的指针返回。在这里我们可以粗咯地认为:useCallback可以避免函数哎每次渲染中被重复创建。

由于useCallback返回的是一个函数的引用,这个引用自然而然就可以放心地放入deps数组中。也就是说,每次渲染运行useCallback钩子拿到的函数引用都是一样的,都指向堆内存的同一个地址。两个引用对比相等,则不会再次运行effectFn

我们将开头的怪象代码改造如下:

const [detail,setDetail] = useState();

const fetchDetail = useCallback(async () => {try {const res = await xxx;setDetail(res?.data || {});} catch(err) {// xxx}
});

useEffect(() => {fetchDetail()
}, [fetchDetail]) 

useMemo,React中的Computed

useMemouseCallback的超集。 useMemo用来缓存一个函数的执行结果。这非常类似于vue中的computed计算属性。假如一个函数的执行结果返回了一个函数,也就相当于缓存了一个函数。这就是为什么说useMemouseCallback的超集,如下:

const memoFn = useCallback(() => console.log('111'), [])

const memoFn2 = useMemo(() => () => console.log('111'), []) 

当然,我们一般不会这么做,让useCallback缓存函数引用,useMemo缓存函数执行结果吧!各司其职,易于理解。useMemo一般用法如下:

const memoValue = useMemo(() => a * b, [a, b]); 

React.memo + useCallback, 避免子组件无谓的渲染

这是一个常用的性能优化手段。

其实React.memo完全可以用useMemo来代替,这里就看个人使用习惯了,就我个人来说,比较喜欢在组件缓存时用React.memo

我们做个小实验:

// App.tsx
import React, { useState } from "react";
import Sub from "./Sub";

function App() {const [num, increaseNum] = useState(0);const logFn = () => console.log("logging...");return (<><Sub logFn={logFn} /><h1>{num}</h1><button onClick={() => increaseNum((prev) => prev + 1)}数字增加</button></>);
}

export default App;

// Sub.tsx
import React, { useEffect } from "react";

export interface SubProps {logFn: () => void;
}

const Sub: React.FC<SubProps> = ({ logFn }) => {useEffect(() => {cosnole.log("sub render");});return <button onClick={logFn}>打印</button>;
};

export default Sub; 

非常简单的一个示例,运行上述代码时可以发现:父组件点击数字+1,子组件莫名其妙被重渲染,打印出sub render

所以这里我们需要两步:

1.缓存子组件,避免子组件无条件重渲染,这是一定要做的;
2.如果子组件用到了父组件内声明的函数,则缓存该函数,避免出现 “父组件重渲染 --> 重新创建函数 --> 子组件受影响重渲染”这样的问题。

上述代码关键地方改造如下(其他地方保持不变):

// App.tsx
const logFn = useCallback(() => console.log("loggin..."), []);

// Sub.tsx
export default React.memo(Sub); 

改造完后,父组件再点击数字+1,可以发现子组件已经不会重渲染了。

自定义hooks,逻辑封装与组合

很喜欢一句话:大千世界无奇不有,但实际上不过是由一百多种元素组成。

所谓自定义hook,就是基于官方的几种hook,通过各种排列组合+实际业务场景得到的独立逻辑体,自定义hook就像是hooks中的歌曲,只要有创造力,脑洞够大,就能写出令人惊叹的自定义hook。

用法

如下是一个简单的示例:

const useFetchUserInfo = () => {const [isLoading, setIsLoading] = useState(false);const [userInfo, setUserInfo] = useState();const fetchFn = useCallback(async () => {setIsLoading(true);try {const res = await xxxx;setUserInfo(res??.data || {})} finally {setIsLoading(false);}});return [isLoading, userInfo, fetchFn];
} 

关于自定义hooks应该return一个数组还是对象,并没有明确的所谓“最佳实践”,我个人认为如果返回的内容足够简单,可以使用数组返回;如果内容较多,则使用对象返回。

还需要注意两个问题:

1.自定义hook的命名格式一定要为useXXX。后面的单词首字母还必须大写,如果尝试违反这种命名规则,编辑器会报错;
2.react官方的hook只能在函数式组件(通过首字母大写+返回ReactElement来判断)里面、或者自定义hook里面,且只能在顶层调用。

原理

其实跟之前的useStateuseEffect调用的方式一样,无论是生成链表还是读取链表,这个过程都完全一致。因为我们要明白自定义hook的本质,它不过就是把几个官方内置hook的调用过程封装成了一个可复用的函数,并没有增加新的内容。

建议

在这一部分中,我们在项目使用了阿里开源的ahooks,着实为项目解决了不少问题,这是一个非常成熟的生产级别的自定义hooks库,后续可以考虑继续使用。最后,为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:05:12  更:2022-09-13 11:07:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:07:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码