微信小程序項目重構之(zhī)Redux狀态管理
發表時(shí)間:2021-3-31
發布人(rén):融晨科技
浏覽次數:81
1.以(yǐ)往處理狀态的(de)一些方式
第一種:App上(shàng)挂globalData 這(zhè)種方式就(jiù)是(shì)直接寫在(zài)App裏面啦,取值賦值比較方便
App.js
- App({
globalData: {
name: '前端學者',
// ...other globalData
},
// ...other globalData
});
// 取值 var name = getApp().globalData.name;
// 賦值
getApp().globalData.name = '改個(gè)名字';
第二種:自己定義一個(gè)或者多個(gè)用于(yú)存儲數據的(de)libData文件,自己造輪子(zǐ),愛折騰的(de)就(jiù)直接撸吧
例如libData.js
module.exports = {
name: '前端學者',
// ...other globalData
}
// ...other // 簡單使用 var store = require('./path/to/lib/libData.js');
// 取值 var name = store.name;
// 賦值
store.name = '改個(gè)名字';
第三種:把要(yào / yāo)使用的(de)數據存在(zài)頁面data或者直接存在(zài)頁面上(shàng),比較分散而(ér)且不(bù)利于(yú)管理和(hé / huò)維護,但是(shì)對于(yú)超小且需要(yào / yāo)快速實現的(de)項目也(yě)足夠了(le/liǎo)。别笑,我們前端不(bù)怕這(zhè)樣寫,O(∩_∩)O哈哈~
dataInPage.js
Page({
data: {
name: '前端學者'
},
name: '前端學者'
});
// 取值 var name = this.data.name;
var name = this.name;
// 賦值 this.setData('name', '改個(gè)名字');
this.name = '改個(gè)名字');
2.對于(yú)處理狀态數據,有時(shí)候可能還會混着使用上(shàng)面的(de)幾種方式,随着項目複雜度提升,狀态将難以(yǐ)管理。
所以(yǐ)呢,我們需要(yào / yāo)借鑒web端現有的(de)一些優秀成熟的(de)方案,今天的(de)主角就(jiù)是(shì)它了(le/liǎo),大(dà)家都可能很熟悉,歡迎Redux出(chū)場!網上(shàng)各類的(de)資料已經很詳細地(dì / de)介紹,不(bù)清楚的(de)同學務必了(le/liǎo)解Redux,這(zhè)裏隻詳細說(shuō)明一下怎麽在(zài)小程序裏面使用,和(hé / huò)在(zài)React中的(de)用法非常相似,因爲(wéi / wèi)核心庫mina-redux.js(原名wechat-weapp-redux)的(de)作者就(jiù)是(shì)這(zhè)麽改過來(lái)的(de),這(zhè)個(gè)輪子(zǐ)已經非常棒了(le/liǎo)。下面直接上(shàng)代碼了(le/liǎo),代碼雖然多,但具有實站參考價值,所以(yǐ)直接貼上(shàng)來(lái)了(le/liǎo)。
不(bù)使用任何構建工具,把微信開發者工具中項目設置的(de)鈎子(zǐ)全部打上(shàng),大(dà)方地(dì / de)使用import和(hé / huò)export吧,現在(zài)不(bù)需要(yào / yāo)自己寫構建了(le/liǎo)
- ES6 轉 ES5
- 上(shàng)傳代碼時(shí)樣式自動補全
- 代碼上(shàng)傳時(shí)自動壓縮
- 不(bù)校驗安全域名、TLS版本以(yǐ)及HTTPS證書
核心還是(shì)Redux,和(hé / huò)幾個(gè)好用的(de)相關庫,這(zhè)幾個(gè)都是(shì)從網上(shàng)精簡了(le/liǎo)拿過來(lái)的(de),沒有用到(dào)npm等包管理工具
lib文件夾下:
- redux.js(核心文件,必選)
- redux-logger(core.js, deep-diff.js, defaults.js, diff.js, helpers.js, index.js 這(zhè)些都是(shì)爲(wéi / wèi)了(le/liǎo)打印日志用的(de),可選)
- redux-thunk.js(異步action中間處理,推薦)
- mina-redux.js(核心文件,把redux的(de)dispatch和(hé / huò)倉庫數據同步到(dào)頁面上(shàng),必選)
- remote-redux-devtool.js(遠端調試使用,可以(yǐ)直觀看到(dào)數據流向,非常強大(dà),可選)
- remotedev-server.js(遠程調試服務,可選,方便調試)
- regenerator-runtime.js(facebook的(de)異步庫,與redux沒有任何關系,可選,極力推薦)
先寫redux相關的(de)type action reducer
app.redux.js (個(gè)人(rén)喜好把type action reducer寫在(zài)一個(gè)文件)
/**
* 處理小程序生命周期和(hé / huò)載入的(de)一些數據
*/ import regeneratorRuntime from '../lib/regenerator-runtime';
import util from '../lib/util';
import api from '../lib/api';
import { minaScecne } from '../config/config';
const WX_SYSTEM_INFO = 'WX_SYSTEM_INFO';
const WX_USER_INFO = 'WX_USER_INFO';
const WX_ON_LAUNCH = 'WX_ON_LAUNCH';
const WX_GET_CODE = 'WX_GET_CODE';
const WX_API_PROMISIFY = 'WX_API_PROMISIFY';
const LOGIN_BEGIN = 'LOGIN_BEGIN';
const LOGIN_PENDING = 'LOGIN_PENDING';
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
const LOGIN_FAIL = 'LOGIN_FAIL';
// 用法1 // 不(bù)用中間件,一個(gè)action每次隻能dispatch一個(gè)action export const wxApiPromisify = () => ({
type: WX_API_PROMISIFY,
data: util.initWxMethod()
})
export const wxSystemInfo = (data) => ({
type: WX_SYSTEM_INFO,
data: data
})
export const wxUserInfo = (data) => ({
type: WX_USER_INFO,
data: data
})
export const wxOnLaunch = (data = {}) => {
const { scene, query, path } = data;
Object.keys(minaScecne).map((item => {
if (scene.toString() === item) {
data.sceneText = minaScecne[item]
}
}));
return {
type: WX_ON_LAUNCH,
data: data
}
}
// 用法2 // app上(shàng)以(yǐ)store.dispatch(actionMethodName)的(de)方式調用,可以(yǐ)組合action export const login = async dispatch => {
const wxLoginResult = await wx.async.login();
await dispatch({
type: WX_GET_CODE,
data: wxLoginResult
})
await dispatch({
type: LOGIN_BEGIN,
data: {
isFetching: false
}
})
await dispatch({
type: LOGIN_PENDING,
data: {
isFetching: true
}
})
if(wxLoginResult.code){
try {
const apiLoginResult = await api.user.login({ code: wxLoginResult.code });
await dispatch({
type: LOGIN_SUCCESS,
data: apiLoginResult
})
wx.setStorageSync('token', apiLoginResult.data.token)
} catch (error) {
await dispatch({
type: LOGIN_FAIL,
data: error
})
}
} else {
await dispatch({
type: LOGIN_FAIL,
data: wxLoginResult.errMsg
})
}
}
// 用法3 // 使用thunk中間件可以(yǐ)對一個(gè)action進行細化出(chū)多個(gè)狀态,比如請求,可以(yǐ)做用于(yú)處理ui loading // TODO 寫個(gè)request action helper 封裝一下,不(bù)用每次寫那麽多... export const thunkExampleAction = () => (
async dispatch => {
const wxLoginResult = await wx.async.login();
await dispatch({
type: WX_GET_CODE,
data: wxLoginResult
})
await dispatch({
type: LOGIN_BEGIN,
data: {
isFetching: false
}
})
await dispatch({
type: LOGIN_PENDING,
data: {
isFetching: true
}
})
if(wxLoginResult.code){
try {
const apiLoginResult = await api.user.login({ code: wxLoginResult.code });
await dispatch({
type: LOGIN_SUCCESS,
data: apiLoginResult
})
wx.setStorageSync('token', apiLoginResult.data.token)
} catch (error) {
await dispatch({
type: LOGIN_FAIL,
data: error
})
}
} else {
await dispatch({
type: LOGIN_FAIL,
data: wxLoginResult.errMsg
})
}
}
);
// 倉庫數據結構預定義 const initState = {
wxApiPromisify: {
isFinish: false
},
wxSystemInfo: {},
wxUserInfo: {},
wxOnLaunch: {},
wxLogin: {
code: null,
token: null,
userId: null,
phone: null,
errMsg: null
},
userLogin: {
isFetching: false,
data: null
}
};
export const appReducer = (state = initState, action) => {
switch (action.type) {
case WX_API_PROMISIFY:
return Object.assign({}, state, { wxApiPromisify: { isFinish: action.data } });
case WX_GET_CODE:
return Object.assign({}, state, { wxLogin: action.data });
case WX_SYSTEM_INFO:
return Object.assign({}, state, { wxSystemInfo: action.data});
case WX_USER_INFO:
return Object.assign({}, state, { wxUserInfo: action.data});
case WX_ON_LAUNCH:
return Object.assign({}, state, { wxOnLaunch: action.data});
case LOGIN_BEGIN:
var userLogin = action.data;
userLogin.isFetching = false;
return Object.assign({}, state, { userLogin });
case LOGIN_PENDING:
var userLogin = action.data;
userLogin.isFetching = true;
return Object.assign({}, state, { userLogin });
case LOGIN_SUCCESS:
var userLogin = action.data;
userLogin.isFetching = false;
return Object.assign({}, state, { userLogin });
case LOGIN_FAIL:
var userLogin = action.data;
userLogin.isFetching = false;
return Object.assign({}, state, { userLogin });
default:
return state;
}
};
app.js 小程序啓動入口
import regeneratorRuntime from './lib/regenerator-runtime';
import util from './lib/util'; // 自定義的(de)工具庫 import { createStore, applyMiddleware, combineReducers, compose } from './lib/redux';
import thunk from './lib/redux-thunk';
import { Provider } from './lib/mina-redux';
// 所有的(de)reducer 主要(yào / yāo)分爲(wéi / wèi)小程序相關和(hé / huò)業務相關兩類 import { appReducer } from './redux/app.redux';
import { channelReducer } from './redux/channel.redux';
import { practiceReducer } from './redux/practice.redux';
// 小程序項目常用的(de)操作相關action,如處理小程序啓動option,小程序api Promise化,系統信息,小程序用戶信息,api登錄等 import { wxOnLaunch, wxApiPromisify, wxSystemInfo, wxUserInfo, login } from './redux/app.redux';
import composeWithDevTools from './lib/remote-redux-devtool';
import logger from './lib/redux-logger/index';
// 創建倉庫 export const store = createStore(combineReducers({
appReducer,
channelReducer,
practiceReducer,
}), compose(
applyMiddleware(thunk, logger),
composeWithDevTools({ hostname: '127.0.0.1', port: 8080, secure: false })
));
App(Provider(store)({
async onLaunch(option) {
// 處理小程序啓動option
store.dispatch(wxOnLaunch(option));
// 小程序api Promise化
store.dispatch(wxApiPromisify());
// 獲取小程序系統信息
store.dispatch(wxSystemInfo(wx.getSystemInfoSync()));
// 獲取小程序用戶信息
store.dispatch(wxUserInfo(await wx.async.getUserInfo()));
// 用戶登錄(小程序登錄 + api登錄)
login(store.dispatch);
}
}));
home.js 頁面
import regeneratorRuntime from '../../lib/regenerator-runtime';
import util from '../../lib/util';
import { connect } from '../../lib/mina-redux';
import { getChannelList } from '../../redux/channel.redux';
const pageConfig = {
data: {
userLogin: {
data: null,
},
channelList: {
data: [],
isFetching: false,
}
},
async onLoad(option) {
try {
console.log(`? ${getCurrentPages().slice(-1)[0]['__route__']} onLoad`);
util.initPageOnLoad.call(this, option);
wx.showLoading({
title: '加載中...',
mask: true,
});
// 首先一切後端一切api調用必須在(zài)登錄後,這(zhè)裏首先等待app生命周期裏面拿到(dào)登錄權限token await util.waitfor(this.data, ['userLogin', 'data', 'token'], 0, null, 50);
// 之(zhī)後就(jiù)可以(yǐ)愉快地(dì / de)進行各種api數據流了(le/liǎo), await this.getChannelList();
wx.hideLoading();
} catch (error) {
console.error(`? `, error);
// TODO
}
}
}
// 定義頁面上(shàng)的(de)data,這(zhè)個(gè)是(shì)自動注入到(dào)page data上(shàng)的(de),需要(yào / yāo)什麽就(jiù)用什麽,據說(shuō)小程序有page data的(de)性能瓶頸,按需注入吧 const mapStateToPage = state => ({
...state.appReducer,
...state.channelReducer,
});
// 定義頁面上(shàng)的(de)方法,直接this.actionName(param)就(jiù)可以(yǐ)了(le/liǎo)調用了(le/liǎo) const mapDispatchToPage = dispatch => ({
getChannelList: async () => {
await dispatch(getChannelList());
}
});
// 串起來(lái) const nextConfig = connect(mapStateToPage, mapDispatchToPage)(pageConfig);
Page(nextConfig);
3.雖然代碼很長,看起來(lái)可能很不(bù)爽,但是(shì)個(gè)人(rén)覺得已經比之(zhī)前優雅多了(le/liǎo),稍微熟悉一下就(jiù)好,最後看下最終效果(^__^) 嘻嘻……
先開啓調試,然後浏覽器訪問http://localhost:8080即可,或者使用Redux DevTools調試插件
$ npm install -g remotedev-server
$ remotedev --hostname=localhost --port=8080
[Busy] Launching SocketCluster
[Active] SocketCluster started
Version: 6.8.0
Environment: dev
WebSocket engine: uws
Port: 8080
Master PID: 14961
Worker count: 1
Broker count: 1
微信開發者工具 AppData pages/home/home 數據截圖
微信開發者工具 Console 日志截圖
Redux RemoteDev 工具 state數據結構Chart圖
Redux RemoteDev 工具 action時(shí)間軸及Tree結構
Redux RemoteDev 工具 Log monitor
Redux DevTools具有很多強大(dà)的(de)功能,如傳說(shuō)中的(de)“時(shí)光穿梭”等,可以(yǐ)實現很多很好玩的(de)功能
THANKS
[教程]微信小程序集成Redux,引入豐富周邊工具
本文作者:Coolgirl_FE
鏈接:https://juejin.im/post/5a5b70726fb9a01ca871ee51