這是 Pastate.js 響應式 react state 管理框架系列教程,歡迎關注,持續更新。 javascript
若是應用比較複雜,有不少個頁面,且一個界面具備比較多的組件和操做時,咱們須要對應用劃分模塊 (Module) 進行管理。
下面咱們以一個 班級信息管理系統 爲例,介紹 pastate 應用的模塊化機制。html
實際體驗:https://birdleescut.github.io/pastate-demovue
應用源碼: https://github.com/BirdLeeSCUT/pastate-demojava
應用的原型以下:react
(1) 學生板塊git
(2) 課程板塊: 顯示課程信息github
這個應用相對比較簡單,在實際開發中咱們不必定須要對其進行模塊化設計,在此咱們只是用於介紹 pastate 模塊化機制,讓你知道如何用 pastate 處理足夠複雜的應用。ajax
模塊化設計的第一步就是模塊劃分,咱們先從對咱們的班級信息管理系統進行模塊劃分:vuex
pastate 的模塊機制是一種簡潔的 flux 模式實現,一個 pastate 模塊由三個基本元素構成:
這三個模塊元素遵循以下的單向數據流過程:
咱們經過一個文件夾組織一個模塊,以 「學生信息模塊」 StudentPanel 爲例,新建一個 StudentPanel 文件夾,並在文件夾下建立如下文件:
StudentPanel
模塊文件夾
StudentPanel.model.js
模型文件:用於定義應用的 state 和 actionsStudentPanel.view.jsx
視圖文件:用於定義的視圖組件(組件渲染邏輯)StudentPanel.css
樣式文件:用於定義用於的樣式(能夠改用 less 或 sass)咱們先在模型文件 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 的 「選擇」 方法而非直接輸入字符串,這能夠爲應用的開發帶來方便並減小無畏的錯誤:賦值時把輸入光標在等號後的引號中間按下 「觸發提示」 快捷鍵便可顯示選項:
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 調試。
以前咱們是把應用的動做邏輯實現爲視圖組件的成員函數,在應用簡單時這種模式會比較直接方便,而當應用複雜且某些操做邏輯須要在不一樣組件甚至模塊間共享時,原理的模式沒法實現。所以咱們把模塊的相關操做邏輯統一放在 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 reducers 和 vuex commit mutations 經過字符串 mutations 名稱發起(dispatch) 的模式,這種函數調用的方式在開發時更加方便且不易出錯:
若是你選擇使用 pastate 的 mutations 機制, 那麼每一個 mutation 都要使用同步函數,不要在 mutation 中使用 ajax 請求或 setTimeout 或 Promise 等異步操做。這樣相關的瀏覽器 devtools 纔可以顯示 有準確意義 的信息:
這種 actions 分類管理的設計體現了 pastate 的精益原則:你能在須要某些高級特性的時候 纔去 且 可以 使用這些高級特性。
咱們能夠像以前那樣簡單地建立 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 應用的開發。
下一章,咱們來建立另外的模塊,並介紹不一樣模塊之間如何協做。