返回顶部
首页 > 资讯 > 精选 >react hooks线上bug后分析
  • 645
分享到

react hooks线上bug后分析

2023-06-27 11:06:30 645人浏览 八月长安
摘要

这篇“React hooks线上bug后分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“react hooks线上bug后

这篇“React hooks线上bug后分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“react hooks线上bug后分析”文章吧。

硬性要求

1. 必须完整阅读一次 React Hooks 官方文档

其中重点必看 hooks: useStateuseReduceruseEffectuseCallbackuseMemo

2. 工程必须引入 lint 插件,并开启相应规则

lint 插件必开规则:

{  "plugins": ["react-hooks"],  "rules": {    "react-hooks/rules-of-hooks": "error",    "react-hooks/exhaustive-deps": "warn"  }}

其中, react-hooks/exhaustive-deps 至少 warn,也可以是 error。建议全新的工程直接配 "error",历史工程配 "warn"。

切记,本条是硬性条件。

如果你的工程,当前没开启 hooks lint rule,请不要编写任何 hooks 代码。如果你 CR 代码时,发现对方前端工程,没有开启相应规则,并且提交了 hooks 代码,请不要合并。该要求适应于任何一个 React 前端工程。

这两条规则会避免我们踩坑。虽然对于 hooks 新手,这个过程可能会比较“痛苦”。不过,如果你觉得这两个规则对你编写代码造成了困扰,那说明你还未完全掌握 hooks。

如果对于某些场景,确实不需要「exhaustive-deps」,可在代码处加:// eslint-disable-next-line react-hooks/exhaustive-deps

切记只能禁本处代码,不能偷懒把整个文件都禁了。

3. 如若有发现 hooks 相关 lint 导致的 warning,不要全局 autofix

除了 hooks 外,正常的 lint 基本不会改变代码逻辑,只是调整编写规范。但是 hookslint 规则不同,exhaustive-deps 的变化会导致代码逻辑发生变化,这极容易引发线上问题,所以对于 hookswaning,请不要做全局 autofix 操作。除非保证每处逻辑都做到了充分回归。

另外公司内部有个小姐姐补充道:eslint-plugin-react-hooks 从2.4.0版本开始,已经取消了 exhaustive-deps 的autofix。所以,请尽量升级工程的lint插件至最新版,减少出错风险

然后建议开启 vscode 的「autofix on save」。未来无论是什么问题,能把 error 与 warning 尽量遏制在最开始的开发阶段,保证自测跟测试时就是符合规则的代码。

常见注意点

依赖问题

依赖与闭包问题是一定要开启exhaustive-deps 的核心原因。最常见的错误即:mount 时绑定事件,后续状态更新出错。

错误代码示例:(此处用 addEventListener 做 onclick 绑定,只是为了方便说明情况)

function ErrorDemo() {  const [count, setCount] = useState(0);  const dom = useRef(null);  useEffect(() => {    dom.current.addEventListener('click', () => setCount(count + 1));  }, []);  return <div ref={dom}>{count}</div>;}

这段代码的初始想法是:每当用户点击 domcount 就加1。理想中的效果是一直点,一直加。但实际效果是 {count} 到「1」以后就加不上了。

我们来梳理一下, useEffect(fn, []) 代表只会在 mount 时触发。也即是首次 render 时,fn 执行一次,绑定了点击事件,点击触发 setCount(count + 1) 。乍一想,count 还是那个 count,肯定会一直加上去呀,当然现实在啪啪打脸。

状态变更触发页面渲染的本质是什么?本质就是 ui = fn(props, state, context) 。props、内部状态、上下文的变更,都会导致渲染函数(此处就是ErrorDemo)的重新执行,然后返回新的 view。

那现在问题来了, ErrorDemo 这个函数执行了多次,第一次函数内部的 count 跟后面几次的 count 会有关系吗?这么一想,感觉又应该没有关系了。那为什么第二次又知道 count 是1,而不是 0 了呢?第一次的setCount 跟后面的是同一个函数吗?这背后涉及到 hooks 的一些底层原理,也关系到了为什么 hooks 的声明需要声明在函数顶部,不允许在条件语句中声明。在这里就不多讲了。

结论是:每次 count 都是重新声明的变量,指向一个全新的数据;每次的setCount 虽然是重新声明的,但指向的是同一个引用。

回到正题,我们知道了每次 render,内部的 count 其实都是全新的一个变量。那我们绑定的点击事件方法,也即:setCount(count + 1) ,这里的 count,其实指的一直是首次 render 时的那个 count,所以一直是 0 ,因此 setCount,一直是设置 count 为1。

那这个问题怎么解?

首先,应该遵守前面的硬性要求,必须要加 lint 规则,并开启 autofix on save。然后就会发现,其实这个 effect 是依赖 count 的。autofix 会帮你自动补上依赖,代码变成这样:

useEffect(() => {  dom.current.addEventListener('click', () => setCount(count + 1));}, [count]);

那这样肯定就不对了,相当于每次 count 变化,都会重新绑定一次事件。所以对于事件的绑定,或者类似的场景,有几种思路,我按我的常规处理优先级排列:

思路1:消除依赖

在这个场景里,很简单,我们主要利用 setCount 的另一个用法 functional updates。这样写就好了:() => setCount(prevCount => ++prevCount) ,不用关心什么新的旧的、什么闭包,省心省事。

思路2:重新绑定事件

那如果我们这个事件就是要消费这个 count 怎么办?比如这样:

dom.current.addEventListener('click', () => {  console.log(count);  setCount(prevCount => ++prevCount);});

我们不必执着于一定只在 mount 时执行一次。也可以每次重新 render 前移除事件,render 后绑定事件即可。这里利用 useEffect 的特性,具体可以自己看文档:

useEffect(() => {  const $dom = dom.current;  const event = () => {    console.log(count);    setCount(prev => ++prev);  };  $dom.addEventListener('click', event);  return () => $dom.removeEventListener('click', event);}, [count]);

思路3

如果嫌这样开销大,或者编写麻烦,也可以用 useRef其实用 useRef 也挺麻烦的,我个人不太喜欢这样操作,但也能解决问题,代码如下:

const [count, setCount] = useState(0);const countRef = useRef(count);useEffect(() => {  dom.current.addEventListener('click', () => {    console.log(countRef.current);    setCount(prevCount => {      const newCount = ++prevCount;      countRef.current = newCount;      return newCount;    });  });}, []);

useCallback 与 useMemo

这两个 api,其实概念上还是很好理解的,一个是「缓存函数」, 一个是缓存「函数的返回值」。但我们经常会懒得用,甚至有的时候会用错。

从上面依赖问题我们其实可以知道,hooks对「有没有变化」这个点其实很敏感。如果一个 effect 内部使用了某数据或者方法。若我们依赖项不加上它,那很容易由于闭包问题,导致数据或方法,都不是我们理想中的那个它。如果我们加上它,很可能又会由于他们的变动,导致 effect 疯狂的执行。真实开发的话,大家应该会经常遇到这种问题。

所以,在此建议:

  1. 在组件内部,那些会成为其他 useEffect 依赖项的方法,建议用 useCallback 包裹,或者直接编写在引用它的useEffect中。

  2. 己所不欲勿施于人,如果你的 function 会作为 props传递给子组件,请一定要使用 useCallback 包裹,对于子组件来说,如果每次render都会导致你传递的函数发生变化,可能会对它造成非常大的困扰。同时也不利于 react 做渲染优化

不过还有一种场景,大家很容易忽视,而且还很容易将 useCallbackuseMemo 混淆,典型场景就是:节流防抖。

举个例子:

function BadDemo() {  const [count, setCount] = useState(1);  const handleClick = debounce(() => {    setCount(c => ++c);  }, 1000);  return <div onClick={handleClick}>{count}</div>;}

我们希望防止用户连续点击触发多次变更,加了防抖,停止点击1秒后才触发 count + 1 ,这个组件在理想逻辑下是OK的。但现实是骨感的,我们的页面组件非常多,这个 BadDemo 可能由于父级什么操作就重新 render 了。现在假使我们页面每500毫秒会重新 render 一次,那么就是这样:

function BadDemo() {  const [count, setCount] = useState(1);  const [, setRerender] = useState(false);  const handleClick = debounce(() => {    setCount(c => ++c);  }, 1000);  useEffect(() => {    // 每500ms,组件重新render    window.setInterval(() => {      setRerender(r => !r);    }, 500);  }, []);  return <div onClick={handleClick}>{count}</div>;}

每次 render 导致 handleClick 其实是不同的函数,那么这个防抖自然而然就失效了。这样的情况对于一些防重点要求特别高的场景,是有着较大的线上风险的。

那怎么办呢?自然是想加上 useCallback :

const handleClick = useCallback(debounce(() => {  setCount(c => ++c);}, 1000), []);

现在我们发现效果满足我们期望了,但这背后还藏着一个惊天大坑。

假如说,这个防抖的函数有一些依赖呢?比如 setCount(c => ++c); 变成了 setCount(count + 1) 。那这个函数就依赖了 count 。代码就变成了这样:

const handleClick = useCallback(  debounce(() => {    setCount(count + 1);  }, 1000),  []);

大家会发现,你的 lint 规则,竟然不会要求你把 count 作为依赖项,填充到 deps 数组中去。这进而导致了最初的那个问题,只有第一次点击会 count++。这是为什么呢?

因为传入 useCallback 的是一段执行语句,而不是一个函数声明。只是说它执行以后返回的新函数,我们将其作为了 useCallback 函数的入参,而这个新函数具体是个啥,其实 lint 规则也不知道。

更合理的姿势应该是使用 useMemo :

const handleClick = useMemo(  () => debounce(() => {    setCount(count + 1);  }, 1000),  [count]);

这样保证每当 count 发生变化时,会返回一个新的加了防抖功能的新函数。

总而言之,对于使用高阶函数的场景,建议一律使用 useMemo

有些网友提供了宝贵的反馈,我继续补充:刚使用useMemo,依旧存在一些问题。

问题1useMemo「将来」并不「稳定」

react 的官方文档中提到:你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 > useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 > useMemo,以达到优化性能的目的。也就是说,在将来的某种特殊情况下,这个防抖函数依旧会失效。当然,这种情况是发生在「将来」,且相对比较极端,出现概率较小,即使出现,也不会“短时间内连续”出现。所以对于不是 「前端防不住抖就要完蛋」的场景,风险相对较小。

问题2useMemo 并不能一劳永逸解决所有高阶函数场景

在示例的场景中,防抖的逻辑是:「连续点击后1秒,真正执行逻辑,在这过程中的重复点击失效」。而如果业务逻辑改成了「点击后立即发生状态变更,再之后的1秒内重复点击无效」,那么我们的代码可能就变成了。

const handleClick = useMemo(   () => throttle(() => { setCount(count + 1); }, 1000), [count] );

然后发现又失效了。原因是点击以后,count 立即发生了变化,然后 handleClick 又重复生成了新函数,这个节流就失效了。

所以这种场景,思路又变回了前面提到的,「消除依赖」 或 「使用ref」

当然啦,也可以选择自己手动实现一个 debouncethrottle 。我建议可以直接使用社区的库,比如 react-use,或者参考他们的实现自己写两个实现。

以上就是关于“react hooks线上bug后分析”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网精选频道。

--结束END--

本文标题: react hooks线上bug后分析

本文链接: https://lsjlt.com/news/309810.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • react hooks线上bug后分析
    这篇“react hooks线上bug后分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“react hooks线上bug后...
    99+
    2023-06-27
  • react hooks的示例分析
    这篇文章将为大家详细讲解有关react hooks的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。React在16.8版本正式发布了Hooks。关注了很久,最近正...
    99+
    2024-04-02
  • Android线上Bug热修复分析
      针对app线上修复技术,目前有好几种解决方案,开源界往往一个方案会有好几种实现。重复的实现会有造轮子之嫌,但分析解决方案在技术上的探索和衍变,这轮子还是值得去推动的 ...
    99+
    2022-06-06
    Android
  • React中Hooks的案例分析
    这篇文章给大家分享的是有关React中Hooks的案例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一个最简单的Hooks首先让我们看一下一个简单的有状态组件:class&n...
    99+
    2024-04-02
  • 分析React Hooks响应式布局
    本篇内容主要讲解“分析React Hooks响应式布局”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“分析React Hooks响应式布局”吧!1. 方案一:in...
    99+
    2024-04-02
  • 分析React组件,Hooks和性能
    这篇文章主要讲解了“分析React组件,Hooks和性能”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“分析React组件,Hooks和性能”吧!正如我们的R...
    99+
    2024-04-02
  • React深入浅出分析Hooks源码
    目录useState 解析useState 使用useState 模拟实现hooks 规则useEffect 解析useEffect 使用useEffect 的模拟实现useEffe...
    99+
    2022-11-13
    React Hooks React Hooks源码
  • Arthas线上项目BUG调试实例分析
    本文小编为大家详细介绍“Arthas线上项目BUG调试实例分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“Arthas线上项目BUG调试实例分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Arthas简介A...
    99+
    2023-07-02
  • Moment的feature导致线上bug解决分析
    目录bug的出现bug排查bug的根因解决方案bug的出现 这一天,本来是平平淡淡的一天,我正准备一如既往的到点下班,结果qa说线上出了个匪夷所思的bug。 表象为:用户在日期选择器...
    99+
    2024-04-02
  • 使用React/Hooks时需要注意过时闭包的示例分析
    小编给大家分享一下使用React/Hooks时需要注意过时闭包的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Hooks...
    99+
    2024-04-02
  • React使用高阶组件与Hooks实现权限拦截教程详细分析
    目录思路实现注入权限列表抽离Context向页面注入权限列表的HOC向根组件注入权限含有权限拦截功能的HOC无权限时显示的组件权限拦截HOC组件测试用于测试的组件在组件中使用权限组件...
    99+
    2023-01-28
    React高阶组件权限拦截 React Hooks权限拦截
  • flume线上配置的示例分析
    这篇文章主要介绍了flume线上配置的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。hdfs_agent.sources = r1hdfs_agent.sinks =...
    99+
    2023-06-02
  • mysql分表之后如何平滑上线详解
    目录分表的目的 举个栗子 分表策略 已经上线的运行中的表怎么办? 步骤1 上线双写 步骤2 全量同步 步骤3 查询新表数据 总结分表的目的 项目开发中,我们的数据库数据越来越大,随...
    99+
    2024-04-02
  • react router 4.0以上路由应用的示例分析
    小编给大家分享一下react router 4.0以上路由应用的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!在4.0以下的react router中,嵌套的路由可以放在一个rou...
    99+
    2024-04-02
  • 数据分析师怎么应对数据库取数后的离线分析
    本篇内容主要讲解“数据分析师怎么应对数据库取数后的离线分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“数据分析师怎么应对数据库取数后的离线分析”吧!因为缺乏好用工具的支持,数据分析人员的离线分...
    99+
    2023-06-03
  • Vue打包上线之后部分CSS不生效怎么办
    这篇文章给大家分享的是有关Vue打包上线之后部分CSS不生效怎么办的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。首先注释掉webpack.prod.conf.js中下面的代码ne...
    99+
    2024-04-02
  • MVC遇上bootstrap后ajax表单验证的示例分析
    这篇文章主要介绍了MVC遇上bootstrap后ajax表单验证的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。使用bootstra...
    99+
    2024-04-02
  • j2Cache线上异常排查问题解决记录分析
    目录问题背景问题分析假设问题小心求证问题重现问题解决问题后记-下面才是真正的原因重新假设最终解决问题背景 开发反馈,线上有个服务在运行一段时间后,就会抛异常导致redis缓存不可用。...
    99+
    2024-04-02
  • 网站初上线内容更新内容的示例分析
    这篇文章给大家分享的是有关网站初上线内容更新内容的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。  第一,网站编辑一定要具备分析和综合能力。姓氏文化对于笔者这样一个文化小白而言兼职就是两个字郁闷,我自己对...
    99+
    2023-06-10
  • CSS盒子隐藏/显示后再最上层的示例分析
    小编给大家分享一下CSS盒子隐藏/显示后再最上层的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!.imgbox{width: 1200px;he...
    99+
    2023-06-08
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作