手把手教你在小程式裡使用 Redux

Chris威發表於2018-05-30

微信小程式裡使用 Redux 狀態管理

前言

前陣子一直在做小程式開發,採用的是官方給的框架 wepy , 如果還不瞭解的同學可以去他的官網查閱相關資料學習;不得不說的是,這個框架確相比於傳統小程式開發模式確實方便很多,它的語法 Vue 的語法很像,可以實現元件化開發,方面後面程式碼的調整和維護...但是!!這個框架的坑也不是一點點,開發的時候總會遇到奇奇怪怪的問題,自己去踩吧,這樣你才能進步~~

手把手教你在小程式裡使用 Redux

廢話了這麼多,咳咳,上面的都不是我們要討論的重點,我們今天的重點是—在小程式裡使用 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

複製程式碼

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 的容器~

手把手教你在小程式裡使用 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>

複製程式碼

不出意外,執行後,你的小程式的介面會跟下面一樣————醜~

手把手教你在小程式裡使用 Redux

點一點看,發現臥槽,很牛逼,有木有~

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 對應的操作,對資料進行變更~

現在效果應該可以看到了吧~

手把手教你在小程式裡使用 Redux

當然,我們也可以手動呼叫容器的 dispatch 方法對資料進行修改,我們的新增刪除 就是這麼做的, 點選新增按鈕,我們直接 dispatch 列表中的 ADD action,如下:

wepy.$store.dispatch({ type : 'ADD' , payload : {
    title : '學習' ,
    text : '好好學習'
}})
複製程式碼

刪除某一項,只需 dispatch 列表的 REMOVE action ,傳入要刪除的索引即可 :

delete(index){
	wepy.$store.dispatch({ type : 'REMOVE' , payload : index })
},
複製程式碼

不信你看~

手把手教你在小程式裡使用 Redux

大功告成~

結語

ok,到現在我們也算是摸索著搞出來了一點名堂,回頭來看發現其實也並沒有那麼困難吧,有學過 React 的同學應該對此不陌生,學起來光速吧~ 不過對於我來說,我確實是屬於初探,希望能給跟我一樣萌新的小夥伴一個拋磚引玉的作用,如果有哪裡寫的不對的地方,還請批評斧正~

程式碼我已經託管到 github上,有需要的小夥伴自行下載查閱~

ps:wepy真的有很多坑~

相關文章