博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从0实现一个tinyredux
阅读量:6569 次
发布时间:2019-06-24

本文共 6946 字,大约阅读时间需要 23 分钟。

从0实现一个tinyredux

讲真,已经很小了,去掉注释代码也就300行吧, 大家可以去读一下, 注释写的也是非常详细了。

redux 更多的是对思维上的变化:数据改变 + 视图更新 二者分开,各自管理自己。而且用了redux就不用处理组件间通信的问题,可以随心所欲的去粒度化组件。 另外由于状态托管给redux,可以大量的使用无状态组件, 无状态组件没有实例减少内存,没有生命周期调用栈简单

现在,让我们从无到有!!

so tiny !

redux 是这样的一个流程:触发一个action --> redux做一些逻辑,返回state --> 触发监听程序。 这不就是图形界面的事件机制吗(在web 上就是addEventListener)!

所以一个 最小的redux:

class Store {    constructor(reducer, state = {}) {        this.state = state        this.listeners = []        this.reducer = reducer    }    dispatch(action) {        this.state = this.reducer(this.state, action)        this.listeners.forEach(listener => listener())    }    getState() {        return this.state    }    subscribe(listener) {        this.listeners.push(listener)    }}

我们的这个 Store 和 redux的store提供了想的api:

  1. dispatch 触发一个action
  2. getState 返回当前状态
  3. subscribe 增加一个监听器

让我们用这个最小的例子实现一个 计数器

function reducer(state, action) {   switch (action.type) {       case 'addOne': {           return {               ...state,               count: state.count + 1           }       }       default: {           return state       }   }}const store = new Store(reducer, {count: 0})store.subscribe(() => {    console.log('subscribe test:', store.getState())})store.dispatch({type: 'addOne'})store.dispatch({type: 'addOne'})

另一个灵魂 middleware

redux的 上关于的部分, 已经讲的很好了。现在我们从另一个角度来看这个问题,

首先,middleware 是redux在dispatch前后,提供的扩展机制。 比如日志功能, 需要在dispath一个action之前记录一下状态,然后reducer处理完逻辑之后, 再次记录一下。 这不就是 面向切面编程吗!
时髦的AOP! 用java的话不管是 静态代理还是动态代理, 写起来都挺复杂的。 但是js实现 很简单:

function enhancer(originF) {  return function(...args) {    console.log('before')    var result = originF(...args)    console.log('after')    return result  }}

enhancer 方法接受一个方法A, 返回一个增强的方法B。 对B我们可以再次 增强,所以这里是可以链式调用的:

var fEnhancer = function (originF) {    return function (...args) {        console.log('this is fEnhancer before')        var r = originF(...args)        console.log('this is fEnhancer after')        return r    }}var hEnhancer = function (originF) {    return function (...args) {        console.log('this is hEnhancer before')        var r = originF(...args)        console.log('this is hEnhancer after')        return r    }}var gEnhancer = function (originF) {    return function (...args) {        console.log('this is gEnhancer before')        var r = originF(...args)        console.log('this is gEnhancer after')        return r    }}function justPrint() {    console.log('justPrint...')}fEnhancer(hEnhancer(gEnhancer(justPrint)))()

这个例子输出[在线地址]():

this is fEnhancer beforethis is hEnhancer beforethis is gEnhancer beforejustPrint...this is gEnhancer afterthis is hEnhancer afterthis is fEnhancer after

对于 fEnhancer(hEnhancer(gEnhancer(justPrint))) 等效的写法如下:

var enhancerArray = [gEnhancer, hEnhancer, fEnhancer]function enhancerFun(originF) {    let of = originF    enhancerArray.forEach(enhancer => {        of = enhancer(of)    })    return of}

更加流弊的写法, 也就是redux的实现(巧妙的使用了数组的reduce方法):

var enhancerArray = [gEnhancer, hEnhancer, fEnhancer]function enhancerFun2(originF) {    return enhancerArray.reduce((a, b) => (...args) => a(b(...args)))(originF)}

回到 redux, 需要我们增强的是dispatch, 所以只需要 enhancerFun(store.dispatch)。 这里有两个问题:

第一个问题 由于我们的dispatch里面使用了 this, 而这个增强的调用: var r = originF() 这里就丢掉了this。解决方法如下:

class Store {    constructor(reducer, state) {        this.state = state        this.listeners = []        this.reducer = reducer        this.dispatch = this.dispatch.bind(this)        this.getState = this.getState.bind(this)        this.subscribe = this.subscribe.bind(this)    }    ...}

这样在任何地方调用 store的方法, 都没有问题了

第二个问题:在gEnhancer 里面我们想要调用 store.getState() 来记录 调用dispatch 前后的状态怎么办? (我们不可能每次去import store吧, 因为在写enhancer的时候,

可能压根就不知道 store在哪里呢。 ) 方法如下:

var fEnhancer = function ({ getState, dispatch }) {    return function (originF) {        return function (...args) {            console.log('this is fEnhancer before', getState())            var r = originF(...args)            console.log('this is fEnhancer after', getState())            return r        }    }}

通过闭包的形式, 我们让 fEnhancer 内部的逻辑 可以直接使用 getState。

那middleware是什么呢? 这里的fEnhancer就是标准的一个 redux middleware, 是的,redux-logger可以不用了, 让我们用fEnhancer吧。 对应的 applyMiddleware:

function applyMiddleware(store, ...args) {    console.log(args)    const enArr = args.map(middleware => middleware({        getState: store.getState,        dispatch: store.dispatch    }))    let of = store.dispatch    enArr.forEach(en => {        of = en(of)    })    store.dispatch = of}

现在, 给我们开头的reducer 增强一下吧!!

辅助函数

到这里, tineyredux其实已经结束了。 但是redux为了方便开发者 提供了两个辅助函数: combineReducers 和 bindActionCreators。

bindActionCreators 就是在 原本调用 actionCreator的时候,默认帮你dispatch一下: actionCreator() ==》 store.dispatch(actionCreator())。
也可以理解为 '增强':

function bindActionCreator(creator, dispatch) {    return function (...args) {        dispatch(creator(args)) // <---- 也可以理解为 '增强'    }}export default function bindActionCreators(creators, dispatch) {    const keys = Object.keys(creators)    const result = {}    keys.forEach(key => {        result[key] = bindActionCreator(creators[key], dispatch)    })    return result}

combineReducers 是为了解决另外的痛点, 比如如下的store 和reducer:

{    clock: {        count: 0    },    yk: {        age: 0    }    ...}function reducer(state, action) {    switch (action.type) {        case 'clock_add':...        case 'clock_cnum'...        case 'yk_older': ...        case 'yk_forever18': ...        default: {            return state        }    }}

大部分情况, 我们发现我们的应用,clock数据部分,对应clock自己的逻辑, yk数据部分的修改逻辑也只会关心自己(通常这都是2个页面的数据了)。

所以这里的一个 "大switch" 是可以切分的。

function clockReducer(state, action) {    switch (action.type) {        case 'clock_addOne': ...        case 'clock_cnum': ...        default: {            return state        }    }}function ykReducer(state, action) {    switch (action.type) {        case 'yk_older': ...        case 'yk_forever18': ...        default: {            return state        }    }}function reducer(state, action) {  return {      clock: clockReducer(state, action),      yk: ykReducer(state, action),  }}

combineReducers 就是对小的reducer进行合并的:

function combineReducers(reducers) {    return function (state, action) {        const keys = Object.keys(reducers)        const newState = {}        keys.forEach(key => {            newState[key] = reducers[key](state[key], action)        })        return newState    }}

题外话: 这里的 combineReducers 如果小reducer特别多, 会有一些性能问题: 因为对于每一个 action,都是走了所有的reducer。 如果我们场景特殊,

是我们刚才说的 一块数据的逻辑 只对于一个reducer, 可以使用下面的变种(只会执行一个reducer, 需要保证action前缀和store中key一致):

function combineReducersVariant(reducers) {    return function (state, action) {        const lineIndex = action.type.indexOf("_")        const actionKey = action.type.substring(0, lineIndex)        const newS = reducers[actionKey](state[actionKey], action)        return state[actionKey] === newS ? state : {            ...state,            [actionKey]: newS        }    }}

这里有一个完整的保护 middleware, bindActionCreators, combineReducers 所有特性的

安装: npm install tiny-redux --save

相关文章

转载地址:http://obvjo.baihongyu.com/

你可能感兴趣的文章
json_encode后的中文不编码成unicode
查看>>
修改纵断面图标注栏
查看>>
Flex创建带有空间信息的椭圆(Polygon)
查看>>
Centos7.1环境下搭建BugFree
查看>>
共用y轴的双图形绘制
查看>>
第31讲 | 数字货币钱包服务
查看>>
P2073 送花
查看>>
iOS端项目注释规范附统一代码块
查看>>
HTTP深入浅出 http请求
查看>>
为YUM设置代理的方法
查看>>
Java 编程的动态性 第1 部分: 类和类装入--转载
查看>>
【转】持久化消息队列之MEMCACHEQ
查看>>
Dom4j学习笔记
查看>>
C语言 HTTP上传文件-利用libcurl库上传文件
查看>>
[MEAN Stack] First API -- 7. Using Route Files to Structure Server Side API
查看>>
调试逆向分为动态分析技术和静态分析技术(转)
查看>>
业务对象和BAPI
查看>>
微软职位内部推荐-Senior Software Engineer
查看>>
程序中的魔鬼数字
查看>>
session cookie
查看>>