javascript - 反应无法从文档/窗口按键访问状态
问题描述
给出下面的例子
#app div {
padding: 10px;
border: 1px solid;
margin: 10px 0;
}
div:focus {
background: red;
}
div:focus:before {
content: "focused";
display: block;
}
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
let { useState, useRef, Fragment } = React;
let App = () => {
let [, forceUpdate] = useState();
let [num, setNum] = useState(0);
let history = useRef('null');
let keyPressHandler = () => {
history.current = num;
// just to force an update
forceUpdate(Date.now());
};
return (
<Fragment>
<button onClick={()=>{setNum(Date.now())}}>update state</button>
<div tabindex="0" onKeyPress={keyPressHandler}>this div has a keypress listener. <br/>for now whatever key you press it will save the state in a ref, that changes on the button click. <br/>Click the button to change the state then focus this div then press any key to save the state in the ref</div>
<div>State : {num}</div>
<div>History: {history.current}</div>
</Fragment>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
现在我要将按键监听器移动到窗口,应该做同样的事情。
#app div {
padding: 10px;
border: 1px solid;
margin: 10px 0;
}
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
let { useEffect, useState, useRef, Fragment } = React;
let App = () => {
let [, forceUpdate] = useState();
let [num, setNum] = useState(0);
let history = useRef('null');
useEffect(() => {
window.addEventListener("keypress", keyPressHandler);
}, []);
let keyPressHandler = () => {
console.log('called keyPressHandler')
history.current = num;
// just to force an update
forceUpdate(Date.now());
};
return (
<Fragment>
<button onClick={()=>{setNum(Date.now())}}>update state</button>
<div>State : {num}</div>
<div>History: {history.current}</div>
</Fragment>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
但它并不num
(状态)始终是初始值。
解决方案
原因是当窗口重新渲染只访问val
第一次渲染中的变量时,它无法访问val
后续渲染中的新变量。
状态钩子也有一个回调,在其中传入当前状态。
在这种情况下,使用回调来读取最新的状态值并确保在添加之前拥有最新的状态值将解决问题。
例子:
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
let { useEffect, useState } = React;
let App = () => {
let [val, setVal] = useState('initial Value')
useEffect(() => {
window.addEventListener("keypress", keyPressHandler);
// document.addEventListener("keypress", keyPressHandler);
}, []);
let keyPressHandler = ({ key }) => {
console.log(val)
setVal(val => val + "-" + key); /*<- change this line*/
};
return (
<div tabindex="0" >
<h1>{val}</h1>
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
更新
至于 Zohir Salak 在评论中提出的问题。我会推荐阅读 Dan Abramov 的这篇博客。关联
编辑
随着问题的更新
最好的方法是使用状态来存储历史值
如果你想使用useref
那么我能想到的方式是
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<script type="text/babel">
let { useEffect, useState, useRef, Fragment } = React;
let App = () => {
let [,ForceUpdate] = useState();
let [keyUpdate, setKeyUpdate] = useState(0);
let [num, setNum] = useState(0);
let history = useRef('null');
useEffect(() => {
window.addEventListener("keypress", keyPressHandler);
}, []);
let keyPressHandler = () => {
console.log('called keyPressHandler');
setKeyUpdate(Date.now());
};
useEffect(() => {history.current = num; ForceUpdate(Date.now()) }, [keyUpdate]);
return (
<Fragment>
<button onClick={()=>{setNum(Date.now())}}>update state</button>
<div>State : {num}</div>
<div >History: {history.current}</div>
</Fragment>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
推荐阅读
- flutter - 从 Flutter 中的另一个类访问更改的 Slider 值
- windows - 任何人都可以修复该命令,我尝试了该命令,但 CMD 命令出错,
- django - 在白色 ip 上运行 Django 应用程序
- mysql - SQL 查询在不同表之间不一致
- mysql - 无法在 laravel 8 中存储数据
- javascript - 我想获取输入类型 Number 的最新值,然后将其传递给调度函数,但 setState 需要一些时间来更新?详情如下
- flutter - 当应用程序处于前台时,firebase 消息不显示通知
- javascript - 打开时在材质 UI 抽屉上设置超时
- javascript - 使用串扰包在传单中绘制动态条形图
- sql - 为什么“WHERE EXISTS()”和 WHERE id IN 的结果不同