V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Meteor Official Site
http://www.meteor.com/
Follow @meteorjs on Twitter
russj
V2EX  ›  Meteor

Meteor Mantra 介绍

  •  
  •   russj · 2016-07-09 11:15:33 +08:00 · 4068 次点击
    这是一个创建于 2819 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1037 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 19:18 · PVG 03:18 · LAX 12:18 · JFK 15:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.