【问题标题】:Right way to update state in redux reducers在 redux reducer 中更新状态的正确方法
【发布时间】:2016-07-02 02:11:57
【问题描述】:

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

下面有JS sn-p。我的观点 - 在 post 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'}));

【问题讨论】:

  • 如果ids 总是唯一的,最好将items 设为对象而不是数组。然后你可以使用items[id]通过id找到。

标签: javascript reactjs redux flux


【解决方案1】:

带数组

如果您更喜欢使用数组,那么您可以编写一个只处理单个 post 对象的 reducer。

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;
}

你的根 reducer 会变成:

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

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

有对象

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

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

然后你可以简化你的 reducer。

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
    })
  });

【讨论】:

  • 简单又好看!谢谢!所以,存储数据结构有点不同,我必须改变我的组件渲染方法。代码{this.props.data.map((item, id) => <PostItem /> 引发错误:TypeError: this.props.data.map is not a function at render 在这里迭代新对象(而不是数组)的正确方法是什么?
  • 应该是这样的:for (let [id, item] of Object.entries(this.props.data)) {
  • 这行得通,或者您也可以对键进行迭代。 Object.keys(this.props.data).map(k => <PostItem post={this.props.data[k]} />.
  • 如果您使用 Lodash 或 Underscore,mapfilter 之类的函数会迭代对象自身的属性以及数组,这在这种情况下非常有用。跨度>
  • 在 REQUEST_POST_BODY 的情况下,"...state" 后面应该有一个逗号(",")。
【解决方案2】:

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

首先,让你的 reducer 也更新数组和其中的对象,使其难以阅读和维护。我建议你看这个short video 解释关于数组的reducer 组合。 您可以使用此处描述的技术来简化您的代码。

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

至于找到合适的工作对象,Dan Prince 的建议让事情变得更容易了。 拥有一个对象映射而不是一个数组会让你更容易。丹的回答中的相关代码sn-p:

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

【讨论】:

    【解决方案3】:

    我几乎使用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

    【讨论】:

      猜你喜欢
      • 2019-07-05
      • 2019-07-28
      • 2018-08-04
      • 1970-01-01
      • 2017-11-27
      • 2021-01-08
      • 2020-10-21
      • 2021-03-03
      • 1970-01-01
      相关资源
      最近更新 更多