微信小程式裡使用 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
複製程式碼
step2
剩下三個資料夾分別是 types
reducers
和 actions
,其中 types
用於定義我們要觸發的 action
的名稱,也就是表示 action
的名稱,這裡我定義了 counter
和 list
兩個 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
})
複製程式碼
首先將 counter
和 list
的分別引入進來,通過 redux
定義的 combineReducers
函式,將所有的 reducers
合併成一個整體,方便我們後面對其進行管理!
那麼 counter
和 list
對應的 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
, 分別定義了 INCREMENT
、 DECREMENT
和 ASYNC_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
匯出即可~
這個裡面我只定義了 counter
的 action
, 也就是為了剛才非同步資料 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'
分別表示從 counter
和 actions
匯出所需的 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)
這裡,我們定義的 加一 、 減一 和 非同步加一
操作直接對映到 INCREMENT
、DECREMENT
、asyncInc
上,也就是相當於直接 dispacth
對應的操作,對資料進行變更~
現在效果應該可以看到了吧~
當然,我們也可以手動呼叫容器的 dispatch
方法對資料進行修改,我們的新增 和 刪除 就是這麼做的,
點選新增按鈕,我們直接 dispatch
列表中的 ADD
action
,如下:
wepy.$store.dispatch({ type : 'ADD' , payload : {
title : '學習' ,
text : '好好學習'
}})
複製程式碼
刪除某一項,只需 dispatch
列表的 REMOVE
action
,傳入要刪除的索引即可 :
delete(index){
wepy.$store.dispatch({ type : 'REMOVE' , payload : index })
},
複製程式碼
不信你看~
大功告成~
結語
ok,到現在我們也算是摸索著搞出來了一點名堂,回頭來看發現其實也並沒有那麼困難吧,有學過 React
的同學應該對此不陌生,學起來光速吧~ 不過對於我來說,我確實是屬於初探,希望能給跟我一樣萌新的小夥伴一個拋磚引玉的作用,如果有哪裡寫的不對的地方,還請批評斧正~
程式碼我已經託管到 github上,有需要的小夥伴自行下載查閱~
ps:wepy
真的有很多坑~