觉得有收获的话,star支持一哈
ahooks是由阿里团队开发的一个React Hooks 库,里面有很多的常用且高质量的hooks,其中的设计实现也是值得我们学习的。今天我就挑几个比较常用的hooks,看看他们是如何使用与实现的。
ahooks的使用
上手还是非常简单的,只需要安装依赖,然后使用import导入就行了。
// 安装依赖
npm i ahooks --save
// 使用 Hooks
import { useRequest } from 'ahooks';
usePrevious
使用
这个hook的作用是可以保存上一次渲染时的状态。他传入一个需要记录变化的值,返回他的上一轮的状态,记录的值初始为undefined。
看看他具体怎么使用:
import React, {useState} from "react";
import ReactDOM from "react-dom";
import { usePrevious } from 'ahooks';
function App () {
const [count, setCount] = useState(0);
const previous = usePrevious(count);
return (
<>
<div>当前值: {count}</div>
<div style={{ marginBottom: 8 }}>上一轮的值: {previous}</div>
<button type="button" onClick={() => setCount((c) => c + 1)}>
增加
</button>
<button type="button" style={{ marginLeft: 8 }} onClick={() => setCount((c) => c - 1)}>
减少
</button>
</>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
点击增加一下,可以看到当前值从0变为1,同时将上一轮的值(0)给记录了下来。
实现
思路
- 要保存上一轮的状态,我们就需要一个存状态的容器。React官方提供的useRef就是一个很好的选择;
- 使用useRef的current属性保存上一轮的值,并配合useEffect一起使用,当数据发生变化时,更新current的值;
- 将current值返回;
实现如下:
function usePrevious(value) {
const pre = useRef();
const cur = useRef();
useEffect(()=>{
pre.current = cur.current;
cur.current = value;
}, [value]);
return pre.current;
}
useTimeout
我们先来看一个关于setTimeout的例子:
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [state, setState] = useState(1);
setTimeout(() => {
setState(state + 1);
}, 3000);
return (
<div>
<p style={{ marginTop: 16 }}> {state} </p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
我们原本的目的是在页面渲染完3s后修改一下state,但是你会发现当state+1后,触发了页面的重新渲染,就会重新有一个3s的定时器出现来给state+1,既而变成了每3秒+1。
我们再来看setTimeout的另一个例子:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const [count, setCount] = useState(0)
const [countInTimeout, setCountInTimeout] = useState(0)
useEffect(() => {
setTimeout(() => {
setCountInTimeout(count)
}, 3000)
setCount(5)
}, [])
return (
<div>
Count: {count}
<br />
setTimeout Count: {countInTimeout}
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
执行结果如下:
可以看到,count发生了变化,但是3s后setTimout的count却还是0。这就是hooks的闭包陷阱。
既然setTimeout在react中存在着一些问题,那么整一个useTimeout的hook出来就很有必要了。ahooks也提供了对应的hook,使用如下。
使用
这个hook的作用是可以处理 setTimeout 计时器函数。使用例子如下:
例子1(对应setTimeout第一个例子)
import React, { useState } from 'react';
import { useTimeout } from 'ahooks';
function App() {
const [state, setState] = useState(1);
useTimeout(() => {
setState(state + 1);
}, 3000);
return (
<div>
<p style={{ marginTop: 16 }}> {state} </p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
例子2(对应setTimeout第二个例子)
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const [count, setCount] = React.useState(0);
const [countInTimeout, setCountInTimeout] = React.useState(0);
useTimeout(() => {
setCountInTimeout(count);
}, 3000);
useEffect(() => {
setCount(5);
}, []);
return (
<div>
Count: {count}
<br />
setTimeout Count: {countInTimeout}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
接下来我们就来实现这个钩子。
实现
- 这个hook传入两个参数,一个为执行的回调,一个为延迟的时间。为了防止组件一刷新就生成一个新的定时器,我们用useEffect包着,把延迟时间作为依赖项,并使用clearTimeout清除副作用;
function useTimeout(callback, delay) {
useEffect(() => {
if (delay !== null) {
const timer = setTimeout(() => {
callback();
}, delay);
return () => {
clearTimeout(timer);
};
}
}, [delay]);
}
- 对于例子2的闭包陷阱,实际上是因为定时器中的回调函数被引用了,形成了闭包被一直保存着,当调用setState后虽然组件重新渲染,但是setTimeout还是上一轮的,所以拿到的state就还是上一轮的1,拿不到最新的值;
- 我们可以使用useRef来保存setTimeout的回调函数,那么在setState后,组件重新渲染,定时器中的回调也会更新,就可以拿到最新的值了。我们把上面的版本修改一下:
function useTimeout(callback, delay) {
const memorizeCallback = useRef();
useEffect(() => {
memorizeCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay !== null) {
const timer = setTimeout(() => {
memorizeCallback.current();
}, delay);
return () => {
clearTimeout(timer);
};
}
}, [delay]);
};
useInterval
setInterval在react中的问题和setTimeout是类似的,就如setTimeout的两个例子,setInterval也会出现同类的问题。所以useInterval的实现思路就和useTimeout类似,只需要把setTimeout换成setInterval就行了:
function useInterval(callback, delay) {
const memorizeCallback = useRef();
useEffect(() => {
memorizeCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay !== null) {
const timer = setInterval(() => {
memorizeCallback.current();
}, delay);
return () => {
clearInterval(timer);
};
}
}, [delay]);
};