React+Redux MVC框架,依賴注入 --@symph/tempo

@symph/tempo

@symph/tempo是一個React應用的輕量框架,基於redux,簡化了redux的使用及其複雜的概念,採用MVC的思想使代碼和應用結構更加清晰,從而可以輕鬆高效的開發。

該框架只提供MVC組件支持,並不包含路由和構建相關的模塊,這樣可以更方便的集成到其它框架中(create-react-app、react-native等)。如果你想要一個全棧的高可用框架,來幫你解決各種技術細節,快速的進入業務開發,請關注 @symph/joy 項目,它基於本項目,爲開發提供更全面的項目能力。

安裝

npm install --save @symph/tempo 

例子

with-create-react-app 使用create-react-app創建空白工程,並集成@symph/tempo

初始化框架

import React, {Component} from 'react';
import {create} from '@symph/tempo'
import {Provider} from '@symph/tempo/provider'

// 創建框架實例,然後就可以使用Controller和Model組件了
const app = create({
  initialState: {}
}, {
  initialReducer: {},
  setupMiddlewares: (middlewares) => {
    return middlewares
  }
})
// 啓動框架
app.start()

// 在React綁定
class App extends Component {
  render() {
    return (
      <Provider app={app}>
        <div> you app content </div>
      </Provider>
    );
  }
}

export default App;

創建MVC組件

  • Model: 管理應用的行爲和數據,普通class類,有初始狀態,業務運行中更新model狀態
  • View: 展示數據,繼承React.Component
  • Controller: 控制View的展示,綁定Model數據到View,響應用戶的操作,調用Model中的業務, 繼承於React.Component

組件間工作流程圖

app work flow

圖中藍色的箭頭表示數據流的方向,紅色箭頭表示控制流的方向,他們都是單向流,store中的state對象是不可修改其內部值的,狀態發生改變後,都會生成一個新的state對象,且只將有變化的部分更新到界面上,這和redux的工作流程是一致的。

這裏只是對redux進行MVC層面的封裝,並未添加新的技術,依然可以使用redux的原生接口,如果想深入瞭解redux,請閱讀其詳細文檔:redux

創建Model

Model管理應用的行爲和數據,Model擁有初始狀態initState和更新狀態的方法setState(nextState),這和Component的state概念類似,業務在執行的過程中,不斷更新state,當state發生改變時,和state綁定的View也會動態的更新。這裏並沒有什麼魔法和創造新的東西,只是將redux的actionactionCreatorreducerthunksaga等複雜概念簡化爲業務方法和業務數據兩個概念,讓我們更專注於業務實現,代碼也更簡潔.

創建一個計數器Model,計數器默認數值爲0,還有一個增加計數的方法。

// models/CalculateModel.js
import model from '@symph/tempo/model'

@model()
export default class CalculateModel {
  //model的唯一標識,通過該名稱來訪問model中的狀態數據
  namespace = 'calculate'

  //初始狀態數據
  initState = {
    counter: 0,
  }

  async add({number}) {
    let {counter} = this.getState()
    counter += number
    // 更新model中的狀態
    this.setState({ 
      counter
    })
  }
}

我們使用@model()將一個類聲明爲Model類,Model類在實例化的時候會添加getStatesetStatedispatch等快捷方法。

Model API

namespace

model將會被註冊到redux store中,由store統一管理model的狀態,使用store.getState()[namespace]來訪問對應model的state, store中不能存在兩個相同的namespace的model。

initState

設置model的初始化狀態,由於model.state可能會被多個async業務方法同時操作,所以爲了保證state的有效性,請在需要使用state時使用getState()來獲取當前state的最新值,並使用setState(nextState)方法更新當前的state。

setState(nextState)

setState(nextState)更新model的狀態,nextState是當前state的一個子集,系統將使用淺拷貝的方式合併當前的狀態。

getState()

getState()獲取當前model的狀態。

getStoreState()

getStoreState()獲取當前整個store的狀態。

dispatch(action)

返回值:Promise,被調用業務的返回值。

在model中使用await this.dispatch(action)調用其它業務方法,這和redux的store.dispatch(action)的使用一樣,由系統分發action到指定的model業務方法中, action.type的格式爲modelNamespace/serviceFunction

如果是調用model自身的業務方法,可以使用await this.otherService({option})的方式,this指的是model本身。

業務方法

async service(action) 業務方法是async函數,內部支持await指令調用其它異步方法。在controller或者其他model中通過dispatch(action)方法調用業務方法並獲得其返回值。

Dva Model

兼容dva風格的model對象,使用方法:Dva Concepts ;

創建Controller

Controller需要申明其依賴哪些Model,以及綁定Model的中的數據,和調用Model中的業務方法。它是一個React組件,可以像其它React組件一樣創建和使用。

下面創建一個計數器Controller,展示Model中存儲的統計值,以及調用Model中的方法來修改統計值。

// models/CalculateController.js
import React, {Component} from 'react'
import {controller, requireModel} from '@symph/tempo/controller'
import {autowire} from '@symph/tempo/autowire'
import CalculateModel from '../models/CalculateModel'

@requireModel(CalculateModel)
@controller((state) => {
  // 綁定calculateModel中的狀態到當前組件
  return {
    counter: state.calculate.counter,  // bind model's state to props
  }
})
export default class CalculateController extends Component {

  @autowire({type: CalculateModel})
  calculateModel
  // or, if support TypeScript
  //@autowire()
  //calculateModel :CalculateModel

  add = async () => {
    let {dispatch} = this.props
    // 調用calculateModel中的業務方法
    this.calculateModel.add({number: 1})
  }

  render() {
    let {counter} = this.props
    return (
      <div>
        <div>counter: {counter}</div>
        <button onClick={this.add}>add 1</button>
      </div>
    )
  }
}

創建和使用Controller的步驟:

  • 使用@controller(mapStateToProps)裝飾器將一個普通的Component聲明爲一個Controller,參數mapStateToProps實現model狀態和組件props屬性綁定,當model的state發生改變時,會觸發組件使用新數據重新渲染界面。

  • 使用@autowire({type: CalculateModel}) 或者 @autowire() calculateModel: CalculateModel(支持TypeScript語法)申明controller需要依賴的model,在運行的時候,該屬性會被注入一個Model的實例,只有在controller運行時,纔會去加載和實例化model。

  • 每個controller的props都會被注入一個redux的dispatch方法,dispatch方法是controller調用model的唯一途徑,該方法的返回值是業務方法的返回值(Promise對象),這和redux的dispatch方法有差別。

如果項目的babel配置還不支持@裝飾器語法,請使用函數調用的方式來聲明Controller,例如:

// models/CalculateController.js
import React, {Component} from 'react'
import {controller, requireModel} from '@symph/tempo/controller'
import CalculateModel from '../models/CalculateModel'

class CalculateController extends Component {
  add = async () => {
    let {dispatch} = this.props
    // 調用calculateModel中的業務方法
    await dispatch({
      type: 'calculate/add',
      number: 1
    })
  }

  render() {
    let {counter} = this.props
    return (
      <div>
        <div>counter: {counter}</div>
        <button onClick={this.add}>add 1</button>
      </div>
    )
  }
}

const Controller = controller((state) => {
 // 綁定calculateModel中的狀態到當前組件
  return {
    counter: state.calculate.counter,  // bind model's state to props
  }
})(CalculateController)
const ModelBound = requireModel(CalculateModel)(Controller)
export default ModelBound

創建View

View是一個普通的React組件,其只負責界面展示,展示的數據來自父組件,通過this.props屬性讀取。

import React, {Component} from 'react'

class TextView extends Component {
  render() {
    let {message} = this.props
    return (
      <div>
        {message}
      </div>
    )
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章