本文正在参加「金石计划」
关于React Hooks(上)
create by db on 2023-4-18 15:01:12
Recently revised in 2023-4-18 18:33:42闲时要有吃紧的心思,忙时要有悠闲的趣味
前言
正文
一、什么是Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
在 React 中,useState 以及任何其他以“use”开头的函数都被称为 Hook。
Hooks 优势
能优化类组件的三大问题
- 能在无需修改组件结构的情况下复用状态逻辑(自定义 Hooks )
- 能将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
- 副作用的关注点分离:副作用指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。以往这些副作用都是写在类组件生命周期函数中的。而 useEffect 在全部渲染完毕后才会执行,useLayoutEffect 会在浏览器 layout 之后,painting 之前执行。
注意事项
- 只能在函数内部的最外层调用 Hook,不要在循环、条件判断或者子函数中调用
- 只能在 React 的函数组件中调用 Hook,不要在其他 JavaScript 函数中调用
二、State Hooks
useState
useState是react自带的一个hook函数,它的作用就是用来声明状态变量。
useState的使用很简单,一句话带过,返回一个数组,数组的值为当前state和更新state的函数;useState的参数是变量、对象或者是函数,变量或者对象会作为state的初始值,如果是函数,函数的返回值会作为初始值。
使用方式如下:
import React, { useState } from "react";
function Count() {
// 声明一个叫 “count” 的 state 变量。
let [count, setCount] = useState(0);
const handleAdd = () => {
// 每次只有一个生效
setCount(count + 1);
setCount(count + 1);
};
return (
<div>
<p>{count}</p>
{/* 每次点击加1 */}
<button onClick={handleAdd}>加一</button>
</div>
);
}
export default Count;
在同一个事件中并不会因为调用了两次setCount而让count增加两次,试想如果在同一个事件中每次调用setCount都生效,那么每调用一次setCount组件就会重新渲染一次,这无疑使非常影响性能的;实际上如果修改的state是同一个,最后一个setCount函数中的新state会覆盖前面的
三、Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作
useEffect
我们写的有状态组件,通常会产生很多的副作用(side effect),比如发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。
我们之前都把这些副作用的函数写在生命周期函数钩子里,比如componentDidMount,componentDidUpdate和componentWillUnmount。而现在的useEffect就相当与这些声明周期函数钩子的集合体。它以一抵三。
#### 使用方式
- useEffect 第一个参数一个函数,该函数会在组件渲染到屏幕之后才执行
- useEffect 第二个参数是一个数组,用第二个参数来告诉react只有当这个参数的值发生改变时,才执行我们传的副作用函数(即第一个参数)
- 组件每次渲染会默认执行一次
- 如果不传第二个参数只要该组件有state改变就会触发回调函数
- 如果传一个空数组,只会在初始化执行一次。
- 如果传数组,只要数组内数据改变就回触发回调函数。
- 如果用return返回了一个函数,组件每次重新渲染的时候都会先执行该函数再调用回调函数。
示例代码如下:
import React, { useState, useEffect } from "react";
function Count() {
// 声明一个叫 “count” 的 state 变量。
let [count1, setCount1] = useState(0);
let [count2, setCount2] = useState(0);
useEffect(() => {
console.log("count1更改");
}, [count1]); // 仅在 count1 更改时更新
useEffect(() => {
console.log("count1或者count2更改");
}, [count1, count2]); // 仅在 counth或者count2 更改时更新
useEffect(() => {
console.log("State更改");
}); // 仅在 State更改时更新
useEffect(() => {
console.log("组件更新");
}, []); // 仅在组件更新时更新
useEffect(() => {
const timer = setInterval(() => {
console.log("timer...count:", count1);
}, 1000);
return () => clearInterval(timer); //组件卸载时调用,不然会timer会一直在
}, []);
const handleAdd = () => {
// 每次只有一个生效
setCount1(count1 + 1);
setCount1(count1 + 1);
};
const handleAdd2 = () => {
// 每次只有一个生效
setCount2(count2 + 1);
};
return (
<div>
<p>{count1}</p>
<button onClick={handleAdd}>加一</button>
<p>{count2}</p>
<button onClick={handleAdd2}>加一</button>
</div>
);
}
export default Count;
注意
如果你在仅在开发模式("development")下,且使用了严格模式("Strict Mode")下会,useEffect会打印两次,不要担心,这不是 Bug,这是 React18 新加的特性,原因及解决方案如下:
useLayoutEffect
useLayoutEffect基本上与useEffect一样
相同点 useLayoutEffecty与useEffect其函数签名与 useEffect 相同,用法一致。
不同点
- 表面上看,这两个hook的区别是执行时机不同,useEffect的回调函数会在页面渲染后执行;useLayoutEffect会在页面渲染前执行。实际上是React对这两个hook的处理不同,useEffect是异步调用,而useLayoutEffect是同步调用。
使用场景 那什么时候用useEffect,什么时候用useLayoutEffect呢?
视情况而定
- 如果回调函数会修改state导致组件重新渲染,可以useLayoutEffect,因为这时候用useEffect可能会造成页面闪烁;
- 如果回调函数中去请求数据或者js执行时间过长,建议使用useEffect;因为这时候用useLayoutEffect堵塞浏览器渲染。
四、Ref Hooks
useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
这个hook通常用来获取组件实例,还可以用来缓存数据。
useRef 创建一个不受组件刷新而影响的变量
示例代码如下:
function UseRef() {
const [x, setX] = useState(0);
//函数组件只要更新了,a就会被重新为 0,所以函数组件需要借助useRef存储变量
let a = 0;
//useRef可以生成一个变量,用于在函数组件中存储数据
let currentVal = useRef(0);
useEffect(() => {
console.log(` =================`);
console.log(` x --- :${x}`);
console.log(` currentVal --- :${currentVal.current}`);
console.log(` a--- :${a}`);
}, [x]);
const clickAdd = () => {
setX(v => v + 1);
currentVal.current += 2;
a += 2;
};
return (
<>
<p>{x} ----</p>
<button onClick={clickAdd}>+1</button>
</>
);
}
因为a是普通变量,只要按钮点击,就会导致函数组件刷新,重新生成一个新的a,值永远都为0。所以在函数组件内如果想创建一个不受组件刷新而影响的变量,必须借助useRef生成
使用场景: 如果我们只是想保存状态不影响视图更新,而且可以同步更新&获取我们的状态,那么就使用 useRef。
注意事项:
- ref.current 不可以作为其他 hooks(useMemo, useCallback, useEffect)依赖项;
- ref.current 的值发生变更并不会造成 re-render, Reactjs 并不会跟踪 ref.current 的变化。
命令式地获取及操作DOM:
示例代码如下:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
五、其他 Hooks
useContext
useReducer
useCallback
useMemo
总结
未完待续
参考文档
后记:Hello 小伙伴们,如果觉得本文还不错,记得点个赞或者给个 star,你们的赞和 star 是我编写更多更丰富文章的动力!GitHub 地址
文档协议
db 的文档库 由 db 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/danygitgit上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。