Hooks

HooksReact16.8 的新增特性,能够在不写 class 的情况下使用 state 以及其他特性。

动机

  • 在组件之间复用状态逻辑很难
  • 复杂组件变得难以理解
  • 难以理解的 class

Hooks 规则

  • 只有在最顶层使用 Hooks不要再循环/条件/嵌套函数中使用`
  • 只有在 React 函数中调用 Hooks

函数组件和类组件的不同

函数组件能够捕获到当前渲染的所用的值。

点击查看示例

对于类组件来说,虽然 props是一个不可变的数据,但是 this是一个可变的数据,在我们渲染组件的时候 this 发生了改变,所以 this.props 发生了改变,因此在 this.showMessage 中会拿到最新的 props 值。

对于函数组件来说捕获了渲染所使用的值,当我们使用 hooks 时,这种特性也同样的试用于 state 上。

点击查看示例

const showMessage = () => {
alert("写入:" + message);
}; const handleSendClick = () => {
setTimeout(showMessage, 3000);
}; const handleMessageChange = (e) => {
setMessage(e.target.value);
};

如果我们想跳出'函数组件捕获当前渲染的所用值‘这个特性,我们可以采用 ref 来追踪某些数据。通ref.current可以获取到最新的值

const showMessage = () => {
alert("写入:" + ref.current);
}; const handleSendClick = () => {
setTimeout(showMessage, 3000);
}; const handleMessageChange = (e) => {
setMessage(e.target.value);
ref.current = e.target.value;
};

useEffect

useEffect 能够在函数组件中执行副作用操作(数据获取/涉及订阅),其实可以把 useEffect 看作是 componentDidMount / componentDidUpdate / componentWillUnMount 的组合

  • 第一个参数是一个 callback,返回 destorydestory 作为下一个 callback 执行前调用,用于清除上一次 callback 产生的副作用
  • 第二个参数是依赖项,一个数组,可以有多个依赖项。依赖项改变,执行上一个 callback 返回的 destory,和执行新的 effect 第一个参数 callback

对于 useEffect 的执行,React 处理逻辑是采用异步调用的,对于每一个 effectcallback 会像 setTimeout 回调函数一样,放到任务队列里面,等到主线程执行完毕才会执行。所以 effect 的回调函数不会阻塞浏览器绘制视图

  1. 相关的生命周期替换方案
  • componentDidMount 替代方案
React.useEffect(()=>{
//请求数据,事件监听,操纵DOM
},[]) //dep=[],只有在初始化执行
/*
因为useEffect会捕获props和state,
所以即使是在回调函数中我们拿到的还是最初的props和state
*/
  • componentDidUnmount 替代方案
React.useEffect(()=>{
/* 请求数据 , 事件监听 , 操纵dom , 增加定时器,延时器 */
return function componentWillUnmount(){
/* 解除事件监听器 ,清除定时器,延时器 */
}
},[])/* 切记 dep = [] */ //useEffect第一个函数的返回值可以作为componentWillUnmount使用
  • componentWillReceiveProps 替代方案

    其实两者的执行时机是完全不同的,一个在 render 阶段,一个在 commit 阶段,useEffect 会初始化执行一次,但是 componentWillReceiveProps 只会在 props 变化时执行更新
React.useEffect(()=>{
console.log('props变化:componentWillReceiveProps')
},[ props ])
  • componentDidUpdate 替代方案

    useEffectcomponentDidUpdate 在执行时期虽然有点差别,useEffect 是异步执行,componentDidUpdate 是同步执行 ,但都是在 commit 阶段
React.useEffect(()=>{
console.log('组件更新完成:componentDidUpdate ')
}) //没有dep依赖项,没有第二个参数,那么每一次执行函数组件,都会执行该 effect。
  1. useEffect 中[]需要处理什么

React 官网 FAQ这样说:

只有当函数(以及它所调用的函数)不引用 propsstate 以及由它们衍生而来的值时,你才能放心地把它们从依赖列表中省略,使用 eslint-plugin-react-hooks 帮助我们的代码做一个校验

点击查看详细示例

function Counter() {
const [count, setCount] = useState(0); useEffect(() => {
const id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
}, []); return <h1>{count}</h1>;
}
//只会做一次更新,然后定时器不再转动
  1. 是否应该把函数当做 effect 的依赖
const loadResourceCatalog = async () => {
if (!templateType) return
const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
const res: any = await API[reqApi]()
if (!res.success) return
setCatalog(res.data)
} useEffect(() => {
loadResourceCatalog();
}, [])
//在函数loadResourceCatalog中使用了templateType这样的一个state
//在开发的过程中可能会忘记函数loadResourceCatalog依赖templateType值

第一个简单的解法,对于某些只在 useEffect 中使用的函数,直接定义在 effect 中,以至于能够直接依赖某些 state

useEffect(() => {
const loadResourceCatalog = async () => {
if (!templateType) return
const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
const res: any = await API[reqApi]()
if (!res.success) return
setCatalog(res.data)
}
loadResourceCatalog();
}, [templateType])

假如我们需要在很多地方用到我们定义的函数,不能够把定义放到当前的 effect 中,并且将函数放到了第二个的依赖参数中,那这个代码将就进入死循环。因为函数在每一次渲染中都返回一个新的引用

const Template = () => {
const getStandardTemplateList = async () => {
const res: any = await API.getStandardTemplateList()
if (!res.success) return;
const { data } = res;
setCascaderOptions(data);
getDefaultOption(data[0])
}
useEffect(()=>{
getStandardTemplateList()
}, [])
}



针对这种情况,如果当前函数没有引用任何组件内的任何值,可以将该函数提取到组件外面去定义,这样就不会组件每次 render 时不会再次改变函数引用。

const getStandardTemplateList = async () => {
const res: any = await API.getStandardTemplateList()
if (!res.success) return;
const { data } = res;
setCascaderOptions(data);
getDefaultOption(data[0])
} const Template = () => {
useEffect(()=>{
getStandardTemplateList()
}, [])
}

如果说当前函数中引用了组件内的一些状态值,可以采用 useCallBack 对当前函数进行包裹

const loadResourceCatalog = useCallback(async () => {
if (!templateType) return
const reqApi = templateType === TEMPLATE_TYPE.STANDARD ? 'listCatalog' : 'getCodeManageCatalog'
const res: any = await API[reqApi]()
if (!res.success) return
setCatalog(res.data)
}, [templateType]) useEffect(() => {
loadResourceCatalog();
}, [loadResourceCatalog])
//通过useCallback的包裹,如果templateType保持不变,那么loadResourceCatalog也会保持不变,所以useEffect也不会重新运行
//如果templateType改变,那么loadResourceCatalog也会改变,所以useEffect也会重新运行

useCallback

React 官网定义

useCallback(fn, deps)

返回一个 memoized 回调函数,该回调函数仅在某个依赖项改变时才会更新

import React, { useCallback, useState } from "react";

const CallBackTest = () => {
const [count, setCount] = useState(0);
const [total, setTotal] = useState(0);
const handleCount = () => setCount(count + 1);
//const handleCount = useCallback(() => setCount(count + 1), [count]);
const handleTotal = () => setTotal(total + 1); return (
<div>
<div>Count is {count}</div>
<div>Total is {total}</div> <div>
<Child onClick={handleCount} label="Increment Count" />
<Child onClick={handleTotal} label="Increment Total" />
</div>
</div>
);
}; const Child = React.memo(({ onClick, label }) => {
console.log(`${label} Child Render`);
return <button onClick={onClick}>{label}</button>;
}); export default CallBackTest;

点击查看详细示例

React.memo 是通过记忆组件渲染结果的方式来提高性能,memoreact16.6 引入的新属性,通过浅比较(源码通过 Object.is 方法比较)当前依赖的 props 和下一个 props 是否相同来决定是否重新渲染;如果使用过类组件方式,就能知道 memo 其实就相当于 class 组件中的 React.PureComponent,区别就在于 memo 用于函数组件。useCallbackReact.memo 一定要结合使用才能有效果。

使用场景

  • 作为 props,传递给子组件,为避免子元素不必要的渲染,需要配合 React.Memo 使用,否则无意义
  • 作为 useEffect 的依赖项,需要进行比较的时候才需要加上 useCallback

useMemo

React 官网定义

返回一个 memoized

仅会在某个依赖项改变时才重新计算 memoized 值,这种优化有助于避免在每次渲染时都进行高开销的计算 useCallback(fn, deps) 相当于 useMemo(() => fn, deps),对于实现上,基本上是和 useCallback 相似,只是略微有些不同

使用场景

  • 避免在每次渲染时都进行高开销的计算

两个 hooks 内置于 React 都有特别的原因:

1.引用相等

当在 React 函数组件中定义一个对象时,它跟上次定义的相同对象,引用是不一样的(即使它具有所有相同值和相同属性)

  • 依赖列表
  • React.memo

大多数时候,你不需要考虑去优化不必要的重新渲染,因为优化总会带来成本。

  1. 昂贵的计算

    计算成本很高的同步计算值的函数

总结

本文介绍了 hooks 产生动机、函数组件和类组件的区别以及 useEffect / useCallback / useMemo 等内容。重点介绍了 useEffect 的生命周期替换方案以及是否把函数作为 useEffect 的第二参数。

参考链接

When to useMemo and useCallback

How to fetch data with React Hooks

A Complete Guide to useEffect

How Are Function Components Different from Classes?

useCallback、useMemo 分析 & 差别

你应该知道的Hooks知识的更多相关文章

  1. 前端工程师应该知道的yarn知识

    yarn 是在工作中离不开的工具,但在工作中,很多人基本只会使用 yarn install,而且会手动删除 node-modules,或删除 yarn.lock 文件等不规范操作.本文将从一些基础的知 ...

  2. Linux系统管理员应该知道的journalctl知识

    在Systemd出现之前,Linux系统及各应用的日志都是分别管理的,Systemd开始统一管理了所有Unit的启动日志,这样带来的好处就是可以只用一个 journalctl命令,查看所有内核和应用的 ...

  3. Spring Boot 学习前你应该知道的 Maven 知识

    Maven 是什么? 回答这个问题,我们先来了解下没有Maven,我们是怎么使用开发者工具IDE去开发Java程序的.我之前开发Java程序不多,但是我还是记得,我是从网上下载或从合作方拷贝 jar ...

  4. 每个开发人员都应该知道的WebSockets知识

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文出处:https://blog.bitsrc.io/deep-dive-into-websockets- ...

  5. 前端必须知道的 Nginx 知识

    Nginx一直跟我们息息相关,它既可以作为Web 服务器,也可以作为负载均衡服务器,具备高性能.高并发连接等. 1.负载均衡 当一个应用单位时间内访问量激增,服务器的带宽及性能受到影响, 影响大到自身 ...

  6. [面试专题]前端需要知道的web安全知识

    前端需要知道的web安全知识 标签(空格分隔): 未分类 安全 [Doc] Crypto (加密) [Doc] TLS/SSL [Doc] HTTPS [Point] XSS [Point] CSRF ...

  7. 理工科应该的知道的C/C++数学计算库(转)

    理工科应该的知道的C/C++数学计算库(转) 作为理工科学生,想必有限元分析.数值计算.三维建模.信号处理.性能分析.仿真分析...这些或多或少与我们常用的软件息息相关,假如有一天你只需要这些大型软件 ...

  8. 嵌入式程序员应知道的0x10个基本问题

     来源:网络 嵌入式程序员应知道的0x10个基本问题 1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)#define SECONDS_PER_YEAR (60 ...

  9. C#开发人员应该知道的13件事情

    本文讲述了C#开发人员应该了解到的13件事情,希望对C#开发人员有所帮助. 1. 开发过程 开发过程是错误和缺陷开始的地方.使用工具可以帮助你在发布之后,解决掉一些问题. 编码标准 遵照编码标准可以编 ...

  10. Android 程序员必须知道的 53 个知识点

    1. android 单实例运行方法 我们都知道 Android 平台没有任务管理器,而内部 App 维护者一个 Activity history stack 来实现窗口显示和销毁,对于常规从快捷方式 ...

随机推荐

  1. 一起学习ML和DL中常用的几种loss函数

    摘要:本篇内容和大家一起学习下机器学习和深度学习中常用到的几种loss函数. 本文分享自华为云社区<[MindSpore易点通]网络实战之交叉熵类Loss函数>,作者:Skytier . ...

  2. 顶会CIKM'21论文解读:基于图神经网络的人类行为轨迹恢复模型

    摘要:本文是由华为云数据库创新Lab联合电子科技大学数据与智能实验室发表在顶会CIKM'21的文章,该文章提出首个克服人类移动轨迹数据中普遍存在的多层次周期性.周期偏移现象以及数据稀疏问题的轨迹恢复模 ...

  3. 非root安装Anaconda

    1.下载对应版本的Anaconda (wget+路径) 下载地址:https://repo.anaconda.com/archive/ 或者清华镜像:https://mirrors.tuna.tsin ...

  4. appuploader 入门使用

    回想一下我们发布 iOS 应用,不仅步骤繁琐,非常耗时.一旦其中一步失误了,又得重新来.作为一名优秀的工程师不应该让这些重复的工作在浪费我们的人生.在软件工程里面,我们一直都推崇把重复.流程化的工作交 ...

  5. 火山引擎 DataTester 上线全新 MAB 智能调优实验

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 近期 DataTester 上线了 MAB 智能调优实验功能,希望通过智能化.自动化的 A/B 实验形式,帮助业务 ...

  6. OpenFeign 各种用法、 logger 日志记录

    <spring-cloud-openfeign.version>2.2.6.RELEASE</spring-cloud-openfeign.version>对应的SpringB ...

  7. 【k8s】基本原理学习

    k8s基本原理 https://www.cnblogs.com/BlueBlueSea/p/16908601.html 深入理解Kubernetes网络 深入理解Kubernetes的pod网络 参考 ...

  8. 它来了!真正的 python 多线程

    哈喽大家好,我是咸鱼 几天前,IBM 工程师 Martin Heinz 发文表示 python 3.12 版本回引入"Per-Interpreter GIL",有了这个 Per-I ...

  9. Codeforce 1327A - Sum of Odd Integers

    Example input 6 3 1 4 2 10 3 10 2 16 4 16 5 output YES YES NO YES YES NO 解题思路:首先我们应该知道:偶数个奇数相加一定是偶数, ...

  10. 【每日一题】33. 简单瞎搞题 (滚动数组 + bitset 优化DP)

    补题链接:Here 这个问题的难点在于如何统计出所有和可能出现的情况,并且不能重复. 很容易想到用桶去存储每一个数,即某个和能够组合出来则为1,否则为0 不妨令 \(dp[i][j]\) 表示为第 \ ...