Meteor Mantra 系列文章:
Meteor Mantra 介紹(一)- 基本概念
Meteor Mantra 介紹(二)- 前端架構詳解
Meteor Mantra 介紹(三)- 后端架構解釋
Meteor Mantra 介紹(四)- 博客例子前端代碼解讀
Meteor Mantra 介紹(五)- 博客例子后端代碼解讀
Meteor Mantra 介紹(六)- 使用 mantra-cli 命令行生成源碼
Mantra 是一種基于 Meteor 1.3+份名、React 和 ES2015 的 Meteor 應用架構薄货,主要作用讓 Meteor 應用代碼架構標準化辰狡,特別是前端部分慌申,當然它對后端代碼的組織也有要求呻疹。注意 Mantra 不是一個框架蝌数,而是一套如何構建 Meteor App 的說明打毛,同時也有配套的開源庫來提高代碼編寫效率物遇。
如果你熟悉 React,Mantra 類似于 Flux娘赴,講究的是對數據流的控制规哲,但是規(guī)定得更加細致。
目的
Mantra 的目的是寫出更易于理解和維護的代碼诽表。它對幾乎所有的情況都有一個標準唉锌,另外還為 Meteor App 增加單元測試覆蓋率隅肥。
和 Perl 類似,JavaScript 的一個難點就是同樣一個問題有太多實現方式袄简,而且可能都是最佳解決方案腥放。所以經常是不同的人使用不同的方法。Mantra 讓 Meteor App 有一個統一的結構绿语,遵循相同的標準秃症,就像設計模式一樣,降低大家理解代碼結構的難度吕粹,確保模塊之間解耦种柑,像 Flux 一樣讓數據單向流動,這樣維護代碼更加容易昂芜。
Mantra 使用的原則很有前瞻性莹规,能夠很長時間不會過時,同時也允許其他人做必要的改變泌神。
偏重前端
現在的 Web App 的大部分代碼都是在前端良漱。后端的代碼邏輯相對簡單也好管理,后端的難點在于性能優(yōu)化欢际,特別是大并發(fā)的處理母市,數據庫等。
Mantra 的核心在如何組織客戶端代碼损趋。它倡導前后端代碼分離患久,前端不用知道后端代碼是如何實現的,但是可以代碼共享浑槽。因為是基于 React 又側重前端蒋失,所以 Mantra 很類似 React 的那些標準,例如 Flux桐玻,Redux 等篙挽,解決的問題也類似,都是控制數據流 data flow镊靴,讓代碼更易理解維護铣卡。如果你對 React 熟悉,理解 Mantra 就不難偏竟。如果理解有困難煮落,建議多看看 React 的高級用法,例如 stateless/pure function踊谋,Higher Order Components 等蝉仇。
Mantra 不相信 Universal App,就是不相信一套前端代碼適應所有終端平臺。它鼓勵一套后端代碼量淌,但是為每個前端平臺開發(fā)單獨的 app 來提高用戶體驗吏砂,盡量通過模塊化來共享代碼芯砸。
其他 Mantra 的基本介紹可以參看這篇中文翻譯 http://www.reibang.com/p/96d6b8e64c3a
下面我來詳細解釋 Mantra 的各個部件芬位。
這里介紹的順序和文檔里的不一樣摸袁,主要是先從新的概念介紹撼短。下圖是一個典型的 Mantra App 的 work flow祈秕。
Application Context
應用上下文 context 對所有 action 和 container 開放讀取牺堰,所以這是你分享變量的地方梁呈。
import * as Collections from '/lib/collections';
import {Meteor} from 'meteor/meteor';
import {FlowRouter} from 'meteor/kadira:flow-router';
import {ReactiveDict} from 'meteor/reactive-dict';
import {Tracker} from 'meteor/tracker';
export default function () {
return {
Meteor,
FlowRouter,
Collections,
LocalState: new ReactiveDict(),
Tracker
};
}
從上面例子中可以看出缨伊,context 可以讓大家少寫重復的代碼摘刑,又可以在不同模塊之間分享變量。
Actions
處理業(yè)務邏輯的模塊刻坊。包括驗證枷恕,狀態(tài)管理和遠程數據交互。
Action 就是一個簡單的函數而已谭胚,第一個參數必須是應用的上下文 Context徐块。Action 不得使用引入除了參數以外的任何變量和模塊,甚至全局變量灾而,但是可以使用庫函數胡控。
export default {
create({Meteor, LocalState, FlowRouter}, title, content) {
if (!title || !content) {
return LocalState.set('SAVING_ERROR', 'Title & Content are required!');
}
LocalState.set('SAVING_ERROR', null);
const id = Meteor.uuid();
// There is a method stub for this in the config/method_stubs
// That's how we are doing latency compensation
Meteor.call('posts.create', id, title, content, (err) => {
if (err) {
return LocalState.set('SAVING_ERROR', err.message);
}
});
FlowRouter.go(`/post/${id}`);
},
clearErrors({LocalState}) {
return LocalState.set('SAVING_ERROR', null);
}
};
UI
Mantra 只使用 React 作為 UI 組件。
在 UI 組件內部不需要知道 App 的其他任何內容旁趟,也不應該讀取和修改應用的 state昼激。UI 使用到的數據和事件應該由 props 從 container 傳入,或者通過事件作為 action props 傳入锡搜。如果 UI 組件使用到本地 state橙困,那么這個 state 不應該被外部的任何組件使用,僅限于組件內部使用耕餐。
Mantra 文檔里給出的代碼示例:
import React from 'react';
const PostList = ({posts}) => (
<div className='postlist'>
<ul>
{posts.map(post => (
<li key={post._id}>
<a href={`/post/${post._id}`}>{post.title}</a>
</li> ))}
</ul>
</div>
);
export default PostList;
上面的例子代碼就是 React 里的無狀態(tài)純函數實現凡傅,UI 只負責展示界面,沒有邏輯蛾方、狀態(tài)等處理像捶。
State 管理
有兩種狀態(tài):本地狀態(tài)(客戶端)和遠程狀態(tài)(服務器)。本地狀態(tài)不和外界發(fā)生聯系桩砰;遠程狀態(tài)需要和外界拓春,例如數據庫同步數據。
類似 Flux 里的 store 概念 (可參考 使用 Meteor 和 React 開發(fā) Web App
)亚隅,Meteor 有不同的方式實現硼莽,例如 MiniMongo,ReactiveDict 等。Mantra 在這方面很靈活懂鸵,沒有要求用哪一種偏螺。但是還是有一些規(guī)則
- Action 里可以讀寫 state
- Container 里只能讀 state
- UI 組件里既不能讀也不能寫 state,只能由 props 傳入
Dependency Injection 依賴注入
首先匆光,什么是依賴套像?Mantra 有兩種依賴
- context - 通常就是配置,models 和各種數據
- actions - 業(yè)務邏輯终息。每個 action 都以 context 為第一個參數
例如:
const context = {
DB,
Router,
appName: 'My Blog'
};
const actions = {
posts: {
create({DB, Router}, title, content) {
const id = String(Math.random());
DB.createPost(id, title, content);
Router.go(`/post/${id}`);
}
}
};
然后注入依賴夺巩。Mantra 使用 react-simple-di 這個包來進行依賴注入。背后其實就是 React context周崭。這個包接受 Context 和 Actions 作為依賴柳譬。
import {injectDeps} from 'react-simple-di';
import Layout from './layout.jsx';
// 上面定義的 context 和 actions 定義在這里
const LayoutWithDeps = injectDeps(context, actions)(Layout);
現在 LayoutWithDeps
就可以在 app 里隨意使用了。
如何使用依賴续镇?
首先創(chuàng)建一個 UI 組件美澳。可以看到這個組件的依賴是通過 props 傳入的
class CreatePost extends React.Component {
render() {
const {appName} = this.props;
return (
<div>
Create a blog post on app: ${appName}. <br/>
<button onClick={this.create.bind(this)}>Create Now</button>
</div>
);
}
create() {
const {createPost} = this.props;
createPost('My Blog Title', 'Some Content');
}
}
使用依賴
const {useDeps} from 'react-simple-di';
// 前面定義的 CreatePost react 組件
const depsToPropsMapper = (context, actions) => ({
appName: context.appName,
createPost: actions.posts.create
});
const CreatePostWithDeps = useDeps(depsToPropsMapper)(CreatePost);
如果你沒有定義自己的 mapper 函數(就是上面的 depsToPropsMapper)摸航, useDeps 將使用下面的默認 mapper 函數制跟,這樣就可以直接使用 context 和 actions 了。
const mapper = (context, actions) => ({
context: () => context,
actions: () => actions
});
Mantra 使用依賴注入的目的是隔離代碼忙厌。例如隔離 UI 組件和 actions凫岖。
一旦配置好,Applicaton Context 就會被注入到把 Context 作為第一參數的 action逢净。
Container 同樣也能讀取 Application Context哥放。
不能在子組件里注入依賴,只能是最上層組件爹土,通常就是 Layout Component甥雕,例如下面代碼。
import React from 'react';
export default function (injectDeps) {
// See: Injecting Deps
const MainLayoutCtx = injectDeps(MainLayout);
// Routes related code
}
Container
Container 的作用是集成胀茵、組裝數據社露。它的中文意思是容器,里面包裹的就是 UI 組件琼娘。主要功能:
- 處理 state峭弟,處理后把值通過 props 傳入 UI 組件
- 把 action 傳入 UI 組件
- 把應用 Context 傳入 UI 組件
Container 是一個 React 組件。如這篇文章所述的 Controller-View
如上圖所示脱拼,使用一個父組件瞒瘸,就是 Mantra 的 container 來監(jiān)聽數據的變化,子組件 UI Component 負責界面渲染和互動熄浓。?Controller 就是高階組件 (Higher Order Components) HOC 來包裹 UI 組件情臭。高階組件負責數據查詢,子組件負責渲染等。
Mantra 使用 react‐komposer 來作為 container 獲取數據狀態(tài)俯在。
container 的規(guī)則
- 每個 jsx 文件只能有一個 container竟秫,而且這個 container 應該是默認 export
- composer 和 mapper 函數應該從 container 模塊輸出
- composer 函數只能使用從 props 輸入的值
- mapper 應該是純函數
Note: 基于 Mantra Draft 0.2.0