反应挂钩状态不使用最新

时间:2019-03-18 12:30:46

标签: javascript reactjs react-hooks

我有以下代码,我想在其中创建标签列表。在此示例中,我要获取setAllTags()中的标签列表,然后获取setAvailableTags()中的许多可用标签。

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

有什么办法可以解决这个问题吗?

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]}

5 个答案:

答案 0 :(得分:2)

在react docs中,您可以看到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([])

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

答案 1 :(得分:1)

setTags更改内部反应状态,并且不直接更改tags的值。因此直到下一次渲染它都不会更新。

请改用此呼叫:setTags(currentTags => ({...currentTags, all}));。并对available执行相同操作。

答案 2 :(得分:0)

来自: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

答案 3 :(得分:0)

问题在于,当您将Effect与第二个参数[]一起使用时,仅在第一次呈现组件时才调用该函数。但是,因为您在其中使用了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()。

答案 4 :(得分:0)

对于初始渲染,您的tags

{
  all: [],
  availableTags:[]
}

因此,对于两个useEffect函数都将更新此初始对象。 而且状态更新不会调用您的useEffect函数。

您可以按以下方式使用它们:

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

useEffect(setAllTags, []);
useEffect(setAvailableTags, [tags.all]);