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

這是 Pastate.js 響應式 react state 管理框架系列教程,歡迎關注,持續更新。 javascript

Pastate.js Githubcss

模塊化實戰任務

若是應用比較複雜,有不少個頁面,且一個界面具備比較多的組件和操做時,咱們須要對應用劃分模塊 (Module) 進行管理。
下面咱們以一個 班級信息管理系統 爲例,介紹 pastate 應用的模塊化機制。html

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

應用源碼: https://github.com/BirdLeeSCUT/pastate-demojava

應用的原型以下:react

(1) 學生板塊git

  • 獲取並顯示學生信息

學生板塊

  • 修改學生信息

學生板塊

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

課程板塊

這個應用相對比較簡單,在實際開發中咱們不必定須要對其進行模塊化設計,在此咱們只是用於介紹 pastate 模塊化機制,讓你知道如何用 pastate 處理足夠複雜的應用。ajax

模塊劃分

模塊化設計的第一步就是模塊劃分,咱們先從對咱們的班級信息管理系統進行模塊劃分:vuex

  1. 導航模塊: Navigator

導航窗模塊

  1. 學生信息模塊: StudentPanel

學生信息模塊

  1. 課程信息模塊: ClassPanel

課程信息模塊

pastate 模塊構成

pastate 的模塊機制是一種簡潔的 flux 模式實現,一個 pastate 模塊由三個基本元素構成:

  • 狀態 (state):保存模塊當前的狀態
  • 視圖 (view):模塊狀態的顯示邏輯
  • 動做 (action):模塊動做的處理邏輯

這三個模塊元素遵循以下的單向數據流過程:

pastate 應用數據流

模塊結構

咱們經過一個文件夾組織一個模塊,以 「學生信息模塊」 StudentPanel 爲例,新建一個 StudentPanel 文件夾,並在文件夾下建立如下文件:

  • StudentPanel 模塊文件夾

    • StudentPanel.model.js 模型文件:用於定義應用的 state 和 actions
    • StudentPanel.view.jsx 視圖文件:用於定義的視圖組件(組件渲染邏輯)
    • StudentPanel.css 樣式文件:用於定義用於的樣式(能夠改用 less 或 sass)

模型文件 *.model.js

(1)設計模塊的 state

咱們先在模型文件 StudentPanel.model.js 下定義模塊的 state 結構:
StudentPanel.model.js

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

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

與以前同樣,咱們經過配合 jsDoc 註釋,把 state 結構的定義和初始值得定義一塊兒進行。

Tips: 建議使用上面定義 status 屬性的模式定義 「枚舉字符串」 類型,對這種枚舉值進行賦值時儘可能採用 intelSence 的 「選擇」 方法而非直接輸入字符串,這能夠爲應用的開發帶來方便並減小無畏的錯誤:賦值時把輸入光標在等號後的引號中間按下 「觸發提示」 快捷鍵便可顯示選項:

選擇 status 的值

State mock 區域: 咱們在開發視圖時,須要對 state 的狀態進行完備測試,好比要讓 state.status 分別等於 "loading" | "ok" | "error" 、讓 state.isEditting 等於 true | false 去完備地測試模塊的渲染邏輯,這時咱們不要直接更改 initState 的值,而是把 initState 下方做爲一個 state mock 測試區域, 對 state 進行修改以實現 mock :

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

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

你能夠根據開發調試需求新建 mock 行,或經過註釋控制某個 mock 行是否生效,以此來使應用處於某個中間 state 狀態,方便調試。並在模塊開發完成時把 mock 區域所有註釋便可,這種模式能夠有效地管理 mock 過程。

模塊的 initState 是對模塊的一種 「定義」,它具備「文檔屬性」,而 mock state 是對應用進行調試時執行的臨時動態操做,若是經過直接修改 initState 來進行 mock,咱們會破壞模塊的定義,而後又嘗試憑記憶對定義進行恢復,這個過程容易出錯或遺漏,特別是當 state 變得複雜的時候。因此咱們推薦採用 MOCK AREA 對 state 進行 mock 調試。

(2)定義模塊的 actions

以前咱們是把應用的動做邏輯實現爲視圖組件的成員函數,在應用簡單時這種模式會比較直接方便,而當應用複雜且某些操做邏輯須要在不一樣組件甚至模塊間共享時,原理的模式沒法實現。所以咱們把模塊的相關操做邏輯統一放在 actions 中進行管理:

StudentPanel.model.js

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

在初步的 actions 聲明階段,咱們只需把 actions 的名字和參數聲明出來,在應用開發過程當中再逐漸實現其業務邏輯。你能夠考慮使用 jsDoc 對 action 的用途和參數進行註釋說明。當模塊簡單的時候,你能夠直接在 actions 中直接實現同步更新 state 的操做和異步從後臺獲取數據等操做,pastate 不對 actions 的實現的內容作限制,不須要像 redux 或 vuex 同樣規定必定要把同步和異步邏輯的分開實現,在 pastate 中,當你認爲有必要時才那樣作就行了。

多級 actions 管理: 當模塊的 actions 比較多的時候,咱們能夠採用多級屬性的模式對 actions 進行分類管理, 具體的分類方法和分類級別根據具體須要自行定義便可,以下:

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

mutations 模式: 若是你的模塊比較複雜,想遵循 redux 或 vuex 把對 state 同步操做 和 異步動做兩類操做分類管理的模式,那麼你能夠對 state 的同步操做放在 actions.mutations 分類下,pastate 提供特殊中間件對 mutations 提供而外的開發調試支持,詳見 規模化 章節。

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

Mutations 其實就是一些同步的 state 更新函數,你能夠經過其餘普通 actions 調用 mutations, 或直接在視圖中調用 mutations。比起 redux dispatch actions to reducersvuex commit mutations 經過字符串 mutations 名稱發起(dispatch) 的模式,這種函數調用的方式在開發時更加方便且不易出錯:

  • 無需爲了調用方便,定義 actions / mutation 的常量名稱
  • 能夠友好的支持 編輯器/ IDE 的智能提示

編輯器 mutations 提示

若是你選擇使用 pastate 的 mutations 機制, 那麼每一個 mutation 都要使用同步函數,不要在 mutation 中使用 ajax 請求或 setTimeout 或 Promise 等異步操做。這樣相關的瀏覽器 devtools 纔可以顯示 有準確意義 的信息:

瀏覽器開發工具中 mutations 做用效果顯示

這種 actions 分類管理的設計體現了 pastate 的精益原則:你能在須要某些高級特性的時候 纔去可以 使用這些高級特性。

(3)建立並配置模塊的 store

咱們能夠像以前那樣簡單地建立 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 進行統一管理控制,具備較強的可擴展性。例如咱們可使用 logActions 中間件對每次 actions 的調用在控制檯進行 log,並使用 dispalyActionNamesInReduxTool 中間件 對把 mutations 名稱顯示出來,以便於調試:

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 提供的工廠函數 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 的結構信息

你也能夠進一步把中間件配置爲僅在開發環境下生效的模式, 生產環境下無效。Pastate 中間件的詳細內容請查看規模化章節。

視圖部分

咱們建立 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 模塊化須要實現一種多模塊能夠互相協做的機制。所以咱們再也不使用 makeOnyContainer 惟一地綁定一個視圖組件與對應的 store。首先,咱們會用各模塊的 store 生成一個全局的 store 樹,並使用 makeContainer 把模塊的視圖封裝爲引用全局 store 的某些節點的容器。

咱們目前只有一個模塊,此處簡單地調用 makeContainer(StudentPanel) 讓 StudentPanel 引用全局的 store 樹 的根節點的 state ,咱們能夠爲 makeContainer 指定第二個參數,指明引用 store 樹 的哪些子節點,詳情會在下一章介紹。

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

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

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

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

這些 actions 也能夠在組件的生命週期函數中調用:

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

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

若是該模塊要須要封裝一些當前模塊專用的子組件,把子組件定義爲獨立的文件,並放在與 StudentPanel 模塊相同的文件夾下便可。若是須要封裝一些多個模塊通用的非容器組件,能夠考慮把它們放在獨立於模塊文件夾的其餘目錄。

導出模塊

最後,爲了方便調用,咱們來爲模塊作一個封裝文件 StudentPanel / index.js,導出模塊的元素:

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

pastate 模塊向外導出 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 函數建立一個 pastate 應用並渲染出來,makeApp 的第一個參數是 根容器,第二個參數是 store 樹, 咱們如今只有一個模塊,因此應用的 store 樹只有 StudentPanel 的 store。

自此,咱們的第一個模塊 StudentPanel 構建完成。

模塊的模板文件

咱們可使用模板文件快速建立模塊,一個模塊的模板文件很是簡單,下面以 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, 你只需把它複製到你的 src 目錄下,並右鍵點擊模塊文件夾,選擇 「在文件夾中查找」,而後把 TemplateModule 字符串所有替換爲你想要的模塊名稱便可:

查找

替換

點擊替換以後保存文件。不過目前還不能自動替換文件名,須要手動替換一下。

Pastate 之後將會實現相關的命令行工具,實現一行命令建立新模塊等功能,加速 pastate 應用的開發。

下一章,咱們來建立另外的模塊,並介紹不一樣模塊之間如何協做。

相關文章
相關標籤/搜索