Isa*_*aac 24 javascript reactjs react-hooks
我正在学习有关如何使用钩子注册事件的Udemy课程,讲师给出了以下代码:
const [userText, setUserText] = useState('');
const handleUserKeyPress = event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(`${userText}${key}`);
}
};
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
};
});
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
Run Code Online (Sandbox Code Playgroud)
现在效果很好,但我不认为这是正确的方法。原因是,如果我理解正确,那么每次重新渲染时,事件都会每次都在注册和注销,而我根本不认为这样做是正确的方法。
所以我useEffect对下面的钩子做了一些修改
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
};
}, []);
Run Code Online (Sandbox Code Playgroud)
通过使用一个空数组作为第二个参数,使组件只运行一次效果,即模仿componentDidMount。当我尝试结果时,奇怪的是,在我键入的每个键上都没有附加,而是被覆盖了。
我期待setUserText(${userText}${key}); 将新键入的键追加到当前状态并设置为新状态,但是,它会忘记旧状态并用新状态重写。
我们应该在每次重新渲染时注册和注销事件真的是正确的方法吗?
for*_*d04 35
[...] 在每次重新渲染时,事件每次都会继续注册和取消注册,我只是认为这不是正确的方法。
你是对的。useEffect在每次渲染时重新启动内部事件处理是没有意义的。
[...] 空数组作为第二个参数,让组件只运行一次效果 [...] 奇怪的是,在我输入的每个键上,不是追加而是覆盖。
这是陈旧的闭包值的问题。
原因:内部使用的函数useEffect应该是依赖项的一部分。您没有设置任何依赖项 ( []),但仍然调用handleUserKeyPress,它本身读取userText状态。
根据您的用例,有一些替代方案。
setUserText(prev => `${prev}${key}`);
Run Code Online (Sandbox Code Playgroud)
? 侵入性最小的方法
? 只能访问自己以前的状态,不能访问其他状态
setUserText(prev => `${prev}${key}`);
Run Code Online (Sandbox Code Playgroud)
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}`); // use updater function here
}
};
window.addEventListener("keydown", handleUserKeyPress);
return () => {
window.removeEventListener("keydown", handleUserKeyPress);
};
}, []); // still no dependencies
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));Run Code Online (Sandbox Code Playgroud)
useReducer- “作弊模式”我们可以切换到useReducer并访问当前状态/道具 - 使用与useState.
const [userText, handleUserKeyPress] = useReducer((state, event) => {
const { key, keyCode } = event;
// isUpperCase is always the most recent state (no stale closure value)
return `${state}${isUpperCase ? key.toUpperCase() : key}`;
}, "");
Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
const [userText, handleUserKeyPress] = useReducer((state, event) => {
const { key, keyCode } = event;
// isUpperCase is always the most recent state (no stale closure value)
return `${state}${isUpperCase ? key.toUpperCase() : key}`;
}, "");
Run Code Online (Sandbox Code Playgroud)
useState更新函数const [userText, setUserText] = useReducer((state, action) =>
typeof action === "function" ? action(state, isUpperCase) : action, "");
// ...
setUserText((prevState, isUpper) => `${prevState}${isUpper ? key.toUpperCase() : key}`);
Run Code Online (Sandbox Code Playgroud)
const App = () => {
const [isUpperCase, setUpperCase] = useState(false);
const [userText, handleUserKeyPress] = useReducer((state, event) => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
// isUpperCase is always the most recent state (no stale closure)
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"));Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
? 不需要管理依赖
?访问多个状态和道具
?与useState
?相同的 API 可扩展到更复杂的案例/减速器
?由于内联减速器(有点可忽略),性能略有下降
?稍微增加了减速器的复杂性
useRef/ 在可变引用中存储回调const cbRef = useRef(handleUserKeyPress);
useEffect(() => { cbRef.current = handleUserKeyPress; }); // update after each render
useEffect(() => {
const cb = e => cbRef.current(e); // then use most recent cb value
window.addEventListener("keydown", cb);
return () => { window.removeEventListener("keydown", cb) };
}, []);
Run Code Online (Sandbox Code Playgroud)
const [userText, setUserText] = useReducer((state, action) =>
typeof action === "function" ? action(state, isUpperCase) : action, "");
// ...
setUserText((prevState, isUpper) => `${prevState}${isUpper ? key.toUpperCase() : key}`);
Run Code Online (Sandbox Code Playgroud)
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"));Run Code Online (Sandbox Code Playgroud)
? 对于不用于重新渲染数据流的回调/事件处理程序
?不需要管理依赖
?React docs 只推荐作为最后一个选项
?更必要的方法
useCallback虽然它可以以各种方式应用,但useCallback不适合这个特定的问题案例。
原因:由于添加了依赖项 -userText此处 - 事件侦听器将在每次按键时重新启动,最好的情况是性能不佳,或者更糟的是导致不一致。
<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>Run Code Online (Sandbox Code Playgroud)
const cbRef = useRef(handleUserKeyPress);
useEffect(() => { cbRef.current = handleUserKeyPress; }); // update after each render
useEffect(() => {
const cb = e => cbRef.current(e); // then use most recent cb value
window.addEventListener("keydown", cb);
return () => { window.removeEventListener("keydown", cb) };
}, []);
Run Code Online (Sandbox Code Playgroud)
为了完整起见,这里有一些useCallback总体上的关键点:
? 万能的务实解决方案
? 微创
? 手动依赖管理
? useCallback使函数定义更加冗长/混乱
useEffect直接在内部useEffect声明事件处理函数与 或多或少有相同的问题useCallback,后者只会导致更多的间接依赖关系。
换句话说:useCallback我们没有通过添加额外的依赖层,而是将函数直接放在里面useEffect——但是仍然需要设置所有依赖项,导致频繁的处理程序更改。
事实上,如果你在handleUserKeyPress里面移动useEffect,ESLint 详尽的 deps 规则会告诉你userText,如果没有指定,缺少什么确切的规范依赖项()。
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"));Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
Shu*_*tri 27
处理此类情况的最佳方法是查看事件处理程序中的操作。如果仅使用先前的状态来设置状态,则最好使用回调模式并仅在初始安装时注册事件侦听器。如果您不使用callback pattern(https://reactjs.org/docs/hooks-reference.html#usecallback),则事件侦听器将使用侦听器引用及其词法范围,但会创建一个新函数,并在新的渲染,因此在处理程序中,您将无法进入更新状态
const [userText, setUserText] = useState('');
const handleUserKeyPress = useCallback(event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(prevUserText => `${prevUserText}${key}`);
}
}, []);
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
};
}, [handleUserKeyPress]);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
Run Code Online (Sandbox Code Playgroud)
Spa*_*Bao 11
新答案:
useEffect(() => {
function handlekeydownEvent(event) {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(prevUserText => `${prevUserText}${key}`);
}
}
document.addEventListener('keyup', handlekeydownEvent)
return () => {
document.removeEventListener('keyup', handlekeydownEvent)
}
}, [])
Run Code Online (Sandbox Code Playgroud)
使用时setUserText,将函数作为参数而不是对象传递,prevUserText将始终是最新状态。
旧答案:
试试这个,它的工作原理与您的原始代码相同:
useEffect(() => {
function handlekeydownEvent(event) {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(`${userText}${key}`);
}
}
document.addEventListener('keyup', handlekeydownEvent)
return () => {
document.removeEventListener('keyup', handlekeydownEvent)
}
}, [userText])
Run Code Online (Sandbox Code Playgroud)
因为在您的useEffect()方法中,它取决于userText变量,但您不要将它放在第二个参数中,否则userText将始终''使用参数绑定到初始值[]。
您不需要这样做,只是想让您知道为什么您的第二个解决方案不起作用。
| 归档时间: |
|
| 查看次数: |
9197 次 |
| 最近记录: |