<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Deno - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Deno - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 23 May 2026 19:22:22 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/deno/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Deno 初学者指南 ]]>
                </title>
                <description>
                    <![CDATA[ 什么是 Deno Deno 是一个新的 JavaScript 运行时。它是由 Node.js 的创建者 Ryan Dahl 创建的。 Dahl 对 Node 的一些做法感到遗憾，他想建立一个能够解决这些问题的运行时。Deno 和 Node 一样，建立在 V8 JavaScript 引擎上，但使用 Rust 而不是 C++ 构建。 Deno 的主要目标之一是使服务器端的 JavaScript 更接近浏览器的 JavaScript。 如果你在 Node 和浏览器的 JavaScript 中都写过，那么你肯定遇到过各自空间中使用的 API 的差异。Deno 旨在使服务器上的 API 与你在浏览器中使用的 API 相同。我们将在后面深入研究这方面的具体例子。 Deno 的主要功能 Deno 使用 TypeScript Deno 最吸引人的特点之一是，它把 TypeScript ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/intro-to-deno/</link>
                <guid isPermaLink="false">632ad5d85ef0a407fd630279</guid>
                
                    <category>
                        <![CDATA[ Deno ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Wed, 21 Sep 2022 07:19:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/09/Screen-Shot-2022-09-07-at-4.09.00-PM.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/intro-to-deno/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Intro to Deno – Guide for Beginners</a>
      </p><!--kg-card-begin: markdown--><h2 id="deno">什么是 Deno</h2>
<p>Deno 是一个新的 JavaScript 运行时。它是由 Node.js 的创建者 Ryan Dahl 创建的。</p>
<p>Dahl 对 Node 的一些做法感到遗憾，他想建立一个能够解决这些问题的运行时。Deno 和 Node 一样，建立在 V8 JavaScript 引擎上，但使用 Rust 而不是 C++ 构建。</p>
<p>Deno 的主要目标之一是使服务器端的 JavaScript 更接近浏览器的 JavaScript。</p>
<p>如果你在 Node 和浏览器的 JavaScript 中都写过，那么你肯定遇到过各自空间中使用的 API 的差异。Deno 旨在使服务器上的 API 与你在浏览器中使用的 API 相同。我们将在后面深入研究这方面的具体例子。</p>
<h2 id="deno">Deno 的主要功能</h2>
<h3 id="denotypescript">Deno 使用 TypeScript</h3>
<p>Deno 最吸引人的特点之一是，它把 TypeScript 当作开箱即用的第一类语言。这意味着你可以运行或使用 TypeScript 而无需安装任何其他外部或第三方软件包。</p>
<p>TypeScript 在 JavaScript 世界里越来越受欢迎，很多工具和公司都在推动使用它。看到像 TypeScript 这样的新的进步技术得到更多的关注是非常酷的，在 Deno 这样的大项目中获得一流的地位是一个巨大的进步。</p>
<h3 id="deno">Deno 默认是安全的</h3>
<p>Deno 默认是安全的。这意味着，除非脚本被特别允许，否则它不能访问系统文件、环境（如环境变量）或网络。</p>
<p>为了允许访问这些功能，你需要在 CLI 命令中传递相应的标志。以下是一些你最常使用的功能。:</p>
<ul>
<li><strong>网络访问</strong>：<code>--allow-net</code>, 你还可以指定代码允许访问哪些 URL。比如说：<code>--allow-net=https://api.deepgram.com</code></li>
<li><strong>文件访问</strong>：<code>--allow-read</code></li>
<li><strong>环境变量访问</strong>：<code>--allow-env</code></li>
</ul>
<h3 id="deno">Deno 的浏览器兼容性</h3>
<p>就像我上面提到的，Deno 努力使其拥有与浏览器相同的 API。在我看来，其中最大的是对 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">fetch API</a>的支持。</p>
<p>如今，在我写的大多数 JavaScript 中，我都使用了 <code>fetch</code> API。能够在我的服务器端代码中使用相同的语法，使我的工作效率提高了很多，而且使上下文切换的负荷大大降低。</p>
<h2 id="">包管理</h2>
<p>Deno 没有一个软件包管理工具。Node 使用 <code>npm</code> 来加载第三方软件包到你的项目中，但 Deno 通过 URL 来加载模块。</p>
<p>老实说，我一开始对此感到很困惑。一直以来使用 Node 和 NPM 的我对没有某种包管理器或 <code>package.json</code> 文件感到奇怪。</p>
<p>Deno 允许软件包开发者将他们的代码托管在他们想要的地方，而不是这种集中式的注册表。如果代码托管在 GitHub 上，你可以在他们的 <a href="https://deno.land/x">托管服务</a> 上注册你的模块，在那里它被缓存。这使得开发人员更容易找到和使用该模块。</p>
<h2 id="es">ES 模块</h2>
<p>Deno 也使用 ES 模块，不支持 <code>require()</code> 语法。同样，我现在写的大部分 JavaScript 都使用了这样的现代功能，所以不用担心我在某个编码环境中是否使用了正确的语法，这是很好的。</p>
<h2 id="">标准库</h2>
<p>Deno 带有一个 <a href="https://deno.land/std@0.138.0">标准库</a>，其中包含 Deno 团队审核过的功能。这使得开始使用 Deno 变得非常容易。</p>
<p>没有必要去寻找第三方软件包来做服务器端代码中需要的相当基本的事情。作为一个开发者，知道我使用的代码是由 Deno 团队正式支持和批准的，这让我很欣慰。</p>
<h3 id="">测试模块</h3>
<p>包含在标准库中的一个模块是 <a href="https://deno.land/std@0.109.0/testing">测试模块</a>。这个模块使得在 Deno 中编写测试更加容易，并且会使它们在不同的项目中更加一致。</p>
<p>这可能不是每个人都喜欢的，特别是如果有些人对测试库有强烈的意见。但我真的很喜欢它。随着 Deno 的不断发展，各项目间的一致性将使维护代码和切换项目更加容易。</p>
<h2 id="denonode">Deno 与 Node 的对比</h2>
<p>围绕 Deno 的最大问题是它与 Node 的比较。</p>
<p>与 Node 相比，Deno 显然具有一些优势。默认情况下是安全的，这无疑是一个有吸引力的功能，而开发者会把对 TypeScript 的开箱即用的支持视为一个巨大的胜利。</p>
<p>另一方面，Node 有一个非常丰富的社区，有一个成熟的生态系统和第三方软件包，使它更容易启动和运行。随着 <a href="https://deno.com/blog/v1.25#experimental-npm-support">Deno 宣布将支持大多数 npm 包</a>，我可以看到人们很快就会转向 Deno。</p>
<p>Deno 最近还发布了 <a href="https://deno.com/deploy">Deno Deploy</a> 公测版。 这将允许用户在边缘快速部署 JavaScript 代码。 随着时间的推移，这项服务可能会给 Deno 公司带来优势并扩大用户群。</p>
<h2 id="">总结</h2>
<p>在过去的几个月中，我使用 Deno 的经历非常有趣，我很喜欢使用它，也很高兴看到它的未来。</p>
<p>在接下来的几周里，我将写几篇文章，深入探讨 Deno 的世界。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 Deno 下使用 AlpehJS 库构建 React 应用 ]]>
                </title>
                <description>
                    <![CDATA[ 如果你是刚刚开始使用 Deno 的前端开发人员，你可能会想知道，你是否可以使用 Deno 构建像 NextJS 或 create-react-app（CRA）应用程序一样复杂的东西？ 我最近在想同样的事情。我想尝试使用能提供高共享性的 Deno，其能够直接从 URL 运行 JavaScript 和 TypeScript 应用程序。同时 Deno 编译器支持从 URL 中导入模块，从而实现了更大的可移植性。 我查阅了线上是否有任何相应的解决方案，但我只先找到了这篇文章 [https://dev.to/adriantwarog/react-deno-server-side-rendering-with-deno-ssr-4438] ，它使用了一些复杂的技术构建了一个 SSR 的 React 应用程序。复杂得并不简单，不像 NextJS 或 CRA 那样入门友好。 所以，通过更多的查阅，我最终找到了 AlephJS 框架，有着相比之前最酷的主页。 与 NextJS 一样，Aleph 是一个零配置，由 TypeScript 驱动的 React 框架。唯一的缺点是 Aleph 仍然处 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/build-react-app-using-deno-and-alephjs/</link>
                <guid isPermaLink="false">608b6d660998fd05ae8c8137</guid>
                
                    <category>
                        <![CDATA[ Deno ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ hylerrix han ]]>
                </dc:creator>
                <pubDate>Fri, 30 Apr 2021 02:40:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/Kapture-2021-03-11-at-11.33.15-4.gif" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>如果你是刚刚开始使用 Deno 的前端开发人员，你可能会想知道，你是否可以使用 Deno 构建像 NextJS 或 create-react-app（CRA）应用程序一样复杂的东西？</p>
<p>我最近在想同样的事情。我想尝试使用能提供高共享性的 Deno，其能够直接从 URL 运行 JavaScript 和 TypeScript 应用程序。同时 Deno 编译器支持从 URL 中导入模块，从而实现了更大的可移植性。</p>
<p>我查阅了线上是否有任何相应的解决方案，但我只先找到了<a href="https://dev.to/adriantwarog/react-deno-server-side-rendering-with-deno-ssr-4438">这篇文章</a>，它使用了一些复杂的技术构建了一个 SSR 的 React 应用程序。复杂得并不简单，不像 NextJS 或 CRA 那样入门友好。</p>
<p>所以，通过更多的查阅，我最终找到了 AlephJS 框架，有着相比之前最酷的主页。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Kapture-2021-03-11-at-11.33.15-3.gif" alt="Kapture-2021-03-11-at-11.33.15-3" width="600" height="400" loading="lazy"></p>
<p>与 NextJS 一样，Aleph 是一个零配置，由 TypeScript 驱动的 React 框架。唯一的缺点是 Aleph 仍然处于 Alpha 状态。</p>
<p>因此，让我们开始使用 AlephJS，来在 Deno 中获得真正的类似于 Next 的 React 体验。它具有 Next 相同的许多约定，例如：</p>
<ul>
<li>一个 <code>/pages</code> 目录用来创建 URL 路由</li>
<li>pages 中直接支持 <code>.js .jsx .ts .tsx</code> 格式。</li>
<li>一个 <code>/public</code> 目录用来存放静态资源比如视频、音频或图片文件。</li>
<li>一个 /pages/api 目录用来存放 JavaScript 或 TypeScript 的 serverless APIs。</li>
</ul>
<h2 id="alephjs">如何从 AlephJS 开始</h2>
<p>为了能够使用到 AlephJS，你需要确保你已安装了 Deno。你可以在我<a href="https://chinese.freecodecamp.org/news/build-a-url-shortener-in-deno/">之前的这篇文章中</a>查看如何安装 Deno。</p>
<p>准备好后，你需要先通过如下命令安装 Aleph CLI：</p>
<pre><code class="language-bash">deno install -A -f -n aleph https://deno.land/x/aleph@v0.3.0-alpha.1/cli.ts
</code></pre>
<p>安装完后，你可以运行 <code>aleph -h</code> 来检查是否安装成功。</p>
<p>由于 Deno 的 URL 特性，你可以使用 <code>deno run -A [https://deno.land/x/aleph@v0.3.0-alpha.1/cli.ts](https://deno.land/x/aleph@v0.3.0-alpha.1/cli.ts) start $app_URI</code> 替换 aleph 来执行任何命令，并且无需本地安装 CLI 即可运行 Aleph 程序。</p>
<p>运行如下命令来初始化一个应用：</p>
<pre><code class="language-bash">aleph init hello
cd hello
</code></pre>
<p>然后使用 <code>aleph dev</code> 在开发模式下启动该应用，以在端口 <code>8080</code> 上启动服务器。</p>
<p>要以生产模式启动该应用程序，你必须首先 <code>build</code> 构建该应用程序。你可以通过以下命令执行此操作：</p>
<pre><code class="language-bash">aleph build # build your app
aleph start # runs built app
</code></pre>
<p>在你初始化完毕 Aleph 之后，你会发现根组件已经在 <code>pages/index.tsx</code> 中定义了。这是正常的 React 组件。你可以对其进行修改，以了解 Aleph 的工作原理。</p>
<p>你也可以通过在 <code>pages</code> 文件夹中创建更多 <code>.jsx</code> 或 <code>.tsx</code> 文件来向应用添加更多的路由。你可以<a href="https://alephjs.org/docs/basic-features/routing">在此处</a>阅读有关 Aleph 中路由的更多知识。</p>
<h2 id="deno">如何在 Deno 中导入库</h2>
<p>我之前在 freeCodeCamp 中写过有关 Deno 的文章，其中演示了一些 Deno 的基础知识，比如 URL 的导入。由于 Aleph 是 Deno 框架，因此所有导入均以“Deno 的方式”进行。</p>
<p>你可以在 Deno 应用程序中导入这两种库：</p>
<ol>
<li>导入 Deno 原生库：这些库要么是为 Deno 构建的，要么是从 npm 移植过来的，均支持 Deno 的使用。</li>
<li>从 NPM 中导入：如果你最近使用过 JS，则很可能了解 npm。不了解的话：npm（node 包管理器背后的公司）是所有 JavaScript 库的一种标准托管库。幸运的是，Deno 对 npm 库有一定限度的支持。可以使用诸如 <a href="http://esm.sh/">esm.sh</a> 或 skypack.dev 这样的工具，用户可以将 npm 库导入到 Deno 中。</li>
</ol>
<h3 id="1deno">1. 如何导入 Deno 原生库</h3>
<p>你可以直接通过 URL 导入 Deno 原生库。你也可以在这里这倒 Deno 库的列表：<a href="http://deno.land/x">deno.land/x</a>。</p>
<p>想要测试这个功能，让我们导入一个标准 Deno 日期格式库，然后在 React 页面中调用日期格式函数。在应用程序的 <code>pages</code> 文件夹中创建一个文件 <code>date-import.tsx</code>。在文件内部编写如下代码：</p>
<pre><code class="language-jsx">// react 是 Aleph 中已经安装好的基础库
import React from "react";

// 从 URL 中导入其 format 函数
import { format } from "https://deno.land/std@0.88.0/datetime/mod.ts";

// 将函数名称大写，以便将其识别为一个 React 组件
export default function DateImport() {
  // 在这里，直接调用 format 函数可以正常使用
  return &lt;section&gt;Hello all! Today is: {format(new Date(), "dd-MM-yyyy")}&lt;/section&gt;;
}
</code></pre>
<p>要查看此文件的输出，请跳转到 <a href="http://localhost:8080/date-import">localhost:8080/date-import</a>，你应该可以看到显示今天日期的页面。</p>
<h3 id="2npm">2. 如何从 NPM 中导入库</h3>
<p>要导入 npm 库，你也可以直接从 URL 导入——但在这种情况下，需要进行一些更改。自从我们讨论了 <a href="http://esm.sh/">esm.sh</a> 和 skypack.dev 以来，让我们尝试在实际中使用它们。此时，我们尝试在项目中导入 <a href="https://www.npmjs.com/package/dayjs">dayjs</a> 库。</p>
<blockquote>
<p>注意：并非所有 npm 库都可以在 Deno 中正常工作，因为它们可能依赖于特定于 Node 的功能。</p>
</blockquote>
<p>要在 <a href="http://esm.sh/">esm.sh</a> 中导入库，请将库的软件包名追加到 URL 后面。比如导入 dayjs 的话，我们可以导入 <code>[https://esm.sh/dayjs](https://esm.sh/dayjs)</code> 地址。这同时适用于你可能从库中导入的任何 CSS 文件。</p>
<p>现在，让我们在名为 <code>pages</code> 中创建一个文件 <code>dayjs-import.tsx</code>。因此，我们页面中的代码将如下所示：</p>
<pre><code class="language-jsx">// react 是 Aleph 中已经安装好的基础库
import React from "react";

// 使用 esm.sh 导入 dayjs npm 库
import dayjs from "https://esm.sh/dayjs";

// 将函数名称大写，以便将其识别为一个 React 组件
export default function DateImport() {
  // 直接调用 dayjs 函数来显示今天的日期
  return &lt;section&gt;Hello all! Today is: {dayjs().format("DD-MM-YYYY")}&lt;/section&gt;;
}
</code></pre>
<p>要查看此文件的输出，打开 <a href="http://localhost:8080/dayjs-import">localhost:8080/dayjs-import</a>，你应该会看到显示当天日期的页面。</p>
<p>但在我们进行下一步之前，有一件重要的事——你如何导入 React 下的 <code>useState</code>、<code>useEffect</code> 等？幸运的是，Aleph 开发人员已经为我们编写了一个示例。</p>
<p>进入 <code>./lib/useCounter.ts</code>，你将找到在页面中用于计数器自定义钩子的代码。</p>
<p>由于我在本文中将重点介绍 Aleph 和 React 本身，因此，要查看在 Aleph 中导入 CSS文件的所有不同方法，请访问<a href="https://alephjs.org/docs/basic-features/built-in-css-support">官方文档中的相关页面</a>。</p>
<h2 id="denoalephjs">如何使用 Deno 和 AlephJS 构建示例应用</h2>
<p>现在，让我们进入实战环境，来尝试自己在 Aleph 中构建一个 React 应用。我们将构建“Is It Down”，这是我使用现有的网站检查 API 制作的一个示例应用。这个应用将使我们能够检查一个网站当前是打开还是关闭的。</p>
<p>这是 CodeSandbox 链接：<a href="https://codesandbox.io/s/awesome-firefly-5dofg">https://codesandbox.io/s/awesome-firefly-5dofg</a></p>
<p>构建此应用将向你展示如何使用 State hook、Effect Hook 以及如何在 Aleph 中进行 API 调用。</p>
<p>在 <code>pages</code> 文件夹中创建一个名为 <code>web-checker.tsx</code> 的新文件。让我们从仅添加 UI 元素开始。我们将显示一个带标题的 <code>h1</code> 元素，一个链接到 API 的 <code>h2</code> 元素以及一个接受用户输入的 form 元素。这是一个非交互式的页面，仅显示元素。</p>
<pre><code class="language-jsx">import React from "react";

export default function App() {
	return (
    &lt;div style={{ fontFamily: "sans-serif", textAlign: "center" }}&gt;
      &lt;h1&gt;Is it Down?&lt;/h1&gt;
      &lt;h2&gt;
        Go{" "}
        &lt;a
          href="https://rapidapi.com/jakash1997/api/website-data-gathering-and-update-tracking"
          target="_blank"
        &gt;
          here
        &lt;/a&gt;{" "}
        to get an API key
      &lt;/h2&gt;

      &lt;form
        onSubmit={(e) =&gt; {
          e.preventDefault();
        }}
      &gt;
        &lt;input
          type="text"
        /&gt;
        &lt;button type="submit"&gt;Submit&lt;/button&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>接下来，为了捕获输入字段的状态，以及捕获我们必须进行的 API 调用的响应，让我们引入一下 state。</p>
<pre><code class="language-jsx">// import useState from react
import React, { useState } from "react";

export default function App() {
  // define both state variables
  const [siteURL, setUrl] = useState("");
  const [response, setResponse] = useState(undefined);
...
</code></pre>
<p>现在，我们可以再输入元素中使用此状态，以便它可以对此作出响应：</p>
<pre><code class="language-jsx">...
&lt;input
  value={siteURL}
  onChange={(e) =&gt; setUrl(e.target.value)}
  type="text"
/&gt;
...
</code></pre>
<p>当 API 返回响应时，我们还需要添加一些代码来显示响应：</p>
<pre><code class="language-jsx">...
	&lt;/form&gt;
	
	

	
	&lt;code&gt;{JSON.stringify(response, null, 2)}&lt;/code&gt;
&lt;/div&gt;
...
</code></pre>
<p>现在，准备开始集成 API。让我们尝试正确的组合请求体。此时，API 是一个简单的 <code>GET</code> 调用，因此我们只需传递一个参数和一个 API 秘钥。</p>
<p>首先转到此处来生成 API 秘钥：<a href="https://rapidapi.com/jakash1997/api/website-data-gathering-and-update-tracking">https://rapidapi.com/jakash1997/api/website-data-gathering-and-update-tracking</a>。如下截图所示，找到密码并将其存储到安全的地方：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot_2021-03-08_at_3.47.01_PM.png" alt="Screenshot_2021-03-08_at_3.47.01_PM" width="600" height="400" loading="lazy"></p>
<p>接下来，让我们创建一个单独的函数 <code>sumitData</code>，用来生成所需的请求体。我们将使用 <code>axios</code> 库进行 GET 调用，同时用到 <code>options</code> 对象。</p>
<pre><code class="language-jsx">...
const [response, setResponse] = useState(undefined);

const submitData = (siteURL) =&gt; {
  setResponse("Loading...");
  const options = {
		// passing siteURL here through object shorthand
    params: { siteURL },

		// passing the required headers here
    headers: {
      "x-rapidapi-key": "YOUR_API_KEY",
      "x-rapidapi-host":
        "website-data-gathering-and-update-tracking.p.rapidapi.com",
    },
  };

	// print options here
	console.log("options", options);
};

return (
...
</code></pre>
<p>然后，将其添加到表单的 <code>onSubmit</code> 函数中。</p>
<pre><code class="language-jsx">onSubmit={(e) =&gt; {
  e.preventDefault();
  submitData(siteURL);
}}
</code></pre>
<p>现在，每当你点击 Submit 按钮时，你都能看到我们在控制台中生成的 <strong>options</strong>。如果你能看到，代表目前为止一切都正常！</p>
<p>接下来，我们只需执行简单的步骤，即使用 <a href="http://esm.sh/">http://esm.sh</a> 来导入 axios 库并使用它进行 API 调用。</p>
<p>在 <code>React</code> 导入之后，像这样导入 <code>axios</code>：</p>
<pre><code class="language-jsx">import React, { useState } from "react";
import axios from "https://esm.sh/axios";

...
</code></pre>
<p>并将其在 submitData 函数中使用：</p>
<pre><code class="language-jsx">...
	axios
    .get(
      "https://website-data-gathering-and-update-tracking.p.rapidapi.com/sitecheck",
      options
    )
    .then(function (response) {
      setResponse(response.data);
      console.log(response.data);
    })
    .catch(function (error) {
      console.error(error);
    });
};
...
</code></pre>
<p>就这么简单！尝试再次提交表单，这次你将在屏幕和控制台上都能看到正确的结果。</p>
<h2 id="">结论</h2>
<p>现在你了解了 Aleph 的基础知识。这是一个非常有趣的工具，它使你可以将现有的 React 知识与 <a href="http://deno.land/">deno.land</a> 的前沿性和安全性结合。</p>
<p>如果你喜欢本教程，可以在 Twitter 上关注我 <a href="http://twitter.com/thewritingdev">@thewritingdev</a>。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/build-react-app-using-deno-and-alephjs/">How to Build React Applications with Deno Using the AlephJS Library</a>，作者：<a href="https://www.freecodecamp.org/news/author/akash/">Akash Joshi</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何在 Deno 中构建一个 URL 短链生成器 ]]>
                </title>
                <description>
                    <![CDATA[ 在本文中，我们将要学习 Deno 的基础，比如如何运行一个程序并且拥抱 Deno 的安全特性。 Deno 是用 Rust 编写的一个全新 JavaScript 和 TypeScript 运行时。它提供了严格的安全性、开箱即用的 TypeScript、一个单个可运行的执行文件，以及一组经过代码审查的标准模块。 像 Node.js 下的 npm 一样，Deno 的生态库被管理在 X [https://deno.land/x/]  中心库下。我们将使用其中的一个库——Oak，在 Deno 中构建基于 REST API 的服务器。 通过使用类似 Express 的路由管理库 Oak [https://deno.land/x/oak@v6.3.0] 的基础知识后，我们将深入探讨 Deno 并构建一个完整的应用程序。 这是我们将构建此应用程序的步骤：  1. 使用基于 JSON 的配置文件来将 URL 短链映射到端口上  2. 在每个 URL 上附加有效期，以便这些重定向仅在有效的时间内生效。 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/build-a-url-shortener-in-deno/</link>
                <guid isPermaLink="false">608b6ad50998fd05ae8c810d</guid>
                
                    <category>
                        <![CDATA[ Deno ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ hylerrix han ]]>
                </dc:creator>
                <pubDate>Fri, 30 Apr 2021 02:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/5f9c9848740569d1a4ca192c.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在本文中，我们将要学习 Deno 的基础，比如如何运行一个程序并且拥抱 Deno 的安全特性。</p>
<p>Deno 是用 Rust 编写的一个全新 JavaScript 和 TypeScript 运行时。它提供了严格的安全性、开箱即用的 TypeScript、一个单个可运行的执行文件，以及一组经过代码审查的标准模块。</p>
<p>像 Node.js 下的 npm 一样，Deno 的生态库被管理在 <a href="https://deno.land/x/">X</a> 中心库下。我们将使用其中的一个库——Oak，在 Deno 中构建基于 REST API 的服务器。</p>
<p>通过使用类似 Express 的路由管理库 <a href="https://deno.land/x/oak@v6.3.0">Oak</a> 的基础知识后，我们将深入探讨 Deno 并构建一个完整的应用程序。</p>
<p>这是我们将构建此应用程序的步骤：</p>
<ol>
<li>使用基于 JSON 的配置文件来将 URL 短链映射到端口上</li>
<li>在每个 URL 上附加有效期，以便这些重定向仅在有效的时间内生效。</li>
</ol>
<h2 id="0">0. 前置准备</h2>
<ol>
<li>从<a href="https://deno.land/#installation">这个链接</a>中安装 Deno。</li>
<li>确保你知道一些 JavaScript 基础。</li>
</ol>
<p>尽管并不是本文所需，但你还是可以通过以下视频的形式看看 Deno 的基础介绍。</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.49999999999999%;" class="fluid-width-video-wrapper">
            <iframe width="200" height="113" src="https://www.youtube.com/embed/VQ8Jb7GLHgk?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" name="fitvid0"></iframe>
          </div>
        </div>
      </figure><!--kg-card-begin: markdown--><p>那么，让我们来正式开始？</p>
<h2 id="1">1. 如何构建路由</h2>
<p>要为我们的应用编写服务端代码，我们将使用 Oak 模块。它具有类似于 Express 定义 API 路由的语法。</p>
<p>如果我们在<a href="https://deno.land/x/oak">这个文档</a>中，“<a href="https://deno.land/x/oak">基础用法</a>”部分几乎涵盖了我们会在本文中用到的一切路由。因此，我们直接拓展这段代码来构建我们的应用。</p>
<p>要直接用到这段代码，可以在文件夹中创建一个名为 index.ts 的文件，然后将“基本用法”里的代码复制到其中。</p>
<p>要了解如何在 Deno 中运行 TypeScript 或 JavaScript 文件，你首先需要理解 Deno 是如何运行文件的。</p>
<p>你可以通过运行 <code>deno run file_name.ts</code> 或 <code>file_name.js</code> 命令来运行文件，后面可以跟一组参数标志，这些标志将为你的应用程序提供某些系统权限。</p>
<p>为了测试刚刚粘贴的“基础用法”代码能否跑通，使用如下命令：<code>deno run index.ts</code>。</p>
<p>你会看到 Deno 警示你没有授予该代码访问网络的权限。所以你需要添加 <code>-allow-net</code> 到刚才的 run 命令中。该命令最终会像这样：<code>deno run index.ts -allow-net</code>。</p>
<p>“基础用法”中的路由代码会如下所示：</p>
<pre><code class="language-jsx">router
  .get("/", (context) =&gt; {
    context.response.body = "Hello world!";
  })
  .get("/book", (context) =&gt; {
    context.response.body = Array.from(books.values());
  })
  .get("/book/:id", (context) =&gt; {
    if (context.params &amp;&amp; context.params.id &amp;&amp; books.has(context.params.id)) {
      context.response.body = books.get(context.params.id);
    }
  });
</code></pre>
<p>拆解上面的代码：首先定义了一个 <code>router</code> 对象，然后在路由器上调用 get 函数，以定义应用程序的各种端口。端口相应的逻辑在回调函数中定义。</p>
<p>例如，对于 "/" 端口，已定义了在响应体重返回 “Hello World” 的回调函数。我们可以先保持此端口不变，以通过接收响应来测试我们的应用程序服务器是否正在运行。</p>
<p>我们不需要已定义的 “/book” URL，因此可以安全地删除其定义。此时，你的路由应具有如下结构：</p>
<pre><code>router
  .get("/", (context) =&gt; {
    context.response.body = "Hello world!";
  })
  .get("/book/:id", (context) =&gt; {
    if (context.params &amp;&amp; context.params.id &amp;&amp; books.has(context.params.id)) {
      context.response.body = books.get(context.params.id);
    }
  });
</code></pre>
<p>在下一节中，我们将着手于开始实战构建应用程序。</p>
<h2 id="2howtobuildtheurlshortener">2. How to Build the URL Shortener</h2>
<p>现在让我们开始实战构建 URL 短链生成器。</p>
<p>它应该根据 <code>shortcode</code> 来重定向到目的地（<code>dest</code>）。重定向还应仅在有效期到期之前有效，可以以年-月-日格式提供。</p>
<p>基于这些假设，让我们创建一个名为 <code>urls.json</code> 的配置文件。该文件的格式为：</p>
<pre><code class="language-jsx">{
  "shortcode": {
    "dest": "destination_url_string",
    "expiryDate": "YYYY-MM-DD"
  }
}
</code></pre>
<p>你可以<a href="https://github.com/akash-joshi/deno-url-shortener/blob/master/urls.json">参考这个 JSON 文件</a>。</p>
<p>要在你的代码中读取这个 JSON 文件，请在 <code>index.ts</code> 顶部添加以下内容：</p>
<pre><code class="language-jsx">import { Application, Router } from "&lt;https://deno.land/x/oak/mod.ts&gt;";

const urls = JSON.parse(Deno.readTextFileSync("./urls.json"));

console.log(urls);
</code></pre>
<p>现在，要运行 <code>index.ts</code>，你需要另一个标志 <code>-allow-read</code>，否则 Deno 将抛出“未提供读取权限”错误。你的最终命令应该是 <code>deno run —allow-net —allow-read index.ts</code>。</p>
<p>运行此命令后，你将在终端窗口中看到打印的 JSON 文件。这意味着你的程序能够正确读取 JSON 文件。</p>
<p>如果我们回到上面看到的“基本用法”示例，则路由 “/book/:id” 风格正是我们接下来所需要的。</p>
<p>将 "/book/:id" 替换为 "/shrt/:urlid"，此时我们将基于 URL ID（<code>:urlid</code>）获得各个 URL。</p>
<p>用以下代码替换 "/book/:id" 路由中存在的现有代码：</p>
<pre><code class="language-jsx">.get("/shrt/:urlid", (context) =&gt; {
    if (context.params &amp;&amp; context.params.urlid &amp;&amp; urls[context.params.urlid]) {
      context.response.redirect(urls[context.params.urlid].dest);
    } else {
      context.response.body = "404";
    }
  });
</code></pre>
<p>路由中的 <code>if</code> 条件执行以下操作：</p>
<ol>
<li>检查参数是否存在于路由中</li>
<li>检查参数 <code>urlid</code> 是否在参数列表中</li>
<li>检查 <code>urlid</code> 是否与我们 JSON 中的任何 URL 匹配。</li>
</ol>
<p>如果有所匹配，用户将重定向到正确的 URL。如果无所匹配，则返回 404 响应。</p>
<p>想要测试这段代码，请将如下代码复制到 <code>index.ts</code> 中。路由现在长这个样子：</p>
<pre><code class="language-jsx">router
  .get("/", (context) =&gt; {
    context.response.body = "Hello world!";
  })
	.get("/shrt/:urlid", (context) =&gt; {
	    if (context.params &amp;&amp; context.params.urlid &amp;&amp; urls[context.params.urlid]) {
	      context.response.redirect(urls[context.params.urlid].dest);
	    } else {
	      context.response.body = "404";
	    }
	  });
</code></pre>
<p>接下来使用 <code>deno run —allow-net —allow-read index.ts</code> 运行文件。</p>
<p>如果你从示例中复制了 JSON 文件，此时打开 <code>http://localhost:8000/shrt/g</code>，你会正常重定向到 Google 主页上。</p>
<p>另一方面，如果你使用的随机 shortcode 在我们网址配置中不起作用，则会进入到 404 页面上。</p>
<p>但是，你会看到我们的短链器不会实时响应 JSON 文件中的变更。想要增加更多的配置，请以如下相同格式向 <code>urls.json</code> 中添加新的重定向。</p>
<pre><code>"shortcode": {
    "dest": "destination_url_string",
    "expiryDate": "YYYY-MM-DD"
  }
</code></pre>
<p>这是因为 <code>urls.json</code> 仅在刚开始时被读取一次。现在，我们需要将实时更新功能添加到服务端上。</p>
<h2 id="3howtoaddlivereloading">3. How to Add Live-Reloading</h2>
<p>为了使 <strong>urls</strong> 对象能够实时响应 JSON 文件中的更改，我们只需将 read 语句移动到路由中即可。会长这样：</p>
<pre><code class="language-jsx">.get("/shrt/:urlid", (context) =&gt; {
  const urls = JSON.parse(Deno.readTextFileSync("./urls.json"));

  if (context.params &amp;&amp; context.params.urlid &amp;&amp; urls[context.params.urlid]) {
    context.response.redirect(urls[context.params.urlid].dest);
  } else {
    context.response.body = "404";
  }
});
</code></pre>
<p>请注意我们如何是路由内部移动 URL 对象的。此时，每次调用该路由时都会读取配置文件，因此它可以实时响应 <code>urls.json</code> 文件中所做的任何更改。即使我们现在添加或删除其他重定向，我们的代码也会做出新的响应。</p>
<h2 id="4howtoaddanexpirationtotheurls">4. How to Add an Expiration to the URLs</h2>
<p>为了使我们的 URL 在某个时间点上过期，我们将使用流行的 Moment.js 库，该库使处理日期变得更容器。</p>
<p>幸运的是，它已经被<a href="https://deno.land/x/moment">良好移植到了 Deno 上</a>。要了解其工作原理，请在上一句的链接中查看其文档。</p>
<p>要在代码中使用到，请直接通过 URL 导入：</p>
<pre><code class="language-jsx">import { Application, Router } from "&lt;https://deno.land/x/oak/mod.ts&gt;";
import { moment } from "&lt;https://deno.land/x/moment/moment.ts&gt;";

const router = new Router();
</code></pre>
<p>为了检查 URL 什么时候过期，我们检查 urls 对象上的 expiryDate 键值。如下所示：</p>
<pre><code class="language-jsx">if (context.params &amp;&amp; context.params.urlid &amp;&amp; urls[context.params.urlid]) {
  if (
    urls[context.params.urlid].expiryDate &gt; moment().format("YYYY-MM-DD")
  ) {
    context.response.redirect(urls[context.params.urlid].dest);
  } else {
    context.response.body = "Link Expired";
  }
} else {
  context.response.body = "404";
}
</code></pre>
<p>在 <code>moment().format("YYYY-MM-DD")</code> 中，我们使用 moment() 来获取当前的时间。然后使用 <code>.format("YYYY-MM-DD")</code> 将其格式化为 "YYYY-MM-DD"（年-月-日）格式。</p>
<p>通过将其与我们的 <strong>expiryDate</strong> 键进行比较，我们可以检查当前的 URL 是否已过期。</p>
<p>就是这样！你已经在 Deno 中构建了功能齐全的 URL 短链器。你可以<a href="https://github.com/akash-joshi/deno-url-shortener">在这个 Github 库</a>中找打最终的代码。</p>
<p>通过将 <code>expiryDate</code> 设置为当前日期并对 <code>urls.json</code> 和我们的代码进行其它更改可以测试更多功能。</p>
<h3 id="deno">我对 Deno 的看法</h3>
<p>为了总结这篇文章，我将谈谈我对 deno.land 的思考。</p>
<p>当看到一种考虑安全性并支持 TypeScript 的服务端运行时令人耳目一新，但 Deno 在应用到生产环境之前还有很长的路要走。</p>
<p>例如，即使对于像我们刚开发的那样简单的程序，使用 TypeScript 编译也得需要约为 20 秒的时间，这非常的慢。</p>
<p>在错误报告方面，描述错误的地方还很糟糕。比如，当在函数本身中嵌入代码以读取 <code>urls.json</code> 时，Deno 无法报告未设置 <code>-allow-read</code> 标志。相反，它只会引发内部错误，而不会在终端上打印正确的错误。</p>
<blockquote>
<p>译者注：现 Deno 1.9 版本已经很好地支持权限提示了，其它的也在逐步支持中。</p>
</blockquote>
<h3 id="">接下来是什么？</h3>
<p>你可以通过构建更复杂的应用程序（比如<a href="https://css-tricks.com/build-a-chat-app-using-react-hooks-in-100-lines-of-code/">聊天应用程序</a>或 <a href="https://auth0.com/blog/building-a-wikipedia-app-using-react-hooks-and-auth0/">Wikipedia 克隆版</a>）来提高你的 Deno 或 TypeScript 的技能。</p>
<p>你也可以浏览 deno.land 上的 Deno 文档，来更熟悉基础知识。</p>
<p>感谢你阅读本文，祝你编程愉快！</p>
<h3 id="importantlinks">Important Links</h3>
<p>Deno - <a href="https://deno.land">https://deno.land</a><br>
Deno X (package repository) - <a href="https://deno.land/x/">https://deno.land/x/</a><br>
Oak (REST framework) - <a href="https://deno.land/x/oak">https://deno.land/x/oak</a><br>
Oak Basic Usage - <a href="https://deno.land/x/oak@v6.3.1#basic-usage">https://deno.land/x/oak@v6.3.1#basic-usage</a><br>
Final GitHub Repo - <a href="https://github.com/akash-joshi/deno-url-shortener">https://github.com/akash-joshi/deno-url-shortener</a></p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/build-a-url-shortener-in-deno/">How to Build a URL Shortener in Deno</a>，作者：<a href="https://www.freecodecamp.org/news/author/akash/">Akash Joshi</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 为什么我认为 Deno 是一个迈向错误方向的 JavaScript 运行时？ ]]>
                </title>
                <description>
                    <![CDATA[ 译者序 在为《Deno 钻研之术 [https://github.com/hylerrix/deno-tutorial] 》引入第一篇翻译文章的时候，就看到了这篇文章，那时还觉得驾驭不了，就重点先写了若干篇入门级别的 Deno 文章。 转眼到 2021 年，从《Deno 双周刊 [https://github.com/hylerrix/deno-feedly]》第一期继续开启新的一年的 Deno 之旅，于是就回想起了本文，觉得可以通过好好阅读本文及相关后，从另一个角度了解 Deno，翻译就开始了。 相比前面对文章的直接翻译外，本文有很多想要记笔记的地方（甚至“大开眼界”的地方），因此独特地开辟本文的“译者序”篇章，来梳理一下原文及其相关视频到底针对 Deno 的另一面，都讲了什么。 也正如原文所说，这注定是一篇有争议的文章，从文章中引入的两个视频中的点踩数都微微大于点赞数、其评论区都充满着“火热”的讨论就能看出多个观点的冲撞。原文也存在些过度批评的地方，但是不妨碍下方译文开始后一比一地还原原作者想要表达的观点！ 所以，原作者 Mehul 在原文以及两个视频中到底想要说 Deno ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/why-deno-is-a-wrong-step-in-the-future/</link>
                <guid isPermaLink="false">6001045a5f61e30501b5bd70</guid>
                
                    <category>
                        <![CDATA[ Deno ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ hylerrix han ]]>
                </dc:creator>
                <pubDate>Fri, 15 Jan 2021 03:27:07 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/01/image-13.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="">译者序</h2>
<p>在为《<a href="https://github.com/hylerrix/deno-tutorial">Deno 钻研之术</a>》引入第一篇翻译文章的时候，就看到了这篇文章，那时还觉得驾驭不了，就重点先写了若干篇入门级别的 Deno 文章。</p>
<p>转眼到 2021 年，从《<a href="https://github.com/hylerrix/deno-feedly">Deno 双周刊</a>》第一期继续开启新的一年的 Deno 之旅，于是就回想起了本文，觉得可以通过好好阅读本文及相关后，从另一个角度了解 Deno，翻译就开始了。</p>
<p>相比前面对文章的直接翻译外，本文有很多想要记笔记的地方（甚至“大开眼界”的地方），因此独特地开辟本文的“译者序”篇章，来梳理一下原文及其相关视频到底针对 Deno 的另一面，都讲了什么。</p>
<p>也正如原文所说，这注定是一篇有争议的文章，从文章中引入的两个视频中的点踩数都微微大于点赞数、其评论区都充满着“火热”的讨论就能看出多个观点的冲撞。原文也存在些过度批评的地方，但是不妨碍下方译文开始后一比一地还原原作者想要表达的观点！</p>
<p>所以，原作者 Mehul 在原文以及两个视频中到底想要说 Deno 为什么没那么好？其观点大致梳理如下：</p>
<blockquote>
<p>注意：如果想先看原文的话可以跳过阅读并在之后来看这份简单的总结。</p>
</blockquote>
<ul>
<li>Deno 和 Node 确实有<strong>竞争关系</strong>，因为你必须在你的下个项目中作出选择。</li>
<li>Deno 现在所做的<strong>成果并不是很多</strong>，大多特性都可以在 Node 生态中较好地解决掉。</li>
<li><strong>URL import</strong> 还是一场灾难。NPM 中已经有很多明星项目“竟然只有一行代码”、“暗中偷窃用户数据”、“注入挖矿代码”、“兼容性出现问题导致很多上游库受影响”等问题，URL import 本身并不能解决这些问题，更没有一个像 Node 一样强壮的社区来保证受人信任的依赖库，也就不会有更多的开发者愿意加入到 Deno 生态中。</li>
<li>由于 <strong>TypeScript</strong> 是 JavaScript 的超集，完全可以选择跳过类型验证，此时推荐新手在 Deno 上直接使用 TypeScript 编程坑会很大，很可能会出现一堆 any 类型。在经常性的调试报错下，TypeScript 的学习成本也较高，很容易写出低质量代码。</li>
<li><strong>TypeScript</strong> 并不是直接在 Deno 上跑的，其实还是变成了 JavaScript 来跑，何必一定要集成到 Deno 中呢？</li>
<li><strong>安全</strong>是一个很难的事情，Deno 宣传自己的“安全沙箱”注定要承担很大的责任。Deno 安全沙箱也没有必要，完全可以用 Docker 等容器或虚拟化技术来支持。同时，真正想搞破坏的脚本也会找到自己的方式来规避安全问题。</li>
<li>以当时版本下的 <code>deno --allow-run</code> 运行主进程从而开启的子进程能轻松突破安全沙箱的验证来获得更多权限为例，发现 Deno 的“<strong>安全沙箱</strong>”并没有所说的那么安全。</li>
<li>Deno 没有必要<strong>集成太多工具链</strong>（代码格式化、测试工具、TypeScript 等等）于一体，让各种第三方工具链来一起共建生态的同时，保证 Deno 本身的专注性并提供更友好的插件支持会很好。</li>
<li>Node 的异步模型<strong>并没有被淘汰</strong>，promise 和事件侦听器模型也没有套题，因此不确定 Deno 将如何解决这些没有被淘汰的问题。</li>
<li>未来并不确定会有多少开发者愿意将 npm 中的成熟库逐渐<strong>迁移到 Deno 中</strong>。</li>
</ul>
<p>可以看出，无论你是 Node 开发者还是 Deno 爱好者，这些观点都有很多值得思考的地方。但也有有失偏颇的地方，比如文中将 Deno 说明为编程语言，也将 Deno 只发展了两年多的生态直接和建设了十年的 Node 生态作横向对比——Deno 注定会有自己独特的发展轨迹。</p>
<p>最后，原文作者 Mehul 总结了他眼中的 Deno v1.0 的真实面貌：</p>
<blockquote>
<p>Deno = (大多数情况下)[TypeScript + Node + 正确配置的 Docker 容器 + 单一可执行程序 + &lt;各种各样的小工具&gt; - NPM]</p>
</blockquote>
<h2 id="">译文开始</h2>
<p>目前，我还没有在 Youtube 上找到任何一个像 <a href="https://www.youtube.com/codedamn">codedamn</a> 一样的频道（超过 10 万名开发者订阅），其对 Deno v1 版本的发布并不感到兴奋。</p>
<p>上周，我在我的频道上发布了一个视频，介绍了一些“为什么我认为我们不需要 Deno——另一个基于 V8 和 Node 的 JavaScript 运行时”的原因，这些原因对我来说都很简明扼要。</p>
<p>同时通过本文，我可以在视频外拓展阐述更多的原因。但如果你想先看视频，可以看看这个：</p>
<figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.25%;" class="fluid-width-video-wrapper">
            <iframe width="560" height="315" src="https://www.youtube.com/embed/Uf1md0k6ATs" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" name="fitvid0"></iframe>
          </div>
        </div>
      </figure>
<p>为了证明我总体上并不是反对 Deno 甚至 JavaScript 本身，我想声明一下：我喜欢 JavaScript 胜过于很多其它的编程技术。我的主要技术栈也只围绕着 JavaScript 展开——Node/React/MongoDB/React Native/NativeScript/Ionic/甚至你能想到的更多相关库。</p>
<p>我也是主要用 JavaScript 这一门语言，就让我的 <a href="https://www.youtube.com/codedamn">Youtube 频道</a>及一个<a href="https://codedamn.com/">开发者平台</a>的订阅人数达到了 10 万多人。</p>
<p>但重要的事情在于，保持公平客观的角度来看到一个事务的两面性很重要。Deno 当然有好的一面，但也有大多数人尚未看到/写文探讨的另一面。一起来看看吧！<br>
_<br>
<strong><em>注意：</em></strong><em>本文注定会是一个有争议性的文章。请先让我们保持礼貌的态度并控制好自己的情绪。如果你能仔细阅读全文直到结尾，然后再说说你的更多想法，那我会备受感激。</em><br>
_<br>
<em>本文底部我会列出我的社交账号，也希望我们能在哪里针对此主题进行更多的良好探讨。</em></p>
<h2 id="denovsnode">Deno vs Node：名副其实的竞争关系</h2>
<p>有很多业内人士都在说：“Deno 和 Node 之间没有任何竞争关系，彼此之间可以学到很多东西”。</p>
<p>在某种程度上，我同意这个看法，Node 和 Deno 确实可以互相学习。但是两者间真的没有竞争关系了吗？我完全不同意这关观点。</p>
<p>让我们重新看一下 Deno 和 Node 的共同特征：</p>
<ol>
<li>它们都是 JavaScript 的运行时环境；</li>
<li>它们都可以在可以运行 V8 的任何计算机上运行；</li>
<li>它们都有 ECMAScript 标准支持；</li>
<li>它们都在被积极的维护中。</li>
</ol>
<p>如果你这两年都是 Deno 的粉丝，这两年里不选择 Node 而直接用 Deno 作为新项目的技术选型是不可能的。</p>
<p>同理，如果你以前从未使用过 TypeScript，并且认为自己想要尝试 Deno，此时你会很难同时用上 NPM 生态中的各种模块。</p>
<p>所以：Deno 和 Node 的开发人员目前确实存在分歧——你必须做选择，我想说这是两者为竞争关系的一个很重要的例子。</p>
<h2 id="deno">Deno 到底好在哪里？</h2>
<p>首先，我需要列举一下 Deno 对自己的宣传中的那些优势，Deno 为什么说自己是更好的运行时：</p>
<ol>
<li>它克服了 Node 的一些缺点；</li>
<li>它在默认情况下是一个安全的运行时环境；</li>
<li>它内置 TypeScript 支持；</li>
<li>它将 Promise 的支持下放到底层；</li>
<li>它基于 Rust 语言构建（对比与 C++ 之于 Node）。</li>
</ol>
<p>在接下来的章节中，我将一个一个针对上面的每一个属于 Deno 的优点，来看看 Node 可以从中学到什么。我也将会在必要之时探讨，为什么 Deno 还没有那么有意义。</p>
<h2 id="deno">Deno 画蛇添足在了哪些地方？</h2>
<p>让我们开始拿起 Deno 的独特宣传点（USP，Unique Selling Proposition） 并将它们一一解析：</p>
<h3 id="">默认安全的运行时环境</h3>
<p>这是 Deno 里很受欢迎的特性，我很惊喜。Deno 直接默认支持一个安全的沙箱环境，除非你明确的选择开启访问文件系统或访问网络等功能权限。</p>
<p>Deno 这样做是因为它想更加地贴近浏览器。Deno 遵守 ECMAScript 标准这点很不错，但为什么如此热衷于率先贴近浏览器？</p>
<p>或许答案是，Deno 想要保持在客户端和服务端上编写的代码之间有良好的兼容性。但 Deno 如此强烈的想要支持浏览器以至于我觉得方向有些偏失、甚至有些过头了。</p>
<p>Node 虽不支持“安全的运行时”——但经过深思熟虑后，我觉得也有理由支持 Node：</p>
<ol>
<li>众所周知，你不应该在系统上运行不受信任的代码和可执行文件。这就是我们总是选择像 Express 库之于 Node 的原因（而不是随便找个声称自己速度比 Express 快 100 倍的库）。信任来自于社区的大量使用。</li>
<li>我不知道有任何编程语言像 Deno 这样提供如此的沙箱环境。尽管这个功能可能不错，但似乎应该交由诸如 Docker 这类的容器环境来完成。我相信一个被良好配置的 Docker 环境，相比在沙箱化的 Deno 环境中运行不受信任的 Node 代码来说，能更好的处理不受信任文件的安全性问题。</li>
<li>沙箱化并没那么容易——我虽然不是网络安全专家，但我觉得某些功能越多，攻击面就可能越大。Deno 承诺提供安全的运行时环境，但我想说安全很难实现。Deno 的承诺带来了巨大的安全责任。世界上最大的企业们为支持它们的安全白帽计划，需要每年为独立开发者和安全公司投入将近数亿美金。因此，Deno 到底会将它们的“安全环境”带向何方？时间会证明一切。</li>
</ol>
<p>所以，Node 可以从 Deno 中学到什么？我想说不会学到太多。Node 或许可以从竞争对手可以引入一些安全环境的标识，但是没有太多意义。如果你想在你的系统上随意运行一些未知代码，则最好克隆一个 C/C++ 仓库并运行 make 命令损害系统。</p>
<blockquote>
<p>译者注：上段最后一句话是“If you randomly run arbitrary code on your systems, you might as well clone a C/C++ repo and run a make command over it and get your whole system compromised.”，有些难以翻译，也不容易看出为什么从 Node/Deno 突然跑到了 C/C++，欢迎交流。</p>
</blockquote>
<p>据我所知，你不能在像 C/C++ 这样的底层语言上来“沙盒化”文件系统或网络访问——这样效率并不高。</p>
<p>注意：最近我发现启用 <code>--allow-run</code> 标志的 Deno 几乎可以完成任何操作。该视频详细介绍了相关内容：</p>
<figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.25%;" class="fluid-width-video-wrapper">
            <iframe width="560" height="315" src="https://www.youtube.com/embed/_lvas914dXI" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" name="fitvid1"></iframe>
          </div>
        </div>
      </figure>
<h3 id="typescript">内置支持 TypeScript</h3>
<p>为 TypeScript 现阶段的进展欢呼，我很高兴 Deno 开箱即用地支持 TypeScript。</p>
<p><em><strong>注:</strong> &nbsp;感谢 <a href="https://chinese.freecodecamp.org/news/lilasquared">@lilasquared </a>指出 Deno 也能开箱即用地运行&nbsp;<code>.js</code> 文件。本文重点强调使用&nbsp;<code>.ts</code> 文件编写代码。Deno 当然可以直接运行 .js 文件。</em></p>
<p>但是，让我们退一步来说：你知道为什么 JavaScript 和 Node 在全球拥有数以万计的开发人员吗？因为进入这个领域的壁垒几乎为零。JavaScript 是灵活的，可以容许你的诸多错误。而 TypeScript 总会给你一些奇怪的错误。</p>
<p>对于生产级的应用程序来说就糟糕了：生产环境上可不需要这些时髦的东西。同时对于学习者来说，JavaScript 是宽容的，纵使你可能会遇到一些 Bug，但也可以很轻松的改正，引用一句话，JavaScript 可以被快速编码并将事情搞定。</p>
<p>对于初学者来说，我担心他们如果选择使用 Deno（并被要求使用 TypeScript），因为他们还不了解 TypeScript，想着快速在服务端上跑通代码，我们可能会看到很多这种的代码：</p>
<pre><code class="language-typescript">const httpResponse: any = await getAPIResponse&lt;any&gt;({ myParams })
// ...
const someOtherVariable = something() as any
// ...
any, any, any
</code></pre>
<p>TypeScript 是 JavaScript 的超集。你完全可以无意识间写一段质量很差的 TypeScript 代码，仅仅使用 TypeScript 并不会让你的项目无懈可击。</p>
<p>直到你想起来本来就能在 Node 中写 TypeScript 之前，这种体验确实很有趣。我相信现在每个在生产环境中使用 Node 的大型公司都引入了 TypeScript 来编写项目——没有例外。当你处理诸多文件及其依赖关系、处理大量代码时，JavaScript 真的很难拓展。</p>
<p>TypeScript 是 JavaScript 生态系统中革命性的工具包，更好地同时支持了静态和动态语言。</p>
<p>因此 Deno 声明自己会内置 TypeScript 支持。但我想问为什么一定要这样？确实，如果不内置的话，可能需要额外引入 Babel 和 Webpack 来完成这项工作，但这不是一堆第三方工具链围绕身边来共建生态的重要意义吗？我们难道不想增强 DX 吗？</p>
<blockquote>
<p>译者注：DX，开发人员体验，是 Developer Experience 的简称。当软件或系统的用户是开发人员时，开发人员体验（DX）就相当于用户体验（UX, User experience design）。</p>
</blockquote>
<p>JavaScript 依然还会是 JavaScript，保持自身的风格。况且，如果 Deno 真的能直接运行 TypeScript（或类似于 TypeScript 的语言），我觉得没什么问题。但事实上，Deno 其实也只是将 TypeScript 代码转换为 JavaScript 代码，并运行它罢了。</p>
<p>从这些角度能看出，Deno 似乎是一个 Node 下各种工具的的集成版本，Deno 将一个测试工具、一个代码格式化程序和 TypeScript 等一次性的包括进来。我更喜欢一个精简的编程语言并在合适的时候由我自己添加各种插件——当然，我不能代表所有开发人员，这也只是我的观点。</p>
<p>为什么我会在已经有专注于代码格式化的 prettier 库的情况下，依然需要领一个内置的代码格式化工具？为什么要解决这种本身就做的不错的东西？</p>
<p>单体架构确实集中起来提供了很多工具，但它也真的很庞大，一个更精简和专注的核心库才能更好的维护和拓展。</p>
<h3 id="promise">Promise 的支持下放到底层</h3>
<p>和 Node 作为对比，Deno v1 的发布对我来说看不出太多的意义。我非常尊重 Node 和 Deno 的创建者，但是 Node 拥有一些 Deno 所没有的东西——世界各地众多经验丰富的开发人员的支持。</p>
<p>Node 由近 3000 位开发者贡献力量，并且是异步 I/O 事件处理的引领者。Deno 确实建立在 Rust 之上，并公开了类似 Promise 的的抽象。但是 Node 有 C++，有 3000 名开发人员以及 10 年以上的开发和维护。</p>
<p>Node 的异步模型并没有被淘汰，promise 和事件侦听器模型也没有被淘汰，因此我不确定 Deno 将如何解决这些并没被淘汰的问题。</p>
<h3 id="npm">再见了，npm</h3>
<p>很重要的事情是：Deno 并不支持 NPM。Ryan（Node 和 Deno 的创建者）在为此推广 Go 语言的相关特性。让我想到一些包管理器：</p>
<ol>
<li>npm for JS (obviously)</li>
<li>npm 之于 JS（真很明细）</li>
<li>apt-get</li>
<li>composer 之于 PHP</li>
<li>brew 之于 macOS</li>
<li>cargo 之于 Rust（Deno 正是基于 Rust 构建)</li>
</ol>
<p>我认为不使用包管理器来管理是很不好的一步。包管理器能做的太多了：标明版本、编写脚本、管理依赖关系等等。为什么 Deno 不使用 npm 呢？我并不清楚，但这些是我想到的：</p>
<ol>
<li>首先，因为 Deno 需要 TypeScript 生态，但是后者生态更多的是 JavaScript 的。更正：Deno 也能良好的运行 <code>.js</code> 文件。</li>
<li>其次，大量 npm 模块需要要求使用到文件/网络甚至更多的条件，而这些 Deno 都很严格的默认不提供这些权限了。所以你需要在 package.json 里注入大量颇许冗余的“permissions”字段来提供更多权限。然而...Deno 无法和 npm 互相配合，因此也没有 package.json。接下来我们会来看看 Deno 到底如何处理模块系统的。</li>
<li>NPM 模块数量及其庞大甚至臃肿，但这也是 Node 生态的强大生命力所在。想要找一个库来将 tar 文件内容提取到 stream 流中？你可以选择 tar-steram。想要一个数据格式验证库？你可以选择 joi。想要配合 JWT 协同使用？你可以选择 jsonwebtoken。我怀疑得有多久才能让开发者们将他们的各种库变得兼容 Deno 系统？</li>
</ol>
<p>Deno 对模块系统采用了一种完全不同的方法。但无论如何，Deno 在尝试以某种方式“修补”现有的 NPM 模块。那么除了尝试在 Docker 容器中“入侵”（hacking around）一个 TS + Node 项目外，我看不到太多使用 Deno 的意义。</p>
<p>根据我目前所了解的有关 Deno 的一切，这是 Deno 现在的真实面貌：</p>
<blockquote>
<p><strong>Deno</strong> = (大多数情况下)[TypeScript + Node + 正确配置的 Docker 容器 + 单一可执行程序 + &lt;各种各样的小工具&gt; - NPM]</p>
</blockquote>
<p>搞定！让我们冷静一下，然后听我一下的总结。</p>
<h2 id="">总结</h2>
<p>我和其它人对 Deno 的出现一样感到兴奋。但当 Deno 准备完全重写 JavaScript 运行时时，我的期望便有所变动。</p>
<p>Deno 的自动化 TypeScript 文档等诸多不错的特性我没有提到，是因为我希望这篇文章旨在展示 Deno 的另一面。因为 Deno 的优点几乎可以再任何其他 Deno 的文章中找到，所以我需要再次强调硬币的两面性。</p>
<p>坦白来说，Deno 看起来为了一些很小的益处承担了巨大的责任和代价，包括转移现有的 NPM 模块和代码库的诸多债务。你同意还是不同意我的这些观点呢？我很期待你的想法。推特联系我 <a href="https://twitter.com/mehulmpt">@mehulmpt</a> 或 <a href="https://instagram.com/mehulmpt">Instagram</a> 也可以！</p>
<p>祝好！</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/why-deno-is-a-wrong-step-in-the-future/">Why I Believe Deno is a Step in the Wrong Direction for JavaScript Runtime Environments</a>，作者：<a href="https://www.freecodecamp.org/news/author/mehulmpt/">Mehul Mohan</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Deno + Oak 连接 MySQL 实战教程 ]]>
                </title>
                <description>
                    <![CDATA[ 我最近写了一篇关于 Deno + Oak 构建酷炫 Todo API [https://www.freecodecamp.org/news/create-a-todo-api-in-deno-written-by-a-guy-coming-from-node/]   的文章 ，其中并没有使用数据库相关的知识点。您可以在我的 Github 仓库adeelibr/deno-playground [https://github.com/adeelibr/deno-playground/tree/master/chapter_1:oak] 的 chapter_1:oak 中查看当时的整套代码。 > 译者注：翻译版的《Deno + Oak 构建酷炫的 Todo API》 [https://deno-tutorial.js.org/translations/004-deno-oak-todo-api.html]在这里，相关 Demo 也可以在电子书仓库中找到。 本文将进一步讲起，一起来学习如何将 MySQL 集成到我们的 Deno + Oak 项目中。 如果你想随时了解本文的完整代码，可以 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-use-mysql-in-deno-oak/</link>
                <guid isPermaLink="false">5f042d11db4be8080eb711af</guid>
                
                    <category>
                        <![CDATA[ Deno ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ hylerrix han ]]>
                </dc:creator>
                <pubDate>Tue, 07 Jul 2020 08:26:10 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/07/photo-1591509867461-d9f58becc082.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>我最近写了一篇关于 <a href="https://www.freecodecamp.org/news/create-a-todo-api-in-deno-written-by-a-guy-coming-from-node/">Deno + Oak 构建酷炫 Todo API</a> 的文章 ，其中并没有使用数据库相关的知识点。您可以在我的 Github 仓库&nbsp;<a href="https://github.com/adeelibr/deno-playground/tree/master/chapter_1:oak">adeelibr/deno-playground</a>&nbsp;的 chapter_1:oak 中查看当时的整套代码。</p>
<blockquote>
<p>译者注：翻译版的<a href="https://deno-tutorial.js.org/translations/004-deno-oak-todo-api.html">《Deno + Oak 构建酷炫的 Todo API》</a>在这里，相关 Demo 也可以在电子书仓库中找到。</p>
</blockquote>
<p>本文将进一步讲起，一起来学习如何将 MySQL 集成到我们的 Deno + Oak 项目中。</p>
<p>如果你想随时了解本文的完整代码，可以在&nbsp;<a href="https://github.com/adeelibr/deno-playground/tree/master/chapter_2:mysql">chapter_2:mysql</a> 中找到，欢迎给仓库点个 Star。</p>
<p>我将假设你已经阅读了上一篇文章，如果没有，可以先在<a href="https://www.freecodecamp.org/news/create-a-todo-api-in-deno-written-by-a-guy-coming-from-node/">此处</a>阅读完后再回到本文来。</p>
<p>在我们开始前，请确保你已经安装了一个 MySQL 客户端并你能成功运行：</p>
<ul>
<li>MySQL community server [<a href="https://dev.mysql.com/downloads/mysql/">在这里下载</a>]</li>
<li>MySQL Workbench [<a href="https://dev.mysql.com/downloads/workbench/">在这里下载</a>]</li>
</ul>
<p>同时我也为 MacOS 用户写了一个简短的，关于<a href="https://github.com/adeelibr/deno-playground/blob/master/guidelines/setting-up-mysql-mac-os-catalina.md">如何安装 MySQL 的教程</a>。</p>
<p>如果你是在 Windows 环境下，你可以使用和上面相同的工具，或者直接使用 <a href="https://www.apachefriends.org/index.html">XAMPP</a>&nbsp;来快速运行 MySQL 实例到你的机器上。</p>
<p>当你将 MySQL 成功跑起来后，我们就可以开始本文的探索了。</p>
<h2 id="">让我们开始吧</h2>
<p>假设你已经阅读了<a href="https://www.freecodecamp.org/news/create-a-todo-api-in-deno-written-by-a-guy-coming-from-node/">上一篇文章</a>，我们将编写如下功能：</p>
<ul>
<li>创建一个 MySQL 数据库的连接；</li>
<li>编写一个小脚本，每当我们重启 Deno 服务器时数据库会自动重置；</li>
<li>在一个数据表上执行 CRUD 操作；</li>
<li>将 CURD 操作连接到我们的 API 控制器中。</li>
</ul>
<p>开始前的最后一件事：我将上一篇的代码添加 MySQL 版本后的具体 Git 变动可以<a href="https://github.com/adeelibr/deno-playground/pull/1/commits/5b63b51ebcadededcfec452fe6877a0bd0f1f83f">在这里</a>查阅。</p>
<p>在你的项目根目录中（我的叫做 <em><code>chapter_2:mysql</code></em>，你的可以随便起），创建一个 <strong>db</strong> 文件夹，并在其中创建一个 <strong>config.ts</strong> 并添加如下内容：</p>
<pre><code class="language-typescript">export const DATABASE: string = "deno";
export const TABLE = {
  TODO: "todo",
};
</code></pre>
<p>这里没什么新知识点，仅仅导出了我们定义的数据库的名称以及一个 TABLE 对象。通过这个导出，我们的项目中将会有一个名为 “deno” 的数据库，其中又会有一个名为 “todo” 的数据表。</p>
<p>接下来，在 <strong>db</strong> 文件夹中再创建一个名为 <strong>client.ts</strong> 的文件并填充如下内容：</p>
<pre><code class="language-typescript">import { Client } from "https://deno.land/x/mysql/mod.ts";
// config
import { DATABASE, TABLE } from "./config.ts";

const client = await new Client();
client.connect({
  hostname: "127.0.0.1",
  username: "root",
  password: "",
  db: "",
});
</code></pre>
<p>这段代码包含了若干条功能。</p>
<p>我们从 Deno 的一个第三方&nbsp;<code>mysql</code>&nbsp;模块中解构出了 <code>Client</code>&nbsp;变量，这个变量可以用来连接数据库并执行指定的增删改查工作。</p>
<pre><code class="language-typescript">client.connect({
  hostname: "127.0.0.1",
  username: "root",
  password: "",
  db: "",
});
</code></pre>
<p><code>Client</code>&nbsp;内置一个 <code>connect</code> 方法，用来供我们设置&nbsp;<code>hostname</code>、<code>username</code>、<code>password</code>&nbsp;和&nbsp;<code>db</code>&nbsp;等字段的值，以设置与 MySQL 的连接配置。</p>
<p>请确保你的 <code>username</code>&nbsp;用户没有设置&nbsp;<code>password</code>，因为目前的 Deno MySQL 模块无法连接有密码的用户。如果你不知道如何清空用户密码，可以阅读<a href="https://github.com/adeelibr/deno-playground/blob/master/guidelines/setting-up-mysql-mac-os-catalina.md#set-your-mysql-password-to-empty">这里</a>。</p>
<p>我在此处将  <code>database</code>&nbsp;字段留空，因为我想稍后在脚本中手动选择它。</p>
<p>让我们添加一个用来初始化名为“deno”的数据库并为其创建一个名为“todo”表的脚本。</p>
<p>在&nbsp;<code>db/client.ts</code>&nbsp;文件中我们添加以下内容：</p>
<pre><code class="language-typescript">import { Client } from "https://deno.land/x/mysql/mod.ts";
// 加载配置文件里的配置
import { DATABASE, TABLE } from "./config.ts";
const client = await new Client();
client.connect({
  hostname: "127.0.0.1",
  username: "root",
  password: "",
  db: "",
});
const run = async () =&gt; {
  // 创建一个数据库 (前提是之前没有创建过)
  await client.execute(CREATE DATABASE IF NOT EXISTS ${DATABASE});
  // 选择我们的数据库
  await client.execute(USE ${DATABASE});
  // 如果已经创建过名为 Todo 的数据表，将其删除
  await client.execute(DROP TABLE IF EXISTS ${TABLE.TODO});
  // 创建 Todo 数据表
  await client.execute(CREATE TABLE ${TABLE.TODO} (
        id int(11) NOT NULL AUTO_INCREMENT,
        todo varchar(100) NOT NULL,
        isCompleted boolean NOT NULL default false,
        PRIMARY KEY (id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;);
};

run();

export default client;
</code></pre>
<p>这里我们从我们的最早的配置文件中导入了 <code>DATABASE</code>&nbsp;和&nbsp;<code>TABLE</code>，并通过  <code>run()</code> 方法创建相关的数据库和表。</p>
<p>让我们截取&nbsp;<code>run()</code> 方法相关的代码片段。我在代码中已经编写了帮助你理解的注释。</p>
<pre><code class="language-typescript">const run = async () =&gt; {
  // 创建一个数据库 (前提是之前没有创建过)
  await client.execute(CREATE DATABASE IF NOT EXISTS ${DATABASE});
  // 选择我们的数据库
  await client.execute(USE ${DATABASE});
  // 如果已经创建过名为 Todo 的数据表，将其删除
  await client.execute(DROP TABLE IF EXISTS ${TABLE.TODO});
  // 创建 Todo 数据表
  await client.execute(CREATE TABLE ${TABLE.TODO} (
        id int(11) NOT NULL AUTO_INCREMENT,
        todo varchar(100) NOT NULL,
        isCompleted boolean NOT NULL default false,
        PRIMARY KEY (id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;);
};

run();
</code></pre>
<ul>
<li>创建一个名为 <code>deno</code>&nbsp;的数据库，如果之前已经有这个数据库则跳过此步骤。</li>
<li>选择我们当前名为 <code>deno</code>&nbsp;的这个数据库。</li>
<li>在 <code>deno</code>&nbsp;数据库中，如果名为&nbsp;<code>todo</code>&nbsp;的表存在，则将其删除。</li>
<li>接下来，在 <code>deno</code> 数据库中创建一个新的&nbsp;<code>todo</code>&nbsp;表，并且定义其表结构：表结构将包含一个唯一的、自增长的、数值型的 <code>id</code> 字段；也将包含一个名为&nbsp;<code>todo</code>&nbsp;的字符串型字段；同时还包含一个名为  <code>isCompleted</code>&nbsp;的布尔型字段；最后将 <code>id</code> 字段定义为主键。</li>
</ul>
<p>我写这段代码的原因是因为我不想在 MySQL 实例中有代码上无法直观看出来的信息。有了这段代码后，每次重启服务器时，它都会重新初始化所有内容。</p>
<p>你可以不编写这段代码。但如果不编写的话，则必须手动创建数据库和表。</p>
<p>同时，你也可以查阅 Deno MySQL 模块的 <a href="https://deno.land/x/mysql/#create-database">db creation</a>&nbsp;和&nbsp;<a href="https://deno.land/x/mysql/#create-table">table creation</a>&nbsp;文档。</p>
<p>回到文章的主旨，我们已经完成了上面提到的四个目标的两个目标：</p>
<ul>
<li>创建一个 MySQL 数据库的连接；</li>
<li>编写一个小脚本，每当我们重启 Deno 服务器时数据库会自动重置。</li>
</ul>
<p>这意味着本文 50% 的知识点已经介绍完毕。但不幸运的是，现在还测试不了任何数据操作功能。一起来快速添加几个 CRUD 功能来看看具体是怎样执行的。</p>
<h2 id="crudapi">在数据表上执行 CRUD 操作并将功能添加到 API 控制器中</h2>
<p>我们需要先编写 Todo 接口。创建&nbsp;<code>interfaces/Todo.ts</code> 文件并添加如下内容：</p>
<pre><code class="language-typescript">export default interface Todo {
  id?: number,
  todo?: string,
  isCompleted?: boolean,
}
</code></pre>
<p>代码中的&nbsp;<code>?</code>&nbsp;符号代表这个键是可选的。之所以这样做是因为接下来我们有的地方仅需要其中的若干个键。</p>
<p>如果你想了解更多 TypeScript 中可选的属性，可以查阅<a href="https://www.typescriptlang.org/docs/handbook/interfaces.html#optional-properties">这里</a>。</p>
<p>接下来，在根目录创建一个名为 <strong>models</strong> 的文件夹并在其中创建一个名为 <strong>todo.ts</strong> 的文件，添加如下内容：</p>
<pre><code class="language-typescript">import client from "../db/client.ts";
// 加载配置文件
import { TABLE } from "../db/config.ts";
// 加载接口文件
import Todo from "../interfaces/Todo.ts";

export default {
  /**
   * 通过解构的 id 参数值，来检查相应的 todo 元素是否存在于数据表中
   * @param id
   * @returns 返回布尔值来代表是否存在
   */
  doesExistById: async ({ id }: Todo) =&gt; {},
  /**
   * 将会返回 todo 表中的所有内容
   * @returns 返回全部都是 todo 元素的数组
   */
  getAll: async () =&gt; {},
  /**
   * 过解构的 id 参数值，来返回相应的 todo 元素
   * @param id
   * @returns 返回一个 todo 元素
   */
  getById: async ({ id }: Todo) =&gt; {},
  /**
   * 在 todo 表中增加一个新的 todo 元素
   * @param todo
   * @param isCompleted
   */
  add: async (
    { todo, isCompleted }: Todo,
  ) =&gt; {},
  /**
   * 修改某个 todo 元素的内容
   * @param id
   * @param todo
   * @param isCompleted
   * @returns 返回一个数字 (代表影响的行数)
   */
  updateById: async ({ id, todo, isCompleted }: Todo) =&gt; {},
  /**
   * 通过 ID 来删除指定的元素
   * @param id
   * @returns integer (count of effect rows)
   */
  deleteById: async ({ id }: Todo) =&gt; {},
};
</code></pre>
<p>此时每个函数都是空的，不用担心，我们接下来会一一填充。</p>
<p>接下来创建 <code>controllers/todo.ts</code> 文件并保证填充如下内容：</p>
<pre><code class="language-typescript">// 加载接口文件
import Todo from "../interfaces/Todo.ts";
// 加载模型操作文件
import TodoModel from "../models/todo.ts";

export default {
  /**
   * @description 获取所有 todo 元素
   * @route GET /todos
   */
  getAllTodos: async ({ response }: { response: any }) =&gt; {},
  /**
   * @description 新增一个 todo 元素
   * @route POST /todos
   */
  createTodo: async (
    { request, response }: { request: any; response: any },
  ) =&gt; {},
  /**
   * @description 通过 id 获取指定的 todo 元素
   * @route GET todos/:id
   */
  getTodoById: async (
    { params, response }: { params: { id: string }; response: any },
  ) =&gt; {},
  /**
   * @description 通过 id 更新指定的 todo 元素
   * @route PUT todos/:id
   */
  updateTodoById: async (
    { params, request, response }: {
      params: { id: string };
      request: any;
      response: any;
    },
  ) =&gt; {},
  /**
   * @description 通过 id 删除指定的 todo 元素
   * @route DELETE todos/:id
   */
  deleteTodoById: async (
    { params, response }: { params: { id: string }; response: any },
  ) =&gt; {},
};
</code></pre>
<p>这个文件目前同样是空的内容，现在开始一一将其填充吧。</p>
<h3 id="gettodosapi">[Get] 获取全部 Todos 的 API</h3>
<p>在&nbsp;<code>models/todo.ts</code>&nbsp;文件中为&nbsp;<code>getAll</code>&nbsp;方法添加具体逻辑：</p>
<pre><code class="language-typescript">import client from "../db/client.ts";
// config
import { TABLE } from "../db/config.ts";
// Interface
import Todo from "../interfaces/Todo.ts";

export default {
   /**
   * 将会返回所有 todo 表中的数据
   * @returns array of todos
   */
  getAll: async () =&gt; {
    return await client.query(`SELECT * FROM ${TABLE.TODO}`);
  },
}
</code></pre>
<p>我们这里直接用 SQL 原生语法来获取表中的所有内容。</p>
<p>除了 <code>connect</code>（使用于 db/client.ts 文件中）方法外， <code>Client</code> 还公开了另一种方法，即 <code>query</code>。通过 <code>client.query</code> 方法，我们可以直接从 Deno 代码上运行 MySQL 查询。</p>
<p>接下来打开&nbsp;<code>controllers/todo.ts</code> 文件并为&nbsp;<code>getAllTodos</code> 填充内容：</p>
<pre><code class="language-typescript">// interfaces
import Todo from "../interfaces/Todo.ts";
// models
import TodoModel from "../models/todo.ts";

export default {
  /**
   * @description 获取所有 todo
   * @route GET /todos
   */
  getAllTodos: async ({ response }: { response: any }) =&gt; {
    try {
      const data = await TodoModel.getAll();
      response.status = 200;
      response.body = {
        success: true,
        data,
      };
    } catch (error) {
      response.status = 400;
      response.body = {
        success: false,
        message: `Error: ${error}`,
      };
    }
  },
}
</code></pre>
<p>我们这里所做的就是导入 <code>TodoModel</code> 对象并使用其中我们刚定义不久的 <code>getAll</code> 方法。因为需要该函数需要处理 Promise 类型的异步过程，所以我们将整个函数定义为 async/await 类型。</p>
<p><code>TodoModel.getAll()</code> 方法返回一个数组后，我们将这个数组包装起来并将 <code>response.body</code> 的响应状态 <code>status</code> 设置为 <code>200</code>。</p>
<p>如果执行过程中有任何异常比如 Promise 报错，程序将通过进入 catch 块，向用户返回状态码为 400 的响应体（此时 <code>success</code> 为 false，<code>message</code> 为错误原因。</p>
<p>就这么简单地搞定了，现在来在终端上运行。</p>
<p>请保证你的 MySQL 实例运行中，然后在终端输入：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts 
</code></pre>
<p>不出意外的话，此时你的终端会有这样类似的结果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-04-at-23.29.19.png" alt="Screenshot-2020-06-04-at-23.29.19" width="600" height="400" loading="lazy"></p>
<p>这也是当我通过命令行运行服务器时终端的样子。</p>
<p>终端告诉了我们两件事：</p>
<ol>
<li>Deno API 服务器成功运行在了 8080 端口上；</li>
<li>Deno API 服务器程序成功连接到了 MySQL 客户端 <code>127.0.0.1:3306</code> （<code>http://localhost:3306</code>）上。</li>
</ol>
<p>让我们测试下我们的 API，我使用的是 <a href="https://www.postman.com/">Postman</a>，但是你可以用任何你喜欢的 API 测试工具。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-04-at-23.31.07.png" alt="Screenshot-2020-06-04-at-23.31.07" width="600" height="400" loading="lazy"></p>
<p>执行 [GET] localhost:8080/todos 后，我们得到了所有 todos 列表。</p>
<p>虽然现在的 todos 列表返回的是空数组，但当我们能成功给 <code>todo</code> 数据表增加数据后就会获得更多数据。</p>
<p>棒极了，一个 API 搞定只剩下四个要搞。</p>
<h3 id="posttodoapi">[Post] 新增一个 Todo 的 API</h3>
<p>在 <code>models/todo.ts</code> 文件中，为 <code>add()</code> 函数添加如下内容：</p>
<pre><code class="language-typescript">export default {
   /**
   * 为 todo 数据表新增一行数据
   * @param todo
   * @param isCompleted
   */
  add: async (
    { todo, isCompleted }: Todo,
  ) =&gt; {
    return await client.query(
      `INSERT INTO ${TABLE.TODO}(todo, isCompleted) values(?, ?)`,
      [
        todo,
        isCompleted,
      ],
    );
  },
}
</code></pre>
<p>add 函数在参数列表中将解构 <code>todo</code> 和&nbsp;<code>isCompleted</code> 两个变量。</p>
<p>同时，上面代码的 <code>add: async ({ todo, isCompleted }: Todo) =&gt; {}</code> 片段和 <code>({todo, isCompleted}: {todo:string, isCompleted:boolean})</code> 语句是等价的。但我们已经在  <code>interfaces/Todo.ts</code> 中定义过 Todo 接口：</p>
<pre><code class="language-typescript">export default interface Todo {
  id?: number,
  todo?: string,
  isCompleted?: boolean,
}
</code></pre>
<p>此时我们将可以简单地写成  <code>add: async ({ todo, isCompleted }: Todo) =&gt; {}</code>。这条语句告诉 TypeScript 当前函数有两个参数：字符串类型的 <code>todo</code>，以及布尔类型的 <code>isCompleted</code>。</p>
<p>如果你想了解更多关于接口的知识，TypeScript 官方文档上有一个绝佳的介绍，可以查看<a href="https://www.typescriptlang.org/docs/handbook/interfaces.html">这里</a>。</p>
<p>在 add 函数中还有如下代码：</p>
<pre><code class="language-typescript">return await client.query(
  `INSERT INTO ${TABLE.TODO}(todo, isCompleted) values(?, ?)`,
  [
    todo,
    isCompleted,
  ],
);
</code></pre>
<p>这是一条 MySQL 查询语句，可以被拆分为如下两个部分：</p>
<ul>
<li><code>INSERT INTO ${TABLE.TODO}(todo, isCompleted) values(?, ?)</code>。其中的两个问号意味着这里需要使用到变量的值。</li>
<li>另一部分 <code>[todo, isCompleted]</code> 是上一部分需要使用的变量，其值将会替代 <code>(?, ?)</code>。</li>
<li><code>Table.Todo</code> 是一个从  <code>db/config.ts</code> 读取来的字符串，其值为"<code>todo</code>"。</li>
</ul>
<p>接下来在我们的控制器 <code>controllers/todo.ts</code> 文件中，编写 <code>createTodo()</code> 函数：</p>
<pre><code class="language-typescript">export default {
   /**
   * @description 新增一个 todo
   * @route POST /todos
   */
  createTodo: async (
    { request, response }: { request: any; response: any },
  ) =&gt; {
    const body = await request.body();
    if (!request.hasBody) {
      response.status = 400;
      response.body = {
        success: false,
        message: "No data provided",
      };
      return;
    }

    try {
      await TodoModel.add(
        { todo: body.value.todo, isCompleted: false },
      );
      response.body = {
        success: true,
        message: "The record was added successfully",
      };
    } catch (error) {
      response.status = 400;
      response.body = {
        success: false,
        message: `Error: ${error}`,
      };
    }
  },
}
</code></pre>
<p>我们继续将其拆分为两个部分来介绍：</p>
<p><strong>第一部分</strong></p>
<pre><code class="language-typescript">const body = await request.body();
if (!request.hasBody) {
  response.status = 400;
  response.body = {
    success: false,
    message: "No data provided",
  };
  return;
}
</code></pre>
<p>我们在这里所做的是检查用户请求当前接口时是否在 body 中传递了请求数据。如果没有的话将返回一个有 <code>400</code> 状态码且包括  <code>success: false</code> 和&nbsp;<code>message: &lt;erromessage-string&gt;</code> 的响应体。</p>
<p><strong>第二部分</strong></p>
<pre><code class="language-typescript">try {
  await TodoModel.add(
    { todo: body.value.todo, isCompleted: false },
  );
  response.body = {
    success: true,
    message: "The record was added successfully",
  };
} catch (error) {
  response.status = 400;
  response.body = {
    success: false,
    message: `Error: ${error}`,
  };
}
</code></pre>
<p>接下来如果没有任何意外错误，则调用 <code>TodoModel.add()</code> 函数并返回一个状态为 <code>200</code> 且给用户提示执行函数成功的响应体。</p>
<p>否则将进入 catch 代码段，来返回函数执行错误及其原因，正如前文介绍过的 API 一样。</p>
<p>现在我们搞定了。打开你的终端并且确保你的 MySQL 正在运行。在终端输入：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>打开 <a href="https://www.postman.com/">Postman</a> 并且测试当前 API 能否正常运行：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-04-at-23.55.02.png" alt="Screenshot-2020-06-04-at-23.55.02" width="600" height="400" loading="lazy"></p>
<p>执行 [POST] localhost:8080/todos =&gt; 将会为 todo 列表新增一个新的数据。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-04-at-23.57.06.png" alt="Screenshot-2020-06-04-at-23.57.06" width="600" height="400" loading="lazy"></p>
<p>再次执行 [GET] localhost:8080/todos =&gt; 将会返回所有 todos，可以看到刚刚新增 todo 已经被加入到数据库中。</p>
<p>很棒，已经搞定了两个 API，只剩三个要做。</p>
<h3 id="getidtodoapi">[GET] 通过 ID 查询某 Todo 的 API</h3>
<p>在你的 <code>models/todo.ts</code> 文件中，为 <code>doesExistById()</code> 和&nbsp;<code>getById()</code> 两个函数填充其内容：</p>
<pre><code class="language-typescript">export default {
   /**
   * Takes in the id params &amp; checks if the todo item exists
   * in the database
   * @param id
   * @returns boolean to tell if an entry of todo exits in table
   */
  doesExistById: async ({ id }: Todo) =&gt; {
    const [result] = await client.query(
      `SELECT COUNT(*) count FROM ${TABLE.TODO} WHERE id = ? LIMIT 1`,
      [id],
    );
    return result.count &gt; 0;
  },
  /**
   * 解构 id 变量 &amp; 返回找到的相关 todo
   * against it.
   * @param id
   * @returns object of todo item
   */
  getById: async ({ id }: Todo) =&gt; {
    return await client.query(
      `SELECT * FROM ${TABLE.TODO} WHERE id = ?`,
      [id],
    );
  },
}
</code></pre>
<p>让我们逐个介绍这两个函数：</p>
<ul>
<li><code>doesExistById</code> 函数从参数列表中解构出&nbsp;<code>id</code> 变量，并返回一个&nbsp;<code>boolean</code> 布尔值，来表明想要检测的这个独特 todo 是否存在于数据库中。</li>
</ul>
<pre><code class="language-typescript">const [result] = await client.query(
  `SELECT COUNT(*) count FROM ${TABLE.TODO} WHERE id = ? LIMIT 1`,
  [id],
);
return result.count &gt; 0;
</code></pre>
<p>我们通过 count 值来检查指定 todo 是否存在。如果其值大于 0 返回 <code>true</code>，否则返回 <code>false</code>。</p>
<ul>
<li><code>getById</code> 函数通过指定的 id 返回相应的数据：</li>
</ul>
<pre><code class="language-typescript">return await client.query(
  `SELECT * FROM ${TABLE.TODO} WHERE id = ?`,
  [id],
);
</code></pre>
<p>上面一行代码直接执行了 MySQL 语句，来通过 id 查询数据并返回结果。</p>
<p>接下来，打开 <code>controllers/todo.ts</code> 文件并为 <code>getTodoById</code> 控制器填充内容：</p>
<pre><code class="language-typescript">export default {
   /**
   * @description 通过 id 获取相关 tod
   * @route GET todos/:id
   */
  getTodoById: async (
    { params, response }: { params: { id: string }; response: any },
  ) =&gt; {
    try {
      const isAvailable = await TodoModel.doesExistById(
        { id: Number(params.id) },
      );

      if (!isAvailable) {
        response.status = 404;
        response.body = {
          success: false,
          message: "No todo found",
        };
        return;
      }

      const todo = await TodoModel.getById({ id: Number(params.id) });
      response.status = 200;
      response.body = {
        success: true,
        data: todo,
      };
    } catch (error) {
      response.status = 400;
      response.body = {
        success: false,
        message: `Error: ${error}`,
      };
    }
  },
}
</code></pre>
<p>这段代码也可以拆分为两段更小的部分来看：</p>
<pre><code class="language-typescript">const isAvailable = await TodoModel.doesExistById(
  { id: Number(params.id) },
);

if (!isAvailable) {
  response.status = 404;
  response.body = {
    success: false,
    message: "No todo found",
  };
  return;
}
</code></pre>
<p>首先我们通过以下代码来检查想要查找的 todo 是否存在于数据库中：</p>
<pre><code class="language-typescript">const isAvailable = await TodoModel.doesExistById(
  { id: Number(params.id) },
);
</code></pre>
<p>我们在其中需要转换 <code>params.id</code> 为 <code>Number</code> 数值类，因为接口中声明了我们的 <code>id</code> 键值必须是一个数字。接下来我们将转换为数值后的 <code>params.id</code> 传递给 <code>doesExistById</code> 方法，这个方法会返回布尔值。</p>
<p>接着检查这个布尔值，如果是 false 则返回像前文一样的包含 <code>404</code> 状态码的响应体：</p>
<pre><code class="language-typescript">if (!isAvailable) {
  response.status = 404;
  response.body = {
    success: false,
    message: "No todo found",
  };
  return;
}
</code></pre>
<p>第二部分是：</p>
<pre><code class="language-typescript">try {
  const todo: Todo = await TodoModel.getById({ id: Number(params.id) });
  response.status = 200;
  response.body = {
    success: true,
    data: todo,
  };
} catch (error) {
  response.status = 400;
  response.body = {
    success: false,
    message: `Error: ${error}`,
  };
}
</code></pre>
<p>这段代码和前文的很像。我们从数据库中获取到指定数据给 <code>todo</code> 变量，然后返回响应体。如果执行过程中有任何错误则响应体将包含错误信息返回给用户。</p>
<p>现在打开你的终端并且确保你的 MySQL 正在运行。在终端输入：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>打开 <a href="https://www.postman.com/">Postman</a> 来测试当前接口能否正常运行。</p>
<p>请记住我们每次重启服务器时都会重置数据库。如果你不想要这个功能，你可以注释掉 <code>db/client.ts</code> 文件中的整个 <code>run</code> 方法。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-07-at-17.09.32.png" alt="Screenshot-2020-06-07-at-17.09.32" width="600" height="400" loading="lazy"></p>
<p>执行 [POST] localhost:8080/todos =&gt; 将会新增一个新的 todo。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-07-at-17.10.13.png" alt="Screenshot-2020-06-07-at-17.10.13" width="600" height="400" loading="lazy"></p>
<p>执行 [POST] localhost:8080/todos =&gt; 将会返回所有的 todo。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-07-at-17.11.03.png" alt="Screenshot-2020-06-07-at-17.11.03" width="600" height="400" loading="lazy"></p>
<p>执行 [GET] localhost:8080/todos/:id =&gt; 将会当查找指定 todo，并返回其内容。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-07-at-17.16.06.png" alt="Screenshot-2020-06-07-at-17.16.06" width="600" height="400" loading="lazy"></p>
<p>执行 [GET] localhost:8080/todos/ =&gt; 将会返回包含 404 状态码及其错误信息的响应体。</p>
<p>目前我们搞定了如下 API：</p>
<ul>
<li>获取所有 todos</li>
<li>创建一个新的 todo</li>
<li>通过 ID 获取指定的 Todo</li>
</ul>
<p>仅剩下的 API：</p>
<ul>
<li>通过 ID 更新指定的 Todo</li>
<li>通过 ID 删除指定的 Todo</li>
</ul>
<h3 id="putidtodoapi">[PUT] 通过 ID 更新某 Todo 的 API</h3>
<p>让我们先为这个 API 创建模型（models）代码。进入 <code>models/todo.ts</code> 文件并为 <code>updateById</code> 方法填充其内容：</p>
<pre><code class="language-typescript">**
 * 更新某个指定 todo
 * @param id
 * @param todo
 * @param isCompleted
 * @returns integer (count of effect rows)
 */
updateById: async ({ id, todo, isCompleted }: Todo) =&gt; {
  const result = await client.query(
    `UPDATE ${TABLE.TODO} SET todo=?, isCompleted=? WHERE id=?`,
    [
      todo,
      isCompleted,
      id,
    ],
  );
  // return count of rows updated
  return result.affectedRows;
},
</code></pre>
<p><code>updateById</code> 方法将会从参数列表解构三个变量：<code>id</code>、<code>todo</code> 以及&nbsp;<code>isCompleted</code>。</p>
<p>我们直接编写了 MySQL 语句来执行查询：</p>
<pre><code class="language-typescript">const result = await client.query(
  `UPDATE ${TABLE.TODO} SET todo=?, isCompleted=? WHERE id=?`,
  [
    todo,
    isCompleted,
    id,
  ],
);
</code></pre>
<p>这段代码将通过 <code>id</code> 来更新指定的 <code>todo</code> 其&nbsp;<code>isCompleted</code> 的值。</p>
<p>接下来我们返回此条 MySQL 语句执行后影响到的数据表行数：</p>
<pre><code class="language-typescript">// return count of rows updated
return result.affectedRows;
</code></pre>
<p>这个行数值只会是 0 或者 1，且绝不会超过 1。因为我们数据表中的 ID 是唯一的——不同 todo 共用同一 ID的情况是不存在的。</p>
<p>接下来打开 <code>controllers/todo.ts</code> 文件并为 <code>updateTodoById</code> 方法填充其内容：</p>
<pre><code class="language-typescript">updateTodoById: async (
  { params, request, response }: {
    params: { id: string };
    request: any;
    response: any;
  },
) =&gt; {
  try {
    const isAvailable = await TodoModel.doesExistById(
      { id: Number(params.id) },
    );
    if (!isAvailable) {
      response.status = 404;
      response.body = {
        success: false,
        message: "No todo found",
      };
      return;
    }

    // 如果 todo 被找到了则更新它
    const body = await request.body();
    const updatedRows = await TodoModel.updateById({
      id: Number(params.id),
      ...body.value,
    });
    response.status = 200;
    response.body = {
      success: true,
      message: `Successfully updated ${updatedRows} row(s)`,
    };
  } catch (error) {
    response.status = 400;
    response.body = {
      success: false,
      message: `Error: ${error}`,
    };
  }
},
</code></pre>
<p>这段代码和前文的几个 API 中的代码几乎一样。与众不同的地方在这里：</p>
<pre><code class="language-typescript">// if todo found then update todo
const body = await request.body();
const updatedRows = await TodoModel.updateById({
  id: Number(params.id),
  ...body.value,
});
</code></pre>
<p>我们将用户传递来的 JSON 格式的 body 数据直接传给 <code>TodoModel.updateById</code> 函数。</p>
<p>记得需要转换 <code>id</code> 的变量类型为数值型以遵循接口的类型约束。</p>
<p>这行代码执行后将返回受到影响的行数。我们直接将其包装在响应体里返回。执行过程中如果有任何错误，将会被 catch 到并返回通用的报错信息。</p>
<p>让我们来重启服务器来检查是否能成功运行。请确保你的 MySQL 正在运行并在终端输入：</p>
<pre><code class="language-typescript">$ deno run --allow-net server.ts
</code></pre>
<p>打开 <a href="https://www.postman.com/">Postman</a> 来测试当前接口能否正常运行：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-07-at-17.42.02.png" alt="Screenshot-2020-06-07-at-17.42.02" width="600" height="400" loading="lazy"></p>
<p>执行 [PUT] localhost:8080/todos/:id =&gt; 将会通过指定的 id 来更新相应的 todo 内容</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-07-at-17.43.13.png" alt="Screenshot-2020-06-07-at-17.43.13" width="600" height="400" loading="lazy"></p>
<p>执行 [GET] localhost:8080/todos/ =&gt; 将会返回所有 todo 列表，来验证是否更新成功。</p>
<h3 id="deleteidtodoapi">[DELETE] 通过 ID 删除某 Todo 的 API</h3>
<p>在你的&nbsp;<code>models/todo.ts</code> 文件中创建一个&nbsp;<code>deleteById</code> 函数并填充如下内容：</p>
<pre><code class="language-typescript">/**
 * 通过指定 ID 来删除相应 todo
 * @param id
 * @returns integer (count of effect rows)
 */
deleteById: async ({ id }: Todo) =&gt; {
  const result = await client.query(
    `DELETE FROM ${TABLE.TODO} WHERE id = ?`,
    [id],
  );
  // return count of rows updated
  return result.affectedRows;
},
</code></pre>
<p>这里我们根据解构出的 <code>id</code> 值来通过 MySQL 删除指定的元素，并返回受到影响的行数。影响的行数的值依然只能是 0 或者 1，因为这个 ID 最多只会对应一个元素。</p>
<p>接下来，打开 <code>controllers/todo.ts</code> 文件并填充 <code>deleteByTodoId</code> 方法：</p>
<pre><code class="language-typescript">/**
 * @description 通过指定 ID 来删除相应 todo 
 * @route DELETE todos/:id
 */
deleteTodoById: async (
  { params, response }: { params: { id: string }; response: any },
) =&gt; {
  try {
    const updatedRows = await TodoModel.deleteById({
      id: Number(params.id),
    });
    response.status = 200;
    response.body = {
      success: true,
      message: `Successfully updated ${updatedRows} row(s)`,
    };
  } catch (error) {
    response.status = 400;
    response.body = {
      success: false,
      message: `Error: ${error}`,
    };
  }
},
</code></pre>
<p>这里很“爽快”地将解构出的 <code>params.id</code> 交给 <code>TodoModel.deleteById</code> 发方法，并返回此次执行过程中在数据库中的影响行数。</p>
<p>如果执行过程中有任何错误都会返回标准错误响应体。</p>
<p>让我们来检查这个 API 能否成功运行。</p>
<p>请确保你的 MySQL 正在运行，并在终端输入：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>打开&nbsp;<a href="https://www.postman.com/">Postman</a>&nbsp;来测试：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-07-at-18.23.04.png" alt="Screenshot-2020-06-07-at-18.23.04" width="600" height="400" loading="lazy"></p>
<p>执行 [GET] localhost:8080/todos/ =&gt; 将会得到所有 todo。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-07-at-18.23.11.png" alt="Screenshot-2020-06-07-at-18.23.11" width="600" height="400" loading="lazy"></p>
<p>执行 [DELETE] localhost:8080/todos/:id =&gt; 将会通过指定的 id 删除相应元素。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Screenshot-2020-06-07-at-18.23.44.png" alt="Screenshot-2020-06-07-at-18.23.44" width="600" height="400" loading="lazy"></p>
<p>执行 [GET] localhost:8080/todos/ =&gt; 将会返回所有 todo 列表，来看看之前想要删除的 todo 是否还在。</p>
<p>到了这里我们就结束了 Deno + Oak + MySQL 的实战教程。</p>
<p>整篇文章的代码可以在这里看到：<a href="https://github.com/adeelibr/deno-playground">https://github.com/adeelibr/deno-playground</a>。如果你有任何问题都可以在上面交流。或者提交你的 PR 到仓库中。</p>
<p>如果你感觉本系列很有帮助，可以分享它到社交网络中。同时我的 <a href="https://twitter.com/adeelibr">Twitter 账号是 @adeelibr</a>。我会很期待听到你的任何想法。</p>
<p>原文：<a href="https://www.freecodecamp.org/news/how-to-use-mysql-in-deno-oak/">How to Use MySQL With Deno and Oak</a>，作者：Adeel Imran</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Deno + Oak 构建酷炫的 Todo API ]]>
                </title>
                <description>
                    <![CDATA[ 序言 我是一位 JavaScript/Node 开发者，默默地喜欢甚至爱慕着 Deno。Deno 诞生之初就深深地吸引了我，此后我成为了 Deno 的忠实粉丝，期待着有朝一日能正式玩上 Deno。 本文专注于创造一个基于 REST API 设计的待做清单（Todo）应用。请记住本文中还不会涉及有关数据库操作的知识，其内容会在我之后的另一篇文章 [https://www.freecodecamp.org/news/how-to-use-mysql-in-deno-oak/]中进行详细介绍。 如果你想能够随时回顾或参考本文的代码，可以访问我的这个仓库：@adeelibr/deno-playground [https://github.com/adeelibr/deno-playground]，收录了该系列的所有代码。 > 译者注：另一篇文章《How to Use MySQL With Deno and Oak》即将会被翻译，其相关 Demo 也会被收录在《Deno 钻研之术》中。 照片来自于Bernard de Clerk [https://unsplash.com/@ber ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/create-a-todo-api-in-deno-written-by-a-guy-coming-from-node/</link>
                <guid isPermaLink="false">5ee70006db4be8080eb70e1d</guid>
                
                    <category>
                        <![CDATA[ Deno ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ hylerrix han ]]>
                </dc:creator>
                <pubDate>Mon, 15 Jun 2020 05:32:31 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/06/deno-oak-todo-api.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="">序言</h2>
<p>我是一位 JavaScript/Node 开发者，默默地喜欢甚至爱慕着 Deno。Deno 诞生之初就深深地吸引了我，此后我成为了 Deno 的忠实粉丝，期待着有朝一日能正式玩上 Deno。</p>
<p>本文专注于创造一个基于 REST API 设计的待做清单（Todo）应用。请记住本文中还不会涉及有关数据库操作的知识，其内容会在我之后的<a href="https://www.freecodecamp.org/news/how-to-use-mysql-in-deno-oak/">另一篇文章</a>中进行详细介绍。</p>
<p>如果你想能够随时回顾或参考本文的代码，可以访问我的这个仓库：<a href="https://github.com/adeelibr/deno-playground">@adeelibr/deno-playground</a>，收录了该系列的所有代码。</p>
<blockquote>
<p>译者注：另一篇文章《How to Use MySQL With Deno and Oak》即将会被翻译，其相关 Demo 也会被收录在《Deno 钻研之术》中。</p>
</blockquote>
<p><img src="https://chinese.freecodecamp.org/news/content/images/2021/04/photo-1590672617573-08866973bf72.jpeg" alt="photo-1590672617573-08866973bf72" width="2000" height="1333" loading="lazy"></p>
<p>照片来自于&nbsp;<a href="https://unsplash.com/@bernardtheclerk?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Bernard de Clerk</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></p>
<h3 id="">本文会涉及的内容</h3>
<ul>
<li>创建一个最基础的服务器</li>
<li>创建 5 个 APIs（路由 routes/控制器 controller）</li>
<li>创建一个中间件来给 API 请求添加终端输出的日志功能</li>
<li>创建一个 404 中间件来处理用户访问未知 API 时的情况</li>
</ul>
<h3 id="">本文需要的知识准备</h3>
<ul>
<li>一个已经安装好的 Deno 环境（别怕，我会告诉你怎么做）</li>
<li>对 TypeScript 有浅要的了解</li>
<li>如果你之前对 Node/Express 一定的了解就更好了（不了解也没关系，本文还是很通俗易懂的）</li>
</ul>
<h2 id="">让我们开始吧</h2>
<p>首先我们要先安装 Deno。由于我使用的是 Mac 操作系统，所以在这里我将使用 brew。只需要打开终端并输入这条命令即可：</p>
<pre><code class="language-bash">$ brew install deno
</code></pre>
<p>但如果你用的是其它操作系统的话，这里有一个安装手册可以看看：<a href="https://deno.land/#installation">deno.land installation</a>。上面有多样化的安装方式可供你根据不同的操作系统来选择。</p>
<p>一旦你安装成功，关闭终端并打开另一个后，输入这条命令：</p>
<pre><code class="language-bash">$ deno --version
</code></pre>
<p>一切正常的话终端会产生如下输出：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-28-at-22.34.24.png" alt="Screenshot-2020-05-28-at-22.34.24" width="600" height="400" loading="lazy"></p>
<p><code>deno --version</code> 命令用来查看当前安装的 Deno 是哪个版本。</p>
<p>棒极了！通过这个介绍我们已经成功完成了本文 10% 的挑战。</p>
<p>让我们继续探索，并为我们的待做清单应用创建一个后端 API 吧。</p>
<h2 id="">项目的准备工作</h2>
<p>阅读下文，可以来提前来仓库里看看本文收录的所有代码：<a href="https://github.com/adeelibr/deno-playground">@adeelibr/deno-playground</a>。</p>
<p>这里我们从零做起：</p>
<ul>
<li>创建一个名为 <code>chapter_1:oak</code> 的新文件夹（你也可以随意起名）。</li>
<li>当你创建完毕后使用 <code>cd</code> 命令进入这个文件夹中。创建一个名为 <strong>server.ts</strong> 的文件并填充如下代码：</li>
</ul>
<pre><code class="language-javascript">import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();
const port: number = 8080;

console.log('running on port ', port);
await app.listen({ port });
</code></pre>
<p>让我们先运行这个文件。打开你的终端并进入当前项目的根目录后，输入如下命令：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>别急别急，我会在之后来介绍 <code>--allow-net</code> 参数到底做了什么的 😄。</p>
<p>不出意外的话，你会得到如下结果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-28-at-22.33.28.png" alt="Screenshot-2020-05-28-at-22.33.28" width="600" height="400" loading="lazy"></p>
<p>到现在为止，我们创建了一个监听着 8080 端口的服务端应用。只有 8080 端口不被占用，这个应用才能正常执行。</p>
<p>如果你有过使用 JavaScript 开发的经验，你可能会注意到我们导入模块的方式有些不一样。我们在这里是这样导入模块的：</p>
<pre><code>import { Application } from "https://deno.land/x/oak/mod.ts";
</code></pre>
<p>当你在终端中执行 <code>deno run ---allow-net &lt;file_name&gt;</code> 命令时，Deno 会读取你的导入信息，并在本地的全局环境中没有安装该模块的情况下安装这些模块。</p>
<p>第一次执行时 Deno 会尝试访问 <code>https://deno.land/x/oak/mod.ts</code> 模块并安装 <code>oak</code> 库。 Oak 是一个专注于编写 API 的 Deno Web 框架。</p>
<p>接下来的一行我们是这样写的：</p>
<pre><code class="language-javascript">const app = new Application();
</code></pre>
<p>这条语句为我们的应用创建了一个实例，这个实例是本文深入探索 Deno 的基石。你可以为这个实例增加路由，配置中间件（如日志中间件），编写 404 未知路由处理程序等等。</p>
<p>接下来我们是这样写的：</p>
<pre><code class="language-javascript">const port: number = 8080;
// const port = 8080; // =&gt; 也可以写成这样
</code></pre>
<p>上面两行在功能上是等价的，唯一的区别是 <code>const port: number = 8080</code> 告诉 TypeScript： <code>port</code> 变量的类型是数值类的。</p>
<p>如果你这样写的话：<code>const port: number = "8080"</code>，终端会产生类似这样的报错：port 变量应该是&nbsp;<code>number</code> 类型的，但是这类尝试用&nbsp;<code>string</code> 类型的 "8080" 来为其赋值。</p>
<p>如果你想学习关于 Type 的更多类型现在就可以看看这个简单的文档：<a href="https://www.typescriptlang.org/docs/handbook/basic-types.html">TypeScript 官方 - 基础 Types 类型</a>。仅仅 2~3 分钟就可以重新回到本文。</p>
<p>在文件的最后我们是这样写的：</p>
<pre><code class="language-javascript">console.log('running on port ', port);
await app.listen({ port });
</code></pre>
<p>如上我们让 Deno 监听了 8080 端口，端口号是写死的。</p>
<p>在你的 <strong>server.ts</strong> 文件中添加如下更多的代码：</p>
<pre><code class="language-javascript">import { Application, Router } from "https://deno.land/x/oak/mod.ts";

const app = new Application();
const port: number = 8080;

const router = new Router();
router.get("/", ({ response }: { response: any }) =&gt; {
  response.body = {
    message: "hello world",
  };
});
app.use(router.routes());
app.use(router.allowedMethods());

console.log('running on port ', port);
await app.listen({ port });
</code></pre>
<p>相比之前新增的内容是从 <code>oak</code> 中同时导入了 <code>Application</code> 和 <code>Router</code>&nbsp;变量。</p>
<p>其中关于 <code>Router</code> 的相关代码是：</p>
<pre><code>const router = new Router();
router.get("/", ({ response }: { response: any }) =&gt; {
  response.body = {
    message: "hello world",
  };
});
app.use(router.routes());
app.use(router.allowedMethods());
</code></pre>
<p>我们通过 <code>const router = new Router()</code> 语句创建了新的 Router 示例，然后我们为其根目录 <code>/</code> 创建了处理 <code>get</code> 请求的执行方式。</p>
<p>让我们重点看看如下内容：</p>
<pre><code class="language-javascript">router.get("/", ({ response }: { response: any }) =&gt; {
  response.body = {
    message: "hello world",
  };
});
</code></pre>
<p><code>router.get</code> 函数接收两个参数。第一个参数是路由挂载的路径 <code>/</code>，第二个参数是一个函数。函数本身也接受一个对象参数，这里使用 ES6 语法将其解构，只取了其中 response 变量的值。</p>
<p>接下来就像之前编写&nbsp;<code>const port: number = 8080;</code> 语句一样为 <code>response</code> 变量声明类型。<code>{ response }: { response: any }</code> 语句告诉 TypeScript 我们这里解构的 <code>response</code> 变量是 <code>any</code> 类型的。</p>
<p><code>any</code> 类型可以帮准你避免 TypeScript 进行严格的类型检查，你可以通过<a href="https://www.typescriptlang.org/docs/handbook/basic-types.html#any">这个文档</a>来了解更多。</p>
<p>接下来我所编写的就是使用 <code>response</code> 变量，并设置 <code>response.body.message = "hello world";</code>。</p>
<pre><code>response.body = {
  message: "hello world",
};
</code></pre>
<p>最后同样重要的是，我们编写了如下两行代码：</p>
<pre><code>app.use(router.routes());
app.use(router.allowedMethods());
</code></pre>
<p>第一行告诉 Deno 要包含我们的 router 变量里设置的所有路径（目前我们只设置了根路径），第二行让 Deno 允许任意访问方法来请求我们设置的路径，比如 <code>GET, POST, PUT, DELETE</code>。</p>
<p>到这里就可以测试运行了 ✅ ，让我们执行这行语句来看看最终会发生什么：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p><code>---allow-net</code> 参数告诉 Deno：用户授予了这个应用在打开的端口上访问网络的权限。</p>
<p>现在通过你常用的浏览器打开 <code>http://localhost:8080</code> 地址，就可以得到如下结果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-28-at-23.11.08.png" alt="Screenshot-2020-05-28-at-23.11.08" width="600" height="400" loading="lazy"><br>
浏览器打开 localhost:8080 的执行结果</p>
<p>最难的部分差不多搞定了，但在对概念的更多了解中我们只进行了 60% 的介绍。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/images.jpeg#align=left&amp;display=inline&amp;height=195&amp;margin=%5Bobject%20Object%5D&amp;originHeight=195&amp;originWidth=258&amp;status=done&amp;style=none&amp;width=258" alt="images" width="600" height="400" loading="lazy"><br>
来自 Yoda 大师的批准</p>
<p>棒极了。</p>
<p>在我们正式开始编写待做清单的 API 前，我们最后要做的事是将如下代码：</p>
<pre><code class="language-javascript">console.log('running on port ', port);
await app.listen({ port });
</code></pre>
<p>替换成这样：</p>
<pre><code class="language-javascript">app.addEventListener("listen", ({ secure, hostname, port }) =&gt; {
  const protocol = secure ? "https://" : "http://";
  const url = ${protocol}${hostname ?? "localhost"}:${port};
  console.log(Listening on: ${port});
});

await app.listen({ port });
</code></pre>
<p>我们之前的代码是先在控制台上简单的打印一条成功日志，然后再让应用开始在端口上监听，不是很优雅（译者注：有可能会在监听失败的情况下依然打印监听成功的日志）。</p>
<p>在替换后的版本中，我们通过 <code>app.addEventListener("listen", ({ secure, hostname, port }) =&gt; {}</code> 语句来向应用实例添加事件侦听器后，再让应用监听在端口上。</p>
<p>侦听器的第一个参数是我们想侦听的事件。一语双关，这里侦听（listen）的就是 <code>listen</code> 事件 😅。第二个参数是一个可以被解构的对象，这里解构出 <code>{ secure, hostname, port }</code> 三个变量。Secure 变量是布尔类型，hostname 变量是字符串类型，port 变量是数值类型。</p>
<p>此时运行这个应用的话，只有在成功监听指定端口后才会输出监听成功的日志，</p>
<p>我们可以再向远方迈出一步，使其更加丰富多彩。让我们在 <code>server.ts</code> 文件的顶部添加这样一个新模块：</p>
<pre><code class="language-javascript">import { green, yellow } from "https://deno.land/std@0.53.0/fmt/colors.ts";
</code></pre>
<p>接下来我们可以在之前的事件侦听器函数里将如下代码：</p>
<pre><code class="language-javascript">console.log(Listening on: ${port});
</code></pre>
<p>替换为：</p>
<pre><code class="language-javascript">console.log(${yellow("Listening on:")} ${green(url)});
</code></pre>
<p>接下来当我们执行：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>将会打印输出如下日志：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-28-at-23.34.29.png" alt="Screenshot-2020-05-28-at-23.34.29" width="600" height="400" loading="lazy"></p>
<p>太酷了，我们现在有了一个色彩缤纷的控制台。</p>
<p>如果你在某处卡住了，你可以直接访问本教程的源码仓库：<a href="https://github.com/adeelibr/deno-playground/tree/master/chapter_1:oak">@adeelibr/deno-playground</a>。</p>
<p>让我们接下来创建待做清单的 API 吧。</p>
<ul>
<li>在项目的根目录创建一个 <code>routes</code> 文件夹，然后再文件夹里面创建一个 <code>todo.ts</code> 文件。</li>
<li>与此同时在项目根目录创建一个 <code>controllers</code> 文件夹，再在文件夹里也创建一个 <code>todo.ts</code> 文件。</li>
</ul>
<p>我们先来填充 <code>controllers/todo.ts</code> 文件里的内容：</p>
<pre><code class="language-javascript">export default {
  getAllTodos: () =&gt; {},
  createTodo: async () =&gt; {},
  getTodoById: () =&gt; {},
  updateTodoById: async () =&gt; {},
  deleteTodoById: () =&gt; {},
};
</code></pre>
<p>我们在这里先简单地导出了一个包含很多有名字的函数的对象，这些函数目前都是空的。</p>
<p>接下来在 <code>routes/todo.ts</code> 文件中填充这些：</p>
<pre><code class="language-javascript">import { Router } from "https://deno.land/x/oak/mod.ts";
const router = new Router();
// controller 控制器
import todoController from "../controllers/todo.ts";
router
  .get("/todos", todoController.getAllTodos)
  .post("/todos", todoController.createTodo)
  .get("/todos/:id", todoController.getTodoById)
  .put("/todos/:id", todoController.updateTodoById)
  .delete("/todos/:id", todoController.deleteTodoById);

export default router;
</code></pre>
<p>对于编写过 Node 和 Express 的人来说，对如上的代码风格一定很熟悉。</p>
<p>其中包括从 <code>oak</code> 中导入了 <code>Route</code> 变量并通过 <code>const router = new Router();</code> 语句将其实例化。</p>
<p>接下来我们导入我们的控制器：</p>
<pre><code>import todoController from "../controllers/todo.ts";
</code></pre>
<p>这里需要注意的是：在 Deno 中我们每次导入一个本地文件到项目中的时候，我们都必须填写完整这个文件的后缀。否则 Deno 是不知道用户想要导入的文件后缀到底以 <code>.js</code>&nbsp;还是 <code>.ts</code> 结尾。</p>
<p>接下来我们通过如下代码为应用配置了我们需要的所有 RESTful 风格的路径。</p>
<pre><code class="language-javascript">router
  .get("/todos", todoController.getAllTodos)
  .post("/todos", todoController.createTodo)
  .get("/todos/:id", todoController.getTodoById)
  .put("/todos/:id", todoController.updateTodoById)
  .delete("/todos/:id", todoController.deleteTodoById);
</code></pre>
<p>上面的代码会将路径解析为这样：</p>
<table>
<thead>
<tr>
<th>请求方式</th>
<th>API 路由</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td>/todos</td>
</tr>
<tr>
<td>GET</td>
<td>/todos/:id</td>
</tr>
<tr>
<td>POST</td>
<td>/todos</td>
</tr>
<tr>
<td>PUT</td>
<td>/todos/:id</td>
</tr>
<tr>
<td>DELETE</td>
<td>/todos/:id</td>
</tr>
</tbody>
</table>
<p>最后我们通过&nbsp;<code>export default router;</code>&nbsp;语句来将配置好的路由导出。</p>
<p>此时我们已经完成了创建路由的工作（但是由于我们的控制器还是空的函数，所以每个路由并都不会做任何反应，我们将向其中添加功能）。</p>
<p>在我们开始向每个控制器添加功能之前的最后一个难题是，我们需要将此 <code>router</code> 挂载到我们的&nbsp;<code>app</code> 实例上。</p>
<p>因此回到 <code>server.ts</code>&nbsp;文件中我们这样做：</p>
<ul>
<li>将这行代码添加至文件顶部：</li>
</ul>
<pre><code class="language-javascript">// routes 路由
import todoRouter from "./routes/todo.ts";
</code></pre>
<ul>
<li>删除这一段代码：</li>
</ul>
<pre><code class="language-javascript">const router = new Router();
router.get("/", ({ response }: { response: any }) =&gt; {
  response.body = {
    message: "hello world",
  };
});
app.use(router.routes());
app.use(router.allowedMethods());
</code></pre>
<ul>
<li>将其替换为：</li>
</ul>
<pre><code>app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());
</code></pre>
<p>终于搞定了，你的 <code>server.ts</code> 现在应该是这个样子：</p>
<pre><code class="language-javascript">import { Application } from "https://deno.land/x/oak/mod.ts";
import { green, yellow } from "https://deno.land/std@0.53.0/fmt/colors.ts";

// routes
import todoRouter from "./routes/todo.ts";

const app = new Application();
const port: number = 8080;

app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());

app.addEventListener("listen", ({ secure, hostname, port }) =&gt; {
  const protocol = secure ? "https://" : "http://";
  const url = `${protocol}${hostname ?? "localhost"}:${port}`;
  console.log(
    `${yellow("Listening on:")} ${green(url)}`,
  );
});

await app.listen({ port });
</code></pre>
<p>如果你在某处卡住了，你可以直接访问本教程的源码仓库：<a href="https://github.com/adeelibr/deno-playground/tree/master/chapter_1:oak">@adeelibr/deno-playground</a>。</p>
<p>由于路由的控制器上暂时没有任何功能，现在一起来手动为我们的控制器添加功能。</p>
<p>在此之前我们得先创建两个（小）文件：</p>
<ul>
<li>在项目的根目录上创建一个 <code>interfaces</code>&nbsp;文件夹并在其中创建一个 <code>Todo.ts</code>（确保 Todo 首字母大写，因为如果不这样做，它将不会在此处给出任何语法错误——这只是一种约定）。</li>
<li>同时在项目根目录创建一个 <code>stubs</code>&nbsp;文件夹并在其中创建一个&nbsp;<code>todos.ts</code>&nbsp;文件。</li>
</ul>
<p>在 <code>interfaces/Todo.ts</code>&nbsp;文件中编写如下接口说明：</p>
<pre><code class="language-interfaces">export default interface Todo {
  id: string,
  todo: string,
  isCompleted: boolean,
}
</code></pre>
<p>什么是 interface（接口）？</p>
<p>要知道 TypeScript 的核心功能之一是检查一个变量的类型。就像前文的 <code>const port: number = 8080</code>&nbsp;和&nbsp;<code>{ response }: { response : any }</code>&nbsp;一样，我们也可以检测一个变量是否为对象类型。</p>
<p>在 TypeScript 中，interface 负责命名类型，并且是<strong>定义代码内外类型约束</strong>的有效方法。</p>
<p>这里有一个有关 interface 的示例：</p>
<pre><code class="language-javascript">// 写了个接口
interface LabeledValue {
  label: string;
}

// 此函数的labeledObj 参数是符合 LabeledValue 接口类型的
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}

let myObj = {label: "Size 10 Object"};
printLabel(myObj);
</code></pre>
<p>希望如上示例可以让你对 interface 有更多的了解。如果你想了解更多的信息可以查看：<a href="https://www.typescriptlang.org/docs/handbook/interfaces.html">Interfaces 官方文档</a>。</p>
<p>现在关于 interface 的知识已经介绍够了，我们一起来模拟一些假数据（因为本文不涉及有关数据库的操作）。</p>
<p>我们在 <code>stubs/todos.ts</code> 文件中来为 todos 变量填充一些模拟数据。这样即可：</p>
<pre><code class="language-javascript">import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interface
import Todo from '../interfaces/Todo.ts';

let todos: Todo[] = [
  {
    id: v4.generate(),
    todo: 'walk dog',
    isCompleted: true,
  },
  {
    id: v4.generate(),
    todo: 'eat food',
    isCompleted: false,
  },
];

export default todos;
</code></pre>
<ul>
<li>有两件需要注意的事项：我们这里引用了一个新的模块并且通过 <code>import { v4 } from "https://deno.land/std/uuid/mod.ts";</code>&nbsp;语句解构了其中的 <code>v4</code>&nbsp;变量。接下来我们每次使用 <code>v4.generate()</code>&nbsp;语句都能生成一个随机的 ID 字符串。这个 <code>id</code>&nbsp;不能是&nbsp;<code>number</code> 类型的，而需是&nbsp;<code>string</code>&nbsp;类型的，因为我们之前的 <code>Todo</code> 接口已经声明了&nbsp;<code>id</code>&nbsp;的类型必须是字符串。</li>
<li>另一个需要注意的是 <code>let todos: Todo[] = []</code>&nbsp;语句。此语句告诉 Deno 我们的 todos 变量是一个&nbsp;<code>Todo</code> 数组（此时编译器将会知道数组的每一个元素都是&nbsp;<code>{id: _string_, todo: _string_ &amp; isCompleted: _boolean_}</code> 类型的，并不允许其他任何类型）。</li>
</ul>
<p>如果你想了解更多的信息可以查看：<a href="https://www.typescriptlang.org/docs/handbook/interfaces.html">Interfaces 官方文档</a>。</p>
<p>太棒了，你已经进行到如此之远，再接再厉。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/download-1.jpeg#align=left&amp;display=inline&amp;height=168&amp;margin=%5Bobject%20Object%5D&amp;originHeight=168&amp;originWidth=300&amp;status=done&amp;style=none&amp;width=300" alt="download-1" width="600" height="400" loading="lazy"></p>
<p>巨石强森感激你所做的一切努力。</p>
<h2 id="">让我们关注在控制器上</h2>
<p>在你的&nbsp;<code>controllers/todo.ts</code>&nbsp;文件中：</p>
<pre><code class="language-javascript">export default {
  getAllTodos: () =&gt; {},
  createTodo: async () =&gt; {},
  getTodoById: () =&gt; {},
  updateTodoById: async () =&gt; {},
  deleteTodoById: () =&gt; {},
};
</code></pre>
<p>让我们先编写&nbsp;<code>getAllTodos</code>&nbsp;控制器：</p>
<pre><code class="language-javascript">// stubs
import todos from "../stubs/todos.ts";

export default {
  /**
   * @description 获取所有 todos
   * @route GET /todos
   */
  getAllTodos: ({ response }: { response: any }) =&gt; {
    response.status = 200;
    response.body = {
      success: true,
      data: todos,
    };
  },
  createTodo: async () =&gt; {},
  getTodoById: () =&gt; {},
  updateTodoById: async () =&gt; {},
  deleteTodoById: () =&gt; {},
};
</code></pre>
<p>在开始介绍这段代码前，让我解释下每个控制器都有的参数——<code>context</code>（上下文）参数。</p>
<p>因此我们才能解构 <code>getAllTodos: (context) =&gt; {}</code>&nbsp;为：</p>
<pre><code class="language-javascript">getAllTodos: ({ request, response, params }) =&gt; {}
</code></pre>
<p>并且自从哪个我们使用&nbsp;<code>typescript</code>&nbsp;后，我们需要为每个这样的变量添加类型声明：</p>
<pre><code class="language-javascript">getAllTodos: (
  { request, response, params }: { 
    request: any, 
    response: any, 
    params: { id: string },
  },
) =&gt; {}
</code></pre>
<p>此时我们为解构的三个变量&nbsp;<code>{ request, response, params }</code>&nbsp;添加了类型说明。</p>
<ul>
<li><code>request</code>&nbsp;变量有关用户发来的请求（比如请求头和 JSON 类的请求体）。</li>
<li><code>response</code>&nbsp;变量有关服务器端通过 API 返回的信息。</li>
<li><code>params</code>&nbsp;变量是我们在路由配置中定义的参数，如下：</li>
</ul>
<pre><code class="language-javascript">.get("/todos/:id", ({ params}: { params: { id: string } }) =&gt; {})
</code></pre>
<p><code>/todos/:id</code>&nbsp;中的&nbsp;<code>:id</code>&nbsp;是一个变量，用来从 URL 中获得动态的数据。因此当用户访问这个 API （比如 <code>/todos/756</code>）的时候，<strong>756</strong> 则是 <strong>:id</strong> 参数的值。并且我们知道 URL 里的这个值的类型是&nbsp;<code>string</code>&nbsp;类的。</p>
<p>现在我们有了基本的声明后，让我们回到我们的 todos 控制器：</p>
<pre><code class="language-javascript">// stubs
import todos from "../stubs/todos.ts";

export default {
  /**
   * @description 获取所有 todos
   * @route GET /todos
   */
  getAllTodos: ({ response }: { response: any }) =&gt; {
    response.status = 200;
    response.body = {
      success: true,
      data: todos,
    };
  },
  createTodo: async () =&gt; {},
  getTodoById: () =&gt; {},
  updateTodoById: async () =&gt; {},
  deleteTodoById: () =&gt; {},
};
</code></pre>
<p>对于 <code>getAllTodos</code>&nbsp;方法来说我们只需要简单的返回结果。如果你记得之前说的，会想起来&nbsp;<code>response</code>&nbsp;是用来处理服务器想要给用户返回的数据。</p>
<p>对于编写过 Node 和 Express 的人来说，这里的一大不同是我们不需要 <code>return</code>&nbsp;响应对象。 Deno 会自动为我们执行此操作。</p>
<p>我们需要做的第一件事是通过 <code>response.status</code> 来设置此次请求的响应码是 <code>200</code>。</p>
<p>更多 HTTP 响应码可以看 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">MDN 上的&nbsp;HTTP 响应状态码文档</a>。</p>
<p>另一件事是设置 <code>response.body</code>&nbsp;的值为：</p>
<pre><code class="language-javascript">{
  success: true,
  data: todos
}
</code></pre>
<p>重新运行我们的服务器：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<blockquote>
<p>修订：--allow-net 属性告诉 Deno，此应用程序授予用户通过打开的端口访问网络的权限。</p>
</blockquote>
<p>一旦你的服务端示例跑通，挺可以通过 <code>GET /todos</code>&nbsp;方式来请求这个 API。这里我使用的是 Google Chrome 浏览器下的一个插件 <code>postman</code>，<a href="https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop//%40">在这里下载</a>。</p>
<p>你可以使用任意的 REST 风格的客户端，我喜欢使用 <code>postman</code> 是因为它真的很简单好用。</p>
<p>在 Postman 中，打开一个新的标签页。设置请求方式为&nbsp;<code>GET</code>&nbsp;请求并且在&nbsp;<code>URL</code>&nbsp;输入框中输入 <code>http://localhost:8080/todos</code>&nbsp;。点击 <code>Send</code> 按钮便会得到想要的结果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-02.01.11.png" alt="Screenshot-2020-05-29-at-02.01.11" width="600" height="400" loading="lazy"></p>
<p>GET /todos API 返回结果。</p>
<p>酷！一个 API 搞定了，还剩 4 个等着我们&nbsp;👍👍。</p>
<p>如果你在某处卡住了，可以在<a href="https://github.com/adeelibr/deno-playground/tree/master/chapter_1:oak">配套的源码仓库</a>中寻找答案。</p>
<p>让我们关注下一个控制器吧：</p>
<pre><code class="language-javascript">import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interfaces
import Todo from "../interfaces/Todo.ts";
// stubs
import todos from "../stubs/todos.ts";

export default {
  getAllTodos: () =&gt; {},
  /**
   * @description Add a new todo
   * @route POST /todos
   */
  createTodo: async (
    { request, response }: { request: any; response: any },
  ) =&gt; {
    const body = await request.body();
    if (!request.hasBody) {
      response.status = 400;
      response.body = {
        success: false,
        message: "No data provided",
      };
      return;
    }

    // 如果请求体验证通过，则返回新增后的所有 todos
    let newTodo: Todo = {
      id: v4.generate(),
      todo: body.value.todo,
      isCompleted: false,
    };
    let data = [...todos, newTodo];
    response.body = {
      success: true,
      data,
    };
  },
  getTodoById: () =&gt; {},
  updateTodoById: async () =&gt; {},
  deleteTodoById: () =&gt; {},
};
</code></pre>
<p>由于我们将要添加一个新的 Todo 到列表中，因此我在 controller 文件中导入了 2 个通用模块：</p>
<ul>
<li><code>import { v4 } from "https://deno.land/std/uuid/mod.ts"</code>&nbsp;语句用来为每一个 todo 元素创建一个独一无二的标识。</li>
<li><code>import Todo from "../interfaces/Todo.ts";</code> 语句用来保证新建的 todo 遵循 todo 元素的接口格式标准。</li>
</ul>
<p>我们的 <code>createTodo</code>&nbsp;控制器是 <code>async</code> 异步的代表函数中会使用到一些 Promise 技术。</p>
<p>让我们来截断说明其中的小片段：</p>
<pre><code class="language-javascript">const body = await request.body();
if (!request.hasBody) {
  response.status = 400;
  response.body = {
    success: false,
    message: "No data provided",
  };
  return;
}
</code></pre>
<p>首先我们读取请求体中用户传来的的 JSON 内容。接下来我们使用 <code>oak</code>&nbsp;的内置 <code>request.hasBody</code>&nbsp;方法来检查用户传来的内容是否为空。如果为空，我们将进入&nbsp;<code>if (!request.hasBody) {}</code>&nbsp;代码块中执行相关操作。</p>
<p>里面我们将响应体的状态码设置成 <code>400</code>（400 代表着用户端出现了一些本不该出现的错误），并且服务端返回的响应体为 <code>{success: false, message: "no data provided }</code>。之后程序直接执行&nbsp;<code>return;</code>&nbsp;语句来保证接下来的代码不会被执行。</p>
<p>接下来我们这样编写：</p>
<pre><code class="language-javascript">// 如果请求体验证通过，则返回新增后的所有 todos
let newTodo: Todo = {
  id: v4.generate(),
  todo: body.value.todo,
  isCompleted: false,
};
let data = [...todos, newTodo];
response.body = {
  success: true,
  data,
};
</code></pre>
<p>其中我们通过如下代码创建了一个全新的 todo 元素：</p>
<pre><code class="language-javascript">let newTodo: Todo = {
  id: v4.generate(),
  todo: body.value.todo,
  isCompleted: false,
};
</code></pre>
<p><code>let newTodo: Todo = {}</code> 保证&nbsp;<code>newTodo</code>&nbsp;变量的值和其它 todo 元素一样都遵循相同的接口格式。然后，我们使用 <code>v4.generate()</code>&nbsp;分配一个随机 ID，将 todo 的键值设置为 <code>body.value.todo</code>&nbsp;并将 <code>isCompleted</code>&nbsp;变量值设置为&nbsp;<code>false</code>。</p>
<p>这里需要知道的是，用户给我们发的内容我们可以通过 <code>oak</code>&nbsp;中的&nbsp;<code>body.value</code>&nbsp;来获取。</p>
<p>接下来我们这样做：</p>
<pre><code class="language-javascript">let data = [...todos, newTodo];
response.body = {
  success: true,
  data,
};
</code></pre>
<p>这里将 <code>newTodo</code>&nbsp;添加到整个 todo 列表中中，并在响应体中返回&nbsp;<code>{success: true &amp; data: data</code>。</p>
<p>此时这个控制器也运行成功了&nbsp;✅。</p>
<p>让我们重新运行我们的服务器：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>在 postman 中，我再打开一个新的标签页。设置请求的方式为 <code>POST</code>&nbsp;类型，并在&nbsp;<code>URL</code>&nbsp;输入框中输入 <code>http://localhost:8080/todos</code>&nbsp;后，点击 <code>Send</code>&nbsp;便会得到如下结果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-02.24.00.png" alt="Screenshot-2020-05-29-at-02.24.00" width="600" height="400" loading="lazy"></p>
<p>因为上面的请求体中发送了空的内容，所以得到了 400 错误响应码及其错误原因。</p>
<p>但如果我们给请求体中加入如下 JSON 内容，并重新发送：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-02.24.15.png" alt="Screenshot-2020-05-29-at-02.24.15" width="600" height="400" loading="lazy"></p>
<p>通过 { todo: "eat a lamma" } 来&nbsp;POST /todos&nbsp;后的成功结果，我们可以看到新的元素已经加入到列表中。</p>
<p>酷，我买可以看到我们的 API 已经一个个以预期的方式执行成功了。</p>
<p>两个 API 搞定，还剩三个要做。</p>
<p>我们快要搞定了，因为大部分难的内容已经介绍完毕。☺️ 🙂🤗🤩</p>
<p>让我们看看第三个 API：</p>
<pre><code class="language-javascript">import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interfaces
import Todo from "../interfaces/Todo.ts";
// stubs
import todos from "../stubs/todos.ts";

export default {
  getAllTodos: () =&gt; {},
  createTodo: async () =&gt; {},
  /**
   * @description 通过 ID 获取 todo
   * @route GET todos/:id
   */
  getTodoById: (
    { params, response }: { params: { id: string }; response: any },
  ) =&gt; {
    const todo: Todo | undefined = todos.find((t) =&gt; {
      return t.id === params.id;
    });
    if (!todo) {
      response.status = 404;
      response.body = {
        success: false,
        message: "No todo found",
      };
      return;
    }

    // 如果 todo 找到了
    response.status = 200;
    response.body = {
      success: true,
      data: todo,
    };
  },
  updateTodoById: async () =&gt; {},
  deleteTodoById: () =&gt; {},
};
</code></pre>
<p>我们先来聊聊&nbsp;<code>GET todos/:id</code>&nbsp;下的控制器，此控制器会通过 ID 来查找相应的 todo 元素。</p>
<p>让我们继续通过截取小片段来深入分析：</p>
<pre><code class="language-javascript">const todo: Todo | undefined = todos.find((t) =&gt; t.id === params.id);
if (!todo) {
  response.status = 404;
  response.body = {
    success: false,
    message: "No todo found",
  };
  return;
}
</code></pre>
<p>在第一行我们声明了一个 <code>const todo</code> 变量并将其类型设置为&nbsp;<code>Todo</code>&nbsp;或&nbsp;<code>undefined</code>&nbsp;类。因此 <code>todo</code> 元素只能是符合 <code>Todo</code>&nbsp;接口规范的变量或者是一个 <code>undefined</code>&nbsp;值，而不能是其它任何类型。</p>
<p>我们接下来使用 <code>todos.find((t) =&gt; t.id === params.id);</code>&nbsp;语句来通过&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find">Array.find()</a>&nbsp;方法和&nbsp;<code>params.id</code>&nbsp;的值来查找指定的 <code>todo</code>&nbsp;元素。如果找到了我们会得到&nbsp;<code>Todo</code>&nbsp;类型的 <code>todo</code>&nbsp;元素，发否则得到一个&nbsp;<code>undefined</code>&nbsp;值。</p>
<p>如果得到的 <code>todo</code>&nbsp;的值是 undefined 的，意味着如下 if 条件中的代码会执行：</p>
<pre><code class="language-javascript">if (!todo) {
  response.status = 404;
  response.body = {
    success: false,
    message: "No todo found",
  };
  return;
}
</code></pre>
<p>这里我们设置响应的状态码为 <code>404</code>，代表着 <code>not found</code>&nbsp;没有找到相关元素，并且返回体的格式也是标准的&nbsp;<code>{ status, message }</code>。</p>
<p>很酷不是嘛？ 😄</p>
<p>接下来我们简单地编写：</p>
<pre><code class="language-javascript">// 如果 todo 找到了
response.status = 200;
response.body = {
  success: true,
  data: todo,
};
</code></pre>
<p>设置一个响应状态码为 <code>200</code>&nbsp;的响应体并返回&nbsp;<code>success: true &amp; data: todo</code>&nbsp;内容。</p>
<p>我们来在 postman 中测试：</p>
<p>先一起重新启动服务端：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>在 postman 中，继续打开一个新的标签页，设置请求方式为&nbsp;<code>GET</code>&nbsp;请求并在&nbsp;<code>URL</code>&nbsp;输入框中输入 <code>http://localhost:8080/todos/:id</code>&nbsp;后，点击 <code>Send</code>&nbsp;来执行请求。</p>
<p>自从我们使用了随机 ID 生成器，首先我们需要调取获取所有元素的 API。并在元素列表里选取一个 ID 来测试这个新的 API。每次你重启 Deno 程序时，新的 ID 都会被重新生成。</p>
<p>我们这样输入：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-02.40.52.png" alt="Screenshot-2020-05-29-at-02.40.52" width="600" height="400" loading="lazy"></p>
<p>服务端返回 404，且告诉我们没有相关数据被找到。,</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-02.41.36.png" alt="Screenshot-2020-05-29-at-02.41.36" width="600" height="400" loading="lazy"></p>
<p>但如果输入一个正确的 ID，服务端会返回其 ID 和这个 ID 的一样的数据并且响应状态为 200。</p>
<p>如果你需要参考本文的源码可以访问这里：<a href="https://github.com/adeelibr/deno-playground">@adeelibr/deno-playground</a>。</p>
<p>不错，3 个 API 搞定，只剩 2 个了。</p>
<pre><code class="language-javascript">import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interfaces
import Todo from "../interfaces/Todo.ts";
// stubs
import todos from "../stubs/todos.ts";

export default {
  getAllTodos: () =&gt; {},
  createTodo: async () =&gt; {},
  getTodoById: () =&gt; {},
  /**
   * @description Update todo by id
   * @route PUT todos/:id
   */
  updateTodoById: async (
    { params, request, response }: {
      params: { id: string },
      request: any,
      response: any,
    },
  ) =&gt; {
    const todo: Todo | undefined = todos.find((t) =&gt; t.id === params.id);
    if (!todo) {
      response.status = 404;
      response.body = {
        success: false,
        message: "No todo found",
      };
      return;
    }

    // 如果找到相应 todo 则更新它
    const body = await request.body();
    const updatedData: { todo?: string; isCompleted?: boolean } = body.value;
    let newTodos = todos.map((t) =&gt; {
      return t.id === params.id ? { ...t, ...updatedData } : t;
    });
    response.status = 200;
    response.body = {
      success: true,
      data: newTodos,
    };
  },
  deleteTodoById: () =&gt; {},
};
</code></pre>
<p>让我们来探讨下一个控制器&nbsp;<code>PUT todos/:id</code>。这个控制器会更新一个元素的内容。</p>
<p>我们继续截断代码来细看：</p>
<pre><code class="language-javascript">const todo: Todo | undefined = todos.find((t) =&gt; t.id === params.id);
if (!todo) {
  response.status = 404;
  response.body = {
    success: false,
    message: "No todo found",
  };
  return;
}
</code></pre>
<p>这里做的和之前控制器做的一样，所以我就不深入介绍了。</p>
<p>高级提示：如果你想将这段代码设为通用代码块，然后在两个控制器中都使用它，完全可以。</p>
<p>接下来我们这样做：</p>
<pre><code class="language-javascript">// 如果找到相应 todo 则更新它
const body = await request.body();
const updatedData: { todo?: string; isCompleted?: boolean } = body.value;
let newTodos = todos.map((t) =&gt; {
  return t.id === params.id ? { ...t, ...updatedData } : t;
});
response.status = 200;
response.body = {
  success: true,
  data: newTodos,
};
</code></pre>
<p>其中我想在这里重点讨论的代码如下：</p>
<pre><code class="language-javascript">const updatedData: { todo?: string; isCompleted?: boolean } = body.value;
let newTodos = todos.map((t) =&gt; {
  return t.id === params.id ? { ...t, ...updatedData } : t;
});
</code></pre>
<p>首先，我们执行 <code>const updatedData = body.value</code>，然后将类型检查添加到 <code>updatedData</code> 上，如下所示：</p>
<pre><code class="language-javascript">updatedData: { todo?: string; isCompleted?: boolean }
</code></pre>
<p>这一小段代码告诉 TS：<code>updatedData</code>&nbsp;变量是一个有可能包含也有可能不包含 todo、isComplete 熟悉的对象。</p>
<p>接下来我们遍历每一个 todo 元素，就像这样：</p>
<pre><code class="language-javascript">let newTodos = todos.map((t) =&gt; {
  return t.id === params.id ? { ...t, ...updatedData } : t;
});
</code></pre>
<p>其中当&nbsp;<code>params.id</code>&nbsp;和&nbsp;<code>t.id</code>&nbsp;的值一致时，我们将此时的对象的内容重新覆盖为用户传来的想要更改为的内容。</p>
<p>我们也编写成功了这个 API。</p>
<p>让我们重新启动服务器：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>在 Postman 中打开一个标签页。将请求方式设置为&nbsp;<code>PUT</code>，并在 <code>URL</code>&nbsp;输入框中输入&nbsp;<code>http://localhost:8080/todos/:id</code> 后，点击 <code>Send</code>：</p>
<p>自从我们使用了随机 ID 生成器，首先我们需要调取获取所有元素的 API。并在元素列表里选取一个 ID 来测试这个新的 API。</p>
<p>每次重启 Deno 程序时，新的 ID 都会被重新生成。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-02.59.39.png" alt="Screenshot-2020-05-29-at-02.59.39" width="600" height="400" loading="lazy"></p>
<p>如上返回了 404 状态码并提示我们没有找到相关的 todo 元素。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-03.00.21.png" alt="Screenshot-2020-05-29-at-03.00.21" width="600" height="400" loading="lazy"></p>
<p>提供一个已知的 ID，并且请求体中填写需要改变的内容。服务端会返回一个更改后的元素及其它所有元素。</p>
<p>酷，四个 API 搞定我们只剩最后一个需要做。</p>
<pre><code class="language-javascript">import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interfaces
import Todo from "../interfaces/Todo.ts";
// stubs
import todos from "../stubs/todos.ts";

export default {
  getAllTodos: () =&gt; {},
  createTodo: async () =&gt; {},
  getTodoById: () =&gt; {},
  updateTodoById: async () =&gt; {},
  /**
   * @description 通过 ID 删除指定 todo
   * @route DELETE todos/:id
   */
  deleteTodoById: (
    { params, response }: { params: { id: string }; response: any },
  ) =&gt; {
    const allTodos = todos.filter((t) =&gt; t.id !== params.id);

    // remove the todo w.r.t id and return
    // remaining todos
    response.status = 200;
    response.body = {
      success: true,
      data: allTodos,
    };
  },
};
</code></pre>
<p>让我们最后来讨论下 <code>Delete todos/:id</code> 控制器的执行过程，此控制器会通过给定的 ID 来删除相应 todo 元素。</p>
<p>我们这里只需简单地加一条过滤方法：</p>
<pre><code>const allTodos = todos.filter((t) =&gt; t.id !== params.id);
</code></pre>
<p>遍历所有元素并删除&nbsp;<code>todo.id</code> 和&nbsp;<code>params.id</code>&nbsp;值一样的元素，并返回其余所有元素。</p>
<p>接下来我们这样编写：</p>
<pre><code class="language-javascript">// 删除这个 todo 并返回其它所有内容
response.status = 200;
response.body = {
  success: true,
  data: allTodos,
};
</code></pre>
<p>只需返回所有没有相同 todo.id 的待办事项清单即可。</p>
<p>让我们重启服务器：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>在 Postman 中打开一个标签页。将请求方式设置为 <code>PUT</code>，并在 <code>URL</code> 输入框中输入 <code>http://localhost:8080/todos/:id</code> 后，点击 <code>Send</code>：</p>
<p>自从我们使用了随机 ID 生成器，首先我们需要调取获取所有元素的 API。并在元素列表里选取一个 ID 来测试这个新的 API。每次你重启 Deno 程序时，新的 ID 都会被重新生成。</p>
<p>每次重启 Deno 程序时，新的 ID 都会被重新生成。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-03.07.54.png" alt="Screenshot-2020-05-29-at-03.07.54" width="600" height="400" loading="lazy"></p>
<p>我们终于搞定了所有 5 个 API。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/75bdf06df3fd6ddd9d3311d8cb2be029.jpg#align=left&amp;display=inline&amp;height=400&amp;margin=%5Bobject%20Object%5D&amp;originHeight=400&amp;originWidth=400&amp;status=done&amp;style=none&amp;width=400" alt="75bdf06df3fd6ddd9d3311d8cb2be029" width="600" height="400" loading="lazy"></p>
<p>现在我们只剩下两件事了：</p>
<ul>
<li>增加一个 404 中间件，来让用户访问不存在的路由时得到该有的提示；</li>
<li>增加一个日志 API 来打印所有请求的执行时间。</li>
</ul>
<h2 id="404">创建一个 404 路由中间件</h2>
<p>在项目的根目录中创建一个名为&nbsp;<code>middlewares</code>&nbsp;的文件夹，并在其中创建一个名为 <code>notFound.ts</code> 的文件后，添加如下代码：</p>
<pre><code class="language-javascript">export default ({ response }: { response: any }) =&gt; {
  response.status = 404;
  response.body = {
    success: false,
    message: "404 - Not found.",
  };
};
</code></pre>
<p>如上代码并没有引入什么新的知识点——它对于我们的控制器结构来使用了说很熟悉的风格。这里仅仅返回了&nbsp;<code>404</code>&nbsp;状态码（代表着相关路由没有找到）并且返回了一段 JSON 内容：&nbsp;<code>{ success, message }</code>。</p>
<p>接下来在你的&nbsp;<code>server.ts</code>&nbsp;文件中增加如下内容：</p>
<ul>
<li>在文件顶部添加相关导入语句：</li>
</ul>
<pre><code class="language-javascript">// 没有找到
import notFound from './middlewares/notFound.ts';
</code></pre>
<ul>
<li>接下来在 <code>app.use(todoRouter.allowedMethods())</code>&nbsp;下面增加如下内容：</li>
</ul>
<pre><code class="language-javascript">app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());

// 404 page
app.use(notFound);
</code></pre>
<p>执行顺序在这里很重要：每当我们尝试访问 API 路由时，它都会首先匹配/检查来自 <code>todoRouter</code>&nbsp;的路由。 如果没有找到，它将执行 <code>app.use(notFound);</code>&nbsp;语句。</p>
<p>让我们看看是否能成功运行。</p>
<p>重启服务器：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>在 Postman 中打开一个标签页。将请求方式设置为 <code>PUT</code>，并在 <code>URL</code> 输入框中输入 <code>http://localhost:8080/todos/:id</code> 后，点击 <code>Send</code>：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-12.28.10.png" alt="Screenshot-2020-05-29-at-12.28.10" width="600" height="400" loading="lazy"></p>
<p>因此，我们现在有了一个路由中间件，将 <code>app.use(notFound);</code>&nbsp;放在&nbsp;<code>server.ts</code>&nbsp;文件中其它路由的后面。如果请求路由不存在，它将执行并返回 <code>404</code>&nbsp;状态代码（表示未找到），并像往常一样简单地返回一个响应消息，即 <code>{success, message}</code>。</p>
<p><strong>高级贴士</strong>：我们已经约束 <code>{success, message}</code>&nbsp;是在请求失败时返回的格式，<code>{success, data}</code> 是在请求成功时候返回给用户的格式。因此，我们甚至可以将其作为对象接口，并将其添加到项目中，以确保接口的一致性和进行安全的类型检查。</p>
<p>酷，现在我们已经搞定了其中一个中间件——让我们添加另一个中间件来在终端打印日志吧。</p>
<p><strong>切记</strong>：如果你在某些地方卡住了，可以看看文章的配套源码：<a href="https://github.com/adeelibr/deno-playground">@adeelibr/deno-playground</a>。</p>
<h2 id="">终端中打印日志的中间件</h2>
<p>在你的 <code>middlewares</code> 文件夹中创建一个新的&nbsp;<code>logger.ts</code> 文件并填充如下内容：</p>
<pre><code class="language-javascript">import {
  green,
  cyan,
  white,
  bgRed,
} from "https://deno.land/std@0.53.0/fmt/colors.ts";

const X_RESPONSE_TIME: string = "X-Response-Time";

export default {
  logger: async (
    { response, request }: { response: any, request: any },
    next: Function,
  ) =&gt; {
    await next();
    const responseTime = response.headers.get(X_RESPONSE_TIME);
    console.log(`${green(request.method)} ${cyan(request.url.pathname)}`);
    console.log(`${bgRed(white(String(responseTime)))}`);
  },
  responseTime: async (
    { response }: { response: any },
    next: Function,
  ) =&gt; {
    const start = Date.now();
    await next();
    const ms: number = Date.now() - start;
    response.headers.set(X_RESPONSE_TIME, `${ms}ms`)
  },
};
</code></pre>
<p>在&nbsp;<code>server.ts</code>&nbsp;文件中添加如下代码：</p>
<ul>
<li>文件顶部添加 import 语句来导入模块：</li>
</ul>
<pre><code class="language-javascript">// logger
import logger from './middlewares/logger.ts';
</code></pre>
<ul>
<li>在之前提到的&nbsp;<code>todoRouter</code>&nbsp;代码前这样增加中间件代码：</li>
</ul>
<pre><code class="language-javascript">// 以下代码的编写顺序很重要
app.use(logger.logger);
app.use(logger.responseTime);

app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());
</code></pre>
<p>现在我们来讨论到底发生了什么。</p>
<p>我们先来讨论 <code>logger.ts</code>&nbsp;文件，先截断看这里：</p>
<pre><code class="language-javascript">import {
  green,
  cyan,
  white,
  bgRed,
} from "https://deno.land/std@0.53.0/fmt/colors.ts";
</code></pre>
<p>我在这里导入了有关终端颜色的模块，想要用在我们的日志中间件上。</p>
<p>这里和我们在之前的&nbsp;<code>server.ts</code> 文件中使用 <code>eventListener</code>&nbsp;的方式很像。我们将使用有颜色的日志信息来记录我们的 API 请求。</p>
<p>接下来我们设置了&nbsp;<code>const X_RESPONSE_TIME: string = "X-Response-Time";</code>。这条语句用来在与用户请求到来时给响应头的 Header 中注入 <code>X_RESPONSE_TIME</code>&nbsp;变量的值：<code>X-Response-Time</code>。我会在后面进行说明。</p>
<p>然后我们像这样一样导出一个对象：</p>
<pre><code class="language-javascript">export default {
  logger: async ({ response, request }, next) {}
  responseTime: async ({ response }, next) {}
};
</code></pre>
<p>此时我们在&nbsp;<code>server.ts</code>&nbsp;中这样使用：</p>
<pre><code class="language-javascript">// 以下两行的编写顺序很重要
app.use(logger.logger);
app.use(logger.responseTime);
</code></pre>
<p>现在我们来讨论下日志中间件到底做了什么，并且通过&nbsp;<code>next()</code> 来说明其执行过程。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-12.51.36.png" alt="Screenshot-2020-05-29-at-12.51.36" width="600" height="400" loading="lazy"></p>
<p>上图为调用 GET / todos API 时日志记录中间件的执行顺序。</p>
<p>这里和以前的控制器唯一的区别是使用了 <code>next()</code>&nbsp;函数，此函数有助于我们从一个控制器跳到另一个控制器，如上图所示。</p>
<p>因此有了这段：</p>
<pre><code class="language-javascript">export default {
  logger: async (
    { response, request }: { response: any, request: any },
    next: Function,
  ) =&gt; {
    await next();
    const responseTime = response.headers.get(X_RESPONSE_TIME);
    console.log(${green(request.method)} ${cyan(request.url.pathname)});
    console.log(${bgRed(white(String(responseTime)))});
  },
  responseTime: async (
    { response }: { response: any },
    next: Function,
  ) =&gt; {
    const start = Date.now();
    await next();
    const ms: number = Date.now() - start;
    response.headers.set(X_RESPONSE_TIME, ${ms}ms)
  },
};
</code></pre>
<p>请留意我们在 <code>server.ts</code> 中的编写方式：</p>
<pre><code class="language-javascript">// 以下代码的编写顺序很重要
app.use(logger.logger);
app.use(logger.responseTime);

app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());
</code></pre>
<p>这里的执行顺序如下：</p>
<ul>
<li>logger.logger 中间件</li>
<li>logger.responseTime 中间件</li>
<li>todoRouter 控制器（无论用户想要访问什么路由，出于解释的目的，这里假设用户都调用 <code>GET /todos</code>&nbsp;来获取所有待办事项）。</li>
</ul>
<p>因此会先执行&nbsp;logger.logger 的内容：</p>
<pre><code class="language-javascript">logger: async (
    { response, request }: { response: any, request: any },
    next: Function,
  ) =&gt; {
    await next();
    const responseTime = response.headers.get(X_RESPONSE_TIME);
    console.log(${green(request.method)} ${cyan(request.url.pathname)});
    console.log(${bgRed(white(String(responseTime)))});
  },
</code></pre>
<p>当遇到&nbsp;<code>await next()</code> 时会立即跳到下一个中间件——<code>responseTime</code>&nbsp;上。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-12.51.36-1.png" alt="Screenshot-2020-05-29-at-12.51.36-1" width="600" height="400" loading="lazy"></p>
<p>再次分享此图来回顾这个过程。</p>
<p>在&nbsp;<code>responseTime</code>&nbsp;中，只会先执行如下两行（参考上图的执行过程 2）：</p>
<pre><code class="language-javascript">const start = Date.now();
await next();
</code></pre>
<p>然后跳转到&nbsp;<code>getAllTodos</code>&nbsp;控制器中并执行 <code>getAllTodos</code>&nbsp;里的所有代码。</p>
<p>在这个控制器中我们不需要使用 <code>next()</code>，它会自动返回到 <code>responseTime</code>&nbsp;中间件中，并执行接下来的内容：</p>
<pre><code class="language-javascript">const ms: number = Date.now() - start;
response.headers.set(X_RESPONSE_TIME, ${ms}ms)
</code></pre>
<p>现在，我们便了解了 2、3、4 的执行顺序过程（参见上图）。</p>
<p>这里是发生的具体过程：</p>
<ul>
<li>我们通过执行 <code>const start = Date.now();</code>&nbsp;来捕获以 <code>ms</code>&nbsp;为单位的数据。然后，我们立即调用 <code>next()</code> 来跳转到 <code>getAllTodos</code>&nbsp;控制器并运行其中的代码。然后再次返回到 <code>responseTime</code>&nbsp;控制器中。</li>
<li>然后，通过执行 <code>const ms: number = Date.now() - start;</code>&nbsp;来减去请求刚来的时间。在这里，它将返回一个毫秒差的数字，将告诉 Deno 执行 getAllTodos 控制器所花费的所有时间。</li>
</ul>
<p>再次分享这个文件来回顾这个过程：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-12.51.36-2.png" alt="Screenshot-2020-05-29-at-12.51.36-2" width="600" height="400" loading="lazy"></p>
<ul>
<li>接下来我们在 <code>response</code>&nbsp;响应头的 Headers 中设置：</li>
</ul>
<pre><code class="language-javascript">response.headers.set(X_RESPONSE_TIME, ${ms}ms)
</code></pre>
<p>将 X-Response-Time 的值设置为 Deno getAllTodos API 所花费的毫秒数。</p>
<ul>
<li>然后从执行顺序 4 返回到执行顺序 5（参考上图）。</li>
</ul>
<p>在这里简单地编写：</p>
<pre><code class="language-javascript">const responseTime = response.headers.get(X_RESPONSE_TIME);
console.log(${green(request.method)} ${cyan(request.url.pathname)});
console.log(${bgRed(white(String(responseTime)))});
</code></pre>
<ul>
<li>打印日志时我们从&nbsp;<code>X-Response-Time</code>&nbsp;中获取到了执行 API 耗费的时间。</li>
<li>接下来我们用带有颜色的字体将其打印在终端。</li>
</ul>
<p><code>request.method</code>&nbsp;返回用户请求的方式，比如&nbsp;<code>GET, PUT 等</code>，同时&nbsp;<code>request.url.pathname</code>&nbsp;返回用户请求的路径，比如&nbsp;<code>/todos</code>。</p>
<p>让我们看看是否能成功运行。</p>
<p>重启服务器：</p>
<pre><code class="language-bash">$ deno run --allow-net server.ts
</code></pre>
<p>在 Postman 中打开一个标签页。将请求方式设置为 <code>GET</code>，并在 <code>URL</code> 输入框中输入 <code>http://localhost:8080/todos</code>&nbsp;后，点击 <code>Send</code>：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-13.17.13.png" alt="Screenshot-2020-05-29-at-13.17.13" width="600" height="400" loading="lazy"></p>
<p>在 Postman 中多请求几次 API，然后返回到控制台查看日志时，应该看到类似如下的内容：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screenshot-2020-05-29-at-13.21.03.png" alt="Screenshot-2020-05-29-at-13.21.03" width="600" height="400" loading="lazy"></p>
<p>每个 API 请求都会被日志中间件记录在终端。</p>
<p>就是这样 —— 我们搞定了这一切。</p>
<p>如果你在哪里卡住了，可以看看本文的全部源码：<a href="https://github.com/adeelibr/deno-playground/tree/master/chapter_1:oak">github.com/adeelibr/deno-playground/tree/master/chapter_1:oak</a></p>
<p>我希望你觉得本文会很有帮助，并且真的能帮助你学到一些新的知识。</p>
<p>如果你喜欢，欢迎分享到社交平台上。如果你想要深入交流，可以在&nbsp;<a href="https://twitter.com/adeelibr">Twitter</a>&nbsp;上与我联系。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Deno 入门手册：附大量 TypeScript 代码实例 ]]>
                </title>
                <description>
                    <![CDATA[ 我每周都在探索新的项目，很少会有一个像 Deno [https://deno.land/] 这样的项目让我如此着迷。 在本手册中我想要让你快速入手 Deno。我会将其与 Node.js 进行对比，然后助力你在 Deno 上搭建第一个 REST API Demo。 目录  * 什么是 Deno？  * 为什么是 Deno？为什么是现在？  * 你应该学习 Deno 吗？  * Deno 将取代 Node.js 吗？  * 一流的 TypeScript 支持  * 与 Node.js 的异同  * 不再有包管理器  * 安装 Deno ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-deno-handbook-with-examples/</link>
                <guid isPermaLink="false">5ec33d6bdb4be8080eb70b6c</guid>
                
                    <category>
                        <![CDATA[ Deno ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ hylerrix han ]]>
                </dc:creator>
                <pubDate>Tue, 19 May 2020 02:24:01 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-11-at-18.55.24.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>我每周都在探索新的项目，很少会有一个像 <a href="https://deno.land/">Deno</a> 这样的项目让我如此着迷。</p><p>在本手册中我想要让你快速入手 Deno。我会将其与 Node.js 进行对比，然后助力你在 Deno 上搭建第一个 REST API Demo。</p><h2 id="-">目录</h2><ul><li>什么是 Deno？</li><li>为什么是 Deno？为什么是现在？</li><li>你应该学习 Deno 吗？</li><li>Deno 将取代 Node.js 吗？</li><li>一流的 TypeScript 支持</li><li>与 Node.js 的异同</li><li>不再有包管理器</li><li>安装 Deno</li><li>Deno 命令</li><li>你的第一个 Deno 应用</li><li>Deno 代码实例</li><li>你的第一个 Deno 应用（深入版）</li><li>Deno 安全沙箱（Sandbox）</li><li>格式化代码</li><li>标准库</li><li>另一个 Deno 示例</li><li>Deno 是否有 Express/Hapi/Koa/*？</li><li>示例：使用 Oak 构建 REST API</li><li>更多内容</li><li>结语</li></ul><p>此外，<a href="https://flaviocopes.com/page/deno-handbook/">你可以在此处获取此 Deno 手册的 PDF / ePub / Mobi 版本。</a></p><h2 id="-deno-">什么是 Deno？</h2><p>如果你熟悉流行的服务器端 JavaScript 运行时 Node.js，那么 Deno 就像 Node.js 一样，但却在很多方面都得到了深刻改善的全新 JavaScript / TypeScript 运行时。</p><p>让我们从 Deno 的功能列表快速了解：</p><ul><li>Deno 基于最新的 JavaScript 语言；</li><li>Deno 具有覆盖面广泛的标准库；</li><li>Deno 以 TypeScript 为核心，配以更多独特的方式从而带来了巨大的优势，其中包括一流的 TypeScript 支持（Deno 自动编译 TypeScript 而无需你单独编译）；</li><li>Deno 大力拥抱 ES 模块标准；</li><li>Deno 没有包管理器；</li><li>Deno 具有一流的 <code>await</code> 语法支持；</li><li>Deno 内置测试工具；</li><li>Deno 旨在尽可能地与浏览器兼容，例如通过提供内置对象 <code>fetch</code> 和全局 <code>window</code> 对象。</li></ul><p>我们将在本手册中展开探索所有上述功能。</p><p>在你实战完 Deno 并了解它独特的功能魅力后，Node.js 或许会看起来有些过时。</p><p>特别是因为 Node.js 的 API 是基于回调机制的，因为 Node.js 是在 Promise 和 Async / Await 定义在标准之前编写的。Node.js 中无法对此机制进行全新的更改，因为此类更改将产生“毁灭性”的影响。因此，在 Node.js 中我们陷入了回调大量 API 的困境。</p><p>Node.js 的确很棒，并在可见的未来将继续成为 JavaScript 世界中事实上的标准。但我认为我们将逐渐看到 Deno 会因其一流的 TypeScript 支持和其内置的、覆盖面广泛的现代标准库而越来越被重视和采用。</p><p>由于没有向后兼容性的历史原因，Deno 将可以承担起所有使用现代 Web 技术编写的工程建设。但目前的现实是，我们也无法保证十年之内 Deno 不会发生像 Node.js 同样的事情，并且不会出现一项新技术代替 Deno。</p><h2 id="-deno--1">为什么是 Deno？为什么是现在？</h2><p>大约 2 年前，Node.js 的创建者 Ryan Dahl 在 JSConf EU 上首次介绍了 Deno。观看<a href="https://www.youtube.com/watch?v=M3BM9TB-8yA">当时的演讲视频</a>会非常有趣。如果你平时在大量接触 Node.js 和 JavaScript，这个视频请不要错过。</p><p>每个项目经理都必须下发决定。Ryan 回看 Node.js 中的一些早期设计依然感觉十分遗憾。此外，在 ES6/2016/2017 等持续发展中的标准加持下，如今的 JavaScript 与 2009 年 Node.js 创立时的 JavaScript 已经大不相同。</p><p>因此，他开启了一个全新项目，从而创建出服务器端的第二代 JavaScript 运行时。</p><p>新生的技术需要大量时间才能成熟，这正是我现在撰写本手册而不是两年前就开始撰写的原因。如今，第一个正式稳定的 Deno v1.0 版本终于指日可待（不出意外的话，v1.0 会在 2020 年 5 月 13 日发布）。</p><p>（译者注：翻译本手册时 Deno 1.0 已经发布。）</p><p>1.0 看起来仅仅是个数字，但在社区约定下，意味着直到 Deno 2.0 前 Deno 都不会有太多重大的破坏性改变——这很重要，因为你终于可以安心学习 Deno 当前的稳定版本了。</p><h2 id="-deno--2">你应该学习 Deno 吗？</h2><p>这并不那么容易回答。</p><p>学习像 Deno 这样全新的知识需要不少的前期技术沉淀。我的建议是：如果你现在才开始在服务器端使用 JavaScript 编程，并且你还不了解 Node.js，更没有任何 TypeScript 应用开发经验——那么请从 Node.js 学起。</p><p>毕竟用通俗观点来说，没有人会在如今因为选择学习 Node.js 而被解雇。</p><p>但如果你喜欢 TypeScript、也不想让项目中依赖无比庞大的 NPM 软件包、还想要随时随地使用 <code>await</code> 等语法，那么你可能真的需要 Deno。</p><h2 id="deno-node-js-">Deno 将取代 Node.js 吗？</h2><p>不能。Node.js 的生态已经十分庞大和完善，获得了数以万计的优秀技术支持，将能再战数十年。</p><h2 id="-typescript-">一流的 TypeScript 支持</h2><p>Deno 基于 Rust 和 TypeScript 这两种今天正在迅速发展的语言编写。</p><p>这意味着，即使我们可能选择编写纯 JavaScript 代码来运行在基于 TypeScript 语言编写的 Deno 上，我们也可以获得 TypeScript 的很多好处。</p><p>使用 Deno 运行 TypeScript 代码无需任何手动编译——Deno 会自动为你执行此步骤。</p><p>你不必非得在 Deno 上编写 TypeScript 代码，但是 Deno 因其核心由 TypeScript 语言编写的相关背景是不容忽视的：</p><p>首先，越来越多的 JavaScript 程序员开始喜欢上了 TypeScript 语言。</p><p>其次，你使用的工具可以方便地推断出许多有关用 TypeScript 语言编写的软件的信息，例如 Deno。</p><p>因此，当我们在 VS Code（紧密集成 TypeScript 的编辑器）上的编码环节就能及时地体会到类型检查和高级<a href="https://code.visualstudio.com/docs/editor/intellisense">智能感知（IntelliSense）</a>功能带来的好处。换句话说，编辑器可以以非常有用的方式来帮助我们了解 TypeScript 项目。</p><h2 id="-node-js-">与 Node.js 的异同</h2><p>由于 Deno 从某种角度来讲是 Node.js 的替代品，因此直接比较两者的异同对我们的理解会很有帮助。</p><p>相似之处：</p><ul><li>两者都是基于 <a href="https://flaviocopes.com/v8/">V8 引擎</a>开发的；</li><li>两者都非常适合在服务器端上编写 JavaScript 应用。</li></ul><p>差异之处：</p><ul><li>Node.js 用 C++ 和 JavaScript 语言编写。Deno 用 Rust 和 TypeScript 语言编写。</li><li>Node.js 有一个官方的软件包管理器，称为 NPM。Deno 不会有，而会允许你从 URL 导入任何 ES 模块。</li><li>Node.js 使用 CommonJS 模块语法导入软件包。Deno 使用 ES 标准模块导入。</li><li>Deno 在其所有 API 和标准库中都使用现代 ECMAScript 功能，而 Node.js 使用基于回调的标准库，并且没有计划对其进行升级。</li><li>Deno 通过权限控制提供了一个安全的沙箱环境，程序只能访问由用户设置为可执行标志的文件。Node.js 程序可以直接访问用户足以访问的任何内容。</li><li>Deno 长期以来一直在探索将程序编译成单个可执行文件的可能性，从而使得该可执行文件可以在没有外部依赖项（例如 Go）的情况下运行，但这并<a href="https://github.com/denoland/deno/issues/986">不是一件容易的事</a>，如果做得到，将会成为更有话语权的游戏规则改变者。</li></ul><h2 id="--1">没有包依赖管理器</h2><p>没有像 NPM 一样的程序包管理器并且大量依靠 URL 来承载和导入程序包是有利有弊的。但我真的很喜欢这个特性：它将会非常灵活，我们可以直接创建软件包而无需在 NPM 这样的存储库中发布它们。</p><p>虽然还没有官方的消息，但我认为 Deno 下的某种软件包管理器将会出现。</p><p>与此同时，Deno 网站为第三方软件包提供代码托管服务（并帮助其通过 URL 分发）：详见 <a href="https://deno.land/x/">https://deno.land/x/</a>。</p><h2 id="install-deno">Install Deno</h2><p>就闲聊到这里吧！让我们开始着手安装 Deno。</p><p>最简单的方法是使用 <a href="https://flaviocopes.com/homebrew/">Homebrew</a>：</p><pre><code class="language-bash">brew install deno
</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-12.04.45.jpg" class="kg-image" alt="brew install" width="600" height="400" loading="lazy"></figure><p>输出如上命令后，你将可以访问 <code>deno</code> 命令。帮助是<code>deno --help</code>：</p><p>（译者注：如果 HomeBrew 安装太慢可以尝试输入如下命令手动关闭 HomeBrew 的自动更新检测： <code>export HOMEBREW_NO_AUTO_UPDATE=true</code>。）</p><pre><code class="language-bash">flavio@mbp~&gt; deno --help
deno 0.42.0
A secure JavaScript and TypeScript runtime

Docs: https://deno.land/std/manual.md
Modules: https://deno.land/std/ https://deno.land/x/
Bugs: https://github.com/denoland/deno/issues
To start the REPL, supply no arguments:
  deno
To execute a script:
  deno run https://deno.land/std/examples/welcome.ts
  deno https://deno.land/std/examples/welcome.ts
To evaluate code in the shell:
  deno eval "console.log(30933 + 404)"
Run 'deno help run' for 'run'-specific flags.
USAGE:
    deno [OPTIONS] [SUBCOMMAND]
OPTIONS:
    -h, --help
            Prints help information
-L, --log-level &amp;lt;log-level&amp;gt;
        Set log level [possible values: debug, info]

-q, --quiet
        Suppress diagnostic output
        By default, subcommands print human-readable diagnostic messages to stderr.
        If the flag is set, restrict these messages to errors.
-V, --version
        Prints version informationSUBCOMMANDS:
    bundle         Bundle module and dependencies into single file
    cache          Cache the dependencies
    completions    Generate shell completions
    doc            Show documentation for a module
    eval           Eval script
    fmt            Format source files
    help           Prints this message or the help of the given subcommand(s)
    info           Show info about cache or info related to source file
    install        Install script as an executable
    repl           Read Eval Print Loop
    run            Run a program given a filename or url to the module
    test           Run tests
    types          Print runtime TypeScript declarations
    upgrade        Upgrade deno executable to newest version

</code></pre><h2 id="deno-">Deno 命令</h2><p>请注意上节中 <code>deno --help</code> 后 <code>SUBCOMMANDS</code> 中的部分，其中列出了我们在当前版本（0.42.0）中可以运行的所有命令，如下：</p><ul><li><code>bundle</code> ：将项目的模块和依赖项捆绑到单个文件中；</li><li><code>cache</code> ：缓存依赖项；</li><li><code>completions</code> ：generate shell completions；</li><li><code>doc</code> ：显示某模块的文档；</li><li><code>eval</code> ：运行一段代码，例如 <code>deno eval "console.log(1 + 2)</code>；</li><li><code>fmt</code> ：内置的代码格式化程序（类似于 Go 语言中的 <code>gofmt</code>）；</li><li><code>help</code> ：打印某消息或某给定子命令的帮助信息；</li><li><code>info</code> ：显示有关缓存的信息或与源文件有关的信息；</li><li><code>install</code> ：将脚本安装为可执行文件；</li><li><code>repl</code> ：开启 REPL 环境（默认子命令）；</li><li><code>run</code> ：运行给定文件名或 URL 的程序；</li><li><code>test</code> ：运行测试；</li><li><code>types</code> ：打印运行时的 TypeScript 声明；</li><li><code>upgrade</code> ：升级 Deno 到最新版本。</li></ul><p>你可以运行 <code>deno &lt;subcommand&gt; help</code> 以获取该子命令的特定文档，例如 <code>deno run --help</code>。</p><p>如下所示，我们可以直接输入 <code>deno</code> 命令命令来默认启动 REPL（Read-Execute-Print-Loop）环境直接调试功能，这与运行 <code>deno repl</code> 效果是相同的。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-12.07.50.png" class="kg-image" alt="Read-Execute-Print-Loop" width="600" height="400" loading="lazy"></figure><p>一个更常见的直接使用 <code>deno</code> 命令的场景是执行在 TypeScript 文件中写的 Deno 应用程序。</p><p>（译者注：现在需要使用 <code>deno run</code> 命令而非 <code>deno</code> 命令来执行 TypeScript 文件。）</p><p>你可以同时运行 TypeScript（.ts）文件或 JavaScript（.js）文件。</p><p>如果你不熟悉 TypeScript，请不要担心——Deno 是用 TypeScript 编写的，并且你可以使用纯 JavaScript 编写“客户端”应用程序。</p><p>如果你想快速上手的 TypeScript 话，可以阅读我的 <a href="https://flaviocopes.com/typescript/">TypeScript 教程</a>。</p><h2 id="-deno--3">你的第一个 Deno 应用</h2><p>让我们来运行第一个 Deno 应用程序。</p><p>Deno 让我感到非常惊奇的特性是：你甚至不必写一行代码，便可以直接运行任何 URL 上的 Deno 应用程序。</p><p>此时 Deno 会将 URL 上的程序下载到本地并进行编译，然后运行：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-12.22.30.jpg" class="kg-image" alt="Deno" width="600" height="400" loading="lazy"></figure><p><strong>当然，我一般不建议从 Internet 运行无法保障安全性的代码。在这种情况下，我们先运行 Deno 官方网站上提供的 Demo；另外 Deno 还有一个沙箱，可以阻止程序执行你不希望做的事情。稍后再详细介绍。</strong></p><p>这个程序很简单，只需要一个<code>console.log()</code>调用：</p><pre><code class="language-ts">console.log("Welcome to Deno 🦕");
</code></pre><p>如果使用浏览器打开直接打开 <a href="https://deno.land/std/examples/welcome.ts">https://deno.land/std/examples/welcome.ts</a> 这个 URL，则会看到以下页面：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-13.50.00.png" class="kg-image" alt="welcome.ts" width="600" height="400" loading="lazy"></figure><p>奇怪吧？你可能期待着打开 URL 后出现一个纯 TypeScript 文件以供下载，但是我们却看到了一个网页。原因是 Deno 网站的 Web 服务器知道你正在使用浏览器，并为你提供了对用户更加友好的页面。</p><p>为了验证这个功能，我们可以使用 <code>wget</code> 命令来测试这个 URL，<code>wget</code> 使用 <code>text/plain</code> 下载文本而不是 <code>text/html</code>：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-13.52.25.png" class="kg-image" alt="wget" width="600" height="400" loading="lazy"></figure><p>如果你想再运行这个程序，现在已经被 Deno 缓存了，不需要再下载和编译了。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-12.22.47.jpg" class="kg-image" alt="Deno" width="600" height="400" loading="lazy"></figure><p>你可以用 <code>--reload</code> 参数强制重新下载和编译原始源码。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-12.28.57.jpg" class="kg-image" alt="Screen-Shot-2020-05-09-at-12.28.57" width="600" height="400" loading="lazy"></figure><p>在当前版本（0.42.0）中，<code>deno run</code> 有许多未在 <code>deno --help</code> 清单中列出的功能。你需要运行 <code>deno run --help</code> 以显示更多。</p><pre><code class="language-bash">flavio@mbp~&gt; deno run --help
deno-run
Run a program given a filename or url to the module.
By default all programs are run in sandbox without access to disk, network or
ability to spawn subprocesses.
  deno run https://deno.land/std/examples/welcome.ts
Grant all permissions:
  deno run -A https://deno.land/std/http/file_server.ts
Grant permission to read from disk and listen to network:
  deno run --allow-read --allow-net https://deno.land/std/http/file_server.ts
Grant permission to read whitelisted files from disk:
  deno run --allow-read=/etc https://deno.land/std/http/file_server.ts
USAGE:
    deno run [OPTIONS] &lt;SCRIPT_ARG&gt;...
OPTIONS:
    -A, --allow-all
            Allow all permissions
    --allow-env
        Allow environment access

    --allow-hrtime
        Allow high resolution time measurement

    --allow-net=&amp;lt;allow-net&amp;gt;
        Allow network access

    --allow-plugin
        Allow loading plugins

    --allow-read=&amp;lt;allow-read&amp;gt;
        Allow file system read access

    --allow-run
        Allow running subprocesses

    --allow-write=&amp;lt;allow-write&amp;gt;
        Allow file system write access

    --cached-only
        Require that remote dependencies are already cached

    --cert &amp;lt;FILE&amp;gt;
        Load certificate authority from PEM encoded file

-c, --config &amp;lt;FILE&amp;gt;
        Load tsconfig.json configuration file

-h, --help
        Prints help information

    --importmap &amp;lt;FILE&amp;gt;
        UNSTABLE:
        Load import map file
        Docs: https://deno.land/std/manual.md#import-maps
        Specification: https://wicg.github.io/import-maps/
        Examples: https://github.com/WICG/import-maps#the-import-map
    --inspect=&amp;lt;HOST:PORT&amp;gt;
        activate inspector on host:port (default: 127.0.0.1:9229)

    --inspect-brk=&amp;lt;HOST:PORT&amp;gt;
        activate inspector on host:port and break at start of user script

    --lock &amp;lt;FILE&amp;gt;
        Check the specified lock file

    --lock-write
        Write lock file. Use with --lock.

-L, --log-level &amp;lt;log-level&amp;gt;
        Set log level [possible values: debug, info]

    --no-remote
        Do not resolve remote modules

-q, --quiet
        Suppress diagnostic output
        By default, subcommands print human-readable diagnostic messages to stderr.
        If the flag is set, restrict these messages to errors.
-r, --reload=&amp;lt;CACHE_BLACKLIST&amp;gt;
        Reload source code cache (recompile TypeScript)
        --reload
          Reload everything
        --reload=https://deno.land/std
          Reload only standard modules
        --reload=https://deno.land/std/fs/utils.ts,https://deno.land/std/fmt/colors.ts
          Reloads specific modules
    --seed &amp;lt;NUMBER&amp;gt;
        Seed Math.random()

    --unstable
        Enable unstable APIs

    --v8-flags=&amp;lt;v8-flags&amp;gt;
        Set V8 command line options. For help: --v8-flags=--help
</code></pre><h2 id="deno--1">Deno 代码实例</h2><p>除了前文我们运行的 Demo 外，Deno 官网还提供了一些其他的例子，可以在这里查看：<a href="https://deno.land/std/examples/">https://deno.land/std/examples/</a>。</p><p>（译者注：你可能需要配置代理来更好地访问 DenoLand。）</p><p>在撰写本手册时，我们可以找到：</p><ul><li><code>cat.ts</code> ：打印的内容是作为参数提供的文件列表；</li><li><code>catj.ts</code> ：打印的内容是作为参数提供的文件列表；</li><li><code>chat/</code> ：聊天的一种实现；</li><li><code>colors.ts</code> ：打印一个彩色版本的 Hello world!；</li><li><code>curl.ts</code> ：一个简单的实现，curl 它打印指定为参数的 URL 的内容；</li><li><code>echo_server.ts</code> ：TCP 回显服务器；</li><li><code>gist.ts</code> ：一个将文件发布到 gist.github.com 的程序；</li><li><code>test.ts</code> ：样本测试套件；</li><li><code>welcome.ts</code> ：一个简单的 console.log 语句（我们在上面运行的第一个程序）；</li><li><code>xeval.ts</code> ：允许你为收到的任何标准输入行运行任何 TypeScript 代码。曾经被设计为 <code>deno xeval</code> 子命令但现在从官方命令中删除。</li></ul><h2 id="-deno--4">你的第一个 Deno 应用（深入版）</h2><p>我们来写一些代码吧。</p><p>前文执行的 <code>deno run https://deno.land/std/examples/welcome.ts</code> 命令执行的是官网提供的一个 Deno 应用，所以我们没有看到任何关于 Deno 代码具体的样子。</p><p>接下来让我们从 Deno 官方网站上列出的默认示例应用开始。</p><pre><code class="language-ts">import { serve } from "https://deno.land/std/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
  req.respond({ body: "Hello World\n" });
}
</code></pre><p>这段代码从 <code>http/server</code> 模块中导入服务函数。可见我们不需要先安装这些模块，而且也不会像 Node.js 那样将这些模块大量存储在本地机器上。这也是 Deno 安装速度快的原因之一。</p><p>从 <code>https://deno.land/std/http/server.ts</code> 中导入会导入最新版本的模块。你可以使用<code>@VERSION</code>导入特定的版本，如下所示。</p><pre><code class="language-ts">import { serve } from "https://deno.land/std@v0.42.0/http/server.ts";
</code></pre><p>该 serve 函数在此文件中的定义如下：</p><pre><code class="language-ts">/**
 * Create a HTTP server
 *
 *     import { serve } from "https://deno.land/std/http/server.ts";
 *     const body = "Hello World\n";
 *     const s = serve({ port: 8000 });
 *     for await (const req of s) {
 *       req.respond({ body });
 *     }
 */
export function serve(addr: string | HTTPOptions): Server {
  if (typeof addr === "string") {
    const [hostname, port] = addr.split(":");
    addr = { hostname, port: Number(port) };
  }

  const listener = listen(addr);
  return new Server(listener);
}
</code></pre><p>我们接下来实例化一个服务器，调用 <code>server()</code> 函数传递一个带有端口属性的对象。</p><p>然后我们运行如下循环来响应来自服务器的每一个请求。</p><pre><code class="language-ts">for await (const req of s) {
  req.respond({ body: "Hello World\n" });
}
</code></pre><p>请注意，我们在这里使用 <code>await</code> 关键字而不需要将其封装到异步函数中，因为 Deno 在其内部实现了顶层的 <code>await</code> 支持。</p><p>让我们在本地运行这个程序。假设你使用的是 VS Code（你可以使用任何你喜欢的编辑器），我建议从 <code>justjavac</code> 开发的 Deno VS Code 扩展入手（当我尝试的时候还有一个同名的扩展，但是已经被淘汰了，可能将来会消失）。</p><p>（译者注：justjavac 的 Deno VS Code 拓展将被官方收录，以后可以直接使用官方的拓展。）</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-15.28.06.png" class="kg-image" alt="justjavac" width="600" height="400" loading="lazy"></figure><p>该扩展将为 <code>VS Code</code> 提供几个实用工具和不错的东西来帮助你编写应用程序。</p><p>现在在一个文件夹中创建一个<code>app.ts</code> 文件，然后粘贴上面的代码。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-15.40.18.png" class="kg-image" alt="app.ts" width="600" height="400" loading="lazy"></figure><p>现在用 <code>deno run app.ts</code> 运行它。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-15.39.28.jpg" class="kg-image" alt="app.ts" width="600" height="400" loading="lazy"></figure><p>Deno 会先下载、编译我们导入的那个依赖及其所有需要的依赖项。</p><p>这是由于我们导入的 <code>https://deno.land/std/http/server.ts</code> 文件本身就有数个其它依赖：</p><pre><code class="language-ts">import { encode } from '../encoding/utf8.ts'
import { BufReader, BufWriter } from '../io/bufio.ts'
import { assert } from '../testing/asserts.ts'
import { deferred, Deferred, MuxAsyncIterator } from '../async/mod.ts'
import {
bodyReader,
chunkedBodyReader,
emptyReader,
writeResponse,
readRequest,
} from './_io.ts'
import Listener = Deno.Listener
import Conn = Deno.Conn
import Reader = Deno.Reader

</code></pre><p>但 Deno 都会帮我们自动导入。</p><p>在最后，我们还有一个问题。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-15.42.05.png" class="kg-image" alt="app.ts" width="600" height="400" loading="lazy"></figure><p>这是怎么回事？我们为什么会收到执行权限被拒绝的提示？</p><p>这就涉及到了 Deno 的 Sandbox 问题，我们一起来看看。</p><h2 id="deno-sandbox-">Deno 安全沙箱（Sandbox）</h2><p>我之前提到过，Deno 有一个安全沙箱，可以防止程序做一些你不允许的事情。</p><p>这意味着什么呢？</p><p>Ryan 曾在 Deno 的介绍讲座中提到的一件事是：有时候你想在 Web 浏览器之外运行一个 JavaScript 程序，却不想让它肆意在你的系统中访问任何它想要的东西，比如使用网络与外部世界对话。</p><p>为什么我们通常只安装来自可信来源的 Node.js 包？这是因为没有什么可以阻止 Node.js 程序获取你系统上的 SSH 密钥或其他任何东西，并将其发送到服务器上。但是，我们该怎么知道自己或其他人使用的一个项目是否被黑客入侵了？</p><p>Deno 的解决方案是试图大量借鉴浏览器实现相同的权限模型——除非你明确允许，否则在浏览器中运行的任何 JavaScript 都不能在你的系统上做不正当的事情。</p><p>回到 Deno，如果一个程序想要像前面的例子一样访问网络，那么我们需要给它权限。</p><p>我们可以通过在运行命令时传递一个标志来实现，本例中是 <code>--allow-net</code>。</p><pre><code class="language-bash">deno run --allow-net app.ts
</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-15.48.41.png" class="kg-image" alt="allow-net" width="600" height="400" loading="lazy"></figure><p>该应用程序现在监听在 8000 端口上运行着 HTTP 服务器：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-15.49.02.png" class="kg-image" alt="allow-net" width="600" height="400" loading="lazy"></figure><p>其他标志允许 Deno 解锁其他功能，如下所示：</p><ul><li><code>--allow-env</code> ：允许访问环境变量；</li><li><code>--allow-hrtime</code> ：允许高分辨率时间测量；</li><li><code>--allow-net=&lt;allow-net&gt;</code> ：允许网络访问；</li><li><code>--allow-plugin</code> ：允许加载插件；</li><li><code>--allow-read=&lt;allow-read&gt;</code> ：允许文件系统读取权限；</li><li><code>--allow-run</code> ：允许运行子进程；</li><li><code>--allow-write=&lt;allow-write&gt;</code> ：允许文件系统写入访问；</li><li><code>--allow-all</code> ：允许所有权限(与<code>-A</code>相同)。</li></ul><p>其中，<code>net</code>、<code>read</code> 和 <code>write</code> 的权限可以是细化的。例如，你可以使用 <code>--allow-read=/dev</code>，允许从特定文件夹中读取。</p><h2 id="--2">格式化代码</h2><p>Go 语言编译器自带的 <code>gofmt</code> 命令是我非常喜欢 Go 语言特性之一。所有的 Go 代码的格式看起来都是一样的。每位 Go 程序员都在使用 <code>gofmt</code>。</p><p>JavaScript 程序员都习惯于运行 <a href="https://flaviocopes.com/prettier/">Prettier</a> 工具，而 <code>deno fmt</code> 实际上直接内置相关库到底层上运行。</p><p>假设你有一个格式化问题严重的文件如下图所示。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-16.06.58.png" class="kg-image" alt="deno fmt" width="600" height="400" loading="lazy"></figure><p>你运行 <code>deno fmt app.ts</code>，它就会执行正确的代码格式化，包括自动加上缺失的分号。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-16.07.25.png" class="kg-image" alt="deno fmt" width="600" height="400" loading="lazy"></figure><h2 id="--3">标准库</h2><p>尽管 Deno 还很年轻，但它的标准库仍然很庞大。这包括：</p><ul><li><code>archive</code> ：tar 文件归档的实用程序</li><li><code>async</code> ：异步工具</li><li><code>bytes</code> ：帮助器来操作字节切片</li><li><code>datetime</code> ：日期 / 时间解析</li><li><code>encoding</code> ：各种格式的编码/解码</li><li><code>flags</code> ：解析命令行标志</li><li><code>fmt</code> ：格式化和打印</li><li><code>fs</code> ：文件系统 API</li><li><code>hash</code> ：加密库</li><li><code>http</code> ：HTTP 服务器</li><li><code>io</code> ：I/O 库</li><li><code>log</code> ：日志实用程序</li><li><code>mime</code> ：支持多类型数据</li><li><code>node</code> ：Node.js 兼容层</li><li><code>path</code> ：路径操纵</li><li><code>ws</code> ：WebSockets</li></ul><h2 id="-deno--5">另一个 Deno 示例</h2><p>我们再来看看另一个 Deno APP 的例子，以如下 <code>cat.ts</code> 为例。</p><pre><code class="language-ts">const filenames = Deno.args;
for (const filename of filenames) {
  const file = await Deno.open(filename);
  await Deno.copy(file, Deno.stdout);
  file.close();
}
</code></pre><p>这里把 <code>Deno.args</code> 的值分配给了 filenames 变量，<code>Deno.args</code> 是一个包含所有发送到命令中的参数的变量。</p><p>我们对这些参数进行迭代：对每一个参数，我们使用 <code>Deno.open()</code> 打开文件，并使用 <code>Deno.copy()</code> 将文件的内容打印到 <code>Deno.stdout</code> 中，最后我们关闭该文件。</p><p>如果你使用如下命令：</p><pre><code class="language-bash">deno run https://deno.land/std/examples/cat.ts
</code></pre><p>程序被下载编译后，由于我们没有指定任何参数，所以没有发生任何事情。</p><p>现在试试这个：</p><pre><code class="language-bash">deno run https://deno.land/std/examples/cat.ts app.ts
</code></pre><p>假设你在同一个文件夹里有之前项目中的 <code>app.ts</code>。</p><p>你会得到如下权限错误。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-17.06.31-1.png" class="kg-image" alt="app.ts" width="600" height="400" loading="lazy"></figure><p>这是因为 Deno 默认情况下不允许访问文件系统。需要使用 <code>--allow-read=./</code> 命令授予对当前文件夹的访问权限：</p><pre><code class="language-bash">deno run --allow-read=./ https://deno.land/std/examples/cat.ts app.ts
</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-09-at-17.07.54-6.png" class="kg-image" alt="allow-read" width="600" height="400" loading="lazy"></figure><h2 id="deno-express-hapi-koa-">Deno 是否有 Express/Hapi/Koa/*</h2><p>当然有。可以看看下方这些库。</p><ul><li><a href="https://github.com/drashland/deno-drash">deno-drash</a></li><li><a href="https://github.com/NMathar/deno-express">deno-express</a></li><li><a href="https://github.com/oakserver/oak">oak</a></li><li><a href="https://github.com/sholladay/pogo">pogo</a></li><li><a href="https://github.com/keroxp/servest">servest</a></li></ul><h2 id="-oak-rest-api">示例：使用 Oak 构建 REST-API</h2><p>我想在这里做一个简单的 Demo 实战，介绍一下如何使用 Oak 框架构建<code>REST API</code>。Oak 很有意思，因为它的灵感来自于 Koa，一个流行的 Node.js 中间件。正因为如此，如果你以前用过 Koa 的话，会很快熟悉 Oak。</p><p>我们要构建的 API 示例也非常简单。</p><p>我们的服务器将在内存中存储一个带有名字和年龄的旺柴的列表。</p><p>我们的需求是：</p><ul><li>添加旺柴；</li><li>列出旺柴；</li><li>获取有关特定旺柴的详细信息；</li><li>从名单上删除一只旺柴；</li><li>更新旺柴的年龄；</li></ul><p>我们将使用 TypeScript 进行此操作，但是没有什么可以阻止你使用 JavaScript 编写 API——你只需要删除下方 TypeScript 文件中所有有关类型描述的代码并将文件名后缀改为 <code>.js</code>。</p><p>创建一个 <code>app.ts</code> 文件。</p><p>让我们开始从 Oak 导入 <code>Application</code> 和 <code>Router</code> 对象：</p><pre><code class="language-ts">import { Application, Router } from "https://deno.land/x/oak/mod.ts";
</code></pre><p>然后我们得到环境变量 <code>PORT</code> 和 <code>HOST</code>:</p><pre><code class="language-ts">const env = Deno.env.toObject();
const PORT = env.PORT || 4000;
const HOST = env.HOST || "127.0.0.1";
</code></pre><p>默认情况下，我们的应用程序将在 <code>localhost：4000</code> 上运行。</p><p>现在，我们创建 <code>Oak</code> 应用程序并启动它：</p><pre><code class="language-ts">const router = new Router();

const app = new Application();

app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Listening on port ${PORT}...`);

await app.listen(`${HOST}:${PORT}`);
</code></pre><p>现在，应用程序应该可以正常编译了。</p><pre><code class="language-bash">deno run --allow-env --allow-net app.ts
</code></pre><p>然后 Deno 将下载依赖项：</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-10-at-16.31.11.jpg" class="kg-image" alt="Deno" width="600" height="400" loading="lazy"></figure><p>这时程序监听在 <code>4000</code> 端口上。</p><p>下次运行该命令时，Deno 会跳过安装部分，因为这些包已经被缓存了。</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-10-at-16.32.40.png" class="kg-image" alt="Deno" width="600" height="400" loading="lazy"></figure><p>在文件的顶部，让我们定义一个旺柴的接口，然后我们声明一个初始的 <code>Dogs</code> 数组 <code>Dog</code> 对象。</p><pre><code class="language-ts">interface Dog {
  name: string;
  age: number;
}

let dogs: Array&lt;Dog&gt; = [
  {
    name: "Roger",
    age: 8,
  },
  {
    name: "Syd",
    age: 7,
  },
];
</code></pre><p>现在，让我们来实现具体 API。</p><p>我们已经准备好了一切。在你创建了路由器之后，让我们添加一些函数，这些函数将在任何时候触发这些路由中的一个端点时被调用。</p><pre><code class="language-ts">const router = new Router();

router
  .get("/dogs", getDogs)
  .get("/dogs/:name", getDog)
  .post("/dogs", addDog)
  .put("/dogs/:name", updateDog)
  .delete("/dogs/:name", removeDog);
</code></pre><p>看到了吗？我们的 API 定义是：</p><ul><li><code>GET /dogs</code></li><li><code>GET /dogs/:name</code></li><li><code>POST /dogs</code></li><li><code>PUT /dogs/:name</code></li><li><code>DELETE /dogs/:name</code></li></ul><p>让我们开始一一实现。</p><p>从开始 <code>GET /dogs</code>，它将返回所有旺柴的列表：</p><pre><code class="language-ts">export const getDogs = ({ response }: { response: any }) =&gt; {
  response.body = dogs;
};
</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-10-at-16.47.41.png" class="kg-image" alt="getDogs" width="600" height="400" loading="lazy"></figure><p>接下来，我们就来看看如何通过名字来检索旺柴。</p><pre><code class="language-ts">export const getDog = ({
  params,
  response,
}: {
  params: {
    name: string;
  };
  response: any;
}) =&gt; {
  const dog = dogs.filter((dog) =&gt; dog.name === params.name);
  if (dog.length) {
    response.status = 200;
    response.body = dog[0];
    return;
  }

  response.status = 400;
  response.body = { msg: `Cannot find dog ${params.name}` };
};
</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-10-at-16.47.53.png" class="kg-image" alt="getDog" width="600" height="400" loading="lazy"></figure><p>这是我们添加一个新的旺柴的方法：</p><pre><code class="language-ts">export const addDog = async ({
  request,
  response,
}: {
  request: any;
  response: any;
}) =&gt; {
  const body = await request.body();
  const dog: Dog = body.value;
  dogs.push(dog);

  response.body = { msg: "OK" };
  response.status = 200;
};
</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-10-at-16.48.02.png" class="kg-image" alt="addDog" width="600" height="400" loading="lazy"></figure><p>注意，我现在使用 <code>const body = await request.body()</code> 来获取正文的内容，因为 <code>name</code> 和 <code>age</code> 值是以 JSON 的形式传递的。</p><p>这是我们更新旺柴的年龄的方法：</p><pre><code class="language-ts">export const updateDog = async ({
  params,
  request,
  response,
}: {
  params: {
    name: string;
  };
  request: any;
  response: any;
}) =&gt; {
  const temp = dogs.filter((existingDog) =&gt; existingDog.name === params.name);
  const body = await request.body();
  const { age }: { age: number } = body.value;

  if (temp.length) {
    temp[0].age = age;
    response.status = 200;
    response.body = { msg: "OK" };
    return;
  }

  response.status = 400;
  response.body = { msg: `Cannot find dog ${params.name}` };
};
</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-10-at-16.48.11.png" class="kg-image" alt="updateDog" width="600" height="400" loading="lazy"></figure><p>这是我们如何从列表中删除旺柴的方法：</p><pre><code class="language-ts">export const removeDog = ({
  params,
  response,
}: {
  params: {
    name: string;
  };
  response: any;
}) =&gt; {
  const lengthBefore = dogs.length;
  dogs = dogs.filter((dog) =&gt; dog.name !== params.name);

  if (dogs.length === lengthBefore) {
    response.status = 400;
    response.body = { msg: `Cannot find dog ${params.name}` };
    return;
  }

  response.body = { msg: "OK" };
  response.status = 200;
};
</code></pre><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/05/Screen-Shot-2020-05-10-at-16.48.32.png" class="kg-image" alt="removeDog" width="600" height="400" loading="lazy"></figure><p>这是完整的示例代码：</p><pre><code class="language-ts">import { Application, Router } from "https://deno.land/x/oak/mod.ts";

const env = Deno.env.toObject();
const PORT = env.PORT || 4000;
const HOST = env.HOST || "127.0.0.1";

interface Dog {
  name: string;
  age: number;
}

let dogs: Array&lt;Dog&gt; = [
  {
    name: "Roger",
    age: 8,
  },
  {
    name: "Syd",
    age: 7,
  },
];

export const getDogs = ({ response }: { response: any }) =&gt; {
  response.body = dogs;
};

export const getDog = ({
  params,
  response,
}: {
  params: {
    name: string;
  };
  response: any;
}) =&gt; {
  const dog = dogs.filter((dog) =&gt; dog.name === params.name);
  if (dog.length) {
    response.status = 200;
    response.body = dog[0];
    return;
  }

  response.status = 400;
  response.body = { msg: `Cannot find dog ${params.name}` };
};

export const addDog = async ({
  request,
  response,
}: {
  request: any;
  response: any;
}) =&gt; {
  const body = await request.body();
  const { name, age }: { name: string; age: number } = body.value;
  dogs.push({
    name: name,
    age: age,
  });

  response.body = { msg: "OK" };
  response.status = 200;
};

export const updateDog = async ({
  params,
  request,
  response,
}: {
  params: {
    name: string;
  };
  request: any;
  response: any;
}) =&gt; {
  const temp = dogs.filter((existingDog) =&gt; existingDog.name === params.name);
  const body = await request.body();
  const { age }: { age: number } = body.value;

  if (temp.length) {
    temp[0].age = age;
    response.status = 200;
    response.body = { msg: "OK" };
    return;
  }

  response.status = 400;
  response.body = { msg: `Cannot find dog ${params.name}` };
};

export const removeDog = ({
  params,
  response,
}: {
  params: {
    name: string;
  };
  response: any;
}) =&gt; {
  const lengthBefore = dogs.length;
  dogs = dogs.filter((dog) =&gt; dog.name !== params.name);

  if (dogs.length === lengthBefore) {
    response.status = 400;
    response.body = { msg: `Cannot find dog ${params.name}` };
    return;
  }

  response.body = { msg: "OK" };
  response.status = 200;
};

const router = new Router();
router
  .get("/dogs", getDogs)
  .get("/dogs/:name", getDog)
  .post("/dogs", addDog)
  .put("/dogs/:name", updateDog)
  .delete("/dogs/:name", removeDog);

const app = new Application();

app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Listening on port ${PORT}...`);

await app.listen(`${HOST}:${PORT}`);
</code></pre><h2 id="--4">更多内容</h2><p>Deno 官方网站为 <a href="https://deno.land">https://deno.land</a>。</p><p>API 文档位于 <a href="https://doc.deno.land">https://doc.deno.land</a> 和 <a href="https://deno.land/typedoc/index.html">https://deno.land/typedoc/index.html</a> 中。</p><p>一份 Awesome Deno 资源清单 <a href="https://github.com/denolib/awesome-deno">https://github.com/denolib/awesome-deno</a>。</p><p>（译者注：中文的 Awesome Deno 清单由译者持续维护中，可以访问这里：<a href="https://github.com/hylerrix/awesome-deno-cn">Awesome Deno 资源全图谱</a>。）</p><h2 id="--5">花絮</h2><ul><li>Deno 提供了一个内置的 <code>fetch</code> 实现，该实现与浏览器中可用的匹配</li><li>Deno 正在进行与 Node.js stdlib 的兼容层</li></ul><h2 id="--6">结语</h2><p>我希望你喜欢这个 Deno 入门手册！</p><p>别忘了，<a href="https://flaviocopes.com/page/deno-handbook/">你可以在此处获取此 Deno 手册的 PDF / ePub / Mobi 版本。</a></p><p>原文：<a href="https://www.freecodecamp.org/news/the-deno-handbook/">The Deno Handbook: A TypeScript Runtime Tutorial with Code Examples</a>，作者：Flavio Copes，译者：hylerrix, Yunkou</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
