Meteor Mantra 介绍

2016-07-09 11:15:33 +08:00
 russj

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 的各个部件。


这里介绍的顺序和文档里的不一样,主要是先从新的概念介绍,不然对已经熟悉的也难理解。

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

处理业务逻辑的模块。包括验证,状态管理和远程数据交互。

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 里的无状态纯函数实现, UI 只负责展示界面,没有逻辑、状态等处理。

State 管理

有两种状态:本地状态(客户端)和远程状态(服务器)。本地状态不和外界发生联系;远程状态需要和外界,例如数据库同步数据。

类似 Flux 里的 store 概念 (可参考 使用 Meteor 和 React 开发 Web App ), Meteor 有不同的方式实现,例如 MiniMongo , ReactiveDict 等。 Mantra 在这方面很灵活,没有要求用哪一种。但是还是有一些规则

Dependency Injection 依赖注入

首先,什么是依赖? Mantra 有两种依赖

  1. context - 通常就是配置, models 和各种数据
  2. actions - 业务逻辑。每个 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 里随意使用了。

如何使用依赖?

首先创建一个 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 组件。主要功能:

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

4102 次点击
所在节点    Meteor
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/291316

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX