昨天有个朋友请教了我一个问题,她在使用原生的 Details 元素封装一个手风琴组件。但是无论如何都不能按照预期工作。
起初我认为是她水平比较差,代码写的有问题。但是她一再向我保证绝对不是她的问题。所以我就抽出时间帮她看了一下。意外发现这一个框架的隐秘 Bug。
我把这个代码放到了码上掘金上,你可以看一下。
import React, { useState } from 'react';
import ReactDom from 'react-dom';
function App() {const [isOpen, setIsOpen] = useState(false)return (<>状态: {isOpen ? 'open' : 'closed'} {setIsOpen(!isOpen)}}>SummaryDetails >)
}
ReactDom.render( , 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 来禁止浏览器的默认行为。这样就只有 React 能够控制它的状态了。
除了上面的方法外,还有一种方法是通过 details 的 toggle 事件来处理它。
function App() {const [isOpen, setIsOpen] = useState(false)return (<>状态: {isOpen ? 'open' : 'closed'} {setIsOpen(!isOpen)}}>Summary Details>)
}
这样似乎正常了。
但是很快我的朋友又遇到了新的麻烦,她在 details 中有一个按钮,这个按钮可以改变 isOpen 的状态。
function App() {const [isOpen, setIsOpen] = useState(false)return (<>状态: {isOpen ? 'open' : 'closed'} {setIsOpen(!isOpen)}}>Summary Details>)
}
当点击这个按钮时,浏览器就抽风了,进入了死循环状态。
我又试着帮她解析了一下这个问题的原因:
1.按钮的 onClick 事件会切换 isOpen,同时会更改 details 的 open 属性。
2.open 属性的变化会触发 onToggle 事件。
3.onToggle 事件会再次切换 isOpen 的状态。同时改变了 details 的 open 属性,这时又回到了第 2 步,所以进入了无限循环状态。
虽然朋友解决了这个问题,但是她也向我吐槽 React 难用。
我很好奇这个问题是 React 独有的问题吗?其他类似的框架,比如 Soild、Svelte 和 Vue 它们会有这个问题吗?
于是我尝试了其他所有框架,发现它们都有这个问题。
Vue 的代码如下:
状态: {{isOpen ? 'open' : 'closed'}} {isOpen = !isOpen}">Summary
Details
我也放到了码上掘金上,你可以看一下。
鉴于所有的框架都有这个问题,所以我认为它不应该是框架的问题。
于是我尝试用原生的 JavaScript 来编写这段程序。
状态: closedSummary
Details
现在看来,这似乎是 details 这个元素的底层工作原理的问题,和框架无关。
能够完美解决的唯一办法就是通过 e.preventDefault 来禁止掉浏览器默认行为,让 JavaScript 中的变量成为唯一的数据源。
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享