Redux может редуктор принимать несколько действий - PullRequest
0 голосов
/ 10 февраля 2019

Оба AddToCart и RemoveFromCart действия обновляют num и qty, в то время как оба HandleClick и InputQuantity действия обновляют qty.

У меня проблема с этими двумя строками кода:

num: productsReducer(state.productsReducer.num, AddToCart)

qty: productsReducer(state.productsReducer.qty, AddToCart)

, где значение свойства или количество обновляется только действием AddToCart.

Остальные 3 действия - RemoveFromCart, HandleClick, InputQuantity - не обновляют значение количества или количества.И я понятия не имею, как разрешить вышеуказанные действия обновлять свойства num и qty.

Как писать productsReducer или мои действия так, чтобы num и qty оба обновлялись с помощью AddToCart и RemoveFromCart, тогда как qty обновлялось с помощью HandleClick иInputQuantity?

Нужен ли редуктор высшего порядка для выполнения нескольких действий?Пожалуйста, помогите.

CartContainer

// Both AddToCart and RemoveFromCart actions update num and qty
// Both HandleClick and InputQuantity actions update qty

const mapStateToProps = state => ({
  num: productsReducer(state.productsReducer.num, AddToCart),
  qty: productsReducer(state.productsReducer.qty, AddToCart),
  products: state.productsReducer.products
});

ПродуктыРедуктор

import * as actionTypes from '../actions/actions';

const initialState = {
  products: [],
  num: [],
  qty: [],
  totalPrice: 0,
  status: ''
};

const productsReducer = (state = initialState, action) => {
  let updatedNum = state.num;
  let updatedQty = state.qty;
  let index;

  if (action.num !== undefined) {
    index = updatedNum.indexOf(action.num);
  }

  switch (action.type) {
    case actionTypes.FETCH_PRODUCTS_REQUEST:
      return {
        ...state,
        status: action.status
      }

    case actionTypes.FETCH_PRODUCTS_SUCCESS:
      return {
        ...state,
        status: action.status,
        products: action.products
      }

    case actionTypes.ADD_TO_CART:
      // if num array doesn't have that element, insert that element
      if (!state.num.includes(action.num)) {
        updatedNum = state.num.concat(action.num); 
        updatedQty = state.qty.concat(1);

        return {
          ...state,
          num: updatedNum,
          qty: updatedQty
        }
      // if element is present in array already, increase qty of it by 1
      } else {
        updatedQty = state.qty.slice(0);
        updatedQty[index] += 1;

        return {
          ...state,
          qty: updatedQty
        }
      }

      // After updating store, use CalculatePrice action to calculate updated price.
      this.calculateTotalPrice(updatedNum, updatedQty);

    case actionTypes.REMOVE_FROM_CART:
      // remove clicked item with splice, and update state
      updatedNum.splice(index, 1);
      updatedQty.splice(index, 1);

      return {
        ...state,
        num: updatedNum,
        qty: updatedQty
      }

    case actionTypes.HANDLE_CLICK:
      let event = action.eventType;
      console.log(event);

      if (event === 'plus') {
        updatedQty[index] += 1;
      } else if (event === 'minus') {
        updatedQty[index] -= 1;
      }

      return {
        ...state,
        qty: updatedQty 
      }

    case actionTypes.INPUT_QUANTITY:
      const regex = /^[0-9\b]+$/;

      if (regex.test(action.event.target.value)) {
        updatedQty[index] = Number(action.event.target.value);

        return {
          ...state,
          qty: updatedQty 
        }
      }

    case actionTypes.CALCULATE_PRICE:
      let arr = [];
      console.log('updatedNum', action.num);

      action.num.map(num => {
        let index = num - 1;
        arr.push(action.qty[index] * state.products[index].price);
      });

      if (arr.length > 0) {
        let total = arr.reduce((acc, currentVal) => acc + currentVal);
        return { 
          totalPrice: total 
        }
      } 

      return { 
        totalPrice: 0 
      }

    default: 
      return state;
  }
}

export default productsReducer;

Ответы [ 2 ]

0 голосов
/ 23 февраля 2019

Вы нарушаете правило для редукторов.Правило, которое говорит, не должно изменять свой аргумент входного состояния.Что вы делаете здесь:

const productsReducer = (state = initialState, action) => {
  let updatedNum = state.num;
  let updatedQty = state.qty;
  let index;

и даже дальше по строкам кода для этого productsReducer.Теперь правило немного вводит в заблуждение и несколько неверно, и я могу понять, что я имею в виду, но, учитывая сказанное, имейте в виду, что мы не должны изменять этот входной аргумент state.

Так, например, всеэто плохо:

export default (state, action) => {
  state[0] = ‘Dan’
  state.pop()
  state.push()
  state.name = ‘Dan’
  state.age = 30;
};

Если ваша функция редуктора имеет state и = где-то внутри нее.Вы нарушаете это правило редуктора и, как следствие, у вас возникают проблемы с вашим приложением.

Поэтому я думаю, что для того, чтобы устранить вашу ошибку здесь, нам нужно понять это правило:

Почему мой компонент не выполняет повторный рендеринг или у меня не запущен mapStateToProps? Случайное изменение или изменение вашего состояния напрямую является самой распространенной причиной, по которой компоненты не выполняют повторный рендеринг после отправки действия

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

Причина, по которой документы Redux говорят не изменятьаргумент состояния заключается в том, что новичку легче сказать, что он не может изменить это состояние, чем сказать им, когда они могут и не могут изменить это состояние.

Чтобы было ясно, мы все равно должны следовать официальным документам Redux.не мутировать state аргумент никогда.Мы, безусловно, можем, и вы, конечно, сделали, но вот почему вы должны следовать тому, что говорит Redux Docs.

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

Давайте рассмотрим исходный код самой библиотеки Redux, в частности фрагмент, который начинается в строке 162 или около того.Вы можете проверить это по адресу: https://raw.githubusercontent.com/reduxjs/redux/master/src/combineReducers.js

Это строка, которая начинается с let hasChanged = false

let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }

Изучите этот блок кода, чтобы действительно понять, что это говорит:

случайные мутации предотвращают повторную визуализацию после отправки действия

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

Поэтому, когда бы мы ни отправляли действие, приведенный выше код Redux будет выполняться.

Я проведу вас через этот код шаг за шагом иполучить лучшее представление о том, что происходит, когда вы отправляете действие.

Первое, что происходит, мы устанавливаем переменную hasChanged и устанавливаем ее равной false.

Затеммы вводим цикл for, который будет перебирать все различные редукторы внутри вашего приложения.

Внутри тела цикла for есть переменная с именем previousStateForKey

Thпеременной будет присвоено последнее значение состояния, которое вернул ваш редуктор.

Каждый раз, когда редуктор вызывается, первый аргумент будет state, который он возвратил при последнем запуске.

Таким образом, previousStateForKey является ссылкой на предыдущее значение state, которое вернул конкретный редуктор.Следующая строка - это место, где вызывается редуктор.

Первый аргумент - state, который ваш редуктор возвратил в последний раз, previousStateForKey, а затем второй аргумент - объект action.

Ваш редуктор будет работать, а затем в конечном итоге повернет новое значение state, которое будет присвоено nextStateForKey.Таким образом, у нас есть hasChanged, previousStateForKey и nextStateForKey, что является нашим новым значением state.

Сразу после того, как ваш редуктор запустится и назначит это новое значение state для nextStateForKey, Redux будетпроверьте, вернул ли ваш редуктор значение undefined, что произойдет, когда наши редукторы будут впервые инициализированы, и Redux проверит это с помощью оператора if здесь:

if (typeof nextStateForKey === 'undefined') {
  const errorMessage = getUndefinedStateErrorMessage(key, action)
  throw new Error(errorMessage)
}

Это одно из главных правил использования редукторов: редуктор никогда не может вернуть значение undefined.Итак, с этим оператором if Redux спрашивает, они вернули undefined?Если это так, выведите это сообщение об ошибке.

Если вы пройдете эту проверку, все станет еще интереснее в этой строке кода:

hasChanged = hasChanged || nextStateForKey !== previousStateForKey

hasChanged собирается взятьзначение прямого сравнения между nextStateForKey и вашим previousStateForKey.

Таким образом, правило не изменять ваш аргумент state сводится к приведенной выше строке кода.

Что это сравнениеОн проверяет, являются ли nextStateForKey и previousStateForKey точно такими же массивами или объектами в памяти.

Так что, если вы только что вернули какой-то массив и его точно такой же массив в памяти из последнего разатогда редуктор побежит, тогда hasChanged будет иметь значение false, иначе, если ваши редукторы вернули совершенно новый массив, созданный в вашем редукторе, и этот массив полностью отличается от массива в последний раз, когда ваш редуктор работал, тогда hasChanged будет равенв true.

Значит, эта переменная hasChanged сообщает, изменилось ли какое-либо состояние, возвращаемое этим редуктором?

Таким образом, цикл for будет повторяться для всех ваших редукторов.и hasChanged будет либо true, либо false, учитывая все те различные редукторы, которые вы ему передали.

Заметьте, как существует только одна переменная hasChanged?Существует не один для каждого редуктора.Если какой-либо редуктор изменил значение или вернул другое значение, массив или объект или другое значение для целого числа, числа или строки, hasChanged будет равно true.

После forпетля, все еще становится интереснее.Мы смотрим на значение hasChanged, и если оно было изменено на true, результатом всей функции будет возвращение нового объекта state.Весь новый объект state собран всеми вашими различными редукторами, в противном случае, если hasChanged равен false, мы вместо этого возвращаем state, а state является ссылкой на все state, которые вернули ваши редукторыпоследний раз, когда они запускались.

Итак, этот фрагмент кода из Redux проверяет, возвращал ли кто-нибудь из ваших редукторов после запуска ваших редукторов совершенно новый массив, объект, число или строку, если они это сделали.тогда Redux вернет совершенно новый результат от всех ваших редукторов, в противном случае, если ваши редукторы не вернут нового значения, он вернет старое состояние из ваших редукторов.

Какое значение здесь?

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

Если у вас есть новый state, если у вас естьмутировав state из одного из ваших редукторов, и вы вернете nextState, Redux посмотрит на этот объект и скажет: о, у нас есть какая-то новая форма состояния, все этиразличные редукторы, и он будет уведомлять остальную часть вашего приложения, включая ваше приложение React, о том, что у вас есть новое доступное состояние, и это заставит ваше приложение React повторно выполнить рендеринг.

Таким образом, причина, по которой вы не должны изменятьваш аргумент состояния в редукторе не потому, что вы не можете его мутировать, причина в том, что если вы случайно вернете то же значение, которое накачано в ваш редуктор, если вы скажете return state; в вашем редукторе, и он все тот же объект илиМассив, измененный или нет, Redux не скажет никакой разницы, это тот же объект или массив в памяти, ничего не изменилось, и поэтому мы не обновляли данные внутри приложения, и приложению React не нужно отображать себя иВот почему вы не увидите обновленного счетчика на экране.Вот что происходит здесь.

Вот почему документы Redux говорят вам не изменять аргумент state в редукторе.Если вы вернете аргумент state, внося в него изменения или нет, если вы вернете то же значение в форме return state;, то в ваше приложение не будет внесено никаких изменений.

Это то, чем занимается Redux docsпытается передать с этим правилом, не мутировать state аргумент.

Гораздо проще сказать вам не изменять аргумент state, как это происходит в документах Redux, чем пройти через огромный ответ, который я только что дал вам.

0 голосов
/ 10 февраля 2019

поэтому я дам вам два ответа, первый из которых в основном объясняет причину, по которой только AddToChart обновляет реквизиты, но не другие действия, а второй - в основном набор предложений, которые вы должны реализовать

1) Почему только AddToChart обновляет реквизит (состояние приращения)

Ниже приведена цитата из официальной документации Redux, которая также относится к вам

Почему мой компонент не выполняет повторный рендеринг или мой mapStateToProps не работает? Случайное изменение или изменение вашего состояния напрямую является наиболее распространенной причиной, по которой компоненты не выполняют повторный рендеринг после отправки действия

Важной частью этой цитаты является

Случайное изменение или изменение вашего состояния напрямую

Давайте посмотрим, как это относится к вам

В вашем initialState у вас есть следующие значения

const initialState = {
  num: [],
  qty: []
};

поля num и qty являются массивами.

В начале васr productReducers, у вас также есть следующая строка

let updatedNum = state.num;
let updatedQty = state.qty;
let index;

Прежде чем идти дальше, давайте вспомним, что особенного в массиве в javascript?

Пожалуйста, найдите цитату из поста https://www.dyn -web.com / javascript / arrays / value-vs-reference.php

Возможно, вы заметили, что если вы назначите массив другой переменной и измените элементы массива этой переменнойисходный массив также изменяется.Или, возможно, вы передали массив функции, надеющейся изменить только локальную копию массива, но вы обнаружите, что исходный массив также был изменен.Это происходит потому, что массивы являются ссылочными типами в JavaScript.

Это означает, что если вы присваиваете массив переменной или передаете массив функции, то это ссылка на исходный массив, который копируется или передается, а неЗначение массиваupdated.

В нашем случае исходные массивы на самом деле являются теми, которые мы имеем в состоянии, поэтому, как правило, всякий раз, когда происходит обновление, исходное состояние фактически изменяется, и, как следует из первой цитаты,

случайные мутации предотвращают повторную визуализацию после отправки действия

In ADD_TO_CART

case actionTypes.ADD_TO_CART:
   ...
   updatedNum = state.num.concat(action.num); 
   updatedQty = state.qty.concat(1);

Используется метод concat ().

Почему это работает для вас?

От MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat

Метод concat () используется для объединения двух илибольше массивов.Этот метод не изменяет существующие массивы, но вместо этого возвращает новый массив.

Так что случайной мутации нет, вы возвращаете новый массив

В REMOVE_FROM_CHART метод

case actionTypes.REMOVE_FROM_CART:
      updatedNum.splice(index, 1);
      updatedQty.splice(index, 1);

используется метод splice ().

From MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice

Метод splice () изменяет содержимое массива путем удаления или замены существующих элементов и/ или добавление новых элементов.

Таким образом, существует мутация, потому что вместо возврата нового массива вы изменяете исходный массив, который ссылается на состояние

В HANDLE_CLICK и UPDATE_QUANTITY

updatedQty[index] -= 1;

Таким образом, вы снова мутируете массив вместо того, чтобы возвращать новый массив, поэтому вы мутируете исходный массив, который ссылается на состояние

В вашем текущем случае, если вы выполните следующееобновление

let updatedNum = state.num.concat();
let updatedQty = state.qty.concat();
//I would suggest you to use spread operator
let updatedNum = [...state.num]
let updatedQty = [...state.qty]

Большинство ваших начальных проблем будет решено

2) Как написать productsReducer или мои действия так, чтобы num и qty оба обновлялись

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

Однако я могу сделать пару предложений

Прежде всего, возможно, я бы избавился от следующего раздела

 let updatedNum = state.num;
  let updatedQty = state.qty;
  let index;

  if (action.num !== undefined) {
    index = updatedNum.indexOf(action.num);
  }

Для меня я обычно выполняю такие проверки и назначения внутри моих обработчиков действий в изолированной области видимости, потому что когда кодстановится больше, мне труднее пройти весь путь, чтобы посмотреть, где я объявляю эти переменные, и, во-вторых, вы никогда не можете быть уверены, являются ли они мутировавшими или нет, и затрудняет отладку.Кроме того, в будущем вам может потребоваться обновить логику объявления, скажем, updateNum для некоторых ваших обработчиков действий.Если у вас есть такая декларация, вы можете сломать некоторую часть вашего приложения, даже не осознавая

Во-вторых, я считаю, что использование ниже является своего рода анти-паттерном

// After updating store, use CalculatePrice action to calculate updated price.
      this.calculateTotalPrice(updatedNum, updatedQty);

ЕслиВаше намерение состоит в том, чтобы вызвать эту функцию внутри обработчика действий, это не сработает, потому что в вышеприведенных строках вы уже возвращаете оператор.

Если вы намереваетесь обновить цену после ADD_CHART, REMOVE_FROM_CHART

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

Например,

...
const totalPrice = Some logic related to updatedNum and updateQty
return {
          ...state,
          num: updatedNum,
          qty: updatedQty,
          totalPrice
        }

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

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

Поэтому, прежде чем пробовать относительно более сложные вещи, такие как редукторы более высокого порядка и т. д., вы можете сосредоточиться на коренных причинах.

...