跳至内容

Meteor.js 3 + React

在本教程中,我们将使用 React 和 Meteor 3.0 创建一个简单的待办事项应用程序。Meteor 与其他框架(如 BlazeVue 3SolidSvelte)配合使用效果很好。

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

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

让我们开始构建您的应用程序!

目录

1:创建应用程序

1.1:安装 Meteor

首先,我们需要安装 Meteor。

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

shell
npx meteor

1.2:创建 Meteor 项目

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

shell
meteor create simple-todos-react

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

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

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

您现在可以使用以下命令运行您的 Meteor 应用程序

shell
meteor run

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

您的 React 代码将位于 imports/ui 目录中,App.jsx 文件是您的 React 待办事项应用程序的根组件。

快速浏览一下 Meteor 创建的所有文件,您现在不必理解它们,但了解它们在哪里是有益的。

1.3:创建任务组件

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

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

js
import React from "react";

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

由于此组件将在列表中,因此您将返回一个 li 元素。

1.4:创建示例任务

由于您尚未连接到服务器和数据库,因此让我们定义一些示例数据,这些数据将很快用于渲染任务列表。它将是一个数组,您可以将其称为 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 属性。发挥创意!

1.5:渲染示例任务

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

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

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

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 将难以重新渲染其中一个。

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

从您的 App 组件中删除 HelloInfo,请记住还要删除文件顶部对它们的导入。也删除 Hello.jsxInfo.jsx 文件。

1.6:热模块替换

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

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

2:集合

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

您可以 此处阅读更多关于集合的信息。

在此步骤中,我们将实现所有必要的代码,以便使用 React hook 运行我们的任务的基本集合。

2.1:创建任务集合

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

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

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

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

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

您可以 此处阅读更多关于应用程序结构和导入/导出的信息。

2.2: 初始化任务集合

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

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

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

你不需要保留 `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` 文档中。

2.3: 渲染任务集合

现在进入有趣的部分,你将使用一个 React 函数组件和一个名为 `useTracker` 的 Hook(来自名为 react-meteor-data 的包)来渲染任务。

Meteor 同时支持 Meteor 包和 NPM 包,通常,Meteor 包使用 Meteor 内部功能或其他 Meteor 包。

此包已包含在 React 骨架中(`meteor create yourproject`),因此你无需添加它,但你始终可以通过运行 `meteor add package-name` 来添加 Meteor 包。

shell
meteor add react-meteor-data

现在,你已准备好导入此包中的代码,从 Meteor 包导入代码时,与 NPM 模块唯一的区别在于,你需要在导入语句的 from 部分前面加上 `meteor/`。

由 `react-meteor-data` 导出的 `useTracker` 函数是一个 React Hook,它允许你在 React 组件中实现响应式。每次数据通过响应式发生变化时,你的组件都会重新渲染。很酷,对吧?

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

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

但是等等!缺少一些东西。如果你现在运行你的应用,你会发现没有渲染任何任务。

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

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

imports/api/TasksPublications.js

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

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

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

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

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

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

剩下的唯一事情是订阅此发布。

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` 函数,你可以在数据准备好之前使用它来渲染一些加载组件。

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

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

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

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

查看如何连接。

查看你的数据库。

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

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

3: 表单和事件

所有应用都需要允许用户对存储的数据执行某种类型的交互。在我们的例子中,第一种交互类型是插入新的任务。没有它,我们的待办事项应用将毫无用处。

用户在网站上插入或编辑数据的主要方法之一是通过表单。在大多数情况下,使用 `

` 标签是一个好主意,因为它为其中的元素提供了语义含义。

3.1: 创建任务表单

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

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

在你的 `ui` 文件夹中创建一个名为 `TaskForm.jsx` 的新文件。

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: 更新 App 组件

然后,我们可以在任务列表上方的 `App` 组件中简单地添加它。

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: 更新样式表

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

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

3.4: 添加提交处理程序

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

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

要创建你的方法,你可以创建一个名为 `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` 客户端文件中导入你的方法。

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` 事件。

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

在更复杂的应用中,如果在诸如 `onChange` 之类的潜在频繁事件之间发生许多计算,你可能希望实现一些 `debounce` 或 `throttle` 逻辑。有一些库可以帮助你实现这一点,例如 Lodash

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` 集合中。第一个参数是我们想要调用的方法的名称,第二个参数是任务的文本。我们还修剪文本以删除任何额外的空格。

此外,在你的 `task` 文档中插入一个 `createdAt` 日期,以便你了解每个任务的创建时间。

3.5: 首先显示最新的任务

现在你只需要进行一项更改,这将使用户感到满意:我们需要首先显示最新的任务。我们可以通过对我们的 Mongo 查询进行排序来快速实现这一点。

js
..

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

你的应用应该如下所示。

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

4: 更新和删除

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

4.1: 添加复选框

首先,你需要将一个 `checkbox` 元素添加到你的 `Task` 组件中。

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

我们还必须将 `checked` 属性强制为布尔值,因为 React 将 `undefined` 值理解为不存在,因此导致组件从非受控组件切换到受控组件。

你还可以进行实验并查看应用的行为,以学习目的。

你还希望接收一个回调,即当复选框被点击时将被调用的函数。

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: 切换复选框

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

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

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

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

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

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>
  ..

你的应用应该如下所示。

4.3: 删除任务

你可以用几行代码删除任务。

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

js
import React from 'react';

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

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

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

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

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

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

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

你的应用应该如下所示。

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

5: 样式

5.1: CSS

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

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

  • 添加新任务的表单;
  • 任务列表。
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 的关于它的免费 视频教程

Flexbox 是一个在 UI 中分配和对齐元素的优秀工具。

5.2: 应用样式

现在你需要在组件周围添加一些元素。你将要向App中的主div添加一个className,还在你的h1周围添加一个带有几个divheader元素,以及一个围绕你的表单和列表的主div。查看下面它应该是什么样子,注意类的名称,它们需要与CSS文件中的名称相同。

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中的保留字。

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

你可以选择类似以下内容

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

你的应用应该如下所示。

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

6: 过滤任务

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

6.1: useState

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

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

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

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

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钩子的信息 here

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

6.2: 按钮样式

你应该为按钮添加一些样式,使其看起来不那么灰暗,并且对比度良好。你可以使用以下样式作为参考

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

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

6.3: 过滤任务

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

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

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

6.4: Meteor开发工具扩展

你可以安装一个扩展来可视化Mini Mongo中的数据。

Meteor DevTools Evolved 将帮助你调试你的应用,因为你可以看到Mini Mongo中的数据。

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

使用此链接在你的Google Chrome浏览器中安装它。

6.5: 待办任务

更新App组件以便在应用栏中显示待办任务的数量。

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

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

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

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

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

你的应用应该如下所示。

在下一步中,我们将向你的应用中添加用户访问权限。

7: 添加用户账户

7.1: 密码认证

Meteor自带了基本的认证和账户管理系统,因此你只需要添加accounts-password来启用用户名和密码认证

shell
meteor add accounts-password

还有许多其他受支持的认证方法。你可以在这里阅读更多关于账户系统的信息。

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

shell
meteor npm install --save bcrypt

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

7.2: 创建用户账户

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

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中看到任何不同。

7.3: 登录表单

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

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

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

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

7.4: 要求认证

我们的应用应该只允许已认证的用户访问其任务管理功能。

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

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

在这里阅读更多关于Fragment的信息 here

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

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: 登录表单样式

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

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

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

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

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

7.6: 服务器启动

从现在开始,每个任务都应该有一个所有者。因此,请转到你的数据库,就像你之前学到的那样,并从那里删除所有任务

db.tasks.remove({});

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

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

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字段。

7.7: 任务所有者

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

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

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

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字段

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

7.8: 注销

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

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

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

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

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

css
.user {
  display: flex;

  align-self: flex-end;

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

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

你的应用应该如下所示。

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

8: 部署

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

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

8.1: 创建你的账户

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

8.2: 设置MongoDB(可选)

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

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

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

你可以在这里阅读更多关于Galaxy MongoDB的信息,以及这里的常规MongoDB设置信息。

8.3: 设置设置

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

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

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

8.4: 部署它

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

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

在本例中,我们将使用react-meteor-3.meteorapp.com,但请确保选择一个不同的子域名,否则您将收到错误。

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

运行部署命令

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

如果您未使用 Galaxy 上的 MongoDB 免费托管,则从部署脚本中删除--mongo标志,并使用您的应用的正确设置添加--settings private/settings.json

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

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 服务器。

Galaxy 会构建一个包含您的应用包的新 Docker 镜像,然后使用它部署容器,了解更多。您可以在 Galaxy 上查看日志,包括 Galaxy 构建 Docker 镜像和部署它的部分。

8.5: 访问应用并享受

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

您也可以访问 Galaxy 2.0(目前处于测试阶段)上的应用,地址为https://galaxy-beta.meteor.com/<your-username>/us-east-1/apps/<your-app-name>.meteorapp.com。请记住使用您自己的子域名替换react-meteor-3

您可以通过react-meteor-3.meteorapp.com访问该应用!只需使用您的子域名即可访问您的应用!

我们部署到在美国(us-east-1)运行的 Galaxy,我们还在世界其他地区运行 Galaxy,请查看列表。这太棒了,您的应用已在 Galaxy 上运行,全球任何人都可以使用!

9: 下一步

您已完成本教程!

到目前为止,您应该已经对使用 Meteor 和 Vue 有了很好的了解。

信息

您可以在我们的GitHub 仓库中找到此应用的最终版本。

以下是您可以下一步执行的操作的一些选项

我们期待看到您接下来构建的内容!