❝
hello,我是海海这一期我们继续ahooks之旅。阅读时间15分钟。
由于ahooks内容比较多,我将拆分成几个章节,本期我们开始讲解部分
副作用相关的hooks不同于其他介绍ahooks的文章,本系列会对每一个hook从what(是什么)、how(怎么用)、why(实现原理)、when(啥时候用)做全方面的讲解。
欢迎转载,请注明原文和作者
有任何不对的地方,欢迎底部发消息给我。
❞
前文链接:
前置知识:
- 异步生成器函数
本期大纲:
- useUpdateEffect
- useUpdateLayoutEffect
- useAsyncEffect
- useDebounceFn
- useDebounceEffect
- useLatest
- useUnmount
异步生成器函数
❝
异步生成器函数 = 异步函数 + 生成器函数
❞
async function* asyncGenerator () {
yield 'Hello';
await new Promise(resolve => setTimeout(resolve, 1000));
yield 'World';
}
(async () => {
for await (const value of asyncGenerator()) {
console.log(value);
}
})()
异步生成器函数会返回一个包含
Symbol.asyncIterator的对象,外部可以通过for await...of对迭代器进行迭代。
说个题外话,可以通过对比普通迭代器和异步迭代器,了解到一些规律。
| 对比 | 遍历方法 | 哪些对象会返回 | next是否阻塞 | 迭代器返回值类型 |
|---|---|---|---|---|
| Symbol.iterator |
|
| 阻塞 | 值 |
| Symbol.asyncIterator |
|
| 不阻塞 | Promise |
值得注意的是,for await...of会自动调用异步迭代器的next方法,就如同for...of可以自动调用同步迭代器的next方法一样。
yield能够返回当前的迭代值,并在外部迭代器调用next之前不会继续执行。
useUpdateEffect
忽略首次执行的useUpdateEffect。
import { useEffect } from 'react';
const useUpdateEffect = createUpdateEffect(useEffect)
可以明显的看出来,通过isMounted进行打标,达到忽略首次执行的目的。
用法和使用时机基本等于useEffect。
useUpdateLayoutEffect
忽略首次执行的useLayoutEffect。
useLayoutEffect是react的内置hook,用于在浏览器repaint之前执行一些逻辑。比如计算hover产生的元素放置在合适的位置。
// ahooks的内置方法,用于创建关于副作用的忽略首次执行的高阶hooks
const createUpdateEffect = (hook) => (effect, deps) => {
const isMounted = false;
hook(() => {
return () => {
isMounted.current = false;
}
});
hook(() => {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
}
}, deps);
}
return createUpdateEffect();
import { useLayoutEffect } from 'react';
const useUpdateLayoutEffect = createUpdateEffect(useLayoutEffect);
可以明显的看出来,通过isMounted进行打标,达到忽略首次执行的目的。
关于如何使用的问题:入参和
useLayoutEffect相同。
那么在什么时候使用呢?我的建议是在网页重渲染之前做逻辑处理。
useAsyncEffect
支持异步的useEffect
// 用来判断e是否是一个异步迭代器对象
const isAsyncGenerator = (e) => {
return typeof e[Symbol.asyncIterator] === 'function';
}
const useAsyncEffect = (effect, deps) => {
useEffect(() => {
const e = effect();
let cancelled = false;
async function execute() {
if (isAsyncGenerator(e)) {
while(true) {
const result = await e.next();
if (reuslt.done || cancelled) {
break;
}
}
} else {
await e;
}
}
execute();
return () => {
cancelled = true;
}
}, deps);
}
从上面的代码中,我们可以看到:为useEffect添加异步能力,不仅考虑了普通的异步函数,还考虑到了异步生成器函数。
关于异步生成器函数,请参阅前置知识“异步生成器函数”。
useDebounceFn
用来处理防抖函数的hook。
const useDebounceFn = (fn, options) => {
const fnRef = useLatest(fn);
const wait = options?.wait ?? 1000;
const debounced = useMemo(
() => debounce(
(...args) => {
return fnRef.current(...args);
},
wait,
options
),
[]);
useUnmount(() => {
debounced.cancel();
});
return {
run: debounced,
cancel: debounced.cancel,
flush: debounced.flush
}
}
和useDebounceEffect的不同点在于,useDebounceFn没有useEffect的功能。只是单纯的做了一层防抖处理。
根据代码,我们可以看到其内部的实现,核心是通过lodash/debounce包装产生防抖函数。那么为什么需要使用useMemo包装呢?
答案是,如果组件触发更新,debounce函数会重新执行,产生不必要的计算浪费。同时,使用useUnmount卸载时,debounced.cancel应当修改为删除上一次,使得清理操作变得更为复杂,因为这可能需要用到全局变量保存上一次的结果。
useDebounceEffect
为useEffect添加防抖能力,会忽略首次执行,并添加了cancel,flush能力。
cancel可以取消,flush可以立即调用。
const useDebounceEffect = (effect, deps, options) => {
const [flag, setFlag] = useState({});
const { run } = useDebounceFn(() => {
setFlag({});
}, options);
useEffect(() => {
return run();
}, deps);
useUpdateEffect(effect, [flag]);
}
在看useDebounceEffect的实现之前,我们需要先了解下useEffect的return触发的时机。
useEffect会在组件卸载或者依赖数组更新时会触发return回调函数的执行。
ahooks内部根据依赖数组更新触发return回调的执行的机制,对effect进行包装,默认是1秒内进行防抖。
关于防抖的实现,则是通过内部设置一个flag对象,当在1s内多次更新依赖数组时,执行run函数,只在最后一次依赖更新时才会执行effect函数。这个run函数是通过lodash/debounce返回的。
那么什么时候可以使用呢?这就是考察前端防抖场景的积累了。
- input实时搜索
- 窗口尺寸调整
- 按钮快速连续点击
- 滚动事件(懒加载)
- 表单项校验
- 编辑器自动保存
- 鼠标移动事件
- ajax防止重复请求(前端幂等)
- 性能检测(监控)
useLatest
返回当前最新值。
const useLatest = (value) => {
const ref = useRef(value);
ref.current = value;
return ref;
}
官方文档所说的用来解决闭包问题是指state多次调用值不会立即更新,而是使用之前的值的场景。
更具体的就是定时器的更新。count对象被useLatest所引用,当count更新时,useLatest会立即更新,因为他们都是引用值。
何时使用呢?笔者认为可以在你需要立即更新state的情况下使用是最合适的。
useUnmount
组件卸载时执行的逻辑。
const useUnmount = (fn) => {
const fnRef = useLatest(fn);
useEffect(() => () => {
fnRef.current();
}, []);
}
需要注意的是,因为useEffect没有依赖数组,所以只有组件卸载才会触发。
何时使用呢?我的建议是可以聚合多个分散在不同useEffect里的卸载逻辑。
今天的旅程到这里就结束了,谢谢你的陪伴!
感谢你的耐心阅读,如果觉得好的话,请给我一个免费的赞
创作不易,感谢你的支持!
本文使用 markdown.com.cn 排版