假设我有一个 React 组件,它可能会接收多个事件,这些事件在一次交互后执行状态更改,如下所示:
const {useRef, useState} = React;
function App() {
const [_, setState] = useState(null);
const renderCount = useRef(0);
renderCount.current += 1;
console.log("rendering");
return (
<div>
{`Render count: ${renderCount.current} `}
<span onClick={() => {
console.log("span clicked");
setState({});
}}>
<button onClick={() => {
console.log("button clicked");
setState({});
}}>Send click events</button>
</span>
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render( < App / > );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>
在上面的示例中,按下按钮触发单击事件后,两个事件处理程序都会运行,但 React 会将状态更新批量处理为仅重新渲染一次。
但是,如果我将这些事件处理程序直接附加到 native HTML 元素上(如下所示),React 会在每次交互时重新渲染两次,或者在每个事件处理程序之后重新渲染两次。
const {useRef, useState, useEffect} = React;
function App() {
const [_, setState] = useState(null);
const spanRef = useRef();
const btnRef = useRef();
const renderCount = useRef(0);
renderCount.current += 1;
console.log("rendering");
useEffect(() => {
const handler1 = () => {
console.log("span clicked");
setState({});
};
const handler2 = () => {
console.log("button clicked");
setState({});
};
const span = spanRef.current;
span.addEventListener("click", handler1);
const btn = btnRef.current;
btn.addEventListener("click", handler2);
return () => {
span.removeEventListener("click", handler1);
btn.removeEventListener("click", handler2);
};
}, []);
return (
<div>
{`Render count: ${renderCount.current} `}
<span ref={spanRef}>
<button ref={btnRef}>Send click events</button>
</span>
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render( < App / > );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>
我怀疑这与 React 的合成事件系统有关,但我正在尝试准确地理解该行为。例如,React 是否总是在 native 事件监听器之后立即重新渲染状态更新?如果是这样,效果是否仍在重新渲染之间运行,或者它们是否推迟到所有事件处理程序执行完毕为止?
无论是答案还是引用官方文档,我们都会表示赞赏。需要澄清的是,这并不是针对我正在解决的特定项目或问题,我只是想更好地理解 React 的行为。
最佳答案
区别在于(很可能),
- 当使用 addEventListener 将事件监听器附加到 native HTML 元素时,用鼠标单击异步调用事件处理程序,而
- React events调用事件处理程序 synchronously .
我希望 React 没有很好地记录这一点,因为你询问事情发生的顺序,而在 React 中你不应该关心这些。
我什至会说,React 可能随时改变内部行为,而不改 rebase 本概念(例如 React 对状态的保证)。
无论如何,我会在这里尝试理解它(抱歉,我需要猜测一下) ...
react 事件
可能相关的事实是React attaches event handlers at the root .
您可以通过记录 event.nativeEvent
property 的 .currentTarget
来确认这一点,例如:
<span onClick={ event => {
console.log("span clicked:", event.nativeEvent.currentTarget, event.nativeEvent.target);
setState({});
}}>
<button onClick={ event => {
console.log("button clicked:", event.nativeEvent.currentTarget, event.nativeEvent.target);
setState({});
}}>Send click events</button>
</span>
原生事件
在javascript中,没有任何东西可以并行运行,所以正常的点击事件, 由例如发起当其他 javascript 代码仍在运行时,无法执行鼠标操作。
因此,点击事件会排队,并且每个事件仅在 call stack 时执行。 (又名“执行堆栈”)为空。
因此,
- 按钮点击处理程序与
setState
和虚拟DOM的重新渲染一起执行, - 然后可以执行 span click 处理程序,并执行第二个
setState
和重新渲染, - 然后实际的浏览器 DOM 就会更新。
合成事件
我假设 React 使用相同的内部事件处理程序处理来自多个元素的事件,并且 使用 synthetic events 调用附加的处理程序.
由于合成事件始终同步执行,因此所有附加的事件处理程序都会在内部 React 事件处理程序返回之前执行,即
- button onClick 与
setState
一起执行,但随后不是重新渲染, - 执行span click处理程序,并执行第二个
setState
。 - 然后会发生重新渲染和 DOM 更新。
检查事件
无法检查或“利用”已附加的事件处理程序 (除了“检查节点”和 getEventListeners 等浏览器开发工具),但您可以进一步检查事件执行的顺序,如果您
- 将 native 事件处理程序和 React 事件处理程序添加到元素中,
- 在
microtask
中添加另一个console.log
,这将说明调用堆栈是空的。
例如:
const handler1 = () => {
console.log("Native event: spanRef click");
queueMicrotask(() => { console.log('Native event: spanRef click (microtask)'); } )
setState({});
};
const handler2 = () => {
console.log("Native event: buttonRef click");
queueMicrotask( () => { console.log('Native event: buttonRef click (microtask)'); } )
setState({});
};
// ...
<span ref={ spanRef } onClick={ () => {
console.log('React event: span onClick');
queueMicrotask( () => {
console.log('React event: span onClick (microtask)');
})
setState({});
}}>
<button ref={ buttonRef } onClick={ () => {
console.log( 'React event: button onClick' );
queueMicrotask( () => {
console.log( 'React event: button onClick (microtask)' );
})
setState({});
}}>Send click events</button>
</span>
鼠标单击按钮后将记录:
rendering 1
Native event: buttonRef click \
rendering 2 | native button event completely executed
Native event: buttonRef click (microtask) /
Native event: spanRef click \
rendering 4 | native span event completely executed
Native event: spanRef click (microtask) /
(Here probably Reacts internal root event handler executed)
React event: button onClick \
React event: span onClick |
rendering 6 | All React events executed
React event: button onClick (microtask) |
React event: span onClick (microtask) /
关于javascript - React 在接收到多个并发事件后如何重新渲染是否有明确定义的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75872279/