在我的 React 自定义挂钩的每次调用中添加和删除事件监听器是否过于昂贵?如何避免?
Is it too expensive to add and remove event listeners on every call of my React custom hook? How to avoid it?
这是我的情况:
我有一个名为 useClick
的自定义挂钩,它获得一个 HTML element
和一个 callback
作为输入,附加一个 click
事件侦听器到element
,并将callback
设置为事件处理程序。
App.js
function App() {
const buttonRef = useRef(null);
const [myState, setMyState] = useState(0);
function handleClick() {
if (myState === 3) {
console.log("I will only count until 3...");
return;
}
setMyState(prevState => prevState + 1);
}
useClick(buttonRef, handleClick);
return (
<div>
<button ref={buttonRef}>Update counter</button>
{"Counter value is: " + myState}
</div>
);
}
useClick.js
import { useEffect } from "react";
function useClick(element, callback) {
console.log("Inside useClick...");
useEffect(() => {
console.log("Inside useClick useEffect...");
const button = element.current;
if (button !== null) {
console.log("Attaching event handler...");
button.addEventListener("click", callback);
}
return () => {
if (button !== null) {
console.log("Removing event handler...");
button.removeEventListener("click", callback);
}
};
}, [element, callback]);
}
export default useClick;
请注意,使用上面的代码,我将在每次调用此挂钩时添加和删除事件侦听器。
而且我非常希望只在挂载时添加和在卸载时删除。而不是在每次调用时添加和删除。
尽管我正在使用这个:
useEffect(()=>{
// Same code as above
},[element,callback]); // ONLY RUN THIS WHEN 'element' OR 'callback' CHANGES
问题在于,即使 element
(ref) 在所有渲染中保持不变,callback
(这是来自 App) 的 handleClick 函数将在每次渲染时改变。所以我最终还是在每个渲染器上添加和删除了处理程序。
我也无法将 handleCLick
转换为 useCallback
,因为它取决于每次渲染都会更改的状态 myState
变量,而我的 useCallback
仍然会在每个渲染器上重新创建(当 myState
更改时)并且问题将继续存在。
const handleClick = useCallback(()=> {
if (myState === 3) {
console.log("I will only count until 3...");
return;
}
setMyState(prevState => prevState + 1);
},[myState]); // THIS WILL CHANGE ON EVERY RENDER!
问题:
我是否应该担心在每次渲染时移除和附加监听器?或者这真的一点也不贵并且不会影响我的表现?有办法避免吗?
您可以将当前回调存储在 ref 中,并为仅调用当前回调的事件侦听器提供静态回调:
function useClick(element, callback) {
console.log('Inside useClick...');
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
const callbackWrapper = useCallback(props => callbackRef.current(props), []);
useEffect(() => {
console.log('Inside useClick useEffect...');
const button = element.current;
if (button !== null) {
console.log('Attaching event handler...');
button.addEventListener('click', callbackWrapper);
}
return () => {
if (button !== null) {
console.log('Removing event handler...');
button.removeEventListener('click', callbackWrapper);
}
};
}, [element, callbackWrapper]);
}
工作示例:
这是我的情况:
我有一个名为 useClick
的自定义挂钩,它获得一个 HTML element
和一个 callback
作为输入,附加一个 click
事件侦听器到element
,并将callback
设置为事件处理程序。
App.js
function App() {
const buttonRef = useRef(null);
const [myState, setMyState] = useState(0);
function handleClick() {
if (myState === 3) {
console.log("I will only count until 3...");
return;
}
setMyState(prevState => prevState + 1);
}
useClick(buttonRef, handleClick);
return (
<div>
<button ref={buttonRef}>Update counter</button>
{"Counter value is: " + myState}
</div>
);
}
useClick.js
import { useEffect } from "react";
function useClick(element, callback) {
console.log("Inside useClick...");
useEffect(() => {
console.log("Inside useClick useEffect...");
const button = element.current;
if (button !== null) {
console.log("Attaching event handler...");
button.addEventListener("click", callback);
}
return () => {
if (button !== null) {
console.log("Removing event handler...");
button.removeEventListener("click", callback);
}
};
}, [element, callback]);
}
export default useClick;
请注意,使用上面的代码,我将在每次调用此挂钩时添加和删除事件侦听器。
而且我非常希望只在挂载时添加和在卸载时删除。而不是在每次调用时添加和删除。
尽管我正在使用这个:
useEffect(()=>{
// Same code as above
},[element,callback]); // ONLY RUN THIS WHEN 'element' OR 'callback' CHANGES
问题在于,即使 element
(ref) 在所有渲染中保持不变,callback
(这是来自 App) 的 handleClick 函数将在每次渲染时改变。所以我最终还是在每个渲染器上添加和删除了处理程序。
我也无法将 handleCLick
转换为 useCallback
,因为它取决于每次渲染都会更改的状态 myState
变量,而我的 useCallback
仍然会在每个渲染器上重新创建(当 myState
更改时)并且问题将继续存在。
const handleClick = useCallback(()=> {
if (myState === 3) {
console.log("I will only count until 3...");
return;
}
setMyState(prevState => prevState + 1);
},[myState]); // THIS WILL CHANGE ON EVERY RENDER!
问题:
我是否应该担心在每次渲染时移除和附加监听器?或者这真的一点也不贵并且不会影响我的表现?有办法避免吗?
您可以将当前回调存储在 ref 中,并为仅调用当前回调的事件侦听器提供静态回调:
function useClick(element, callback) {
console.log('Inside useClick...');
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
const callbackWrapper = useCallback(props => callbackRef.current(props), []);
useEffect(() => {
console.log('Inside useClick useEffect...');
const button = element.current;
if (button !== null) {
console.log('Attaching event handler...');
button.addEventListener('click', callbackWrapper);
}
return () => {
if (button !== null) {
console.log('Removing event handler...');
button.removeEventListener('click', callbackWrapper);
}
};
}, [element, callbackWrapper]);
}
工作示例: