从零开始实现一个 Redux,知其然并知其所以然

前言

记得开始接触 react 技术栈的时候,最难理解的地方就是 redux。全是新名词:reducer、store、dispatch、middleware 等等,我就理解 state 一个名词。

网上找的 redux 文章,要不有一本书的厚度,要不很玄乎,晦涩难懂,越看越觉得难,越看越怕,信心都没有了!

花了很长时间熟悉 redux,慢慢的发现它其实真的很简单。本章不会把 redux 的各种概念,名词解释一遍,这样和其他教程没有任何区别,没有太大意义。我会带大家从零实现一个完整的 redux,让大家知其然,知其所以然。

开始前,你必须知道一些事情:

redux 和 react 没有关系,redux 可以用在任何框架中,忘掉 react。

connect 不属于 redux,它其实属于 react-redux,请先忘掉它,下一章节,我们会介绍它。

请一定先忘记 reducer、store、dispatch、middleware 等等这些名词。

redux 是一个状态管理器。

Let's Go!

状态管理器

简单的状态管理器

redux 是一个状态管理器,那什么是状态呢?状态就是数据,比如计数器中的 count。

letstate = {

count: 1

}

我们来使用下状态

console.log(state.count);

我们来修改下状态

state.count = 2;

好了,现在我们实现了状态(计数)的修改和使用了。

读者:你当我傻吗?你说的这个谁不知道?捶你👊!

笔者:哎哎哎,别打我!有话好好说!redux 核心就是这个呀!我们一步一步扩展开来嘛!

当然上面的有一个很明显的问题:修改 count 之后,使用 count 的地方不能收到通知。我们可以使用发布-订阅模式来解决这个问题。

/*------count 的发布订阅者实践------*/

letstate = {

count:1

};

letlisteners = [];

/*订阅*/

functionsubscribe(listener){

listeners.push(listener);

}

functionchangeCount(count){

state.count = count;

/*当 count 改变的时候,我们要去通知所有的订阅者*/

for(leti =0; i < listeners.length; i++) {

constlistener = listeners[i];

listener();

}

}

我们来尝试使用下这个简单的计数状态管理器。

/*来订阅一下,当 count 改变的时候,我要实时输出新的值*/

subscribe(()=>{

console.log(state.count);

});

/*我们来修改下 state,当然我们不能直接去改 state 了,我们要通过 changeCount 来修改*/

changeCount(2);

changeCount(3);

changeCount(4);

现在我们可以看到,我们修改 count 的时候,会输出相应的 count 值。

现在有两个新的问题摆在我们面前

这个状态管理器只能管理 count,不通用

公共的代码要封装起来

我们尝试来解决这个问题,把公共的代码封装起来

constcreateStore =function(initState){

letstate = initState;

letlisteners = [];

/*订阅*/

functionsubscribe(listener){

listeners.push(listener);

}

functionchangeState(newState){

state = newState;

/*通知*/

for(leti =0; i < listeners.length; i++) {

constlistener = listeners[i];

listener();

}

}

functiongetState(){

returnstate;

}

return{

subscribe,

changeState,

getState

}

}

我们来使用这个状态管理器管理多个状态 counter 和 info 试试

letinitState = {

counter: {

count:0

},

info: {

name:'',

description:''

}

}

letstore = createStore(initState);

store.subscribe(()=>{

letstate = store.getState();

console.log(`${state.info.name}:${state.info.description}`);

});

store.subscribe(()=>{

letstate = store.getState();

console.log(state.counter.count);

});

store.changeState({

...store.getState(),

info: {

name:'前端九部',

description:'我们都是前端爱好者!'

}

});

store.changeState({

...store.getState(),

counter: {

count:1

}

});

到这里我们完成了一个简单的状态管理器。

这里需要理解的是 createStore,提供了 changeState,getState,subscribe 三个能力。

本小节完整源码见 demo-1

有计划的状态管理器

我们用上面的状态管理器来实现一个自增,自减的计数器。

letinitState = {

count:0

}

letstore = createStore(initState);

store.subscribe(()=>{

letstate = store.getState();

console.log(state.count);

});

/*自增*/

store.changeState({

count: store.getState().count +1

});

/*自减*/

store.changeState({

count: store.getState().count -1

});

/*我想随便改*/

store.changeState({

count:'abc'

});

你一定发现了问题,count 被改成了字符串 abc,因为我们对 count 的修改没有任何约束,任何地方,任何人都可以修改。

我们需要约束,不允许计划外的 count 修改,我们只允许 count 自增和自减两种改变方式!

那我们分两步来解决这个问题

制定一个 state 修改计划,告诉 store,我的修改计划是什么。

修改 store.changeState 方法,告诉它修改 state 的时候,按照我们的计划修改。

我们来设置一个 plan 函数,接收现在的 state,和一个 action,返回经过改变后的新的 state。

/*注意:action = {type:'',other:''}, action 必须有一个 type 属性*/

functionplan(state, action){

switch(action.type) {

case'INCREMENT':

return{

...state,

count: state.count +1

}

case'DECREMENT':

return{

...state,

count: state.count -1

}

default:

returnstate;

}

}

我们把这个计划告诉 store,store.changeState 以后改变 state 要按照我的计划来改。

/*增加一个参数 plan*/

constcreateStore =function(plan, initState){

letstate = initState;

letlisteners = [];

functionsubscribe(listener){

listeners.push(listener);

}

functionchangeState(action){

/*请按照我的计划修改 state*/

state = plan(state, action);

for(leti =0; i < listeners.length; i++) {

constlistener = listeners[i];

listener();

}

}

functiongetState(){

returnstate;

}

return{

subscribe,

changeState,

getState

}

}

我们来尝试使用下新的 createStore 来实现自增和自减

letinitState = {

count:0

}

/*把plan函数*/

letstore = createStore(plan, initState);

store.subscribe(()=>{

letstate = store.getState();

console.log(state.count);

});

/*自增*/

store.changeState({

type:'INCREMENT'

});

/*自减*/

store.changeState({

type:'DECREMENT'

});

/*我想随便改 计划外的修改是无效的!*/

store.changeState({

count:'abc'

});

到这里为止,我们已经实现了一个有计划的状态管理器!

我们商量一下吧?我们给 plan 和 changeState 改下名字好不好?plan 改成 reducer,changeState 改成 dispatch!不管你同不同意,我都要换,因为新名字比较厉害(其实因为 redux 是这么叫的)!

本小节完整源码见 demo-2

多文件协作

reducer 的拆分和合并

这一小节我们来处理下 reducer 的问题。啥问题?

我们知道 reducer 是一个计划函数,接收老的 state,按计划返回新的 state。那我们项目中,有大量的 state,每个 state 都需要计划函数,如果全部写在一起会是啥样子呢?

所有的计划写在一个 reducer 函数里面,会导致 reducer 函数及其庞大复杂。按经验来说,我们肯定会按组件维度来拆分出很多个 reducer 函数,然后通过一个函数来把他们合并起来。

我们来管理两个 state,一个 counter,一个 info。

letstate = {

counter: {

count: 0

},

info: {

name:'前端九部',

description:'我们都是前端爱好者!'

}

}

他们各自的 reducer

/*counterReducer, 一个子reducer*/

/*注意:counterReducer 接收的 state 是 state.counter*/

functioncounterReducer(state, action){

switch(action.type) {

case'INCREMENT':

return{

count: state.count +1

}

case'DECREMENT':

return{

...state,

count: state.count -1

}

default:

returnstate;

}

}

/*InfoReducer,一个子reducer*/

/*注意:InfoReducer 接收的 state 是 state.info*/

functionInfoReducer(state, action){

switch(action.type) {

case'SET_NAME':

return{

...state,

name: action.name

}

case'SET_DESCRIPTION':

return{

...state,

description: action.description

}

default:

returnstate;

}

}

那我们用 combineReducers 函数来把多个 reducer 函数合并成一个 reducer 函数。大概这样用

constreducer = combineReducers({

counter: counterReducer,

info: InfoReducer

});

我们尝试实现下 combineReducers 函数

functioncombineReducers(reducers){

/* reducerKeys = ['counter', 'info']*/

constreducerKeys =Object.keys(reducers)

/*返回合并后的新的reducer函数*/

returnfunctioncombination(state = {}, action){

/*生成的新的state*/

constnextState = {}

/*遍历执行所有的reducers,整合成为一个新的state*/

for(leti =0; i < reducerKeys.length; i++) {

constkey = reducerKeys[i]

constreducer = reducers[key]

/*之前的 key 的 state*/

constpreviousStateForKey = state[key]

/*执行 分 reducer,获得新的state*/

constnextStateForKey = reducer(previousStateForKey, action)

nextState[key] = nextStateForKey

}

returnnextState;

}

}

我们来尝试下 combineReducers 的威力吧

constreducer = combineReducers({

counter: counterReducer,

info: InfoReducer

});

letinitState = {

counter: {

count:0

},

info: {

name:'前端九部',

description:'我们都是前端爱好者!'

}

}

letstore = createStore(reducer, initState);

store.subscribe(()=>{

letstate = store.getState();

console.log(state.counter.count, state.info.name, state.info.description);

});

/*自增*/

store.dispatch({

type:'INCREMENT'

});

/*修改 name*/

store.dispatch({

type:'SET_NAME',

name:'前端九部2号'

});

本小节完整源码见 demo-3

state 的拆分和合并

上一小节,我们把 reducer 按组件维度拆分了,通过 combineReducers 合并了起来。但是还有个问题, state 我们还是写在一起的,这样会造成 state 树很庞大,不直观,很难维护。我们需要拆分,一个 state,一个 reducer 写一块。

这一小节比较简单,我就不卖关子了,用法大概是这样(注意注释)

/* counter 自己的 state 和 reducer 写在一起*/

letinitState = {

count:0

}

functioncounterReducer(state, action){

/*注意:如果 state 没有初始值,那就给他初始值!!*/

if(!state) {

state = initState;

}

switch(action.type) {

case'INCREMENT':

return{

count: state.count +1

}

default:

returnstate;

}

}

我们修改下 createStore 函数,增加一行 dispatch({ type: Symbol() })

constcreateStore =function(reducer, initState){

letstate = initState;

letlisteners = [];

functionsubscribe(listener){

listeners.push(listener);

}

functiondispatch(action){

state = reducer(state, action);

for(leti =0; i < listeners.length; i++) {

constlistener = listeners[i];

listener();

}

}

functiongetState(){

returnstate;

}

/* 注意!!!只修改了这里,用一个不匹配任何计划的 type,来获取初始值 */

dispatch({type:Symbol() })

return{

subscribe,

dispatch,

getState

}

}

我们思考下这行可以带来什么效果?

createStore 的时候,用一个不匹配任何 type 的 action,来触发 state = reducer(state, action)

因为 action.type 不匹配,每个子 reducer 都会进到 default 项,返回自己初始化的 state,这样就获得了初始化的 state 树了。

你可以试试

/*这里没有传 initState 哦 */

conststore = createStore(reducer);

/*这里看看初始化的 state 是什么*/

console.dir(store.getState());

本小节完整源码见 demo-4

到这里为止,我们已经实现了一个七七八八的 redux 啦!

中间件 middleware

中间件 middleware 是 redux 中最难理解的地方。但是我挑战一下用最通俗的语言来讲明白它。如果你看完这一小节,还没明白中间件是什么,不知道如何写一个中间件,那就是我的锅了!

中间件是对 dispatch 的扩展,或者说重写,增强 dispatch 的功能!

记录日志

我现在有一个需求,在每次修改 state 的时候,记录下来 修改前的 state ,为什么修改了,以及修改后的 state。我们可以通过重写 store.dispatch 来实现,直接看代码

conststore = createStore(reducer);

constnext = store.dispatch;

/*重写了store.dispatch*/

store.dispatch =(action) =>{

console.log('this state', store.getState());

console.log('action', action);

next(action);

console.log('next state', store.getState());

}

我们来使用下

store.dispatch({

type:'INCREMENT'

});

日志输出为

thisstate{counter: { count:0} }

action{type:'INCREMENT'}

1

nextstate{counter: { count:1} }

现在我们已经实现了一个完美的记录 state 修改日志的功能!

记录异常

我又有一个需求,需要记录每次数据出错的原因,我们扩展下 dispatch

conststore = createStore(reducer);

constnext = store.dispatch;

store.dispatch =(action) =>{

try{

next(action);

}catch(err) {

console.error('错误报告: ', err)

}

}

这样每次 dispatch 出异常的时候,我们都会记录下来。

多中间件的合作

我现在既需要记录日志,又需要记录异常,怎么办?当然很简单了,两个函数合起来呗!

store.dispatch =(action)=>{

try{

console.log('this state', store.getState());

console.log('action', action);

next(action);

console.log('next state', store.getState());

}catch(err) {

console.error('错误报告: ', err)

}

}

如果又来一个需求怎么办?接着改 dispatch 函数?那再来10个需求呢?到时候 dispatch 函数肯定庞大混乱到无法维护了!这个方式不可取呀!

我们需要考虑如何实现扩展性很强的多中间件合作模式。

1、我们把 loggerMiddleware 提取出来

conststore = createStore(reducer);

constnext = store.dispatch;

constloggerMiddleware =(action) =>{

console.log('this state', store.getState());

console.log('action', action);

next(action);

console.log('next state', store.getState());

}

store.dispatch =(action) =>{

try{

loggerMiddleware(action);

}catch(err) {

console.error('错误报告: ', err)

}

}

2、我们把 exceptionMiddleware 提取出来

constexceptionMiddleware =(action) =>{

try{

/*next(action)*/

loggerMiddleware(action);

}catch(err) {

console.error('错误报告: ', err)

}

}

store.dispatch = exceptionMiddleware;

3、现在的代码有一个很严重的问题,就是 exceptionMiddleware 里面写死了loggerMiddleware,我们需要让 next(action)变成动态的,随便哪个中间件都可以

constexceptionMiddleware =(next) =>(action) => {

try{

/*loggerMiddleware(action);*/

next(action);

}catch(err) {

console.error('错误报告: ', err)

}

}

/*loggerMiddleware 变成参数传进去*/

store.dispatch = exceptionMiddleware(loggerMiddleware);

4、同样的道理,loggerMiddleware 里面的 next 现在恒等于 store.dispatch,导致 loggerMiddleware 里面无法扩展别的中间件了!我们也把 next 写成动态的

const loggerMiddleware =(next)=>(action) => {

console.log('this state', store.getState());

console.log('action', action);

next(action);

console.log('next state', store.getState());

}

到这里为止,我们已经探索出了一个扩展性很高的中间件合作模式!

conststore = createStore(reducer);

constnext = store.dispatch;

constloggerMiddleware =(next) =>(action) => {

console.log('this state', store.getState());

console.log('action', action);

next(action);

console.log('next state', store.getState());

}

constexceptionMiddleware =(next) =>(action) => {

try{

next(action);

}catch(err) {

console.error('错误报告: ', err)

}

}

store.dispatch = exceptionMiddleware(loggerMiddleware(next));

这时候我们开开心心的新建了一个 loggerMiddleware.js,一个exceptionMiddleware.js文件,想把两个中间件独立到单独的文件中去。会碰到什么问题吗?

loggerMiddleware 中包含了外部变量 store,导致我们无法把中间件独立出去。那我们把 store 也作为一个参数传进去好了~

conststore = createStore(reducer);

constnext  = store.dispatch;

constloggerMiddleware =(store) =>(next) =>(action) =>{

console.log('this state', store.getState());

console.log('action', action);

next(action);

console.log('next state', store.getState());

}

constexceptionMiddleware =(store) =>(next) =>(action) =>{

try{

next(action);

}catch(err) {

console.error('错误报告: ', err)

}

}

constlogger = loggerMiddleware(store);

constexception = exceptionMiddleware(store);

store.dispatch = exception(logger(next));

到这里为止,我们真正的实现了两个可以独立的中间件啦!

现在我有一个需求,在打印日志之前输出当前的时间戳。用中间件来实现!

consttimeMiddleware =(store) =>(next) =>(action) =>{

console.log('time',newDate().getTime());

next(action);

}

...

const time = timeMiddleware(store);

store.dispatch = exception(time(logger(next)));

本小节完整源码见 demo-6

中间件使用方式优化

上一节我们已经完全实现了正确的中间件!但是中间件的使用方式不是很友好

importloggerMiddlewarefrom'./middlewares/loggerMiddleware';

importexceptionMiddlewarefrom'./middlewares/exceptionMiddleware';

importtimeMiddlewarefrom'./middlewares/timeMiddleware';

...

const store = createStore(reducer);

constnext = store.dispatch;

constlogger = loggerMiddleware(store);

constexception = exceptionMiddleware(store);

consttime = timeMiddleware(store);

store.dispatch = exception(time(logger(next)));

其实我们只需要知道三个中间件,剩下的细节都可以封装起来!我们通过扩展 createStore 来实现!

先来看看期望的用法

/*接收旧的 createStore,返回新的 createStore*/

constnewCreateStore = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware)(createStore);

/*返回了一个 dispatch 被重写过的 store*/

conststore = newCreateStore(reducer);

实现 applyMiddleware

constapplyMiddleware =function(...middlewares){

/*返回一个重写createStore的方法*/

returnfunctionrewriteCreateStoreFunc(oldCreateStore){

/*返回重写后新的 createStore*/

returnfunctionnewCreateStore(reducer, initState){

/*1. 生成store*/

conststore = oldCreateStore(reducer, initState);

/*给每个 middleware 传下store,相当于 const logger = loggerMiddleware(store);*/

/* const chain = [exception, time, logger]*/

constchain = middlewares.map(middleware=>middleware(store));

letdispatch = store.dispatch;

/* 实现 exception(time((logger(dispatch))))*/

chain.reverse().map(middleware=>{

dispatch = middleware(dispatch);

});

/*2. 重写 dispatch*/

store.dispatch = dispatch;

returnstore;

}

}

}

让用户体验美好

现在还有个小问题,我们有两种 createStore 了

/*没有中间件的 createStore*/

import{ createStore }from'./redux';

conststore = createStore(reducer, initState);

/*有中间件的 createStore*/

constrewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);

constnewCreateStore = rewriteCreateStoreFunc(createStore);

conststore = newCreateStore(reducer, initState);

为了让用户用起来统一一些,我们可以很简单的使他们的使用方式一致,我们修改下createStore 方法

constcreateStore =(reducer, initState, rewriteCreateStoreFunc) =>{

/*如果有 rewriteCreateStoreFunc,那就采用新的 createStore */

if(rewriteCreateStoreFunc){

constnewCreateStore =  rewriteCreateStoreFunc(createStore);

returnnewCreateStore(reducer, initState);

}

/*否则按照正常的流程走*/

...

}

最终的用法

constrewriteCreateStoreFunc = applyMiddleware(exceptionMiddleware, timeMiddleware, loggerMiddleware);

conststore = createStore(reducer, initState, rewriteCreateStoreFunc);

本小节完整源码见 demo-7

完整的 redux

退订

不能退订的订阅都是耍流浪!我们修改下 store.subscribe 方法,增加退订功能

functionsubscribe(listener){

listeners.push(listener);

returnfunctionunsubscribe(){

constindex = listeners.indexOf(listener)

listeners.splice(index,1)

}

}

使用

constunsubscribe = store.subscribe(()=>{

letstate = store.getState();

console.log(state.counter.count);

});

/*退订*/

unsubscribe();

中间件拿到的store

现在的中间件拿到了完整的 store,他甚至可以修改我们的 subscribe 方法,按照最小开放策略,我们只用把 getState 给中间件就可以了!因为我们只允许你用getState 方法!

修改下 applyMiddleware 中给中间件传的 store

/*const chain = middlewares.map(middleware => middleware(store));*/

constsimpleStore = {getState: store.getState };

constchain = middlewares.map(middleware=>middleware(simpleStore));

compose

我们的 applyMiddleware 中,把 [A, B, C] 转换成 A(B(C(next))),是这样实现的

constchain = [A, B, C];

letdispatch = store.dispatch;

chain.reverse().map(middleware=>{

dispatch = middleware(dispatch);

});

redux 提供了一个 compose 方式,可以帮我们做这个事情

const chain = [A, B, C];

dispatch = compose(...chain)(store.dispatch)

看下他是如何实现的

exportdefaultfunctioncompose(...funcs){

if(funcs.length ===1) {

returnfuncs[0]

}

returnfuncs.reduce((a, b) =>(...args) => a(b(...args)))

}

当然 compose 函数对于新人来说可能比较难理解,你只需要他是做什么的就行啦!

省略initState

有时候我们创建 store 的时候不传 initState,我们怎么用?

conststore = createStore(reducer, {}, rewriteCreateStoreFunc);

redux 允许我们这样写

conststore = createStore(reducer, rewriteCreateStoreFunc);

我们仅需要改下 createStore 函数,如果第二个参数是一个object,我们认为他是 initState,如果是 function,我们就认为他是 rewriteCreateStoreFunc。

functioncraeteStore(reducer, initState, rewriteCreateStoreFunc){

if(typeofinitState ==='function'){

rewriteCreateStoreFunc = initState;

initState =undefined;

}

...

}

2 行代码的 replaceReducer

reducer 拆分后,和组件是一一对应的。我们就希望在做按需加载的时候,reducer也可以跟着组件在必要的时候再加载,然后用新的 reducer 替换老的 reducer。

constcreateStore =function(reducer, initState){

...

functionreplaceReducer(nextReducer){

reducer = nextReducer

/*刷新一遍 state 的值,新来的 reducer 把自己的默认状态放到 state 树上去*/

dispatch({ type: Symbol() })

}

...

return{

...

replaceReducer

}

}

我们来尝试使用下

constreducer = combineReducers({

counter: counterReducer

});

conststore = createStore(reducer);

/*生成新的reducer*/

constnextReducer = combineReducers({

counter: counterReducer,

info: infoReducer

});

/*replaceReducer*/

store.replaceReducer(nextReducer);

replaceReducer 示例源码见 demo-5

bindActionCreators

bindActionCreators 我们很少很少用到,一般只有在 react-redux 的 connect 实现中用到。

他是做什么的?他通过闭包,把 dispatch 和 actionCreator 隐藏起来,让其他地方感知不到 redux 的存在。

我们通过普通的方式来 隐藏 dispatch 和 actionCreator 试试,注意最后两行代码

constreducer = combineReducers({

counter: counterReducer,

info: infoReducer

});

conststore = createStore(reducer);

/*返回 action 的函数就叫 actionCreator*/

functionincrement(){

return{

type:'INCREMENT'

}

}

functionsetName(name){

return{

type:'SET_NAME',

name: name

}

}

constactions = {

increment:function(){

returnstore.dispatch(increment.apply(this,arguments))

},

setName:function(){

returnstore.dispatch(setName.apply(this,arguments))

}

}

/*注意:我们可以把 actions 传到任何地方去*/

/*其他地方在实现自增的时候,根本不知道 dispatch,actionCreator等细节*/

actions.increment();/*自增*/

actions.setName('九部威武');/*修改 info.name*/

我眼睛一看,这个 actions 生成的时候,好多公共代码,提取一下

constactions = bindActionCreators({ increment, setName }, store.dispatch);

来看一下 bindActionCreators 的源码,超级简单(就是生成了刚才的 actions)

/*核心的代码在这里,通过闭包隐藏了 actionCreator 和 dispatch*/

functionbindActionCreator(actionCreator, dispatch){

returnfunction(){

returndispatch(actionCreator.apply(this,arguments))

}

}

/* actionCreators 必须是 function 或者 object */

exportdefaultfunctionbindActionCreators(actionCreators, dispatch){

if(typeofactionCreators ==='function') {

returnbindActionCreator(actionCreators, dispatch)

}

if(typeofactionCreators !=='object'|| actionCreators ===null) {

thrownewError()

}

constkeys =Object.keys(actionCreators)

constboundActionCreators = {}

for(leti =0; i < keys.length; i++) {

constkey = keys[i]

constactionCreator = actionCreators[key]

if(typeofactionCreator ==='function') {

boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)

}

}

returnboundActionCreators

}

bindActionCreators 示例源码见 demo-8

大功告成

完整的示例源码见 demo-9,你可以和 redux 源码做一下对比,你会发现,我们已经实现了 redux 所有的功能了。

当然,为了保证代码的理解性,我们少了一些参数验证。比如 createStore(reducer)的参数 reducer 必须是 function 等等。

最佳实践

纯函数

什么是纯函数?

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用。

通俗来讲,就两个要素

相同的输入,一定会得到相同的输出

不会有 “触发事件”,更改输入参数,依赖外部参数,打印 log 等等副作用

/*不是纯函数,因为同样的输入,输出结果不一致*/

functiona( count ){

returncount +Math.random();

}

/*不是纯函数,因为外部的 arr 被修改了*/

functionb( arr ){

returnarr.push(1);

}

letarr = [1,2,3];

b(arr);

console.log(arr);//[1, 2, 3, 1]

/*不是纯函数,以为依赖了外部的 x*/

letx =1;

functionc( count ){

returncount + x;

}

我们的 reducer 计划函数,就必须是一个纯函数!

只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

总结

到了最后,我想把 redux 中关键的名词列出来,你每个都知道是干啥的吗?

createStore

创建 store 对象,包含 getState, dispatch, subscribe, replaceReducer

reducer

reducer 是一个计划函数,接收旧的 state 和 action,生成新的 state

action

action 是一个对象,必须包含 type 字段

dispatch

dispatch( action ) 触发 action,生成新的 state

subscribe

实现订阅功能,每次触发 dispatch 的时候,会执行订阅函数

combineReducers

多 reducer 合并成一个 reducer

replaceReducer

替换 reducer 函数

middleware

扩展 dispatch 函数!

你再看 redux 流程图,是不是大彻大悟了?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容