『翻译』METEOR教程(REACT VERSION)

Read the original

如果你有成为全栈的梦想,METEOR是个不错的平台。


1.创建你的第一个App(Creating your first app)

在这个教程中,我们会创建一个简单的App去管理待办事项列表并和他人合作完成任务。在完成这个项目后,你会对Meteor和它的项目结构有个基本的了解,。我们先创建App,打开命令行,键入以下命令:

译者注:前提是你安装了meteor,具体安装步骤可以参考这里

1
meteor create simple-todos

它会创建一个名叫simple-todos的文件夹,里面包含我们开发meteor App所需要的文件:

1
2
3
4
5
6
7
client/main.js # 在客户端的JavaScript入口文件
client/main.html # 定义视图容器
client/main.css # 定义App样式
server/main.js # 服务端的JavaScript入口文件
package.json # 用于安装和管理NPM包
.meteor # Meteor内部文件
.gitignore # git的管理文件

运行这个新创建的App:

1
2
3
cd simple-todos
meteor npm install
meteor

打开你的浏览器并输入http://localhost:3000查看App是否运行成功。

在我们教程正式开始之前,你可以看看这个App的内部组成。比如,用你惯用的编辑器修改client/main.html文件中的<h1>内的文字。当你修改完毕保存文件时,浏览器会自动更新并展示新的内容,我们叫它热拔插(hot code push)

ES2015特性

如果你还没有尝试过下个版本的JavaScript特性,初始App内的代码语法,还有整个教程使用的语法,都会让你感到怪异。这是因为Meteor支持大部分ES2015的特性,也就是下个版本的JavaScript。常用的特性包括:

  1. 箭头函数:(arg) => {return result;}

  2. 简写方法:render() { ... }

  3. constlet取代var

你可以在ecmascript docs查看Meteor支持的新特性。更多关于ES2015的信息可以查看下面几篇文章:

现在你有一些如何开发Meteor App的经验了,让我们开始创建一个待办事项列表应用程序吧!如果你在这个教程中发现了bug或是错误,可以在GitHub上请发起issuePR

2.使用React Components定义视图层(Defining views with React components)

我们把React作为视图层,让我们增加一些NPM packages,以便使用React进行开发。打开一个新的terminal在你的App根目录下,键入以下命令:

1
meteor npm install --save react react-dom

替换初始化的代码

在开始前,我们要替换App的初始代码。接下来我们讨论要做些什么。

第一步,替换初始化的HTML中的内容:

1
2
3
4
5
6
7
<head>
<title>Todo List</title>
</head>
<body>
<div id="render-target"></div>
</body>

第二步,删除client/main.js,并创建三个新文件:

1
2
3
4
5
6
7
8
9
10
11
// ./client/main.jsx
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import App from './imports/ui/App.jsx';
Meteor.startup(() => {
render(<App />, document.getElementById('render-target'));
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// ./imports/ui/App.jsx
import Reacr, { Component } from 'react';
import Task from './Task.jsx';
//App组件 —— 代表整个App
export default class App extends Component {
getTasks() {
return [
{ _id: 1, text: 'This is task 1' },
{ _id: 2, text: 'This is task 2' },
{ _id: 3, text: 'This is task 3' },
];
}
renderTasks(){
return this.getTasks().map.((task) => {
<Task key={task._id} task={task} />
});
}
render(){
return (
<div className="container">
<header>
<h1>Todo List</h1>
</header>
<ul>
{this.renderTasks()}
</ul>
</div>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ./imports/ui/Task.jsx
import React, {Component, PropTypes} from 'react';
// Task组件 —— 代表一个单独的todo条目
export default class Task extends Component {
render(){
return (
<li>{this.props.task.text}</li>
);
}
}
Task.propTypes = {
//这个组件通过React prop去得到并显示任务
//我们可以用propTypes去指明哪些是必须的
task:PropTypes.object.isRequired,
}

我们仅在我们的App中做了3件事:

  1. 一个App React component

  2. 一个Task React component

  3. 一些初始化代码(在client/main.jsx客户端JavaScript入口文件中),在Meteor.startup代码块中,我们知道当页面加载完毕后会执行里面的代码。这段代码加载其他组件,并渲染#render-target元素。

你还可以在Application Structure article中了解import是如何工作的,还有如何组织你的代码。

在接下来的教程中,我们增加或更改代码,都会涉及到这些组件。

检查结果

在浏览器中,我们的app应该看起来像这个样子:

Todo List

  • This is task 1

  • This is task 2

  • This is task 3

如果你的app看起来不是这样,可以去GitHub下载代码,并和你自己的代码对比,找出不同的地方。

HTML文件定义静态内容

Meteor会解析你app目录下的所有HTML文件,并识别三个顶级标签<head>,<body><template><head>标签内的所有内容会发送到客户端HTML的head标签,<body>标签内的所有内容也会发送到客户端HTML的body标签内,就和正常的HTML文件一样。

任何<template>标签内的内容会编译成Meteor Templates,你可以在HTML中用{{>templateName}}去调用,或在JavaScript中用Template.templateName去调用。在这个教程中,我们不会用到Meteor提供的模板特性,因为我们将用React去定义所有视图组件。

用React定义视图组件

在React中,视图组件是React.Component的子类(当我们用import { Component } from 'react'的方式引入时)。你可以自由的在组件上添加方法,但有几个特殊的方法是不行的,比如render方法。组件通过其父组件的props属性,也可以接收数据。在这个教程中,我们会重温一些React的通用特性。你也可以查看React官方教程

重新审视JSX中的render方法

在每个React Component中最重要的方法就是render(),它会访问React并得到被描述的HTML,然后把组件显示出来。这个HTML内容用JavaScript扩展语法写成,叫JSX,看起来就像在JavaScript中写HTML。你可以看到一些显而易见的差异:在JSX中,你要用className去代替class属性。还有一件很重要的事,它不是像Spacebars和Angular一样的模板语言,实际上它是直接编译成正常的JavaScript。查看更多JSX的信息

JSX支持ecmascript扩展包,所以它默认支持所有Meteor app的扩展包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/*./client/main.css*/
body {
font-family: sans-serif;
background-color: #315481;
background-image: linear-gradient(to bottom, #315481, #918e82 100%);
background-attachment: fixed;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 0;
margin: 0;
font-size: 14px;
}
.container {
max-width: 600px;
margin: 0 auto;
min-height: 100%;
background: white;
}
header {
background: #d2edf4;
background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
padding: 20px 15px 15px 15px;
position: relative;
}
#login-buttons {
display: block;
}
h1 {
font-size: 1.5em;
margin: 0;
margin-bottom: 10px;
display: inline-block;
margin-right: 1em;
}
form {
margin-top: 10px;
margin-bottom: -10px;
position: relative;
}
.new-task input {
box-sizing: border-box;
padding: 10px 0;
background: transparent;
border: none;
width: 100%;
padding-right: 80px;
font-size: 1em;
}
.new-task input:focus{
outline: 0;
}
ul {
margin: 0;
padding: 0;
background: white;
}
.delete {
float: right;
font-weight: bold;
background: none;
font-size: 1em;
border: none;
position: relative;
}
li {
position: relative;
list-style: none;
padding: 15px;
border-bottom: #eee solid 1px;
}
li .text {
margin-left: 10px;
}
li.checked {
color: #888;
}
li.checked .text {
text-decoration: line-through;
}
li.private {
background: #eee;
border-color: #ddd;
}
header .hide-completed {
float: right;
}
.toggle-private {
margin-left: 5px;
}
@media (max-width: 600px) {
li {
padding: 12px 15px;
}
.search {
width: 150px;
clear: both;
}
.new-task input {
padding-bottom: 5px;
}
}

增加上面的CSS代码到你的项目中,这个首页看起来会很棒。在你的浏览器中检查样式是否加载进去了。

3.在集合中储存任务(Storing tasks in a collection)

Meteor使用集合(collections)储存持久化数据。在Meteor中,集合的特殊之处在于,可以同时在服务端和客户端读取数据,这让我们很容易编写视图层逻辑,而不需要编写太多服务端代码。集合会自动更新自身,所以一个视图组件配合集合,可以自动的显示最新的数据。

你可以在Meteor教程中阅读更多有关集合的文章

创建一个集合很简单,只需要在你的代码中调用MyCollection = new Mongo.Collection("my-collection");。在服务端,这会创建一个名叫my-collection的MongoDB集合,它会创建一个缓存去链接服务端的集合。我们将在第12章节了解更多客户端和服务端的区别,我们假设整个数据库都运行在客户端,并开始编写我们的代码。

我们创建一个Mongo集合来定义新的tasks模块,并导出它:

1
2
3
4
//imports/api/tasks.js
import { Mongo } from 'meteor/mongo';
export const Tasks = new Mongo.Collection('tasks');

注意,我们把这个文件放在了新的目录imports/api下。这是一个合适的位置,用于放置和API有关的文件。我们在这里抛出”collections”,之后读取它并添加一个”publications”,再用”methods”去编辑它。你可以在Meteor指南中了解更多有关如何在APP中组织你的代码结构

我们需要在服务端导入这个模块(这将创建MongoDB集合,并设置管道得到数据给Client):

1
2
//server/main.js
import '../imports/api/tasks.js';

在React component中使用集合数据

我们使用Atmosphere包:react-meteor-data,它会创建一个数据容器,把Meteor的响应式数据供给给React component。

我们要安装一个Meteor包,以及它要使用到的NPM包,react-addons-pure-render-mixin

1
2
meteor npm install --save react-addons-pure-render-mixin
meteor add react-meteor-data

在使用react-meteor-data前,我们需要使用createContainer高阶组件把我们的组件包裹在一个容器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//imports/ui/App.jsx
import React, { Component, PropTypes } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
import Task from './Task.jsx';
//App容器 - 代表整个App
class App extends Component {
renderTasks() {
return this.props.tasks.map((task) => (
<Task key={task._id} task={task} />
));
}
render() {
return (
<div className="container">
<header>
<h1>Todo List</h1>
</header>
<ul>
{this.renderTasks()}
</ul>
</div>
);
}
}
App.propTypes = {
tasks: PropTypes.array.isRequired,
};
export default createContainer(() => {
return {
tasks: Tasks.find({}).fetch(),
};
}, App);

这个被包裹的App组件从Tasks集合中提取tasks,并将它们用作App底层组件,它们被包装为tasks prop。它以响应的方式运行,当数据库内容改变,App会重新渲染,我们可以立刻看到!

当你对代码进行以上更改时,你会发现之前在代办列表中的任务消失了。因为我们现在的数据库是空的,我们需要插入一些任务。

从服务端数据库控制台插入任务

我们称集合中的每一项为文档。使用服务端数据库控制台插入一些文档到我们的集合中。在一个新的terminal中进入你的app项目目录,并键入:

1
meteor mongo

这会在你app的本地开发数据库中打开一个控制台。键入下面的代码:

1
db.tasks.insert({ text: "Hello world!", createdAt: new Date() });

在浏览器中,你可以立刻看到用户界面更新并展示了这个新的任务。可以看到,我们不需要编写任何代码将服务端数据库连接到前端——一切都是自动的。

用同样的方法从数据库控制台添加一些不同的任务吧。在下一章节,我们将了解怎样在app用户界面增加一些功能,比如不使用数据库控制台去增加任务。

4.使用表单添加任务

在这一章节,我们将增加一个表单字段,给用户在列表中添加任务。

首先,让我们增加一个表单域到App组件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//imports/ui/App.jsx
<div className="container">
<header>
<h1>Todo List</h1>
<form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
<input
type="text"
ref="textInput"
placeholder="Type to add new tasks"
/>
</form>
</header>
<ul>

提示:JSX中添加注释的方法:{/* … */}

你可以看到form元素有一个onSubmit属性,它映射到组件的handleSubmit方法。在React中,你可以这样监听浏览器事件,就像表单上的submit事件。input元素有一个ref属性,它能让我们稍后轻松的访问到这个元素。

让我们添加handleSubmit放到到App组件里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//imports/ui/App.jsx
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { createContainer } from 'meteor/react-meteor-data';
import { Tasks } from '../api/tasks.js';
//...省略一些代码...
class App extends Component {
handleSubmit(event) {
event.preventDefault();
// 通过React的ref拿到文本字段
const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
Tasks.insert({
text,
createdAt: new Date(), // 当前时间
});
// 清空表单
ReactDOM.findDOMNode(this.refs.textInput).value = '';
}
//...省略一些代码...
}

现在你的App有一个新的表单字段了。只需要在输入框输入文本,并按下回车键,就可以添加任务了。如果你打开一个新的浏览器窗口并再打开这个app,你会发现列表会自动自动在所有客户端同步。

在React中监听事件

就如你看到的,你可以通过参考组件上切确的方法来管理DOM事件。在事件管理器内部,你可以通过使用React.findDOMNode去拿到带有ref属性的元素。查看更多React支持的事件类型,以及事件系统如何运行的,请参考React文档

插入集合

在事件管理器内部,我们通过调用Tasks.insert添加了一个任务到tasks集合,因为我们不用为集合定义一个模式,所以可以添加任何属性到任务对象,比如创建时间。

客户端可以添加任何数据到数据库,这样并不安全,但目前来说一切都没问题。在第10章节,我们将学习怎样安全并有限制的将数据插入到数据库中。

储存我们的任务

目前,我们最新的任务在列表的最底下。这对任务列表来说不是很好,我们希望看到最新的任务在最前面。

我们可以通过使用createdAt字段自动排序来解决此问题。只需在包裹App组件的数据容器内的find方法调用时添加一个排序选项:

1
2
3
4
5
6
//imports/ui/App.jsx
export default createContainer(() => {
return {
tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
};
}, App);

回到浏览器,并确认是否如期运行:任何新添加的任务都会出现在列表的最顶端,而不是底部。

在下一章节,我们会增加一些十分重要的待办事项列表特性:检查任务和删除任务。

5.检查任务和删除任务

到目前为止,我们还仅仅只能插入文档。现在,我们将学习怎么去更新和删除它们。

让我们添加两个新元素到我们的组件,一个复选框和一个删除按钮,以及它们对应的时间管理器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { Component, PropTypes } from 'react';
import { Tasks } from '../api/tasks.js';
// Task组件 - 代表一个单独的代办事项
export default class Task extends Component {
toggleChecked() {
// 设置checked属性和当前值相反
Tasks.update(this.props.task._id, {
$set: { checked: !this.props.task.checked },
});
}
deleteThisTask() {
Tasks.remove(this.props.task._id);
}
render() {
//当修改checked后,给tasks一个不同的className
// 以便我们更好的在CSS中修改样式
const taskClassName = this.props.task.checked ? 'checked' : '';
return (
<li className={taskClassName}>
<button className="delete" onClick={this.deleteThisTask.bind(this)}>
&times;
</button>
<input
type="checkbox"
readOnly
checked={this.props.task.checked}
onClick={this.toggleChecked.bind(this)}
/>
<span className="text">{this.props.task.text}</span>
</li>
);
}
}

更新

在上面代码中,我们调用Tasks.update去检查一个任务。

集合上的更新功能需要两个参数。第一个参数是标识集合子集的选择器,第二个是一个update参数,用来指定要对匹配对象做什么。

在这个例子中,选择器就是相对应的任务的_id,update参数用$set去切换checked字段,这将代表任务是否已经完成。

删除

上面代码使用Tasks.remove去删除任务。remove函数只需要一个参数,一个选择器,用于确定要在集合中删除的项目。

6.在Android或iOS上运行app

目前,Meteor并不支持在Windows上进行移动端打包。如果你是Windows用户,请忽略这一章节。

目前为止,我们构建了我们的app,并在浏览器上对它进行测试,但是Meteor是为了跨平台而设计的——只需要几句命令,就可以把你的待办事项列表网站构建成Android或iOS应用。

Meteor可以轻松的设置所有构建app所需的工具,但是下载所有程序可能需要一段时间——Android大约300M,iOS要安装Xcode大约2GB,如果你不希望下载这些工具,你可以调到下一个章节。

在iOS模拟器上运行

如果你有Mac,你可以运行在iOS模拟器上运行app。

进入到app目录并键入:

1
meteor install-sdk ios

它会安装你构建iOS app所要用到的所有所有配置。当上面安装完成,键入:

1
2
meteor add-platform ios
meteor run ios

你会看到iOS模拟器弹出,并且你的app在里面运行。

在Android模拟器上运行

在terminal中进入你的app目录并键入:

1
meteor install-sdk android

它会安装你构建Android app所要用到的所有所有配置。当上面安装完成,键入:

1
meteor add-platform android

同意协议条款,键入:

1
meteor run android

在一些初始化后,我们将看到一个Android模拟器弹出,你的app在原生生Android容器里运行。

模拟器运行可能会有一些慢,所以如果你想看到应用真实的情况,还得到真实设备上使用。

在Android设备上运行

首先,完成上述的所有Android设置步骤。然后,确定你的设备有USB调试模式而且手机插进了电脑的USB插口。此外,你必须在真机运行前关闭安卓模拟器。

接下来,键入以下命令:

1
meteor run android-device

你的app将会构建并安装到你的设备上。

在iPhone或iPad上运行(只限Mac:要有苹果开发者账号)

如果你有苹果开发者账号,你也可以在iOS设备上运行你的app。键入以下命令:

1
meteor run ios-device

这将会为你的iOS app项目打开Xcode。你可以使用Xcode让app运行在任何Xcode支持的设备或模拟器上。

现在我们看到了在移动端运行我们的app多么容易,让我们给app再添加一些特性。

7.在组件状态中存储临时的用户界面数据

在这一章节,我们将在app中添加客户端数据过滤功能,以便用户选择只查看未完成的任务。我们将学习怎样使用React的component state去存储只在客户端用到的临时数据。

首先,我们需要增加一个复选框到App组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//imports/ui/App.jsx
<header>
<h1>Todo List</h1>
//新添加内容
<label className="hide-completed">
<input
type="checkbox"
readOnly
checked={this.state.hideCompleted}
onClick={this.toggleHideCompleted.bind(this)}
/>
Hide Completed Tasks
</label>
//新添加内容

你可以看到它从this.state.hideCompleted读取数据。React有一个特殊的字段叫state,你可以把组件数据存储和封装在里面。我们需要在组件的构造器中初始化this.state.hideCompleted的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
//imports/ui/App.jsx
class App extends Component {
//新添加的内容
constructor(props) {
super(props);
this.state = {
hideCompleted: false,
};
}
//新添加的内容
}

我们可以事件管理器中调用this.setState去更新this.state,这将异步更新state属性,并重新渲染组件:

1
2
3
4
5
6
7
8
9
10
11
12
//imports/ui/App.jsx
ReactDOM.findDOMNode(this.refs.textInput).value = '';
}
//新添加的内容
toggleHideCompleted() {
this.setState({
hideCompleted: !this.state.hideCompleted,
});
}
//新添加的内容
}

this.state.hideCompleted为true时,我们需要更新renderTasks方法去过滤已经完成的任务:

1
2
3
4
5
6
7
8
9
10
11
//imports/ui/App.jsx
renderTasks() {
let filteredTasks = this.props.tasks;
if (this.state.hideCompleted) {
filteredTasks = filteredTasks.filter(task => !task.checked);
}
return filteredTasks.map((task) => (
<Task key={task._id} task={task} />
));
}

如果你现在选择复选框,任务列表不会再展现已经完成的任务了。

新的特性:显示未完成任务的数目

现在我们要写一个查询条件,用来过滤掉已经完成的任务。我们也可以用相同的查询来显示已完成的任务总数。为此,我们需要在我们的数据容器中拿到一个计数,并在render方法中添加一行代码。因为我们已经有客户端集合中的数据,增加这个额外的计数不需要向服务端发起任何请求。

1
2
3
4
5
6
7
8
9
10
//imports/ui/App.jsx
export default createContainer(() => {
return {
tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
//新添加的的内容
incompleteCount: Tasks.find({ checked: { $ne: true } }).count(),
//新添加的的内容
};
}, App);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//imports/ui/App.jsx
return (
<div className="container">
<header>
//新添加的的内容
<h1>Todo List ({this.props.incompleteCount})</h1>
//新添加的的内容
<label className="hide-completed">
<input
//...省略一些代码...
App.propTypes = {
tasks: PropTypes.array.isRequired,
//新添加的的内容
incompleteCount: PropTypes.number.isRequired,
//新添加的的内容
};
)

8.添加账户

Meteor自带一个账户系统和用户登录接口,让你在短短几分钟就能添加多用户功能到你的app内。

目前,Meteor使用Blaze UI组件作为它的默认UI引擎。在未来,也可能会有专为React定制的组件。

开启账户系统和UI,我们需要增加相对应的包。在你的app目录,运行以下命令:

1
meteor add accounts-ui accounts-password

在React中写Blaze组件

要通过accounts-ui包使用Blaze UI,我们需要把它包裹在React组件中。这样让我们在新文件中创建了一个叫AccountsUIWrapper的新组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//imports/ui/AccountsUIWrapper.jsx
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Template } from 'meteor/templating';
import { Blaze } from 'meteor/blaze';
export default class AccountsUIWrapper extends Component {
componentDidMount() {
//使用Meteor Blaze渲染登录按钮
this.view = Blaze.render(Template.loginButtons,
ReactDOM.findDOMNode(this.refs.container));
}
componentWillUnmount() {
//清除Blaze视图
Blaze.remove(this.view);
}
render() {
//只渲染一个占位符容器即可
return <span ref="container" />;
}
}

在App中定义刚刚创建的组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//imports/ui/App.jsx
import { Tasks } from '../api/tasks.js';
import Task from './Task.jsx';
//...新添加的代码...
import AccountsUIWrapper from './AccountsUIWrapper.jsx';
//...新添加的代码...
class App extends Component {
//...省略一些代码...
Hide Completed Tasks
</label>
//...新添加的代码...
<AccountsUIWrapper />
//...新添加的代码...
}

接下来,增加下列代码去配置accounts UI,用username去替换email address:

1
2
3
4
5
6
7
//imports/startup/accounts-config.js
import { Accounts } from 'meteor/accounts-base';
Accounts.ui.config({
passwordSignupFields: 'USERNAME_ONLY',
});

同时我们需要在客户端入口导入这个配置文件:

1
2
3
4
5
6
7
8
9
//client/main.jsx
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
//...新添加的代码...
import '../imports/startup/accounts-config.js';
//...新添加的代码...
import App from '../imports/ui/App.jsx';

添加用户相关功能

本文作者:余震(Freak)
本文出处:Rockjins Blog
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN许可协议。转载请注明出处!

坚持,您的支持将鼓励我继续爬下去!