当我必须遍历和查找/删除状态下的项目时如何安全地更新状态

时间:2018-09-05 00:58:18

标签: reactjs react-redux

我需要修改我的状态,但不确定如何正确执行操作。

我在我的状态下的帐户属性如下:

{     
 "account":{  
    "id":7,    
    "categories":[  
       {  
          "id":7,          
          "products":[  
             {                  
                "productId":54                
             }
          ]
       },
       {  
          "id":9,          
          "products":[  
             {                  
                "productId":89                
             }
          ]
       }
    ]
 }
}

我的动作调度了以下内容:

dispatch({
  type: Constants.MOVE_PRODUCT,
  productId: 54,
  sourceCategoryId: 7,
  targetCategoryId: 9
});

现在我的减速器骨架是:

const initialState = {
  account: null,
};


const accounts = (state = initialState, action) => {
  switch (action.type) {
    case Constants.MOVE_PRODUCT:
      /*
      action.productId
      action.sourceCategoryId
      action.targetCategoryId
      */

      const sourceCategoryIndex = state.account.categories.findIndex((category) => { return category.id === action.sourceCategoryId; });
      const sourceCategory = state.account.categories[sourceCategoryIndex];

      const targetCategoryIndex = state.account.categories.findIndex((category) => { return category.id === action.targetCategoryId; });
      const targetCategory = state.account.categories[targetCategoryIndex];

    // ??

      return {...state};
  }
}

export default accounts;

我很困惑,如果我直接在switch块内部更新状态,那是错误的吗?

是否必须是单线更新,该突变是否就位?或者只要我在switch块中进行就可以了?

更新

从操作中,我需要从sourceCategoryId中删除productId并将其添加到帐户状态对象内部的targetCategoryId中。

4 个答案:

答案 0 :(得分:1)

是的,您不应该在减速器中执行state.foo = 'bar'。来自redux docs

  

我们不会改变状态。我们使用Object.assign()创建一个副本。 Object.assign(state, { visibilityFilter: action.filter })也是错误的:它将改变第一个参数。您必须提供一个空对象作为第一个参数。您还可以启用对象散布运算符建议来改为编写{ ...state, ...newState }

所以减速器看起来像

function accountsReducer (state = initialState, { sourceCategoryId, productId }) {
  const targetProduct = state.categories
    .find(({ id }) => id === sourceCategoryId)
    .products
    .find(({ id }) => id === productId);

  switch (action.type) {
    case Constants.MOVE_PRODUCT:
      return {
        ...state,
        categories: state.categories.reduce((acc, cat) => {
          return cat.id !== sourceCategoryId
            ? {
                ...acc,
                cat: { ...cat, products: cat.products.filter(({ id }) => id !== productId) }
              }
            : {
                ...acc,
                cat: { ...cat, products: [...cat.products, targetProduct] }
              }
        }, {});
      };
  }
}

但这很痛苦...您应该尝试normalize your data成平面阵列。

答案 1 :(得分:0)

// first, let's clean up the action a bit
// type and "payload". I like the data wrapped up in a bundle with a nice
// bow on it. ;) If you don't like this, just adjust the code below. 
dispatch({
  type: Constants.MOVE_PRODUCT,
  payload: { 
      product: { productId: 54 }
      sourceCategoryId: 7,
      targetCategoryId: 9
  }
});

// destructure to get our id and categories from state
const { id, categories } = state

// map the old categories to a new array
const adjustedCategories = categories.map(cat => {
    // destructure from our payload
    const { product, sourceCategoryId, targetCategoryId } = action.payload

    // if the category is the "moving from" category, filter out the product
    if (cat.id === sourceCategoryId) {
        return { id: cat.id, products: [...cat.products.filter(p => p.productId !== product.productId)
    }

    // if the category is our "moving to" category, use the spread operator and add the product to the new array
    if (cat.id === targetCategoryId) {
        return { id: cat.id, products: [...cat.products, product] }
    }
)

// construct our new state
return { id, categories: adjustedCategories }

此解决方案使函数保持纯净,应为您提供所需的内容。它未经测试,因此可能并不完美。

答案 2 :(得分:0)

您可以采用以下方法:

const accounts = (state = initialState, action) => {
  switch (action.type) {
    case Constants.MOVE_PRODUCT:

      // Extract action parameters
      const { productId, sourceCategoryId, targetCategoryId } = action

      // Manually "deep clone" account state
      const account = {
        id : state.account.id,
        categories : state.account.categories.map(category => ({          
          id : category.id,
          products : category.products.map(product => ({ productId : product.productId })
        }))
      }

      // Extract source and target categories
      const sourceCategory = account.categories.find(category => category.id === sourceCategoryId);
      const targetCategory = account.categories.find(category => category.id === targetCategoryId);

      if(sourceCategory && targetCategory) {

        // Find product index
        const index = sourceCategory.products.findIndex(product => (product.productId === action.productId))
        if(index !== -1) {

          const product = sourceCategory.products[index]

          // Remove product from source category
          sourceCategory.products.splice(index, 1) 

          // Add product to target category
          targetCategory.products.splice(index, 0, product) 
        }
      }

      return { account };
  }
}

答案 3 :(得分:0)

这是一个丑陋的解决方案:)

const accounts = (state = initialState, action) => {
  switch (action.type) {
    case Constants.MOVE_PRODUCT:
      const sourceCategoryIndex = state.account.categories.findIndex(
        el => el.id === action.sourceCategoryId
      );

      const targetCategoryIndex = state.account.categories.findIndex(
        el => el.id === action.targetCategoryId
      );

      const sourceCategory = state.account.categories.find(
        el => el.id === action.sourceCategoryId
      );

      const targetCategory = state.account.categories.find(
        el => el.id === action.targetCategoryId
      );

      const itemToMove = sourceCategory.products.find(
        el => el.productId === action.productId
      );

      const newSourceCategory = {
        ...sourceCategory,
        products: sourceCategory.products.filter(
          el => el.productId !== action.productId
        )
      };

      const newTargetCategory = {
        ...targetCategory,
        products: [...targetCategory.products, itemToMove]
      };

      const newCategories = Object.assign([], state.account.categories, {
        [sourceCategoryIndex]: newSourceCategory,
        [targetCategoryIndex]: newTargetCategory
      });

      return { ...state, account: { ...state.account, categories: newCategories } };
  }
};

Phew :)作为一个学习者,对我来说非常好:)但是,我喜欢@Daniel Lizik的方法,使用reduce

这是工作示例:

const action = {
  productId: 54,
  sourceCategoryId: 7,
  targetCategoryId: 9,
}

const state = {     
 "account":{  
    "id":7,    
    "categories":[  
       {  
          "id":7,          
          "products":[  
             {                  
                "productId":54,             
             },
             {                  
                "productId":67,             
             },
          ]
       },
       {  
          "id":9,          
          "products":[  
             {                  
                "productId":89,               
             }
          ]
       }
    ]
 }
};

const sourceCategoryIndex = state.account.categories.findIndex( el => el.id === action.sourceCategoryId );
const targetCategoryIndex = state.account.categories.findIndex( el => el.id === action.targetCategoryId );

const sourceCategory = state.account.categories.find( el => el.id === action.sourceCategoryId );
const targetCategory = state.account.categories.find( el => el.id === action.targetCategoryId );

const itemToMove = sourceCategory.products.find( el => el.productId === action.productId );

const newSourceCategory = {...sourceCategory, products: sourceCategory.products.filter( el => el.productId !== action.productId ) };

const newTargetCategory = { ...targetCategory, products: [ ...targetCategory.products, itemToMove ] };

const newCategories = Object.assign([], state.account.categories, {             [sourceCategoryIndex]: newSourceCategory,
   [targetCategoryIndex]: newTargetCategory }
);

const newState = { ...state, account: { ...state.account, categories: newCategories } };

console.log( newState );