(转)微信小程序里使用 Redux 状态管理

前言

前阵子一直在做小程序开发,采用的是官方给的框架 wepy , 如果还不了解的同学可以去他的官网查阅相关资料学习;不得不说的是,这个框架确相比于传统小程序开发模式确实方便很多,它的语法 Vue 的语法很像,可以实现组件化开发,方面后面代码的调整和维护...但是!!这个框架的坑也不是一点点,开发的时候总会遇到奇奇怪怪的问题,自己去踩吧,这样你才能进步~~

废话了这么多,咳咳,上面的都不是我们要讨论的重点,我们今天的重点是—在小程序里使用 Redux 进行状态管理,Redux 是一个前端状态管理的容器,对于构建大型应用,对里面共享数据、状态的管理非常方便,学过 React 的同学对它应该不陌生,如果还不了解的同学,不如进服瞧一瞧;

wepy 框架本身是支持 Redux 的,我们在构建项目的时候,将 是否安装 Redux 选择 y 就好了,会自动安装依赖,运行项目后看官方给的demo确实是可以做到的,但是官方文档里却对这一块只字不提,经过我自己尝试了一波,这才稍微摸清了它的使用方式,赶紧拿来与你们分享~

注意了,接下来划重点了~

具体实现

运行我们的项目,发现官网已经给了我们一些 Redux 的使用方法,实际上主要是放在 store 文件夹下面了,我们现在来一探究竟~

step1

入口文件 index.js ,里面主要是 初始化 Redux , 其中 promiseMiddleware 是一个中间件,方便后面 action 做异步处理~ reducers 是一个纯函数,用于接受 Action 和当前 State 作为参数,返回一个新的 State~

import { createStore , applyMiddleware } from 'redux'
import promiseMiddleware from 'redux-promise'
import reducer from './reducers'

const Store = createStore(
    reducer ,
    applyMiddleware(promiseMiddleware)
)

export default configStore => Store   
???
  这句等价于 export default function(configStore){return Store}
  是否应该是 export default function configStore(){return Store}
???

step2

剩下三个文件夹分别是 types reducersactions ,其中 types 用于定义我们要触发的 action 的名称,也就是表示 action的名称,这里我定义了 counterlist 两个 types ,内容分别如下:

counter.js

export const INCREMENT = 'INCREMENT'

export const DECREMENT = 'DECREMENT'

export const ASYNC_INCREMENT = 'ASYNC_INCREMENT'

list.js

export const ADD = 'ADD'

export const REMOVE = 'REMOVE'

最后通过 types 文件夹的入口文件 index.js 将他们暴露出去~

export * from './counter'
export * from './list'

step3

reducers文件件存放我们的纯函数,用来更改我们的状态 , 他也有一个入口文件 index.js,定义如下:

    import { combineReducers } from 'redux'
    import counter from './counter'
    import list from './list'

    export default combineReducers({
        counter ,
        list
    })


首先将 counterlist 的分别引入进来,通过 redux 定义的 combineReducers 函数,将所有的 reducers 合并成一个整体,方便我们后面对其进行管理!

那么 counterlist 对应的 reducer 分别是 什么样的?我们直接看代码:

counter.js

    import { handleActions } from 'redux-actions'
    import { INCREMENT , DECREMENT , ASYNC_INCREMENT } from '../types/counter'

    const defaultState  = {
        num: 0 ,
        asyncNum: 0
    }

    export default handleActions({
        [INCREMENT](state){
            return{
                ...state,
                num : state.num + 1
            }
        },
        [DECREMENT](state){
            return{
                ...state,
                num : state.num - 1
            }
        },
        [ASYNC_INCREMENT](state, action){
            return {
                ...state ,
                asyncNum : state.asyncNum + action.payload
            }
        }
    },defaultState)

我们介绍一下 counter.js 里面的 reducer , 首先引入了 handleActions 方法用来创建 actions , 它将多个相关的 reducer 写在一起也是 ,方面后期维护,也方便后期通过 dispatch 来调用他们更改 state 里面的状态,它主要接收两个参数,第一个参数时候个大对象,里面存放多个 reducer , 第二个参数是初始化的时候 state 的状态值,因此,我们一开始就定义了 defaultState ;

接着,我们看看里面的 reducer , 分别定义了 INCREMENTDECREMENTASYNC_INCREMENT 三个 reducer ,前两个比较简单,分别是对 state 里面的 num 值进行 加减操作 , 最后一个是通过 action.payload 的值来对 asyncNum 的值进行异步操作的,具体怎么做到的,我们一会再看~

list.js 里定义的 reducer 跟上面类似,我就不一一介绍了,直接贴代码即可~

list.js

    import { handleActions } from 'redux-actions'
    import { ADD , REMOVE } from '../types/list'

    const defaultState = [
        {
            title : '吃饭' ,
            text : '今天我要吃火锅'
        },
        {
            title : '工作' ,
            text : '今天我要学习Redux'
        }
    ]

    export default handleActions({
        [ADD]( state , action ){
            state.push(action.payload)
            return [...state]
        },
        [REMOVE]( state , action ){
            state.splice( action.payload , 1 );
            return [ ...state ]

        }
    },defaultState)


step4

我们终于走到这一步了,到这里,你已经离预期不远啦,就剩一个 actions 文件件了,毫不例外,入口文件 index.js 如下:

index.js

    export * from './counter'

很简单,只需要将所需的 action 导出即可~

这个里面我只定义了 counteraction , 也就是为了刚才异步数据 asyncNum 准备的~

counter.js

    import { ASYNC_INCREMENT } from '../types/counter'
    import { createAction } from 'redux-actions'

    export const asyncInc = createAction(ASYNC_INCREMENT,()=>{
        return new Promise(resolve=>{
            setTimeout(()=>{
                resolve(1)
            },1000)
        })
    })

这里跟 reducer 里面的要区分,这里是可以对数据进行一系列处理的,我们通过 createAction 创建一个 action , 该方法主要有两个参数,第一个参数 type 表示 action 的类型,第二个参数 payloadCreator 是一个 function,处理并返回需要的 payload ;如果空缺,会使用默认方法。这里我们是延迟 1s 后返回一个 1

ok,到此为止,你已经基本完成了一个 redux 的容器~

接下来,就是展示它怎么使用的时候了~

step5

我们创建一个 index.wpy 的文件,这里我把代码直接贴出来,然后慢慢来分析看看~

代码如下:

    <template lang="wxml">
      <view class="container">
        <text>同步{{ num }}</text>
        <text>异步{{ asyncNum }}</text>
        <button @tap="increment" type="primary">加一</button>
        <button @tap="decrement" type="primary">减一</button>
        <button @tap="asyncIncrement" type="primary">异步加一</button>

        <button @tap="addList">添加</button>

        <view class="box">
            <view class="item" wx:for-items="{{ todoList }}" wx:key="index">
                <view class="title">{{ item.title }}</view>
                <view class="content">{{ item.text }}</view>
                <button type="primary" class="delete" @tap="delete({{index}})">删除</button>
            </view>
        </view>

      </view>

    </template>

    <script>
        import wepy from 'wepy'
        import { connect } from 'wepy-redux'
        import { INCREMENT , DECREMENT } from '../store/types/counter'
        import { asyncInc } from '../store/actions'

        @connect({
            num(state){
                return state.counter.num;
            },
            asyncNum(state){
                return state.counter.asyncNum;
            }
        },{
            increment : INCREMENT ,
            decrement : DECREMENT ,
            asyncIncrement : asyncInc
        })

        export default class Index extends wepy.page {

        components = {}

        computed = {
            todoList(){
                return wepy.$store.getState().list;
            }
        }

        methods = {
            delete(index){
                wepy.$store.dispatch({ type : 'REMOVE' , payload : index })
                },
            addList(){
                wepy.$store.dispatch({ type : 'ADD' , payload : {
                    title : '学习' ,
                    text : '好好学习'
                }})
            }
        }

        onLoad () {
            console.log(wepy.$store.getState())
        }
    }
    </script>

    <style lang="less">
        text{
            display: block;
            text-align: center;
            margin: 10px auto;
        }
        button{
            width: 90%;
            display: block;
            margin: 10px auto;
        }

        .item{
            display: flex;
            align-items: center;
            text-align: center;
            padding: 0 15px;
            .title{
                font-size: 14px;
                line-height: 20px;
                margin: 10px auto;
            }
            .content{
                font-size: 15px;
                flex: 1;
            }

            .delete{
                width: 70px;
                height: 40px;
                line-height: 40px;
            }
        }
    </style>


不出意外,运行后,你的小程序的界面会跟下面一样————丑~

点一点看,发现卧槽,很牛逼,有木有~

ok~ 我们一起看看上面的代码是怎么做的~

样式结构方面我们这里不做讨论,主要看 js 部分,其中 import { INCREMENT , DECREMENT } from '../store/types/counter'import { asyncInc } from '../store/actions' 分别表示从 counteractions 导出所需的 action

我们重点看看 从 wepy-redux 中 引入的 connect ,这个 connect 很关键,它是连接 组件 和 状态 的桥梁,主要用法是 @connect(states, actions) ~

  • states: 访问 state 上的值,可以是数组或者对象,如果是对象的话,则包含的是 K-V 对,V 可以是函数还可以是字符串,如果是字符串的话则默认获取 state[V], 否则的话则是使用返回值;而对于如果是数组的话(数组中的项只能为字符串),则认为是相同的 K-V 对象结构。states 最终会附加到组件的 computed 属性值上。

  • actions: 只能传入对象,对象的 K-V 结构,如果 V 是字符串的话,则直接会 distatch 如下的结构:

    // args 就是调用传入参数
    {
        type: val,
        // 修正一般情况下的参数 一般支持只传一个参数
        // 如果真的是多个参数的话 那么 payload 就是参数组成的数组
        payload: args.length > 1 ? args : args[0]
    }
    
    

    如果是一个函数 fn,则会 dispatch(val.apply(store, args)),否则的话则直接 dispatch(V)

这里,我们定义的 加一减一异步加一 操作直接映射到 INCREMENTDECREMENTasyncInc 上,也就是相当于直接 dispacth 对应的操作,对数据进行变更~

现在效果应该可以看到了吧~

image

当然,我们也可以手动调用容器的 dispatch 方法对数据进行修改,我们的添加删除 就是这么做的, 点击添加按钮,我们直接 dispatch 列表中的 ADD action,如下:

wepy.$store.dispatch({ type : 'ADD' , payload : {
    title : '学习' ,
    text : '好好学习'
}})

删除某一项,只需 dispatch 列表的 REMOVE action ,传入要删除的索引即可 :

delete(index){
    wepy.$store.dispatch({ type : 'REMOVE' , payload : index })
},

不信你看~

image

大功告成~

结语

ok,到现在我们也算是摸索着搞出来了一点名堂,回头来看发现其实也并没有那么困难吧,有学过 React 的同学应该对此不陌生,学起来光速吧~ 不过对于我来说,我确实是属于初探,希望能给跟我一样萌新的小伙伴一个抛砖引玉的作用,如果有哪里写的不对的地方,还请批评斧正~

代码我已经托管到 github上,有需要的小伙伴自行下载查阅~

ps:wepy真的有很多坑~

作者:Chris威
链接:https://juejin.im/post/5b067f6ff265da0de02f3887
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

推荐阅读更多精彩内容