问题
[...]在每次重新渲染时,事件都会每次都在注册和注销,而我根本不认为这样做是正确的方法。
你是对的。它没有意义的重启事件中处理useEffect的每个渲染。
[...]空数组作为第二个参数,让组件只运行一次效果,奇怪的是,在我键入的每个键上都没有覆盖,而是覆盖了它。
这是陈旧的闭包值问题。
原因:内部使用的函数useEffect应该是dependencies的一部分。您没有设置任何依赖项([]),但仍调用handleUserKeyPress,其本身读取userText状态。
解
根据您的用例,有一些替代方案。
1.状态更新器功能
setUserText(prev => `${prev}${key}`);
✔微创方法
✖仅访问自己的先前状态,不能访问其他状态
const App = () => {
const [userText, setUserText] = useState("");
useEffect(() => {
const handleUserKeyPress = event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(prev => `${prev}${key}`);
}
};
window.addEventListener("keydown", handleUserKeyPress);
return () => {
window.removeEventListener("keydown", handleUserKeyPress);
};
}, []);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
2. useReducer- “作弊模式”
我们可以切换到useReducer并可以访问当前状态/道具-使用与相似的API useState。
变体2a:减速器功能内的逻辑
const [userText, handleUserKeyPress] = useReducer((state, event) => {
const { key, keyCode } = event;
return `${state}${isUpperCase ? key.toUpperCase() : key}`;
}, "");
const App = () => {
const [isUpperCase, setUpperCase] = useState(false);
const [userText, handleUserKeyPress] = useReducer((state, event) => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
return `${state}${isUpperCase ? key.toUpperCase() : key}`;
}
}, "");
useEffect(() => {
window.addEventListener("keydown", handleUserKeyPress);
return () => {
window.removeEventListener("keydown", handleUserKeyPress);
};
}, []);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
<button style={{ width: "150px" }} onClick={() => setUpperCase(b => !b)}>
{isUpperCase ? "Disable" : "Enable"} Upper Case
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
变体2b:减速器功能之外的逻辑-类似于
useState更新器功能
const [userText, setUserText] = useReducer((state, action) =>
typeof action === "function" ? action(state, isUpperCase) : action, "");
setUserText((prevState, isUpper) => `${prevState}${isUpper ? key.toUpperCase() : key}`);
const App = () => {
const [isUpperCase, setUpperCase] = useState(false);
const [userText, setUserText] = useReducer(
(state, action) =>
typeof action === "function" ? action(state, isUpperCase) : action,
""
);
useEffect(() => {
const handleUserKeyPress = event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(
(prevState, isUpper) =>
`${prevState}${isUpper ? key.toUpperCase() : key}`
);
}
};
window.addEventListener("keydown", handleUserKeyPress);
return () => {
window.removeEventListener("keydown", handleUserKeyPress);
};
}, []);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
<button style={{ width: "150px" }} onClick={() => setUpperCase(b => !b)}>
{isUpperCase ? "Disable" : "Enable"} Upper Case
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
✔无需管理依赖项
✔访问多个状态和道具
✔与API相同useState
✔可扩展到更复杂的案例/减速器
✖由于内联减速器(有点可忽略)而导致的性能
略有下降✖减速器的复杂性略有增加
3. useCallback/事件处理程序里面useEffect
如果您进入handleUserKeyPress内部useEffect,ESLint详尽的deps规则将告诉您,缺少哪些确切的规范依赖(userText):
const App =() => {
const [userText, setUserText] = useState("");
useEffect(() => {
const handleUserKeyPress = event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(`${userText}${key}`);
}
};
window.addEventListener("keydown", handleUserKeyPress);
return () => {
window.removeEventListener("keydown", handleUserKeyPress);
};
}, [userText]);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
useCallback 是等效的替代品,具有更多的依赖关系间接方式:
const App = () => {
const [userText, setUserText] = useState("");
const handleUserKeyPress = useCallback(
event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(`${userText}${key}`);
}
},
[userText]
);
useEffect(() => {
window.addEventListener("keydown", handleUserKeyPress);
return () => {
window.removeEventListener("keydown", handleUserKeyPress);
};
}, [handleUserKeyPress]);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
注意:虽然可以以多种方式应用此变体,但它不适合出现问题的情况,因此出于完整性考虑而列出。原因:每次按键都会重新启动事件监听器。
✔通用的务实解决方案
✔最小的侵入性
✖手动依赖项的管理
✖useCallback使功能定义更加冗长/混乱
4. useRef/将回调存储在可变引用中
const cbRef = useRef(handleUserKeyPress);
useEffect(() => { cbRef.current = handleUserKeyPress; });
useEffect(() => {
const cb = e => cbRef.current(e);
window.addEventListener("keydown", cb);
return () => { window.removeEventListener("keydown", cb) };
}, []);
const App = () => {
const [userText, setUserText] = useState("");
const handleUserKeyPress = event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(`${userText}${key}`);
}
};
const cbRef = useRef(handleUserKeyPress);
useEffect(() => {
cbRef.current = handleUserKeyPress;
});
useEffect(() => {
const cb = e => cbRef.current(e);
window.addEventListener("keydown", cb);
return () => {
window.removeEventListener("keydown", cb);
};
}, []);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
✔对于不用于重新渲染数据流的回调/事件处理程序
✔无需管理依赖
项React仅由React docs建议作为最后一个选项
✖更强制性的方法
请查看以下链接以获取更多信息:1 2 3