
Mantra 是一种基于 Meteor 1.3+、 React 和 ES2015 的应用程序架构。它不是一个框架,而是一套如何构建 Meteor App 的标准,同时也有一套相关开源库来提高代码编写效率。
简单来说, Mantra 是关于如何组织你的 Meteor 应用代码的标准,特别是前端部分 (基于 React),当然它对后端代码的组织也有要求。
如果你熟悉 React , Mantra 类似于 Flux ,讲究的是对数据流的控制,但是规定得更加细致。
Mantra 的目的是让程序写出更易于理解和维护的代码。它对几乎所有的情况都有一个标准,另外还为 Meteor App 增加单元测试覆盖率。
和 Perl 类似, Javascript 的一个难点就是同样一个问题有太多实现方式,而且可能都是最佳解决方案。所以经常是不同的人使用不同的方法。 Mantra 让 Meteor App 有一个统一的结构,遵循相同的标准,就像设计模式一样,降低大家理解代码结构的难度,确保模块之间解耦,像 Flux 一样让数据单向流动,这样维护代码更加容易。
Mantra 使用的原则很有前瞻性,能够很长时间不会过时,同时也允许其他人做必要的改变。
现在的 Web App 的大部分代码都是在前端。后端的代码逻辑相对简单也好管理,后端的难点在于性能优化,特别是大并发的处理,数据库等。
Mantra 的核心在如何组织客户端代码。它倡导前后端代码分离,前端不用知道后端代码是如何实现的,但是可以代码共享。因为是基于 React 又侧重前端,所以 Mantra 很类似 React 的那些标准,例如 Flux , Redux 等,解决的问题也类似,都是控制数据流 data flow ,让代码更易理解维护。如果你对 React 熟悉,理解 Mantra 就不难。如果理解有困难,建议多看看 React 的高级用法,例如 stateless/pure function , Higher Order Components 等。
Mantra 不相信 Universal App ,就是不相信一套前端代码适应所有终端平台。它鼓励一套后端代码,但是为每个前端平台开发单独的 app 来提高用户体验,尽量通过模块化来共享代码。
其他 Mantra 的基本介绍可以参看这篇中文翻译 http://www.jianshu.com/p/96d6b8e64c3a
下面我来详细解释 Mantra 的各个部件。
这里介绍的顺序和文档里的不一样,主要是先从新的概念介绍,不然对已经熟悉的也难理解。
应用上下文 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 可以让大家少写重复的代码,又可以在不同模块之间分享变量。
处理业务逻辑的模块。包括验证,状态管理和远程数据交互。
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); } }; 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 里的无状态纯函数实现, UI 只负责展示界面,没有逻辑、状态等处理。
有两种状态:本地状态(客户端)和远程状态(服务器)。本地状态不和外界发生联系;远程状态需要和外界,例如数据库同步数据。
类似 Flux 里的 store 概念 (可参考 使用 Meteor 和 React 开发 Web App ), Meteor 有不同的方式实现,例如 MiniMongo , ReactiveDict 等。 Mantra 在这方面很灵活,没有要求用哪一种。但是还是有一些规则
首先,什么是依赖? Mantra 有两种依赖
例如:
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 里随意使用了。
如何使用依赖?
首先创建一个 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 的作用是集成、组装数据。它的中文意思是容器,里面包裹的就是 UI 组件。主要功能:
Container 是一个 React 组件。如这篇文章所述的 Controller-View

如上图所示,使用一个父组件,就是 Mantra 的 container 来监听数据的变化,子组件 UI Component 负责界面渲染和互动。 Controller 就是高阶组件 (Higher Order Components) HOC 来包裹 UI 组件。高阶组件负责数据查询,子组件负责渲染等。
Mantra 使用 react komposer 来作为 container 获取数据状态。
container 的规则
Note: 基于 Mantra Draft 0.2.0