【问题标题】:react can't access state from document/window keypress反应无法从文档/窗口按键访问状态
【发布时间】:2019-09-21 04:07:15
【问题描述】:

举个例子

#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(state) 并不总是初始值。

【问题讨论】:

  • 刚试过keyDown同样的问题,我不认为是哪个事件的问题
  • 如果你不使用sate,你想在这里证明什么,只需使用history.current = history.current + '-' + key
  • @saurabh 我有两个状态,history.current 默认为空,当其中一个状态发生变化时,我将另一个状态存储在 history.current
  • 这正是我的问题,如果您使用useref,那么您为什么要这样做,那么您可以简单地使用`history.current = history.current + '-' + key`声明你可以直接在jsx中使用val

标签: javascript reactjs


【解决方案1】:

我认为您必须在每次更新中注册监听器。或者,您可以将状态存储在服务或变量中,或者使用 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 } = React;
let App = () => {

	let [val, setVal] = useState('initial Value')
  
  useEffect(() => {
		window.addEventListener("keypress", keyPressHandler);
		// document.addEventListener("keypress", keyPressHandler);
        return () => {
          window.removeEventListener("keypress", keyPressHandler);
        }
	});
  
  let keyPressHandler = ({ key }) => {
    console.log(val)
		setVal(val + "-" + key);
	};
  
	return (
		<div tabindex="0" >
			<h1>{val}</h1>
		</div>
	);
};
ReactDOM.render(<App />, document.querySelector("#app"));

</script>

【讨论】:

  • 你不觉得这听起来很沉重吗?我的意思是我不能只使用一个类组件,而且我不必在每次更新时都注册监听器
  • 视情况而定。您还可以将您的状态保存在服务或变量中的组件之外。例如让 AppState = {}。
  • 违背了使用状态的目的
  • 性能不应该是您的第一个问题。如果您有性能问题,您可以使用服务或变量作为瞬态。
【解决方案2】:

原因是当窗口重新渲染在第一次渲染中只访问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 的这篇博客。 link

编辑

随着问题的更新

最好的方法是使用 a 状态来存储历史值

如果你想使用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>

【讨论】:

  • 这可能会解决问题,但我正在寻找一个非常彻底的答案来解释为什么会发生这种情况,因为正如您在类组件中知道的那样,我不必做任何这些。
  • 好吧,问题是react can't access state from document/window keypress ,而且使用的两个演示都没有使用类
  • 您的解决方案仅在我想更新状态时才有效,但在某些情况下我只想访问新状态并对其进行处理而不更新状态
  • 我没听懂,你能举个例子吗?
  • 有第二个状态违背了目的,现在每次击键都会重新渲染
猜你喜欢
  • 2023-04-10
  • 1970-01-01
  • 2021-08-30
  • 1970-01-01
  • 2018-11-13
  • 2017-02-22
  • 1970-01-01
  • 2021-01-15
  • 1970-01-01
相关资源
最近更新 更多