前面有一个Redux,我们去撩(聊)一下它。

什么是Redux?

管理整个前端项目(单页应用)所有的状态数据,统一把整个应用的状态存到一个地方(store),保存成一个状态树,修改数据需要派发(dispatch)一个动作(action)通知store修改。组件通过订阅(subscribe)修改事件,获取最新数据来修改自身状态。

三大原则

  1. 整个应用的state存储在store中,有且只存在一个store。
  2. store里面的state是只读的,唯一改变state的方法就是派发(dispatch)一个动作(action)。
  3. 纯函数(reducer)修改state,每次返回一个新的state,不能直接修改原对象。

为什么要使用Redux(应用场景)

  1. 单页应用复杂,管理不断变化的state非常困难。
  2. 非父子关系组件通信。
  3. 所有页面的公用状态。

从源码出发,整个redux源码,总共导出5个函数,和一个__DO_NOT_USE__ActionTypes(默认的action,所有action类型不能重复,下面会细撩这个点),那么下面就来细细的撩一下5个函数。

1.createStore(reducer, preloadedState, enhancer)

想要使用redux,首先就是创建一个全局的store(当然是唯一的一个),就得调用这个函数(createStore)。该函数接收三个参数。store里面保存了所有的数据,可以看做一个容器。

image

传入reducerinitState创建store。

store返回给钥匙,修改器,电话

有了钥匙就能随时取数据,如果需要修改数据就得通过修改器,如需要需要知道数据什么时候修改了,就打一个电话给store,告诉它,数据修改好了给我说。

先来看看它返回的函数:

getState() => currentState

返回当前的store状态树,包含所有state。这里在读源码的时候发现一个疑问。

image

这里返回的是原本的对象,那么外部拿到这个state,不是可以直接修改吗?这样违背了只读。为什么不返回一个新引用对象,防止此操作。

带着这个疑问,给Redux提了一个PR. 得到回复:

Yes - getState() just returns whatever the current state value is. It's up to you to avoid accidentally mutating it.

(译文)是的,getState()只返回当前状态值。你要避免不小心把它弄乱。

也就是说在这里需要注意一下,getState()返回的值不能直接修改,否则你将陷入深渊

subscribe(listener) => unsubscribe()

传入一个函数用来监听store发生变化,一旦store发生了变化,将循环执行所有的监听函数。调用该函数还返回了一个取消监听的函数。调用返回的函数,则取消该listener监听。

dispatch(action) => action

dispatch接收一个action。在函数内部会把所有的reducer执行一遍并把当前的state和action传入reducer,然后再执行所有的监听函数。从源码中截了一段出来:

const dispatch = (action) => {
    currentState = currentReducer(currentState, action);
    
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    
    return action
}
action

一个对象,必须包含一个type,表示所要发生的动作,其他属性自由设置,社区中有一个规范,把其他所有数据放入payload中,一般开发中会写一个函数来生成action。

    function login(){
        retrun {
            type: 'LOGIN',
            payload: {
                username: 'xxx',
                passworld: 'xxxx'
            }
        }
    }

replaceReducer(nextReducer)

传入新的reducer,替换之前的reducer,并且派发一个ActionType为REPLACE的action。

再来看看它接收的三个参数

reducer

一个纯函数,接收当前的state和action做为参数,返回新的state。

Store 收到 Action 以后,会调用reducer,必须给出一个新的 State,这样 Store里的数据才会发生变化。

编写一个简单的reducer

const reducer = function (state, action) {
    switch(action.type){
        case 'TYPE':
            return {...state, newState}
        default:
            return state;
    }
};  

preloadedState

源码里面的参数名叫这个,其实我更愿意叫它initState,初始化的状态,为什么源码不叫initState呢?因为不准确,在源码里会默认发送一个type为init的dispatch(),这个时候走到reducer里面(看看上面的reducer代码),state如果在这个时候设置一个默认值,比如:

const reducer = function (state = {list: []}, action) {
    switch(action.type){
        case 'TYPE':
            return {...state, newState}
        default:
            return state;
    }
};  

这个时候就默认返回了{list: []},就给出了一个真正的initState。

enhancer

用来配合快速创建中间件的。


上面提到的__DO_NOT_USE__ActionTypes,就是2个actionType:

  • @@redux/INIT: 用来内部发送一个默认的dispatch
  • @@redux/REPLACE: 用来替换reducer

2.bindActionCreators(actionCreators, dispatch) => boundActionCreators

遍历所有生成action函数并执行,返回被dispatch包裹的函数,可供直接调用派发一个请求。

actionCreators

该参数为一个对象,包含生成action的函数,例如:

const actionCreators = {
    login: () => {
        return {
            type: 'LOGIN',
            payload: {
                username: 'xxx',
                passworld: 'xxxx'
            }
        }
    },
    logout: () => {
        retrun {
            type: 'LOGOUT'
        }
    }   
}

如果传入一个函数,则执行函数得到action,返回一个dispatch(action)。

dispatch

这里就是createStore所返回的dispatch



该函数返回对象或函数(根据传入的actionCreators来决定),可以直接调用xx.login()去派发一个登陆。

3.combineReducers(reducers)

在项目开发中,需要分模块写reducer,利用该函数合并多个reducer模块。传入一个reducer集合。

a.js
export (state = {list: []}, action) => {
    switch (action.type) {
        case "LIST_CHANGE":
            return { ...state, ...action.payload };
        default:
            return state;
    }
}

b.js
export (state = {list: []}, action) => {
    switch (action.type) {
        case "LIST":
            return { ...state, ...action.payload };
        default:
            return state;
    }
}

combineReducers.js
import a from './a';
import b from './b';

combineReducers({
    a: a,
    b: b
})

ab都有list这个state,但是他们并不相关,要把他们分开使用,就得用combineReducers去合并。

下面简单实现了该函数:

function combineReducers(reducers) {
    return (state = {}, action) => {
        return Object.keys(reducers).reduce((currentState, key) => {
            currentState[key] = reducers[key](state[key], action);
            return currentState;
        }, {})
    };
}

4.compose

可以说是js中函数式中很重要的方法,把一堆函数串联起来执行,从右至左执行(也就是倒序),函数的参数是上一个函数的结果。看一个使用例子:

const fn1 = (val) => val + 'fn1';

const fn2 = (val) => val + 'fn2';

const fn3 = (val) => val + 'fn3';

const dispatch = compose(fn1, fn2, fn3);

console.log(dispatch('test'));

最终输出结果testfn3fn2fn1

test传给fn3当参数,fn3的返回值给了fn2....

compose.js

function compose(...fns){
    if(fns.length==1) return fns[0];
    return fns.reduce((a,b)=>(...args)=>a(b(...args)));
}

5.applyMiddleware

该函数是用来添加中间件,在修改数据的时候做一些其他操作,redux通过改造dispatch来实现中间件.

使用中间件

const middleware = applyMiddleware(logger);

需要传入中间件函数,可以是多个函数,依次传入,在applyMiddleware里面用到了compose,所以我们传入的函数的从右至左依次执行的,这里需要注意一下。

const createStoreWithMiddleware = middleware(createStore);

因为中间件是通过改造dispatch来实现,所以需要把创建store的方法传进去。

const store = createStoreWithMiddleware(reducer, preloadedState)

这里再传入createStore需要接收的参数,返回store对象。

实现一个logger中间件

const logger = function({dispatch, getState}){
   return function(next){
      return function(action){
            console.log('oldState',getState());
            next(action); // 真实派发动作
            console.log('newState',getState());
        }
    }
}

首先middleware会把未改造的dispatchgetState传入进来,这里的next相当于dispatch,去派发一个真正的修改数据动作。

源码贴上:

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

middlewareAPI是存储未改造的方法,compose上面讲过,第一个参数传入的就是dispatch,返回的一个改造后dispatch就是通过compose过后的一个函数,会依次执行。

最后

一些学习心得,如有不对欢迎指正

我的github

谢谢你阅读我的文章

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

推荐阅读更多精彩内容