Meteor Mantra 介绍
发布于 4 小时前 作者 russj 30 次浏览 来自 分享

如果排版有问题可以参看这个链接 http://www.jianshu.com/p/e1406c6d99f2

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 在这方面很灵活,没有要求用哪一种。但是还是有一些规则

  • Action 里可以读写 state
  • Container 里只能读 state
  • UI 组件里既不能读也不能写 state,只能由 props 传入

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 组件。主要功能:

  • 处理 state,处理后把值通过 props 传入 UI 组件
  • 把 action 传入 UI 组件
  • 把应用 Context 传入 UI 组件

Container 是一个 React 组件。如这篇文章所述的 Controller-View

Flux

如上图所示,使用一个父组件,就是 Mantra 的 container 来监听数据的变化,子组件 UI Component 负责界面渲染和互动。Controller 就是高阶组件 (Higher Order Components) HOC 来包裹 UI 组件。高阶组件负责数据查询,子组件负责渲染等。

Mantra 使用 react‐komposer 来作为 container 获取数据状态。

container 的规则

  • 每个 jsx 文件只能有一个 container,而且这个 container 应该是默认 export
  • composer 和 mapper 函数应该从 container 模块输出
  • composer 函数只能使用从 props 输入的值
  • mapper 应该是纯函数

Note: 基于 Mantra Draft 0.2.0

下面是我创建的 Meteor Mantra React 讨论群,欢迎加入

FullSizeRender.jpg

回到顶部