32

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

我尝试对useEffect钩子做同样的事情,但似乎状态在useEffect.

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

这是一个虚拟示例。当您按下关闭时,结果始终为 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"));

代码沙盒

4

6 回答 6

25

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

造成这种情况的原因是由于关闭。闭包是函数对其作用域内变量的引用。您的useEffect回调仅在组件挂载时运行一次,因此返回回调引用初始计数值 0。

这里给出的答案是我推荐的。我会推荐@Jed Richard 对传递[count]到的回答,它的作用是仅在计数发生变化时才useEffect写入。localStorage这比在每次更新时都不传递任何东西的方法要好。除非您非常频繁地更改计数(每隔几毫秒),否则您不会看到性能问题,并且可以在更改localStorage时写入count

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

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

代码沙盒链接

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>

于 2018-12-05T21:46:53.263 回答
6

这将起作用 - 使用 React 的 useRef - 但它并不漂亮:

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

  // set/update countRef just like a regular variable
  countRef.current = count;

  // this effect fires as per a true componentWillUnmount
  useEffect(() => () => {
    console.log("count:", countRef.current);
  }, []);
}

请注意 useEffect 稍微更容易忍受的(在我看来!)“返回函数的函数”代码构造。

问题是 useEffect 在组合时复制道具和状态,因此从不重新评估它们 - 这对这个用例没有帮助,但它不是 useEffects 的真正用途。

感谢@Xitang 直接分配给 .current 的 ref,这里不需要 useEffect 。甜的!

于 2019-08-25T08:32:58.610 回答
4

您的 useEffect 回调函数显示初始计数,这是因为您的 useEffect 在初始渲染时仅运行一次,并且回调与初始渲染期间存在的计数值一起存储,该值为零。

在你的情况下你会做的是

 useEffect(() => {
    // TODO: Load state from localStorage on mount
    return () => {
      console.log("count:", count);
    };
  });

在 react docs 中,您会找到一个原因,说明为什么它是这样定义的

React 究竟何时清理效果?React 在组件卸载时执行清理。然而,正如我们之前所了解的,效果会为每次渲染运行,而不仅仅是一次。这就是为什么 React 还会在下次运行效果之前清理上一次渲染中的效果。

阅读反应文档Why Effects Run on Each Update

它确实在每个渲染上运行,为了优化它,你可以让它在count变化时运行。但这是useEffect文档中也提到的当前提议的行为,并且可能在实际实现中发生变化。

 useEffect(() => {
    // TODO: Load state from localStorage on mount
    return () => {
      console.log("count:", count);
    };
  }, [count]);
于 2018-12-05T13:52:12.353 回答
2

您可以使用 useEffect 来更新参考,而不是像在接受的答案中那样手动跟踪您的状态更改。

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

  // update the ref if the counter changes
  useEffect(() => {
    currentCountRef.current = count;
  }, [count]);

  // use the ref on unmount
  useEffect(
    () => () => {
      console.log("count:", currentCountRef.current);
    },
    []
  );

  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>
  );
}
于 2019-10-10T16:03:25.683 回答
2

另一个答案是正确的。为什么不传递[count]给您的 useEffect,以便在count更改时保存到 localStorage?像这样调用 localStorage 并没有真正的性能损失。

于 2018-12-05T13:56:07.460 回答
0

发生的事情是第一次 useEffect 运行,它对你传递的状态值创建一个闭包;那么如果你想得到实际的而不是第一个......你有两个选择:

  • 使 useEffect 具有超过计数的依赖项,这将在该依赖项的每次更改时刷新它。
  • 在 setCount 上使用函数更新程序。

如果您执行以下操作:

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

我添加了这个解决方案,以防有人来这里寻找一个问题,试图在不重新加载的情况下根据旧值对 useEffect 进行更新。

于 2020-08-19T14:55:34.977 回答