useEffect不会针对依赖项数组中的每个更改运行

时间:2020-03-24 12:30:46

标签: reactjs react-hooks

当我更新费用时,它不会触发useEffect将数据存储在本地存储中。

codesandbox

import React, { useState, useEffect } from 'react';

export const ExpenseContext = React.createContext();

const ExpenseState = (props) => {
   const [state, setState] = useState(
      () => JSON.parse(localStorage.getItem('expenses')) || []
);

useEffect(() => {
    console.log('didUpdate', state)
    state.length !== 0 && localStorage.setItem('expenses', JSON.stringify(state));
}, [state]) 

function addExpense({id, name, category, value, note, date}){
    const expense = state.find(exp => exp.id === id);
    if(!expense){
        setState(state => ([...state, {id, name, category, value, note, date}]))
    }else{
        const updatedExpense = state.map(exp => {
                if(exp.id === expense.id){
                    return {
                        ...exp,
                        ...{id, name, category, value, note, date}
                    }
                }else{
                    return exp
                }
            })

        setState(updatedExpense)
    }
    console.log(state)
}



return (
    <ExpenseContext.Provider
        value={{
            expenses: state,
            addExpense: addExpense,
        }}
    >
        {props.children}
    </ExpenseContext.Provider>
)
}

我正在为数组的每个元素调用addExpense。假设我们有4个费用存储在存储中。现在,我们更新了一项费用,然后运行以下代码。

for(const expense of expenses){
        expenseContext.addExpense(expense);
    }

现在,使用效果仅针对最后一个元素触发,费用也不会在上下文中更新。

2 个答案:

答案 0 :(得分:0)

您的代码正在运行,我不确定您是否只是缺少一些东西?

1。export default ExpenseState丢失了吗?

2。ExpenseState上下文包装您的应用

假设您具有默认的 CRA 设置:

// index.js
import React from "react"
import ReactDOM from "react-dom"
import ExpenseState from "./AppContext" // or where your Context

import App from "./App"

const rootElement = document.getElementById("root")
ReactDOM.render(
  <ExpenseState>
    <App />
  </ExpenseState>,
  rootElement
)

和您的 App.js

// App.js
import React, { useContext } from "react"
import { ExpenseContext } from "./AppContext"

export default () => {
  const { expenses, addExpense } = useContext(ExpenseContext)

  const handleExpense = () => {
    addExpense({
      id: 1, // Change ID to add another object
      name: "some name",
      category: "some category", // keep the id and change some values for update
      value: "some value",
      note: "some note",
      date: "some date"
    })
  }

  return (
    <div className="App">
      <button onClick={handleExpense}>Add Expenses</button>
      <pre>
        <code>{JSON.stringify(expenses, null, 2)}</code>
      </pre>
    </div>
  )
}

有效的codeSandBox示例。

答案 1 :(得分:0)

问题在于useState的工作方式与您认为的不同。在使用者内部,您连续三次调用api.addExpense(...),同时期望调用setState会就地更新state-不会,状态只会更新下一次渲染调用,而不是第40行,因为您的console.log暗示您期望如此。

因此,除了您期望的三个完整状态更新(每个expense一个状态更新)之外,您只能从{id: "d6109eb5-9d7b-4cd3-8daa-ee485b08361b", name: "book", category: "personal care", value: 200}的最新更新中获取状态,该状态没有更改,仍然基于原始状态,而不是第一项的值设置为800的状态。

我建议您修改您的ExpenseState api以花费一系列费用,以便它可以适当地处理多个内部更新。


  function addOrUpdateExpense(state, { id, name, category, value }) {
    const expense = state.find(exp => exp.id === id);
    if (!expense) {
      return [...state, { id, name, category, value }];
    } else {
      const updatedExpense = state.map(exp => {
        if (exp.id === expense.id) {
          return {
            ...exp,
            ...{ id, name, category, value }
          };
        } else {
          return exp;
        }
      });

      return updatedExpense;
    }
  }

  function addExpenses(expenses) {
    let newState = [...state];
    for (const expense of expenses) {
      newState = addOrUpdateExpense(newState, expense);
    }
    setState(newState);
  }

  // ...usage

  function doIt() {
    api.addExpenses(expenses);
  }

这是一个常见的误解,请记住,下一个状态仅在下一个渲染阶段异步可用,将useState中的状态视为在渲染过程中是不可变的。