在redux reducer中更新状态的正确方法

时间:2016-03-16 09:32:39

标签: javascript reactjs redux flux

我是redux和es6语法的新手。我使用官方的redux教程和example创建我的应用程序。

下面有JS片段。我的观点 - 在帖子reducer中定义REQUEST_POST_BODY和RECEIVE_POST_BODY个案。 主要难点 - 在商店中查找和更新正确的对象。

我尝试使用示例中的代码:

  return Object.assign({}, state, {
    [action.subreddit]: posts(state[action.subreddit], action)
  })

但它使用了简单的帖子数组。不需要通过id找到正确的帖子。

这是我的代码:

  const initialState = {
    items: [{id:3, title: '1984', isFetching:false}, {id:6, title: 'Mouse', isFetching:false}]
  }

  // Reducer for posts store
  export default function posts(state = initialState, action) {
    switch (action.type) {
    case REQUEST_POST_BODY:
      // here I need to set post.isFetching => true
    case RECEIVE_POST_BODY:
      // here I need to set post.isFetching => false and post.body => action.body
    default:
      return state;
    }
  }

  function requestPostBody(id) {
    return {
      type: REQUEST_POST_BODY,
      id
    };
  }

  function receivePostBody(id, body_from_server) {
    return {
      type: RECEIVE_POST_BODY,
      id,
      body: body_from_server
    };
  }

  dispatch(requestPostBody(3));
  dispatch(receivePostBody(3, {id:3, body: 'blablabla'}));

3 个答案:

答案 0 :(得分:26)

使用数组

如果您更喜欢坚持使用数组,那么您可以编写一个仅处理单个post对象的减速器。

export default function reducePost(post, action) {
  if(post.id !== action.id) return post;

  switch(action.type) {
  case REQUEST_POST_BODY:
    return Object.assign({}, post, { isFetching: true });
  case RECEIVE_POST_BODY:
    return Object.assign({}, post, { isFetching: false, body: action.body });
  default:
    return post;
}

您的根减速器将变为:

export default function posts(state = initialState, action) {
  return state.map(post => reducePost(post, action);
}

我们只是在列表中的每个帖子上运行我们的新reducer,以返回更新的帖子数组。在这种情况下,唯一ID将确保只更改一个项目。

使用对象

如果每个项目都有唯一的字符串/数字ID,那么您可以翻转数组并使用object代替。

const initialState = {
  items: {
    3: {id:3, title: '1984', isFetching:false},
    6: {id:6, title: 'Mouse', isFetching:false}
  };
}

然后你可以简化你的减速器。

switch (action.type) {
case REQUEST_POST_BODY:
  let id = action.id;
  return Object.assign({}, state, {
    [id]: Object.assign({}, state[id], { isFetching: true })
  });
case RECEIVE_POST_BODY:
  let id = action.id;
  return Object.assign({}, state, {
    [id]: Object.assign({}, state[id], {
      isFetching: false,
      body: action.body
    })
  });
default:
  return state;
}

如果您也乐于尝试使用某些ES7语法,则可以使用Babel启用Object spread运算符并重写对Object.assign的调用。

switch (action.type) {
case REQUEST_POST_BODY:
  let id = action.id;
  return {
    ...state,
    [id]: {...state[id], isFetching: true }
  };
case RECEIVE_POST_BODY:
  let id = action.id;
  return {
    ...state,
    [id]: {
      ...state[id],
      isFetching: false,
      body: action.body
    }
  };
default:
  return state;
}

如果您不太热衷于使用点差语法,那么仍然可以使Object.assign更加可口。

function $set(...objects) {
  return Object.assign({}, ...objects); 
}
case RECEIVE_POST_BODY:
  let id = action.id;
  return $set(state, {
    [id]: $set(state[id], {
      isFetching: false,
      body: action.body
    })
  });

答案 1 :(得分:1)

如果我理解正确,您无法获得所需的特定帖子。

首先,让reducer更新数组及其中的对象,使其难以阅读和维护。我建议你看一下short video解释有关带阵列的减速器组合。 您可以使用此处描述的技术简化代码。

在您的情况下,您会使用posts减速器和post减速器,而posts减速器会调用post减速器。

至于找到合适的对象,丹·普林斯的建议使其变得更容易。 拥有一个对象图而不是一个数组会让你更容易。 Dan的回答相关代码片段:

const initialState = {
  items: {
    3: {id:3, title: '1984', isFetching:false},
    6: {id:6, title: 'Mouse', isFetching:false}
  ];
}

答案 2 :(得分:0)

我通过使用Object.assign实现了对象简化器,该方法很有效,但是随着我们项目的发展以及我们添加了许多依赖组件,它变得非常低效,渲染速度非常慢。

如果我对immer有所了解,那么我一开始就会使用它。

基本上,您按如下方式使用immer,其中示例中的layers对象如下所示:

const initialState = {
  layers: {
   'layer1': { selected: false },
   'layer2': { selected: true }
  }
}

Reducers.js(摘录)

import produce from 'immer'
import has from 'lodash/has'
export const layers = (state = null, action) => {
  switch (action.type) {
    case ADD_LAYER:
      // Using `immer.produce` only for consistency 
      // clearly the whole object changes in this case.
      return produce(state, layers => {
        // Take a copy of the prebuilt layer
        var layer = Object.assign({}, action.layer)
        // Add some defaults
        if (!has(layer, 'selected')) {
          layer.selected = false
        }
        layers[action.id] = layer
      })
    case SELECT_LAYER:
      return produce(state, layers => {
        layers[action.id].selected = true
      })
    case UNSELECT_LAYER:
      return produce(state, layers => {
        layers[action.id].selected = false
      })
    default:
      return state
  }
}

Actions.js(摘录)

export const addLayer = id => ({
  type: ADD_LAYER,
  id
})

export const selectLayer = id => ({
  type: SELECT_LAYER,
  id
})

export const unSelectLayer = id => ({
  type: UNSELECT_LAYER,
  id
})

参考文献:

https://github.com/immerjs/immer

https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns