本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5MDAzNzkwNA==&action=getalbum&album_id=1566025152667107329)
我们来看一下目前我们的reducer
:
function reducer(state = initialState, action) {
switch (action.type) {
case ADD_NUMBER:
return { ...state, counter: state.counter + action.num };
case SUB_NUMBER:
return { ...state, counter: state.counter - action.num };
case CHANGE_BANNER:
return { ...state, banners: action.banners };
case CHANGE_RECOMMEND:
return { ...state, recommends: action.recommends };
default:
return state;
}
}
reducer
既有处理counter
的代码,又有处理home
页面的数据;counter
相关的状态或home
相关的状态会进一步变得更加复杂;如果将所有的状态都放到一个reducer
中进行管理,随着项目的日趋庞大,必然会造成代码臃肿、难以维护。
因此,我们可以对reducer
进行拆分:
我们先抽取一个对counter
处理的reducer
:
// counter相关的状态
const initialCounter = {
counter: 0
}
function counterReducer(state = initialCounter, action) {
switch (action.type) {
case ADD_NUMBER:
return { ...state, counter: state.counter + action.num };
case SUB_NUMBER:
return { ...state, counter: state.counter - action.num };
default:
return state;
}
}
再抽取一个对home
处理的reducer
:
// home相关的状态
const initialHome = {
banners: [],
recommends: []
}
function homeReducer(state = initialHome, action) {
switch (action.type) {
case CHANGE_BANNER:
return { ...state, banners: action.banners };
case CHANGE_RECOMMEND:
return { ...state, recommends: action.recommends };
default:
return state;
}
}
如果将它们合并起来呢?
const initialState = {
}
function reducer(state = initialState, action) {
return {
counterInfo: counterReducer(state.counterInfo, action),
homeInfo: homeReducer(state.homeInfo, action),
}
}
目前我们已经将不同的状态处理拆分到不同的reducer中,我们来思考:
reducer
中用到的constant、action
等我们也依然是在同一个文件中;所以,接下来,我们可以对文件结构再次进行拆分:
./store
├── counter
│ ├── actioncreators.js
│ ├── constants.js
│ ├── index.js
│ └── reducer.js
├── home
│ ├── actioncreators.js
│ ├── constants.js
│ ├── index.js
│ └── reducer.js
├── index.js
├── reducer.js
└── saga.js
这里不再给出代码,每个文件中存放的内容即可:
home/actioncreators.js
:存放home
相关的action
;home/constants.js
:存放home
相关的常量;home/reducer.js
:存放分离的reducer
代码;index.js
:统一对外暴露的内容;目前我们合并的方式是通过每次调用reducer
函数自己来返回一个新的对象:
import { reducer as counterReducer } from './counter';
import { reducer as homeReducer } from './home';
const initialState = {
}
function reducer(state = initialState, action) {
return {
counterInfo: counterReducer(state.counterInfo, action),
homeInfo: homeReducer(state.homeInfo, action),
}
}
事实上,redux
给我们提供了一个combineReducers
函数可以方便的让我们对多个reducer
进行合并:
import { combineReducers } from 'redux';
import { reducer as counterReducer } from './counter';
import { reducer as homeReducer } from './home';
const reducer = combineReducers({
counterInfo: counterReducer,
homeInfo: homeReducer
})
export default reducer;
那么combineReducers
是如何实现的呢?
reducers
合并到一个对象中,最终返回一个combination
的函数(相当于我们之前的reducer
函数了);combination
函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state
还是新的state
;state
会触发订阅者发生对应的刷新,而旧的state
可以有效的组织订阅者发生刷新;export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// This is used to make sure we don't warn about the same
// keys multiple times.
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
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
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
在React开发中,我们总是会强调数据的不可变性:
state
,还是redux
中管理的state
;数据的可变性引发的问题:
const obj = {
name: "why",
age: 18
}
console.log(obj); // {name: "why", age: 18}
const obj2 = obj;
obj2.name = "kobe";
console.log(obj); // {name: "kobe", age: 18}
obj
,只是修改了obj2
,但是最终obj
也被我们修改掉了;有没有办法解决上面的问题呢?
Object.assign
或扩展运算符console.log(obj); // {name: "why", age: 18}
const obj2 = {...obj};
obj2.name = "kobe";
console.log(obj); // {name: "kobe", age: 18}
这种对象的浅拷贝有没有问题呢?
有人会说,开发中不都是这样做的吗?
为了解决上面的问题,出现了Immutable
对象的概念:
Immutable
对象的特点是只要修改了对象,就会返回一个新的对象,旧的对象不会发生改变;
但是这样的方式就不会浪费内存了吗?
为了节约内存,又出现了一个新的算法:Persistent Data Structure
(持久化数据结构或一致性数据结构);
当然,我们一听到持久化第一反应应该是数据被保存到本地或者数据库,但是这里并不是这个含义:
如何做到这一点呢?结构共享:
我们来学习一下ImmutableJS
常用的API:
const imjs = Immutable;
// 1.定义JavaScript的Array和转换成immutable的List
const friends = [
{ name: "why", age: 18 },
{ name: "kobe", age: 30 }
]
// 不会进行深层转换
const imArray1 = imjs.List(friends);
// 会进行深层转换
const imArray2 = imjs.fromJS(friends);
// console.log(imArray1);
// console.log(imArray2);
// 1.定义JavaScript的Object和转换成immutable的Map
const info = {
name: "coderwhy",
age: 18,
friend: {
name: "kobe",
age: 30
}
}
const imObj1 = imjs.Map(info);
const imObj2 = imjs.fromJS(info);
// console.log(imObj1);
// console.log(imObj2);
// 3.对immutable操作
// 3.1.添加数据
// 产生一个新的immutable对象
console.log(imArray1.push("aaaa"));
console.log(imArray1.set(2, "aaaa"));
// 原来的是不变的
console.log(imArray1);
// 3.2.修改数据
console.log(imArray1.set(1, "aaaa"));
console.log(imArray2.set(2, imjs.fromJS("bbbb")));
// 3.3.删除数据
console.log(imArray1.delete(0).get(0)); // {name: "kobe", age: 30}
// 3.4.查询数据
console.log(imArray1.get(1));
console.log(imArray2.get(1));
console.log(imArray1.get(1).name);
console.log(imArray2.getIn([1, "name"]));
// 4.转换成JavaScript对象
const array = imArray1.toJS();
const obj = imObj1.toJS();
console.log(array);
console.log(obj);
目前Redux已经学习了过多的内容了,很多同学已经认识到redux的难度,所以如何将ImmutableJS和redux结合使用我们这里先不讲解。
具体ImmutableJS和Redux的结合使用,放到后续项目练习时,再详细说明。
是否将所有的状态应用到redux
我们学习了Redux用来管理我们的应用状态,并且非常好用(当然,你学会前提下,没有学会,好好回顾一下)。
目前我们已经主要学习了三种状态管理方式:
state
管理;Context
数据的共享状态;Redux
管理应用状态;在开发中如何选择呢?
redux
中进行管理,因为这样方便追踪和共享;Context
中进行共享和管理;目前项目中我采用的state
管理方案:
当然,根据不同的情况会进行适当的调整,在后续学习网易云音乐项目时,我也会再次讲解以实战的角度来设计数据的管理方案。