首页 > 解决方案 > 反应无法从文档/窗口按键访问状态

问题描述

给出下面的例子

#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&nbsp;&nbsp;&nbsp;&nbsp;: {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&nbsp;&nbsp;&nbsp;&nbsp;: {num}</div>
    <div>History: {history.current}</div>
  </Fragment>
  ); 
}; 

ReactDOM.render(<App />, document.querySelector("#app"));
</script>

但它并不num(状态)始终是初始值。

标签: javascriptreactjs

解决方案


原因是当窗口重新渲染只访问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&nbsp;&nbsp;&nbsp;&nbsp;: {num}</div>
    <div >History: {history.current}</div>
  </Fragment>
  ); 
}; 

ReactDOM.render(<App />, document.querySelector("#app"));
</script>


推荐阅读