【问题标题】:React hooks state not using latest反应钩子状态不使​​用最新
【发布时间】:2019-03-18 12:30:45
【问题描述】:

我有下面的代码,我想在其中创建标签列表。在这个例子中,我在setAllTags() 中获取标签列表,然后在setAvailableTags() 中获取一些可用标签。

然后我遇到的问题是,当setAvailableTags() 运行时,它将删除在setAllTags() 中获取的标签。当setAvailableTags()设置它的状态时,我在setAllTags()中设置的状态似乎没有使用。

知道我能做些什么来解决这个问题吗?

https://codesandbox.io/s/rj40lz6554

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

const Search = () => {
  const [tags, setTags] = useState({
    all: [],
    available: []
  });

  const setAllTags = () => {
    const all = ["tag 1", "tag 2", "tag 3"];
    const newValue = {
      ...tags,
      all
    };
    console.log("setting all tags to ", newValue);
    setTags(newValue);
  };

  const setAvailableTags = () => {
    const available = ["tag 1", "tag 2"];
    const newValue = {
      ...tags,
      available
    };
    console.log("setting available tags to", newValue);
    setTags(newValue);
  };

  useEffect(setAllTags, []);
  useEffect(setAvailableTags, []);

  return (
    <div>
      <div>
        <select placeholder="Tags">
          {tags.all.map((tag, i) => (
            <option key={tag + i} value={tag}>
              {tag}
            </option>
          ))}
        </select>
      </div>
    </div>
  );
};

const App = () => {
  return (
    <div>
      <h1>Hello React!</h1>
      <Search />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));

控制台记录了

setting all tags to Object {all: Array[3], available: Array[0]}
setting available tags to Object {all: Array[0], available: Array[2]}

【问题讨论】:

  • 你不应该在 useEffect 中设置状态。
  • useEffect中设置状态是完全可以的。
  • 没有警戒条件的情况下不会。它将永远运行。

标签: javascript reactjs react-hooks


【解决方案1】:

在 react 文档中,您可以看到 useEffect 的工作原理 (https://reactjs.org/docs/hooks-effect.html)

有经验的 JavaScript 开发人员可能会注意到,传递给 useEffect 的函数在每次渲染时都会有所不同。这是故意的。事实上,这让我们可以从效果内部读取计数值,而不必担心它会过时。每次重新渲染时,我们都会安排不同的效果,替换之前的效果。在某种程度上,这使得效果更像渲染结果的一部分——每个效果“属于”特定的渲染。我们将在本页后面更清楚地了解为什么这很有用。

这意味着,渲染函数中的每个副作用都只能访问useState 的初始结果。请记住,反应状态是不可变的,因此更新状态并尝试在同一渲染周期内使用更新后的版本没有任何意义。

你可以通过简单的调用来看到这一点:

setTags({ all: ['test'] })
console.log(tags)

您应该注意到tags 根本没有改变。

我个人会在这里使用钩子约定,并将你的 useState 分成两个单独的变量:

const [allTags, setAllTags] = useState([])
const [availableTags, setAvailableTags] = useState([])

这使您的效果更加明确(因为它们只需要更新一个变量),并提高了可读性。

【讨论】:

  • 谢谢马特,我从答案的数量看出有多种解决方法,但这是我解决的方法。
  • 钩子的描述方式是一种有漏洞的抽象。因为它泄露了内部运作。如果手册更明确并且更少“害怕”显示复杂性;反应钩子可能是一个更自然的抽象。
【解决方案2】:

setTags 改变内部反应状态,并不会直接改变tags 的值。所以直到下一次渲染它才会更新。

改用这个调用:

setTags(currentTags => ({...currentTags, all}));

available做同样的事情。

【讨论】:

    【解决方案3】:

    来自:https://reactjs.org/docs/hooks-reference.html#usestate

    与类组件中的 setState 方法不同,useState 不会自动合并更新对象。您可以通过将函数更新程序表单与对象扩展语法相结合来复制此行为:

    setState(prevState => {
      // Object.assign would also work
      return {...prevState, ...updatedValues};
    });
    

    另一个选项是useReducer,它更适合管理包含多个子值的状态对象。

    如果你想 setAvailableTags()setAllTags() 为基础,你应该这样做 useEffect(setAvailableTags, [tags.all])

    工作示例:https://codesandbox.io/s/5vzr20x99p?from-embed

    【讨论】:

      【解决方案4】:

      问题是当你使用第二个参数[]时,该函数只会在组件第一次渲染时被调用。但是因为你在其中使用了 setState 组件被重新渲染,但是 useEffect 没有被调用,所以它不会正确更新。解决问题的一种方法是不在每个函数中使用 setState,而是将数据返回到另一个函数,然后像这样设置状态。

      import React, { useState, useEffect } from "react";
      import ReactDOM from "react-dom";
      
      const Search = () => {
        const [tags, setTags] = useState({
          all: [],
          available: []
        });
      
        const setAllTags = () => {
          const all = ["tag 1", "tag 2", "tag 3"];
          return all;
        };
      
        const setAvailableTags = () => {
          const available = ["tag 1", "tag 2"];
          return available;
        };
      
        useEffect(() => {
          const all = setAllTags();
          const available = setAvailableTags();
          setTags({
            all: all,
            available: available
          });
        }, []);
      
        return (
          <div>
            <div>
              <select placeholder="Tags">
                {tags.all.map((tag, i) => (
                  <option key={tag + i} value={tag}>
                    {tag}
                  </option>
                ))}
              </select>
            </div>
          </div>
        );
      };
      
      const App = () => {
        return (
          <div>
            <h1>Hello React!</h1>
            <Search />
          </div>
        );
      };
      
      const rootElement = document.getElementById("root");
      ReactDOM.render(<App />, rootElement);
      

      如果您需要从 API 获取数据,可以使用 Promise.all()。

      【讨论】:

      • const available = setAvailableTags(all); 会是更好的选择,因为我们想根据所有标签设置AvailableTags
      • 肯定是扩展场景,但不是这种场景。
      【解决方案5】:

      对于初始渲染,您的tags

      {
        all: [],
        availableTags:[]
      }
      

      所以useEffect 函数都更新这个初始对象。 并且在状态更新时不会调用您的 useEffect 函数。

      你可以像下面这样使用它们:

      const setAvailableTags = () => {
        const available = ["tag 1", "tag 2"];
        setTags(prevTags => ({...prevTags, available}));
      };
      
      useEffect(setAllTags, []);
      useEffect(setAvailableTags, [tags.all]);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-04-19
        • 2021-05-19
        • 2020-03-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-27
        • 1970-01-01
        相关资源
        最近更新 更多