网站首页 > 技术文章 正文
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。它们通过以下两种方式实现优化的效果。减少在一次渲染中需要完成的工作量。减少一个组件需要重新渲染的次数。
猜你喜欢
- 2025-05-14 高性能 React 表格组件 ali-react-table
- 2025-05-14 React状态管理专题:什么是Redux
- 2025-05-14 React:我们一直在做什么
- 2025-05-14 Web前端:NextJS与React——主要差异、优势和局限性
- 2025-05-14 一起了解 React 19 带来的6个主流特性?
- 2025-05-14 React 组件频繁重渲染?3 行代码实现性能翻倍的实战解法
- 2025-05-14 前端 - React 受控组件和非受控组件
- 2025-05-14 Web前端:React开发人员需要具备的十大技能
- 2025-05-14 React 组件频繁重绘?5 个实战技巧让页面流畅度暴涨 70%!
- 2025-05-14 致前端:react开发的5个资源合集
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端md5加密 (49)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- 前端接口 (46)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle约束 (46)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- mac oracle (47)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)