为什么原始数据会在此简单任务中发生变异?

时间:2018-10-26 04:39:06

标签: reactjs redux react-redux

我知道数据在redux中是不可变的,并且总是应该存在,但是我面临一个奇怪的问题,那就是我没有看到大局。

这个简单的应用程序只有一个选择框,可更改猫的图像,单击猫的图像应增加其下方的点击次数,因此每只猫应具有自己的点击次数。

该应用程序运行良好,但我在这里不明白:

在imageContainer组件中,记录props.cats与记录原始数据(猫)相同。两者都返回经过修改的clickCount的经过修改的cats对象,这些对象在单击图像后会递增。

尽管我每次都由化简器返回新数据而不改变状态,但我无法理解原始数据在此过程中是如何受到影响的!

我的初始数据:

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 :(得分:1)

在第二个reducer中,您是从cats导入中获取数据,而不是从状态中获取数据!因此,reduce总是从导入而不是传入的任何状态返回对象。您不应该在化简代码中导入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;
  }
}

请注意,Reducer主体代码引用的是state,而不是cats 还要注意,reducer现在使用对象散布运算符{...cat}{...state.filter()[0]}返回状态为对象的克隆的新对象(不是原始对象)。

虽然不是最佳设计,但导入cats并将其用作默认值不一定是问题。 (问题是代码根本没有使用state参数,而是使用了导入的对象集合。)为避免完全在reducers中使用cats导入,一种选择是将它们作为默认存储内容传递给到createStore

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

const store = createStore(
  rootReducer,
  defaultState
);

然后,化简器参数的默认值应为第一个化简器返回一个空数组:

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

,第二个reducer的对象为空:

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

另一种选择是添加一个LOAD_CATS动作来填充您的cat集合,该动作可能在顶级应用程序组件的componentDidMount上调度。

另一种有助于避免使用现有对象的优化是更改SET_CURRENT_CAT以仅更改状态下存储的猫的名称/ id:

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

然后要获取当前猫,您可以添加一个选择器,该选择器从第一个reducer返回的状态按名称/ id获取猫。 (添加id字段可帮助您处理多只猫具有相同名称的情况。)选择器可能如下所示:

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

答案 1 :(得分:1)

当您在状态上使用++运算符时,您正在对其进行突变。改用简单的加法

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