我发现了一个React、Vue等所有前端框架都存在的隐秘Bug?

简介: 我发现了一个React、Vue等所有前端框架都存在的隐秘Bug?

什么 Bug?


昨天有个朋友请教了我一个问题,她在使用原生的 Details 元素封装一个手风琴组件。但是无论如何都不能按照预期工作。

起初我认为是她水平比较差,代码写的有问题。但是她一再向我保证绝对不是她的问题。所以我就抽出时间帮她看了一下。意外发现这一个框架的隐秘 Bug。

我把这个代码放到了码上掘金上,你可以看一下。

image.pnghttps://code.juejin.cn/pen/7145527312400777254


import React, { useState } from 'react';
import ReactDom from 'react-dom';
function App() {
  const [isOpen, setIsOpen] = useState(false)
  return (
    <>
      <span>状态: {isOpen ? 'open' : 'closed'}</span>
      <details open={isOpen}>
        <summary
          onClick={() => {
            setIsOpen(!isOpen)
          }}
        >
          Summary
        </summary>
        Details
      </details>
    </>
  )
}
ReactDom.render(<App />, document.getElementById('app'));

我们发现在组件的一开始并没有按照预期去渲染组件。之后的每一次点击,都不会按照预期去渲染。


为什么会这样?


原因在于 details 元素具有自身的状态,React 并不知道。

简单来说,这个问题在于 details 的 open 属性有两个数据来源:React 和浏览器。

更详细的讨论可以看这个 Github issue

首先我解释一下当第一次单击按钮时会发生什么:

  1. summary 元素的 onClick 事件触发,状态 isOpen 从 false 变为 true。
  2. React 重新渲染组件,将 details 元素的 open 属性设置为 true。
  3. details 元素的默认行为会切换自身 open 状态,将 open 设置为 false,但 React 并不知道。

所以这就是 details 元素最终没有将 open 属性设置为 true 的原因,而我们的 isOpen 状态依然是 true。

第二次点击:

  1. summary 元素的 onClick 处理程序 被触发,切换 isOpen 到 false.
  2. React 重新渲染,发现 details 已经关闭,所以它不会去改变它。
  3. details 元素的默认行为再次切换它的 open 状态。现在是 false,所以它会把 open 状态改变为 true,而 React 仍然不知道。

在此之后,一切都会打破。


怎么解决?


e.preventDefault


解决思路其实很简单,只要不让浏览器乱动状态就可以了。我们可以使用 e.preventDefault 来禁止浏览器的默认行为。这样就只有 React 能够控制它的状态了。


toggle


除了上面的方法外,还有一种方法是通过 details 的 toggle 事件来处理它。


function App() {
  const [isOpen, setIsOpen] = useState(false)
  return (
    <>
      <span>状态: {isOpen ? 'open' : 'closed'}</span>
      <details
        open={isOpen} 
        onToggle={() => {
          setIsOpen(!isOpen)
        }}>
        <summary>
          Summary
        </summary>
        Details
      </details>
    </>
  )
}

这样似乎正常了。

但是很快我的朋友又遇到了新的麻烦,她在 details 中有一个按钮,这个按钮可以改变 isOpen 的状态。

image.pnghttps://code.juejin.cn/pen/7145537955291987976


function App() {
  const [isOpen, setIsOpen] = useState(false)
  return (
    <>
      <span>状态: {isOpen ? 'open' : 'closed'}</span>
      <details
        open={isOpen}
        onToggle={() => {
          setIsOpen(!isOpen)
        }}>
        <summary
        >
          Summary
        </summary>
        Details
        <button onClick={() => setIsOpen(!isOpen)}>切换状态</button>
      </details>
    </>
  )
}

当点击这个按钮时,浏览器就抽风了,进入了死循环状态。

我又试着帮她解析了一下这个问题的原因:

  1. 按钮的 onClick 事件会切换 isOpen,同时会更改 details 的 open 属性。
  2. open 属性的变化会触发 onToggle 事件。
  3. onToggle 事件会再次切换 isOpen 的状态。同时改变了 details 的 open 属性,这时又回到了第 2 步,所以进入了无限循环状态。


这个 Bug 是 React 框架独有的吗?


虽然朋友解决了这个问题,但是她也向我吐槽 React 难用。

我很好奇这个问题是 React 独有的问题吗?其他类似的框架,比如 Soild、Svelte 和 Vue 它们会有这个问题吗?

于是我尝试了其他所有框架,发现它们都有这个问题。

Vue 的代码如下:


<template>
  <span>状态: {{isOpen ? 'open' : 'closed'}}</span>
  <details :open="isOpen">
    <summary @click="()=> {isOpen = !isOpen}">
      Summary
    </summary>
    Details
  </details>
</template>
<script>
  import { defineComponent, ref } from 'vue';
export default defineComponent({
  setup() {
    const isOpen = ref(false);
    return {
      isOpen
    };
  },
});
</script>

我也放到了码上掘金上,你可以看一下。

image.pnghttps://code.juejin.cn/pen/7145527732409991200


这个 Bug 到底是谁的锅?


鉴于所有的框架都有这个问题,所以我认为它不应该是框架的问题。

于是我尝试用原生的 JavaScript 来编写这段程序。


<!DOCTYPE html>
<html>
  <body>
    <span>状态: closed</span>
    <details>
      <summary>Summary</summary>
      Details
    </details>
    <script>
      const span = document.querySelector('span')
      const details = document.querySelector('details')
      const summary = document.querySelector('summary')
      let isOpen = false
      summary.addEventListener('click', () => {
        if (isOpen) {
          isOpen = false
          span.textContent = '状态: closed'
          details.removeAttribute('open')
          return
        }
        isOpen = true
        span.textContent = '状态: open'
        details.setAttribute('open', '')
      })
    </script>
  </body>
</html>

image.pnghttps://code.juejin.cn/pen/7145540132412588062

现在看来,这似乎是 details 这个元素的底层工作原理的问题,和框架无关。

能够完美解决的唯一办法就是通过 e.preventDefault 来禁止掉浏览器默认行为,让 JavaScript 中的变量成为唯一的数据源。



相关文章
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
DeepSeek Artifacts:在线实时预览的前端 AI 编程工具,基于DeepSeek V3快速生成React App
DeepSeek Artifacts是Hugging Face推出的免费AI编程工具,基于DeepSeek V3,支持快速生成React和Tailwind CSS代码,适合快速原型开发和前端组件构建。
1282 39
DeepSeek Artifacts:在线实时预览的前端 AI 编程工具,基于DeepSeek V3快速生成React App
|
11天前
|
JavaScript 前端开发 算法
vue和react 哪个更强大
vue和react 哪个更强大
|
2月前
|
JavaScript 前端开发 jenkins
抛弃node和vscode,如何用记事本开发出一个完整的vue前端项目
本文探讨了在不依赖Node和VSCode的情况下,仅使用记事本和浏览器开发一个完整的Vue3前端项目的方法。通过CDN引入Vue、Vue Router、Element-UI等库,直接编写HTML文件实现页面功能,展示了前端开发的本质是生成HTML。虽然日常开发离不开现代工具,但掌握这种基础方法有助于快速实现想法或应对特殊环境限制。文章还介绍了如何用Node简单部署HTML文件到服务器,提供了一种高效、轻量的开发思路。
70 10
|
4月前
|
JavaScript 前端开发 搜索推荐
Vue的数据驱动视图与其他前端框架的数据驱动方式有何不同?
总的来说,Vue 的数据驱动视图在诸多方面展现出独特的优势,其与其他前端框架的数据驱动方式的不同之处主要体现在绑定方式、性能表现、触发机制、组件化结合、灵活性、语法表达以及与后端数据交互等方面。这些差异使得 Vue 在前端开发领域具有独特的地位和价值。
122 58
|
4月前
|
存储 缓存 JavaScript
如何优化React或Vue应用的性能
需要注意的是,性能优化是一个持续的过程,需要根据具体的应用场景和性能问题进行针对性的优化。同时,不同的项目和团队可能有不同的优化重点和方法,要结合实际情况灵活运用这些优化策略,以达到最佳的性能效果。
159 51
|
4月前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
307 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
4月前
|
监控 JavaScript 前端开发
如何在实际应用中测试和比较React和Vue的性能?
总之,通过多种方法的综合运用,可以相对客观地比较 React 和 Vue 在实际应用中的性能表现,为项目的选择和优化提供有力的依据。
72 1
|
4月前
|
JavaScript 前端开发 测试技术
React和Vue的性能对比如何?
需要注意的是,性能不仅仅取决于框架本身,还与开发者的代码质量、架构设计以及项目的优化程度等密切相关。因此,在评估性能时,应该综合考虑多个因素,而不是仅仅局限于框架之间的比较。
294 1
|
4月前
|
前端开发 JavaScript 算法
探索现代前端框架——React 的性能优化策略
探索现代前端框架——React 的性能优化策略
53 0
|
4月前
|
前端开发 JavaScript API
探索现代前端框架——React 的性能优化策略
探索现代前端框架——React 的性能优化策略
55 0