我知道数据在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);
那么为什么原始数据会发生突变?还是不是?
答案 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);