博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何实现一个Interval Hook
阅读量:6324 次
发布时间:2019-06-22

本文共 3632 字,大约阅读时间需要 12 分钟。

【React】如何实现一个Interval Hook

useInterval(() => {  // do something...}, 1000);复制代码

可能你看过也写过一些 react hook ,不过你对 hook 的种种行为真的了解吗?这篇文章为你剖析 hook 对比 class component “反常” 的那些事儿。

有了自带的 setInterval 为何还要再实现一个

its arguments are “dynamic”

可以注意到我们的 setInterval 是接受一个 dealy 值的, 并且这个值是可以由我们的代码控制的, 这意味着我们可以随时调整这个值来做动态的改变.

可以做到这样: 用一个 interval 控制另一个 interval 的速度

class component 实现

第一次尝试

function Counter() {  const [count, setCount] = useState(0);  useEffect(() => {    const id = setInterval(() => {      setCount(count + 1);    }, 1000);    return () => clearInterval(id);  });  return 

{count}

;}复制代码

我们一开始一般会写出这样的实现, useEffect 设置 interval, return cleanup. 然而这样写会有个奇怪的表现...

react 在默认在每次 render 之后会重新执行 effects, 这其实也是 react 所预期的, 因为这样能避免 .

我们通常会使用 effect 来订阅, 退订一些 api, 但是在 setInterval 上使用的时候就会有问题, 因为执行 clearIntervalsetInterval 是有时间差的, 当 react 渲染过于频繁的时候, 就会出现 interval 压根没机会执行的情况!

我们以 100ms 的频率去渲染 counter 组件,我们会发现 count 值一直没有更新

第二次尝试

在上一个阶段中, 我们的问题是重复执行 effects 导致了 interval 被清理的太早.

我们知道 useEffect 可以传入一个参数来决定是否重复执行 effects, 试一下

function Counter() {  const [count, setCount] = useState(0);  useEffect(() => {    const id = setInterval(() => {      setCount(count + 1);    }, 1000);    return () => clearInterval(id);  }, []);  return 

{count}

;}复制代码

好的, 现在我们 counter 更新到 1 就停止了

发生了什么?!

这其实是个很常见的闭包问题, 也有了对应的 .

我们的 effects 现在只会运行一次, 所以 effects 每次捕获的 count 值都是第一次 render 的 count 值(0), 所以 count + 1 一直是 1

有一种 fix 的方式是, 用 setState 的函数参数, setCount(count => count + 1), 这样我们就可以读取最新的 state, 但是这种方式不是万能的, 比如不能读取最新的 props, 那么假如我们需要根据最新的 props 来 setState 就无法实现了

使用 Refs

我们回到上个问题, count 无法被正确读取的原因是 count 的值一直引用的是第一次 render 的.

那如果我们在每次 render 的时候动态地改变 setInterval(fn, delay) 中 fn 函数, 使这个函数带上最新的 props 和 state, 并且这个 fn 函数要能在多次 render 之间可持续(persist), 这样 setInterval 执行的时候, 就可以实时的读取这个函数拿到最新的值了

第一版实现:

function setInterval(callback) {  const savedCallback = useRef();  useEffect(() => {    savedCallback.current = callback;  });  useEffect(() => {    const tick = () => savedCallback.current();    const id = setInterval(tick, 1000);    return () => clearInterval(id);  }, []);}复制代码

支持动态 delay暂停 的最终版:

function setInterval(callback, delay) {  const savedCallback = useRef();  useEffect(() => {    savedCallback.current = callback;  });  useEffect(() => {    const tick = () => savedCallback.current();    if (delay !== undefined) {      const id = setInterval(tick, delay);      return () => clearInterval(id);    }  }, [delay]);}复制代码

我们可以用这个 hook 做一些更加好玩的事 -- 用一个 interval 控制另一个 interval 的速度,就是一开始我们看到的那个动图的样子。

练习

function Counter() {  const [count, setCount] = useState(0);  useEffect(() => {    setTimeout(() => {      console.log(`Clicked ${count} times`);    }, 3000);  });  return [    

{count}

, ];}复制代码

猜猜打印结果?

看看 class component 的表现如何?

class Counter extends React.Component {  state = {    count: 0  };  componentDidUpdate() {    setTimeout(() => {      console.log(`Clicked ${this.state.count} times`);    }, 3000);  }  render() {    const { count } = this.state;    return [      

{count}

, ]; }}复制代码

如何改造上面的 class component 让它跟使用 hook 的组件一样打印不同值?

hook 版本的怎么改能变得跟之前的 class component 一样打印相同值呢?

function Counter() {  const [count, setCount] = useState(0);  const saved = useRef(count);  useEffect(() => {    saved.current = count;    setTimeout(() => {      console.log(`Clicked ${saved.current} times`);    }, 3000);  });  return [    

{count}

, ];}复制代码

专栏其他文章

FE One
关注我们的公众号FE One,会不定期分享JS函数式编程、深入Reaction、Rxjs、工程化、WebGL、中后台构建等前端知识

参考:

转载于:https://juejin.im/post/5cad6809f265da039955c112

你可能感兴趣的文章
恢复低版本的FlashPlayer
查看>>
Opengl VS2008开发环境
查看>>
ylbtech-QQ(腾讯)-群空间-数据库设计
查看>>
面试书籍
查看>>
模式识别 - 处理多个演示样本研究(MIL)特点(matlab)
查看>>
lintcode :Remove Duplicates from Sorted Array II 删除排序数组中的重复数字 II
查看>>
CSS 简介
查看>>
atitit.短信 验证码 破解 v3 p34 识别 绕过 系统方案规划----业务相关方案 手机验证码 .doc...
查看>>
C# TextBox常用方法总结
查看>>
[android] 调用系统照相机和摄像机
查看>>
JDBC数据库编程常用接口(转)
查看>>
解析js中( ( ) { } ( ) )的含义
查看>>
劈荆斩棘:Gitlab 部署 CI 持续集成
查看>>
Mongodb后台daemon方式启动
查看>>
cp显示进度条
查看>>
Hadoop RPC
查看>>
SuperSpider——打造功能强大的爬虫利器
查看>>
MySql状态查看方法 MySql如何查看连接数和状态?
查看>>
memcached 安装
查看>>
Python与Redis的连接教程
查看>>