【教程】Pastate.js 響應(yīng)式 react 框架(五) 模塊化

這是 Pastate.js 響應(yīng)式 react state 管理框架系列教程,歡迎關(guān)注趣斤,持續(xù)更新。Pastate.js Github

模塊化實戰(zhàn)任務(wù)

如果應(yīng)用比較復(fù)雜杏节,有很多個頁面唬渗,且一個界面具有比較多的組件和操作時典阵,我們需要對應(yīng)用劃分模塊 (Module) 進行管理。
下面我們以一個 班級信息管理系統(tǒng) 為例镊逝,介紹 pastate 應(yīng)用的模塊化機制壮啊。

實際體驗:https://birdleescut.github.io/pastate-demo

應(yīng)用源碼: https://github.com/BirdLeeSCUT/pastate-demo

應(yīng)用的原型如下:

(1) 學(xué)生板塊

  • 獲取并顯示學(xué)生信息
學(xué)生板塊
  • 修改學(xué)生信息
學(xué)生板塊

(2) 課程板塊: 顯示課程信息

課程板塊

這個應(yīng)用相對比較簡單,在實際開發(fā)中我們不一定需要對其進行模塊化設(shè)計撑蒜,在此我們只是用于介紹 pastate 模塊化機制歹啼,讓你知道如何用 pastate 處理足夠復(fù)雜的應(yīng)用。

模塊劃分

模塊化設(shè)計的第一步就是模塊劃分座菠,我們先從對我們的班級信息管理系統(tǒng)進行模塊劃分:

  1. 導(dǎo)航模塊: Navigator
導(dǎo)航窗模塊
  1. 學(xué)生信息模塊: StudentPanel
學(xué)生信息模塊
  1. 課程信息模塊: ClassPanel
課程信息模塊

pastate 模塊構(gòu)成

pastate 的模塊機制是一種簡潔的 flux 模式實現(xiàn)狸眼,一個 pastate 模塊由三個基本元素構(gòu)成:

  • 狀態(tài) (state):保存模塊當(dāng)前的狀態(tài)
  • 視圖 (view):模塊狀態(tài)的顯示邏輯
  • 動作 (action):模塊動作的處理邏輯

這三個模塊元素遵循如下的單向數(shù)據(jù)流過程:

pastate 應(yīng)用數(shù)據(jù)流

模塊結(jié)構(gòu)

我們通過一個文件夾組織一個模塊,以 “學(xué)生信息模塊” StudentPanel 為例浴滴,新建一個 StudentPanel 文件夾拓萌,并在文件夾下創(chuàng)建以下文件:

  • StudentPanel 模塊文件夾
    • StudentPanel.model.js 模型文件:用于定義應(yīng)用的 state 和 actions
    • StudentPanel.view.jsx 視圖文件:用于定義的視圖組件(組件渲染邏輯)
    • StudentPanel.css 樣式文件:用于定義用于的樣式(可以改用 less 或 sass)

模型文件 *.model.js

(1)設(shè)計模塊的 state

我們先在模型文件 StudentPanel.model.js 下定義模塊的 state 結(jié)構(gòu):
StudentPanel.model.js

const initState = {
    initialized: false, // 初始化狀態(tài)
    /** @type { "loading" | "ok" | "error" } */
    status: 'loading', // 加載狀態(tài)
    isEditting: false, // 是否在編輯中
    selected: 0, // 選中的學(xué)生
    /** @type {studentType[]} */
    students: [] // 學(xué)生數(shù)組
}

const studentType = {
    name: '張小明',
    studentNumber: '2018123265323',
    age: 22,
    isBoy: true,
    introduction: '我是簡介'
}
...

與之前一樣,我們通過配合 jsDoc 注釋升略,把 state 結(jié)構(gòu)的定義和初始值得定義一起進行微王。

Tips: 建議使用上面定義 status 屬性的模式定義 “枚舉字符串” 類型,對這種枚舉值進行賦值時盡量采用 intelSence 的 “選擇” 方法而非直接輸入字符串品嚣,這可以為應(yīng)用的開發(fā)帶來方便并減少無畏的錯誤:賦值時把輸入光標(biāo)在等號后的引號中間按下 “觸發(fā)提示” 快捷鍵即可顯示選項:

選擇 status 的值

State mock 區(qū)域: 我們在開發(fā)視圖時炕倘,需要對 state 的狀態(tài)進行完備測試,比如要讓 state.status 分別等于 "loading" | "ok" | "error" 翰撑、讓 state.isEditting 等于 true | false 去完備地測試模塊的渲染邏輯罩旋,這時我們不要直接更改 initState 的值,而是把 initState 下方作為一個 state mock 測試區(qū)域, 對 state 進行修改以實現(xiàn) mock :

const initState = {...}
const studentType ={...}

/***** MOCK AREA *****/
// initState.status = 'ok'
// initState.isEditting = true
// initState.students = [studentType, studentType]

你可以根據(jù)開發(fā)調(diào)試需求新建 mock 行眶诈,或通過注釋控制某個 mock 行是否生效涨醋,以此來使應(yīng)用處于某個中間 state 狀態(tài),方便調(diào)試册养。并在模塊開發(fā)完成時把 mock 區(qū)域全部注釋即可东帅,這種模式可以有效地管理 mock 過程压固。

模塊的 initState 是對模塊的一種 “定義”球拦,它具有“文檔屬性”,而 mock state 是對應(yīng)用進行調(diào)試時執(zhí)行的臨時動態(tài)操作帐我,如果通過直接修改 initState 來進行 mock坎炼,我們會破壞模塊的定義,然后又嘗試憑記憶對定義進行恢復(fù)拦键,這個過程容易出錯或遺漏谣光,特別是當(dāng) state 變得復(fù)雜的時候。所以我們推薦采用 MOCK AREA 對 state 進行 mock 調(diào)試芬为。

(2)定義模塊的 actions

之前我們是把應(yīng)用的動作邏輯實現(xiàn)為視圖組件的成員函數(shù)萄金,在應(yīng)用簡單時這種模式會比較直接方便蟀悦,而當(dāng)應(yīng)用復(fù)雜且某些操作邏輯需要在不同組件甚至模塊間共享時,原理的模式無法實現(xiàn)氧敢。因此我們把模塊的相關(guān)操作邏輯統(tǒng)一放在 actions 中進行管理:

StudentPanel.model.js

const actions = {
    init(){ },
    loadStudents(){ },
    switchEditting(){ },
    /** @param {number} index 學(xué)生數(shù)組索引號 */
    selectStudent(index){ },
    increaseAge(){ },
    decreaseAge(){ }
}

在初步的 actions 聲明階段日戈,我們只需把 actions 的名字和參數(shù)聲明出來,在應(yīng)用開發(fā)過程中再逐漸實現(xiàn)其業(yè)務(wù)邏輯孙乖。你可以考慮使用 jsDoc 對 action 的用途和參數(shù)進行注釋說明浙炼。當(dāng)模塊簡單的時候,你可以直接在 actions 中直接實現(xiàn)同步更新 state 的操作和異步從后臺獲取數(shù)據(jù)等操作唯袄,pastate 不對 actions 的實現(xiàn)的內(nèi)容做限制弯屈,不需要像 redux 或 vuex 一樣規(guī)定一定要把同步和異步邏輯的分開實現(xiàn),在 pastate 中恋拷,當(dāng)你認(rèn)為有必要時才那樣做就好了资厉。

多級 actions 管理: 當(dāng)模塊的 actions 比較多的時候,我們可以采用多級屬性的模式對 actions 進行分類管理, 具體的分類方法和分類級別根據(jù)具體需要自行定義即可蔬顾,如下:

const actions = {
    init(){ },
    handle:{
        handleBtnClick(){ },
        handleXxx1(){ },
        handleXxx2(){ }
    },
    ajax:{
        getStudentsData(){ },
        getXxx(){ },
        postXxx(data){ }
    }
}

** mutations 模式**: 如果你的模塊比較復(fù)雜酌住,想遵循 redux 或 vuex 把對 state 同步操作 和 異步動作兩類操作分類管理的模式,那么你可以對 state 的同步操作放在 actions.mutations 分類下阎抒,pastate 提供特殊中間件對 mutations 提供而外的開發(fā)調(diào)試支持酪我,詳見 規(guī)模化 章節(jié)且叁。

const actions = {
    init(){ },
    handleBtnClick(){ },
    getStudentsData(){ },
    mutations:{
        increaseAge(){ },
        decreaseAge(){ }
    }
}

Mutations 其實就是一些同步的 state 更新函數(shù),你可以通過其他普通 actions 調(diào)用 mutations, 或直接在視圖中調(diào)用 mutations。比起 redux dispatch actions to reducersvuex commit mutations 通過字符串 mutations 名稱發(fā)起(dispatch) 的模式未妹,這種函數(shù)調(diào)用的方式在開發(fā)時更加方便且不易出錯:

  • 無需為了調(diào)用方便埋凯,定義 actions / mutation 的常量名稱
  • 可以友好的支持 編輯器/ IDE 的智能提示
編輯器 mutations 提示

如果你選擇使用 pastate 的 mutations 機制, 那么每個 mutation 都要使用同步函數(shù),不要在 mutation 中使用 ajax 請求或 setTimeout 或 Promise 等異步操作塞关。這樣相關(guān)的瀏覽器 devtools 才能夠顯示 有準(zhǔn)確意義 的信息:

瀏覽器開發(fā)工具中 mutations 作用效果顯示

這種 actions 分類管理的設(shè)計體現(xiàn)了 pastate 的精益原則:你能在需要某些高級特性的時候 才去能夠 使用這些高級特性蜻牢。

(3)創(chuàng)建并配置模塊的 store

我們可以像之前那樣簡單地創(chuàng)建 store:
StudentPanel.model.js

import { Pastore } from 'pastate'
const initState = {...}
const actions = {...}
...

const store = new Pastore(initState);
/** @type {initState} */
let state = store.state;

export { initState, actions, store}

Pastate 采用一種 可選的 的 actions 注入模式,你可以自愿決定是否把 actions 注入 store框往。 把 actions 注入 store 后清焕,可利用 pastate 的中間件機制對 actions 進行統(tǒng)一管理控制,具有較強的可擴展性键畴。例如我們可以使用 logActions 中間件對每次 actions 的調(diào)用在控制臺進行 log突雪,并使用 dispalyActionNamesInReduxTool 中間件 對把 mutations 名稱顯示出來咏删,以便于調(diào)試:

import { ..., logActions, dispalyActionNamesInReduxTool } from 'pastate'

...
const store = new Pastore(initState);
store.name = 'StudentPanel';
store.actionMiddlewares = [logActions(), dispalyActionNamesInReduxTool(true)]
store.actions = actions;

/** @type {initState} */
let state = store.state;

export { initState, actions, store}

如果你覺得上面的定義方式比較瑣碎督函,你可以直接使用 pastate 提供的工廠函數(shù) createStore 來定義一個完整地 store:

import { ..., createStore, logActions, dispalyActionNamesInReduxTool } from 'pastate'

const store = createStore({
    name: 'StudentPanel',
    initState: initState,
    actions: actions,
    middlewares: [logActions(), dispalyActionNamesInReduxTool(true)]
})
const { state } = store // createStore 具有良好的泛型定義侨核,無需額外的 jsdoc 注釋即可獲取 state 的結(jié)構(gòu)信息

你也可以進一步把中間件配置為僅在開發(fā)環(huán)境下生效的模式, 生產(chǎn)環(huán)境下無效搓译。Pastate 中間件的詳細(xì)內(nèi)容請查看規(guī)耐慵Γ化章節(jié)涯冠。

視圖部分

我們創(chuàng)建 StudentPanel.view.jsx 文件來保存我們的模塊視圖, 視圖定義和原來的模式類似:
StudentPanel.view.jsx

import React from 'react'
import { makeContainer, Input, Select} from 'pastate'
import { initState, actions } from './StudentPanel.model'
import './StudentPanel.css'

const isBoyOptions = [{
    value: true,
    tag: '男'
},{
    value: false,
    tag: '女'
}]

class StudentPanel extends React.PureComponent {

    componentDidMount(){
        actions.init()
    }

    render() {
        let state = this.props.state
        return (
            <div className="info-panel">
                {this['view_' + state.status](state)}
            </div>
        )
    }

    view_loading() {
        return (
            <div className="info-panel-tip-loading">
                加載中...
            </div>
        )
    }

    view_error() {
        return (
            <div className="info-panel-tip-error">
                加載失敗, 請刷新重試
            </div>
        )
    }

    /** @param {initState} state */
    view_ok(state) {
        let selectedStudent = state.students[state.selected];
        return (
            <div className="info-panel-ok">
                ...
            </div>
        )
    }
}

export default makeContainer(StudentPanel)

Pastate 模塊化需要實現(xiàn)一種多模塊可以互相協(xié)作的機制。因此我們不再使用 makeOnyContainer 唯一地綁定一個視圖組件與對應(yīng)的 store派任。首先,我們會用各模塊的 store 生成一個全局的 store 樹,并使用 makeContainer 把模塊的視圖封裝為引用全局 store 的某些節(jié)點的容器篓像。

我們目前只有一個模塊遗淳,此處簡單地調(diào)用 makeContainer(StudentPanel) 讓 StudentPanel 引用全局的 store 樹 的根節(jié)點的 state ,我們可以為 makeContainer 指定第二個參數(shù)屈暗,指明引用 store 樹 的哪些子節(jié)點,詳情會在下一章介紹脂男。

在上面視圖組件的代碼中,我們引入了 model 中的 actions:

import { store, initState, actions } from './StudentPanel.model'

這些 actions 可以直接賦值到組件的 onClick 或 onChange 等位置:

<button className="..." onClick={actions.increaseAge} > + </button>

這些 actions 也可以在組件的生命周期函數(shù)中調(diào)用:

...
componentDidMount(){
    actions.init()
}
...

視圖部分還包含樣式文件 StudentPanel.css 宰翅,在此就不列出了。

如果該模塊要需要封裝一些當(dāng)前模塊專用的子組件汁讼,把子組件定義為獨立的文件淆攻,并放在與 StudentPanel 模塊相同的文件夾下即可。如果需要封裝一些多個模塊通用的非容器組件瓶珊,可以考慮把它們放在獨立于模塊文件夾的其他目錄。

導(dǎo)出模塊

最后,為了方便調(diào)用蝉娜,我們來為模塊做一個封裝文件 StudentPanel / index.js南缓,導(dǎo)出模塊的元素:

export { default as view } from './StudentPanel.view'
export { store, actions, initState } from './StudentPanel.model'

pastate 模塊向外導(dǎo)出 view, initState, actions, store 四個元素荧呐。

大功告成!這時我們可以嘗試在 src / index.js 中引入該模塊并渲染出來:

import ReactDOM from 'react-dom';
import { makeApp } from 'pastate';
import * as StudentPanel from './StudentPanel';

ReactDOM.render(
    makeApp(<StudentPanel.view />, StudentPanel.store), 
    document.getElementById('root')
);
...

我們使用 makeApp 函數(shù)創(chuàng)建一個 pastate 應(yīng)用并渲染出來获雕,makeApp 的第一個參數(shù)是 根容器薄腻,第二個參數(shù)是 store 樹收捣, 我們現(xiàn)在只有一個模塊届案,所以應(yīng)用的 store 樹只有 StudentPanel 的 store。

自此罢艾,我們的第一個模塊 StudentPanel 構(gòu)建完成楣颠。

模塊的模板文件

我們可以使用模板文件快速創(chuàng)建模塊,一個模塊的模板文件非常簡單咐蚯,下面以 TemplateModule 模塊為例完整給出:

  • /index.js
export { default as view } from './TemplateModule.view'
export { initState, actions, store } from './TemplateModule.model'
  • /TemplateModule.model.js
import { createStore } from 'pastate';

const initState = {

}

const actions = {

}

const store = createStore({
    name: 'TemplateModule',
    initState,
    actions
})
const { state } = store
export { initState, actions, store }
  • /TemplateModule.view.jsx
import React from 'react';
import { makeContainer } from 'pastate';
import { initState, actions } from './ClassPanel.model';
import './TemplateModule.css'

class TemplateModule extends React.PureComponent{
    render(){
        /** @type {initState} */
        const state = this.props.state;
        return (
            <div>
                TemplateModule
            </div>
        )
    }
}

export default makeContainer(TemplateModule, 'template')
  • /.css
// css 樣式文件初始為空童漩,你也可以選用 less 或 sass 來定義樣式

這個例子的 demo 源碼已包含該模板模塊 src/TemplateModule, 你只需把它復(fù)制到你的 src 目錄下,并右鍵點擊模塊文件夾春锋,選擇 “在文件夾中查找”矫膨,然后把 TemplateModule 字符串全部替換為你想要的模塊名稱即可:

查找
替換

點擊替換之后保存文件。不過目前還不能自動替換文件名期奔,需要手動替換一下侧馅。

Pastate 以后將會實現(xiàn)相關(guān)的命令行工具,實現(xiàn)一行命令創(chuàng)建新模塊等功能呐萌,加速 pastate 應(yīng)用的開發(fā)馁痴。

下一章,我們來創(chuàng)建另外的模塊肺孤,并介紹不同模塊之間如何協(xié)作罗晕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赠堵,隨后出現(xiàn)的幾起案子小渊,更是在濱河造成了極大的恐慌,老刑警劉巖茫叭,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粤铭,死亡現(xiàn)場離奇詭異,居然都是意外死亡杂靶,警方通過查閱死者的電腦和手機梆惯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吗垮,“玉大人垛吗,你說我怎么就攤上這事∷傅牵” “怎么了怯屉?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵蔚舀,是天一觀的道長。 經(jīng)常有香客問我锨络,道長赌躺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任羡儿,我火速辦了婚禮礼患,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掠归。我一直安慰自己缅叠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布虏冻。 她就那樣靜靜地躺著肤粱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厨相。 梳的紋絲不亂的頭發(fā)上领曼,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音蛮穿,去河邊找鬼庶骄。 笑死,一個胖子當(dāng)著我的面吹牛绪撵,可吹牛的內(nèi)容都是我干的瓢姻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼音诈,長吁一口氣:“原來是場噩夢啊……” “哼幻碱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起细溅,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤褥傍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后喇聊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恍风,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年誓篱,在試婚紗的時候發(fā)現(xiàn)自己被綠了朋贬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡窜骄,死狀恐怖锦募,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邻遏,我是刑警寧澤糠亩,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布虐骑,位于F島的核電站,受9級特大地震影響赎线,放射性物質(zhì)發(fā)生泄漏廷没。R本人自食惡果不足惜敲街,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一苟鸯、第九天 我趴在偏房一處隱蔽的房頂上張望放典。 院中可真熱鬧极祸,春花似錦、人聲如沸有勾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蓖扑。三九已至,卻和暖如春台舱,著一層夾襖步出監(jiān)牢的瞬間律杠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工竞惋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留柜去,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓拆宛,卻偏偏與公主長得像嗓奢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浑厚,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容