Meteor Mantra 介绍 (六)- 使用 mantra-cli 命令行生成源码

Meteor Mantra 系列文章:

Meteor Mantra 介绍(一)- 基本概念
Meteor Mantra 介绍(二)- 前端架构详解
Meteor Mantra 介绍(三)- 后端架构解释
Meteor Mantra 介绍(四)- 博客例子前端代码解读
Meteor Mantra 介绍(五)- 博客例子后端代码解读
Meteor Mantra 介绍(六)- 使用 mantra-cli 命令行生成源码


Mantra CLI 是一个用来生成 Mantra 项目源码的命令行工具,因为 Mantra 的文件结构是固定的,所以能够自动生成 boilerplate code 可以减少很多重复性工作。

它很简单,目前一共只有三个命令

  • create
  • generate
  • destroy

必须在根目录使用这个 CLI 命令。下面来介绍这三个命令

mantra create [path]

可以使用 c 代替 create。

这个命令可以为你依照 Mantra 标准创建一个完整的 Meteor 应用,安装 Meteor 包和 npm 依赖的包,并且添加 .eslintrc 和 .gitignore。

使用 -v 参数可以看到所有 log 信息。

mantra generate [type][name]

可以使用 g 代替 generate。下面介绍不同的 type 值 CLI 产生的不同行为。

action、component

type 的值可以是 action 和 component,意味着它可以自动为你生成 action 和 UI 组件。默认是生成 stateless 的组件,如果使用 --use-class 或者 -c 可以生成继承 React.Component 的 ES2015 类。例如

mantra g component core:header -c //记得指明 module 的名字,这里是 core 模块  

它同时也会生成 storybook 和测试的相关文件。下面是命令完成后终端显示的 log

  create  ./client/modules/core/components/header.jsx
  create  ./client/modules/core/components/.stories/header.js
  update  ./client/modules/core/components/.stories/index.js
  create  ./client/modules/core/components/tests/header.js

如果是 mantra g action core:header 生成 action

  create  ./client/modules/core/actions/header.js
  update  ./client/modules/core/actions/index.js
  create  ./client/modules/core/actions/tests/header.js

CLI 目前不会帮你产生 method_stub, 所以如果需要 Optimisitc UI 特性,你得自己添加。

container

如果 type 值为 container,那么生成一个container 和相关的 component.

collection

如果 type 值为 collection, 生成 MongoDB 的集合,也可以是 collection2 或者 astronomy 这两种基于MongoDB 的数据集合。 加上 --schema(-s)生成相应 schema。

mantra g collection books -s collection2  

method、publication、module

type 为以上值的话也可以生成相应的文件夹和代码。

指明模块

当 type 值为 action, component 和 container 时,需要指明 module 的名字,例如

mantra generate component core:posts  
mantra generate publication users  
mantra generate method comments  

index.js

当 type 值为 action、collection、method 或者 publication 是,命名会在 index.js 里自动插入 import export 申明。

mantra destroy [type] [name]

可以使用 d 代替 destroy。

generate 的反向功能,删除所有相关文件和代码。

mantra d component core:header //结果见下面

remove  ./client/modules/core/components/header.jsx  
remove  ./client/modules/core/components/.stories/header.js  
update  ./client/modules/core/components/.stories/index.js  
remove  ./client/modules/core/components/tests/header.js  

CLI 还可以定制生成代码的 template。


下面附录为 mantra create 命令生成的应用包信息,可以了解 Mantra 使用的详细技术栈

// Meteor package - 非 meteor create 命名自带的
kadira:flow-router

// npm packages:
"dependencies": {
    "mantra-core": "^1.2.0",
    "react": "^15.0.0",
    "react-dom": "^15.0.0",
    "react-komposer": "^1.3.0",
    "react-mounter": "^1.0.0",
    "react-simple-di": "^1.0.1"
  },
  "devDependencies": {
    "@kadira/storybook": "^1.x.x",
    "enzyme": "^2.2.0",
    "jsdom": "^8.0.4",
    "eslint": "2.7.x",
    "eslint-plugin-react": "4.3.x",
    "babel-core": "6.x.x",
    "babel-plugin-react-require": "2.x.x",
    "babel-polyfill": "6.x.x",
    "babel-preset-es2015": "6.x.x",
    "babel-preset-react": "6.x.x",
    "babel-preset-stage-2": "6.x.x",
    "babel-root-slash-import": "1.x.x",
    "sinon": "1.17.x",
    "chai": "3.x.x",
    "mocha": "2.x.x",
    "react-addons-test-utils": "^15.0.0"
  }

下面我们来尝试使用这个 CLI 为博客例子添加一个 action,这个 action 的功能是修改文章标题。

添加 action

运行 mantra g action core:header 生成 action

  create  ./client/modules/core/actions/header.js
  update  ./client/modules/core/actions/index.js
  create  ./client/modules/core/actions/tests/header.js

然后在 header.js 输入

export default {  
  modify({Meteor, LocalState}, id, title) {
    if (!title) {
      return LocalState.set('SAVING_ERROR', ' 标题不能为空');
    }

    LocalState.set('SAVING_ERROR', null);

    Meteor.call('posts.modify', id, title, (err) => {
      if (err) {
        return LocalState.set('SAVING_ERROR', err.message);
      }
    });
  },

  clearErrors({LocalState}) {
    return LocalState.set('SAVING_ERROR', null);
  }
}

这里有两个函数输出,一个是修改数据库的动作,一个是在组件 unmount 时重置错误信息。

CLI 会自动更新 ./client/modules/core/actions/index.js 文件,加入 header 的输出信息。这样修改标题的 action 就添加到应用的 context 了,后面我可以随时引用。

注入 action 到 container 组件

这里我们简化一下流程,不生成一个新的 container,而只是使用 post.js 这个已有 container。

新的 container/post.js,修改的部分都有注释

export const composer = ({context, postId, clearErrors}, onData) => { // 新加了 clearErrors  
  const {Meteor, Collections, LocalState} = context(); // 新加了 LocalState
  const error = LocalState.get('SAVING_ERROR');  // 获取 error 信息

  if (Meteor.subscribe('posts.single', postId).ready()) {
    const post = Collections.Posts.findOne(postId);
    onData(null, {post, error}); // 传入 error 信息
  } else {
    const post = Collections.Posts.findOne(postId);
    if (post) {
      onData(null, {post, error}); // 传入 error 信息
    } else {
      onData();
    }
  }

  return clearErrors; // 返回 clearErrors 函数清理错误信息
};

// 注入 context 和 actions
export const depsMapper = (context, actions) => ({  
  modify: actions.header.modify,
  clearErrors: actions.header.clearErrors,
  context: () => context
});

export default composeAll(  
  composeWithTracker(composer),
  useDeps(depsMapper)  // 在这里注入 action
)(Post);

这样在这个 container 里就注入了使用到的 actions。

UI 组件实现 view 和交互

CLI 生成的 UI 组件还是 .jsx 后缀的。

mantra d component core:modify_header -c //结果见下面

remove  ./client/modules/core/components/modify_header.jsx  
remove  ./client/modules/core/components/.stories/modify_header.js  
update  ./client/modules/core/components/.stories/modify_header.js  
remove  ./client/modules/core/components/tests/modify_header.js  

然后把下面的代码加入 modify_header.jsx

  render() {
    const {error} = this.props;
    return (
      <span>
        <button onClick={this._modify.bind(this)} style={{marginLeft: 10}}>修改标题</button>
        {error ? <span style={{color: 'red'}}>{error}</span> : null}  {/* 空标题错误提示*/}
      </span>
    );
  }

  _modify() {
    const newTitle = prompt("修改标题", this.props.post.title)
    const postId = this.props.post._id
    const {modify} = this.props
    modify(postId, newTitle)
  }

这里我们可以看到 modify 这个 action 是作为 props 的一个属性传入组件的,而 action 又是在之前的 container 里被注入。这样可以倒推看出被分离的 action 和 UI 结合的路线。

当然, 在 Post UI 组件里记得添加 ModifyHeader 元素。

 <h2>{props.post.title} <ModifyHeader {...props}/></h2>

这样就完成了一个 action 的添加。

数据 state 传递

例子里使用了 LocalState 来传递数据,或者叫本地状态。

以 error 信息为例来说明如何传递本地数据。和 MiniMongo 类似,例子也是在 action (header.js) 里修改 LocalState,然后在 container (post.js) 的 composer 里引入 context 来获取 LocalState,最后把 error 信息通过 props 传入 UI 组件显示。

小结

以上就是如何在 Mantra 里添加 action,传递 state 的一个简单例子。具体代码可以参见 Mantra 博客例子修改版