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

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

问题描述

我想在卸载组件时将状态保存到 localStorage.这曾经在 componentWillUnmount 中工作.

我尝试对 useEffect 钩子做同样的事情,但在 useEffect 的返回函数中似乎状态不正确.

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

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

import React, { useState, useEffect } from "react";从react-dom"导入 ReactDOM;函数示例(){const [tab, setTab] = useState(0);返回 (<div>{tab === 0 &&<Content onClose={() =>setTab(1)}/>}{tab === 1 &&<div>为什么控制台中的计数总是 0?</div>}

);}功能内容(道具){const [count, setCount] = useState(0);useEffect(() => {//TODO: 从 localStorage 加载状态挂载返回 () =>{console.log("count:", count);};}, []);返回 (<div><p>天:{count}</p><button onClick={() =>setCount(count - 1)}>-1</button><button onClick={() =>setCount(count + 1)}>+1<button onClick={() =>props.onClose()}>close</button>

);}ReactDOM.render(, document.querySelector("#app"));

CodeSandbox

解决方案

我尝试对 useEffect 钩子做同样的事情,但似乎 useEffect 的返回函数中的状态不正确.

这是由于关闭造成的.闭包是函数对其作用域内变量的引用.您的 useEffect 回调仅在组件安装时运行一次,因此返回回调引用的初始计数值为 0.

这里给出的答案是我推荐的.我会推荐@Jed Richard 将 [count] 传递给 useEffect 的答案,这具有仅在计数更改时写入 localStorage 的效果.这比在每次更新时完全不传递任何内容的方法要好.除非您非常频繁地(每隔几毫秒)更改计数,否则您不会看到性能问题,只要 count 更改,就可以写入 localStorage.

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

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

CodeSandbox 链接

const {useState, useEffect, useRef} = React;函数示例(){const [tab, setTab] = useState(0);返回 (<div>{tab === 0 &&<Content onClose={() =>setTab(1)}/>}{tab === 1 &&<div>控制台中的计数并不总是 0</div>}

);}功能内容(道具){常量值 = useRef(0);const [count, setCount] = useState(value.current);useEffect(() => {返回 () =>{console.log('count:', value.current);};}, []);返回 (<div><p>天:{count}</p><按钮onClick={() =>{value.current -= 1;setCount(value.current);}}>-1<按钮onClick={() =>{value.current += 1;setCount(value.current);}}>+1<button onClick={() =>props.onClose()}>close</button>

);}ReactDOM.render(, 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天全站免登陆