热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Reacthooks】你不得不知道的闭包问题

需求分析我们实现了一个这样的功能点击Start开始执行interval,并且一旦有可能就往lapse上加一点击Stop后取消interval点击Clear会取消interval,并且设置lapse为0importReactfromreactimportReactDOMfromreact-domconstbu

需求分析

我们实现了一个这样的功能
【React hooks】你不得不知道的闭包问题

  • 点击 Start 开始执行 interval,并且一旦有可能就往 lapse 上加一
  • 点击 Stop 后取消 interval
  • 点击 Clear 会取消 interval,并且设置 lapse 为 0
import React from 'react'
import ReactDOM from 'react-dom'

const buttOnStyles= {
  border: '1px solid #ccc',
  background: '#fff',
  fontSize: '2em',
  padding: 15,
  margin: 5,
  width: 200,
}
const labelStyles = {
  fontSize: '5em',
  display: 'block',
}

function Stopwatch() {
  const [lapse, setLapse] = React.useState(0)
  const [running, setRunning] = React.useState(false)

  React.useEffect(() => {
    if (running) {
      const startTime = Date.now() - lapse
      const intervalId = setInterval(() => {
        setLapse(Date.now() - startTime)
      }, 0)
      return () => {
        clearInterval(intervalId)
      }
    }
  }, [running])

  function handleRunClick() {
    setRunning(r => !r)
  }

  function handleClearClick() {
    setRunning(false)
    setLapse(0)
  }

  if (!running) console.log('running is false')

  return (
    
) } function App() { const [show, setShow] = React.useState(true) return (
{show ? : null}
) } ReactDOM.render(, document.getElementById('root'))

点击进入demo测试

 

问题描述

1.我们首先点击start,2.然后点击clear,3.发现问题:显示的并不是0ms

 

问题分析

为什么通过clear设置了值为0,却显示的不是0?

出现这样的情况主要原因是:useEffect 是异步的,也就是说我们执行 useEffect 中绑定的函数或者是解绑的函数,都不是在一次 setState 产生的更新中被同步执行的。啥意思呢?我们来模拟一下代码的执行顺序:
1.在我们点击来 clear 之后,我们调用了 setLapse 和 setRunning,这两个方法是用来更新 state 的,所以他们会标记组件更新,然后通知 React 我们需要重新渲染来。
2.然后 React 开始来重新渲染的流程,并很快执行到了 Stopwatch 组件。
3.先执行了Stopwatch组件中的同步组件,然后执行异步组件,因此通过clear设置的0被渲染,然后即将执行useEffect中的异步事件,由于在执行清除interval之前,interval还存在,因此它计算了最新的值,并把通过clear设置的0给更改了并渲染出来,然后才清除。

顺序大概是这样的:
useEffect:setRunning(false) => setLapse(0) => render(渲染) => 执行Interval => (clearInterval => 执行effect) => render(渲染)

 

问题解决

方法1:使用useLayoutEffect

useLayoutEffect 可以看作是 useEffect 的同步版本。使用 useLayoutEffect 就可以达到我们上面说的,在同一次更新流程中解绑 interval 的目的。
useLayoutEffect里面的callback函数会在DOM更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,阻塞了浏览器的绘制.

顺序大概是这样的:
useLayoutEffect: setRunning(false) => setLapse(0) => render(渲染) => (clearInterval =>执行effect)

 

方法2: 使用useReducer解决闭包问题

把 lapse 和 running 放在一起,变成了一个 state 对象,有点类似 Redux 的用法。在这里我们给 TICK action 上加了一个是否 running 的判断,以此来避开了在 running 被设置为 false 之后多余的 lapse 改变。

那么这个实现跟我们使用 updateLapse 的方式有什么区别呢?

最大的区别是我们的 state 不来自于闭包,在之前的代码中,我们在任何方法中获取 lapse 和 running 都是通过闭包,而在这里,state 是作为参数传入到 Reducer 中的,也就是不论何时我们调用了 dispatch,在 Reducer 中得到的 State 都是最新的,这就帮助我们避开了闭包的问题。

import React from 'react'
import ReactDOM from 'react-dom'

const buttOnStyles= {
  border: '1px solid #ccc',
  background: '#fff',
  fontSize: '2em',
  padding: 15,
  margin: 5,
  width: 200,
}
const labelStyles = {
  fontSize: '5em',
  display: 'block',
}

const TICK = 'TICK'
const CLEAR = 'CLEAR'
const TOGGLE = 'TOGGLE'

function stateReducer(state, action) {
  switch (action.type) {
    case TOGGLE:
      return {...state, running: !state.running}
    case TICK:
      if (state.running) {
        return {...state, lapse: action.lapse}
      }
      return state
    case CLEAR:
      return {running: false, lapse: 0}
    default:
      return state
  }
}

function Stopwatch() {
  // const [lapse, setLapse] = React.useState(0)
  // const [running, setRunning] = React.useState(false)

  const [state, dispatch] = React.useReducer(stateReducer, {
    lapse: 0,
    running: false,
  })

  React.useEffect(
    () => {
      if (state.running) {
        const startTime = Date.now() - state.lapse
        const intervalId = setInterval(() => {
          dispatch({
            type: TICK,
            lapse: Date.now() - startTime,
          })
        }, 0)
        return () => clearInterval(intervalId)
      }
    },
    [state.running],
  )

  function handleRunClick() {
    dispatch({
      type: TOGGLE,
    })
  }

  function handleClearClick() {
    // setRunning(false)
    // setLapse(0)
    dispatch({
      type: CLEAR,
    })
  }

  return (
    
) } function App() { const [show, setShow] = React.useState(true) return (
{show ? : null}
) } ReactDOM.render(, document.getElementById('root'))

推荐阅读
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 1简介本文结合数字信号处理课程和Matlab程序设计课程的相关知识,给出了基于Matlab的音乐播放器的总体设计方案,介绍了播放器主要模块的功能,设计与实现方法.我们将该设 ... [详细]
  • 开发笔记:图像识别基于主成分分析算法实现人脸二维码识别
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了图像识别基于主成分分析算法实现人脸二维码识别相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • 带添加按钮的GridView,item的删除事件
    先上图片效果;gridView无数据时显示添加按钮,有数据时,第一格显示添加按钮,后面显示数据:布局文件:addr_manage.xml<?xmlve ... [详细]
  • Java图形化计算器设计与实现
    本文介绍了使用Java编程语言设计和实现图形化计算器的方法。通过使用swing包和awt包中的组件,作者创建了一个具有按钮监听器和自定义界面尺寸和布局的计算器。文章还分享了在图形化界面设计中的一些心得体会。 ... [详细]
  • 今天就跟大家聊聊有关怎么在Android应用中实现一个换肤功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根 ... [详细]
  • android 触屏处理流程,android触摸事件处理流程 ? FOOKWOOD「建议收藏」
    android触屏处理流程,android触摸事件处理流程?FOOKWOOD「建议收藏」最近在工作中,经常需要处理触摸事件,但是有时候会出现一些奇怪的bug,比如有时候会检测不到A ... [详细]
  • SmartRefreshLayout自定义头部刷新和底部加载
    1.添加依赖implementation‘com.scwang.smartrefresh:SmartRefreshLayout:1.0.3’implementation‘com.s ... [详细]
  • python3 logging
    python3logginghttps:docs.python.org3.5librarylogging.html,先3.5是因为我当前的python版本是3.5之所 ... [详细]
author-avatar
金叶诺一的妈妈
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有