开发者问题收集

如何更新对象数组中的对象

2022-05-23
658

我有以下部分 React 代码: 这是用于将新对象项添加到数组的处理程序。

因此,我正在检查对象是否存在于整个对象数组中。如果是,我想将此对象的数量增加 1。否则,我想添加对象并将数量设置为 1。

当我想添加第二个相同的产品(相同的 ID)时,我收到错误

Uncaught TypeError: Cannot assign to read only property 'quantity' of object '#<Object>'
export const Component = ({ book }: { book: Book }) => {
    const [basket, setBasket] = useRecoilState(Basket);

    const handleAddBookToBasket = () => {

        const find = basket.findIndex((product: any) => product.id === book.id)

        if (find !== -1) {
            setBasket((basket: any) => [...basket, basket[find].quantity = basket[find].quantity + 1])
        } else {
            setBasket((basket: any) => [...basket, { ...book, quantity: 1 }])
        }
    }

编辑:

if (find !== -1) {
  setBasket((basket: any) =>
    basket.map((product: any) => ({
      ...product,
      quantity: product.quantity + 1,
    }))
  );
} else {
  setBasket((basket: any) => [...basket, { ...book, quantity: 1 }]);
}
2个回答

数据结构

我认为您的“问题”的根源在于您为购物篮选择了错误的数据结构。使用 Array<Item> 意味着每次您需要更新特定商品时,您都必须执行 O(n) 线性搜索。

如果将购物篮更改为 { [ItemId]: Item } ,则可以执行即时 O(1) 更新。请参阅下面的完整示例。单击某些产品以将其添加到您的购物篮中。在输出中检查更新后的购物篮数量。

function App({ products = [] }) {
  const [basket, setBasket] = React.useState({})
  function addToBasket(product) {
    return event => setBasket({
      ...basket,
      [product.id]: {
        ...product,
        quantity: basket[product.id] == null
          ? 1
          : basket[product.id].quantity + 1
      }
    })
  }
  return <div>
    {products.map((p, key) =>
      <button key={key} onClick={addToBasket(p)} children={p.name} />
    )}
    <pre>{JSON.stringify(basket, null, 2)}</pre>
  </div>
}

const products = [
  { id: 1, name: "ginger" },
  { id: 2, name: "garlic" },
  { id: 3, name: "turmeric" }
]

ReactDOM.render(<App products={products}/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

对象更新

作为一种良好做法,您可以创建一个用于不可变对象更新的函数。

// Obj.js

const update = (o, key, func) =>
    ({ ...o, [key]: func(o[key]) })

export { update }

然后在需要此行为的地方将其 import 。请注意,也可以在嵌套对象更新中使用。

// App.js

import * as Obj from "./Obj"

function App({ products = [] }) {
  // ...
  function addToBasket(product) {
    return event => setBasket(
      Obj.update(basket, product.id, (p = product) =>  // ✅
        Obj.update(p, "quantity", (n = 0) => n + 1)    // ✅
      )
    )
  }
  // ...
}

对象移除

您可以使用类似的技术从购物篮中 移除 一个项目。不要将行为直接耦合到需要移除的组件中,而是将其添加到 Obj 模块中。

// Obj.js

const update = (o, key, func) =>
    ({ ...o, [key]: func(o[key]) })

const remove: (o, key) => { // ✅
  const r = { ...o }
  delete r[key]
  return r
}

export { update, remove } // ✅

现在,您可以在任何需要它的组件中 导入 移除 行为。

function App() {
  const [basket, setBasket] = React.useState({
    1: { id: 1, name: "ginger", quantity: 5 },
    2: { id: 2, name: "garlic", quantity: 6 },
    3: { id: 3, name: "tumeric", quantity: 7 },
  })
  function removeFromBasket(product) {
    return event => {
      setBasket(
        Obj.remove(basket, product.id) // ✅
      )
    }
  }
  return <div>
    {Object.values(basket).map((p, key) =>
      <div key={key}>
        {p.name} ({p.quantity})
        <button onClick={removeFromBasket(p)} children="❌" />
      </div>
    )}
    <pre>{JSON.stringify(basket, null, 2)}</pre>
  </div>
}

// inline module for demo
const Obj = {
  remove: (o, key) => {
    const r = {...o}
    delete r[key]
    return r
  }
}

ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
よつば
2022-05-23

在您的 setBasket 中,您应该创建一个新对象,而不是更新当前对象

您的代码应该看起来像这些:

{...basket, ...{
quantity : basket.quantity + 1
}}
OrcusZ
2022-05-23