Skip to content

Meteor.js 3 + React

在本教程中,我们将使用 React 和 Meteor 3.0 创建一个简单的 To-Do 应用。Meteor 可以很好地与其他框架配合使用,例如 BlazeVue 3SolidSvelte

¥In this tutorial, we will create a simple To-Do app using React and Meteor 3.0. Meteor works well with other frameworks like Blaze, Vue 3, Solid, and Svelte.

React 是一个用于构建用户界面的流行 JavaScript 库。它允许你通过组合 UI 组件来创建动态和交互式应用。React 使用声明式方法,你可以根据状态定义 UI 的外观,并在状态发生变化时有效地更新视图。使用 JSX(一种结合了 JavaScript 和 HTML 的语法扩展),React 可以轻松创建可重用的组件,这些组件可以管理自己的状态并在浏览器中无缝渲染。

¥React is a popular JavaScript library for building user interfaces. It allows you to create dynamic and interactive applications by composing UI components. React uses a declarative approach, where you define how the UI should look based on the state, and it efficiently updates the view when the state changes. With JSX, a syntax extension that combines JavaScript and HTML, React makes it easy to create reusable components that manage their own state and render seamlessly in the browser.

要开始构建你的 React 应用,你需要一个代码编辑器。如果你不确定选择哪一个,Visual Studio 代码 是一个不错的选择。安装后,你可以通过添加 Meteor 工具箱 等扩展来增强你的体验。

¥To start building your React app, you'll need a code editor. If you're unsure which one to choose, Visual Studio Code is a good option. After installing it, you can enhance your experience by adding extensions like Meteor Toolbox.

让我们开始构建你的应用!

¥Let’s begin building your app!

目录

¥Table of Contents

1:创建应用

¥1: Creating the app

1.1:安装 Meteor

¥1.1: Install Meteor

首先,我们需要安装 Meteor。

¥First, we need to install Meteor.

如果你尚未安装 Meteor,则可以通过运行以下命令进行安装:

¥If you don't have Meteor installed, you can install it by running:

shell
npx meteor

1.2:创建 Meteor 项目

¥1.2: Create Meteor Project

使用 React 设置 Meteor 的最简单方法是使用命令 meteor create 和选项 --react 以及你的项目名称(你也可以省略 --react 选项,因为它是默认选项):

¥The easiest way to setup Meteor with React is by using the command meteor create with the option --react and your project name (you can also omit the --react option since it is the default):

shell
meteor create simple-todos-react

Meteor 将为你创建所有必要的文件。

¥Meteor will create all the necessary files for you.

位于 client 目录中的文件正在设置你的客户端(Web),例如,你可以看到 client/main.jsx,其中 Meteor 正在将你的 App 主要组件渲染到 HTML 中。

¥The files located in the client directory are setting up your client side (web), you can see for example client/main.jsx where Meteor is rendering your App main component into the HTML.

另外,检查 Meteor 设置服务器端 (Node.js) 的 server 目录,你可以看到 server/main.js 正在用一些数据初始化你的 MongoDB 数据库。你不需要安装 MongoDB,因为 Meteor 提供了一个可供你使用的嵌入式版本。

¥Also, check the server directory where Meteor is setting up the server side (Node.js), you can see the server/main.js is initializing your MongoDB database with some data. You don't need to install MongoDB as Meteor provides an embedded version of it ready for you to use.

你现在可以使用以下命令运行 Meteor 应用:

¥You can now run your Meteor app using:

shell
meteor run

不用担心,从现在开始,Meteor 将使你的应用与所有更改保持同步。

¥Don't worry, Meteor will keep your app in sync with all your changes from now on.

你的 React 代码将位于 imports/ui 目录中,App.jsx 文件是你的 React To-do 应用的根组件。

¥Your React code will be located inside the imports/ui directory, and App.jsx file is the root component of your React To-do app.

快速浏览 Meteor 创建的所有文件,你现在不需要了解它们,但知道它们在哪里会很好。

¥Take a quick look at all the files created by Meteor, you don't need to understand them now but it's good to know where they are.

1.3:创建任务组件

¥1.3: Create Task Component

你现在将进行第一次更改。在 ui 文件夹中创建一个名为 Task.jsx 的新文件。

¥You will make your first change now. Create a new file called Task.jsx in your ui folder.

此文件将导出一个名为 Task 的 React 组件,该组件将代表你的待办事项列表中的一项任务。

¥This file will export a React component called Task that will represent one task in your To-Do list.

js
import React from "react";

export const Task = ({ task }) => {
  return <li>{task.text}</li>;
};

由于此组件将位于列表中,因此你将返回 li 元素。

¥As this component will be inside a list you are returning a li element.

1.4:创建示例任务

¥1.4: Create Sample Tasks

由于你尚未连接到服务器和数据库,让我们定义一些示例数据,这些数据将很快用于渲染任务列表。它将是一个数组,你可以将它称为 tasks

¥As you are not connecting to your server and your database yet let's define some sample data which will be used shortly to render a list of tasks. It will be an array, and you can call it tasks.

js
import React from 'react';

const tasks = [
  {_id: 1, text: 'First Task'},
  {_id: 2, text: 'Second Task'},
  {_id: 3, text: 'Third Task'},
];

export const App = () => ...

你可以将任何内容作为每个任务的 text 属性。发挥创意!

¥You can put anything as your text property on each task. Be creative!

1.5:渲染示例任务

¥1.5: Render Sample Tasks

现在我们可以使用 React 实现一些简单的渲染逻辑。我们现在可以使用我们之前的 Task 组件来渲染我们的列表项。

¥Now we can implement some simple rendering logic with React. We can now use our previous Task component to render our list items.

在 React 中,你可以使用 { } 在它们之间编写 Javascript 代码。

¥In React you can use { } to write Javascript code between them.

请参见下文,你将使用 Array 对象中的 .map 函数来迭代示例任务。

¥See below that you will use a .map function from the Array object to iterate over your sample tasks.

js
import React from 'react';
import { Task } from './Task';

const tasks = ..;

export const App = () => (
  <div>
    <h1>Welcome to Meteor!</h1>

    <ul>
      { tasks.map(task => <Task key={ task._id } task={ task }/>) }
    </ul>
  </div>
);

请记住将 key 属性添加到你的任务中,否则 React 会发出警告,因为它会看到许多相同类型的组件作为同级组件。如果没有密钥,React 将很难在必要时重新渲染其中一个。

¥Remember to add the key property to your task, otherwise React will emit a warning because it will see many components of the same type as siblings. Without a key, it will be hard for React to re-render one of them if necessary.

你可以阅读有关 React 和 Keys 此处 的更多信息。

¥You can read more about React and Keys here.

App 组件中删除 HelloInfo,记得还要删除文件顶部的导入。同时删除 Hello.jsxInfo.jsx 文件。

¥Remove the Hello and Info from your App component, remember to also remove the imports for them at the top of the file. Remove the Hello.jsx and Info.jsx files as well.

1.6:热模块替换

¥1.6: Hot Module Replacement

默认情况下,使用 React 时,Meteor 已经为你添加了一个名为 hot-module-replacement 的包。此软件包更新正在运行的应用中在重建期间修改的 javascript 模块。减少开发过程中的反馈周期,以便你可以更快地查看和测试更改(它甚至在构建完成之前更新应用)。你也不会丢失状态,你的应用代码将被更新,你的状态将保持不变。

¥Meteor by default when using React is already adding for you a package called hot-module-replacement. This package updates the javascript modules in a running app that were modified during a rebuild. Reduces the feedback cycle while developing so you can view and test changes quicker (it even updates the app before the build has finished). You are also not going to lose the state, your app code will be updated and your state will be the same.

在下一步中,我们将使用我们的 MongoDB 数据库来存储我们的任务。

¥In the next step, we are going to work with our MongoDB database to store our tasks.

2:集合

¥2: Collections

Meteor 已经为你设置了 MongoDB。为了使用我们的数据库,我们需要创建一个集合,我们将在其中存储我们的文档,在我们的例子中是 tasks

¥Meteor already sets up MongoDB for you. In order to use our database, we need to create a collection, which is where we will store our documents, in our case our tasks.

你可以阅读有关集合 此处 的更多信息。

¥You can read more about collections here.

在此步骤中,我们将实现所有必要的代码,以便使用 React hooks 为我们的任务建立一个基本集合。

¥In this step, we will implement all the necessary code to have a basic collection for our tasks up and running using React hooks.

2.1:创建任务集合

¥2.1: Create Tasks Collection

我们可以通过在 imports/api/TasksCollection.js 处创建一个新文件来创建一个新集合来存储我们的任务,该文件实例化一个新的 Mongo 集合并将其导出。

¥We can create a new collection to store our tasks by creating a new file at imports/api/TasksCollection.js which instantiates a new Mongo collection and exports it.

js
import { Mongo } from "meteor/mongo";

export const TasksCollection = new Mongo.Collection("tasks");

请注意,我们将文件存储在 imports/api 目录中,该目录是存储 API 相关代码(如发布物和方法)的地方。你可以根据需要命名此文件夹,这只是一个选择。

¥Notice that we stored the file in the imports/api directory, which is a place to store API-related code, like publications and methods. You can name this folder as you want, this is just a choice.

你可以删除此文件夹中的 links.js 文件,因为我们不会使用此集合。

¥You can delete the links.js file in this folder as we are not going to use this collection.

你可以阅读有关应用结构和导入/导出 此处 的更多信息。

¥You can read more about app structure and imports/exports here.

2.2:初始化任务集合

¥2.2: Initialize Tasks Collection

为了使我们的集合正常工作,你需要将其导入服务器,以便设置一些管道。

¥For our collection to work, you need to import it in the server so it sets some plumbing up.

如果你要在同一个文件上使用,你可以使用 import "/imports/api/TasksCollection"import { TasksCollection } from "/imports/api/TasksCollection",但请确保它已导入。

¥You can either use import "/imports/api/TasksCollection" or import { TasksCollection } from "/imports/api/TasksCollection" if you are going to use on the same file, but make sure it is imported.

现在很容易检查我们的集合中是否有数据,否则,我们也可以轻松插入一些示例数据。

¥Now it is easy to check if there is data or not in our collection, otherwise, we can insert some sample data easily as well.

你不需要保留 server/main.js 的旧内容。

¥You don't need to keep the old content of server/main.js.

js
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "/imports/api/TasksCollection";

const insertTask = (taskText) =>
  TasksCollection.insertAsync({ text: taskText });

Meteor.startup(async () => {
  if ((await TasksCollection.find().countAsync()) === 0) {
    [
      "First Task",
      "Second Task",
      "Third Task",
      "Fourth Task",
      "Fifth Task",
      "Sixth Task",
      "Seventh Task",
    ].forEach(insertTask);
  }
});

因此,你正在导入 TasksCollection 并向其添加一些任务,遍历字符串数组,并为每个字符串调用一个函数将此字符串作为我们的 text 字段插入到我们的 task 文档中。

¥So you are importing the TasksCollection and adding a few tasks to it iterating over an array of strings and for each string calling a function to insert this string as our text field in our task document.

2.3:渲染任务集合

¥2.3: Render Tasks Collection

现在到了有趣的部分,你将使用 React 函数组件和来自 react-meteor-data 包的名为 useTracker 的 Hook 来渲染任务。

¥Now comes the fun part, you will render the tasks using a React Function Component and a Hook called useTracker from a package called react-meteor-data.

Meteor 与 Meteor 包和 NPM 包一起工作,通常,Meteor 包使用 Meteor 内部或其他 Meteor 包。

¥Meteor works with Meteor packages and NPM packages, usually, Meteor packages are using Meteor internals or other Meteor packages.

此包已包含在 React 骨架(meteor create yourproject)中,因此你不需要添加它,但你始终可以添加运行 meteor add package-name 的 Meteor 包:

¥This package is already included in the React skeleton (meteor create yourproject) so you don't need to add it but you can always add Meteor packages running meteor add package-name:

shell
meteor add react-meteor-data

现在你已准备好从此包导入代码,当从 Meteor 包导入代码时,与 NPM 模块的唯一区别是你需要在导入的 from 部分中添加 meteor/

¥Now you are ready to import code from this package, when importing code from a Meteor package the only difference from NPM modules is that you need to prepend meteor/ in the from part of your import.

react-meteor-data 导出的 useTracker 函数是一个 React Hook,可让你在 React 组件中具有反应性。每次数据通过反应性发生变化时,你的组件都会重新渲染。很酷吧?

¥The useTracker function exported by react-meteor-data is a React Hook that allows you to have reactivity in your React components. Every time the data changes through reactivity your component will re-render. Cool, right?

有关 React Hooks 的更多信息,请阅读 此处

¥For more information about React Hooks read here.

javascript
import React from "react";
import { useTracker } from "meteor/react-meteor-data";
import { TasksCollection } from "/imports/api/TasksCollection";
import { Task } from "./Task";

export const App = () => {
  const tasks = useTracker(() => TasksCollection.find({}).fetch());

  return (
    <div>
      <h1>Welcome to Meteor!</h1>

      <ul>
        {tasks.map((task) => (
          <Task key={task._id} task={task} />
        ))}
      </ul>
    </div>
  );
};

但请稍等!缺少某些内容。如果你现在运行你的应用,你将看到你没有渲染任何任务。

¥But wait! Something is missing. If you run your app now, you'll see that you don't render any tasks.

这是因为我们需要将数据发布到客户端。

¥That's because we need to publish our data to the client.

首先,为我们的任务创建一个发布:

¥Fist, create a publication for our tasks:

imports/api/TasksPublications.js

javascript
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "./TasksCollection";

Meteor.publish("tasks", () => {
  return TasksCollection.find();
});

现在,我们需要将此文件导入我们的服务器:

¥Now, we need to import this file in our server:

js
...
import { TasksCollection } from '/imports/api/TasksCollection';

import "../imports/api/TasksPublications"; 

const insertTask = taskText => TasksCollection.insertAsync({ text: taskText });
...

剩下唯一要做的事情就是订阅此发布:

¥The only thing left is subscribe to this publication:

imports/ui/App.jsx

javascript
import React from 'react';
import { useTracker, useSubscribe } from 'meteor/react-meteor-data'; 
import { TasksCollection } from '/imports/api/TasksCollection';
import { Task } from './Task';

export const App = () => {

  const isLoading = useSubscribe("tasks");  
  const tasks = useTracker(() => TasksCollection.find({}).fetch());

  if (isLoading()) {
    return <div>Loading...</div>;
  }
  ...
}

如你所见,当使用 useSubscribe 订阅发布物时,你将获得一个 isLoading 函数,你可以使用它在数据准备好之前渲染一些加载组件。

¥As you can see, when subscribing to a publication using useSubscribe you'll get a isLoading function, that you can use to render some loading component before the data is ready.

有关发布物/订阅的更多信息,请查看我们的 docs

¥For more information on Publications/Subscriptions, please check our docs.

查看你的应用现在应该是什么样子:

¥See how your app should look like now:

你可以在服务器上更改 MongoDB 上的数据,你的应用将做出反应并为你重新渲染。

¥You can change your data on MongoDB in the server and your app will react and re-render for you.

你可以从应用文件夹或使用 Mongo UI 客户端(如 NoSQLBooster)连接到在终端中运行 meteor mongo 的 MongoDB。你的嵌入式 MongoDB 正在端口 3001 中运行。

¥You can connect to your MongoDB running meteor mongo in the terminal from your app folder or using a Mongo UI client, like NoSQLBooster. Your embedded MongoDB is running in port 3001.

查看如何连接:

¥See how to connect:

查看你的数据库:

¥See your database:

你可以双击你的集合以查看存储在其中的文档:

¥You can double-click your collection to see the documents stored on it:

在下一步中,我们将使用表单创建任务。

¥In the next step, we are going to create tasks using a form.

3:表单和事件

¥3: Forms and Events

所有应用都需要允许用户与存储的数据进行某种交互。在我们的例子中,第一种交互类型是插入新任务。没有它,我们的 To-Do 应用就不会很有用。

¥All apps need to allow the user to perform some sort of interaction with the data that is stored. In our case, the first type of interaction is to insert new tasks. Without it, our To-Do app wouldn't be very helpful.

用户在网站上插入或编辑数据的主要方式之一是通过表单。在大多数情况下,使用 <form> 标签是个好主意,因为它赋予其中的元素语义。

¥One of the main ways in which a user can insert or edit data on a website is through forms. In most cases, it is a good idea to use the <form> tag since it gives semantic meaning to the elements inside it.

3.1:创建任务表单

¥3.1: Create Task Form

首先,我们需要创建一个简单的表单组件来封装我们的逻辑。如你所见,我们设置了 useState React Hook。

¥First, we need to create a simple form component to encapsulate our logic. As you can see we set up the useState React Hook.

请注意数组解构 [text, setText],其中 text 是我们要使用的存储值,在本例中将是一个字符串;setText 是用于更新该值的函数。

¥Please note the array destructuring [text, setText], where text is the stored value which we want to use, which in this case will be a string; and setText is a function used to update that value.

ui 文件夹中创建一个新文件 TaskForm.jsx

¥Create a new file TaskForm.jsx in your ui folder.

js
import React, { useState } from "react";

export const TaskForm = () => {
  const [text, setText] = useState("");

  return (
    <form className="task-form">
      <input type="text" placeholder="Type to add new tasks" />

      <button type="submit">Add Task</button>
    </form>
  );
};

3.2:更新应用组件

¥3.2: Update the App component

然后我们可以简单地将其添加到任务列表上方的 App 组件中:

¥Then we can simply add this to our App component above your list of tasks:

js
import React from "react";
import { useTracker, useSubscribe } from "meteor/react-meteor-data";
import { TasksCollection } from "/imports/api/TasksCollection";
import { Task } from "./Task";
import { TaskForm } from "./TaskForm";

export const App = () => {
  const isLoading = useSubscribe("tasks");
  const tasks = useTracker(() => TasksCollection.find({}).fetch());

  if (isLoading()) {
    return <div>Loading...</div>;
  }
  return (
    <div>
      <h1>Welcome to Meteor!</h1>

      <TaskForm />

      <ul>
        {tasks.map((task) => (
          <Task key={task._id} task={task} />
        ))}
      </ul>
    </div>
  );
};

3.3:更新样式表

¥3.3: Update the Stylesheet

你还可以根据需要设置其样式。目前,我们只需要顶部的一些边距,这样表单就不会显得偏离目标。添加 CSS 类 .task-form,这需要与表单组件中 className 属性中的名称相同。

¥You also can style it as you wish. For now, we only need some margin at the top so the form doesn't seem off the mark. Add the CSS class .task-form, this needs to be the same name in your className attribute in the form component.

css
.task-form {
  margin-top: 1rem;
}

3.4:添加提交处理程序

¥3.4: Add Submit Handler

现在让我们创建一个函数来处理表单提交并将新任务插入数据库。为此,我们需要实现一个 Meteor 方法。

¥Now let's create a function to handle the form submit and insert a new task into the database. To do it, we will need to implement a Meteor Method.

方法本质上是对服务器的 RPC 调用,可让你安全地在服务器端执行操作。你可以阅读有关 Meteor 方法 此处 的更多信息。

¥Methods are essentially RPC calls to the server that let you perform operations on the server side securely. You can read more about Meteor Methods here.

要创建方法,你可以创建一个名为 tasksMethods.js 的文件。

¥To create your methods, you can create a file called tasksMethods.js.

javascript
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "./TasksCollection";

Meteor.methods({
  "tasks.insert"(doc) {
    return TasksCollection.insertAsync(doc);
  },
});

请记住在 main.js 服务器文件和 main.jsx 客户端文件上导入你的方法。

¥Remember to import your method on the main.js server file and the main.jsx client file.

javascript
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "../imports/api/tasksCollection";
import "../imports/api/TasksPublications";
import "../imports/api/tasksMethods"; 
javascript
import React from "react";
import { createRoot } from "react-dom/client";
import { Meteor } from "meteor/meteor";
import { App } from "/imports/ui/App";

import "../imports/api/tasksMethods"; 

现在你可以使用 onSubmit 事件将提交处理程序附加到表单,并将你的 React Hook 插入输入元素中的 onChange 事件。

¥Now you can attach a submit handler to your form using the onSubmit event, and also plug your React Hook into the onChange event present in the input element.

如你所见,你正在使用 useState React Hook 来存储 <input> 元素的 value。请注意,你还需要将 value 属性设置为 text 常量,这将允许 input 元素与我们的钩子保持同步。

¥As you can see you are using the useState React Hook to store the value of your <input> element. Note that you also need to set your value attribute to the text constant as well, this will allow the input element to stay in sync with our hook.

在更复杂的应用中,如果在可能频繁发生的事件(如 onChange)之间发生许多计算,你可能需要实现一些 debouncethrottle 逻辑。有些库可以帮助你实现这一点,例如 Lodash

¥In more complex applications you might want to implement some debounce or throttle logic if there are many calculations happening between potentially frequent events like onChange. There are libraries which will help you with this, like Lodash, for instance.

js
import React, { useState } from "react";
import { TasksCollection } from "/imports/api/TasksCollection";

export const TaskForm = () => {
  const [text, setText] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();

    if (!text) return;

    await Meteor.callAsync("tasks.insert", {
      text: text.trim(),
      createdAt: new Date(),
    });

    setText("");
  };

  return (
    <form className="task-form" onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Type to add new tasks"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />

      <button type="submit">Add Task</button>
    </form>
  );
};

在函数内部,我们通过调用 Meteor.callAsync() 将任务添加到 tasks 集合。第一个参数是我们要调用的方法的名称,第二个参数是任务的文本。我们还在修剪文本以删除任何多余的空格。

¥Inside the function, we are adding a task to the tasks collection by calling Meteor.callAsync(). The first argument is the name of the method we want to call, and the second argument is the text of the task. We are also trimming the text to remove any extra spaces.

另外,在你的 task 文档中插入一个日期 createdAt,这样你就知道每个任务的创建时间。

¥Also, insert a date createdAt in your task document so you know when each task was created.

3.5:首先显示最新任务

¥3.5: Show Newest Tasks First

现在,你只需要进行一些让用户满意的更改:我们需要先显示最新任务。我们可以通过对 Mongo 查询进行排序来快速完成此操作。

¥Now you just need to make a change that will make users happy: we need to show the newest tasks first. We can accomplish this quite quickly by sorting our Mongo query.

js
..

export const App = () => {
  const tasks = useTracker(() => TasksCollection.find({}, { sort: { createdAt: -1 } }).fetch());
  ..

你的应用应如下所示:

¥Your app should look like this:

在下一步中,我们将更新你的任务状态并为用户提供一种删除任务的方法。

¥In the next step, we are going to update your tasks state and provide a way for users to remove tasks.

4:更新和删除

¥4: Update and Remove

到目前为止,你只将文档插入到我们的集合中。让我们看看如何通过与用户界面交互来更新和删除它们。

¥Up until now, you have only inserted documents into our collection. Let's take a look at how you can update and remove them by interacting with the user interface.

4.1:添加复选框

¥4.1: Add Checkbox

首先,你需要向你的 Task 组件添加一个 checkbox 元素。

¥First, you need to add a checkbox element to your Task component.

请务必添加 readOnly 属性,因为我们不使用 onChange 来更新状态。

¥Be sure to add the readOnly attribute since we are not using onChange to update the state.

我们还必须将我们的 checked prop 强制为 boolean,因为 React 知道 undefined 值不存在,因此导致组件从不受控切换到受控。

¥We also have to force our checked prop to a boolean since React understands that an undefined value as inexistent, therefore causing the component to switch from uncontrolled to a controlled one.

我们还邀请你进行实验,并查看应用如何表现以供学习。

¥You are also invited to experiment and see how the app behaves for learning purposes.

你还想接收一个回调,即单击复选框时将调用的函数。

¥You also want to receive a callback, a function that will be called when the checkbox is clicked.

js
import React from "react";

export const Task = ({ task, onCheckboxClick }) => {
  return (
    <li>
      <input
        type="checkbox"
        checked={!!task.isChecked}
        onClick={() => onCheckboxClick(task)}
        readOnly
      />
      <span>{task.text}</span>
    </li>
  );
};

4.2:切换复选框

¥4.2: Toggle Checkbox

现在,你可以通过切换其 isChecked 字段来更新你的任务文档。

¥Now you can update your task document by toggling its isChecked field.

首先,创建一个名为 tasks.toggleChecked 的新方法来更新 isChecked 属性。

¥First, create a new method called tasks.toggleChecked to update the isChecked property.

javascript
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "./TasksCollection";

Meteor.methods({
  ..
  "tasks.toggleChecked"({ _id, isChecked }) {
    return TasksCollection.updateAsync(_id, {
      $set: { isChecked: !isChecked },
    });
  },
});

现在,创建一个函数来更改你的文档并将其传递给你的 Task 组件。

¥Now, create a function to change your document and pass it along to your Task component.

js
..

export const App = () => {
  const handleToggleChecked = ({ _id, isChecked }) =>
    Meteor.callAsync("tasks.toggleChecked", { _id, isChecked });
  ..
  <ul>
    { tasks.map(task => <Task key={ task._id } task={ task } onCheckboxClick={handleToggleChecked} />) }
  </ul>
  ..

你的应用应如下所示:

¥Your app should look like this:

4.3:删除任务

¥4.3: Remove tasks

你只需几行代码即可删除任务。

¥You can remove tasks with just a few lines of code.

首先,在 Task 组件中的文本后添加一个按钮并接收回调函数。

¥First, add a button after text in your Task component and receive a callback function.

js
import React from 'react';

export const Task = ({ task, onCheckboxClick, onDeleteClick }) => {
  return (
..
      <span>{task.text}</span>
      <button onClick={ () => onDeleteClick(task) }>&times;</button>
..

现在在 App 中添加删除逻辑,你需要有一个函数来删除任务,并在 Task 组件中的回调属性中提供此函数。

¥Now add the removal logic in the App, you need to have a function to delete the task and provide this function in your callback property in the Task component.

为此,让我们创建一个名为 tasks.delete 的新方法:

¥For that, let's create a new method called tasks.delete:

javascript
import { Meteor } from "meteor/meteor";
import { TasksCollection } from "./TasksCollection";

Meteor.methods({
  ..
  "tasks.delete"({ _id }) {
    return TasksCollection.removeAsync(_id);
  },
});

然后,让我们在 handleDelete 函数内调用此方法:

¥Then, let's call this method inside a handleDelete function:

js
export const App = () => {
  ..
  const handleDelete = ({ _id }) =>
    Meteor.callAsync("tasks.delete", { _id });
  ..
  <ul>
    { tasks.map(task => <Task
      key={ task._id }
      task={ task }
      onCheckboxClick={handleToggleChecked}
      onDeleteClick={handleDelete} 
    />) }
  </ul>
  ..
}

你的应用应如下所示:

¥Your app should look like this:

在下一步中,我们将使用带有 Flexbox 的 CSS 改善应用的外观。

¥In the next step, we are going to improve the look of your app using CSS with Flexbox.

5:样式

¥5: Styles

5.1:CSS

到目前为止,我们的用户界面看起来相当丑陋。让我们添加一些基本样式,作为更专业的应用的基础。

¥Our user interface up until this point has looked quite ugly. Let's add some basic styling which will serve as the foundation for a more professional looking app.

用下面的内容替换我们的 client/main.css 文件的内容,想法是在顶部有一个应用栏,以及一个可滚动的内容,包括:

¥Replace the content of our client/main.css file with the one below, the idea is to have an app bar at the top, and a scrollable content including:

  • 添加新任务的表单;

    ¥form to add new tasks;

  • 任务列表。

    ¥list of tasks.

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;
}

button {
  font-weight: bold;
  font-size: 1em;
  border: none;
  color: white;
  box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
  padding: 5px;
  cursor: pointer;
}

button:focus {
  outline: 0;
}

.app {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.app-header {
  flex-grow: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.main {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  overflow: auto;
  background: white;
}

.main::-webkit-scrollbar {
  width: 0;
  height: 0;
  background: inherit;
}

header {
  background: #d2edf4;
  background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
  padding: 20px 15px 15px 15px;
  position: relative;
  box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
}

.app-bar {
  display: flex;
  justify-content: space-between;
}

.app-bar h1 {
  font-size: 1.5em;
  margin: 0;
  display: inline-block;
  margin-right: 1em;
}

.task-form {
  display: flex;
  margin: 16px;
}

.task-form > input {
  flex-grow: 1;
  box-sizing: border-box;
  padding: 10px 6px;
  background: transparent;
  border: 1px solid #aaa;
  width: 100%;
  font-size: 1em;
  margin-right: 16px;
}

.task-form > input:focus {
  outline: 0;
}

.task-form > button {
  min-width: 100px;
  height: 95%;
  background-color: #315481;
}

.tasks {
  list-style-type: none;
  padding-inline-start: 0;
  padding-left: 16px;
  padding-right: 16px;
  margin-block-start: 0;
  margin-block-end: 0;
}

.tasks > li {
  display: flex;
  padding: 16px;
  border-bottom: #eee solid 1px;
  align-items: center;
}

.tasks > li > span {
  flex-grow: 1;
}

.tasks > li > button {
  justify-self: flex-end;
  background-color: #ff3046;
}

如果你想了解有关此样式表的更多信息,请查看有关 Flexbox 的这篇文章,以及来自 Wes Bos 的关于它的免费 视频教程

¥If you want to learn more about this stylesheet check this article about Flexbox, and also this free video tutorial about it from Wes Bos.

Flexbox 是分发和对齐 UI 中元素的绝佳工具。

¥Flexbox is an excellent tool to distribute and align elements in your UI.

5.2:应用样式

¥5.2: Applying styles

现在,你需要在组件周围添加一些元素。你将在 App 中的主 div 中添加一个 className,在 h1 周围添加一个 header 元素和一些 divs,在表单和列表周围添加一个主 div。检查下面应该如何,注意类的名称,它们需要与 CSS 文件中的名称相同:

¥Now you need to add some elements around your components. You are going to add a className to your main div in the App, also a header element with a few divs around your h1, and a main div around your form and list. Check below how it should be, pay attention to the name of the classes, they need to be the same as in the CSS file:

js
  ..
  return (
    <div className="app">
      <header>
        <div className="app-bar">
          <div className="app-header">
            <h1>Welcome to Meteor!</h1>
          </div>
        </div>
      </header>
      <div className="main">
        <TaskForm />

        <ul className="tasks">
          {tasks.map((task) => (
            <Task
              key={task._id}
              task={task}
              onCheckboxClick={handleToggleChecked}
              onDeleteClick={handleDelete}
            />
          ))}
        </ul>
      </div>
    </div>
  );

在 React 中,我们使用 className 而不是 class,因为 React 使用 Javascript 来定义 UI,而 class 是 Javascript 中的保留字。

¥In React we use className instead of class as React uses Javascript to define the UI and class is a reserved word in Javascript.

另外,为你的应用选择一个更好的标题,Meteor 很棒,但你不想一直在应用顶部栏看到 Welcome to Meteor!

¥Also, choose a better title for your app, Meteor is amazing but you don't want to see Welcome to Meteor! in your app top bar all the time.

你可以选择类似以下内容:

¥You could choose something like:

js
  ..
  <h1>📝️ To Do List</h1>
  ..

你的应用应如下所示:

¥Your app should look like this:

在下一步中,我们将使此任务列表更具交互性,例如,提供一种过滤任务的方法。

¥In the next step, we are going to make this task list more interactive, for example, providing a way to filter tasks.

6:过滤任务

¥6: Filter tasks

在此步骤中,你将按状态过滤任务并显示待处理任务的数量。

¥In this step, you will filter your tasks by status and show the number of pending tasks.

6.1:useState

首先,你要添加一个按钮来显示或隐藏列表中已完成的任务。

¥First, you are going to add a button to show or hide the completed tasks from the list.

React 中的 useState 函数是保持此按钮状态的最佳方式。它返回一个包含两个项目的数组,其中第一个元素是状态的值,第二个元素是设置函数,这是你将如何更新状态。你可以使用数组解构来取回这两个并为它们声明一个变量。

¥The useState function from React is the best way to keep the state of this button. It returns an array with two items, where the first element is the value of the state, and the second is a setter function that is how you are going to update your state. You can use array destructuring to get these two back and already declare a variable for them.

请记住,用于常量的名称不属于 React API,你可以随意命名它们。

¥Bear in mind that the names used for the constants do not belong to the React API, you can name them whatever you like.

此外,在任务表单下方添加一个 button,它将根据当前状态显示不同的文本。

¥Also, add a button below the task form that will display a different text based on the current state.

js
import React, { useState } from 'react';
..
export const App = () => {
  const [hideCompleted, setHideCompleted] = useState(false);

  ..
    <div className="main">
      <TaskForm />
       <div className="filter">
         <button onClick={() => setHideCompleted(!hideCompleted)}>
           {hideCompleted ? 'Show All' : 'Hide Completed'}
         </button>
       </div>
  ..

你可以阅读有关 useState 钩子 此处 的更多信息。

¥You can read more about the useState hook here.

我们建议你始终在组件的顶部添加钩子,这样可以更容易地避免一些问题,例如始终以相同的顺序运行它们。

¥We recommend that you add your hooks always in the top of your components, so it will be easier to avoid some problems, like always running them in the same order.

6.2:按钮样式

¥6.2: Button style

你应该为按钮添加一些样式,使其看起来不会是灰色的并且没有很好的对比度。你可以使用以下样式作为参考:

¥You should add some style to your button so it does not look gray and without a good contrast. You can use the styles below as a reference:

css
.filter {
  display: flex;
  justify-content: center;
}

.filter > button {
  background-color: #62807e;
}

6.3:过滤任务

¥6.3: Filter Tasks

现在,如果用户只想查看待处理的任务,你可以在 Mini Mongo 查询中向选择器添加一个过滤器,你希望获取所有不为 isChecked 的任务。

¥Now, if the user wants to see only pending tasks you can add a filter to your selector in the Mini Mongo query, you want to get all the tasks that are not isChecked true.

js
..
  const hideCompletedFilter = { isChecked: { $ne: true } };

  const tasks = useTracker(() =>
    TasksCollection.find(hideCompleted ? hideCompletedFilter : {}, {
      sort: { createdAt: -1 },
    }).fetch()
  );
..

6.4:Meteor Dev Tools 扩展

¥6.4: Meteor Dev Tools Extension

你可以安装扩展程序以可视化 Mini Mongo 中的数据。

¥You can install an extension to visualize the data in your Mini Mongo.

Meteor DevTools Evolved 将帮助你调试应用,因为你可以看到 Mini Mongo 上有什么数据。

¥Meteor DevTools Evolved will help you to debug your app as you can see what data is on Mini Mongo.

你还可以查看 Meteor 从服务器发送和接收的所有消息,这对你了解 Meteor 的工作原理很有用。

¥You can also see all the messages that Meteor is sending and receiving from the server, this is useful for you to learn more about how Meteor works.

使用此 link 将其安装在你的 Google Chrome 浏览器中。

¥Install it in your Google Chrome browser using this link.

6.5:待处理任务

¥6.5: Pending tasks

更新 App 组件以显示应用栏中待处理任务的数量。

¥Update the App component in order to show the number of pending tasks in the app bar.

当没有待处理任务时,你应该避免在应用栏中添加零。

¥You should avoid adding zero to your app bar when there are no pending tasks.

js
..
  const pendingTasksCount = useTracker(() =>
    TasksCollection.find(hideCompletedFilter).count()
  );

  const pendingTasksTitle = `${
    pendingTasksCount ? ` (${pendingTasksCount})` : ''
  }`;
..

    <h1>
      📝️ To Do List
      {pendingTasksTitle}
    </h1>
..

你可以在同一个 useTracker 中执行两次查找,然后返回具有两个属性的对象,但为了让代码更容易理解,我们在这里创建了两个不同的跟踪器。

¥You could do both finds in the same useTracker and then return an object with both properties but to have a code that is easier to understand, we created two different trackers here.

你的应用应如下所示:

¥Your app should look like this:

下一步,我们将在你的应用中包含用户访问权限。

¥In the next step we are going to include user access in your app.

7:添加用户账户

¥7: Adding User Accounts

7.1:密码身份验证

¥7.1: Password Authentication

Meteor 已经自带了一个基本的开箱即用的身份验证和账户管理系统,因此你只需添加 accounts-password 即可启用用户名和密码身份验证:

¥Meteor already comes with a basic authentication and account management system out of the box, so you only need to add the accounts-password to enable username and password authentication:

shell
meteor add accounts-password

支持更多身份验证方法。你可以阅读有关账户系统 此处 的更多信息。

¥There are many more authentication methods supported. You can read more about the accounts system here.

我们还建议你安装 bcrypt Node 模块,否则你将看到一条警告,提示你正在使用它的纯 Javascript 实现。

¥We also recommend you to install bcrypt node module, otherwise, you are going to see a warning saying that you are using a pure-Javascript implementation of it.

shell
meteor npm install --save bcrypt

你应该始终使用 meteor npm 而不是仅使用 npm,因此你始终使用 Meteor 固定的 npm 版本,这有助于你避免由于不同版本的 npm 安装不同的模块而导致的问题。

¥You should always use meteor npm instead of only npm so you always use the npm version pinned by Meteor, this helps you to avoid problems due to different versions of npm installing different modules.

7.2:创建用户账户

¥7.2: Create User Account

现在你可以为我们的应用创建一个默认用户,我们将使用 meteorite 作为用户名,如果我们在数据库中找不到它,我们只需在服务器启动时创建一个新用户。

¥Now you can create a default user for our app, we are going to use meteorite as username, we just create a new user on server startup if we didn't find it in the database.

js
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TasksCollection } from '/imports/api/TasksCollection';

..

const SEED_USERNAME = 'meteorite';
const SEED_PASSWORD = 'password';

Meteor.startup(async () => {
  if (!(await Accounts.findUserByUsername(SEED_USERNAME))) {
    await Accounts.createUser({
      username: SEED_USERNAME,
      password: SEED_PASSWORD,
    });
  }
  ..
});

你目前不应该在应用 UI 中看到任何不同。

¥You should not see anything different in your app UI yet.

7.3:登录表单

¥7.3: Login Form

你需要为用户提供一种输入凭据和身份验证的方式,为此我们需要一个表单。

¥You need to provide a way for the users to input the credentials and authenticate, for that we need a form.

我们可以使用 useState 钩子来实现它。创建一个名为 LoginForm.jsx 的新文件并向其中添加表单。你应该使用 Meteor.loginWithPassword(username, password); 通过提供的输入对你的用户进行身份验证。

¥We can implement it using useState hook. Create a new file called LoginForm.jsx and add a form to it. You should use Meteor.loginWithPassword(username, password); to authenticate your user with the provided inputs.

js
import { Meteor } from "meteor/meteor";
import React, { useState } from "react";

export const LoginForm = () => {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");

  const submit = (e) => {
    e.preventDefault();

    Meteor.loginWithPassword(username, password);
  };

  return (
    <form onSubmit={submit} className="login-form">
      <label htmlFor="username">Username</label>

      <input
        type="text"
        placeholder="Username"
        name="username"
        required
        onChange={(e) => setUsername(e.target.value)}
      />

      <label htmlFor="password">Password</label>

      <input
        type="password"
        placeholder="Password"
        name="password"
        required
        onChange={(e) => setPassword(e.target.value)}
      />

      <button type="submit">Log In</button>
    </form>
  );
};

好的,现在你有了一个表单,让我们使用它。

¥Ok, now you have a form, let's use it.

7.4:需要身份验证

¥7.4: Require Authentication

我们的应用应该只允许经过身份验证的用户访问其任务管理功能。

¥Our app should only allow an authenticated user to access its task management features.

我们可以通过在没有经过身份验证的用户时返回 LoginForm 组件来实现这一点,否则我们将返回表单、过滤器和列表组件。

¥We can accomplish that by returning the LoginForm component when we don't have an authenticated user, otherwise we return the form, filter, and list component.

你应该首先将 3 个组件(表单、过滤器和列表)封装在 <Fragment> 中,Fragment 是 React 中的特殊组件,你可以使用它将组件组合在一起而不会影响最终的 DOM,这意味着不会影响你的 UI,因为它不会在 HTML 中引入其他元素。

¥You should first wrap the 3 components (form, filter, and list) in a <Fragment>, Fragment is a special component in React that you can use to group components together without affecting your final DOM, it means without affecting your UI as it is not going to introduce other elements in the HTML.

阅读有关 Fragments 此处 的更多信息

¥Read more about Fragments here

因此,你可以从 Meteor.user() 获取经过身份验证的用户或 null,你应该将其封装在 useTracker 钩子中以使其具有响应性。然后你可以根据用户是否存在于会话中返回包含任务和其他所有内容的 FragmentLoginForm

¥So you can get your authenticated user or null from Meteor.user(), you should wrap it in a useTracker hook for it to be reactive. Then you can return the Fragment with Tasks and everything else or LoginForm based on the user being present or not in the session.

js
import { Meteor } from 'meteor/meteor';
import React, { useState, Fragment } from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { TasksCollection } from '/imports/api/TasksCollection';
import { Task } from './Task';
import { TaskForm } from './TaskForm';
import { LoginForm } from './LoginForm';

..
export const App = () => {
  const user = useTracker(() => Meteor.user());

  ..
  return (
      ..
      <div className="main">
        {user ? (
          <Fragment>
            <TaskForm />

            <div className="filter">
              <button onClick={() => setHideCompleted(!hideCompleted)}>
                {hideCompleted ? 'Show All' : 'Hide Completed'}
              </button>
            </div>

            <ul className="tasks">
              {tasks.map(task => (
                <Task
                  key={task._id}
                  task={task}
                  onCheckboxClick={handleToggleChecked}
                  onDeleteClick={handleDelete}
                />
              ))}
            </ul>
          </Fragment>
        ) : (
          <LoginForm />
        )}
      </div>
..

7.5:登录表单样式

¥7.5: Login Form style

好的,现在让我们设置登录表单的样式:

¥Ok, let's style the login form now:

将你的标签和输入对封装在 div 中,以便更容易在 CSS 上控制它。对按钮标签执行相同操作。

¥Wrap your pairs of label and input in divs so it will easier to control it on CSS. Do the same to the button tag.

jsx
<form onSubmit={submit} className="login-form">
  <div>
    <label htmlFor="username">Username</label>

    <input
      type="text"
      placeholder="Username"
      name="username"
      required
      onChange={(e) => setUsername(e.target.value)}
    />
  </div>

  <div>
    <label htmlFor="password">Password</label>

    <input
      type="password"
      placeholder="Password"
      name="password"
      required
      onChange={(e) => setPassword(e.target.value)}
    />
  </div>

  <div>
    <button type="submit">Log In</button>
  </div>
</form>

然后更新 CSS:

¥And then update the CSS:

css
.login-form {
  display: flex;
  flex-direction: column;
  height: 100%;

  justify-content: center;
  align-items: center;
}

.login-form > div {
  margin: 8px;
}

.login-form > div > label {
  font-weight: bold;
}

.login-form > div > input {
  flex-grow: 1;
  box-sizing: border-box;
  padding: 10px 6px;
  background: transparent;
  border: 1px solid #aaa;
  width: 100%;
  font-size: 1em;
  margin-right: 16px;
  margin-top: 4px;
}

.login-form > div > input:focus {
  outline: 0;
}

.login-form > div > button {
  background-color: #62807e;
}

现在,你的登录表单应该集中且美观。

¥Now your login form should be centralized and beautiful.

7.6:服务器启动

¥7.6: Server startup

从现在开始,每个任务都应该有一个所有者。因此,正如你之前所了解的那样,转到你的数据库,并从那里删除所有任务:

¥Every task should have an owner from now on. So go to your database, as you learn before, and remove all the tasks from there:

db.tasks.remove({});

更改你的 server/main.js 以使用你的 meteorite 用户作为所有者添加种子任务。

¥Change your server/main.js to add the seed tasks using your meteorite user as owner.

确保在此更改后重新启动服务器,以便 Meteor.startup 块再次运行。无论如何,这可能都会自动发生,因为你将在服务器端代码中进行更改。

¥Make sure you restart the server after this change so Meteor.startup block will run again. This is probably going to happen automatically anyway as you are going to make changes in the server side code.

js
import { Meteor } from "meteor/meteor";
import { Accounts } from "meteor/accounts-base";
import { TasksCollection } from "/imports/api/TasksCollection";

const insertTask = (taskText, user) =>
  TasksCollection.insert({
    text: taskText,
    userId: user._id,
    createdAt: new Date(),
  });

const SEED_USERNAME = "meteorite";
const SEED_PASSWORD = "password";

Meteor.startup(async () => {
  if (!(await Accounts.findUserByUsername(SEED_USERNAME))) {
    await Accounts.createUser({
      username: SEED_USERNAME,
      password: SEED_PASSWORD,
    });
  }

  const user = await Accounts.findUserByUsername(SEED_USERNAME);

  if ((await TasksCollection.find().countAsync()) === 0) {
    [
      "First Task",
      "Second Task",
      "Third Task",
      "Fourth Task",
      "Fifth Task",
      "Sixth Task",
      "Seventh Task",
    ].forEach((taskText) => insertTask(taskText, user));
  }
});

看到我们正在将一个名为 userId 的新字段与用户 _id 字段一起使用,我们还设置了 createdAt 字段。

¥See that we are using a new field called userId with our user _id field, we are also setting createdAt field.

7.7:任务所有者

¥7.7: Task owner

首先,让我们将发布更改为仅为当前登录的用户发布任务。这对于安全性很重要,因为你只发送属于该用户的数据。

¥First, let's change our publication to publish the tasks only for the currently logged user. This is important for security, as you send only data that belongs to that user.

js
Meteor.publish("tasks", function () {
  const userId = this.userId;
  if (!userId) {
    return this.ready();
  }
  return TasksCollection.find({ userId });
});

现在让我们在尝试获取任何数据之前检查我们是否有 user

¥Now let's check if we have a user before trying to fetch any data:

js
..
    const tasks = useTracker(() => {
      if (!user) {
        return [];
      }

      return TasksCollection.find(
        hideCompleted ? hideCompletedFilter : {},
        {
          sort: { createdAt: -1 },
        }
      ).fetch();
    });

    const pendingTasksCount = useTracker(() => {
      if (!user) {
        return 0;
      }
      ..
    });
..

此外,更新 tasks.insert 方法以在创建新任务时包含字段 userId

¥Also, update the tasks.insert method to include the field userId when creating a new task:

js
..
Meteor.methods({
  "tasks.insert"(doc) {
    return TasksCollection.insertAsync({
      ...doc,
      userId: this.userId,
    });
  },
..

7.8:注销

¥7.8: Log out

我们还可以通过在应用栏下方显示所有者的用户名来更好地组织我们的任务。你可以在我们的 Fragment 开始标记之后立即包含一个新的 div

¥We also can better organize our tasks by showing the username of the owner below our app bar. You can include a new div right after our Fragment start tag.

在此,你也可以添加一个 onClick 处理程序来注销用户。它非常简单,只需调用 Meteor.logout() 即可。

¥On this, you can add an onClick handler to logout the user as well. It is very straightforward, just call Meteor.logout() on it.

js
..
  const logout = () => Meteor.logout();

  return (
..
    <Fragment>
      <div className="user" onClick={logout}>
        {user.username} 🚪
      </div>
..

请记住也要为你的用户名设置样式。

¥Remember to style your username as well.

css
.user {
  display: flex;

  align-self: flex-end;

  margin: 8px 16px 0;
  font-weight: bold;
  cursor: pointer;
}

呼!你在此步骤中做了很多工作。对用户进行身份验证,在任务中设置用户,并为用户提供注销方式。

¥Phew! You have done quite a lot in this step. Authenticated the user, set the user in the tasks, and provided a way for the user to log out.

你的应用应如下所示:

¥Your app should look like this:

在下一步中,我们将学习如何部署你的应用!

¥In the next step, we are going to learn how to deploy your app!

8:部署

¥8: Deploying

部署 Meteor 应用类似于部署任何其他使用 websockets 的 Node.js 应用。你可以在 我们的指南 中找到部署选项,包括 Meteor Up、Docker 和我们推荐的方法 Galaxy。

¥Deploying a Meteor application is similar to deploying any other Node.js app that uses websockets. You can find deployment options in our guide, including Meteor Up, Docker, and our recommended method, Galaxy.

在本教程中,我们将在 Galaxy 上部署我们的应用,这是我们自己的云解决方案。Galaxy 提供免费计划,因此你可以部署和测试你的应用。很酷,对吧?

¥In this tutorial, we will deploy our app on Galaxy, which is our own cloud solution. Galaxy offers a free plan, so you can deploy and test your app. Pretty cool, right?

8.1:创建你的账户

¥8.1: Create your account

你需要一个 Meteor 账户来部署你的应用。如果你还没有,可以使用 在此处注册。使用此账户,你可以访问我们的包管理器、Atmosphere论坛 等。

¥You need a Meteor account to deploy your apps. If you don’t have one yet, you can sign up here. With this account, you can access our package manager, Atmosphere, Forums and more.

8.2:设置 MongoDB(可选)

¥8.2: Set up MongoDB (Optional)

由于你的应用使用 MongoDB,第一步是设置 MongoDB 数据库,Galaxy 提供免费计划的 MongoDB 托管以供测试,你还可以请求允许你扩展的生产就绪数据库。

¥As your app uses MongoDB the first step is to set up a MongoDB database, Galaxy offers MongoDB hosting on a free plan for testing purposes, and you can also request for a production ready database that allows you to scale.

在任何 MongoDB 提供程序中,你都会有一个必须使用的 MongoDB URL。如果你使用 Galaxy 提供的免费选项,则初始设置已为你完成。

¥In any MongoDB provider you will have a MongoDB URL which you must use it. If you use the free option provided by Galaxy, the initial setup is done for you.

Galaxy MongoDB URL 将如下所示:mongodb://username:<password>@org-dbname-01.mongodb.galaxy-cloud.io .

¥Galaxy MongoDB URL will be like this: mongodb://username:<password>@org-dbname-01.mongodb.galaxy-cloud.io .

你可以阅读有关 Galaxy MongoDB 此处 和常规 MongoDB 设置 此处 的更多信息。

¥You can read more about Galaxy MongoDB here and general MongoDB set up here.

8.3:设置设置

¥8.3: Set up settings

你需要创建一个设置文件,它是一个 JSON 文件,Meteor 应用可以从中读取配置。在项目根目录中名为 private 的新文件夹中创建此文件。需要注意的是,private 是一个特殊文件夹,不会发布到应用的客户端。

¥You need to create a setting file, it’s a JSON file that Meteor apps can read configurations from. Create this file in a new folder called private in the root of your project. It is important to notice that private is a special folder that is not going to be published to the client side of your app.

确保将 Your MongoDB URL 替换为你自己的 MongoDB URL 😃

¥Make sure you replace Your MongoDB URL by your own MongoDB URL 😃

json
{
  "galaxy.meteor.com": {
    "env": {
      "MONGO_URL": "Your MongoDB URL"
    }
  }
}

8.4:部署它

¥8.4: Deploy it

现在你已准备好部署,请在部署之前运行 meteor npm install 以确保安装了所有依赖。

¥Now you are ready to deploy, run meteor npm install before deploying to make sure all your dependencies are installed.

你还需要选择一个子域来发布你的应用。我们将使用免费的主域 meteorapp.com,它包含在任何 Galaxy 计划中。

¥You also need to choose a subdomain to publish your app. We are going to use the main domain meteorapp.com that is free and included on any Galaxy plan.

在此示例中,我们将使用 react-meteor-3.meteorapp.com,但请确保你选择其他 XX,否则你将收到错误。

¥In this example we are going to use react-meteor-3.meteorapp.com but make sure you select a different one, otherwise you are going to receive an error.

你可以了解如何在 Galaxy 此处 上使用自定义域。从 Essentials 计划开始,可以使用自定义域。

¥You can learn how to use custom domains on Galaxy here. Custom domains are available starting with the Essentials plan.

运行部署命令:

¥Run the deployment command:

shell
meteor deploy react-meteor-3.meteorapp.com --free --mongo

如果你没有在 Galaxy 上使用 MongoDB 的免费托管,请从部署脚本中删除 --mongo 标志,并使用适合你应用的设置添加 --settings private/settings.json

¥If you are not using the free hosting with MongoDB on Galaxy, then remove the --mongo flag from the deploy script and add --settings private/settings.json with the proper setting for your app.

确保将 react-meteor-3 替换为你想要用作子域的自定义名称。你将看到如下日志:

¥Make sure you replace react-meteor-3 by a custom name that you want as subdomain. You will see a log like this:

shell
meteor deploy react-meteor-3.meteorapp.com --settings private/settings.json
Talking to Galaxy servers at https://us-east-1.galaxy-deploy.meteor.com
Preparing to build your app...                
Preparing to upload your app... 
Uploaded app bundle for new app at vue-tutorial.meteorapp.com.
Galaxy is building the app into a native image.
Waiting for deployment updates from Galaxy... 
Building app image...                         
Deploying app...                              
You have successfully deployed the first version of your app.
For details, visit https://galaxy.meteor.com/app/react-meteor-3.meteorapp.com

此过程通常只需几分钟,但这取决于你的互联网速度,因为它会将你的应用包发送到 Galaxy 服务器。

¥This process usually takes just a few minutes, but it depends on your internet speed as it’s going to send your app bundle to Galaxy servers.

Galaxy 构建一个包含你的应用包的新 Docker 映像,然后使用它部署容器,阅读更多。你可以查看 Galaxy 上的日志,包括 Galaxy 构建 Docker 映像并部署它的部分。

¥Galaxy builds a new Docker image that contains your app bundle and then deploy containers using it, read more. You can check your logs on Galaxy, including the part that Galaxy is building your Docker image and deploying it.

8.5:访问应用并享受

¥8.5: Access the app and enjoy

现在,你应该能够在 https://galaxy.meteor.com/app/react-meteor-3.meteorapp.com 访问你的 Galaxy 仪表板。

¥Now you should be able to access your Galaxy dashboard at https://galaxy.meteor.com/app/react-meteor-3.meteorapp.com.

你还可以在 Galaxy 2.0 上访问你的应用,该应用目前处于 https://galaxy-beta.meteor.com/<your-username>/us-east-1/apps/<your-app-name>.meteorapp.com 测试阶段。请记住使用你自己的子域而不是 react-meteor-3

¥You can also access your app on Galaxy 2.0 which is currently in beta at https://galaxy-beta.meteor.com/<your-username>/us-east-1/apps/<your-app-name>.meteorapp.com. Remember to use your own subdomain instead of react-meteor-3.

你可以在 react-meteor-3.meteorapp.com 访问应用!只需使用你的子域即可访问你的子域!

¥You can access the app at react-meteor-3.meteorapp.com! Just use your subdomain to access yours!

我们部署到在美国(us-east-1)运行的 Galaxy,我们也在世界其他地区运行 Galaxy,请查看列表 此处。这很重要,你的应用在 Galaxy 上运行,可供世界上任何人使用!

¥We deployed to Galaxy running in the US (us-east-1), we also have Galaxy running in other regions in the world, check the list here. This is huge, you have your app running on Galaxy, ready to be used by anyone in the world!

9:后续步骤

¥9: Next Steps

你已完成本教程!

¥You have completed the tutorial!

现在,你应该对使用 Meteor 和 Vue 有了很好的理解。

¥By now, you should have a good understanding of working with Meteor and Vue.

信息

你可以在我们的 GitHub 存储库 中找到此应用的最终版本。

¥You can find the final version of this app in our GitHub repository.

以下是你接下来可以执行的一些选项:

¥Here are some options for what you can do next:

我们迫不及待地想看看你接下来会构建什么!

¥We can't wait to see what you build next!