最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 浅谈redux、applyMiddleware、redux-thunk

    正文概述 掘金(一只前端小菜鸡)   2020-12-04   579

    redux是一个可预测的状态管理工具,唯一可以改变state的方式就是dispatch一个action,action描述了以何种方式改变state,交由reducer去改变state。

    创建redux应用

    npm i redux
    

    src/index.js:

    import {createStore} from 'redux'
    
    const initState = {
        list: []
    }
    
    //  reducer,createStore的第一个参数,当store初始化的时候redux会调用reducer,传入state为undefined,action.type为一个@@redux/INIT开头的随机字符串
    //  所以在这里可以设置state的默认值,防止下次reducer改变数据的时候报错
    //  reducer应当返回一个state,来作为新的state。
    function todo(state = initState, action){
        switch(action.type){
            case 'todoAdd':
                return {
                    list: state.list.concat(action.text)
                }
            case 'todoRemove':
                return {
                    list: state.list.filter((v) => v !== action.text)
                }
            default: 
                return state
        }
    }
    
    let store = createStore(todo)
    
    //订阅store更新
    store.subscribe(() => {
        console.log(store.getState())
    })
    
    //派发action,这个action会被传入到reducer的第二个参数
    store.dispatch({
        type: 'todoAdd',
        text: '吃饭',
    })
    store.dispatch({
        type: 'todoAdd',
        text: '睡觉',
    })
    store.dispatch({
        type: 'todoAdd',
        text: '打豆豆',
    })
    store.dispatch({
        type: 'todoRemove',
        text: '睡觉',
    })
    

    控制台打印结果为: 浅谈redux、applyMiddleware、redux-thunk

    合并reducer

    假如说有多个reducer,一个是todo,另外一个是用户数据,我们可以使用redux提供的combineReducers来合并reducer,src下新建一个文件夹为store,src/store/index.js为创建的store,src/store/todo.js和src/store/user.js分别为todo的reducer和user的reducer。 src/store/index.js代码为:

    import {createStore, combineReducers} from 'redux'
    import todo from './todo'
    import user from './user'
    
    const reducer = combineReducers({
        todo,
        user,
    })
    
    const store = createStore(reducer)
    
    export default store
    

    src/store/user.js代码为

    const initState = {
        name: 'xiaobai',
        age: 18,
    }
    
    function user(state = initState, action){
        switch(action.type){
            case 'userAgeAdd':
                return {
                    ...state,
                    age: state.age + 1,
                }
            case 'userNameChange':
                return {
                    ...state,
                    name: action.name,
                }
            default: 
                return state
        }
    }
    
    export default user
    

    现在在src/index.js里面增加一段代码

    store.dispatch({
        type: 'userNameChange',
        name: 'xiaohei',
    })
    

    打印结果 浅谈redux、applyMiddleware、redux-thunk

    创建action生成函数

    上面的写法dispatch每次都要写一个action,可是试想一下如果我们封装成一个函数来返回一个action的话会更方便一点,就以user这个reducer开始封装生成action的函数。 src/store/user.js增加两个函数

    export function userNameChange(name){
        return {
            type: 'userNameChange',
            name,
        }
    }
    export function userAgeAdd(){
        return {
            type: 'userAgeAdd',
        }
    }
    

    src/index.js修改为:

    import store from './store'
    import {userNameChange} from './store/user'
    
    //订阅store更新
    store.subscribe(() => {
        console.log(store.getState())
    })
    
    store.dispatch(userNameChange('xiaohei'))
    

    打印结果看到user.name已经被修改为'xiaohei'

    创建异步action生成函数

    reducer是一个纯函数,不应该修改传入的参数,不应该有执行有副作用的API 请求和路由跳转,不能调用非纯函数。只要传入参数相同,返回计算得到的下一个 state 就一定相同,单纯执行计算。 那么怎么执行异步操作呢,这时候就要用到一个插件redux-thunk。通过使用redux提供的applyMiddleware,action创建函数除了返回 action 对象外还可以返回函数,当返回函数时,这个函数会被执行,接收一个参数为dispatch。这个函数并不需要保持纯净。

    npm i redux-thunk
    

    继续修改src/store/user.js导出的userNameChange

    export function userNameChange(name){
        return (dispatch) => {  //返回的函数会被执行,并被传入dispatch
            setTimeout(() => {  //模拟api请求
                console.log('一秒后dispatch一个action')
                dispatch({
                    type: 'userNameChange',
                    name,
                })
            },2000)
        }
    }
    

    /src/store/index.js

    import {createStore, combineReducers, applyMiddleware} from 'redux'
    import reduxThunk from 'redux-thunk'
    import todo from './todo'
    import user from './user'
    
    const reducer = combineReducers({
        todo,
        user,
    })
    
    const store = createStore(reducer, applyMiddleware(
        reduxThunk
    ))
    
    export default store
    

    现在打开控制台刷新页面,一秒之后打印结果正常,说明我们已经做好了action的异步操作。 redux-thunk并不是redux处理异步操作唯一的解决方式,当你读完下一章节你也可以写一个自定义的middleware

    Middleware分析

    middleware 是指可以被嵌入在框架接收请求到产生响应过程之中的代码。例如,Express 或者 Koa 的 middleware 可以完成添加 CORS headers、记录日志、内容压缩等工作。middleware 最优秀的特性就是可以被链式组合。你可以在一个项目中使用多个独立的第三方 middleware。 Redux middleware 它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。

    手动记录日志

    假如我们没有redux提供的applyMiddleware,如果想记录redux日志的话,可能会需要这样写来手动记录。 src/store/user.js修改为原来的userNameChange,src/store/index.js删除middleware。 src/index.js

    import store from './store' 
    import {userNameChange} from './store/user'
    
    
    let action = userNameChange('xiaohei')
    console.log('dispatch', action.type)
    store.dispatch(action)
    console.log('newState', store.getState())
    

    重写dispatch

    上面的方法虽然可以实现记录日志的功能,但是需要每次dispatch都需要记录。既然改变数据就一定会用dispatch,我们可以尝试重写dispatch,在保留原来dispatch完整功能的情况下,增加一些我们自己需要做的操作。 src/index.js

    import store from './store'
    import {userNameChange} from './store/user'
    
    let next = store.dispatch   //保存原来的dispatch完整功能
    
    store.dispatch = (action) => {      //重写dispatch,接收一个action
        console.log('dispatch', action.type)
        let result = next(action)       //执行原来的dispatch功能
        console.log('newState', store.getState())
        return result
    }
    
    let action = userNameChange('xiaohei')
    store.dispatch(action)
    

    现在打开控制台,不管在哪里dispatch,都已经可以正常的记录日志了,

    新增middleware

    实际开发中捕获异常也是很重要的,现在如果需要新增功能的话,在原来重写的dispatch上面写新功能会让代码看起来很乱,我们完全可以写成两个独立的功能。 src/index.js

    import store from './store'
    import {userNameChange} from './store/user'
    
    const logMiddleware = (store) => {
        let next = store.dispatch
        store.dispatch = (action) => {
            console.log('dispatch', action.type)
            let result = next(action)
            console.log('newState', store.getState())
            return result
        }
    }
    
    const errMiddlware = (store) => {
        let next = store.dispatch
        store.dispatch = (action) => {
            try {
                return next(action)
            }catch(err){
                console.log('redux抛出异常')
                throw err
            }
        }
    }
    
    logMiddleware(store)
    errMiddlware(store)
    
    let action = userNameChange('xiaohei')
    store.dispatch(action)
    //为了试验异常捕获,dispatch不传参数。
    store.dispatch()
    

    现在打开控制台打印结果为浅谈redux、applyMiddleware、redux-thunk 不出所料,两个中间件功能全部实现了,整个代码运行流程为:执行logMiddleware,传入createStore生成的store,logMiddleware对store的dispatch方法进行重写;执行errMiddlware,传入dispatch方法已经被logMiddleware方法重写过的store,errMiddlware在保留原来被处理过的完整的dispatch方法之上,继续添加新的功能。

    applyMiddleware源码

    通过研究redux提供的applyMiddleware源码

    function applyMiddleware(...middlewares) {
    	//createStore会判断如果执行applyMiddleware返回函数,创建store的工作就交由下面的代码来执行
    	//返回一个处理过dispatch的store,现在的...args为我们传入的reducer。
    	//createStore的代码为 enhancer(createStore)(reducer, preloadedState)
    	return (createStore) => (...args) => {
    		const store = createStore(...args)
    		let dispatch = () => {
    			throw new Error(
    				'Dispatching while constructing your middleware is not allowed. ' +
              		'Other middleware would not be applied to this dispatch.'
            	)
        	}
    		const middlewareAPI = {		//中间件可访问的参数
    			getState: store.getState,
    			dispatch: (...args) => dispatch(...args)
    		}
    		const chain = middlewares.map(middleware => middleware(middlewareAPI)),	//接收next,返回dispatch的函数组成的数组。
    		dispatch = compose(...chain)(store.dispatch)	//原始dispatch传入compose生成的函数被链式处理。
    		return {
    			...store,
    			dispatch	//被处理过的dispatch
    		}
    	}
    }
    

    下面为compose:

    export default function compose(...funcs) {
    	 if (funcs.length === 0) {
    	   	return arg => arg
    	 }
    	if (funcs.length === 1) {
    		return funcs[0]
    	}
    	return funcs.reduce((a, b) => {
    		return (...args) => a(b(...args))
    	})
    }
    //compose 的作用是:传入一组任意数量的函数,比如 funcA, funcB,funcC,
    //可生成一个新的函数 (...args) => funcA(funcB(funcC(...args)))
    //它的含义是每个函数均以上一个函数的返回值为参数传入,并将自己计算得到的返回值作为下一个函数的参数。
    

    通过以上源码不难发现,其实middleware就是一个接收middlewareAPI的函数,返回的函数接收一个参数next,返回一个函数作为下一个中间件的next。

    使用applyMiddleware

    先改写logMiddleware和errMiddlware src/store/index.js

    import {createStore, combineReducers, applyMiddleware} from 'redux'
    import reduxThunk from 'redux-thunk'
    import todo from './todo'
    import user from './user'
    
    const reducer = combineReducers({
        todo,
        user,
    })
    
    const logMiddleware = (store) => {  //store为applyMiddleware传出的middlewareAPI
        return (next) => {              //返会函数接收next,执行返回dispatch作为下一个middleware的next参数
            return (action) => {        //dispatch
                console.log('dispatch', action.type)
                let result = next(action)
                console.log('newState', store.getState())
                return result
            }
        }
    }
    
    const errMiddlware = (store) => {   //middlewareAPI
        return (next) => {              //logMiddleware返回的dispatch
            return (action) => {        //返回dispatch,作为下一个middleware的next
                try {
                    return next(action)
                }catch(err){
                    console.log('redux抛出异常')
                    throw err
                }
            }
        }
    }
    
    const store = createStore(reducer, applyMiddleware(
        //middlewares
        logMiddleware,
        errMiddlware,
    ))
    
    export default store
    

    src/index.js删除对dispatch的处理。

    import store from './store'
    import {userNameChange} from './store/user'
    
    store.dispatch(userNameChange('xiaohei'))
    //为了试验异常捕获,dispatch不传参数。
    store.dispatch()
    

    现在打开控制台,既打印了redux日志,也有异常捕获,说明我们middleware写法是正确的,现在对他们进行柯里化。

    const logMiddleware = (store) => (next) => (action) => {
        console.log('dispatch', action.type)
        let result = next(action)
        console.log('newState', store.getState())
        return result
    }
    
    const errMiddlware = (store) => (next) => (action) => {
        try {
            return next(action)
        }catch(err){
            console.log('redux抛出异常')
            throw err
        }
    }
    

    异步action

    上面以优雅的写法增加了logMiddleware和errMiddleware,现在还不能支持异步action的写法,继续增加一个middleware让action可以返回一个函数,函数处理异步操作最终dispatch一个action来改变数据。 src/store/user.js增加一个异步action

    export function userAgeAddSync(){
        return (dispatch) => {	//接收dispatch用来异步操作完成后的派发动作
            setTimeout(() => {
                dispatch(userAgeAdd())
            }, 1000)
        }
    }
    

    刷新页面发现redux抛出异常浅谈redux、applyMiddleware、redux-thunk 因为执行userAgeAddSync得到的是一个接收dispatch的函数,action.type为undefined的原因也是如此,因此需要增加一个中间件来处理这个异步action。 src/store/index.js增加一个middleware并应用到applyMiddleware中

    const syncMiddlware = (store) => (next) => (action) => {
        if(typeof action === 'function'){       //如果action是一个函数,就直接执行这个函数,传入dispatch
            action(store.dispatch)
        }else{
            return next(action)
        }
    }
    

    现在来看打印结果 浅谈redux、applyMiddleware、redux-thunk 现在一秒之后派发userAgeAdd我们已经做到了,但是中间有一个步骤打印action.type为undefined,如果你对middleware链式调用理解的还不错的话,你已经知道什么原因了。因为在我们执行store.dispatch(userAgeAddSync())的时候,userAgeAddSync()返回的函数被logMiddleware处理,打印action.type一定会是undefined,现在只需要在logMiddleware里面座一层判断,如果action为函数的话,直接执行action,传入dispatch,如果不是的话执行原来的记录日志逻辑。 修改logMiddleware代码为

    const logMiddleware = (store) => (next) => (action) => {
        let result
        if(typeof action === 'function'){
            action(store.dispatch)
        }else{
            console.log('dispatch', action.type)
            result = next(action)
            console.log('newState', store.getState())
            return result
        }
    }
    

    现在打开控制台,页面加载完毕只有userNameChange的日志,一秒后打印userAgeAdd的日志,这正是我们想要的结果。后期如果有时间的话我会基于这套代码进行封装,应用到React上面。本篇博客代码和所有的章节提交记录我放在了Gitee上面,链接链接: Gitee,有兴趣的同学可以下载研究。


    起源地下载网 » 浅谈redux、applyMiddleware、redux-thunk

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元