@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
組件間工作流程圖
圖中藍色的箭頭表示數據流的方向,紅色箭頭表示控制流的方向,他們都是單向流,store中的state
對象是不可修改其內部值的,狀態發生改變後,都會生成一個新的state對象,且只將有變化的部分更新到界面上,這和redux的工作流程是一致的。
這裏只是對redux進行MVC層面的封裝,並未添加新的技術,依然可以使用redux的原生接口,如果想深入瞭解redux,請閱讀其詳細文檔:redux
創建Model
Model管理應用的行爲和數據,Model擁有初始狀態initState
和更新狀態的方法setState(nextState)
,這和Component的state概念類似,業務在執行的過程中,不斷更新state
,當state
發生改變時,和state
綁定的View也會動態的更新。這裏並沒有什麼魔法和創造新的東西,只是將redux的action
、actionCreator
、reducer
、thunk
、saga
等複雜概念簡化爲業務方法和業務數據兩個概念,讓我們更專注於業務實現,代碼也更簡潔.
創建一個計數器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類在實例化的時候會添加getState
、setState
,dispatch
等快捷方法。
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>
)
}
}