web前端开发 | React中常见的Hook
bigegpt 2025-06-24 11:54 3 浏览
Hook是 React16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用。
一、Hook的优点
Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。现在你可以在现有的函数组件中使用 Hook。
- 简化逻辑复用:在之前的React使用中难以实现逻辑的复用,必须借助于高阶组件等复杂的设计模式。但是高阶组件会产生冗余的组件节点,让调试变得困难。所以Hooks的好处就是简化了逻辑复用。
- 有助于关注分离:意思是说Hooks能够让针对用一个业务逻辑的代码尽可能聚合在一块。在过去的Class组件中是很难做到的。因为Class组件中,不得不把同一个业务逻辑的代码分散在类组件的不同生命周期的方法中。所以通过Hooks的方式,把业务逻辑清晰地隔离开,能够让代码更加容易理解和维护。
二、useState状态钩子
useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
useState让函数式组件支持state状态。通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时记住它当前state的值,并且提供最新的值给我们的函数。
useState 返回一个只有两个元素的数组:
- 第一个元素是当前的 state 的值。
- 第二个元素是一个函数,用来替换原来state中的值,这个函数的修改state和setState一样是异步的,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState。
useState 唯一的参数就是初始 state,这个初始 state 参数只有在第一次渲染时会被用到。
import { useState } from 'react'
export default function App() {
// 声明一个叫 count 的 state 变量 useState(0) 传参 0 为设置的初始值
const [count, setCount] = useState(0);//[] 数组解构 取到数组中对应位置的值 赋给相应变量
const [isHot, setIsHot] = useState(true);
const changeCount = () => {
setCount(count + 1)
console.log(count, "1111");
}
const changeHot = () => {
setIsHot(!isHot)
console.log(isHot, "1111");
}
console.log("组件被重新渲染了");
return (
<div>
<h1>累加的值是{count}</h1>
<h2>天气真的{isHot ? "热啊" : "冷a"}</h2>
<button onClick={changeCount}>累加</button>
<button onClick={changeHot}>变天</button>
</div>
)
}
三、useEffect副作用钩子
useEffect 就是一个 Effect Hook,可以让你在函数组件中执行副作用操作。
useEffect可以告诉 React 组件需要在挂载完成、更新完成、卸载前执行某些操作。它跟 class 组件中的componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
它的常见用途有下面几种。
- 获取数据(data fetching)
- 事件监听或订阅(setting up a subscription)
- 改变 DOM(changing the DOM)
- 输出日志(logging)
useEffect接受两个参数:
- 第一个参数是一个回调函数,当达到条件的时候,会触发当前的回调函数。
- 第二个参数是一个数组,数组中传入state,则当前state发生改变的时候触发当前effect中的回调函数。如果传递的是一个空数组,则useEffct回调函数只有在初始化阶段才执行。如果不传递第二个参数,则无论初始化还是更新的时候都会执行。
import React, { useEffect, useState } from 'react'
export default function App() {
const [count, setCount] = useState(0);
const [isHot, setIsHot] = useState(true);
useEffect(() => {
console.log("what happened?");
});
useEffect(() => {
console.log("组件渲染了");
}, []);
useEffect(() => {
console.log("isHot执行了");
}, [isHot]);
useEffect(() => {
console.log("count执行了");
}, [count])
return (
<div>
<h1>count的值是{count}</h1>
<button onClick={() => { setCount(count + 1) }}>累加</button>
<hr />
<h1>今天的天气真{isHot ? "晴朗" : "下雨"}</h1>
<button onClick={() => { setIsHot(!isHot) }}>修改天气</button>
</div>
)
}
如何使用不需要清除的副作用,还有一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!
useEffect的回调函数可以返回一个函数,当组件被卸载的时候,或再次渲染的时候,会执行这个函数,用来做收尾工作。
import { useState } from "react";
import { useEffect } from "react";
export default function App() {
let [opacity, setOpacity] = useState(1);
const [count, setCount] = useState(0);
//初始化的时候添加一个定时器
useEffect(() => {
const opacityTimer = setInterval(() => {
opacity -= 0.1;
if (opacity <= 0) {
opacity = 1;
}
setOpacity(opacity);
}, 200);
return () => {
console.log("清空定时器" + opacityTimer);
clearInterval(opacityTimer);
};
}, []);
useEffect(() => {
console.log("count初始或更新了");
return () => {
console.log("gogogo");
};
}, [count]);
return (
<div>
<h1>{count}</h1>
<hr />
<h1 style={{ opacity }}>ReactHook 真好用</h1>
<button
onClick={() => {
setCount(count + 1);
}}
>
累加
</button>
</div>
);
}
为什么要在 effect 中返回一个函数?这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
React 何时清除 effect?React 会在组件卸载的时候执行清除操作。effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。
四、useContext共享状态钩子
如果需要在组件之间共享状态,可以使用useContext()。
第一步
React Context API,在组件外部建立一个 Context
export const AppContext = React.createContext();//可以接受一个初始值
第二步
AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享。共享对象AppContext上有一个Provide属性是一个组件,他可以给组件内部包含的组件提供数据,提供的数据放在value属性上。
<AppContext.Provider value={count}>
<List />
</AppContext.Provider>
第三步
useContext()钩子函数用来引入 AppContext对象,从中获取count的值。
import Item from "./Item"
export default function List() {
return (
<div>
<Item />
</div>
)
}
import { useContext } from 'react'
import { AppContext } from '../../../App'
export default function Item() {
const count = useContext(AppContext);
return (
<div>
<h1>Item{count}</h1>
</div>
)
}
调用了 useContext 的组件总会在 context 值变化时重新渲染。
五、useMemo
useMemo 的基本概念就是:它能帮助我们 “记录” 每次渲染之间的计算值。并在组件重新渲染时,做出一些不同的决策。
useMemo接受2个参数:
- 第一个参数:需要执行的一些计算处理工作,包裹在一个函数中。
- 第二个参数:一个依赖数组。
当没使用useMemo时,组件的渲染情况:
import { useEffect, useState } from "react";
import { format } from "date-fns";
function App() {
const [num, setNum] = useState("0");
const [time, setTime] = useState(new Date());
useEffect(() => {
setTimeout(() => {
setTime(new Date());
}, 1000);
}, [time]);
const getNum = () => {
console.log("正在进行大量运算!!!");
return num;
};
return (
<div>
<h1>App</h1>
<p>{format(time, "hh:mm:ss a")}</p>
<input
type="text"
value={num}
onChange={(e) => {
setNum(e.target.value);
}}
/>
<h2>{getNum()}</h2>
</div>
);
}
export default App;
此时当time更新,就会导致组件重新渲染,致使getNum函数一直在重新调用,但获取的num的值却没有变化。那么我们就可以使用useMemo这个Hook。
import { useMemo, useEffect, useState } from "react";
import { format } from "date-fns";
function App() {
const [num, setNum] = useState("0");
const [time, setTime] = useState(new Date());
useEffect(() => {
setTimeout(() => {
setTime(new Date());
}, 1000);
}, [time]);
const getNum = () => {
console.log("正在进行大量运算!!!");
return num;
};
const result = useMemo(() => {
return getNum();
}, [num]);
return (
<div>
<h1>App</h1>
<p>{format(time, "hh:mm:ss a")}</p>
<input
type="text"
value={num}
onChange={(e) => {
setNum(e.target.value);
}}
/>
<h2>{result}</h2>
{/* <h2>{getNum()}</h2> */}
</div>
);
}
export default App;
对于每一个后续的渲染,React 都要从以下两种情况中做出选择:
1.再次调用 useMemo 中的计算函数,重新计算数值;
2.重复使用上一次已经计算出来的数据。
为了做出一个正确的选择,React 会判断你传入的依赖数组,这个数组中的每个变量是否在两次渲染间 值是否改变了 ,如果发生了改变,就重新执行计算的逻辑去获取一个新的值,否则不重新计算,直接返回上一次计算的值。
useMemo 本质上就像一个小的缓存,而依赖数组就是缓存的失效策略。
六、useCallback
简单概括:useMemo 和 useCallback 是一个东西,只是将返回值从 数组/对象 替换为了 函数。
父组件:
import React, { useState } from "react";
import Child from "./Child";
function App() {
const [count, setCount] = useState(0);
const [num, setNum] = useState(3);
const addNum = () => {
setNum(num + 3);
};
const addCount = () => {
setCount(count + 1);
};
return (
<div>
<h1>App</h1>
<p>count:{count}</p>
<p>num:{num}</p>
<button onClick={addCount}>addCount累加</button>
{/* 更新count的方法由子组件触发,传递方法给子组件 */}
<Child add={addNum} />
</div>
);
}
export default App;
子组件:
import React, { memo, useState } from "react";
interface ChildProps {
add(): void;
}
export default memo(function Child({ add }: ChildProps) {
console.log("Child render");
return (
<div>
<h1>Child</h1>
<button onClick={add}>addNum累加</button>
</div>
);
});
父组件重新渲染会重新定义addNum、addCount两个函数,子组件接受到的porps会发生变化,便也会重新渲染。当我们调用父组件的addCount事件,只渲染当前的count,传递给子组件的addNum函数不需要重新生成,子组件也不用重新渲染。这时候就可以用到useCallback Hook,专门用来缓存函数的,必须要指定依赖项(第二个参数)。
import React, { useCallback, useState } from "react";
import Child from "./Child";
function App() {
const [count, setCount] = useState(0);
const [num, setNum] = useState(3);
// const addNum = () => {
// setNum(num + 3);
// };
const addNum = useCallback(() => {
setNum(num + 3);
}, [num]);
const addCount = () => {
setCount(count + 1);
};
return (
<div>
<h1>App</h1>
<p>count:{count}</p>
<p>num:{num}</p>
<button onClick={addCount}>addCount累加</button>
{/* 更新count的方法由子组件触发,传递方法给子组件 */}
<Child add={addNum} />
</div>
);
}
export default App;
useCallback的作用:
1.用来缓存函数的;
2.当依赖项数据发生变化,才会生成新的函数,否则一直使用之前的函数
3.注意使用场景:如果函数功能很简单,就没必要缓存了;如果这个函数要传递给其他组件使用,需要缓存;如果这个函数本身功能比较复杂,需要缓存。
总结
本文主要介绍了当前React当中的常见基础Hook,分别有useState、useEffect、useContext、useMemo、useCallback。useState通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。useEffect给函数组件增加了操作副作用的能力。useContext给函数组件之间共享状态。useMemo 和 useCallback 都是用来帮助我们优化 重新渲染 的工具 Hook。它们通过以下两种方式实现优化的效果。减少在一次渲染中需要完成的工作量。减少一个组件需要重新渲染的次数。
相关推荐
- Docker篇(二):Docker实战,命令解析
-
大家好,我是杰哥上周我们通过几个问题,让大家对于Docker有了一个全局的认识。然而,说跟练往往是两个概念。从学习的角度来说,理论知识的学习,往往只是第一步,只有经过实战,才能真正掌握一门技术所以,本...
- docker学习笔记——安装和基本操作
-
今天学习了docker的基本知识,记录一下docker的安装步骤和基本命令(以CentOS7.x为例)一、安装docker的步骤:1.yuminstall-yyum-utils2.yum-con...
- 不可错过的Docker完整笔记(dockerhib)
-
简介一、Docker简介Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,...
- 扔掉运营商的 IPTV 机顶盒,全屋全设备畅看 IPTV!
-
其实现在看电视节目的需求确实大大降低了,折腾也只是为了单纯的让它实现,享受这个过程带来的快乐而已,哈哈!预期构想家里所有设备直接接入网络随时接收并播放IPTV直播(电信点播的节目不是太多,但好在非常稳...
- 第五节 Docker 入门实践:从 Hello World 到容器操作
-
一、Docker容器基础运行(一)单次命令执行通过dockerrun命令可以直接在容器中执行指定命令,这是体验Docker最快捷的方式:#在ubuntu:15.10容器中执行ech...
- 替代Docker build的Buildah简单介绍
-
Buildah是用于通过较低级别的coreutils接口构建OCI兼容镜像的工具。与Podman相似,Buildah不依赖于Docker或CRI-O之类的守护程序,并且不需要root特权。Builda...
- Docker 命令大全(docker命令大全记录表)
-
容器生命周期管理run-创建并启动一个新的容器。start/stop/restart-这些命令主要用于启动、停止和重启容器。kill-立即终止一个或多个正在运行的容器rm-于删除一个或...
- docker常用指令及安装rabbitMQ(docker安装rabbitmq配置环境)
-
一、docker常用指令启动docker:systemctlstartdocker停止docker:systemctlstopdocker重启docker:systemctlrestart...
- 使用Docker快速部署Storm环境(docker部署confluence)
-
Storm的部署虽然不是特别麻烦,但是在生产环境中,为了提高部署效率,方便管理维护,使用Docker来统一管理部署是一个不错的选择。下面是我开源的一个新的项目,一个配置好了storm与mono环境的D...
- Docker Desktop安装使用指南:零基础教程
-
在之前的文章中,我多次提到使用Docker来安装各类软件,尤其是开源软件应用。鉴于不少读者对此有需求,我决定专门制作一期关于Docker安装与使用的详细教程。我主要以Macbook(Mac平台)为例进...
- Linux如何成功地离线安装docker(linux离线安装httpd)
-
系统环境:Redhat7.2和Centos7.4实测成功近期因项目需要用docker,所以记录一些相关知识,由于生产环境是不能直接连接互联网,尝试在linux中离线安装docker。步骤1.下载...
- Docker 类面试题(常见问题)(docker面试题目)
-
Docker常见问题汇总镜像相关1、如何批量清理临时镜像文件?可以使用sudodockerrmi$(sudodockerimages-q-fdanging=true)命令2、如何查看...
- 面试官:你知道Dubbo怎么优雅上下线的吗?你:优雅上下线是啥?
-
最近无论是校招还是社招,都进行的如火如荼,我也承担了很多的面试工作,在一次面试过程中,和候选人聊了一些关于Dubbo的知识。Dubbo是一个比较著名的RPC框架,很多人对于他的一些网络通信、通信协议、...
- 【Docker 新手入门指南】第五章:Hello Word
-
适合人群:完全零基础新手|学习目标:30分钟掌握Docker核心操作一、准备工作:先确认是否安装成功打开终端(Windows用户用PowerShell或GitBash),输入:docker--...
- 松勤软件测试:详解Docker,如何用portainer管理Docker容器
-
镜像管理搜索镜像dockersearch镜像名称拉取镜像dockerpullname[:tag]列出镜像dockerimages删除镜像dockerrmiimage名称或id删除...
- 一周热门
- 最近发表
-
- Docker篇(二):Docker实战,命令解析
- docker学习笔记——安装和基本操作
- 不可错过的Docker完整笔记(dockerhib)
- 扔掉运营商的 IPTV 机顶盒,全屋全设备畅看 IPTV!
- 第五节 Docker 入门实践:从 Hello World 到容器操作
- 替代Docker build的Buildah简单介绍
- Docker 命令大全(docker命令大全记录表)
- docker常用指令及安装rabbitMQ(docker安装rabbitmq配置环境)
- 使用Docker快速部署Storm环境(docker部署confluence)
- Docker Desktop安装使用指南:零基础教程
- 标签列表
-
- mybatiscollection (79)
- mqtt服务器 (88)
- keyerror (78)
- c#map (65)
- resize函数 (64)
- xftp6 (83)
- bt搜索 (75)
- c#var (76)
- mybatis大于等于 (64)
- xcode-select (66)
- mysql授权 (74)
- 下载测试 (70)
- linuxlink (65)
- pythonwget (67)
- androidinclude (65)
- logstashinput (65)
- hadoop端口 (65)
- vue阻止冒泡 (67)
- oracle时间戳转换日期 (64)
- jquery跨域 (68)
- php写入文件 (73)
- kafkatools (66)
- mysql导出数据库 (66)
- jquery鼠标移入移出 (71)
- 取小数点后两位的函数 (73)