微信小程序里使用 Redux 狀態(tài)管理
前言
前陣子一直在做小程序開發(fā)沉填,采用的是官方給的框架 wepy
, 如果還不了解的同學可以去他的官網(wǎng)查閱相關資料學習醇锚;不得不說的是,這個框架確相比于傳統(tǒng)小程序開發(fā)模式確實方便很多酱吝,它的語法 Vue 的語法很像,可以實現(xiàn)組件化開發(fā)窍蓝,方面后面代碼的調整和維護...但是R傅摺!這個框架的坑也不是一點點吓笙,開發(fā)的時候總會遇到奇奇怪怪的問題淑玫,自己去踩吧,這樣你才能進步~~
廢話了這么多面睛,咳咳絮蒿,上面的都不是我們要討論的重點,我們今天的重點是—在小程序里使用 Redux
進行狀態(tài)管理叁鉴,Redux
是一個前端狀態(tài)管理的容器土涝,對于構建大型應用,對里面共享數(shù)據(jù)幌墓、狀態(tài)的管理非常方便但壮,學過 React
的同學對它應該不陌生,如果還不了解的同學常侣,不如進服瞧一瞧;
wepy
框架本身是支持 Redux
的蜡饵,我們在構建項目的時候,將 是否安裝 Redux
選擇 y
就好了胳施,會自動安裝依賴溯祸,運行項目后看官方給的demo
確實是可以做到的,但是官方文檔里卻對這一塊只字不提舞肆,經(jīng)過我自己嘗試了一波焦辅,這才稍微摸清了它的使用方式,趕緊拿來與你們分享~
注意了椿胯,接下來劃重點了~
具體實現(xiàn)
運行我們的項目筷登,發(fā)現(xiàn)官網(wǎng)已經(jīng)給了我們一些 Redux
的使用方法,實際上主要是放在 store
文件夾下面了压状,我們現(xiàn)在來一探究竟~
step1
入口文件 index.js
仆抵,里面主要是 初始化 Redux
, 其中 promiseMiddleware
是一個中間件种冬,方便后面 action
做異步處理~ reducers
是一個純函數(shù)镣丑,用于接受 Action
和當前 State
作為參數(shù),返回一個新的 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
用于定義我們要觸發(fā)的 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
文件件存放我們的純函數(shù)趣竣,用來更改我們的狀態(tài) 摇庙, 他也有一個入口文件 index.js
,定義如下:
import { combineReducers } from 'redux'
import counter from './counter'
import list from './list'
export default combineReducers({
counter ,
list
})
首先將 counter
和 list
的分別引入進來遥缕,通過 redux
定義的 combineReducers
函數(shù)卫袒,將所有的 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)
里面涉及到了不少 ES6 的語法,不太明白的同學可以看看我之前寫的 關于 ES6 的文章~
我們介紹一下 counter.js
里面的 reducer
户秤, 首先引入了 handleActions
方法用來創(chuàng)建 actions
码秉, 它將多個相關的 reducer
寫在一起也是 ,方面后期維護鸡号,也方便后期通過 dispatch
來調用他們更改 state
里面的狀態(tài)转砖,它主要接收兩個參數(shù),第一個參數(shù)時候個大對象鲸伴,里面存放多個 reducer
府蔗, 第二個參數(shù)是初始化的時候 state
的狀態(tài)值,因此挑围,我們一開始就定義了 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
我們終于走到這一步了红碑,到這里舞吭,你已經(jīng)離預期不遠啦,就剩一個 actions
文件件了析珊,毫不例外羡鸥,入口文件 index.js
如下:
index.js
export * from './counter'
很簡單,只需要將所需的 action
導出即可~
這個里面我只定義了 counter
的 action
忠寻, 也就是為了剛才異步數(shù)據(jù) 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
里面的要區(qū)分惧浴,這里是可以對數(shù)據(jù)進行一系列處理的,我們通過 createAction
創(chuàng)建一個 action
, 該方法主要有兩個參數(shù)奕剃,第一個參數(shù) type
表示 action
的類型衷旅,第二個參數(shù) payloadCreator
是一個 function
捐腿,處理并返回需要的 payload
;如果空缺柿顶,會使用默認方法茄袖。這里我們是延遲 1s
后返回一個 1
;
ok嘁锯,到此為止绞佩,你已經(jīng)基本完成了一個 redux
的容器~
接下來,就是展示它怎么使用的時候了~
step5
我們創(chuàng)建一個 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>
不出意外,運行后烤低,你的小程序的界面會跟下面一樣————丑~
點一點看肘交,發(fā)現(xiàn)臥槽,很牛逼扑馁,有木有~
ok~ 我們一起看看上面的代碼是怎么做的~
樣式結構方面我們這里不做討論涯呻,主要看 js
部分,其中
import { INCREMENT , DECREMENT } from '../store/types/counter'
和 import { asyncInc } from '../store/actions'
分別表示從 counter
和 actions
導出所需的 action
我們重點看看 從 wepy-redux
中 引入的 connect
腻要,這個 connect
很關鍵复罐,它是連接 組件 和 狀態(tài) 的橋梁,主要用法是 @connect(states, actions)
~
states
: 訪問state
上的值雄家,可以是數(shù)組或者對象效诅,如果是對象的話,則包含的是K-V
對趟济,V
可以是函數(shù)還可以是字符串乱投,如果是字符串的話則默認獲取state[V]
, 否則的話則是使用返回值顷编;而對于如果是數(shù)組的話(數(shù)組中的項只能為字符串)戚炫,則認為是相同的K-V
對象結構。states
最終會附加到組件的computed
屬性值上媳纬。-
actions
: 只能傳入對象双肤,對象的K-V
結構,如果V
是字符串的話钮惠,則直接會distatch
如下的結構:// args 就是調用傳入?yún)?shù) { type: val, // 修正一般情況下的參數(shù) 一般支持只傳一個參數(shù) // 如果真的是多個參數(shù)的話 那么 payload 就是參數(shù)組成的數(shù)組 payload: args.length > 1 ? args : args[0] }
如果是一個函數(shù)
fn
茅糜,則會dispatch(val.apply(store, args))
,否則的話則直接dispatch(V)
這里萌腿,我們定義的 加一 限匣、 減一 和 異步加一
操作直接映射到 INCREMENT
、DECREMENT
、asyncInc
上米死,也就是相當于直接 dispacth
對應的操作锌历,對數(shù)據(jù)進行變更~
現(xiàn)在效果應該可以看到了吧~
當然,我們也可以手動調用容器的 dispatch
方法對數(shù)據(jù)進行修改峦筒,我們的添加 和 刪除 就是這么做的究西,
點擊添加按鈕,我們直接 dispatch
列表中的 ADD
action
物喷,如下:
wepy.$store.dispatch({ type : 'ADD' , payload : {
title : '學習' ,
text : '好好學習'
}})
刪除某一項卤材,只需 dispatch
列表的 REMOVE
action
,傳入要刪除的索引即可 :
delete(index){
wepy.$store.dispatch({ type : 'REMOVE' , payload : index })
},
不信你看~
大功告成~
結語
ok峦失,到現(xiàn)在我們也算是摸索著搞出來了一點名堂扇丛,回頭來看發(fā)現(xiàn)其實也并沒有那么困難吧,有學過 React
的同學應該對此不陌生尉辑,學起來光速吧~ 不過對于我來說帆精,我確實是屬于初探,希望能給跟我一樣萌新的小伙伴一個拋磚引玉的作用隧魄,如果有哪里寫的不對的地方卓练,還請批評斧正~
代碼我已經(jīng)托管到 github上,有需要的小伙伴自行下載查閱~
ps:wepy
真的有很多坑~