useState挂钩是否更改状态的值

时间:2019-11-16 18:19:11

标签: reactjs

我刚刚启动React,在此项目列表教程中,我对更新项目状态有一些疑问。另外,我正在使用功能组件。因此在app.js中

const [items, setItems] = useState([
  {
    id: 1,
    title: 'Banana',
    bought: false
  },
 ...
])

然后我在app.js中有一个函数,当我选中一个复选框时会将购买的商品更新为true或false

// The id is passed in from Item.js down below
const markBought = (id) => {
  setItems(
    items.map(
     item => {
      if (item.id === id) {
        /// If bought is false, checking it will make it true and vice versa
        item.bought = !item.bought; // (1)
      }
      return item; // (2)
    })
  );
};
return (
  <div className="App">
    <Items items={items} markBought={markBought}></Items>
  </div>
);

老师说我们正在使用一种叫做“组件钻探”的方法。因此,在Items.js中,我们会映射每个项目以逐一显示它们,但我认为不必显示它。

最后在Item.js中

<input type="checkbox" onChange={() => props.markBought(props.item.id)} />
{props.item.title}

该应用程序运行良好,但对我来说有点令人困惑。所以:

  1. 在app.js中,更改购买状态后,是否也不需要返回商品,如果条件为假,我们是否也需要返回商品?为什么只在错误的情况下退货,而在正确的情况下我们只退货而没有退货?
  2. 我读到map不会修改数组,所以markBought函数应该创建一个新的items数组,已经进行了修改,但是这个数组会发生什么,React如何知道将其“支持”到item.js,而不是我的硬编码?

很抱歉,如果这有点长,我们将不胜感激。感谢您的阅读

3 个答案:

答案 0 :(得分:0)

您好,欢迎来到Stackoverflow。

  1. 您总是返回item。您只有一个if语句,它将更改购买状态,即使以上条件为false,也将返回item,这是正确的方法。

  2. Map确实不会修改数组,但会返回一个新数组。如果要获取该返回数组,则可以执行以下操作:

const myNewArray = items.map(...)

这个新的array到达您的其他组件的方式是因为这个新的array被赋予了您的useState()。 您看到setItems()吗?这将设置您的状态,并且Item.js将自动更新。 那真是太棒了。 state的所有组件将在此state更新后进行更新。

答案 1 :(得分:0)

您的地图函数总是返回一个项目。如果您要修改的项目与当前正在映射的项目的ID相匹配,它只会首先修改该项目。 Map会返回一个新的项目数组(即使它不做任何更改),这会使useState看到一个新值。默认情况下,在React中,检查更新不是很聪明-只是检查是否oldValue === newValue

对于诸如字符串之类的基元,只要两个对象的值匹配,对象相等性测试就会返回true。

"foo" === "foo"  // => true

但是,这不适用于对象或数组。包含相同值的两个不同数组将进行相等比较(因为Javascript不比较它们的内容,而是对象的ID):

["foo"] === ["foo"]  // => false

因此,当您map items时,您将获得一个新的数组对象(因为调用:map将回调函数的返回值收集到一个新的数组中),这将从不匹配items的先前值,因此每次调用setItems都会使React说“ hm,我先前的items与该对象不同我的新items,我必须重新渲染此组件”。

答案 2 :(得分:0)

您正在对地图中的项目进行突变,如果您将Item组件优化为纯组件,则由于发生突变,该组件将不会重新呈现。请尝试以下操作:

//use useCallback so marBought doesn't change and cause
//  needless DOM re renders
const markBought = useCallback(id => {
  setItems((
    items //pass callback to the setter from useState
  ) =>
    items.map(
      item =>
        item.id === id
          ? { ...item, bought: !item.bought } //copy item with changed value
          : item //not this item, just return the item
    )
  );
}, []);

这是一个完整的例子:

const { useCallback, useState, useRef, memo } = React;
function Items() {
  const [items, setItems] = useState([
    {
      id: 1,
      title: 'Banana',
      bought: false,
    },
    {
      id: 2,
      title: 'Peach',
      bought: false,
    },
  ]);
  const toggleBought = useCallback(id => {
    setItems((
      items //pass callback to the setter from useState
    ) =>
      items.map(
        item =>
          item.id === id
            ? { ...item, bought: !item.bought } //copy item with changed value
            : item //not this item, just return the item
      )
    );
  }, []);
  return (
    <div>
      {items.map(item => (
        <Item
          key={item.id}
          item={item}
          toggleBought={toggleBought}
        />
      ))}
    </div>
  );
}
//use memo to make Item a pure component
const Item = memo(function Item({ item, toggleBought }) {
  const renderedRef = useRef(0);
  renderedRef.current++;
  return (
    <div>
      <div>{item.title}</div>
      <div>bought: {item.bought ? 'yes' : 'no'}</div>
      <button onClick={() => toggleBought(item.id)}>
        toggle bought
      </button>
      <div>Rendered: {renderedRef.current} times</div>
    </div>
  );
});

//render the application
ReactDOM.render(<Items />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

这是一个残破的示例,其中您更改了项目并且即使状态确实发生了变化也看不到重新渲染:

const { useCallback, useState, useRef, memo } = React;
function Items() {
  const [items, setItems] = useState([
    {
      id: 1,
      title: 'Banana',
      bought: false,
    },
    {
      id: 2,
      title: 'Peach',
      bought: false,
    },
  ]);
  const toggleBought = useCallback(id => {
    setItems((
      items //pass callback to the setter from useState
    ) =>
      items.map(
        item =>
          item.id === id
            ? ((item.bought = !item.bought),item) //mutate item
            : item //not this item, just return the item
      )
    );
  }, []);
  return (
    <div>
      <div>
        {items.map(item => (
          <Item
            key={item.id}
            item={item}
            toggleBought={toggleBought}
          />
        ))}
      </div>
      <div>{JSON.stringify(items)}</div>
    </div>
  );
}
//use memo to make Item a pure component
const Item = memo(function Item({ item, toggleBought }) {
  const renderedRef = useRef(0);
  renderedRef.current++;
  return (
    <div>
      <div>{item.title}</div>
      <div>bought: {item.bought ? 'yes' : 'no'}</div>
      <button onClick={() => toggleBought(item.id)}>
        toggle bought
      </button>
      <div>Rendered: {renderedRef.current} times</div>
    </div>
  );
});

//render the application
ReactDOM.render(<Items />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>