基于 Meteor1.3 和 React 创建简单 App

本文介绍如何使用Meteor 1.3和Mantra框架构建一个日志应用,包括项目设置、组件构建和路由配置等内容。

由于目前 Meteor 1.3 正式版仍在开发中,在这份 Meteor 指南里我们采用了目前可以获取到的 Meteor 1.3 beta 版本进行开发。尽管 Meteor 1.3 版本很棒并有着许多精彩的改进,但部分人对于到底应该如何使用它来进行开发仍有一些困惑。 MDG(Meteor Development Group) 目前正在编写 Meteor 1.3 版指南,随着 1.3 正式版的发布,我们将会获得 Meteor 1.3 最佳开发实践的确切信息。

旁注:我写了一本关于使用 Meteor 1.3 ,React ,React-Bootstrap 遵循 Mantra 框架规范进行应用开发的书,点击这里可以了解更多并免费获取前三章的内容

我写这份指南的目的是让开发者现在就能用上 Meteor 1.3 。当你阅读本指南时,需要留意 1.3 版本目前仍处于 beta 阶段,因此内容可能发生任何变化。我会尽我所能的更新这份指南来适应最新版本。如果你发现了什么过期的内容,希望能指出来让我知道。

在这份指南中,我们将要构建一个简单的任务清单,开个玩笑,不会再是任务清单了。我们将用 Meteor 1.3 ,React 和 React-Bootstrap 构建一个基本的日志应用。

我们将采用 Arunoda 的 Mantra 规范。如果你对 Mantra 不够熟悉,你可以访问这里了解更多。 基本来说, Mantra 应用程序架构规范向我们提供了一个宜于维护的方式去构建 Meteor 应用。

在我们开始前,你需要安装好 Meteor 并需要对 Meteor 的原理及使用方法具备一定的理解。如果你并不熟悉,可以看看官方 Meteor 向导

首先我们将通过一些资源来熟悉 Meteor 1.3 和 Mantra ,然后运用它们创建一个简单的日志应用。

了解 Meteor 1.3

首先我们要介绍 Meteor 1.3 并且了解它的主要改动包含什么。在 1.3 版本中它最大的改动是完全支持 ES2015 并提供了模块功能。

一开始你会发现这和我们以往开发 Meteor 应用很不一样,但一旦你习惯了你会发现体验是相当不错的,尤其是你要使用 Mantra 的架构的话。

这里有一篇关于 Meteor 1.3 的模块机制是怎样工作的精彩介绍:github.com/meteor/mete…

使用模块可以让我们更容易的去写更多的代码,更加模块化。这样我们可以更好地组织我们的应用,由于 Meteor 1.3 也添加了对 npm 包的支持,我们不必再像过去那样只有 Meteor 包支持的情况下进行开发了。

接下来,你可以看看这三篇文章来了解如何在 Meteor 1.3 中配置 React ,并用它来处理数据。第二篇会向你介绍容器组件,这是使用 Mantra 开发的一个重要部分。

  1. voice.kadira.io/getting-sta…
  2. voice.kadira.io/let-s-compo…
  3. voice.kadira.io/using-meteo…
第一步 — 项目设置

通常来说,我们需要做的第一件事就是通过 Meteor 1.3 来创建我们的 Meteor 项目,像下面这样。

meteor create journal --release 1.3-modules-beta.8

但是稍等一下,构建一个 Mantra 应用需要非常多的项目设置 
,为了加快开发速度,我已经使用 Meteor 1.3,React,Mantra 创建创建了一个样板项目。我们就用它来代替初始方案直接开始。

如果你想知道这些具体做了什么,查看 Mantra 规范和 Mantra 博客应用实例

现在我们安装完样板项目,它完全包含了遵循 Mantra 规范的 Meteor 项目中所有你需要的核心文件和目录。

你可以通过以下命令 clone 项目:

git clone git@github.com:kenrogers/mantraplate.git

然后切换到刚创建的目录中运行

npm install

这样会安装本应用依赖的所有的包。你可以查看示例项目来熟悉整个目录结构。

它包含完整的布局,路由系统以及具有注册,登录登出功能的用户系统。

在这份指南中,我们将要讨论这些内容是如何组合在一起的,以及如何使用户在应用中添加日志记录的功能。

在我们添加内容前我们来看看样例项目的目录结构,你可以发现,在客户端文件夹中我们将整个应用分成一个个模块,这些模块是你的应用的主要组成部分。

我们总是需要一个核心模块,如果你的 APP 比较简单,这个核心模块就是你所唯一需要的。在我们的 APP 中包含了核心模块和用户模块,这里还要加入一个条目模块来添加我们的日志记录。

这样的模块结构让我们可以轻松地组织我们的代码。

在用户模块中,看看 containers 和 components 文件夹中的 NewUser 文件,。container 文件夹如下所示。

import NewUser from ‘../components/NewUser.jsx’;
import {useDeps, composeWithTracker, composeAll} from ‘mantra-core’;
export const composer = ({context, clearErrors}, onData) => {
 const {LocalState} = context();
 const error = LocalState.get(‘CREATE_USER_ERROR’);
 onData(null, {error});
 return clearErrors;
};
export const depsMapper = (context, actions) => ({
 create: actions.users.create,
 clearErrors: actions.users.clearErrors,
 context: () => context
});
export default composeAll(
 composeWithTracker(composer),
 useDeps(depsMapper)
)(NewUser);

你可以看到我们在这里实际上并没有进行任何渲染,我们只是做一些设置和清理的工作,然后在 NewUser 组件中我们才实际上渲染了视图。

如果你运行应用并访问 /register 路由,打开 React 开发者工具,你可以看到 react-komposer 正在后台执行。它会创建一个容器组件负责处理底层子组件的数据或是 UI 组件。

当我们获取数据时容器组件的用途将会得到具体的展现,但是这里我们不这样处理。

第二步 — 原型制作

对于这个日志程序我们准备使用 React-Bootstrap 。它可以很方便地使用 Bootstrap 来创建 React 应用。这种方式易于上手,并且保持了模块化,正如我们所愿。

让我们设置好并添加一个简单的表单。

首先让我们为项目添加 react-bootstrap

npm install react-bootstrap

因为 React-Bootstrap 并不依赖任何特定的 Bootstrap 库,所以我们需要自行添加,现在让我们添加 Twitter 的官方 Meteor 包。

meteor add twbs:bootstrap

首先我们用 React-Bootstrap 来修改 MainLayout.jsx 文件的内容如下:

import React from ‘react’;
import {Grid, Row} from ‘react-bootstrap’;
const Layout = ({content = () => null }) => (
 <grid>
  <row>
   <h1>Journal</h1>
   {content()}
  </row>
 </grid>
);
export default Layout;

在这里,我们从 react-boostrap 包中引入 Grid 和 Row 组件,并且像使用 div 一样为它们添加合适的 bootstrap 类。想要了解更多关于这个优秀的包的工作原理,可以在这里查看组件列表。

现在让我们修改 NewUser 和 Login UI 的组件让他们更友好地贴近 Bootstrap 。打开 NewUser.jsx 文件进行如下修改:

import React from ‘react’;
import { Col, Panel, Input, ButtonInput, Glyphicon } from ‘react-bootstrap’;
class NewUser extends React.Component {
 render() {
 const {error} = this.props;
 return (
   <col xs="{12}" sm="{6}" smoffset="{3}">
    <panel>
     <h1>Register</h1>
     {error ? <p style="{{color:" ‘red’}}="">{error}</p> : null}
     <form>
      <input ref="”email”" type="”email”" placeholder="”Email”">
      <input ref="”password”" type="”password”" placeholder="”Password”">
      <buttoninput onclick="{this.createUser.bind(this)}" bsstyle="”primary”" type="”submit”" value="”Sign" up”="">
     </buttoninput></form>
    </panel>

  )
 }
createUser(e) {
 e.preventDefault();
 const {create} = this.props;
 const {email, password} = this.refs;
 create(email.getValue(), password.getValue());
 email.getInputDOMNode().value = ‘’;
 password.getInputDOMNode().value = ‘’;
 }
}
export default NewUser;

这个表单十分简单,它仅仅负责显示自身并调用 create 方法。这里我们简单介绍一下。

在我们的 actions 文件夹中,它们负责处理我们应用的逻辑,下面这一行

create(email.getValue(), password.getValue());

将调用该方法并创建实际用户。 Mantra 重点强调了希望把一切分离成单独的文件。因此,我们将文件分为展示、逻辑、以及这个应用程序的每个组件。

现在让我们修改登录表单如下:

import React from ‘react’;
import { Col, Panel, Input, ButtonInput, Glyphicon } from ‘react-bootstrap’;
class Login extends React.Component {
 render() {
  const {error} = this.props;
  return (
   <col xs="{12}" sm="{6}" smoffset="{3}">
    <panel>
     <h1>Login</h1>
     {error ? <p style="{{color:" ‘red’}}="">{error}</p> : null}
     <form>
      <input ref="”email”" type="”email”" placeholder="”Email”">
      <input ref="”password”" type="”password”" placeholder="”Password”">
      <buttoninput onclick="{this.login.bind(this)}" bsstyle="”primary”" type="”submit”" value="”Login”/">
     </buttoninput></form>
    </panel>

  )
 }
login(e) {
 e.preventDefault();
 const {loginUser} = this.props;
 const {email, password} = this.refs;
 loginUser(email.getValue(), password.getValue());
 email.getInputDOMNode().value = ‘’;
 password.getInputDOMNode().value = ‘’;
 }
}
export default Login;

这基本上是一个相同的表单,但我们将用登录方法来代替它的逻辑。

React-Boostrap 非常易于使用,我们只需要安装好项目,使用 import 函数引入每个我们想要引用的组件,就像其他类型一样渲染这些组件。

我们处理使用数据的方法则有一些不同,因为它是组件,而不是我们实际需要处理的输入内容,我们需要使用特殊的 React-Bootstrap 函数 getValue() 来帮我们轻松地取值。

第二步 — 添加条目模块

现在,我们将添加新的模块来管理我们的日志条目,首先让我们设置目录和文件。

mkdir client/modules/entries
cd client/modules/entries
mkdir actions components containers
touch index.js
touch actions/index.js actions/entries.js
touch components/NewEntry.jsx components/Entry.jsx components/EntryList.jsx
touch containers/NewEntry.js containers/Entry.js containers/EntryList.js

好了,现在我们有了应用中所需要的所有文件和文件夹。让我们来做一些真正的开发工作吧。

首先,让我们再来看一下我们创建的应用结构。这里我们制造了一个简单的 Mantra 模块。我们通过这些目录文件来看看他们是怎么做到交互的。通过这些将会让你很好地理解如何使用 Meteor 1.3 和 Mantra 。

索引

Mantra 有一个庞大的单一入口。这个索引文件负责导入内容随后导出路由和动作,这样在我们导入模块时即可使用。通过这种方式我们不用担心再单独导入每个文件。

import actions from ‘./actions’;
import routes from ‘../core/routes.jsx’;
export default {
 routes,
 actions
};

动作

动作文件夹负责我们应用的所有逻辑。你可以看到我们在这里创建了两个文件。首先是一个索引文件。这是一个类似目的模块的索引文件。我们向里面添加下面的内容。

import entries from ‘./entries’;
export default {
 entries
};

上面所做的就是导入条目文件,在条目文件中有我们的动作逻辑。这只是为了更容易地从其他文件导入我们的逻辑。

接下来我们要添加实际逻辑,这些包含了我们的应用逻辑。这里我们要添加一个创建条目的函数方法。

你可以通过查看例子中 users 模块的方法文件来了解这是怎么工作的。

在 actions.js 中添加下面的内容来补全条目模块。

export default {
 create({Meteor, LocalState, FlowRouter}, text) {
  if (!text) {
   return LocalState.set(‘CREATE_ENTRY_ERROR’, ‘Text is required.’);
  }
  LocalState.set(‘CREATE_ENTRY_ERROR’, null);
  Meteor.call(‘entries.create’, text, (err) => {
   if (err) {
    return LocalState.set(‘CREATE_ENTRY_ERROR’, err.message);
   }
  });
 }
};

当我们填写表格来创建一个新条目时,这就是会被执行的方法,我们就快设置好这些组件了,让我们先别管服务端的东西,为我们的条目创建集合和方法。

在 lib 目录中打开 collections.js 文件然后添加条目集合。

export const Entries = new Mongo.Collection(‘entries’);

现在在 server 目录下的 methods 目录中添加 entries.js 文件,并添加以下内容来创建一个创建新条目的方法。

import {Entries} from ‘/lib/collections’;
import {Meteor} from ‘meteor/meteor’;
import {check} from ‘meteor/check’;
export default function () {
 Meteor.methods({
  ‘entries.create’(text) {
   check(text, String);
   const createdAt = new Date();
   const entry = {text, createdAt};
   Entries.insert(entry);
  }
 });
}

这是一个我们刚创建的将要被调用的方法。

我们还需要将下面代码添加到 methods 文件夹中的 index.js 文件。

import entries from ‘./entries’;
export default function () {
 entries();
}

组件

组件目录存放着我们的 UI 组件。这里的组件只负责显示我们的接口内容,他们不操作任何数据,这些是容器组件需要做的。

让我们创建 UI 组件,然后我们将建立相应的容器组件。

import React from ‘react’;
import {Grid, Row, Col} from 'react-bootstrap';
const Entry = ({entry}) => (
 <grid>
  <row>
   <col xs="{6}" xsoffset="{3}">
    <p>
     {entry.text}
    </p> 

  </row>
 </grid>
);
export default Entry;

这里获取到的 {entry} 对象是我们容器组件要传递给它属性。它包含了我们的数据。

接下来我们创建 NewEntry 组件。

import React from ‘react’;
import { Col, Panel, Input, ButtonInput, Glyphicon } from ‘react-bootstrap’;
class NewEntry extends React.Component {
 render() {
  const {error} = this.props;
  return (
   <col xs="{12}" sm="{6}" smoffset="{3}">
    <panel>
     <h1>Add a New Entry</h1>
     {error ? <p style="{{color:" ‘red’}}="">{error}</p> : null}
     <form>
      <input ref="”text”" type="”textarea”" placeholder="”Add" your="" entry”="">
      <buttoninput onclick="{this.newEntry.bind(this)}" bsstyle="”primary”" type="”submit”" value="”Create”/">
     </buttoninput></form>
    </panel>

  )
 }
 newEntry(e) {
  e.preventDefault();
  const {create} = this.props;
  const {text} = this.refs;
  create(text.getValue());
  text.getInputDOMNode().value = ‘’;
 }
}
export default NewEntry;

这里我们使用了更多的 React-Bootstrap 组件,你会留意到为了获取输入的值,我们用了一个特别的 getValue() 方法。这是因为我们的渲染组件实际上并不是输入框,输入框是在这些组件的内部。所以我们需要使用这个函数来访问它。

最后,我们创建一个 EntryList 组件。

import React from ‘react’;
import {Grid, Row, Col, Panel} from ‘react-bootstrap’;
const EntryList = ({entries}) => (
 <grid>
  <row>
   {entries.map(entry => (
    <col xs="{3}" key="{entry._id}">
     <panel>
      <p>{entry.title}</p>
      <a href="{`/entry/${entry._id}`}">View Entry</a>
     </panel>

   ))}
  </row>
 </grid>
);
export default EntryList;

接下来,我们通过属性来获取数据,设置一些 React-Bootstrap 组件,并为每个入口映射一个对应专属的面板。

现在,让我们来设置这些容器组件,首先从最简单的 NewEntry 容器组件开始。

import NewEntry from ‘../components/NewEntry.jsx’;
import {useDeps, composeWithTracker, composeAll} from ‘mantra-core’;
export const composer = ({context, clearErrors}, onData) => {
 const {LocalState} = context();
 const error = LocalState.get(‘CREATE_ENTRY_ERROR’);
 onData(null, {error});
 return clearErrors;
};
export const depsMapper = (context, actions) => ({
 create: actions.entries.create,
 clearErrors: actions.entries.clearErrors,
 context: () => context
});
export default composeAll(
 composeWithTracker(composer),
 useDeps(depsMapper)
)(NewEntry);

这里你应该已经对 react-komposer 较为熟悉了,我们将用它来创建这一容器组件。它负责创建一个容器组件,用于处理错误、调用合适的动作。在大多数情况下,它还将获取数据并通过属性传给 UI 组件。

depsMapper 通过 react-komposer 中的 useDeps 函数检索动作及上下文内容并将它们传递给 UI 组件。

clearErrors 方法负责清除组件卸载时发生的所有错误。

让我们在创建条目方法时创建这一方法。

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

现在我们将要创建 EntryList 组件的容器。这个稍许有些复杂,因为我们会实际上获取一些数据。

import EntryList from ‘../components/EntryList.jsx’;
import {useDeps, composeWithTracker, composeAll} from ‘mantra-core’;
export const composer = ({context}, onData) => {
 const {Meteor, Collections} = context();
 if (Meteor.subscribe(‘entries.list’).ready()) {
  const entries = Collections.Entries.find().fetch();
  onData(null, {entries});
 }
};
export default composeAll(
 composeWithTracker(composer),
 useDeps()
)(EntryList);

这也确实与其他容器组件较为相似,但一个重要的区别在于,我们会检查我们的入口集合条目结合,并将它们分配给一个变量。最终我们通过 onData 函数将这个变量传给 UI 组件。

让我们在 publications 目录下的 entries.js 文件中设置发布

import {Entries} from ‘/lib/collections’;
import {Meteor} from ‘meteor/meteor’;
import {check} from ‘meteor/check’;
export default function () {
 Meteor.publish(‘entries.list’, function () {
  const selector = {};
  const options = {
   fields: {_id: 1, text: 1},
   sort: {createdAt: -1}
  };
  return Entries.find(selector, options);
 });
}

同时我们将要为此发布创建一个 index 文件。

import entries from ‘./entries’;
export default function () {
 entries();
}

我们需要在 server 目录中打开 main.js 文件,取消注释行,导入 publications 和 methods ,所以文件就像这样:

import publications from ‘./publications’;
import methods from ‘./methods’;

// publications();
// methods();

最后我们将要为独立的 Entry 组件创建容器组件。

import Entry from ‘../components/Entry.jsx’;
import {useDeps, composeWithTracker, composeAll} from ‘mantra-core’;
export const composer = ({context, entryId}, onData) => {
 const {Meteor, Collections} = context();
 if (Meteor.subscribe(‘entries.single’, entryId).ready()) {
  const entry = Collections.Entries.findOne(entryId);
  onData(null, {entry});
 } else {
  const entry = Collections.Entries.findOne(entryId);
  if (entry) {
   onData(null, {entry});
  } else {
   onData();
  }
 }
};
export default composeAll(
 composeWithTracker(composer),
 useDeps()
)(Entry);

此容器使用了一个 entryId (将通过我们之后设立的一个路由进行传递)并且找到一个合适的入口,来通过属性传递它给UI组件。

让我们在之前设置的发布列表中快速设置发布来展示发布条目。

Meteor.publish(‘entries.single’, function (entryId) {
 check(entryId, String);
 const selector = {_id: entryId};
 return Entries.find(selector);
});

现在让我们设置我们的路由吧。

路由

打开 routes 文件来添加一些新的路由,修改 routes 文件类似如下所示。

import React from ‘react’;
import {mount} from ‘react-mounter’;
import Layout from ‘./components/MainLayout.jsx’;
import Home from ‘./components/Home.jsx’;
import NewUser from ‘../users/containers/NewUser.js’;
import Login from ‘../users/containers/Login.js’;
import EntryList from ‘../entries/containers/EntryList.js’;
import Entry from ‘../entries/containers/Entry.js’;
import NewEntry from ‘../entries/containers/NewEntry.js’;
export default function (injectDeps, {FlowRouter}) {
 const MainLayoutCtx = injectDeps(Layout);
 FlowRouter.route(‘/’, {
  name: ‘items.list’,
  action() {
   mount(MainLayoutCtx, {
    content: () => (<entrylist>)
   });
  }
 });
 FlowRouter.route(‘/entry/:entryId’, {
  name: ‘entries.single’,
  action({entryId}) {
   mount(MainLayoutCtx, {
    content: () => (<entry entryid="{entryId}/">)
   });
  }
 });
FlowRouter.route(‘/new-entry’, {
  name: ‘newEntry’,
  action() {
   mount(MainLayoutCtx, {
    content: () => (<newentry>)
   });
  }
 });

 FlowRouter.route(‘/register’, {
  name: ‘users.new’,
  action() {
   mount(MainLayoutCtx, {
    content: () => (<newuser>)
   });
  }
 });
FlowRouter.route(‘/login’, {
  name: ‘users.login’,
  action() {
   mount(MainLayoutCtx, {
    content: () => (<login>)
   });
  }
 });
FlowRouter.route(‘/logout’, {
  name: ‘users.logout’,
  action() {
   Meteor.logout();
   FlowRouter.go(‘/’);
  }
 }); 
}</login></newuser></newentry></entry></entrylist>

在运行我们的应用之前我们还需要做最后一件事,打开 main.js 文件并导入我们的 entries 模块,修改内容如下。

import {createApp} from ‘mantra-core’;
import initContext from ‘./configs/context’;
// modules
import coreModule from ‘./modules/core’;
import usersModule from ‘./modules/users’;
import entriesModule from ‘./modules/entries’;
// init context
const context = initContext();
// create app
const app = createApp(context);
app.loadModule(coreModule);
app.loadModule(usersModule);
app.loadModule(entriesModule);
app.init();

现在我们设置了我们的所有路由并且应用已经准备好运行,让我们切换目录到根目录并运行

meteor

你可以看到应用程序在 Mantra 提供的默认加载效果中启动,让我们添加一个条目,这样我们应该可以在屏幕上看到效果了。

访问 localhost:3000/new-entry ,填写并提交表单来添加一个条目。

然后访问根目录,你应该可以看到一个可以逐个查看链接的的条目列表。

希望这个简单的 Mantra 引导以及目前的 Meteor 1.3 beta 版本有助于让你更加了解如何运用它们来构建一个应用。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值