我需要添加一些与React之外的对象进行交互的事件处理程序(以Google Maps为例)。 在此处理函数中,我想访问一些可以发送给该外部对象的状态。
如果将状态作为对效果的依赖关系进行传递,则它可以工作(我可以正确访问状态)。但是每次状态更改时都会添加添加/删除处理程序。
如果我没有将状态作为依赖项传递,则添加/删除处理程序将添加适当的时间(基本上是一次),但是状态永远不会更新(或更准确地说,处理程序无法拉最新状态)。
也许最好用Codepen解释一下: https://codepen.io/cjke/pen/dyMbMYr?editors=0010
const App = () => {
const [n, setN] = React.useState(0);
React.useEffect(() => {
const os = document.getElementById('outside-react')
const handleMouseOver = () => {
// I know innerHTML isn't "react" - this is an example of interacting with an element outside of React
os.innerHTML = `N=${n}`
}
console.log('Add handler')
os.addEventListener('mouseover', handleMouseOver)
return () => {
console.log('Remove handler')
os.removeEventListener('mouseover', handleMouseOver)
}
}, []) // <-- I can change this to [n] and `n` can be accessed, but add/remove keeps getting invoked
return (
<div>
<button onClick={() => setN(n + 1)}>+</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
如果效果的dep列表为[n]
,则会更新状态,但会为每次状态更改添加/删除添加/删除处理程序。如果效果的dep列表为[]
,则添加/删除处理程序可以正常工作,但状态始终为0(初始状态)。
我想要两者兼而有之。访问状态,但只访问一次useEffect(就像依赖项是[]
一样。)
我知道如何使用生命周期方法解决该问题,但不确定如何与Hooks配合使用。
如果以上是类组件,则看起来像:
class App extends React.Component {
constructor(props) {
super(props)
this.state = { n: 0 };
}
handleMouseOver = () => {
const os = document.getElementById("outside-react");
os.innerHTML = `N=${this.state.n}`;
};
componentDidMount() {
console.log("Add handler");
const os = document.getElementById("outside-react");
os.addEventListener("mouseover", this.handleMouseOver);
}
componentWillUnmount() {
console.log("Remove handler");
const os = document.getElementById("outside-react");
os.removeEventListener("mouseover", handleMouseOver);
}
render() {
const { n } = this.state;
return (
<div>
<strong>Info:</strong> Click button to update N in state, then hover the
orange box. Open the console to see how frequently the handler is
added/removed
<br />
<button onClick={() => this.setState({ n: n + 1 })}>+</button>
<br />
state inside react: {n}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
注意到状态更改后,添加/删除处理程序仅被添加一次(显然忽略了未卸载App组件的事实)。
我正在寻找一种用钩子复制它的方法
答案 0 :(得分:1)
正在发生的是,该函数在n
上关闭,但是闭包通常会看到对变量的更新,而钩子变量会一直在重新创建,从而使闭包变老。
在基于钩子的组件中,状态在每个渲染器上分配了一个新变量,侦听器函数从未关闭过该变量,并且闭包不会被更新,因为仅在挂载时创建函数一次(使用空的依赖项数组) )。相反,在基于类的组件中,this
保持不变,因此闭包可以看到更改。
我认为不会不断增加和删除侦听器。考虑一下事实,除非您在日常的React事件中使用useCallback()
创建事件处理程序(您只应对记忆的孩子进行此操作,否则,这是过早的优化),React本身实际上就是这样做,即删除前一个功能并设置新功能。
答案 1 :(得分:1)
您可以使用mutable refs来使当前状态与效果依赖项脱钩:
const [n, setN] = useState(0);
const nRef = useRef(n); // define mutable ref
useEffect(() => { nRef.current = n }) // nRef is updated after each render
useEffect(() => {
const handleMouseOver = () => {
os.innerHTML = `N=${nRef.current}` // n always has latest state here
}
os.addEventListener('mouseover', handleMouseOver)
return () => { os.removeEventListener('mouseover', handleMouseOver) }
}, []) // no need to set dependencies
const App = () => {
const [n, setN] = React.useState(0);
const nRef = React.useRef(n); // define mutable ref
React.useEffect(() => { nRef.current = n }) // nRef.current is updated after each render
React.useEffect(() => {
const os = document.getElementById('outside-react')
const handleMouseOver = () => {
os.innerHTML = `N=${nRef.current}` // n always has latest state here
}
os.addEventListener('mouseover', handleMouseOver)
return () => { os.removeEventListener('mouseover', handleMouseOver) }
}, []) // no need to set dependencies
return (
<div>
<button onClick={() => setN(prev => prev + 1)}>+</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<div id="outside-react">div</div>
<p>Update counter with + button, then mouseover the div to see recent counter state.</p>
在安装/卸载时,只会添加/删除一次事件监听器。可以在n
内读取当前状态useEffect
,而无需将其设置为依赖项([]
deps),因此无需重新触发更改。
您可以将useRef
视为函数组件和Hooks的可变实例变量。类组件中的等效项将是this
上下文-这就是为什么类组件示例的this.state.n
中的handleMouseOver
总是返回最新状态并起作用的原因。
上面的setInterval
模式展示了一个很棒的example by Dan Abramov。博客文章还说明了useCallback
的潜在问题以及每次状态更改时都读取/删除事件侦听器的情况。
其他有用的示例是os.addEventListener
之类的(全局)事件处理程序,或与React边缘的外部库/框架集成。
注意: React文档推荐给use this pattern sparingly。从我的角度来看,在您只需要“最新状态”(独立于React渲染周期更新)的情况下,它是一个可行的选择。通过使用可变变量,我们可以使用可能过时的闭包值突破函数闭包的范围。
写状态独立于依赖项还有其他选择-您可以查看How to register event with useEffect hooks?了解更多信息。
答案 2 :(得分:0)
您可以使用window
对象或global
对象使用useEffect分配所需的变量,如下所示:
try{
const App = () => {
const [n, setN] = React.useState(0);
React.useEffect(()=>{
window.num = n
},[n])
React.useEffect(() => {
const os = document.getElementById('outside-react')
const handleMouseOver =() => {
os.innerHTML = `N=${window.num}`
}
console.log('Add handler')
os.addEventListener('mouseover', handleMouseOver)
return () => {
console.log('Remove handler')
os.removeEventListener('mouseover', handleMouseOver)
}
}, []) // <-- I can change this to [n] and it works, but add/remove keeps getting invoked
return (
<div>
<strong>Info:</strong> Click button to update N in state, then hover the orange box. Open the console to see how frequently the handler is added/removed
<br/>
<button onClick={() => setN(n + 1)}>+</button>
<br/>
state inside react: {n}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
}
catch(error){
console.log(error.message)
}
<div id="outside-react">OUTSIDE REACT - hover to get state</div>
<div id="root"></div>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
这将在n个状态更改时更改window.num
答案 3 :(得分:0)
获取最新值的唯一方法是将其指定为依赖项,这是背后的原因
为什么一次又一次添加或删除调用?
每次依赖项更改时,它都会重新执行整个功能
为什么n
的值未更新?
每次渲染功能组件时,所有分配都将像普通函数一样重新发生,因此存储“ n = 0”的参考对象的值将保持不变,并在随后的每个渲染对象上创建一个新对象。将指向更新后的值
答案 4 :(得分:0)
您尝试解决此特定问题的方式中存在一些问题。
如果我将状态作为对效果的依赖关系进行传递,则它将起作用(我可以 正确访问状态),但每次添加添加/删除处理程序 状态改变的时候。
之所以有效,是因为处理程序功能已根据acc更新。到调用n
时的最新useeffect
值。
如果我不将状态作为依赖项传递,则添加/删除处理程序为 添加了适当的时间(基本上是一次),但是 状态永远不会更新(或更准确地说,处理程序无法拉出 最新状态)。
这是因为处理程序函数未获取n
的当前值
在此处使用refs
可能是一个优点,因为该值将在黑白时一直保留。在此处检查此示例:https://codesandbox.io/s/wispy-pond-j80j7?file=/src/App.js
export default function App() {
const [n, setN] = React.useState(0);
const nRef = React.useRef(0);
const outsideReactRef = React.useRef(null);
const handleMouseOver = React.useCallback(() => {
outsideReactRef.current.innerHTML = `N=${nRef.current}`;
}, []);
React.useEffect(() => {
outsideReactRef.current = document.getElementById("outside-react");
console.log("Add handler");
outsideReactRef.current.addEventListener("mouseover", handleMouseOver);
return () => {
console.log("Remove handler");
outsideReactRef.current.removeEventListener("mouseover", handleMouseOver);
};
}, []); // <-- I can change this to [n] and `n` can be accessed, but add/remove keeps getting invoked
return (
<div>
<button
onClick={() =>
setN(n => {
const newN = n + 1;
nRef.current = newN;
return newN;
})
}
>
+
</button>
</div>
);
}