首頁  >  文章  >  web前端  >  Pastate.js 響應式 react 框架之 模組化

Pastate.js 響應式 react 框架之 模組化

不言
不言原創
2018-04-10 16:34:451764瀏覽

這篇文章給大家分享的內容是關於Pastate.js 響應式react 框架之模組化 ,有著一定的參考價值,有需要的朋友可以參考一下

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

Pastate.js Github

模組化實戰任務

如果應用程式比較複雜,有很多個頁面,且一個介面具有比較多的元件和操作時,我們需要對應用程式劃分模組 (Module) 進行管理。
下面我們以一個 班級資訊管理系統 為例,介紹 pastate 應用的模組化機制。

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

#應用程式原始碼: https://github.com/BirdLeeSCUT/pastate-demo

應用的原型如下:

(1) 學生板塊

  • 取得並顯示學生資訊

Pastate.js 響應式 react 框架之 模組化

Pastate.js 響應式 react 框架之 模組化

修改學生資訊

Pastate.js 響應式 react 框架之 模組化

#(2) 課程板塊: 顯示課程資訊

  1. 這個應用程式相對比較簡單,在實際開發中我們不一定需要對其進行模組化設計,在此我們只是用來介紹pastate 模組化機制,讓你知道如何用pastate 處理足夠複雜的應用。

模組劃分Pastate.js 響應式 react 框架之 模組化模組化設計的第一步就是模組劃分,我們先從對我們的班級資訊管理系統進行模組劃分:

  1. 導航模組: Navigator

Pastate.js 響應式 react 框架之 模組化

  1. #學生資訊模組: StudentPanel

Pastate.js 響應式 react 框架之 模組化

課程資訊模組: ClassPanel

  • pastate 模組構成

  • pastate 的模組機制是一種簡潔的flux 模式實現,一個pastate 模組由三個基本元素構成:
  • 狀態(state):儲存模組目前的狀態

檢視(view):模組狀態的顯示邏輯Pastate.js 響應式 react 框架之 模組化

動作(action) :模組動作的處理邏輯

這三個模組元素遵循如下的單向資料流過程:
  • 模組結構
    • 我們透過一個資料夾組織一個模組,以「學生資訊模組」 StudentPanel 為例,新建一個StudentPanel 資料夾,並在資料夾下建立以下檔案:

    • #StudentPanel
    • 模組資料夾

    • #StudentPanel.model.js

      模型檔案:用於定義應用的state 和actions

    StudentPanel.view.jsx
檢視檔:用於定義的檢視元件(元件渲染邏輯)

StudentPanel.css
樣式檔:用於定義用於的樣式(可以改用less 或sass)

模型檔*.model .js

(1)設計模組的state

我們先在模型檔StudentPanel.model.js 下定義模組的state 結構:Pastate.js 響應式 react 框架之 模組化 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 调试。

(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 reducers 和 vuex commit mutations 通过字符串 mutations 名称发起(dispatch) 的模式,这种函数调用的方式在开发时更加方便且不易出错:

  • 无需为了调用方便,定义 actions / mutation 的常量名称

  • 可以友好的支持 编辑器/ IDE 的智能提示

Pastate.js 響應式 react 框架之 模組化

如果你选择使用 pastate 的 mutations 机制,  那么每个 mutation 都要使用同步函数,不要在 mutation 中使用 ajax 请求或 setTimeout 或 Promise 等异步操作。这样相关的浏览器 devtools 才能够显示 有准确意义 的信息:

Pastate.js 響應式 react 框架之 模組化

这种 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 (
            <p className="info-panel">
                {this[&#39;view_&#39; + state.status](state)}
            </p>
        )
    }

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

    view_error() {
        return (
            <p className="info-panel-tip-error">
                加载失败, 请刷新重试
            </p>
        )
    }

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

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 &#39;./StudentPanel.model&#39;

这些 actions 可以直接赋值到组件的 onClick 或 onChange 等位置:

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

这些 actions 也可以在组件的生命周期函数中调用:

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

视图部分还包含样式文件 StudentPanel.css ,在此就不列出了。

如果该模块要需要封装一些当前模块专用的子组件,把子组件定义为独立的文件,并放在与 StudentPanel 模块相同的文件夹下即可。如果需要封装一些多个模块通用的非容器组件,可以考虑把它们放在独立于模块文件夹的其他目录。

导出模块

最后,为了方便调用,我们来为模块做一个封装文件 StudentPanel / index.js,导出模块的元素:

export { default as view } from &#39;./StudentPanel.view&#39;
export { store, actions, initState } from &#39;./StudentPanel.model&#39;

pastate 模块向外导出 view, initState, actions, store 四个元素。

大功告成!这时我们可以尝试在 src / index.js 中引入该模块并渲染出来:

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

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

我们使用 makeApp 函数创建一个 pastate 应用并渲染出来,makeApp 的第一个参数是 根容器,第二个参数是 store 树, 我们现在只有一个模块,所以应用的 store 树只有 StudentPanel 的 store。

自此,我们的第一个模块 StudentPanel 构建完成。

模块的模板文件

我们可以使用模板文件快速创建模块,一个模块的模板文件非常简单,下面以 TemplateModule 模块为例完整给出:

  • /index.js

export { default as view } from &#39;./TemplateModule.view&#39;
export { initState, actions, store } from &#39;./TemplateModule.model&#39;
  • /TemplateModule.model.js

import { createStore } from &#39;pastate&#39;;

const initState = {

}

const actions = {

}

const store = createStore({
    name: &#39;TemplateModule&#39;,
    initState,
    actions
})
const { state } = store
export { initState, actions, store }
  • /TemplateModule.view.jsx

import React from &#39;react&#39;;
import { makeContainer } from &#39;pastate&#39;;
import { initState, actions } from &#39;./ClassPanel.model&#39;;
import &#39;./TemplateModule.css&#39;

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

export default makeContainer(TemplateModule, &#39;template&#39;)
  • /.css

// css 样式文件初始为空,你也可以选用 less 或 sass 来定义样式

这个例子的 demo 源码已包含该模板模块 src/TemplateModule, 你只需把它复制到你的 src 目录下,并右键点击模块文件夹,选择 “在文件夹中查找”,然后把 TemplateModule 字符串全部替换为你想要的模块名称即可:

Pastate.js 響應式 react 框架之 模組化

Pastate.js 響應式 react 框架之 模組化

点击替换之后保存文件。不过目前还不能自动替换文件名,需要手动替换一下。

Pastate 以后将会实现相关的命令行工具,实现一行命令创建新模块等功能,加速 pastate 应用的开发。

下一章,我们来创建另外的模块,并介绍不同模块之间如何协作。

相关推荐:

Pastate.js 响应式框架之数组渲染与操作

Pastate.js 响应式 react 框架之表单渲染与操作

Pastate.js 响应式框架之多组件应用

以上是Pastate.js 響應式 react 框架之 模組化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn