React Hooks使用避坑指南
函数组件比类组件更加方便实现业务逻辑代码的分离和组件的复用,函数组件也比类组件轻量,没有react hooks之前,函数组件是无法实现LocalState的,这导致有localstate状态的组件无法用函数组件来书写,这限制了函数组件的应用范围,而react hooks扩展了函数组件的能力。可是在使用的过程中,也要注意下面这些问题,否则就会掉进坑里,造成性能损失。按照下面的方法做,,才能避开这些陷阱。
1. 将与状态改变无关的变量和方法提取到组件函数外面
每次状态改变时,整个函数组件都会重新执行一遍。导致函数组件内部定义的方法和变量,都会重新创建,重新给它们分配内存,这会导致性能受到影响。
import React, {useState,useCallback} from "react";
// 测试每次状态改变时,方法是不是重新分配内存
let testFooMemoAlloc = new Set();
const Page = (props:any) => {
console.log('每次状态改变,函数组件从头开始执行')
const [count, setCount] = useState(0);
const calc = () => {
setCount(count + 1);
}
const bar = {
a:1,
b:2,
c: '与状态无关的变量定义'
}
const doFoo = () => {
console.log('与状态无关的方法');
}
testFooMemoAlloc.add(doFoo)
return (
<>
<button onClick={calc}>加1</button>
<p>count:{count}</p>
<p>testFooMemoAlloc.size增加的话,说明每次都重新分配了内存:{testFooMemoAlloc.size}</p>
</>
)
}
export default Page;

与改变状态相关的变量和方法,必须放在hooks组件内,而无状态无关的变量和方法,可以提取到函数组件外,避免每次状态更新,都重新分配内存。也可以分别使用useMemo和useCallback包裹变量与函数,也能达到同样的效果,后面会讲。
import React, {useState,useCallback} from "react";
// 测试每次状态改变时,方法是不是重新分配内存
let testFooMemoAlloc = new Set();
const bar = {
a:1,
b:2,
c: '与状态无关的变量定义'
}
const doFoo = () => {
console.log('与状态无关的方法');
}
const Page = (props:any) => {
console.log('每次状态改变,函数组件从头开始执行')
const [count, setCount] = useState(0);
const calc = () => {
setCount(count + 1);
}
testFooMemoAlloc.add(doFoo)
return (
<>
<button onClick={calc}>加1</button>
<p>count:{count}</p>
<p>testFooMemoAlloc.size增加的话,说明每次都重新分配了内存:{testFooMemoAlloc.size}</p>
</>
)
}
export default Page;

2. 用memo对子组件进行包装
父组件引入子组件,会造成一些不必要的重复渲染,每次父组件更新count,子组件都会更新。
import React,{useState} from "react";
const Child = (props:any) => {
console.log('子组件?')
return(
<div>我是一个子组件</div>
);
}
const Page = (props:any) => {
const [count, setCount] = useState(0);
return (
<>
<button onClick={(e) => { setCount(count+1) }}>加1</button>
<p>count:{count}</p>
<Child />
</>
)
}
export default Page;

使用memo,count变化子组件没有更新
import React,{useState,memo} from "react";
const Child = memo((props:any) => {
console.log('子组件?')
return(
<div>我是一个子组件</div>
);
})
const Page = (props:any) => {
const [count, setCount] = useState(0);
return (
<>
<button onClick={(e) => { setCount(count+1) }}>加1</button>
<p>count:{count}</p>
<Child />
</>
)
}
export default Page;

给memo传入第二个参数,开启对象深度比较。当子组件传递的属性值未发生改变时,子组件不会做无意义的render。
memo不仅适用于函数组件,也适用于class组件,是一个高阶组件,默认情况下只会对复杂对象做浅层比较,如果想做深度比较,可以传入第二个参数。与 shouldComponentUpdate 不同的是,deepCompare 返回 true 时,不会触发 render,如果返回 false,则会。而 shouldComponentUpdate 刚好与其相反。
import React, {useState, memo } from "react";
import deepCompare from "./deepCompare";
const Child = memo((props:any) => {
console.log('子组件')
return (
<>
<div>我是一个子组件</div>
<div>{ props.fooObj.a}</div>
</>
);
}, deepCompare)
const Page = (props:any) => {
const [count, setCount] = useState(0);
const [fooObj, setFooObj] = useState({ a: 1, b: { c: 2 } })
console.log('页面开始渲染')
const calc = () => {
setCount(count + 1);
if (count === 3) {
setFooObj({ b: { c: 2 }, a: count })
}
}
const doBar = () => {
console.log('给子组件传递方法,测试一下是否会引起不必须的渲染')
}
return (
<>
<button onClick={calc}>加1</button>
<p>count:{count}</p>
<Child fooObj={fooObj} doBar={doBar} />
</>
)
}
export default Page;
// 深度比较两个对象是否相等
export default function deepCompare(prevProps: any, nextProps: any) {
const len: number = arguments.length;
let leftChain: any[] = [];
let rightChain: any = [];
// // console.log({ arguments });
//
if (len < 2) {
// console.log('需要传入2个对象,才能进行两个对象的属性对比');
return true;
}
// for (let i = 1; i < len; i++) {
// leftChain = [];
// rightChain = [];
console.log({ prevProps, nextProps });
if (!compare2Objects(prevProps, nextProps, leftChain, rightChain)) {
// console.log('两个对象不相等');
return false;
}
// }
// console.log('两个对象相等'); return true;
} function compare2Objects(prevProps: any, nextProps: any, leftChain: any, rightChain: any) {
var p; // 两个值都为为NaN时,在js中是不相等的, 而在这里认为相等才是合理的
if (isNaN(prevProps) && isNaN(nextProps) && typeof prevProps === 'number' && typeof nextProps === 'number') {
return true;
} // 原始值比较
if (prevProps === nextProps) {
console.log('原始值', prevProps, nextProps);
return true;
} // 构造类型比较
if (
(typeof prevProps === 'function' && typeof nextProps === 'function') ||
(prevProps instanceof Date && nextProps instanceof Date) ||
(prevProps instanceof RegExp && nextProps instanceof RegExp) ||
(prevProps instanceof String && nextProps instanceof String) ||
(prevProps instanceof Number && nextProps instanceof Number)
) {
console.log('function', prevProps.toString() === nextProps.toString());
return prevProps.toString() === nextProps.toString();
} // 两个比较变量的值如果是null和undefined,在这里会退出
if (!(prevProps instanceof Object && nextProps instanceof Object)) {
console.log(prevProps, nextProps, 'prevProps instanceof Object && nextProps instanceof Object');
return false;
} if (prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)) {
console.log('prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)');
return false;
} // 构造器不相等则两个对象不相等
if (prevProps.constructor !== nextProps.constructor) {
console.log('prevProps.constructor !== nextProps.constructor');
return false;
} // 原型不相等则两个对象不相等
if (prevProps.prototype !== nextProps.prototype) {
console.log('prevProps.prototype !== nextProps.prototype');
return false;
} if (leftChain.indexOf(prevProps) > -1 || rightChain.indexOf(nextProps) > -1) {
console.log('leftChain.indexOf(prevProps) > -1 || rightChain.indexOf(nextProps) > -1');
return false;
} // 遍历下次的属性对象,优先比较不相等的情形
for (p in nextProps) {
if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) {
console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)');
return false;
} else if (typeof nextProps[p] !== typeof prevProps[p]) {
console.log('typeof nextProps[p] !== typeof prevProps[p]');
return false;
}
}
// console.log('p in prevProps');
// 遍历上次的属性对象,优先比较不相等的情形
for (p in prevProps) {
// 是否都存在某个属性值
if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) {
console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)');
return false;
}
// 属性值的类型是否相等
else if (typeof nextProps[p] !== typeof prevProps[p]) {
console.log('typeof nextProps[p] !== typeof prevProps[p]');
return false;
} console.log('typeof prevProps[p]', typeof prevProps[p]);
switch (typeof prevProps[p]) {
// 对象类型和函数类型的处理
case 'object':
case 'function':
leftChain.push(prevProps);
rightChain.push(nextProps); if (!compare2Objects(prevProps[p], nextProps[p], leftChain, rightChain)) {
console.log('!compare2Objects(prevProps[p], nextProps[p], leftChain, rightChain)');
return false;
} leftChain.pop();
rightChain.pop();
break; default:
// 基础类型的处理
if (prevProps[p] !== nextProps[p]) {
return false;
}
break;
}
} return true;
}

3.用useCallback对组件方法进行包装
当父组件传递方法给子组件的时候,memo好像没什么效果,无论是用const定义的方法,还在用箭头函数或者bind定义的方法,子组件还是执行了
import React, { useState,memo } from 'react';
//子组件会有不必要渲染的例子
interface ChildProps {
changeName: ()=>void;
}
const FunChild = ({ changeName}: ChildProps): JSX.Element => {
console.log('普通函数子组件')
return(
<>
<div>我是普通函数子组件</div>
<button onClick={changeName}>普通函数子组件按钮</button>
</>
);
}
const FunMemo = memo(FunChild);
const ArrowChild = ({ changeName}: ChildProps): JSX.Element => {
console.log('箭头函数子组件')
return(
<>
<div>我是箭头函数子组件</div>
<button onClick={changeName.bind(null,'test')}>箭头函数子组件按钮</button>
</>
);
}
const ArrowMemo = memo(ArrowChild);
const BindChild = ({ changeName}: ChildProps): JSX.Element => {
console.log('Bind函数子组件')
return(
<>
<div>我是Bind函数子组件</div>
<button onClick={changeName}>Bind函数子组件按钮</button>
</>
);
}
const BindMemo = memo(BindChild);
const Page = (props:any) => {
const [count, setCount] = useState(0);
const name = "test";
const changeName = function() {
console.log('测试给子组件传递方法,使用useCallback后,子组件是否还会进行无效渲染');
}
return (
<>
<button onClick={(e) => { setCount(count+1) }}>加1</button>
<p>count:{count}</p>
<ArrowMemo changeName={()=>changeName()}/>
<BindMemo changeName={changeName.bind(null)}/>
<FunMemo changeName={changeName} />
</>
)
}
export default Page;

使用useCallback,参数为[],页面初始渲染后,改变count的值,传递普通函数的子组件不再渲染, 传递箭头函数和bind方式书写的方法的子组件还是会渲染
import React, { useState,memo ,useCallback} from 'react';
//子组件会有不必要渲染的例子
interface ChildProps {
changeName: ()=>void;
}
const FunChild = ({ changeName}: ChildProps): JSX.Element => {
console.log('普通函数子组件')
return(
<>
<div>我是普通函数子组件</div>
<button onClick={changeName}>普通函数子组件按钮</button>
</>
);
}
const FunMemo = memo(FunChild);
const ArrowChild = ({ changeName}: ChildProps): JSX.Element => {
console.log('箭头函数子组件')
return(
<>
<div>我是箭头函数子组件</div>
<button onClick={changeName.bind(null,'test')}>箭头函数子组件按钮</button>
</>
);
}
const ArrowMemo = memo(ArrowChild);
const BindChild = ({ changeName}: ChildProps): JSX.Element => {
console.log('Bind函数子组件')
return(
<>
<div>我是Bind函数子组件</div>
<button onClick={changeName}>Bind函数子组件按钮</button>
</>
);
}
const BindMemo = memo(BindChild);
const Page = (props:any) => {
const [count, setCount] = useState(0);
const name = "test";
const changeName = useCallback(() => {
console.log('测试给子组件传递方法,使用useCallback后,子组件是否还会进行无效渲染');
},[])
return (
<>
<button onClick={(e) => { setCount(count+1) }}>加1</button>
<p>count:{count}</p>
<ArrowMemo changeName={()=>changeName()}/>
<BindMemo changeName={changeName.bind(null)}/>
<FunMemo changeName={changeName} />
</>
)
}
export default Page;

4.用useMemo对组件中的对象变量进行包装
在子组件使用了memo,useCallback的情况下,给子组件传递一个对象属性,对象值和方法都未发生改变的情况下,父组件无关状态变更,子组件也会重新渲染。
import React, { useState,memo ,useCallback} from 'react';
//子组件会有不必要渲染的例子-使用了memo,useCallback的情况下,给子组件传递一个对象属性值
interface ChildProps {
childStyle: { color: string; fontSize: string;};
changeName: ()=>void;
}
const FunChild = ({ childStyle,changeName}: ChildProps): JSX.Element => {
console.log('普通函数子组件')
return(
<>
<div style={childStyle}>我是普通函数子组件</div>
<button onClick={changeName}>普通函数子组件按钮</button>
</>
);
}
const FunMemo = memo(FunChild);
const Page = (props:any) => {
const [count, setCount] = useState(0);
const childStyle = {color:'green',fontSize:'16px'};
const changeName = useCallback(() => {
console.log('测试给子组件传递方法,使用useCallback后,子组件是否还会进行无效渲染');
},[])
return (
<>
<button onClick={(e) => { setCount(count+1) }}>加1</button>
<p>count:{count}</p>
<FunMemo childStyle={childStyle} changeName={changeName} />
</>
)
}
export default Page;

使用useMemo可以解决给子组件传递对象属性时的不必要更新问题。
import React, { useState,memo, useMemo, useCallback} from 'react';
//子组件会有不必要渲染的例子
interface ChildProps {
childStyle: { color: string; fontSize: string;};
changeName: ()=>void;
}
const FunChild = ({ childStyle,changeName}: ChildProps): JSX.Element => {
console.log('普通函数子组件')
return(
<>
<div style={childStyle}>我是普通函数子组件</div>
<button onClick={changeName}>普通函数子组件按钮</button>
</>
);
}
const FunMemo = memo(FunChild);
const Page = (props:any) => {
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const childStyle = {color:'green',fontSize:'16px'};
const changeName = useCallback(() => {
setName('变一下名称')
}, [])
const childStyleMemo = useMemo(() => {
return {
color: name === '变一下名称' ? 'red':'green',
fontSize: '16px'
}
}, [name])
return (
<>
<button onClick={(e) => { setCount(count+1) }}>加1</button>
<p>count:{count}</p>
<FunMemo childStyle={childStyleMemo} changeName={changeName} />
</>
)
}
export default Page;

React Hooks使用避坑指南的更多相关文章
- electron 编译 sqlite3避坑指南---尾部链接有已经编译成功的sqlite3
electron 编译 sqlite3避坑指南(尾部链接有已经编译成功的sqlite3) sqlite很好用,不需要安装,使用electron开发桌面程序,sqlite自然是存储数据的不二之选,奈何编 ...
- CEF避坑指南(一)——下载并编译第一个示例
CEF即Chromium Embedded Framework,Chrome浏览器嵌入式框架.它提供了接口供程序员们把Chrome放到自己的程序中.许多大型公司,如网易.腾讯都开始使用CEF进行前端开 ...
- Canal v1.1.4版本避坑指南
前提 在忍耐了很久之后,忍不住爆发了,在掘金发了条沸点(下班时发的): 这是一个令人悲伤的故事,这条情感爆发的沸点好像被屏蔽了,另外小水渠(Canal意为水道.管道)上线一段时间,不出坑的时候风平浪静 ...
- Linux下Python3.6的安装及避坑指南
Python3的安装 1.安装依赖环境 Python3在安装的过程中可能会用到各种依赖库,所以在正式安装Python3之前,需要将这些依赖库先行安装好. yum -y install zlib-dev ...
- Hive改表结构的两个坑|避坑指南
Hive在大数据中可能是数据工程师使用的最多的组件,常见的数据仓库一般都是基于Hive搭建的,在使用Hive时候,遇到了两个奇怪的现象,今天给大家聊一下,以后遇到此类问题知道如何避坑! 坑一:改变字段 ...
- Harmony OS 开发避坑指南——源码下载和编译
Harmony OS 开发避坑指南--源码下载和编译 本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(Hi3516,Hi3518和Hi3861)的编译环境,以及如何将源码编译为三个 ...
- 今天 1024,为了不 996,Lombok 用起来以及避坑指南
Lombok简介.使用.工作原理.优缺点 Lombok 项目是一个 Java 库,它会自动插入编辑器和构建工具中,Lombok 提供了一组有用的注解,用来消除 Java 类中的大量样板代码. 目录 L ...
- Android连接远程数据库的避坑指南
Android连接远程数据库的避坑指南 今天用Android Studio连接数据库时候,写了个测试连接的按钮,然后连接的时候报错了,报错信息: 2021-09-07 22:45:20.433 705 ...
- Windows环境下Anaconda安装TensorFlow的避坑指南
最近群里聊天时经常会提到DL的东西,也有群友在学习mxnet,但听说坑比较多.为了赶上潮流顺便避坑,我果断选择了TensorFlow,然而谁知一上来就掉坑里了…… 我根据网上的安装教程,默认安装了最新 ...
随机推荐
- android手势识别
一.简单手势识别: 简单手势识别分为以下几个步骤: 1.手指触碰屏幕时,触发MotionEvent事件: 2.该事件被OnTouchListener监听,可在它的onTouch()方法中获得该Moti ...
- 【Docker】2. Docker的架构介绍、安装与卸载 (CentOS 7)
一.docker的基本组成 开局一张图. docker的组成: Client:客户端,可以通过它与docker服务进行交互.比如容器的构建.拉取.运行. DOCKER_HOST:就是docker服务. ...
- FHD 4K 8K分辨率
4K(2160P,即4096×2160的像素分辨率)和8K(4320P,即7,680 × 4,320的像素分辨率)属于UHDTV. FHD是FULL HD(Full High Definition)的 ...
- MongoDB·Windows下管理员密码重置解决方案
阅文时长 | 1.07分钟 字数统计 | 1730.4字符 主要内容 | 1.问题切入 2.详细步骤 3.声明与参考资料 『MongoDB·Windows下管理员密码重置解决方案』 编写人 | SCs ...
- Jmeter(四十五) - 从入门到精通高级篇 - Jmeter之网页爬虫-上篇(详解教程)
1.简介 上大学的时候,第一次听同学说网页爬虫,当时比较幼稚和懵懂,觉得就是几只电子虫子爬在网页上在抓取东西.后来又听说写代码可以实现网页爬虫,宏哥感觉高大上,后来工作又听说,有的公司做爬虫被抓的新闻 ...
- sed -i 's/Search_String/Replacement_String/g' Input_File sed详细手册
本文列出的十五个例子可以帮助你掌握 sed 命令.如果要使用 sed 命令删除文件中的行,去下面的文章.注意:由于这是一篇演示文章,我们使用不带 -i 选项的 sed 命令,该选项会在 Linux 终 ...
- 利用IOzone进行存储性能测试
利用IOzone进行存储性能测试 命令:1.iozone -s 10G -r 4k -i 0(0代表顺序写) -w(代表文件不删除) -+n(不测重读重写) -Rb(以某种格式生成测试文件) /t ...
- Hutool :一个小而全的 Java 工具类库
Hutool 简介 Hutool 是一个小而全的 Java 工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以"甜甜的 ...
- 一些固化了的语音识别模块demo, 手机重力传感器获取
helloH5 这个软件里面有好多这个东东哦
- Halide应用开发
Halide应用开发 1. 基本原理 1.1.介绍 随着人工智能的普及,深度学习网络的不断涌现,为了让各硬件(CPU, GPU, NPU,...)能够支持深度学习应用,各硬件芯片需要软件库去支持高性能 ...