在React useEffect钩子中引用过时状态 [英] Referencing outdated state in React useEffect hook

查看:70
本文介绍了在React useEffect钩子中引用过时状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在卸载组件时将状态保存到 localStorage
这曾经用于 componentWillUnmount



我试图对<$ c做同样的事情$ c> useEffect hook,但似乎在 useEffect 的返回函数中状态不正确。



为什么?如何在不使用类的情况下保存状态?



这是一个虚拟示例。按下关闭时,结果始终为0.

  import React,{useState,useEffect}来自反应; 
从react-dom导入ReactDOM;

函数Example(){
const [tab,setTab] = useState(0);
返回(
< div>
{tab === 0&&< Content onClose = {()=> setTab(1)} />}
{tab === 1&&< div>为什么控制台中的计数始终为0?< / div>}
< / div>
);
}

函数内容(道具){
const [count,setCount] = useState(0);

useEffect(()=> {
// TODO:在mount

$ b返回()=> {
控制台上从localStorage加载状态.log(count:,count);
};
},[]);

返回(
< div>
< p> Day:{count}< / p>
< button onClick = {()=> ; setCount(count - 1)}> -1< / button>
< button onClick = {()=> setCount(count + 1)}> + 1< / button>
< button onClick = {()=> props.onClose()}> close< / button>
< / div>
);
}

ReactDOM.render(< Example />,document.querySelector(#app));

CodeSandbox

解决方案


我试图用useEffect钩子做同样的事情,但似乎在useEffect的return函数中状态不正确。


原因是闭包。闭包是函数对其作用域中变量的引用。您的 useEffect 回调仅在组件安装时运行一次,因此返回回调引用初始计数值0.



这里给出的答案是我推荐的。我建议@Jed Richard回答将 [count] 传递给 useEffect ,这会产生写入<$的效果c $ c> localStorage 仅在计数发生变化时。这比在每次更新时都没有传递任何内容的方法要好。除非你非常频繁地更改计数(每隔几毫秒),否则你不会看到性能问题,只要 count <就可以写入 localStorage / code>更改。

  useEffect(()=> {...},[count]); 

如果您坚持只写 localStorage 在卸载时,你可以使用一个丑陋的黑客/解决方案 - 参考。基本上,您将创建一个在组件的整个生命周期中存在的变量,您可以从其中的任何位置引用该变量。但是,您必须手动将状态与该值同步,这非常麻烦。 Refs不会给你上面提到的闭包问题,因为refs是一个带有当前字段的对象,并且多次调用 useRef 将返回相同的对象。只要你改变 .current 值,你的 useEffect 就可以(只)读取最新的值。 / p>

CodeSandbox链接



  const {useState,useEffect,useRef} = React; function Example(){const [tab,setTab] = useState(0); return(< div> {tab === 0&&< Content onClose = {()=> setTab(1)} />} {tab === 1&&< div>控制台中的计数并不总是0< / div>}< / div>);} function Content(props){const value = useRef(0); const [count,setCount] = useState(value.current); useEffect(()=> {return()=> {console.log('count:',value.current);};},[]); return(< div>< p> Day:{count}< / p>< button onClick = {()=> {value.current  -  = 1; setCount(value.current);}}> -1< / button>< button onClick = {()=> {value.current + = 1; setCount(value.current);}}> +1< / button>< button onClick = { ()=> props.onClose()}> close< / button>< / div>);} ReactDOM.render(< Example />,document.querySelector('#app'));  

 < script src =https://unpkg.com/ react@16.7.0-alpha.0/umd/react.development.js>< / script>< script src =https://unpkg.com/react-dom@16.7.0-alpha.0 /umd/react-dom.development.js\"></script><div id =app>< / div>  


I want to save state to localStorage when a component is unmounted. This used to work in componentWillUnmount.

I tried to do the same with the useEffect hook, but it seems state is not correct in the return function of useEffect.

Why is that? How can I save state without using a class?

Here is a dummy example. When you press close, the result is always 0.

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function Example() {
  const [tab, setTab] = useState(0);
  return (
    <div>
      {tab === 0 && <Content onClose={() => setTab(1)} />}
      {tab === 1 && <div>Why is count in console always 0 ?</div>}
    </div>
  );
}

function Content(props) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // TODO: Load state from localStorage on mount

    return () => {
      console.log("count:", count);
    };
  }, []);

  return (
    <div>
      <p>Day: {count}</p>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => props.onClose()}>close</button>
    </div>
  );
}

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

CodeSandbox

解决方案

I tried to do the same with the useEffect hook, but it seems state is not correct in the return function of useEffect.

The reason for this is due to closures. A closure is a function's reference to the variables in its scope. Your useEffect callback is only ran once when the component mounts and hence the return callback is referencing the initial count value of 0.

The answers given here are what I would recommend. I would recommend @Jed Richard's answer of passing [count] to useEffect, which has the effect of writing to localStorage only when count changes. This is better than the approach of not passing anything at all writing on every update. Unless you are changing count extremely frequently (every few ms), you wouldn't see a performance issue and it's fine to write to localStorage whenever count changes.

useEffect(() => { ... }, [count]);

If you insist on only writing to localStorage on unmount, there's an ugly hack/solution you can use - refs. Basically you would create a variable that is present throughout the whole lifecycle of the component which you can reference from anywhere within it. However, you would have to manually sync your state with that value and it's extremely troublesome. Refs don't give you the closure issue mentioned above because refs is an object with a current field and multiple calls to useRef will return you the same object. As long as you mutate the .current value, your useEffect can always (only) read the most updated value.

CodeSandbox link

const {useState, useEffect, useRef} = React;

function Example() {
  const [tab, setTab] = useState(0);
  return (
    <div>
      {tab === 0 && <Content onClose={() => setTab(1)} />}
      {tab === 1 && <div>Count in console is not always 0</div>}
    </div>
  );
}

function Content(props) {
  const value = useRef(0);
  const [count, setCount] = useState(value.current);

  useEffect(() => {
    return () => {
      console.log('count:', value.current);
    };
  }, []);

  return (
    <div>
      <p>Day: {count}</p>
      <button
        onClick={() => {
          value.current -= 1;
          setCount(value.current);
        }}
      >
        -1
      </button>
      <button
        onClick={() => {
          value.current += 1;
          setCount(value.current);
        }}
      >
        +1
      </button>
      <button onClick={() => props.onClose()}>close</button>
    </div>
  );
}

ReactDOM.render(<Example />, document.querySelector('#app'));

<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

这篇关于在React useEffect钩子中引用过时状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆