Почему исходные данные видоизменяются в этой простой задаче?

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

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

Приложение работает очень хорошо, но я чего-то не понимаю:

В компоненте imageContainer ведение журнала props.cats такое же, как и запись исходных данных (коты). Оба возвращают измененные объекты кошек с измененным значением clickCount, которое увеличивается после щелчка по изображениям.

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

Мои исходные данные:

import images from './images';
const cats =  [
  {
    name: 'Lucy',
    src: images.cat1,
    clickCount: 0
  },
  {
    name: 'Julia',
    src: images.cat2,
    clickCount: 0,
  },
  {
    name: 'Kattie',
    src: images.cat3,
    clickCount: 0,
  },
  {
    name: 'Hend',
    src: images.cat4,
    clickCount: 0,
  }
];

 export default cats;

Первый редуктор:

import cats from '../cats'

export default (state = cats, action) => {
  switch(action.type) {
    case 'INCREMENT':
      return cats.map(cat => cat.name === action.name ? {...cat, clickCount: ++cat.clickCount} : cat);

    default:
      return state;
  }
}

Второй редуктор:

import cats from '../cats'

export default (state = cats[0], action) => {
  switch(action.type) {
    case 'SET_CURRENT_CAT':
      return cats.filter(cat => cat.name === action.name)[0];
    default: 
      return state;
  }
}

Мои компоненты:

Первый компонент:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import cats from '../cats';

const ImageContainer  = props =>{
  const {currentCat, incrementClicks} = props;
  console.log(props.cats)//cats with modified clickCount
   console.log(cats)     // cats with modified clickCounts
  return(
   <div className="img-cont">
     <img 
       src={currentCat.src}
       className="cat-img"
       onClick={() => incrementClicks(currentCat.name)}
       alt='cat_img'
     />
     <div className="clicks-count">
       <p>{currentCat.clickCount} clicks</p>
     </div>
   </div>
  );

}

const mapStateToProps = (state) => {
 return {
   cats: state.cats,
   currentCat: state.currentCat,
 }
}

const mapDispatchToProps = (dispatch) => {
  return {
    incrementClicks: (name) => dispatch({
      type: 'INCREMENT',
      name
    })
  };
}


export default connect(mapStateToProps, mapDispatchToProps)(ImageContainer);

второй компонент:

import React, { Component } from 'react';
import cats from '../cats';
import { connect } from 'react-redux';

const CatChanger = (props) => {

  return(
    <div className="cat-changer">
      <select onChange={(e) => {props.setCurrentCat(e.target.value)} }>    

        {cats.map((cat,i) => (
          <option value={cat.name} key={i}>{cat.name}</option>
        ))}
      </select>
    </div>
  );

}


const mapDispatchToProps = (dispatch) => {
  return {
    setCurrentCat: (name) => dispatch({
      type: 'SET_CURRENT_CAT',
      name
    })
  };
}

export default connect(null, mapDispatchToProps)(CatChanger);

Так почему же исходные данные изменены? или это не так ??

0
0
36
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

Точно так же в вашем первом редукторе вы возвращаете данные из импорта кошек.

В обоих случаях вы должны возвращать данные из переданного параметра state, а не из импорта cats.

Тогда ваш первый редуктор будет выглядеть так:

export default (state = cats, action) => {
  switch(action.type) {
    case 'INCREMENT':
      return state.map(cat => cat.name === action.name ? {...cat, clickCount: cat.clickCount + 1} : {...cat});

    default:
      return state;
  }
}

Второй редуктор:

export default (state = cats[0], action) => {
  switch(action.type) {
    case 'SET_CURRENT_CAT':
      return {...state.filter(cat => cat.name === action.name)[0]};
    default: 
      return state;
  }
}

Обратите внимание, что основной код редуктора ссылается на state, а не на cats. Также обратите внимание, что редукторы теперь возвращают новые объекты, которые являются клонами объектов в состоянии (НЕ исходными объектами), используя оператор распространения объекта: {...cat} и {...state.filter()[0]}.

Импорт cats и использование его по умолчанию не обязательно является проблемой, хотя это не лучший дизайн. (Проблема в том, что код вообще не использует параметр состояния, а вместо этого использует импортированную коллекцию объектов.) Чтобы полностью избежать использования импорта cats в редукторах, можно передать их в качестве содержимого хранилища по умолчанию. кому: createStore:

import cats from '../cats';
const defaultState = { cats, currentCatName: 'Lucy' };

const store = createStore(
  rootReducer,
  defaultState
);

Тогда значения по умолчанию для параметров редуктора должны возвращать пустой массив для первого редуктора:

export default (state = [], action) => {

и пустой объект для второго редуктора:

export default (state = {}, action) => {

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

Другая оптимизация, которая поможет избежать использования существующих объектов, - это изменить SET_CURRENT_CAT, чтобы просто изменить имя / идентификатор кошки, хранящиеся в состоянии:

export default (state, action) => {
      switch(action.type) {
        case 'SET_CURRENT_CAT':
          return action.name;

Затем, чтобы получить текущую кошку, вы можете добавить селектор, который получает кошку по имени / идентификатору из состояния, возвращаемого первым редуктором. (Добавление поля id поможет вам справиться со сценарием, когда есть несколько кошек с одним и тем же именем.) Селектор может выглядеть следующим образом:

function getCatByName(state, name) { return state.cats.filter(cat => cat.name === name) }

У тебя есть идеи, как этого избежать ?? моя логика пошла по этому пути .... Какая разница между вашим кодом и моим, вы все равно зависите от импорта кошек !!

Saher Elgendy 26.10.2018 07:06

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

Saher Elgendy 26.10.2018 07:23

Массив новый, но объекты в нем являются исходными. Ваш код должен клонировать объекты Cat. В JS объекты передаются по ссылке.

stone 26.10.2018 07:28

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

stone 26.10.2018 07:33

да, я знаю, что объекты передаются по ссылке, поэтому почему я попытался создать новый объект с помощью оператора распространения объекта es7 и Object.assign, ... это не сработало, но кошки изменены, несмотря на то, что я передаю совершенно новый объект !!

Saher Elgendy 26.10.2018 07:40

Хм, я не совсем понимаю. Где вы проезжаете совершенно новый объект? В каких строках кода?

stone 26.10.2018 07:45

в качестве начального состояния в редукторах

Saher Elgendy 26.10.2018 07:45

Позвольте нам продолжить обсуждение в чате.

stone 26.10.2018 07:47

Вы изменяете состояние, когда используете для него оператор ++. Вместо этого используйте простое дополнение

 return cats.map(cat => cat.name === action.name ? {...cat, clickCount: cat.clickCount + 1} : cat);

Я использовал добавление, но оно не увеличивало ничего ... это еще одна часть, которую я не могу понять. Удивительно, что cat.clickCount + 1 не работает. cat.clickCount ++ работает. ???

Saher Elgendy 26.10.2018 07:14

Другие вопросы по теме