Почему исходные данные видоизменяются в этой простой задаче? - PullRequest
0 голосов
/ 26 октября 2018

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

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

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

В компоненте 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);

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

Ответы [ 2 ]

0 голосов
/ 26 октября 2018

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

 return cats.map(cat => cat.name === action.name ? {...cat, clickCount: cat.clickCount + 1} : cat);
0 голосов
/ 26 октября 2018

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

Аналогично, в вашем первом редукторе вы возвращаете данные из импорта кошек.

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

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

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 и использовать его по умолчанию, хотя это не лучший дизайн. (Проблема в том, что код вообще не использует параметр состояния, а вместо этого использует импортированную коллекцию объектов.) Чтобы избежать полного использования импорта котов в редукторах, можно передать их как содержимое хранилища по умолчанию. до 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) }
...