Правильный способ обновления состояния в редукторах redux

Я новичок в синтаксисе redux и es6. Я делаю свое приложение с официальным учебником по сокращению, и с этим example.

Ниже приведен фрагмент JS. Моя задача - определить 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'}));

Ответ 1

С массивами

Если вы предпочитаете придерживаться массивов, то вы можете написать редуктор, который только Снасти одиночных 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);
}

Мы просто запускаем наш новый редуктор для каждого поста в списке, чтобы вернуть обновленный массив постов. В этом случае уникальный идентификатор гарантирует, что будет изменен только один элемент.

С объектами

Если у каждого элемента есть уникальный идентификатор строки/числа, вы можете перевернуть массив и использовать вместо него 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.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
    })
  });

Ответ 2

Если я правильно понимаю, у вас возникли проблемы с получением конкретной должности, которую вы хотите.

Прежде всего, если ваш редуктор также обновляет массив и объект в нем, он затрудняет чтение и обслуживание. Я предлагаю вам посмотреть короткое видео , объясняющее состав редуктора с массивами. Вы можете упростить свой код, используя описанную там технику.

В вашем случае вы бы использовали редуктор posts и редуктор post, а редуктор posts вызывал редуктор post.

Что касается поиска правильного объекта для работы, предложение Дэна Принца облегчает его. Наличие карты объектов вместо массива облегчит вам задачу. Соответствующий фрагмент кода из ответа Дэна:

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