<?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[ TypeScript - 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[ TypeScript - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 11 Jun 2026 04:55:07 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/typescript/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 学习 TypeScript：开发者手册 ]]>
                </title>
                <description>
                    <![CDATA[ 这本手册将教你 TypeScript 的基础知识，包括它是什么、它为什么非常有用，以及它提供的关键功能。 TypeScript 是由微软的著名软件工程师 Anders Hejlsberg 创建的，他同时也因为在 C# 和 Delphi 上的贡献而广为人知。TypeScript 的设计目的是通过添加静态类型来增强 JavaScript，使构建和维护大规模应用程序变得更容易。 我们将通过“使用 Vite 将 TypeScript 整合进 React 项目”这件事开始。然后你会学习到诸如类型注释、类型推论、如何处理对象和数组等关键概念。 之后，我们将探讨一些高级的主题，例如联合类型和 any 类型、只读属性、具有特定参数和返回类型的函数、用于灵活和可重用代码的泛型，以及类型别名和接口的不同角色。 我将通过这本手册提供一些详细的示例和解释，帮助你全面理解 TypeScript 的特性是如何改善 JavaScript 开发的。 前提条件 我假设你已经熟悉 JavaScript 和 React 的基础知识。因此在这本手册中，我不会深入解释某些概念，例如在搭建项目时的文件结构。 目录   ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/learn-typescript-with-react-handbook/</link>
                <guid isPermaLink="false">681742a21084d10483d33a71</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tsukistar ]]>
                </dc:creator>
                <pubDate>Sun, 04 May 2025 11:16:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/05/----_20250504202035.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/learn-typescript-with-react-handbook/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Learn TypeScript – A Handbook for Developers</a>
      </p><!--kg-card-begin: markdown--><p>这本手册将教你 TypeScript 的基础知识，包括它是什么、它为什么非常有用，以及它提供的关键功能。</p>
<p>TypeScript 是由微软的著名软件工程师 Anders Hejlsberg 创建的，他同时也因为在 C# 和 Delphi 上的贡献而广为人知。TypeScript 的设计目的是通过添加静态类型来增强 JavaScript，使构建和维护大规模应用程序变得更容易。</p>
<p>我们将通过“使用 Vite 将 TypeScript 整合进 React 项目”这件事开始。然后你会学习到诸如类型注释、类型推论、如何处理对象和数组等关键概念。</p>
<p>之后，我们将探讨一些高级的主题，例如联合类型和 any 类型、只读属性、具有特定参数和返回类型的函数、用于灵活和可重用代码的泛型，以及类型别名和接口的不同角色。</p>
<p>我将通过这本手册提供一些详细的示例和解释，帮助你全面理解 TypeScript 的特性是如何改善 JavaScript 开发的。</p>
<h3 id="">前提条件</h3>
<p>我假设你已经熟悉 JavaScript 和 React 的基础知识。因此在这本手册中，我不会深入解释某些概念，例如在搭建项目时的文件结构。</p>
<h2 id="">目录</h2>
<ol>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-what-is-typescript">什么是 TypeScript？</a></p>
</li>
<li>
<p><a href="#heading-setting-up-the-project">项目设置</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-type-annotations-and-type-inference">类型注释和类型推论</a></p>
<ul>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-commonly-used-type-annotations">常用类型注释</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-type-inference">类型推论</a></p>
</li>
</ul>
</li>
<li>
<p><a href="#heading-the-union-and-any-types">联合类型和 Any 类型</a></p>
<ul>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-be-careful-when-using-any-in-typescript">在 TypeScript 中使用 any 时要小心</a></p>
</li>
<li>
<p><a href="#heading-unknown-as-a-safer-alternative-to-any-in-typescript">在 TypeScript 中使用 unknown 作为 any 的更安全替代方案</a></p>
</li>
</ul>
</li>
<li>
<p><a href="#heading-objects-in-typescript">TypeScript 中的对象</a></p>
<ul>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-the-problem-of-mutability">可变性问题</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-readonly-on-object-properties">对象上的只读属性声明</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-readonly-arrays">只读数组</a></p>
</li>
</ul>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-function-params-and-function-returns">函数参数和函数返回值</a></p>
<ul>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-the-risks-of-using-any">使用 any 的风险</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-use-explicit-types-for-parameters-and-return-values">为参数和返回值使用显式类型</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-using-unknown-as-a-safer-alternative-to-any-in-typescript">在 TypeScript 中使用 unknown 作为 any 的更安全替代方案</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-handling-optional-default-in-typescript">在 TypeScript 中处理可选和默认值</a></p>
</li>
</ul>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-rest-parameters">剩余参数</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-objects-as-parameters-in-typescript">TypeScript 中作为参数的对象</a></p>
</li>
<li>
<p><a href="#heading-type-aliases-in-typescript">TypeScript 中的类型别名</a></p>
<ul>
<li><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-what-is-an-intersection-type-in-typescript">在 TypeScript 中的交叉类型是什么？</a></li>
</ul>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-interfaces-in-typescript">TypeScript 中的接口</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-tuples-and-enums">元组和枚举</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-type-assertion-type-unknown-and-type-never-in-typescript">TypeScript 中的类型断言、类型 Unknown 和类型 Never</a></p>
</li>
<li>
<p><a href="#heading-generics-in-typescript">TypeScript 中的泛型</a></p>
</li>
<li>
<p><a href="https://chinese.freecodecamp.org/news/learn-typescript-with-react-handbook/heading-conclusion">写在最后</a></p>
</li>
</ol>
<h2 id="typescript">什么是 TypeScript？</h2>
<p>在深入了解 TypeScript 是什么之前，理解它为什么被创造出来是很重要的。JavaScript 是一种<strong>弱类型的语言</strong>，这意味着变量是在运行时被定义，变量的类型是在运行时被决定的。这种灵活性会导致意想不到的行为，尤其是在较大的项目中。</p>
<p>例如，你可能会意外地将一个错误类型的值分配给一个变量，这会引发一些你只有在执行代码时才会发现的错误。</p>
<p>下面是一个展示此问题的 JavaScript 的示例：</p>
<pre><code class="language-javascript">let userName = "Alice";
userName = 42; // 在赋值时没有错误，但这可能会破坏之后的代码。

function greetUser(name) {
  console.log("Hello, " + name.toUpperCase()); // 如果 `name` 不是字符串，会在运行时报错。
}

greetUser(userName); // 抛出异常，因为 `userName` 是数字而不是字符串，没有toUpperCase方法。
</code></pre>
<p>这类错误对于调试来说是一个巨大的挑战，因为它只在运行时出现。这样的错误使得大型项目更难维护且更容易出现 bug。</p>
<p>这就是 TypeScript 大显身手的地方。TypeScript 是一种通过添加<strong>静态类型</strong>构建在 JavaScript 之上的编程语言。静态类型意味着你可以显式指定变量、函数参数、返回值等的类型。与在运行时确定类型的<strong>动态类型</strong>不同，静态类型允许 TypeScript 在开发过程中提前捕获与类型相关的错误，提高代码质量并减少 bug 。</p>
<p>例如，下面是用 TypeScript 编写的同一代码：</p>
<pre><code class="language-typescript">let userName: string = "Alice";
// userName = 42; // 错误：类型“number”不能分配给类型“string”。

function greetUser(name: string): void {
  console.log("Hello, " + name.toUpperCase());
}

greetUser(userName); // 完美运行，因为 `userName` 类型正确。
</code></pre>
<h2 id="">项目设置</h2>
<p>我们将使用 <a href="https://vite.dev/guide/">Vite</a> 来设置我们的 TypeScript 项目。Vite 是一个旨在为 Web 项目提供更快速和更精简开发体验的现代构建工具。</p>
<p>第一步，运行以下命令创建一个支持 TypeScript 的新 Vite 项目：</p>
<pre><code class="language-shell">npm create vite@latest
</code></pre>
<p>然后为你的项目输入一个名称（你可以选择任何你喜欢的名称）。在接下来的步骤中，请仔细按照说明操作。</p>
<p>选择项目模板时，从可用选项中选择 “React”。我们将在本项目的开发中使用带有 TypeScript 的 React。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736769912180/e94dc70c-32e2-4f9f-89cc-d70d35e3a86e.png" alt="运行 create vite@latest 时的项目模板" width="600" height="400" loading="lazy"></p>
<p>当提示选择变体时，从可用选项中选择 “TypeScript”。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736770059262/d605726e-8d4f-4e73-8fb7-3854ce0b4e72.png" alt="在 create vite@latest 模板中的 TypeScript 变体选择" width="600" height="400" loading="lazy"></p>
<p>完成这些步骤后，系统会提示您切换到项目目录并运行 <code>npm install</code>。您可以选择任何代码编辑器。在本例中，我将使用 VS Code。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736771043869/e3f81f8b-19b7-4fb6-a439-2f24e3f55df5.png" alt="e3f81f8b-19b7-4fb6-a439-2f24e3f55df5" width="600" height="400" loading="lazy"></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736771426441/4c524149-4557-40bf-b50a-79400c6c3c91.png" alt="在 vscode 中查看项目概况并运行 npm install 以安装项目依赖" width="600" height="400" loading="lazy"></p>
<p>运行 <code>npm install</code> 后，运行 <code>npm run dev</code> 以在本地服务器上启动项目。一旦该项目启动并运行，我们就可以准备开始学习 TypeScript 概念。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736772238962/36f9523c-d316-43e3-ae05-e1ebfa9398f1.png" alt="运行项目中的 npm run dev 后的登录页面" width="600" height="400" loading="lazy"></p>
<p>首先，让我们创建第一个 TypeScript 文件，<code>test.ts</code>（您可以选择使用 <code>.ts</code> 或 <code>.tsx</code>）。在项目的 <code>src</code> 文件夹内创建 <code>test.ts</code> 文件，并添加以下代码将测试消息在控制台中打印为日志：</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">console.log('Testing our first TypeScript file');
</code></pre>
<p>要在控制台中查看此内容，请将 <code>test.ts</code> 文件导入到位于 <code>src</code> 文件夹中的 <code>main.tsx</code> 文件中。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736773745661/8492e586-7bc0-44a8-ac54-fb576119cdea.png" alt="突出显示 main.tsx 和 test.tsx 文件" width="600" height="400" loading="lazy"></p>
<p><code>main.tsx</code></p>
<pre><code class="language-typescript">import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
import "./test.ts";

createRoot(document.getElementById("root")!).render(
  &lt;StrictMode&gt;
    &lt;App /&gt;
  &lt;/StrictMode&gt;
);
</code></pre>
<p>要在控制台中查看日志，请确保将 <code>test.ts</code> 文件导入到位于 <code>src</code> 文件夹中的 <code>main.tsx</code> 文件中。之后，检查在本地服务器上运行的项目的控制台，您应该会看到显示的日志消息。</p>
<p><strong>Voilà!</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736774231199/9a270631-0639-40e0-84de-513143b4478d.png" alt="在 console.log 中的结果" width="600" height="400" loading="lazy"></p>
<p>现在，让我们正式开始学习 TypeScript。</p>
<h2 id="">类型注解和类型推论</h2>
<h3 id="">什么是类型注解？</h3>
<p>TypeScript 中的类型注解使您能够显式地指定变量的类型。这确保了变量仅被赋予指定类型的值，增强了类型安全性并使您的代码更易于维护。</p>
<p>要在 TypeScript 中定义类型注解，只需在变量名称后加上一个冒号 <code>:</code>，然后是所需的类型。这允许您指定变量将持有的类型，为您的代码添加一个清晰和精确的层次。例如，让我们在 <code>test.ts</code> 文件中指定一个类型为 <code>string</code> 的变量，以确保只分配字符串值：</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let name: string = 'Stephen';
</code></pre>
<p>在这个例子中，我们声明了一个变量 <code>name</code> 并指定它的类型必须是 <code>string</code>。TypeScript 现在将确保只能将字符串值分配给 <code>name</code>。</p>
<p>**📄 注意：**所有代码片段都在一个名为 <code>test.ts</code> 的文件中用于演示目的。您可以根据需要重命名文件或将片段复制到您的 TypeScript 项目中。我在本文中没有遵循一致的文件命名。</p>
<h3 id="">常用的类型注解</h3>
<p>以下是 TypeScript 中一些常用的类型注解：</p>
<ul>
<li>
<p><code>string</code>: 表示文本值。</p>
</li>
<li>
<p><code>number</code>: 表示数值（包括整数和浮点数）。</p>
</li>
<li>
<p><code>boolean</code>: 表示值为 <code>true</code> 或 <code>false</code>。</p>
</li>
<li>
<p><code>any</code>: 一种允许将任何值分配给变量的回退类型，禁用类型检查。</p>
</li>
<li>
<p><code>void</code>: 通常用于不返回值的函数。</p>
</li>
<li>
<p><code>null</code> 和 <code>undefined</code>: 用于表示没有值。</p>
</li>
</ul>
<p>一旦您定义了带有类型注解的变量，TypeScript 会确保它只能持有指定类型的值。您还可以访问与该类型相关的方法。例如，如果您声明了一个字符串变量，TypeScript 就会提供所有字符串方法的访问支持，如 <code>.toUpperCase()</code>。</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let name: string = 'Stephen';  // 类型显式设置为字符串
name = 'John';  // 这没有问题，因为它仍然是一个字符串

// 访问字符串方法
console.log(name.toUpperCase());  // 输出: JOHN
</code></pre>
<p>在这里，变量 <code>name</code> 被重新分配了一个新字符串值 <code>'John'</code>。由于类型仍然是 <code>string</code>，您可以毫无问题地使用字符串方法，比如 <code>.toUpperCase()</code>。</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let numbers: number[] = [1, 2, 3];  // 类型显式设置为数字数组
numbers.push(4);  // 可以的，因为 4 是一个数字

// 访问数组方法
console.log(numbers.length);  // 输出: 4
</code></pre>
<p>在这个例子中，<code>numbers</code> 是一个数字数组。你可以安全地使用数组方法，比如 <code>.push()</code> 和 <code>.length</code>，这些都是对数字数组有效的操作。</p>
<p>如果你尝试将变量重新赋值为不兼容类型的值，TypeScript 会在开发时立即捕获错误，甚至在代码运行之前。</p>
<p>例如：</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let name: string = 'Stephen';
name = 2;  // 错误: 类型 '2' 不能赋值给类型 'string'
</code></pre>
<p>在这里，你试图将一个数字 (<code>2</code>) 赋给一个先前声明为字符串的变量。TypeScript 会立即抛出错误，指出不能将一个数字赋给字符串变量。</p>
<p>同样地，对于一个数组：</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let numbers: number[] = [1, 2, 3];
numbers = 'Hello';  // 错误: 类型 'string' 不能赋值给类型 'number[]'
</code></pre>
<p>在这里，你试图将一个字符串 (<code>'Hello'</code>) 赋给一个先前声明为数字数组的变量。TypeScript 会捕捉到这个错误并强调类型不匹配。</p>
<p>尝试不同的类型来体验 TypeScript 如何执行类型安全。例如，在你的数组和变量中尝试使用布尔值、数字或其他类型。</p>
<p>既然你已经了解了类型注释如何与字符串和数组配合工作，现在是时候尝试其他类型了。TypeScript 允许你定义具有各种类型的数组和变量，从而确保代码的类型安全性。尝试创建具有其他数据类型的数组，例如 <code>boolean</code>、<code>number</code>。</p>
<h4 id="">示例: 布尔数组</h4>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let booleanArray: Array&lt;boolean&gt; = [true, false, true];

// 访问数组方法
console.log(booleanArray.length);  // 输出: 3
</code></pre>
<p>在这个例子中，数组 <code>booleanArray</code> 明确声明为仅包含 <code>boolean</code> 值。尝试添加 <code>string</code> 或 <code>number</code> 元素，看看 TypeScript 如何捕获类型错误。</p>
<h4 id="">示例: 数字数组</h4>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let numberArray: Array&lt;number&gt; = [1, 2, 3];

// 访问数组方法
console.log(numberArray[0] * 2);  // 输出: 2
</code></pre>
<p>欢迎你试验这些例子，并观察 TypeScript 如何提供强大的类型安全性和实时捕获错误。探索得越多，你就越能理解如何利用 TypeScript 的类型系统编写更整洁、更可靠的代码。</p>
<h3 id="">什么是类型推论？</h3>
<p>TypeScript 中的类型推论是一种强大的功能，它允许 TypeScript 编译器根据赋给变量的值自动确定变量的类型。TypeScript 被设计得足够智能，以在许多情况下推断类型，从而减少显式类型注释的需求。这增强了代码的简洁性，同时保持了类型安全性的优点。</p>
<p>通过类型推论，TypeScript 可以通过分析赋给变量的值来预测变量的类型，确保即使不手动指定类型，也能获得类型检查的所有优势。</p>
<h5 id="1"><strong>示例 1</strong>：推断的字符串类型</h5>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let message = "Hello, TypeScript!";  // TypeScript 推断 'message' 的类型为字符串
console.log(message.toUpperCase());  // 输出: HELLO, TYPESCRIPT!
</code></pre>
<p>在这个例子中，TypeScript 自动推断 <code>message</code> 的类型为 <code>string</code>，因为赋值给它的值是一个字符串。</p>
<h5 id="2"><strong>示例 2</strong>：推断的数字类型</h5>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let count = 42;  // TypeScript 推断 'count' 的类型为数字
console.log(count + 8);  // 输出: 50
</code></pre>
<p>这里，TypeScript 根据值 <code>42</code> 推断 <code>count</code> 的类型为 <code>number</code>，你可以在它上面执行算术操作而不会出现类型错误。</p>
<h5 id="3"><strong>示例 3:</strong> 推断的数组类型</h5>
<p><code>test.ts</code></p>
<pre><code>let numbers = [1, 2, 3];  // TypeScript 推断 'numbers' 为数字数组 (number[])
console.log(numbers.length);  // 输出: 3
</code></pre>
<p>在这种情况下，TypeScript 推断 <code>numbers</code> 为类型 <code>number[]</code> 的数组，因为数组包含数字。</p>
<h4 id=""><strong>不正确的示例:</strong></h4>
<h5 id="4"><strong>示例 4</strong>：类型不匹配的赋值</h5>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let count = 42;  // TypeScript 推断 'count' 的类型为数字
count = "Not a number";  // 错误: 类型 'string' 不能赋值给类型 'number'
</code></pre>
<p>尽管 TypeScript 推断 <code>count</code> 是一个数字，但尝试将一个 <code>string</code> 赋给它会导致一个错误。TypeScript 抓住了这是一个类型不匹配，因为 <code>count</code> 最初被推断为 <code>number</code>。</p>
<h5 id="5"><strong>示例 5:</strong> 推断为混合类型数组</h5>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let mixedArray = [1, "apple", true];  // TypeScript 推断 'mixedArray' 为 (string | number | boolean)[]
console.log(mixedArray[0].toFixed(2));  // 错误: 属性 'toFixed' 不存在于类型 'string | boolean' 上。
</code></pre>
<p>在这个例子中，TypeScript 推断 <code>mixedArray</code> 是一个包含多种类型的数组 (<code>string | number | boolean</code>)。虽然这是允许的，但对元素使用类似 <code>.toFixed()</code> 的方法可能会导致错误，因为并非所有数组元素都支持该方法（例如，<code>boolean</code> 和 <code>string</code> 没有 <code>.toFixed()</code>）。</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let price = 99.99;  // TypeScript 推断 'price' 为一个数字
price = "Free";  // 错误: 不能将类型 'string' 分配给类型 'number'
</code></pre>
<p>在这里，TypeScript 推断 <code>price</code> 是一个 <code>number</code>，但是尝试将其重新赋值为一个 <code>string</code> 会导致类型错误，从而确保变量维持其推断的类型。</p>
<h2 id="any">联合类型和 Any 类型</h2>
<p>在之前的例子中，我们使用了混合类型。现在，让我们正确定义这些概念，并通过各种例子来扩展它们：</p>
<h3 id=""><strong>什么是联合类型？</strong></h3>
<p>联合类型允许变量或参数拥有多种特定类型，提供灵活性的同时保持类型安全性。你可以使用管道符 (<code>|</code>) 来定义一个联合类型。</p>
<p><strong>简单联合类型：</strong></p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let value: string | number;

value = "Hello";  // ✅ 正确
console.log(value.toUpperCase());  // 输出: HELLO

value = 42;  // ✅ 正确
console.log(value + 8);  // 输出: 50

value = true;  // ❌ 错误: 不能将类型 'boolean' 分配给类型 'string | number'.
</code></pre>
<p>在这个例子中，<code>value</code> 可以是字符串或数字。任何其他类型的赋值都会导致类型错误。</p>
<p><strong>函数参数中的联合类型：</strong></p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">function printId(id: string | number): void {
  console.log(`Your ID is: ${id}`);
}

printId(12345);      // ✅ 正确
printId("abc123");   // ✅ 正确
printId(true);       // ❌ 错误: 不能将类型 'boolean' 分配给类型 'string | number'.
</code></pre>
<p>在这里，<code>id</code> 参数只能接受 <code>string</code> 或 <code>number</code>，确保了类型安全性并同时提供了灵活性。</p>
<p><strong>自定义联合类型：</strong></p>
<p>你可以使用 <code>type</code> 关键字创建自定义类型，以提高可读性和可重用性。</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">type ID = string | number;

function getUser(id: ID): void {
  console.log(`Fetching user with ID: ${id}`);
}

getUser(12345);      // ✅ 正确
getUser("abc123");   // ✅ 正确
getUser(true);       // ❌ 错误: 不能将类型 'boolean' 分配给类型 'string | number'.
</code></pre>
<h3 id="any"><strong>什么是</strong> <code>any</code> 类型？</h3>
<p><code>any</code> 类型是 TypeScript 中最灵活的类型。它允许变量持有任何类型的值，并对该变量禁用类型检查。</p>
<p><code>any</code> 类型牺牲了类型安全以获取最大灵活性。在你不确定类型或者处理动态数据时，这非常有用。</p>
<h5 id="1any"><strong>例子 1</strong>：Any 类型数组</h5>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let mixedArray: any[] = [1, "apple", true];

console.log(mixedArray[0]);  // 输出: 1
console.log(mixedArray[1].toUpperCase());  // 输出: APPLE
console.log(mixedArray[2]);  // 输出: true
</code></pre>
<p>在这里，<code>mixedArray</code> 可以包含任何类型的元素而不会触发类型错误。</p>
<h4 id="any"><strong>何时使用联合类型与</strong> <code>any</code>？</h4>
<ul>
<li>
<p><strong>联合类型</strong>：当可能的值已知或限制为几种特定类型时使用联合类型。它提供类型安全并避免了运行时错误。</p>
</li>
<li>
<p><code>any</code> <strong>类型</strong>：作为最后的手段在类型未知或动态时使用 <code>any</code>。</p>
</li>
</ul>
<p>但请记住，过度使用 <code>any</code> 会使 TypeScript 的类型系统的优势丧失。通过谨慎地在联合类型和 <code>any</code> 之间进行选择，你可以编写既灵活又类型安全的 TypeScript 代码。</p>
<h3 id="typescriptany"><strong>在 TypeScript 中使用</strong> <code>any</code> <strong>时要小心</strong></h3>
<p>TypeScript 中的 <code>any</code> 类型是一个强大而又冒险的特性。虽然这种灵活性有时会很有用，但它往往会导致 TypeScript 无法在编译时捕获的意外行为或错误。</p>
<p>让我们通过一个例子来理解这种潜在的陷阱。</p>
<p>这是一个展示滥用any类型带来风险的函数：</p>
<pre><code class="language-typescript">function combineValues(value: any) {
  let anotherValue: number = 10;

  return value + anotherValue;
}

const result = combineValues(5); // 这里没有错误。
const anotherResult = result;

// 试图调用 `anotherResult` 上的方法
anotherResult.someUndefinedMethod(); // 没有编译时错误！
</code></pre>
<p>运行这段代码的时候发生了什么？</p>
<p>首先，我们没有使用 <code>any</code> 时的类型检查。参数 <code>value</code> 是 <code>any</code> 类型，这意味着它可以持有任何值：字符串、数字、对象等等。TypeScript 会跳过对 <code>value</code> 的类型检查。</p>
<p>其次，返回值假设为 <code>any</code>。由于 <code>value</code> 是 <code>any</code>，因此返回类型也被推论为 <code>any</code>。</p>
<p>第三，调用未定义方法时没有错误。函数调用后，<code>anotherResult</code> 也被视为 <code>any</code>。TypeScript 允许在 <code>any</code> 类型的变量上调用任何方法（甚至是不存在的方法）而不报错。在这种情况下，<code>someUndefinedMethod</code> 并不存在，但 TypeScript 不会发出警告。</p>
<h4 id="any"><strong>使用</strong> <code>any</code> <strong>的风险</strong></h4>
<ol>
<li>
<p><strong>类型安全的丧失</strong>：你会失去 TypeScript 类型系统的好处，比如编译时错误检查。潜在的运行时错误在开发过程中可能会被忽视。</p>
</li>
<li>
<p><strong>意外的行为</strong>：函数可能会接受意外的输入（例如：字符串、数组或对象），导致结果不正确或崩溃。</p>
</li>
<li>
<p><strong>调试复杂性</strong>：由于类型未被强制执行，由不正确类型引发的问题的调试变得更加复杂。</p>
</li>
</ol>
<h3 id=""><strong>如何解决</strong></h3>
<h4 id=""><strong>对参数和返回值使用显式类型</strong></h4>
<p>这是一个具有正确类型注释的改进版本：</p>
<pre><code class="language-typescript">function combineValues(value: number): number {
  let anotherValue: number = 10;
  return value + anotherValue;
}
const result = combineValues(5);
// result.someUndefinedMethod(); // 错误：类型 “number” 上不存在属性 “someUndefinedMethod”。
</code></pre>
<ol>
<li>
<p><strong>参数类型</strong>: 该函数现在明确要求 <code>value</code> 参数为 <code>number</code> 类型。</p>
</li>
<li>
<p><strong>返回类型</strong>: 返回类型被声明为 <code>number</code>，确保只返回数字。</p>
</li>
</ol>
<p>这确保了如果您尝试传递无效类型或调用不存在的方法，TypeScript 将会抛出错误。</p>
<h4 id=""><strong>要点</strong></h4>
<ul>
<li>
<p><code>any</code> 类型禁用了 TypeScript 的类型检查，使您的代码容易出现运行时错误。</p>
</li>
<li>
<p>尽可能避免使用 <code>any</code>。在类型无法预先确定的情况下，应当改用显式类型声明或更严格的替代方案（例如 <code>unknown</code> 类型）。</p>
</li>
<li>
<p>明确的类型通过利用 TypeScript 的编译时检查，增强了代码的清晰度、可维护性和可靠性。</p>
</li>
</ul>
<p>如果您因为不清楚类型而有使用 <code>any</code> 的冲动，考虑重构您的代码或结合使用类型守卫与 <code>unknown</code> 以获得更好的安全性。</p>
<h3 id="typescriptunknown">在 TypeScript 中使用 <code>unknown</code> 作为更安全的替代方案</h3>
<p>TypeScript 中的 <code>unknown</code> 类型是 <code>any</code> 的更严格且更安全的替代方案。虽然 <code>any</code> 和 <code>unknown</code> 都可以容纳任意类型的值，但 <code>unknown</code> 要求您在使用值之前执行类型检查。这在提供灵活性的同时确保了更高的类型安全性。</p>
<pre><code class="language-typescript">function processValue(input: unknown): string {
  if (typeof input === 'string') {
    return `这个值是一个字符串：${input}`;
  } else if (typeof input === 'number') {
    return `这个值是一个数字：${input}`;
  } else {
    return '这个值是未知类型';
  }
}

console.log(processValue('Hello, TypeScript!')); // 这个值是一个字符串：Hello, TypeScript!
console.log(processValue(42)); // 这个值是一个数字：42
console.log(processValue(true)); // 这个值是未知类型
</code></pre>
<p>使用 <code>unknown</code> 而不是 <code>any</code> 有几个好处：</p>
<ol>
<li>
<p><strong>类型安全处理</strong>: 与 <code>any</code> 不同，<code>unknown</code> 迫使您在使用变量的值之前进行类型检查。这防止了在意外类型上执行无效操作所引发的运行时错误。</p>
</li>
<li>
<p><strong>显式类型缩小</strong>: TypeScript 要求您先通过类型守卫（<code>typeof</code>、<code>instanceof</code> 等）将 <code>unknown</code> 缩小到特定类型（如 <code>string</code>、<code>number</code>）后，才能访问其属性或方法。</p>
</li>
<li>
<p><strong>增强代码清晰度</strong>: 通过使用 <code>unknown</code>，您向其他开发人员表明类型是不确定的，必须在使用之前检查。</p>
</li>
</ol>
<h3 id="anyvsunknown"><strong>主要区别:</strong> <code>any</code> vs. <code>unknown</code></h3>
<p>| <strong>特性</strong> | <code>any</code> | <code>unknown</code> |<br>
| 类型检查 | 无类型检查 | 使用前需类型检查 |<br>
| 灵活性 | 可直接使用 | 必须先缩小类型 |<br>
| 常见用例 | 快速修复（不推荐） | 安全处理不确定类型 |</p>
<p>总结一下，每当您处理不确定类型的值时，请使用 <code>unknown</code> 而不是 <code>any</code>。它有助于维护类型安全并减少错误风险。同时尽量避免使用 <code>any</code>，因为它会绕过 TypeScript 的安全特性。</p>
<h2 id="typescript">TypeScript 中的对象</h2>
<p>在 TypeScript 中，对象是属性的集合，每个属性都有一个名称（键）和一个值。TypeScript 允许我们为这些属性定义类型，以确保对象符合特定结构。</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let car = { car: 'Toyota', brand: 2024 };
console.log(car);
</code></pre>
<p>这段代码可以正常工作，因为 TypeScript 根据提供的值自动推断出 <code>car</code> 和 <code>brand</code> 的类型。</p>
<h3 id=""><strong>显式对象类型</strong></h3>
<p>当我们需要显式定义对象的结构时，可以使用内联类型注解。这种方式能明确指定每个属性应有的类型，例如：</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let carOne: { car: string; brand: number } = { car: 'Evil Spirit', brand: 2025 };
console.log(carOne);
</code></pre>
<p>这确保 <code>carOne</code> 始终拥有一个类型为 <code>string</code> 的 <code>car</code> 属性和一个类型为 <code>number</code> 的 <code>brand</code> 属性。</p>
<p>假设我们想为 <code>carOne</code> 添加一个 <code>color</code> 属性：</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">let carOne: { car: string; brand: number } = { car: 'Evil Spirit', brand: 2025, color: 'Black' };
</code></pre>
<p>上面的代码会显示红线，因为 <code>color</code> 不是已定义类型 <code>{ car: string; brand: number }</code> 的一部分。错误可能会像这样：</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736933755272/8a3d48dd-3ae0-4769-9e13-fa1f6ca37331.png" alt="8a3d48dd-3ae0-4769-9e13-fa1f6ca37331" width="600" height="400" loading="lazy"></p>
<blockquote>
<p>类型 '{ car: string; brand: number; color: string; }' 无法分配给类型 '{ car: string; brand: number; }'。对象字面量只能指定已知属性，且 'color' 不存在于类型 '{ car: string; brand: number; }' 中。</p>
</blockquote>
<p>类似地，如果您试图将 <code>brand</code> 的类型更改为 <code>string</code>：</p>
<p><code>test.ts</code></p>
<pre><code class="language-typescript">carOne.brand = "2026";
</code></pre>
<p>您将收到另一个错误：</p>
<blockquote>
<p>类型 'string' 无法分配给类型 'number'。</p>
</blockquote>
<p>每次编写完整的对象类型可能会显得重复，尤其是对于拥有许多属性或在多个地方使用相同结构的对象。但不用担心——我会很快介绍<strong>类型别名</strong>，这将使定义和重用对象类型更简单。之后您将了解到如何使用类型别名简化对象类型并使您的代码更简洁。接下来，我们将探索如何在 React 中应用这些概念。</p>
<p>现在，我们要专注于理解基础知识以及 TypeScript 如何强制执行这种处理对象的模式。这就像是掀开引擎盖，窥探 TypeScript 幕后是如何运作的。</p>
<h3 id=""><strong>对象与数组</strong></h3>
<p>在 TypeScript 中，我们经常处理对象数组，其中每个对象都有特定的结构。TypeScript 可以帮助确保数组中的每个对象都符合预期的类型。</p>
<p>想象你在经营一家杂货店，并想要记录你的蔬菜库存。可以这样开始：</p>
<pre><code class="language-typescript">let tomato = { name: 'Tomato', price: 2 };
let potato = { name: 'Potato', price: 1 };
let carrot = { name: 'Carrot' };

let vegetables: { name: string; price: number }[] = [tomato, potato, carrot];
</code></pre>
<p>当 TypeScript 检查这段代码时，它会抛出一个错误，因为 <code>carrot</code> 没有 <code>price</code> 属性。<code>vegetables</code> 数组的每一项的预期类型是 <code>{ name: string; price: number }</code>。由于 <code>carrot</code> 缺少 <code>price</code>，TypeScript 会将其标记为错误。</p>
<blockquote>
<p>类型 '{ name: string; }' 不能赋值给类型 '{ name: string; price: number; }'。类型 '{ name: string; }' 中缺少属性 'price'，但在类型 '{ name: string; price: number; }' 中该属性是必需的。</p>
</blockquote>
<p>如果 <code>price</code> 并不总是已知或需要（例如，也许胡萝卜的价格正在谈判中），可以使 <code>price</code> 属性为可选。可以通过在属性名后添加一个 <code>?</code> 来实现这一点：</p>
<pre><code class="language-typescript">let vegetables: { name: string; price?: number }[] = [tomato, potato, carrot];
</code></pre>
<p>现在，TypeScript 知道 <code>price</code> 属性是可选的。这意味着在 <code>vegetables</code> 数组中的对象可以包含 <code>price</code> 或不包含 <code>price</code> 而不产生错误。</p>
<p>当一个属性是可选的时，TypeScript 允许它：</p>
<ol>
<li>
<p>以指定的类型存在。</p>
</li>
<li>
<p>完全不存在。</p>
</li>
</ol>
<p>这种灵活性消除了对于像 <code>carrot</code> 这样缺少 <code>price</code> 属性的对象的错误。</p>
<h3 id="readonly"><strong><code>readonly</code> 修饰符</strong></h3>
<p>在 TypeScript 中，<code>readonly</code> 修饰符是确保某些属性或整个对象保持不变的好方法。这在你想防止数据的意外更改时特别有用。</p>
<p>让我们继续用蔬菜商店的例子来看看 <code>readonly</code> 是如何工作的。</p>
<h4 id=""><strong>可变性的问题</strong></h4>
<p>假设我们有这样的设置：</p>
<pre><code class="language-typescript">let tomato = { name: 'Tomato', price: 2 };
let potato = { name: 'Potato', price: 1 };
let carrot = { name: 'Carrot' };

let vegetables: { name: string; price?: number }[] = [tomato, potato, carrot];
</code></pre>
<p>如果有人不小心尝试更改 <code>tomato</code> 对象的 <code>name</code> 或从 <code>vegetables</code> 数组中删除 <code>carrot</code> 对象，TypeScript 不会报错：</p>
<pre><code class="language-typescript">vegetables[0].name = 'Cucumber'; // 没有错误，但这可能是无意的！
vegetables.pop(); // 移除最后一个蔬菜，没有警告。
</code></pre>
<p>我们可以使用 <code>readonly</code> 来使这些对象和数组不可变，以确保它们的原始状态不能被更改。</p>
<h3 id="readonly"><strong>对象属性上的 Readonly</strong></h3>
<p>要使每个蔬菜的属性不可变，可以这样做：</p>
<pre><code class="language-typescript">let vegetables: { readonly name: string; readonly price?: number }[] = [
  { name: 'Tomato', price: 2 },
  { name: 'Potato', price: 1 },
  { name: 'Carrot' },
];
</code></pre>
<p>现在，如果你尝试更改任何蔬菜的 <code>name</code> 或 <code>price</code>，TypeScript 会抛出错误：</p>
<pre><code class="language-typescript">vegetables[0].name = 'Cucumber'; // 错误：不能分配给 'name'，因为它是一个只读属性。
</code></pre>
<h3 id="readonly"><strong>Readonly 数组</strong></h3>
<p>你还可以通过将整个 <code>vegetables</code> 数组声明为 <code>readonly</code> 来使其不可变：</p>
<pre><code class="language-typescript">let vegetables: readonly { name: string; price?: number }[] = [
  { name: 'Tomato', price: 2 },
  { name: 'Potato', price: 1 },
  { name: 'Carrot' },
];
</code></pre>
<p>这会阻止修改数组本身的操作，例如 <code>push</code>、<code>pop</code> 或 <code>splice</code>：</p>
<pre><code class="language-typescript">vegetables.push({ name: 'Onion', price: 3 }); // 错误：属性 'push' 在类型 'readonly { name: string; price?: number; }[]' 上不存在。
vegetables.pop(); // 错误：属性 'pop' 在类型 'readonly { name: string; price?: number; }[]' 上不存在。
</code></pre>
<h3 id="readonly"><strong>使用 <code>readonly</code> 的时机</strong></h3>
<ol>
<li>
<p><strong>不可变数据</strong>：当你希望对对象或数组实现不可变性时使用 <code>readonly</code>，特别是在数据应该保持不变的情况下（例如，配置、初始状态、常量）。</p>
</li>
<li>
<p><strong>防止错误</strong>：保护你的数据不被代码的其他部分无意中更改。</p>
</li>
</ol>
<h3 id=""><strong>完整示例</strong></h3>
<p>这是一个带有 <code>readonly</code> 的更新示例：</p>
<pre><code class="language-typescript">let vegetables: readonly { readonly name: string; readonly price?: number }[] = [
  { name: 'Tomato', price: 2 },
  { name: 'Potato', price: 1 },
  { name: 'Carrot' },
];

// 尝试修改数据
vegetables[0].name = 'Cucumber'; // 错误：不能分配给 'name'，因为它是一个只读属性。
vegetables.pop(); // 错误：方法 'pop' 在类型 'readonly { readonly name: string; readonly price?: number; }[]' 上不存在。

console.log(vegetables);
</code></pre>
<p>以下是对 readonly 的总结：</p>
<ul>
<li>
<p>属性上的 <code>readonly</code> 确保对象的各个字段不能被更改。</p>
</li>
<li>
<p>数组上的 <code>readonly</code> 使数组本身不可变，阻止诸如 <code>push</code> 和 <code>pop</code> 之类的操作。</p>
</li>
<li>
<p>将两者结合可以为数组中的对象提供完全的不可变性。</p>
</li>
</ul>
<p>通过使用 <code>readonly</code>，你可以创建更安全、更可预测的代码，减少由于无意更改导致的错误。</p>
<h2 id=""><strong>函数参数与函数返回值</strong></h2>
<p>在 TypeScript 中，函数允许您显式定义<strong>参数</strong>和<strong>返回类型</strong>。这确保函数按预期运行并避免运行时错误。让我们通过一个简单的例子来详细说明。</p>
<h3 id=""><strong>推断的返回类型</strong></h3>
<pre><code class="language-typescript">function arithmeticOp(price: number) {
  return price * 9;
}

const FP = arithmeticOp(2); // 结果是 18。
</code></pre>
<ol>
<li>
<p>参数 <code>price</code> 被显式定义为 <code>number</code>。</p>
</li>
<li>
<p>返回类型没有被显式声明，但 TypeScript <strong>推断</strong>它为 <code>number</code>，因为函数返回的是 <code>price * 9</code>，这是一个数值运算。</p>
</li>
</ol>
<p>TypeScript 足够聪明，可以根据返回语句推断函数的返回类型。在这种情况下，它正确地推断 <code>arithmeticOp</code> 返回一个 <code>number</code>。</p>
<h3 id=""><strong>显式返回类型</strong></h3>
<pre><code class="language-typescript">function arithmeticOp(price: number): number {
  return price * 9;
}

const FP = arithmeticOp(2); // 结果仍然是 18。
</code></pre>
<ol>
<li>
<p>函数通过语法 <code>functionName(parameters): returnType</code> 显式声明返回类型为 <code>number</code>。</p>
</li>
<li>
<p>这不会改变结果，但使函数声明更加清晰。</p>
</li>
</ol>
<p>那么为什么要使用显式返回类型呢？首先，这提高了代码的可读性，并确保未来的更改不会意外改变返回类型。其次，它相当于为其他开发者提供了文档说明。</p>
<h3 id=""><strong>返回类型不匹配</strong></h3>
<pre><code class="language-typescript">function arithmeticOp(price: number): number {
  if (hasDiscount) {
    return 'discount'; // 这里出错了！
  }
  return price * 9;
}

const FP = arithmeticOp(2);
</code></pre>
<p>在上面的代码中，返回类型被显式声明为 <code>number</code>。但函数尝试在某些情况下返回一个 <code>string</code>（<code>'discount'</code>）。这导致 TypeScript 抛出错误：</p>
<blockquote>
<p>类型 'string' 不能赋值给类型 'number'。</p>
</blockquote>
<p>这是因为 TypeScript 强制执行声明的返回类型。如果您声明一个函数返回 <code>number</code>，它<strong>必须始终</strong>返回一个 <code>number</code>，无论函数内的逻辑如何。</p>
<p>如果您希望函数返回多种类型（例如，<code>number</code> 或 <code>string</code>），请使用<strong>联合类型</strong>：</p>
<pre><code class="language-typescript">function arithmeticOp(price: number): number | string {
  if (hasDiscount) {
    return 'discount'; // 现在有效！
  }
  return price * 9;
}

const FP = arithmeticOp(2);
</code></pre>
<p>返回类型 <code>number | string</code> 告诉 TypeScript 函数可以返回 <code>number</code> 或 <code>string</code>。这解决了类型不匹配错误。</p>
<h4 id="">关键要点：</h4>
<ol>
<li>
<p>TypeScript 在未显式定义时<strong>推断</strong>返回类型，但为了清晰和可维护性，鼓励使用显式返回类型。</p>
</li>
<li>
<p>声明的返回类型确保函数仅返回指定类型的值。</p>
</li>
<li>
<p>类型不匹配，例如从预期返回 <code>number</code> 的函数返回 <code>string</code>，会导致 TypeScript 错误。</p>
</li>
<li>
<p>联合类型（<code>type1 | type2</code>）允许函数在需要时返回多种类型。</p>
</li>
</ol>
<h3 id="typescript"><strong>在 TypeScript 中处理可选参数和默认值</strong></h3>
<p>在使用 TypeScript 的函数时，指定参数的行为对于灵活性和防止运行时错误至关重要。让我们通过实际的例子来探讨如何有效处理可选参数和默认参数。</p>
<h3 id="1">示例 1：理解缺少参数的问题</h3>
<p>思考下面这个函数：</p>
<pre><code class="language-typescript">function calculateFinalScore(baseScore: number, deductions: number): number {
  return baseScore - deductions;
}

let scoreWithDeductions = calculateFinalScore(50, 10);
let scoreWithoutDeductions = calculateFinalScore(50); // 错误
</code></pre>
<p>对 <code>calculateFinalScore</code> 的第一次调用完全正常。但第二次调用会抛出 TypeScript 错误：</p>
<pre><code class="language-typescript">⚠ Error (TS2554) | 预期有2个参数，但只传入了1个。
Tutorial.ts(7, 47): 参数 'deductions' 没有提供。
</code></pre>
<p>这是因为 TypeScript 期望同时提供 <code>baseScore</code> 和 <code>deductions</code>，因为它们都是必需参数。如果省略 <code>deductions</code> 值，TypeScript 将不允许函数调用。</p>
<h3 id="2">示例 2：使用默认参数解决问题</h3>
<p>为了解决这个问题，我们可以为 <code>deductions</code> 参数定义一个默认值。默认参数在没有传递参数时提供回退值。</p>
<pre><code class="language-typescript">function calculateFinalScore(baseScore: number, deductions: number = 0): number {
  return baseScore - deductions;
}

let scoreWithDeductions = calculateFinalScore(50, 10); // 40
let scoreWithoutDeductions = calculateFinalScore(50);  // 50
</code></pre>
<p>在这个更新的例子中：</p>
<ul>
<li>
<p>如果没有显式提供 <code>deductions</code> 参数，其默认值将设为 <code>0</code>。</p>
</li>
<li>
<p>两次调用现在都可以正常执行且没有错误。</p>
</li>
</ul>
<h3 id="">为什么这种解决方案有效</h3>
<p>通过将 <code>deductions</code> 定义为默认参数，TypeScript 确保函数在调用时拥有执行所需的所有参数，即使某些参数在调用中被省略。这种方法增加了函数的灵活性，同时保持了类型安全。</p>
<p>当一个参数值是函数正常工作所必需的，请使用默认参数，这能确保它在被忽略时可以安全地有回退值。这种方法提高了代码清晰度并减少了运行时错误的可能性。</p>
<p>TypeScript中的剩余参数可以让你在不知道将会收到多少个参数的情况下处理多个参数。你可以传递任意多的参数，TypeScript将处理它们。对于输入数量不固定的情况，它们非常完美。</p>
<p>要使用剩余参数，你需要在参数名称前加上三个点（<code>...</code>），这些点会将所有额外的参数收集到一个数组中。</p>
<p>假设你想将多个单词组合成一个句子：</p>
<pre><code class="language-typescript">function joinWords(...words: string[]): string {
  return words.join(" ");
}

let sentence = joinWords("TypeScript", "makes", "coding", "fun");
console.log(sentence); // "TypeScript makes coding fun"
</code></pre>
<ul>
<li>
<p><code>...words</code> 将所有参数收集到一个数组中（<code>["TypeScript", "makes", "coding", "fun"]</code>）。</p>
</li>
<li>
<p><code>join</code> 方法将它们组合成一个用空格分隔的字符串。</p>
</li>
</ul>
<h3 id="">数字的剩余参数</h3>
<p>现在，假设你想累加多个数字：</p>
<pre><code class="language-typescript">function sumNumbers(...numbers: number[]): number {
  return numbers.reduce((total, num) =&gt; total + num, 0);
}

let total = sumNumbers(10, 20, 30);
console.log(total); // 60
</code></pre>
<ul>
<li>
<p><code>...numbers</code> 将所有数字收集到一个数组中（<code>[10, 20, 30]</code>）。</p>
</li>
<li>
<p><code>reduce</code> 方法将它们相加得到总和。</p>
</li>
</ul>
<p>我们也可以使用剩余参数将多个数组合并为一个：</p>
<pre><code class="language-typescript">function mergeArrays(...arrays: number[][]): number[] {
  return arrays.flat();
}

let combined = mergeArrays([1, 2], [3, 4], [5, 6]);
console.log(combined); // [1, 2, 3, 4, 5, 6]
</code></pre>
<ul>
<li>
<p><code>...arrays</code> 将每个参数作为数组收集到一个数组的数组中（<code>[[1, 2], [3, 4], [5, 6]]</code>）。</p>
</li>
<li>
<p><code>flat</code> 方法将它们合并为一个数组。</p>
</li>
</ul>
<p>剩余参数必须始终放在参数列表的最后。例如：</p>
<pre><code class="language-typescript">function example(a: string, ...others: number[]): void {
  console.log(a, others);
}
</code></pre>
<p>这确保所有剩余的参数都进入剩余参数。</p>
<h2 id="typescript">TypeScript中的对象作为参数</h2>
<p>在TypeScript中，函数可以接受对象作为参数。这在处理多个相关值时特别有用。</p>
<h3 id="">使用具有特定属性的对象</h3>
<p>这是一个接受具有<code>id</code>属性的对象并返回一个新对象的函数：</p>
<pre><code class="language-typescript">function createEmployee({ id }: { id: number }): { id: number; isActive: boolean } {
  return { id, isActive: id % 2 === 0 };
}

const firstEmployee = createEmployee({ id: 1 });
console.log(firstEmployee); // { id: 1, isActive: false }

const secondEmployee = createEmployee({ id: 2 });
console.log(secondEmployee); // { id: 2, isActive: true }
</code></pre>
<p>函数 <code>createEmployee</code>：</p>
<ul>
<li>
<p>接受具有单个属性<code>id</code>的对象作为参数。</p>
</li>
<li>
<p>返回一个具有两个属性的对象：<code>id</code> 和 <code>isActive</code>。</p>
</li>
</ul>
<p><code>isActive</code> 属性的值是通过检查 <code>id</code> 是否为偶数来确定的（<code>id % 2 === 0</code>）。</p>
<p>参数中使用了 <strong>解构</strong> 的语法（参见 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring">解构</a>）：</p>
<ul>
<li>返回值中的 <code>id</code> 属性直接提取自输入对象 <code>{ id }</code> 中的 <code>id</code> 属性。</li>
</ul>
<h3 id="">接受更复杂的对象</h3>
<p>现在，让我们看看接受具有多属性对象的函数：</p>
<pre><code class="language-typescript">function createStudent(student: { id: number; name: string }): void {
  console.log(`Welcome to the course, ${student.name}!`);
}

const newStudent = { id: 1, name: "John" };
createStudent(newStudent); // "Welcome to the course, John!"
</code></pre>
<p>函数 <code>createStudent</code>：</p>
<ul>
<li>
<p>接受一个具有两个属性的对象：<code>id</code> 和 <code>name</code>。</p>
</li>
<li>
<p>使用 <code>name</code> 属性记录欢迎信息。</p>
</li>
</ul>
<p><code>newStudent</code> 对象与函数期望的结构匹配，因此可以直接传递。</p>
<h3 id="">为什么使用对象作为参数？</h3>
<p>首先，使用对象作为参数的函数更易于阅读，尤其是在处理多个相关值时。此外，利用解构，你可以从对象中提取所需的属性，使代码更加简洁。最后，对象可以在多个函数之间复用，而无需每次都创建新对象。</p>
<h3 id="typescript">TypeScript中的多余属性检查</h3>
<p>在TypeScript中，多余属性检查有助于确保传递给函数的对象只能包含定义在函数参数类型中的属性。如果有额外的属性，TypeScript将引发错误。让我们通过简单的例子来看看它是如何工作的。</p>
<h4 id="1">1. 额外属性错误</h4>
<p>这是一个接受具有 <code>id</code> 和 <code>name</code> 对象的函数，但没有额外属性：</p>
<pre><code class="language-typescript">function createStudent(student: { id: number; name: string }): void {
  console.log(`Welcome, ${student.name}!`);
}

const newStudent = { id: 1, name: "John", age: 20 }; // 多余属性 'age'

createStudent(newStudent); // 错误: 'age' 是不被期望的
</code></pre>
<p>TypeScript 会报错，因为 <code>age</code> 属性不属于预期的对象结构。</p>
<h4 id="2">2. 修复错误</h4>
<p>要避免此错误，只需删除任何额外的属性：</p>
<pre><code class="language-typescript">const validStudent = { id: 1, name: "John" };
createStudent(validStudent); // 没有问题
</code></pre>
<p>这可行是因为对象只有预期的属性：<code>id</code> 和 <code>name</code>。</p>
<h4 id="3">3. 使用类型断言（不推荐）</h4>
<p>如果你确实需要传递一个带有额外属性的对象，可以使用<strong>类型断言</strong> 告诉 TypeScript 忽略多余的属性：</p>
<pre><code class="language-typescript">const studentWithExtras = { id: 1, name: "John", age: 20 };
createStudent(studentWithExtras as { id: number; name: string }); // 绕过该错误
</code></pre>
<p>虽然这样做可以工作，但最好是匹配预期的结构，而不是使用类型断言。</p>
<ul>
<li>
<p>TypeScript 期望对象与参数类型的精确结构匹配。</p>
</li>
<li>
<p>多余的属性会导致错误，以确保结构的正确性。</p>
</li>
<li>
<p>如果需要额外的属性，请修复对象或（谨慎）使用类型断言。</p>
</li>
</ul>
<p>针对作为函数参数的对象属性中多余属性的检查可以帮助保持代码安全，并确保只有正确的数据传递到函数中。</p>
<h2 id="typescript">TypeScript 中的类型别名</h2>
<p>TypeScript 中的<strong>类型别名</strong>本质上是现有类型的<strong>缩写</strong>或<strong>替代名称</strong>。它允许您为在代码中可能会使用的复杂类型或反复使用的类型定义一个更简单或更易读的名称。</p>
<p>使用类型别名不会创建新类型，而是为现有类型提供一个新的标识符。使用类型别名时，代码的原有功能逻辑不会改变——它只是使代码更具可读性和可重用性。</p>
<p>下面是使用类型别名前的示例：</p>
<pre><code class="language-typescript">function getUserInfo(user: { name: string; age: number; address: string }) {
  console.log(`User Info: 
    Name: ${user.name}, 
    Age: ${user.age}, 
    Address: ${user.address}`);
}

const user: UserInfo = { name: 'Alice', age: 30, address: '123 Main St' };

getUserInfo(user);
</code></pre>
<p>现在，让我们对函数参数使用类型别名，使代码更具可读性：</p>
<pre><code class="language-typescript">// 使用类型别名
type UserInfo = { name: string, age: number, address: string };

function getUserInfo(user: UserInfo) {
  console.log(`User Info: 
    Name: ${user.name}, 
    Age: ${user.age}, 
    Address: ${user.address}`);
}

const user: UserInfo = { name: 'Alice', age: 30, address: '123 Main St' };

getUserInfo(user);
</code></pre>
<p>在上面的示例中：</p>
<ul>
<li>
<p>使用类型别名前，我们在函数内分别定义了参数。</p>
</li>
<li>
<p>定义类型别名（<code>UserInfo</code>）后，我们在函数参数中使用它，使函数签名更简单，更具可读性。</p>
</li>
</ul>
<p>使用类型别名<strong>不会改变代码的功能</strong>。它只是通过使用别名，使处理代码变得更容易。别名作为复杂类型的可重用引用，如果 <code>UserInfo</code> 的结构发生变化，我们只需在一个地方更新它，从而使代码更易维护。</p>
<h3 id="">如何使用类型别名</h3>
<p>类型别名允许您为一种类型定义一个新名称。这个新名称可以表示基本类型、对象结构，甚至是类型的联合。主要好处是使您的代码更具可读性、可重用性，并防止错误。</p>
<p>您可以使用 <code>type</code> 关键字定义类型别名，后跟一个名称和类型结构。</p>
<pre><code class="language-typescript">type TypeName = TypeStructure;
</code></pre>
<p>例如，让我们为一个用户对象创建一个类型别名：</p>
<pre><code class="language-typescript">type User = {
  name: string;
  age: number;
}
</code></pre>
<p>这意味着 <code>User</code> 是一个期待具有两个属性的对象的类型：</p>
<ul>
<li>
<p><code>name</code> 应该是一个字符串。</p>
</li>
<li>
<p><code>age</code> 应该是一个数字。</p>
</li>
</ul>
<h3 id="">为什么使用类型别名？</h3>
<p>使用类型别名有几个原因。首先，类型别名显式定义了对象的结构，因此任何阅读代码的人都确切知道会得到什么。其次，您可以在代码中的任何地方重用 <code>User</code> 类型，而无需重复其结构。最后，TypeScript 将检查分配给 <code>User</code> 类型的任何对象是否具有必需属性以及正确的类型。</p>
<h4 id="">使用类型别名：</h4>
<pre><code class="language-typescript">type User = {
  name: string;
  age: number;
};

function getUserDetails(user: User): string {
  return `${user.name} (${user.age} years old)`;
}

const user: User = { name: "Alice", age: 30 };
console.log(getUserDetails(user)); // "Alice (30 years old)"
</code></pre>
<p>在这个示例中，我们定义了 <code>User</code> 类型别名，指定 <code>user</code> 对象必须有一个 <code>name</code> 类型为 <code>string</code> 和 <code>age</code> 类型为 <code>number</code>。</p>
<p>如果您试图分配一个不匹配此结构的对象，TypeScript 会捕获错误，如下所示：</p>
<pre><code class="language-typescript">// 这将导致 TypeScript 错误：
const invalidUser: User = { name: "Alice" }; // 缺少 'age' 属性
</code></pre>
<h3 id="typescript">什么是 TypeScript 中的<strong>交叉类型</strong>？</h3>
<p><strong>交叉类型</strong>是 TypeScript 中一个强大的功能，允许您将多种类型组合成一个。当您创建交叉类型时，生成的类型必须同时具备每个交叉类型的<strong>所有属性</strong>。</p>
<p>您可以组合任意数量的类型，生成的类型必须满足所有原始类型的每一种条件。</p>
<h4 id="">交叉类型的语法</h4>
<p>要定义一个交叉类型，您可以使用 <code>&amp;</code> 运算符来组合两个或更多的类型。</p>
<pre><code class="language-typescript">type TypeA &amp; TypeB;
</code></pre>
<h4 id="">交叉类型的示例</h4>
<p>假设您想为 <code>User</code> 类型扩展包含用户的地址。您可以使用<strong>交叉类型</strong>组合 <code>User</code> 和 <code>Address</code>，而不是修改原始 <code>User</code> 类型。</p>
<pre><code class="language-typescript">type Address = {
  city: string;
  country: string;
};

type UserWithAddress = User &amp; Address; // User 和 Address 的交叉
</code></pre>
<p>现在，<code>UserWithAddress</code> 将需要同时具备 <code>User</code> 和 <code>Address</code> 的属性。</p>
<h4 id="">在函数中使用交叉类型的示例</h4>
<p>以下是如何在函数中使用此方法：</p>
<pre><code class="language-typescript">type User = {
  name: string;
  age: number;
};

type Address = {
  city: string;
  country: string;
};

type UserWithAddress = User &amp; Address;

function getUserDetails(user: UserWithAddress): string {
  return `${user.name} (${user.age} years old), lives in ${user.city}, ${user.country}`;
}

const user: UserWithAddress = {
  name: "Alice",
  age: 30,
  city: "New York",
  country: "USA"
};

console.log(getUserDetails(user));
// 输出: "Alice (30 years old), lives in New York, USA"
</code></pre>
<p>在此示例中：</p>
<ul>
<li>
<p><code>UserWithAddress</code> 是一个交叉类型，这意味着 <code>user</code> 对象必须同时拥有 <code>User</code> 和 <code>Address</code> 的属性。</p>
</li>
<li>
<p>TypeScript 会检查对象中是否存在 <code>name</code> 和 <code>age</code>（来自 <code>User</code>），以及 <code>city</code> 和 <code>country</code>（来自 <code>Address</code>）。</p>
</li>
</ul>
<p>如果遗漏了这些属性中的任意一个，TypeScript 将显示错误。</p>
<pre><code>// 这会导致 TypeScript 错误：
const incompleteUser: UserWithAddress = {
  name: "Alice",
  age: 30,
  city: "New York"
}; // 缺少 'country'
</code></pre>
<h3 id="">为什么使用<strong>交叉类型</strong>？</h3>
<p>交叉类型在多个场景下非常有用。首先，它们允许你在不修改原有类型的情况下扩展其功能，从而让代码更具模块化和灵活性。当需要将多个不同的结构合并为一个时，例如将 <code>User</code> 与 <code>Address</code> 或 <code>OrderDetails</code> 合并，使用交叉类型也是非常有用的。而使用交叉类型时，你可以轻松地看到对象必须具有的所有必需属性。</p>
<h3 id="vs">类型别名 vs 交叉类型：</h3>
<p>| 特性 | 类型别名 | 交叉类型 |<br>
| <strong>定义</strong> | 定义单一类型。 | 将多个类型合并为一个类型。 |<br>
| <strong>使用场景</strong> | 为对象或原始数据创造可重用类型。 | 合并多个类型，并要求所有属性存在。 |<br>
| <strong>合并类型</strong> | 不用于合并类型。 | 用于合并多种类型。 |<br>
| <strong>示例</strong> | <code>type User = { name: string, age: number };</code> | <code>type UserWithAddress = User &amp; Address;</code> |</p>
<h3 id="">何时使用类型别名或交叉类型</h3>
<ul>
<li>
<p>当需要为对象、函数或其他数据结构定义<strong>单一类型</strong>时，使用类型别名。它们有助于提升代码的清晰度、重用性和类型安全。</p>
</li>
<li>
<p>当需要将多个类型<strong>合并为一个</strong>时，使用交叉类型。在对象需要同时满足多种合同时理想，比如合并不同的类型或扩展现有类型的功能。</p>
</li>
</ul>
<p>通过在 TypeScript 中运用类型别名和交集类型，你的代码将变得更易于理解、更安全且更易于维护。这些功能为你的数据提供了结构，帮助尽早捕获错误。</p>
<h2 id="typescript">TypeScript 中的接口</h2>
<p>在 TypeScript 中，<strong>接口</strong>是一种定义对象结构、描述其属性及其类型的方法。接口用于在代码中强制执行类型检查，确保对象遵循特定的结构。类似于类型别名，接口使你的代码更具可读性、可重用性和可维护性。</p>
<h3 id="">什么是接口？</h3>
<p>接口是对象的蓝图，定义了它应有的属性和方法。接口可用于为对象、函数或类定义自定义类型。</p>
<p>这是一个基本示例：</p>
<pre><code class="language-typescript">interface User {
  name: string;
  age: number;
  address: string;
}

function getUserInfo(user: User): string {
  return `${user.name} (${user.age} years old) lives at ${user.address}`;
}

const user: User = {
  name: "Alice",
  age: 30,
  address: "123 Main St",
};

console.log(getUserInfo(user)); // 输出: Alice (30 years old) lives at 123 Main St
</code></pre>
<p>在这个示例中：</p>
<ul>
<li>
<p><code>User</code> 接口定义了对象的结构。</p>
</li>
<li>
<p>任何 <code>User</code> 类型的对象必须具有 <code>name</code>，<code>age</code> 和 <code>address</code> 属性且符合指定类型。</p>
</li>
<li>
<p><code>getUserInfo</code> 函数确保 <code>user</code> 参数遵循 <code>User</code> 接口。</p>
</li>
</ul>
<h3 id="">接口和类型别名的相似之处</h3>
<ul>
<li>
<p>接口和类型别名都可以定义对象的结构。</p>
</li>
<li>
<p>两者都可以扩展，尽管语法不同。</p>
</li>
<li>
<p>两者都提高了代码的可读性和可重用性。</p>
</li>
<li>
<p>在大多数情况下，可以互换使用接口或类型别名来定义对象类型。</p>
</li>
</ul>
<p>使用类型别名的示例：</p>
<pre><code class="language-typescript">type User = {
  name: string;
  age: number;
  address: string;
};

const user: User = {
  name: "Bob",
  age: 25,
  address: "456 Elm St",
};
</code></pre>
<p>在这种情况下，<code>type</code> 和 <code>interface</code> 实现了同样的结果。</p>
<h3 id="">接口和类型别名的区别</h3>
<p>让我们总结一下它们的主要区别：</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>接口</th>
<th>类型别名</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>语法</strong></td>
<td>使用 <code>interface</code> 关键字。</td>
<td>使用 <code>type</code> 关键字。</td>
</tr>
<tr>
<td><strong>扩展性</strong></td>
<td>可以使用 <code>extends</code> 扩展。</td>
<td>可以使用交集（<code>&amp;</code>）扩展。</td>
</tr>
<tr>
<td><strong>声明合并</strong></td>
<td>支持在多个声明中合并。</td>
<td>不支持声明合并。</td>
</tr>
<tr>
<td><strong>联合类型</strong></td>
<td>不能定义联合类型。</td>
<td>可以定义联合类型。</td>
</tr>
</tbody>
</table>
<h3 id="">使用接口和类型别名进行扩展</h3>
<p><strong>扩展接口：</strong></p>
<pre><code class="language-typescript">interface Address {
  city: string;
  country: string;
}

interface User extends Address {
  name: string;
  age: number;
}

const user: User = {
  name: "Alice",
  age: 30,
  city: "New York",
  country: "USA",
};
</code></pre>
<pre><code class="language-typescript">type Address = {
  city: string;
  country: string;
};

type User = {
  name: string;
  age: number;
} &amp; Address;

const user: User = {
  name: "Alice",
  age: 30,
  city: "New York",
  country: "USA",
};
</code></pre>
<p>两种方法得到相同的结果，但语法不同。</p>
<h3 id="">使用接口的高级概念</h3>
<p><strong>1. 可选属性:</strong></p>
<p>接口可以使用 <code>?</code> 符号定义属性为可选：</p>
<pre><code class="language-typescript">interface User {
  name: string;
  age?: number; // 可选
}

const user1: User = { name: "Alice" };
const user2: User = { name: "Bob", age: 25 };
</code></pre>
<p><strong>2. 只读属性:</strong></p>
<p>使用 <code>readonly</code> 修饰符使属性不可变：</p>
<pre><code class="language-typescript">interface User {
  readonly id: number;
  name: string;
}

const user: User = { id: 1, name: "Alice" };
// user.id = 2; // 错误: 不能分配给 'id' 因为它是只读属性。
</code></pre>
<p><strong>3. 函数类型:</strong></p>
<p>接口可以定义函数签名：</p>
<pre><code class="language-typescript">interface Add {
  (a: number, b: number): number;
}

const add: Add = (a, b) =&gt; a + b;
console.log(add(5, 3)); // 输出: 8
</code></pre>
<p><strong>4. 索引签名:</strong></p>
<p>接口可以定义动态属性名：</p>
<pre><code class="language-typescript">interface StringDictionary {
  [key: string]: string;
}

const dictionary: StringDictionary = {
  hello: "world",
  name: "Alice",
};
</code></pre>
<p><strong>5. 扩展多个接口:</strong></p>
<p>一个接口可以扩展多个接口：</p>
<pre><code class="language-typescript">interface A {
  propA: string;
}

interface B {
  propB: number;
}

interface C extends A, B {
  propC: boolean;
}

const obj: C = {
  propA: "Hello",
  propB: 42,
  propC: true,
};
</code></pre>
<h3 id="">接口与类型别名的使用时机</h3>
<ul>
<li>
<p>当你需要定义对象形状，尤其是当你计划扩展它们时，使用 <strong>接口</strong> 。如果你需要声明合并，也使用接口，因为类型别名不支持它。</p>
</li>
<li>
<p><strong>类型别名</strong> 常用于更复杂的类型，例如联合或交叉。</p>
</li>
</ul>
<h2 id="">元组和枚举</h2>
<p>在 TypeScript 中， <strong>元组</strong> 是一种特殊类型的数组，它具有固定数量的元素，其中每个元素可以有不同的类型。元组确保值的顺序和类型保持一致。</p>
<pre><code class="language-typescript">// 一个包含字符串和数字的元组
let user: [string, number] = ["Alice", 25];

console.log(user[0]); // 输出: Alice
console.log(user[1]); // 输出: 25
</code></pre>
<p>在这个例子中，元组 <code>user</code> 包含一个字符串（名字）和一个数字（年龄）。顺序和类型必须按定义遵循。</p>
<h4 id=""><strong>包含可选元素的元组:</strong></h4>
<pre><code class="language-typescript">let person: [string, number, boolean?] = ["Bob", 30];

console.log(person); // 输出: ["Bob", 30]
</code></pre>
<p>这里，第三个元素（boolean）是可选的。</p>
<h4 id=""><strong>只读属性的元组:</strong></h4>
<pre><code class="language-typescript">const coordinates: readonly [number, number] = [10, 20];

// coordinates[0] = 50; // 错误: 不能分配给 '0' 因为它是一个只读元组
</code></pre>
<p><code>readonly</code> 关键字防止修改元组的值。</p>
<h3 id=""><strong>枚举</strong></h3>
<p>在 TypeScript 中， <strong>枚举</strong> 是一种定义一组命名常量的方法。枚举使代码更易读并帮助管理一组固定的值。</p>
<h4 id=""><strong>数值枚举（默认）:</strong></h4>
<pre><code class="language-typescript">enum Status {
  Pending,   // 0
  InProgress, // 1
  Completed,  // 2
}

console.log(Status.Pending);   // 输出: 0
console.log(Status.Completed); // 输出: 2
</code></pre>
<p>默认情况下，TypeScript 从 <code>0</code> 开始分配数值。</p>
<h4 id=""><strong>自定义枚举数值:</strong></h4>
<pre><code class="language-typescript">enum OrderStatus {
  Pending = 1,
  Shipped = 5,
  Delivered = 10,
}

console.log(OrderStatus.Shipped); // 输出: 5
</code></pre>
<p>这里为每个状态分配了自定义值。</p>
<h4 id=""><strong>字符串枚举:</strong></h4>
<pre><code class="language-typescript">enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

console.log(Direction.Up); // 输出: "UP"
</code></pre>
<p>字符串枚举存储固定的文本值而不是数字。</p>
<h4 id=""><strong>在函数中使用枚举:</strong></h4>
<pre><code class="language-typescript">function getStatusText(status: Status): string {
  switch (status) {
    case Status.Pending:
      return "Order is pending.";
    case Status.InProgress:
      return "Order is in progress.";
    case Status.Completed:
      return "Order is completed.";
    default:
      return "Unknown status.";
  }
}

console.log(getStatusText(Status.InProgress)); // 输出: "Order is in progress."
</code></pre>
<p>这个函数接受一个枚举值并根据状态返回一个信息。</p>
<p>元组定义了具有不同数据类型的固定长度数组，而枚举为更好的可读性提供命名常量，使代码更具结构性和类型安全。</p>
<h2 id="typescriptunknownnever"><strong>TypeScript 中的类型断言、unknown类型和never类型</strong></h2>
<h3 id=""><strong>类型断言</strong></h3>
<p>类型断言告诉 TypeScript 将某个值视为特定类型。它不会改变值，但帮助编译器理解类型。</p>
<pre><code class="language-typescript">let value: unknown = "Hello, TypeScript!";

// 使用类型断言将 'value' 视为字符串
let strLength: number = (value as string).length;

console.log(strLength); // 输出: 18
</code></pre>
<p>这里，<code>value</code> 最初是 <code>unknown</code> 类型，但类型断言 (<code>as string</code>) 允许将其视为字符串。</p>
<p>这里还有另一种编写类型断言的方式：</p>
<pre><code class="language-typescript">let num = &lt;number&gt;(10);
console.log(num); // 输出: 10
</code></pre>
<p><code>&lt;number&gt;</code> 语法也执行类型断言。</p>
<h3 id="unknown"><strong>unknown类型</strong></h3>
<p>现在让我们简单回顾一下 <code>unknown</code> 类型。记住，它是一个比 <code>any</code> 更安全的选择，并且可以保存任何值——但是TypeScript在使用它之前需要进行类型检查。</p>
<pre><code class="language-typescript">let data: unknown;

data = "Hello";
data = 42;
data = true;

// 使用值之前进行类型检查
if (typeof data === "string") {
  console.log(data.toUpperCase()); // 仅当数据是字符串时有效
}
</code></pre>
<p>由于 <code>data</code> 是 <code>unknown</code> 类型，TypeScript 不允许在不先检查其类型的情况下直接操作。</p>
<h3 id="never"><strong>never 类型</strong></h3>
<p><code>never</code> 类型表示永远不会发生的值。它通常用于永不返回或总是抛出错误的函数。</p>
<pre><code class="language-typescript">function throwError(message: string): never {
  throw new Error(message);
}

// throwError("Something went wrong!"); // 此函数永远不返回
</code></pre>
<p>在这里，<code>throwError</code> 不会返回任何东西，因为它总是抛出一个错误。</p>
<h4 id="switchnever"><strong>Switch 案例中的 Never 类型示例：</strong></h4>
<pre><code class="language-typescript">type Status = "success" | "failure";

function checkStatus(status: Status): void {
  switch (status) {
    case "success":
      console.log("Operation was successful.");
      break;
    case "failure":
      console.log("Operation failed.");
      break;
    default:
      const unexpected: never = status; // 确保所有情况都被处理
  }
}
</code></pre>
<p>这确保了 <code>Status</code> 的所有可能值都得到了处理，从而防止了意料之外的行为。</p>
<p>下面是这些不同方法的快速比较：</p>
<p>| <strong>功能</strong> | <strong>描述</strong> |<br>
| <strong>类型断言</strong> | 告诉 TypeScript 将某个值视为特定类型。 |<br>
| <strong>Unknown 类型</strong> | 允许存储任何值，但在使用前需要进行类型检查。 |<br>
| <strong>Never 类型</strong> | 表示永远不会发生的值，用于函未返回的数。 |</p>
<h2 id="typescript">TypeScript 中的泛型</h2>
<p>泛型允许编写灵活、可重用且类型安全的代码。泛型让函数、类或接口在不指定特定类型的情况下工作，同时保持类型安全。</p>
<h3 id=""><strong>基本泛型</strong></h3>
<p>一个泛型函数可以与任何类型一起工作，同时保持类型安全。</p>
<pre><code class="language-typescript">function identity&lt;T&gt;(value: T): T {
  return value;
}

console.log(identity&lt;string&gt;("Hello")); // 输出: "Hello"
console.log(identity&lt;number&gt;(42));      // 输出: 42
</code></pre>
<p>这里，<code>&lt;T&gt;</code> 是一个<strong>泛型类型参数</strong>，允许 <code>identity</code> 使用任何类型。</p>
<h3 id=""><strong>数组中的泛型</strong></h3>
<p>泛型有助于在数组中强制执行类型安全。</p>
<p>以下是使用泛型反转数组的示例：</p>
<pre><code class="language-typescript">function reverseArray&lt;T&gt;(arr: T[]): T[] {
  return arr.reverse();
}

console.log(reverseArray&lt;number&gt;([1, 2, 3]));  // 输出: [3, 2, 1]
console.log(reverseArray&lt;string&gt;(["A", "B", "C"])); // 输出: ["C", "B", "A"]
</code></pre>
<p>这确保了函数始终返回与接收到的相同类型的数组。</p>
<h3 id=""><strong>接口中的泛型</strong></h3>
<p>泛型可以在接口中用来定义灵活的对象结构。</p>
<pre><code class="language-typescript">interface StorageBox&lt;T&gt; {
  content: T;
}

let numberBox: StorageBox&lt;number&gt; = { content: 100 };
let stringBox: StorageBox&lt;string&gt; = { content: "TypeScript" };

console.log(numberBox.content); // 输出: 100
console.log(stringBox.content); // 输出: "TypeScript"
</code></pre>
<p>在这里，<code>StorageBox&lt;T&gt;</code> 允许存储不同类型的内容，同时确保一致性。</p>
<h3 id=""><strong>类中的泛型</strong></h3>
<p>泛型在类中同样有效，使其更具可重用性。</p>
<p>这是一个泛型队列类的示例：</p>
<pre><code class="language-typescript">class Queue&lt;T&gt; {
  private items: T[] = [];

  enqueue(item: T): void {
    this.items.push(item);
  }

  dequeue(): T | undefined {
    return this.items.shift();
  }
}

let numberQueue = new Queue&lt;number&gt;();
numberQueue.enqueue(10);
numberQueue.enqueue(20);
console.log(numberQueue.dequeue()); // 输出: 10

let stringQueue = new Queue&lt;string&gt;();
stringQueue.enqueue("Hello");
stringQueue.enqueue("World");
console.log(stringQueue.dequeue()); // 输出: "Hello"
</code></pre>
<p>此类适用于任何类型，同时保持类型安全。</p>
<h3 id=""><strong>具有多个类型参数的泛型</strong></h3>
<p>函数或类可以接受多个泛型类型。</p>
<p>以下是交换两个值的函数示例：</p>
<pre><code class="language-typescript">function swap&lt;T, U&gt;(first: T, second: U): [U, T] {
  return [second, first];
}

console.log(swap&lt;string, number&gt;("Age", 25)); // 输出: [25, "Age"]
console.log(swap&lt;boolean, string&gt;(true, "Yes")); // 输出: ["Yes", true]
</code></pre>
<p>在这里，<code>&lt;T, U&gt;</code> 允许函数同时处理不同的类型。</p>
<h3 id=""><strong>具有约束的泛型</strong></h3>
<p>有时候，泛型类型应遵循某些规则。<strong>约束</strong>确保某个类型具有特定属性。</p>
<p>以下是确保类型具有 <code>length</code> 属性的示例：</p>
<pre><code class="language-typescript">function getLength&lt;T extends { length: number }&gt;(item: T): number {
  return item.length;
}

console.log(getLength("Hello"));   // 输出: 5
console.log(getLength([1, 2, 3])); // 输出: 3
</code></pre>
<p>在这里，<code>T extends { length: number }</code> 确保 <code>T</code> 具有 <code>length</code> 属性。</p>
<h3 id="keyof"><strong>高级：使用</strong> <code>keyof</code> <strong>操作符的泛型</strong></h3>
<p><code>keyof</code> 操作符可用于确保有效的属性名称。</p>
<p>以下是按名称获取属性值的示例：</p>
<pre><code class="language-typescript">function getProperty&lt;T, K extends keyof T&gt;(obj: T, key: K): T[K] {
  return obj[key];
}

let user = { name: "Alice", age: 30 };

console.log(getProperty(user, "name")); // 输出: "Alice"
console.log(getProperty(user, "age"));  // 输出: 30
</code></pre>
<h2 id="">结语</h2>
<p>在本手册中，您深入了解了如何在 React 中使用 TypeScript 的基础知识。我们讨论了诸如类型注解、类型推论以及对象和数组管理等重要概念，展示了 TypeScript 如何提高代码的稳定性和可维护性。</p>
<p>我们还涵盖了一些高级主题，如联合类型和 any 类型、只读属性的使用，以及泛型、类型别名和接口的应用。希望这些示例能帮助您理解 TypeScript 如何增强您的 JavaScript 开发，使 TS 成为构建健壮的大型应用程序的宝贵工具。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React 开发人员的 TypeScript 手册 —— 如何构建类型安全的 Todo 应用程序 ]]>
                </title>
                <description>
                    <![CDATA[ 在当今的JavaScript生态中，TypeScript越来越受欢迎。越来越多的React开发者开始使用它。 如果你是React开发者，希望探索TypeScript或提升自己的技能，这本手册正适合你。我将指导你通过构建一个经典的待办事项应用，来在React应用中使用TypeScript。 我将涵盖作为一个React开发者开始使用TypeScript所需知道的一切。你将学会如何使用强类型处理状态和属性，如何用TypeScript创建React组件，如何在React Hooks中使用TypeScript，以及如何与Context API一起使用TypeScript。 通过本教程的学习，你将对TypeScript有一个坚实的理解，并准备好自信地开发类型安全的React应用程序。所以，不用再等待，让我们开始吧！ 我们将涵盖以下内容  * 先决条件  * 我们将要构建什么  * 如何开始  * 如何设置待办事项应用的组件  * 如何在React中创建一个简单的表单元素  * TypeScript中的类型错误是什么以及如何修复它  * TypeScript中的泛型是什么  * 如何在Rea ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/typescript-tutorial-for-react-developers/</link>
                <guid isPermaLink="false">65dc09724985d903ee575e18</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ YiWei ]]>
                </dc:creator>
                <pubDate>Mon, 26 Feb 2024 04:17:04 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2024/02/TypeScript-Handbook-for-React-Developers-Cover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/typescript-tutorial-for-react-developers/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">TypeScript Handbook for React Developers – How to Build a Type-Safe Todo App</a>
      </p><!--kg-card-begin: markdown--><p>在当今的JavaScript生态中，TypeScript越来越受欢迎。越来越多的React开发者开始使用它。</p>
<p>如果你是React开发者，希望探索TypeScript或提升自己的技能，这本手册正适合你。我将指导你通过构建一个经典的待办事项应用，来在React应用中使用TypeScript。</p>
<p>我将涵盖作为一个React开发者开始使用TypeScript所需知道的一切。你将学会如何使用强类型处理状态和属性，如何用TypeScript创建React组件，如何在React Hooks中使用TypeScript，以及如何与Context API一起使用TypeScript。</p>
<p>通过本教程的学习，你将对TypeScript有一个坚实的理解，并准备好自信地开发类型安全的React应用程序。所以，不用再等待，让我们开始吧！</p>
<h2 id="">我们将涵盖以下内容</h2>
<ul>
<li><a href="#prerequisites">先决条件</a></li>
<li><a href="#what-are-we-going-to-build">我们将要构建什么</a></li>
<li><a href="#getting-started">如何开始</a></li>
<li><a href="#how-to-set-up-the-todo-app-component">如何设置待办事项应用的组件</a></li>
<li><a href="#how-to-create-a-simple-form-element-in-react">如何在React中创建一个简单的表单元素</a></li>
<li><a href="#what-is-a-type-error-in-typescript-and-how-to-fix-it">TypeScript中的类型错误是什么以及如何修复它</a></li>
<li><a href="#what-are-the-generic-types-in-typescript">TypeScript中的泛型是什么</a></li>
<li><a href="#how-to-handle-form-submission-with-typescript-in-react">如何在React中用TypeScript处理表单提交</a></li>
<li><a href="#how-to-automatically-focus-on-an-input-field-in-react">如何在React中自动聚焦一个输入字段</a></li>
<li><a href="#what-is-useref-and-how-to-to-use-it-with-typescript">什么是<code>useRef</code>以及如何在TypeScript中使用它</a></li>
<li><a href="#how-to-create-type-safe-react-components-with-typescript">如何用TypeScript创建类型安全的React组件</a></li>
<li><a href="#what-is-forwardref-in-react">React中的<code>forwardRef</code>是什么</a></li>
<li><a href="#how-to-create-a-todo-item-on-the-form-submission">如何在表单提交时创建一个待办事项</a></li>
<li><a href="#what-is-react-context">什么是React上下文</a></li>
<li><a href="#how-to-use-react-context-with-typescript">如何在TypeScript中使用React上下文</a></li>
<li><a href="#what-are-interfaces-in-typescript">TypeScript中的接口是什么</a></li>
<li><a href="#how-to-use-typescript-interfaces-with-react-context">如何将TypeScript接口与React上下文一起使用</a></li>
<li><a href="#how-to-create-a-custom-hook-to-consume-react-context">如何创建一个自定义钩子来使用React上下文</a></li>
<li><a href="#how-to-define-an-interface-for-todo-items">如何为待办事项定义一个接口</a></li>
<li><a href="#how-to-build-a-custom-react-component-for-displaying-todo-items">如何构建一个自定义的React组件来展示待办事项</a></li>
<li><a href="#how-to-implement-functionality-edit-delete-and-update-todo-items">如何实现功能：编辑、删除和更新待办事项</a></li>
<li><a href="#conclusion">结论</a></li>
</ul>
<h2 id="prerequisites">先决条件</h2>
<p>开始本教程无需事先了解TypeScript，使其非常适合初学者。然而，拥有React的背景知识将极大地增强你的理解力，并在整个教程中最大限度地提升你的学习潜力。</p>
<p>在本教程中，你将使用以下工具：</p>
<ol>
<li><strong>React 18.2.0：</strong> React是一个用于构建用户界面的JavaScript库。它允许开发者创建可重用的UI组件，并根据数据变化高效地更新UI。</li>
<li><strong>TypeScript：</strong> TypeScript是JavaScript的一种静态类型超集，增加了可选的类型注释。它提供了增强的工具，并帮助在开发过程中捕获潜在的错误，使代码更可靠，更易于维护。</li>
<li><strong>Vite：</strong> Vite是一个用于现代Web应用的快速开发服务器和构建工具。它提供即时服务器启动、热模块替换和优化的构建输出，使开发流程快速而高效。</li>
<li><strong>Framer Motion：</strong> Framer Motion是React的一种流行动画库。它提供了一个易于使用的界面，用于在Web应用中创建流畅的互动动画和过渡，增强了整体用户体验。</li>
</ol>
<p>在接下来的部分中，你将对你将在本教程中构建的项目有一个简洁的预览。</p>
<h2 id="what-are-we-going-to-build">我们将要构建什么</h2>
<p>我们将要构建一个经典的待办事项应用程序。它将具有以下功能：</p>
<ul>
<li>添加一个待办事项。</li>
<li>编辑一个待办事项。</li>
<li>删除一个待办事项。</li>
<li>标记一个待办事项是否完成。</li>
<li>在浏览器的本地存储中存储待办事项。</li>
<li>当用户尝试添加或编辑一个空标题的待办事项时，显示适当的错误消息。</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/ezgif-3-98866e5ad0.gif" alt="This is a todo app where users can add or delete an item, also they can edit an existing item or mark them as completed" width="600" height="400" loading="lazy"></p>
<p>上图是应用程序最终预览。</p>
<h2 id="getting-started">如何开始</h2>
<p>为了开始本教程，我已经为你准备了一个包含所有必需依赖项的样板项目。这消除了从头开始设置项目的需要。</p>
<p>只需从GitHub仓库克隆<a href="https://github.com/Yazdun/react-ts-fcc-tutorial/tree/starter">起始样板</a>，然后跟随教程。这样，你可以专注于学习和实现概念，而不会被设置细节所困扰。</p>
<ul>
<li>起始样板：<a href="https://github.com/Yazdun/react-ts-fcc-tutorial/tree/starter">在GitHub上查看</a></li>
<li>最终版本：<a href="https://github.com/Yazdun/react-ts-fcc-tutorial">在GitHub上查看</a></li>
</ul>
<p>一旦你设置好起始样板并成功地在你的本地机器上运行它，你应该能够看到初始页面。这个页面将作为我们旅程的起点。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-314.png" alt="简单的页面，显示着“待办事项应用”的文字。这个页面作为我们教程的起点" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>起始样板</figcaption>
</figure>
<p>现在，我们将开始为我们的应用添加令人兴奋的功能。让我们立即开始吧！</p>
<h2 id="how-to-set-up-the-todo-app-component">如何设置待办事项应用的组件</h2>
<p>在这一部分，你将设置你的待办事项应用的主要组件，并逐渐增强它的附加功能。打开<code>./src/App.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/App.tsx

import { TodoList, AddTodo } from './components'
import { Toaster } from 'react-hot-toast'

function App() {
  return (
    &lt;div&gt;
      &lt;Toaster position="bottom-center" /&gt;
      &lt;AddTodo /&gt;
      &lt;TodoList /&gt;
    &lt;/div&gt;
  )
}

export default App
</code></pre>
<p>让我们一步步分解：</p>
<ul>
<li><code>&lt;Toaster position="bottom-center" /&gt;</code>：这个组件负责在屏幕底部中央显示toast通知。</li>
<li><code>&lt;AddTodo /&gt;</code>：这个组件将表示一个输入字段和一个按钮，用于向应用添加新的待办事项。</li>
<li><code>&lt;TodoList /&gt;</code>：这个组件将渲染现有待办事项的列表。</li>
</ul>
<p>现在，在你的浏览器上打开你的本地服务器，你将能看到以下页面：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-315.png" alt="App.tsx的预览" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>App.tsx的预览</figcaption>
</figure>
<p>这两个组件在你的应用中起着至关重要的作用。在接下来的部分中，你将构建使用<code>&lt;AddTodo /&gt;</code>组件添加待办事项的功能。具体来说，你将学习如何在React中使用TypeScript处理表单提交。</p>
<h2 id="how-to-create-a-simple-form-element-in-react">如何在React中创建一个简单的表单元素</h2>
<p>首先，你需要为创建一个待办事项创建一个表单元素。为了在你的应用中实现这一点，你需要创建一个表单并有效地处理表单提交。在这一部分中，你将探索如何在React应用中使用TypeScript处理表单提交。</p>
<p>我只是想给你一个快速提示，因为你即将遇到你在TypeScript中的第一个类型错误！将以下代码添加到<code>components/AddTodo.tsx</code>：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx
//⚠️TypeScript is not happy with this code

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState()

  return (
    &lt;form&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p>你创建了一个useState钩子，它会随着输入值的改变而更新状态。然而，TypeScript对这段代码不满意。但为什么TypeScript会不满意呢？</p>
<h3 id="what-is-a-type-error-in-typescript-and-how-to-fix-it">TypeScript中的类型错误是什么以及如何修复它</h3>
<p>TypeScript中的类型定义了变量可以持有的数据种类，并在开发过程中启用了错误和漏洞的检测。</p>
<p>当一个值以与其预期类型不兼容的方式使用时，就会在TypeScript中出现类型错误，导致代码中可能出现漏洞或意外行为。</p>
<p>在我们的案例中，TypeScript显示这段代码有错误，因为它无法自动推断状态变量<code>input</code>的类型。要解决这个问题，你需要明确地提供TypeScript类型信息。在这种情况下，你希望input是字符串类型，因为它代表输入字段的值。</p>
<p>要修复这个错误，你有两个选择。简单的解决方案是向<code>useState</code>钩子添加一个初始值，TypeScript将自动推断<code>input</code>类型为字符串：</p>
<pre><code class="language-tsx"> const [input, setInput] = useState('')
</code></pre>
<p>通过添加上述代码，你可能会注意到错误消失了，TypeScript也满意了。但并不是所有的错误都能在TypeScript中这么容易解决。</p>
<p>让我们考虑一个情况，你对你的状态的类型不确定，不能确定它应该初始化为数字还是字符串。这种不确定性引导我们使用第二个选项，即使用泛型。</p>
<h3 id="what-are-the-generic-types-in-typescript">TypeScript中的泛型是什么</h3>
<p>泛型提供了一种处理你不确定特定值类型的情况的方法。通过泛型，你可以定义一个占位符来代表实际的类型，使你的代码更加灵活和可重用：</p>
<pre><code class="language-tsx">const [state, setState] = useState&lt;string | number&gt;('')
</code></pre>
<p>上述代码初始化了一个名为“state”的状态变量，其初始值为空字符串，但它允许状态变量存储字符串或数字作为其值。</p>
<p>现在，让我们在你的应用中引入一个泛型。我们不希望你的用户添加数字作为待办事项 - 我们希望他们只能添加字符串：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx
//✅TypeScript is happy with this code

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')

  return (
    &lt;form&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p>通过在<code>useState</code>函数后指定<code>&lt;string&gt;</code>，我们确保状态变量<code>input</code>只能持有字符串类型的值。这样可以防止用户输入数字或任何其他不兼容的数据类型作为待办事项。</p>
<h3 id="how-to-handle-form-submission-with-typescript-in-react">如何在React中使用TypeScript处理表单提交</h3>
<p>既然你已经成功地将输入值存储在状态中，让我们继续处理表单提交本身：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    console.log('form has been submitted')
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}

</code></pre>
<p>当表单被提交时，会调用<code>handleSubmission</code>函数。让我们逐步分解它：</p>
<ol>
<li><code>(e: React.FormEvent)</code>是函数的参数声明。它指定函数期望传递一个类型为<code>React.FormEvent</code>的事件对象作为参数。<code>React.FormEvent</code>是表示在表单元素上发生的事件的事件对象类型，例如提交表单或与表单字段互动。</li>
<li><code>e.preventDefault()</code>是属于事件对象（<code>e</code>）的方法。它被调用以阻止表单提交的默认行为，即刷新页面。通过调用<code>preventDefault()</code>，我们覆盖了默认行为并阻止了页面刷新。</li>
<li><code>console.log('form has been submitted')</code>是一个简单的语句，将消息记录到浏览器的控制台。在这种情况下，它在表单提交事件发生时记录消息“form has been submitted”。</li>
</ol>
<p>太好了！你已经完成了处理表单提交所需的步骤。现在让我们继续到下一部分，在那里你将通过做一些修改来增强你的表单功能。</p>
<h3 id="how-to-automatically-focus-on-an-input-field-in-react">如何在React中自动聚焦输入字段</h3>
<p>为了提升用户体验，你可以在应用最初加载时自动将焦点设置在“添加待办事项”的输入字段上。这消除了用户在打开应用时手动点击输入框的需要。</p>
<p>为了实现这个功能，你可以使用一个特定的React钩子，称为<code>useRef</code>，它允许你将这个特性整合到输入框中。</p>
<h4 id="what-is-useref-and-how-to-to-use-it-with-typescript">什么是`useRef`以及如何在TypeScript中使用它</h4>
<p><code>useRef</code>是React中的一个特殊钩子，用于在组件中创建对一个元素或值的引用。这个引用可以用来直接访问和操作被引用的元素，而不会导致重新渲染。</p>
<p>你通常会用它来访问DOM元素、管理焦点或在组件渲染中存储可变值。</p>
<p>打开应用<code>components/AddTodo.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')
  const inputRef = useRef&lt;HTMLInputElement&gt;(null)

  useEffect(() =&gt; {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }, [])

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    console.log('form has been submitted')
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          ref={inputRef}
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p>这里，React的<code>useRef</code>钩子与TypeScript一起使用。</p>
<ul>
<li>行<code>const inputRef = useRef&lt;HTMLInputElement&gt;(null)</code>使用useRef钩子声明了一个名为<code>inputRef</code>的引用变量。类型参数<code>&lt;HTMLInputElement&gt;</code>指定该ref用于输入元素。ref的初始值设置为<code>null</code>。</li>
<li>在useEffect钩子中，检查<code>inputRef.current</code>是否存在。如果存在，则调用其上的<code>focus()</code>方法，这意味着当组件被装载时，输入字段将接收焦点。</li>
</ul>
<p><code>useRef</code>钩子使用<code>&lt;HTMLInputElement&gt;</code>进行类型参数化，以确保引用与输入元素兼容。</p>
<p>通过结合使用useRef和TypeScript，代码不仅受益于TypeScript的静态类型检查，还能使用useRef与输入元素的DOM引用进行交互。</p>
<p>虽然这段代码可以正确运行，但将此输入组件在应用的其他部分复用将是有益的。因此，让我们创建一个可复用的输入组件，并探索如何通过实现这个输入来开发类型安全的React组件。</p>
<h3 id="how-to-create-type-safe-react-components-with-typescript">如何用TypeScript创建类型安全的React组件</h3>
<p>在这一部分中，你将为应用中未来的使用案例创建一个类型安全的Input组件。</p>
<p>为了创建这个自定义的Input组件，你需要将在上一节中创建的ref作为prop传递给这个组件。</p>
<p>Refs作为普通的props传递，为了将refs传递给子组件，你需要实现一个名为forwardRef的特殊内置React函数。</p>
<h4 id="what-is-forwardref-in-react">React中的`forwardRef`是什么</h4>
<p>在React中，<code>forwardRef</code>函数是一个特性，它允许你从父组件向子组件传递ref。Refs用于直接访问和操作底层的DOM元素。</p>
<p>通过使用<code>forwardRef</code>，你可以创建一个自定义组件，该组件可以接收一个ref，并将其传递到组件内的特定元素。</p>
<p>这使得父组件能够与子组件的底层元素进行交互，例如聚焦输入字段或触发某些动作。</p>
<p>简而言之，<code>forwardRef</code>帮助你在组件之间连接ref，使你在需要时能够控制或访问子组件的内部元素。</p>
<p>现在，让我们创建一个可复用的Input组件。打开<code>components/Input.tsx</code>：</p>
<pre><code class="language-tsx">// 📂./src/components/Input.tsx

import { InputHTMLAttributes, forwardRef } from 'react'
import cn from 'classnames'

export const Input = forwardRef&lt;
  HTMLInputElement,
  InputHTMLAttributes&lt;HTMLInputElement&gt;
&gt;(({ className, ...rest }, ref) =&gt; {
  return (
    &lt;input
      {...rest}
      ref={ref}
      className={cn(
        'w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white',
        className,
      )}
    /&gt;
  )
})
</code></pre>
<p>让我们逐步分解这个组件：</p>
<ol>
<li>该组件使用React中的<code>forwardRef</code>函数将ref传递到底层的<code>&lt;input&gt;</code>元素。这允许父组件直接访问和操作输入元素。</li>
<li><code>HTMLInputElement</code>指定了将被传递到底层<code>&lt;input&gt;</code>元素的ref的类型。这确保了ref与输入元素期望的类型兼容。</li>
<li><code>InputHTMLAttributes&lt;HTMLInputElement&gt;</code>指定了组件接受的props对象的类型。这包括所有标准的HTML输入元素属性，例如<code>value</code>、<code>placeholder</code>、<code>onChange</code>等。</li>
<li>该组件从<code>rest</code>对象中解构出<code>className</code>属性，并且接收<code>ref</code>作为参数。</li>
<li>在组件内部，使用JSX表达式来渲染一个<code>&lt;input&gt;</code>元素。扩展运算符（<code>{...rest}</code>）被用于将组件接收到的所有props（除了<code>className</code>和<code>ref</code>）传递给<code>&lt;input&gt;</code>元素。这确保传递给<code>&lt;Input&gt;</code>组件的任何额外属性都将应用于底层的<code>&lt;input&gt;</code>元素。</li>
<li>使用<code>ref</code>属性将<code>ref</code>分配给底层的<code>&lt;input&gt;</code>元素，使得父组件能够引用输入元素。</li>
<li><code>className</code>是通过<code>classnames</code>模块中的<code>cn</code>函数构建的。这个函数基于提供的条件组合多个CSS类名。在这种情况下，它将默认输入元素的类名与传递给<code>&lt;Input&gt;</code>组件的<code>className</code>属性结合起来。</li>
</ol>
<p>最终渲染的<code>&lt;input&gt;</code>元素将具有组合的类名，并继承传递给<code>&lt;Input&gt;</code>组件的所有其他属性。</p>
<p>现在，让我们更新<code>&lt;AddTodo /&gt;</code>组件，以使用自定义的<code>&lt;Input /&gt;</code>替代默认的HTML输入元素：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')
  const inputRef = useRef&lt;HTMLInputElement&gt;(null)

  useEffect(() =&gt; {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }, [])

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    console.log('form has been submitted')
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;Input
          ref={inputRef}
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p>现在，你可以在整个应用程序中使用这个自定义的<code>&lt;Input /&gt;</code>组件。在下一部分中，你将创建在表单提交时添加待办事项的功能。</p>
<h3 id="how-to-create-a-todo-item-on-the-form-submission">如何在表单提交时创建一个待办事项</h3>
<p>为了存储每个待办事项，你可以使用一个数组来保存用户的输入。本质上，我们需要一个字符串数组来存储每个待办事项：</p>
<pre><code class="language-tsx">const [todos, setTodos] = useState&lt;string[]&gt;([])
</code></pre>
<p><code>string[]</code>指定了将存储在<code>todos</code>状态变量中的数据类型。在这种情况下，它是一个字符串数组，意味着它将保存一个待办事项列表，其中每个项都表示为一个字符串。</p>
<p>现在让我们在表单提交时向<code>todos</code>中添加一个项：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')
  const [todos, setTodos] = useState&lt;string[]&gt;([])

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    if (input.trim() !== '') {
      setTodos([...todos, input])
      setInput('')
    }
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;input
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
          type="text"
          className="w-full px-5 py-2 bg-transparent border-2 outline-none border-zinc-600 rounded-xl placeholder:text-zinc-500 focus:border-white"
          placeholder="start typing ..."
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<p><code>handleSubmission</code>检查<code>input</code>（用户输入的待办事项）在使用<code>input.trim() !== ''</code>去除任何前导或尾随空格后是否不是空字符串。</p>
<p>如果它不为空，则使用<code>setTodos([...todos, input])</code>将<code>input</code>值添加到现有的<code>todos</code>数组中。这将创建一个新数组，其中包含所有之前的待办事项和在末尾添加的新待办事项。它使用<code>setInput('')</code>将<code>input</code>值重置为空字符串，这样输入字段就变为空的，准备好输入下一个待办事项。</p>
<p>现在，虽然你已经成功实现了创建待办事项的功能，但它还不能在屏幕上显示。</p>
<p>这是因为<code>&lt;AddTodo /&gt;</code>组件负责添加待办事项，而不是显示它们。</p>
<p>另一方面，<code>&lt;TodoList /&gt;</code>组件负责显示所有项目。为了弥合这一差距并在这些组件之间共享待办事项，你可以利用React Context的力量。</p>
<h2 id="what-is-react-context">什么是React上下文</h2>
<p>React Context API是React中的一个特性，它允许数据在不通过props显式传递的情况下被组件共享和访问。它提供了一种创建全局状态的方法，该状态可以被应用中的任何组件访问。</p>
<p>假设你有一个类似树的组件结构，其中某些数据需要被不同层级的多个组件访问。与其通过多层组件传递数据，你可以使用React Context为该数据创建一个中心存储。</p>
<p>它是这样工作的：</p>
<ol>
<li><strong>创建Context：</strong> 首先，你使用<code>createContext()</code>函数定义一个context。这将创建一个包含共享数据的context对象。</li>
<li><strong>提供Context：</strong> 你用<code>&lt;Context.Provider&gt;</code>包裹父组件或应用的特定部分。这个提供者组件接受一个<code>value</code>属性，你可以在其中传递你想要共享的数据。</li>
<li><strong>使用Context：</strong> 要在一个组件内访问共享数据，你使用React提供的<code>useContext()</code>钩子。通过将创建的context作为参数传递给<code>useContext()</code>，你可以访问共享数据，并在该组件内使用它。</li>
<li><strong>更新Context：</strong> 如果你需要更新共享数据，可以通过修改提供者组件中的值来实现。这个更改将自动传播到所有使用context的组件。</li>
</ol>
<p>React Context API简化了跨组件共享数据的过程，消除了手动传递prop的需要。</p>
<p>在你的情况下，你需要创建一个Context来在多个组件之间共享待办事项。让我们创建一个Context来看看这个机制在实践中是如何工作的。</p>
<h3 id="how-to-use-react-context-with-typescript">如何在TypeScript中使用React上下文</h3>
<p>在这一部分中，你将学习如何创建一个React Context来隔离应用逻辑，并提高你的应用的状态管理能力。</p>
<p>如果你打开<code>context/TodoContext.tsx</code>，你会看到以下代码：</p>
<pre><code class="language-tsx">// 📂./src/context/TodoContext.tsx

import React, { createContext } from 'react'
import { nanoid } from 'nanoid'
import { useLocalStorage } from 'usehooks-ts'

export const TodoContext = createContext&lt;undefined&gt;(undefined)

export const TodoProvider = (props: { children: React.ReactNode }) =&gt; {
  return (
    &lt;TodoContext.Provider value={undefined}&gt;
      {props.children}
    &lt;/TodoContext.Provider&gt;
  )
}

</code></pre>
<p>让我们逐步分解：</p>
<ul>
<li><code>TodoContext</code>是使用React提供的<code>createContext</code>函数创建的。它以未定义的值进行初始化。</li>
<li>此外，定义了一个名为<code>TodoProvider</code>的组件。它接受一个<code>children</code>属性，代表将被这个提供者包裹的子组件。</li>
<li>在<code>TodoProvider</code>组件内部，渲染了一个<code>&lt;TodoContext.Provider&gt;</code>组件。它包裹了<code>props.children</code>，允许子组件访问TodoContext。</li>
<li>目前为止，提供给<code>&lt;TodoContext.Provider&gt;</code>组件的值被设置为<code>undefined</code>。</li>
</ul>
<p>在接下来的部分中，你将通过学习TypeScript中所谓的<strong>接口</strong>来创建一个更复杂的Context。</p>
<h3 id="what-are-interfaces-in-typescript">TypeScript中的接口是什么</h3>
<p>在TypeScript中，接口是一种定义对象结构和形状的方式。它们允许你指定一个对象应该具有的属性及其类型。可以将接口视为一个蓝图或契约，描述一个对象应该具备的外观。</p>
<p>想象一下你正在建造一座房子。在开始施工之前，你会有一个蓝图，勾画出房子的设计和布局。类似地，TypeScript中的接口就像是一个对象的蓝图。</p>
<p>让我们来看一个简单的接口示例：</p>
<pre><code class="language-ts">interface Person {
  name: string;
  age: number;
}
</code></pre>
<p>在这个示例中，我们定义了一个名为<code>Person</code>的接口，描述了一个人对象的结构。它指定一个人对象应该有两个属性：<code>name</code>，其类型应为<code>string</code>，和<code>age</code>，其类型应为<code>number</code>。</p>
<p>让我们考虑你的Todo Context以及你想传递给其消费者的属性。在这种情况下，你需要一个定义所需属性的接口，包括包含所有待办事项的字符串数组，以及一个接受字符串并将其添加到待办事项列表中的函数。</p>
<pre><code class="language-tsx">interface TodoContextProps {
  todos: string[]
  addTodo: (text: string) =&gt; void
}
</code></pre>
<p><code>TodoContextProps</code>接口指定了TodoContext中期望的属性结构。它有两个属性：</p>
<ol>
<li><code>todos</code>：表示待办事项的字符串数组。这个属性包含了所有现有的待办事项。</li>
<li><code>addTodo</code>：一个接受类型为字符串（<code>text</code>）的参数并返回<code>void</code>类型的函数。这个函数负责将新的待办事项添加到列表中。它接受新的待办事项作为输入，并执行必要的操作，但不返回任何值。</li>
</ol>
<h3 id="how-to-use-typescript-interfaces-with-react-context">如何在React Context中使用TypeScript接口</h3>
<p>现在你已经了解了TypeScript接口的好处，是时候通过整合这个接口来增强你的Context了：</p>
<pre><code class="language-tsx">// 📂./src/context/TodoContext.tsx

import React, { createContext, useState } from 'react'
import { nanoid } from 'nanoid'
import { useLocalStorage } from 'usehooks-ts'

interface TodoContextProps {
  todos: string[]
  addTodo: (text: string) =&gt; void
}
export const TodoContext = createContext&lt;TodoContextProps | undefined&gt;(
  undefined,
)

export const TodoProvider = (props: { children: React.ReactNode }) =&gt; {
  const [todos, setTodos] = useState&lt;string[]&gt;([])

  // ::: ADD NEW TODO :::
  const addTodo = (text: string) =&gt; {
    setTodos([...todos, text])
  }

  const value: TodoContextProps = {
    todos,
    addTodo,
  }

  return (
    &lt;TodoContext.Provider value={value}&gt;{props.children}&lt;/TodoContext.Provider&gt;
  )
}
</code></pre>
<p>在这个更新的代码中，与之前的版本相比有显著的变化。这些变化引入了TypeScript，并修改了TodoContext和TodoProvider组件：</p>
<ol>
<li>这里，<code>TodoContextProps</code>指定它应该有两个属性：<code>todos</code>，表示待办事项的字符串数组，以及<code>addTodo</code>，一个接受字符串参数并返回void（无返回值）的函数。</li>
<li>现在使用<code>createContext</code>创建了<code>TodoContext</code>，并用<code>TodoContextProps | undefined</code>类型进行初始化。这意味着context值可以是<code>TodoContextProps</code>类型或未定义。</li>
<li><code>TodoProvider</code>组件现在使用<code>useState</code>钩子初始化<code>todos</code>状态。它使用一个字符串数组来跟踪待办事项。</li>
<li>引入了一个新函数<code>addTodo</code>，它接受一个字符串<code>text</code>作为参数。它使用<code>setTodos</code>函数通过将新的待办事项追加到现有数组来更新<code>todos</code>状态。</li>
<li>创建context的值：<code>value</code>变量被赋值为一个<code>TodoContextProps</code>类型的对象，包含<code>todos</code>数组和<code>addTodo</code>函数。</li>
<li>提供context值：<code>&lt;TodoContext.Provider&gt;</code>组件包裹<code>props.children</code>，并将value属性设置为<code>value</code>，它向子组件提供<code>todos</code>和<code>addTodo</code>。</li>
</ol>
<p>总而言之，你正在使用TypeScript为TodoContextProps定义一个接口，使用useState和自定义函数添加新的待办事项，并向子组件提供更新后的context值。</p>
<h3 id="how-to-create-a-custom-hook-to-consume-react-context">如何创建一个自定义钩子来使用React Context</h3>
<p>为了使用context提供的值，你需要创建一个自定义钩子来使用这个context，并将其值提供给子组件。打开<code>context/useTodo.ts</code>并添加以下代码：</p>
<pre><code class="language-tsx">// 📂./src/context/useTodo.ts

import { useContext } from 'react'
import { TodoContext } from './TodoContext'

export const useTodo = () =&gt; {
  const context = useContext(TodoContext)

  if (!context) {
    throw new Error('useTodo must be used within a TodoProvider')
  }

  return context
}
</code></pre>
<p>让我们逐步分解：</p>
<ol>
<li>你从'react'模块导入<code>useContext</code>钩子，并从<code>./TodoContext</code>文件导入<code>TodoContext</code>。</li>
<li>在钩子内部，调用<code>useContext</code>钩子并以<code>TodoContext</code>作为参数。这样连接到<code>TodoContext</code>并检索其当前值。</li>
<li>如果<code>context</code>值是<code>undefined</code>，这意味着<code>useTodo</code>钩子正在<code>TodoProvider</code>的范围之外使用。在这种情况下，会抛出一个错误消息，内容为'<code>useTodo</code>必须在<code>TodoProvider</code>内部使用'。</li>
</ol>
<p>总体来说，这段代码允许你创建一个名为<code>useTodo</code>的自定义钩子，可以在你的组件中使用。</p>
<p>通过调用这个钩子，你可以访问<code>TodoContext</code>并检索其值，其中包括在<code>TodoProvider</code>中定义的与待办事项相关的数据和函数。</p>
<p>它还确保<code>useTodo</code>钩子只在<code>TodoProvider</code>的范围内使用，以维护正确的使用方式并防止任何错误。</p>
<p>接下来，你需要用TodoProvider组件包裹整个应用程序。这确保了通过使用<code>useTodo</code>钩子，context值可以被其子组件访问：</p>
<pre><code class="language-tsx">// 📂 ./src/main.tsx

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  &lt;React.StrictMode&gt;
    &lt;TodoProvider&gt;
      &lt;App /&gt;
    &lt;/TodoProvider&gt;
  &lt;/React.StrictMode&gt;,
)
</code></pre>
<p><code>&lt;TodoProvider&gt;</code>包裹了整个应用程序，并提供了管理待办事项相关数据所需的context。</p>
<p>现在，让我们在<code>&lt;AddTodo /&gt;</code>组件中集成<code>useTodo</code>钩子，以通过context高效管理待办事项。此外，让我们实现toast通知，以根据用户交互提供反馈：</p>
<pre><code class="language-tsx">//📂./src/components/AddTodo.tsx

import React, { useEffect, useRef, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useTodo } from '../context/useTodo'
import { Input } from './Input'

export const AddTodo = () =&gt; {
  const [input, setInput] = useState&lt;string&gt;('')
  const inputRef = useRef&lt;HTMLInputElement&gt;(null)
  const { addTodo } = useTodo()

  useEffect(() =&gt; {
    if (inputRef.current) {
      inputRef.current.focus()
    }
  }, [])

  const handleSubmission = (e: React.FormEvent) =&gt; {
    e.preventDefault()
    if (input.trim() !== '') {
      addTodo(input)
      setInput('')
      toast.success('Todo added successfully!')
    } else {
      toast.error('Todo field cannot be empty!')
    }
  }

  return (
    &lt;form onSubmit={handleSubmission}&gt;
      &lt;div className="flex items-center w-full max-w-lg gap-2 p-5 m-auto"&gt;
        &lt;Input
          ref={inputRef}
          type="text"
          placeholder="start typing ..."
          value={input}
          onChange={e =&gt; setInput(e.target.value)}
        /&gt;
        &lt;button
          type="submit"
          className="px-5 py-2 text-sm font-normal text-blue-300 bg-blue-900 border-2 border-blue-900 active:scale-95 rounded-xl"
        &gt;
          Submit
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  )
}
</code></pre>
<ol>
<li>行<code>const { addTodo } = useTodo()</code>使用<code>useTodo</code>钩子从待办事项context中检索<code>addTodo</code>函数。这使我们能够添加新的待办事项。</li>
<li>行<code>toast.success('Todo added successfully!')</code>显示一个成功的toast通知，指示待办事项已成功添加。</li>
<li>行<code>toast.error('Todo field cannot be empty!')</code>在尝试提交时如果待办事项字段为空，则显示一个错误的toast通知。</li>
<li>如果<code>input</code>值（去除空格）不为空，则调用<code>addTodo</code>函数并传入输入值，清除<code>input</code>状态，并显示成功的toast通知。</li>
<li>如果<code>input</code>值为空，则显示一个错误的toast通知，指出待办事项字段不能为空。</li>
</ol>
<p>这段代码集成了<code>useTodo</code>钩子，通过context管理待办事项。它捕获用户输入，添加待办事项，并显示toast通知，以提供关于添加待办事项成功或失败的反馈。</p>
<p>现在，让我们也修改<code>&lt;TodoList /&gt;</code>组件，并在屏幕上显示待办事项。打开<code>components/TodoList.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/components/TodoList.tsx

import { useTodo } from '../context/useTodo'
import { SiStarship } from 'react-icons/si'

export const TodoList = () =&gt; {
  const { todos } = useTodo()

  if (!todos.length) {
    return (
      &lt;div className="max-w-lg px-5 m-auto"&gt;
        &lt;h1 className="flex flex-col items-center gap-5 px-5 py-10 text-xl font-bold text-center rounded-xl bg-zinc-900"&gt;
          &lt;SiStarship className="text-5xl" /&gt;
          You have nothing to do!
        &lt;/h1&gt;
      &lt;/div&gt;
    )
  }

  return (
    &lt;ul className="grid max-w-lg gap-2 px-5 m-auto"&gt;
      {todos.map(todo =&gt; (
        &lt;li key={todo}&gt;{todo}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}
</code></pre>
<ol>
<li>导入语句<code>import { useTodo } from '../context/useTodo'</code>从自定义context中导入<code>useTodo</code>钩子，使我们能够访问<code>todos</code>数组。</li>
<li>如果<code>todos</code>数组为空（<code>!todos.length</code>），意味着没有待办事项，将显示一条消息表明没有要做的事情。</li>
<li>如果<code>todos</code>数组中有待办事项，则渲染一个无序列表（<code>&lt;ul&gt;</code>）。</li>
<li>在<code>&lt;ul&gt;</code>内部，使用<code>map</code>函数遍历<code>todos</code>数组。对于每个待办事项，创建一个带有唯一<code>key</code>的列表项（<code>&lt;li&gt;</code>），<code>key</code>设置为待办事项的值。</li>
<li>然后将待办事项本身显示在列表项中。</li>
</ol>
<p>这个组件使用<code>useTodo</code>钩子从context中检索<code>todos</code>数组。如果没有待办事项，它会显示一条消息。如果有待办事项，它会渲染一个无序列表，并为每个待办事项填充列表项。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/ezgif-5-ff3ed7ffc5.gif" alt="添加待办事项和显示toast通知" width="600" height="400" loading="lazy"></p>
<p>上图显示添加待办事项和显示toast通知。</p>
<p>到目前为止做得很好！你现在有了一个基本的待办事项应用程序。是时候增加一些令人兴奋的功能，进一步提升你的应用了。</p>
<h2 id="how-to-define-an-interface-for-todo-items">如何为待办事项定义一个接口</h2>
<p>在这一部分中，你将基于上一节中的现有context进行构建，并增强它，以创建具有额外功能的更复杂的待办事项。</p>
<p>每个待办事项由三个属性组成：</p>
<ul>
<li><strong>id：</strong> 一个独特的字符串，作为该项的标识符</li>
<li><strong>text：</strong> 一个简单的字符串，代表待办事项的内容</li>
<li><strong>status：</strong> 待办事项的状态，可以是“未完成”或“已完成”</li>
</ul>
<p>基于上述信息，适当的待办事项接口如下所示：</p>
<pre><code class="language-ts">interface Todo {
  id: string
  text: string
  status: 'undone' | 'completed'
}
</code></pre>
<p>为了将Todo接口集成到你的context中，我们将进行必要的更新和修改，以有效地利用这个增强的context：</p>
<pre><code class="language-tsx">//📂./src/context/TodoContext.tsx

import React, { createContext, useState } from 'react'
import { nanoid } from 'nanoid'
import { useLocalStorage } from 'usehooks-ts'

interface TodoContextProps {
  todos: Todo[]
  addTodo: (text: string) =&gt; void
}

export interface Todo {
  id: string
  text: string
  status: 'undone' | 'completed'
}

export const TodoContext = createContext&lt;TodoContextProps | undefined&gt;(
  undefined,
)

export const TodoProvider = (props: { children: React.ReactNode }) =&gt; {
  const [todos, setTodos] = useState&lt;Todo[]&gt;([])

  // ::: ADD NEW TODO :::
  const addTodo = (text: string) =&gt; {
    const newTodo: Todo = {
      id: nanoid(),
      text,
      status: 'undone',
    }

    setTodos([...todos, newTodo])
  }

  const value: TodoContextProps = {
    todos,
    addTodo,
  }

  return (
    &lt;TodoContext.Provider value={value}&gt;{props.children}&lt;/TodoContext.Provider&gt;
  )
}
</code></pre>
<p>以下是context中变更的解释：</p>
<p><strong>Todo接口：</strong></p>
<ul>
<li>Todo接口定义了待办事项的结构。</li>
<li>它包括三个属性：id（一个字符串），text（一个代表待办事项内容的字符串），以及status（一个可以取值为'undone'或'completed'的字符串）。</li>
<li>这个接口有助于确保待办事项具有一致的属性和数据类型。</li>
</ul>
<p><strong>useState&lt;Todo[]&gt;：</strong></p>
<ul>
<li>useState钩子用于在函数组件中管理状态。</li>
<li>在这种情况下，<code>useState&lt;Todo[]&gt;</code>初始化了一个名为"todos"的状态变量作为Todo项目的数组。</li>
<li>"todos"状态变量将用于存储和更新待办事项。</li>
</ul>
<p><strong><code>addTodo</code>函数和<code>newTodo</code>变量：</strong></p>
<ul>
<li>addTodo函数是一个回调函数，它接受一个文本参数（字符串）。</li>
<li>在addTodo函数内部，声明了一个名为newTodo的变量作为Todo对象。</li>
<li>newTodo对象使用nanoid()函数生成的唯一id、提供的文本以及初始状态'undone'创建。</li>
<li>调用useState中的setTodos函数来更新todos状态，通过将newTodo对象添加到现有的todos数组中。</li>
<li>这允许向列表中添加新的待办事项。</li>
</ul>
<p>现在，你需要更新<code>&lt;TodoList /&gt;</code>组件以反映你对context所做的更改：</p>
<pre><code class="language-tsx">//📂./src/components/TodoList.tsx

import { useTodo } from '../context/useTodo'
import { SiStarship } from 'react-icons/si'

export const TodoList = () =&gt; {
  const { todos } = useTodo()

  if (!todos.length) {
    return (
      &lt;div className="max-w-lg px-5 m-auto"&gt;
        &lt;h1 className="flex flex-col items-center gap-5 px-5 py-10 text-xl font-bold text-center rounded-xl bg-zinc-900"&gt;
          &lt;SiStarship className="text-5xl" /&gt;
          You have nothing to do!
        &lt;/h1&gt;
      &lt;/div&gt;
    )
  }

  return (
    &lt;ul className="grid max-w-lg gap-2 px-5 m-auto"&gt;
      {todos.map(todo =&gt; (
        &lt;li key={todo.id}&gt;{todo.text}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}
</code></pre>
<p>通过这个更新的代码，现在每个渲染的待办事项的id被用作每个待办事项的key属性，待办事项的text被用来显示每个待办事项的内容。</p>
<p>现在，让我们创建一个自定义的React组件来适当地显示每个待办事项，并在我们的应用中引入诸如编辑、删除和更新单个待办事项等额外功能。</p>
<h2 id="how-to-build-a-custom-react-component-for-displaying-todo-items">如何构建一个自定义的React组件来显示待办事项</h2>
<p>在这一部分中，你将创建一个自定义的React组件，用于处理每个单独待办事项的显示和管理。</p>
<p>打开<code>components/TodoItem.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/components/TodoItem.tsx

export const TodoItem = (props: { todo: Todo }) =&gt; {
  const { todo } = props

  return (
    &lt;motion.li
      layout
      className={cn(
        'p-5 rounded-xl bg-zinc-900',
        todo.status === 'completed' &amp;&amp; 'bg-opacity-50 text-zinc-500',
      )}
    &gt;
      &lt;motion.span
        layout
        style={{
          textDecoration: todo.status === 'completed' ? 'line-through' : 'none',
        }}
      &gt;
        {todo.text}
      &lt;/motion.span&gt;
    &lt;/motion.li&gt;
  )
}
</code></pre>
<p><code>&lt;TodoItem /&gt;</code>负责渲染单个待办事项：</p>
<ul>
<li>该组件接受一个名为<code>props</code>的属性，这是一个包含名为<code>todo</code>的属性的对象。<code>todo</code>属性是<code>Todo</code>类型，代表单个待办事项。</li>
<li>在组件内部，使用解构赋值从<code>props</code>对象中提取<code>todo</code>属性。</li>
<li>使用Framer Motion的<code>motion.li</code>组件提供动画效果。它代表一个列表项（<code>&lt;li&gt;</code>），并支持布局动画。</li>
<li><code>className</code>属性使用<code>cn</code>实用函数（来自<code>classnames</code>库）根据<code>todo.status</code>条件性地应用CSS类。如果待办事项已完成，它会添加半透明背景和文本颜色的类。</li>
<li>在列表项内部，使用<code>motion.span</code>组件包裹待办事项文本。它同样支持布局动画。</li>
<li>span元素的样式根据<code>todo.status</code>设置。如果待办事项已完成，会应用删除线文本装饰。</li>
<li><code>{todo.text}</code>表达式渲染待办事项的文本内容。</li>
</ul>
<p>TodoItem接收一个待办事项作为属性，并根据待办事项的状态，使用可选动画、样式和条件CSS类进行渲染。</p>
<p>现在让我们修改<code>&lt;TodoList /&gt;</code>组件，以使用<code>&lt;TodoItem /&gt;</code>组件：</p>
<pre><code class="language-tsx">//📂./src/components/TodoList.tsx

import { TodoItem } from './TodoItem'
import { useTodo } from '../context/useTodo'
import { SiStarship } from 'react-icons/si'
import { motion } from 'framer-motion'

export const TodoList = () =&gt; {
  const { todos } = useTodo()

  if (!todos.length) {
    return (
      &lt;div className="max-w-lg px-5 m-auto"&gt;
        &lt;h1 className="flex flex-col items-center gap-5 px-5 py-10 text-xl font-bold text-center rounded-xl bg-zinc-900"&gt;
          &lt;SiStarship className="text-5xl" /&gt;
          You have nothing to do!
        &lt;/h1&gt;
      &lt;/div&gt;
    )
  }

  return (
    &lt;motion.ul className="grid max-w-lg gap-2 px-5 m-auto"&gt;
      {todos.map(todo =&gt; (
        &lt;TodoItem todo={todo} key={todo.id} /&gt;
      ))}
    &lt;/motion.ul&gt;
  )
}
</code></pre>
<p>以下是<code>&lt;TodoList /&gt;</code>中所做更改的解释：</p>
<p><strong>导入额外的依赖：</strong></p>
<ul>
<li>现在的代码从<code>framer-motion</code>库中导入了<code>motion</code>组件。这允许在组件中实现动画效果。</li>
</ul>
<p><strong>渲染TodoItem组件：</strong></p>
<ul>
<li>之前，待办事项被作为简单的列表项（<code>&lt;li&gt;</code>）直接在TodoList组件中渲染。</li>
<li>在更新的版本中，导入（<code>import { TodoItem } from './TodoItem'</code>）并使用TodoItem组件来渲染每个待办事项。</li>
<li>TodoItem组件传递了一个代表单个待办事项的<code>todo</code>属性。</li>
<li>同时为每个TodoItem组件提供了<code>key</code>属性，确保每个渲染的待办事项具有唯一标识符。</li>
</ul>
<p><strong>使用motion组件包裹列表：</strong></p>
<ul>
<li><code>&lt;ul&gt;</code>元素现在被<code>&lt;motion.ul&gt;</code>组件包裹，以使用<code>framer-motion</code>库启用动画效果。</li>
<li>这允许在添加、移除或更新待办事项时实现动态和平滑的过渡。</li>
</ul>
<p>总的来说，更新后的TodoList组件使用<code>framer-motion</code>的<code>motion</code>组件引入了动画，并用<code>&lt;TodoItem /&gt;</code>组件替换了直接渲染待办事项的方式。</p>
<p>现在你已经成功创建了<code>&lt;TodoItem /&gt;</code>组件，让我们将重点转向实现必要的功能，以启用使用Todo Context和TodoItem组件来编辑、删除和更新每个待办事项。</p>
<h2 id="how-to-implement-functionality-edit-delete-and-update-todo-items">如何实现功能：编辑、删除和更新待办事项</h2>
<p>在这一部分中，你将通过增加额外功能来增强你的待办事项应用。</p>
<p>首先，你将在待办事项context中实现处理这些功能所需的逻辑。然后，你将向<code>&lt;TodoItem /&gt;</code>组件添加相应的JSX，以引入交互性，并使用户能够与应用互动。</p>
<p>正如你所记得的，你使用context处理了向应用添加待办事项，你将采用类似的方法来处理编辑、删除和更新功能。</p>
<p>这些操作的逻辑将被封装在待办事项context中，将使用useTodo钩子在<code>&lt;TodoItem /&gt;</code>组件中利用这些逻辑。你还将把待办事项存储在浏览器的本地存储中，以确保用户离开应用时不会丢失他们的进度。</p>
<p>打开<code>context/TodoContext.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">// 📂./src/context/TodoContext.tsx

import React, { createContext } from 'react'
import { nanoid } from 'nanoid'
import { useLocalStorage } from 'usehooks-ts'

interface TodoContextProps {
  todos: Todo[]
  addTodo: (text: string) =&gt; void
  deleteTodo: (id: string) =&gt; void
  editTodo: (id: string, text: string) =&gt; void
  updateTodoStatus: (id: string) =&gt; void
}

export interface Todo {
  id: string
  text: string
  status: 'undone' | 'completed'
}

export const TodoContext = createContext&lt;TodoContextProps | undefined&gt;(
  undefined,
)

export const TodoProvider = (props: { children: React.ReactNode }) =&gt; {
  const [todos, setTodos] = useLocalStorage&lt;Todo[]&gt;('todos', [])

  // ::: ADD NEW TODO :::
  const addTodo = (text: string) =&gt; {
    const newTodo: Todo = {
      id: nanoid(),
      text,
      status: 'undone',
    }

    setTodos([...todos, newTodo])
  }

  // ::: DELETE A TODO :::
  const deleteTodo = (id: string) =&gt; {
    setTodos(prevTodos =&gt; prevTodos.filter(todo =&gt; todo.id !== id))
  }

  // ::: EDIT A TODO :::
  const editTodo = (id: string, text: string) =&gt; {
    setTodos(prevTodos =&gt; {
      return prevTodos.map(todo =&gt; {
        if (todo.id === id) {
          return { ...todo, text }
        }
        return todo
      })
    })
  }

  // ::: UPDATE TODO STATUS :::
  const updateTodoStatus = (id: string) =&gt; {
    setTodos(prevTodos =&gt; {
      return prevTodos.map(todo =&gt; {
        if (todo.id === id) {
          return {
            ...todo,
            status: todo.status === 'undone' ? 'completed' : 'undone',
          }
        }
        return todo
      })
    })
  }

  const value: TodoContextProps = {
    todos,
    addTodo,
    deleteTodo,
    editTodo,
    updateTodoStatus,
  }

  return (
    &lt;TodoContext.Provider value={value}&gt;{props.children}&lt;/TodoContext.Provider&gt;
  )
}
</code></pre>
<p>以下是正在发生的事情的解释：</p>
<p><strong>定义TodoContextProps：</strong></p>
<ul>
<li>TodoContextProps是一个接口，指定了TodoContext的值的结构。</li>
<li>它包括诸如todos（一个Todo项的数组）之类的属性，以及添加、删除、编辑和更新待办事项状态的函数。</li>
</ul>
<p><strong>实现<code>addTodo</code>：</strong></p>
<ul>
<li>addTodo函数接受一个文本参数，使用nanoid生成一个唯一ID，并用提供的文本和初始状态'undone'创建一个新的待办事项对象。</li>
<li>它使用useLocalStorage提供的setTodos函数，通过将newTodo追加到现有的todos数组来更新todos状态。</li>
</ul>
<p><strong>实现<code>deleteTodo</code>：</strong></p>
<ul>
<li>deleteTodo函数接受一个id参数，并使用setTodos函数从todos状态中过滤掉具有匹配id的待办事项。</li>
</ul>
<p><strong>实现<code>editTodo</code>：</strong></p>
<ul>
<li>editTodo函数接受一个id和文本参数。</li>
<li>它使用setTodos函数遍历todos状态，并更新具有匹配id的待办事项的文本。</li>
</ul>
<p><strong>实现<code>updateTodoStatus</code>：</strong></p>
<ul>
<li>updateTodoStatus函数接受一个id参数。</li>
<li>它使用setTodos函数遍历todos状态，并在'undone'和'completed'之间切换具有匹配id的待办事项的状态。</li>
</ul>
<p><strong>提供值并渲染子组件：</strong></p>
<ul>
<li>使用todos数组和定义的函数创建了一个value对象。</li>
<li>它作为value属性传递给TodoContext.Provider组件，以向其嵌套的子组件提供定义的值。</li>
</ul>
<p>总而言之，<code>TodoContext</code>和<code>TodoProvider</code>处理与管理待办事项相关的状态和逻辑。它们通过TodoContext提供必要的函数和数据供子组件使用，如<code>&lt;TodoItem /&gt;</code>，以执行添加、删除、编辑和更新待办事项等操作。</p>
<p>现在，让我们加入相应的JSX，使用户能够与你刚刚实现的逻辑进行交互。打开<code>components/TodoItem.tsx</code>并添加以下代码：</p>
<pre><code class="language-tsx">//📂./src/components/TodoItem.tsx

import { useEffect, useRef, useState } from 'react'
import { Todo } from '../context/TodoContext'
import { useTodo } from '../context/useTodo'
import { Input } from './Input'
import { BsCheck2Square } from 'react-icons/bs'
import { TbRefresh } from 'react-icons/tb'
import { FaRegEdit } from 'react-icons/fa'
import { RiDeleteBin7Line } from 'react-icons/ri'
import { toast } from 'react-hot-toast'
import cn from 'classnames'
import { motion } from 'framer-motion'

export const TodoItem = (props: { todo: Todo }) =&gt; {
  const { todo } = props

  const [editingTodoText, setEditingTodoText] = useState&lt;string&gt;('')
  const [editingTodoId, setEditingTodoId] = useState&lt;string | null&gt;(null)

  const { deleteTodo, editTodo, updateTodoStatus } = useTodo()

  const editInputRef = useRef&lt;HTMLInputElement&gt;(null)

  useEffect(() =&gt; {
    if (editingTodoId !== null &amp;&amp; editInputRef.current) {
      editInputRef.current.focus()
    }
  }, [editingTodoId])

  const handleEdit = (todoId: string, todoText: string) =&gt; {
    setEditingTodoId(todoId)
    setEditingTodoText(todoText)

    if (editInputRef.current) {
      editInputRef.current.focus()
    }
  }

  const handleUpdate = (todoId: string) =&gt; {
    if (editingTodoText.trim() !== '') {
      editTodo(todoId, editingTodoText)
      setEditingTodoId(null)
      setEditingTodoText('')
      toast.success('Todo updated successfully!')
    } else {
      toast.error('Todo field cannot be empty!')
    }
  }

  const handleDelete = (todoId: string) =&gt; {
    deleteTodo(todoId)
    toast.success('Todo deleted successfully!')
  }

  const handleStatusUpdate = (todoId: string) =&gt; {
    updateTodoStatus(todoId)
    toast.success('Todo status updated successfully!')
  }

  return (
    &lt;motion.li
      layout
      key={todo.id}
      className={cn(
        'p-5 rounded-xl bg-zinc-900',
        todo.status === 'completed' &amp;&amp; 'bg-opacity-50 text-zinc-500',
      )}
    &gt;
      {editingTodoId === todo.id ? (
        &lt;motion.div layout className="flex gap-2"&gt;
          &lt;Input
            ref={editInputRef}
            type="text"
            value={editingTodoText}
            onChange={e =&gt; setEditingTodoText(e.target.value)}
          /&gt;
          &lt;button
            className="px-5 py-2 text-sm font-normal text-orange-300 bg-orange-900 border-2 border-orange-900 active:scale-95 rounded-xl"
            onClick={() =&gt; handleUpdate(todo.id)}
          &gt;
            Update
          &lt;/button&gt;
        &lt;/motion.div&gt;
      ) : (
        &lt;div className="flex flex-col gap-5"&gt;
          &lt;motion.span
            layout
            style={{
              textDecoration:
                todo.status === 'completed' ? 'line-through' : 'none',
            }}
          &gt;
            {todo.text}
          &lt;/motion.span&gt;
          &lt;div className="flex justify-between gap-5 text-white"&gt;
            &lt;button onClick={() =&gt; handleStatusUpdate(todo.id)}&gt;
              {todo.status === 'undone' ? (
                &lt;span className="flex items-center gap-1"&gt;
                  &lt;BsCheck2Square /&gt;
                  Mark Completed
                &lt;/span&gt;
              ) : (
                &lt;span className="flex items-center gap-1"&gt;
                  &lt;TbRefresh /&gt;
                  Mark Undone
                &lt;/span&gt;
              )}
            &lt;/button&gt;
            &lt;div className="flex items-center gap-2"&gt;
              &lt;button
                onClick={() =&gt; handleEdit(todo.id, todo.text)}
                className="flex items-center gap-1 "
              &gt;
                &lt;FaRegEdit /&gt;
                Edit
              &lt;/button&gt;
              &lt;button
                onClick={() =&gt; handleDelete(todo.id)}
                className="flex items-center gap-1 text-red-500"
              &gt;
                &lt;RiDeleteBin7Line /&gt;
                Delete
              &lt;/button&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      )}
    &lt;/motion.li&gt;
  )
}
</code></pre>
<p>让我们关注<code>handleEdit</code>、<code>handleUpdate</code>、<code>handleDelete</code>和<code>handleStatusUpdate</code>函数及其工作方式：</p>
<p><strong><code>handleEdit</code>函数：</strong></p>
<p>当用户点击“编辑”按钮时调用此函数。它接受<code>todoId</code>（待办事项的唯一标识符）和<code>todoText</code>（待办事项当前文本）作为参数。</p>
<p>它将<code>editingTodoId</code>状态设置为<code>todoId</code>，将<code>editingTodoText</code>状态设置为<code>todoText</code>。此外，如果<code>editInputRef</code>（输入字段的引用）存在，它将使用<code>focus</code>方法将焦点设置在输入字段上。</p>
<p><strong><code>handleUpdate</code>函数：</strong></p>
<p>当用户在编辑待办事项后点击“更新”按钮时调用此函数。它接受<code>todoId</code>作为参数。</p>
<p>它首先检查修剪后的<code>editingTodoText</code>是否不为空。如果不为空，它将调用<code>useTodo</code>钩子中的<code>editTodo</code>函数，传递<code>todoId</code>和<code>editingTodoText</code>作为参数。然后将<code>editingTodoId</code>和<code>editingTodoText</code>状态分别重置为null和空字符串。</p>
<p>最后，如果更新成功则显示成功的toast消息，如果待办事项字段为空则显示错误的toast消息。</p>
<p><strong><code>handleDelete</code>函数：</strong></p>
<p>当用户点击“删除”按钮时调用此函数。它接受<code>todoId</code>作为参数。它将调用<code>useTodo</code>钩子中的<code>deleteTodo</code>函数，传递<code>todoId</code>作为参数。然后显示一条成功的toast消息，指示待办事项已成功删除。</p>
<p><strong><code>handleStatusUpdate</code>函数：</strong></p>
<p>当用户点击“标记完成”或“标记未完成”按钮时调用此函数。它接受<code>todoId</code>作为参数。</p>
<p>它将调用<code>useTodo</code>钩子中的<code>updateTodoStatus</code>函数，传递<code>todoId</code>作为参数。然后显示一条成功的toast消息，指示待办事项的状态已成功更新。</p>
<p>这些函数处理与在TodoItem组件中编辑、更新、删除和更新待办事项状态相关的交互和操作。</p>
<p>JSX显示待办事项的文本，并提供编辑、删除和更新其状态的选项。待办事项的外观和行为由<code>todo</code>对象的值和组件的状态变量决定。</p>
<p>如果待办事项正在被编辑，则显示输入字段和“更新”按钮。否则，将显示待办事项的文本，并提供标记为完成或未完成、编辑和删除的按钮。</p>
<p><code>handleEdit</code>、<code>handleUpdate</code>、<code>handleDelete</code>和<code>handleStatusUpdate</code>函数用作这些按钮的事件处理程序，使用户能够与待办事项进行交互和修改。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/ezgif-1-f7b9438717.gif" alt="Final todo app, a user adds an item, then edit and delete the todo item in order to display the app's functionality" width="600" height="400" loading="lazy"></p>
<p>以上是最终结果。</p>
<p>恭喜！你已经成功创建了一个具有基本功能的漂亮的待办事项应用。</p>
<p>通过本文所获得的知识，你现在已经准备好根据你的特定需求和偏好进一步增强和定制应用程序。</p>
<h2 id="conclusion">结论</h2>
<p>在整篇文章中，我们介绍了使用TypeScript进行React开发的基础知识，并学习了如何创建一个功能齐全的待办事项应用。</p>
<p>我们探索了状态管理、context和钩子等概念，使你能够添加、编辑、删除和更新待办事项。</p>
<p>有了这些知识，你现在已经准备好将这些原则应用到你的未来项目中，并使用React构建类型安全的应用程序。继续探索和实验新功能，将你的应用提升到一个新的水平。</p>
<p>你可以在<a href="https://twitter.com/Yazdun">Twitter</a>上关注我，我会在那里分享更多关于Web开发的有用提示。编码愉快！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 写给 React 开发者的 TypeScript 指南 ]]>
                </title>
                <description>
                    <![CDATA[ 如果你已经使用 React 一段时间，你会察觉 JavaScript 一些自由野性的天性让你难以驾驭（这当然不是 JS 的问题 😄），当你和团队协作的时候，这个特点尤为显著。 或许你不知道，你需要 TypeScript，至少试一下。 我先声明一下，我喜欢 JavaScript 的自由，甚至有相当长的时间，我“反对”使用 TypeScript。 我想和你一起探索 TypeScript 是否值得使用，还只是适合那些不怎么会写代码的人（这是我们团队内部玩笑）。 本文旨在介绍 TS 的基础以便你了解它的优势，决定是否使用它。本文的第二部分会介绍在 React 中的 TS。 目录  * 参考资料  * 为什么使用 ESLint、Prettier 和 Husky  * 何为 TypeScript  * 为什么要使用 TS  * 如何设置 TypeScript  * 购物清单项目示例 * TypeScript ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/typescript-for-react-developers/</link>
                <guid isPermaLink="false">6381ebef206aea0762969c1a</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Fri, 25 Nov 2022 10:19:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/11/typescript-cover.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/typescript-for-react-developers/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">TypeScript for React Developers – Why TypeScript is Useful and How it Works</a>
      </p><!--kg-card-begin: markdown--><p>如果你已经使用 React 一段时间，你会察觉 JavaScript 一些自由野性的天性让你难以驾驭（这当然不是 JS 的问题 😄），当你和团队协作的时候，这个特点尤为显著。</p>
<p><strong>或许你不知道，你需要 TypeScript，至少试一下。</strong></p>
<p>我先声明一下，我喜欢 JavaScript 的自由，甚至有相当长的时间，我“反对”使用 TypeScript。</p>
<p>我想和你一起探索 TypeScript 是否值得使用，还只是适合那些不怎么会写代码的人（这是我们团队内部玩笑）。</p>
<p>本文旨在介绍 TS 的基础以便你了解它的优势，决定是否使用它。本文的第二部分会介绍在 React 中的 TS。</p>
<h2 id="">目录</h2>
<ul>
<li><a href="#resources">参考资料</a></li>
<li><a href="#whyuseeslintprettierandhusky">为什么使用 ESLint、Prettier 和 Husky</a></li>
<li><a href="#whatistypescript">何为 TypeScript</a></li>
<li><a href="#whybotherdealingwithts">为什么要使用 TS</a></li>
<li><a href="#howtosetuptypescript">如何设置 TypeScript</a></li>
<li><a href="#sampleshoppinglistproject">购物清单项目示例</a>
<ul>
<li><a href="#typescriptmodules">TypeScript 模块</a></li>
<li><a href="#typescripttypes">TypeScript 类型</a>
<ul>
<li><a href="#inferenceintypescript">TypeScript 中的类型推论</a></li>
<li><a href="#anyandunknownintypescript">TypeScript 中的<code>any</code> 和 <code>unknown</code></a></li>
<li><a href="#arraysintypescript">TypeScript 中的数组</a></li>
<li><a href="#objectsintypescript">TypeScript 中的对象</a></li>
<li><a href="#aliasesintypescript">TypeScript 中的类型别名</a></li>
</ul>
</li>
<li><a href="#functionsintypescript">TypeScript 中的函数</a>
<ul>
<li><a href="#optionalparametersintypescript">TypeScript 中的可选参数</a></li>
</ul>
</li>
<li><a href="#typescriptenums">TypeScript 枚举</a></li>
<li><a href="#typescriptgenerics">TypeScript 泛型</a></li>
<li><a href="#tuplesintypescript">TypeScript 中的元组</a></li>
<li><a href="#classesintypescript">TypeScript 中的类</a></li>
<li><a href="#interfacesintypescript">TypeScript 中的接口</a></li>
<li><a href="#dommanipulationintypescript">TypeScript 中的 DOM 操作</a></li>
</ul>
</li>
<li><a href="#howtocombinereacttypescript">如何结合 React + TypeScript</a>
<ul>
<li><a href="#setup">设置</a></li>
<li><a href="#typingcomponentprops">设置组件 Props 类型</a>
<ul>
<li><a href="#reactbuiltintypes">React 内置类型</a></li>
<li><a href="#returntypeofareactcomponent">React 组件返回类型</a></li>
<li><a href="#combinationswithtemplateliterals">结合模板字面量</a></li>
<li><a href="#howtouseexclude">如何使用<code>Exclude</code></a></li>
<li><a href="#customhtmlcomponents">自定义 HTML 组件</a></li>
</ul>
</li>
<li><a href="#typinghooks">定义 hook 的类型</a>
<ul>
<li><a href="#usestatehook">useState hook</a></li>
<li><a href="#usereducerhook">useReducer hook</a></li>
<li><a href="#usecontext">useContext</a></li>
<li><a href="#userefhook">useRef hook</a></li>
</ul>
</li>
<li><a href="#forwardingref">传递 ref</a></li>
<li><a href="#howtousetypescriptgenericsinreact">如何在 React 中使用 TypeScript 泛型</a></li>
<li><a href="#typingacustomusefetchhook">定义自定义 useFetch Hook 类型</a></li>
</ul>
</li>
<li><a href="#conclusion">总结</a></li>
</ul>
<h2 id="resources">参考资料</h2>
<p>你可以从以下样板着手：</p>
<ul>
<li><a href="https://github.com/dastasoft/react-boilerplate/tree/cra-typescript">Create React App + TypeScript + ESLint + Prettier Boilerplate</a></li>
<li><a href="https://github.com/dastasoft/react-boilerplate/tree/vite-typescript">Vite + TypeScript + ESLint + Prettier Boilerplate</a></li>
</ul>
<p>如果你喜欢游戏编程，可以尝试 <a href="https://phaser.io/">PhaserJS</a>。你可以在浏览器通过创建游戏边玩边学 TypeScript 。</p>
<p>确保你也阅读了 <a href="https://www.typescriptlang.org/docs/handbook/intro.html">TS 官方文档</a>。里面包含大量有用的文档和案例。</p>
<p>另外还有两个示例项目，这样你就可以看到代码是如何实现的：</p>
<h3 id="">购物清单项目</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/shopping-list.jpg" alt="shopping-list" width="600" height="400" loading="lazy"></p>
<p>这是一个简单的体验 TypeScript 开发的项目，不需要 Webpack、React 以及任何其他组件，仅需要把 TypeScript 转换成 JavaScript。</p>
<ul>
<li><a href="https://shopping-list.dastasoft.com/">在线示例</a></li>
<li><a href="https://github.com/dastasoft/shopping-list">源码</a></li>
</ul>
<h3 id="">动漫预告片项目</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/animetrailers-screenshot.jpg" alt="animetrailers-screenshot" width="600" height="400" loading="lazy"></p>
<p>借助 <a href="https://jikan.moe/">JikanAPI</a>我搭建了一个简单的结合 React 和 TypeScript 的应用，该应用提供一系列动画和基本信息，你可以观看你最喜欢的动画的最新预告。</p>
<ul>
<li><a href="https://animetrailers.dastasoft.com/">在线示例</a></li>
<li><a href="https://github.com/dastasoft/animetrailers">源码</a></li>
</ul>
<h2 id="whyuseeslintprettierandhusky">为什么使用 ESLint、Prettier 和 Husky</h2>
<p>在样板中我使用了 Airbnb 的 ESlint 规则、Prettier 建议规则以及 Husky 的提前提交（pre-commit)行为。团队协作的时候，这样可以促使大家遵循同样的代码规则，即便你是单人作业或者学习开发，这样操作也会对你的项目有所助益。</p>
<p>有些 Airbnb 的规则可能会有些奇怪，但是规则都有注解和示例，你可以以此来决定采不采用，如果想要关闭某个规则，可以放在<code>.eslintrc</code>文件中。</p>
<p>这些规则对入门开发和刚刚开始使用 JS 或者 TS 的人来说非常有用。所以我建议你将它们纳入你的项目，尝试一下。 😉</p>
<h2 id="whatistypescript">何为 TypeScript</h2>
<p><a href="https://www.typescriptlang.org/">TypeScript</a>或者 TS 是由微软开发并且维护的开源语言，它具有以下特性：</p>
<ul>
<li>它是一个多范式语言（和 JavaScript 一样）；</li>
<li>它是 JavaScript 的一个替代品（更准确地说，是一个超集）；</li>
<li>它允许静态类型；</li>
<li>它具有额外的特性: 泛型（generics)、接口（interfaces）、元组（tuples）等，将在下文详细说明；</li>
<li>它允许阶段性采用（也就是你可以一个文件一个文件地将现有的项目改写成 TS，而不是一次性改变）；</li>
<li>你可以在前端和后端中使用（和 JS 一样）。</li>
</ul>
<p>浏览器不能解读 TS 代码，必须 <em>转译</em> 为 JS。JS 为动态类型映射值，而 TS 是静态类型，所以不易出错。</p>
<p>React 中已经是通过 <a href="https://babeljs.io/">Babel</a> 转译 JS 了，所以转译代码并不是 TS 额外的优势。</p>
<h2 id="whybotherdealingwithts">为什么要使用 TS</h2>
<p>问题就在这儿： 为什么要使用 TS，JS 不好用吗？你用得不开心吗？你不怕麻烦吗？正如上文所述，过去我们团队内部会取笑像 TS 这样带类型的语言（当时我还在使用 Java）。我们团队会说如果你需要类型，证明你不会正确地写代码。</p>
<p>TypeScript、Java 以及其他一些语言具备<strong>静态类型</strong>，也就是会定义变量的类型。一旦你将变量定义为 <em>string</em> 或者 <em>boolean</em> ，你就不能改变它的类型。</p>
<p>而 JavaScript 拥有<strong>动态类型</strong>。也就是说，变量一开始是字符串，之后可以变为布尔值、数字或者任意你想要的值。变量类型会在运行时动态分配。</p>
<p>当你浏览网络上的 TS 代码，你会看到……（语法糖）。</p>
<p><img src="https://blog.dastasoft.com/_next/image?url=%2Fassets%2Fposts%2Fcontent%2Ftypescript%2Fsyntaxsugar.jpeg&amp;w=1920&amp;q=75" alt="sintactic sugar" title="Syntactic Sugar, syntactic sugar everywhere." width="1000" height="420" loading="lazy"></p>
<p>回到我们团队的玩笑，当然这个说法<strong>没错</strong>：如果你知道自己在做什么，你不需要别人不断提醒你这是字符串，也只能是字符串，在某一刻它变成了布尔值或者其他类型……我知道自己在做什么！</p>
<p>真相是人非完人，总有这样的事情发生：</p>
<ul>
<li>赶进度的时候；</li>
<li>心情糟糕的时候；</li>
<li>周五的想法下周一再回顾的时候发现自己无法理解；</li>
<li>团队协作时，团队成员的技术和看法不在一个水平。</li>
</ul>
<p>出于同样的原因，我们使用 IDE、IDE 插件、代码高亮、linter 而不是记事本应用。TypeScript 和这些辅助工具一样。</p>
<p><img src="https://blog.dastasoft.com/_next/image?url=%2Fassets%2Fposts%2Fcontent%2Ftypescript%2Fairbnb.jpg&amp;w=1920&amp;q=75" alt="airbnb bugs" title="Airbnb claims that 38% of bugs on Airbnb could have been prevented by using TypeScript." width="1920" height="1440" loading="lazy"></p>
<h3 id="">一些常见的问题</h3>
<p>让我们看一看使用和不使用 TS 的一些对比示例：</p>
<h4 id="">拜托，我知道自己用的是什么！</h4>
<pre><code class="language-js">// App.js
import { MemoryRouter as Router } from 'react-router-dom';

import Routes from './routes';

export default function App() {
  return (
    &lt;Router basename="/my-fancy-app"&gt;
      &lt;Routes /&gt;
    &lt;/Router&gt;
  );
}
</code></pre>
<p>你知道上面代码块的问题出在哪儿吗？如果知道的话，请给自己一朵大红花！</p>
<p>这个文件在我的样板中存在了很长时间，这并不是一个 bug，但是…… <code>MemoryRouter</code>并不需要任何 <code>basename</code>。它出现的原因是我之前使用了<code>BrowserRouter</code>，所以需要<code>basename</code>属性。</p>
<p>如果使用 TS 你会被提示 <code>No overload matches this call</code> 告诉你有这个属性的组件并没有被签名。</p>
<p><strong>TypeScript 不仅可以使用静态类型，也可以帮助你决定是否需要其他的库</strong>。这里的库可以是第三方或者同事提供的组件和函数。</p>
<p>肯定会有一些声音——“了解正在使用的库不是必须么”，是的，你是对的。但是让参与项目的每个人都知道每个“外部”库以及版本的细微差别，可是艰巨的任务！</p>
<h4 id="">魔鬼标志变量</h4>
<pre><code class="language-javascript">let isVerified = false;
verifyAmount();

// isVerified = "false"
if (isVerified) proceedPayment();
</code></pre>
<p>我被这个问题困扰了很多次。虽然每次不是一模一样的代码，一些细微的差别，但是你可以从这个示例中体会我的用意：你设置一个布尔值变量来决定一些代码运不运行，但很有可能其他人（或者你自己）后来将布尔值变成了字符串，而非空字符串为真值。</p>
<p>如果使用 TypeScript，会出现报错: <code>The type 'string' is not assignable to the type 'boolean'</code>。代码在编译时就会出现这个报错，不需要等到运行时，那么在生产阶段出现这样的报错的机率非常低。</p>
<p>当然，和前文的规则一样，如果你正确编写代码，这个问题不会发生，如果你采用简洁代码的策略并且在编码的时候非常小心也可以避免这样的错误。<strong>TypeScript 并不是为了让我们偷懒，而是我们的好帮手</strong>，正如代码高亮可以帮助我们避免错误，找出不正常的变量。</p>
<h4 id="">我以为盒子里面的猫是活着的</h4>
<pre><code class="language-ts">const MONTH_SELECT_OPTIONS = MONTHS.map((month) =&gt; ({
  label: getMonthName(month),
  value: month
}));

export default function PaymentDisplayer() {
  const [currentMonthFilter, setCurrentMonthFilter] = useState(
    MONTH_SELECT_OPTIONS[0]
  );

  const onChangeHandler = (option) =&gt; {
    setCurrentMonthFilter(option.value);
  };

  return (
    &lt;select onChange={onChangeHandler}&gt;
      {MONTH_SELECT_OPTIONS.map(({ label, value }) =&gt; (
        &lt;option key="value" value={value}&gt;
          {label}
        &lt;/option&gt;
      ))}
    &lt;/select&gt;
  );
}
</code></pre>
<p>改变一个 state 的类型非常常见（虽然不建议这么做），有些时候会设置一个<code>isError</code> 标志变量，突然从布尔假值变成表示错误信息的字符串（也不建议这么做！）。还有一些时候是无意为之。</p>
<p>编写这段代码的人一开始认为 <code>currentMonthFilter</code> 会存储实际的选项，一个包含 label 和 value 的<code>HTMLOptionElement</code>。之后，同样的开发在另一个天（或者另一个开发）创建了 <code>changeHandler</code>函数并只设置了<code>value</code>而不是整个选项。</p>
<p>上述代码可以运行，为了方便学习我也做了简化，但是假设项目规模更大，特别是组件的行为是由 props 传递的时候，问题就复杂得多。</p>
<p>使用 TypeScript 可以从两个方面解决这个问题：</p>
<ul>
<li>当你将<code>currentMonthFilter</code>的<code>{label: string, value: number}</code>改成<code>number</code>时，静态类型会报错。</li>
<li>下一个步骤的开发人员，在调用服务检索包含这个筛选条件的付款时候，会通过 <em>IntelliSense</em>（VS Code 中的代码提示）了解他从 state 中获得什么类型，这个类型是否和服务匹配。</li>
</ul>
<p>所以使用 TypeScript，<strong>可以从 IDE 检查第三方库提供的函数、参数以及文档或者是同事编写的组件</strong>。</p>
<p>从上文例子中（可能不那么典型），我们可以得出，TypeScript 在 React 的环境中，可以帮助我们：</p>
<ul>
<li>代码前后一致，静态类型一致</li>
<li>提供文档记录，并通过 <em>IntelliSense</em> 了解可能性</li>
<li>尽早发现问题</li>
</ul>
<h2 id="howtosetuptypescript">如何设置 TypeScript</h2>
<p>在本文中我们将使用全局安装。因为我认为第一次探索 TypeScript 应该不受到 Webpack、React 等其他变量的干扰，这样才能更加了解 TypeScript 是如何运行和处理问题的。</p>
<h3 id="typescript">如何全局安装 TypeScript</h3>
<pre><code class="language-bash">npm install -g typescript

#或

yarn install --global typescript
</code></pre>
<h3 id="typescripttsc">TypeScript 编译器（tsc）是如何工作的</h3>
<p>在系统中安装好 TypeScript 之后，就可以使用 TypeScript 的编译器，使用 <code>tsc</code> 命令行。</p>
<p>让我们通过简单配置编译器测试一下：</p>
<ul>
<li>创建一个新的空文件夹。</li>
<li>放置一个<code>index.html</code>文件，文件内容是基础的 HTML5 结构。</li>
<li>在<code>index.html</code>同一层，创建一个空的<code>index.ts</code>文件。</li>
<li>打开终端，并输入<code>tsc --init</code>（假设你是全局安装 TypeScript），便会创建一个 <code>tsconfig.json</code>文件。（我们将在下一章详细探讨这个文件）</li>
</ul>
<p>你的文件夹结构如下：</p>
<pre><code class="language-sh">- index.html
- index.ts
- tsconfig.json
</code></pre>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta http-equiv="X-UA-Compatible" content="IE=edge" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;Document&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>你需要在 HTML 中添加 TS 文件，但是浏览器并不理解 TypeScript，只认识 JavaScript，所以你可以将<code>index.html</code>修改为：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta http-equiv="X-UA-Compatible" content="IE=edge" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;Document&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;&lt;/body&gt;
  &lt;script src="./index.js"&gt;&lt;/script&gt;
&lt;/html&gt;
</code></pre>
<p>打开一个新的终端，并输入<code>tsc</code>，你的 <code>index.ts</code> 文件将转换为 <code>index.js</code>，浏览器就可以理解。</p>
<p>为了不在每一次将 TS 文件转换为 JS 文件的时候都输入<code>tsc</code>，你可以将 TypeScript 设置为监控模式，使用<code>tsc -w</code>。</p>
<p>现在我建议你同时打开 TS 文件和 JS 文件，在<code>index.ts</code>文件中输入普通的 JS，测试输出是什么。（我们将在接下来的章节大量使用这样的方法）。</p>
<p><img src="https://blog.dastasoft.com/_next/image?url=%2Fassets%2Fposts%2Fcontent%2Ftypescript%2Fside-by-side.png&amp;w=1920&amp;q=75" alt="side by side" title="Do some test using tsc -w option" width="1281" height="640" loading="lazy"></p>
<h3 id="tsconfigjson"><code>tsconfig.json</code>里是什么</h3>
<p>跟着文章一边看一边实践的话，通过<code>tsc --init</code>命令，你将创建 <code>tsconfig.json</code>并包含默认配置和初始化的注解。</p>
<p>让我们看一看一些关键的属性：</p>
<ul>
<li><code>target</code>是 TS 代码将要转换成的 JS 的版本。版本主要取决于支持的浏览器，你可能需要使用比较早期版本的 JS。这也是很好的学习资源，你可以修改不同版本来看生成什么样的 JS 代码。</li>
<li><code>module</code> 设置模块的语法。 <code>commonjs</code>默认使用<code>require/module.exports</code>，现代 JS (ES6+)使用<code>import/export</code>。如果你希望使用 <code>import/export</code>，你需要将<code>target</code>设置为 ES6 或更高。 本文中的示例项目将使用这个语法。</li>
<li><code>lib</code>你需要指定你在项目中额外使用的库，检查额外的类型，如 DOM 相关。</li>
<li><code>jsx</code>如果使用 React，我们需要把这一项设置为<code>preserve</code>，也就是由另一个工具（即 Babel）来编译这 JSX，TSC 仅用于检查类型。你也可以设置为<code>react</code>或者<code>react-native</code>。这个配置决定是否使用 TSC 将你的 JSX 代码转换为常规的 JS 代码。大多数情况，我们将这个属性设置为<code>preserve</code>，将文件设置为常规的 JSX 并由 Babel 或者 Webpack 来处理编译工作。</li>
<li><code>outDir</code>是编译后文件存储的地方，例如大部分 React 项目会被存放在<code>build</code>文件。</li>
<li><code>rootDir</code> 是需要被编译的文件的位置，大部分 React 项目的位置为<code>./src</code>。</li>
<li><code>strict</code>开启一系列检查类型的规则，这些规则对"正确"的要求更为严格。我建议在学习阶段将它设置为 false，当你掌握得还不错了之后再开启。记住开启这个选项就是开启了 TS 的所有潜能，其中包含的一些选项你可以单独关闭。</li>
<li><code>include</code> 你想要编译的文件夹，如<code>src</code>文件夹。</li>
<li><code>exclude</code> 你不想要编译的文件夹，如<code>node_modules</code>文件夹。</li>
</ul>
<p>在示例中，我们将 <code>rootDir</code>设置为 <code>./src</code>， <code>outDir</code>设置为 <code>public</code>文件夹。</p>
<h2 id="sampleshoppinglistproject">购物清单项目示例</h2>
<p>项目示例很简单：你可以在购物清单中添加不同的物品、修改数量、删除物品以及查看需要买什么物品。</p>
<p>示例项目是为了让你适应 TypeScript 的工作流。一旦使用 React 环境，Webpack 和其他一些打包器就完成很多神奇的事情，所以我认为了解基础之后再接触 React 的打包器比较重要。</p>
<p>让我们来看看如何使用 TS 来获得一个更优并不容易出错的代码库。</p>
<h3 id="typescriptmodules"> TypeScript 模块</h3>
<p>你如果想使用 ES6 <code>import/export</code>模块，你必须设置 <code>tsconfig</code> ：</p>
<ul>
<li><strong>target</strong>: es6 or higher</li>
<li><strong>module</strong>: es2015 or later</li>
</ul>
<p>并且在<code>index.html</code> 文件中增加模块类型：</p>
<pre><code class="language-html">&lt;script type="module" src="app.js"&gt;&lt;/script&gt;
</code></pre>
<p>注意使用模块有两个弊端：</p>
<ul>
<li>和旧浏览器的兼容性不好</li>
<li>生产阶段的文件很分散，所以在文件间需要使用大量的请求（这可以通过使用 Webpack 这样的打包器来处理）。</li>
</ul>
<h3 id="typescripttypes">TypeScript 类型</h3>
<p>在 JavaScript 中，类型在运行时分配。当编译器遇到变量和值时候再决定它们的类型是什么。这就意味着我们可以这样做：</p>
<pre><code class="language-js">let job = 'Warrior'; // 字符串
let level = 75; // 数字
let isExpansionJob = false; // 布尔值

level = 'iLevel' + 75;
// 现在是一个字符串
</code></pre>
<p>在 TypeScript 中，类型在编译时分配，一旦一个类型被定义就受到该签名的保护：</p>
<pre><code class="language-ts">let job: string = 'Samurai';
let level: number = 75;
let isExpansionJob: boolean = true;

level = 'iLevel' + 75;
// Error, Type string cannot
// be assign to type number!
</code></pre>
<h4 id="inferenceintypescript">TypeScript 中的类型推论</h4>
<p>实际上不需要明确指定变量类型，TS 可以自行推断：</p>
<pre><code class="language-ts">let job = 'Samurai';
let level = 75;
let isExpansionJob = true;

level = 'iLevel' + 75;
// Error, Type string cannot
// be assign to type number!
</code></pre>
<p>在接下来的 React 项目中，我们也会看到类似的推论，如在使用<code>useState</code>的时候：</p>
<pre><code class="language-ts">const [currentMonthFilter, setCurrentMonthFilter] = useState('January');

useEffect(() =&gt; {
  setCurrentMonthFilter(1);
  // Error, Type number cannot
  // be assign to type string!
}, []);
</code></pre>
<h4 id="anyandunknownintypescript">TypeScript 中的`any`和`unknown`</h4>
<p>我一直说 TS 有静态类型，但有一个细微的点需要说明：</p>
<pre><code class="language-ts">let level: any = 10;

level = 'iLevel' + 125;
// OK, still type any

level = false;
// OK, still type any
</code></pre>
<p>欢迎回到 JavaScript！<code>any</code> 是一种动态类型，当你不知道这个变量在未来的值为何时可以使用，当然这也就放弃掉了 TS 提供的所有优势。</p>
<pre><code class="language-ts">let level: any = 10;

level = 'iLevel' + 125;

level = false;

let stringLevel: string = level;
console.log(typeof stringLevel);
stringLevel.replace('false', 'true');
</code></pre>
<p>当你将 <code>level</code>分配给<code>stringLevel</code>时，变量类型并没有变成<code>string</code>，而是保持布尔值。所以<code>replace</code>函数并不存在，代码在运行时失效，你会收到报错： <code>Uncaught TypeError: stringLevel.replace is not a function</code>。</p>
<p>在这种情况下我们可以使用比<code>any</code>更安全的替换方案:</p>
<pre><code class="language-ts">let level: unknown = 10;

level = 'iLevel' + 125;

level = false;

let stringLevel: string = level;
// Error
</code></pre>
<p><code>unknown</code>和<code>any</code>一样，可以分配任何类型，当但你想要将它分配给另外一个变量时，编译器会报错。所以当你不知道变量未来是什么类型的值时，使用 <code>unknown</code> 而不是 <code>any</code>。</p>
<h4 id="arraysintypescript">TypeScript 中的数组</h4>
<pre><code class="language-ts">let job = 'Red Mage';
let level = 75;
let isExpansionJob = false;
let jobAbilities = ['Chainspell', 'Convert'];

jobAbilities.push('Composure'); // OK
jobAbilities.push(2); // Error
jobAbilities[0] = 2; // Error
</code></pre>
<p>在上面例子中，我们定义了一个由字符串组成的数组：<code>jobAbilities</code>，我们可以在这个数组中添加其他的字符串，但是不能添加其他类型的值，也不能将当前值转换成其他类型。因为在声明数组时，我们将类型推论设置为了 <code>string[]</code>。</p>
<pre><code class="language-ts">let job = 'Red Mage';
let level = 75;
let isExpansionJob = false;
let jobAbilities = ['Chainspell', 'Convert'];
let swordSkill = ['B', 5, 144, 398];

swordSkill.push('B+'); // OK
swordSkill.push(230); // OK

swordSkill[1] = 'C';
// OK, the type is not position related

swordSkill.push(true); // Error
</code></pre>
<p>和之前的例子一样，声明时类型推论就形成。所以我们声明了一个由字符串和数组组成的数组<code>swordSkill</code>。</p>
<p>如果你希望指定声明数组的类型，可以：</p>
<pre><code class="language-ts">let jobAbilities: string[] = ['Chainspell', 'Convert'];
let swordSkill: (string | number)[] = ['B', 5, 144, 398];
</code></pre>
<p><code>|</code> 是 <code>union</code>（联合声明）不同的类型。</p>
<h4 id="objectsintypescript">TypeScript 中的对象</h4>
<p>让我们回到例子，不过这一次是以对象的形式：</p>
<pre><code class="language-ts">let job = {
  name: 'Summoner',
  level: 75,
  isExpansion: true,
  jobAbilities: ['Astral Flow', 'Elemental Siphon']
};

job.name = 'Blue Mage'; // OK
job.level = 'Four'; // Error
job.avatars = ['Carbuncle']; // Error
</code></pre>
<ul>
<li><code>job.level = "Four"</code> 不可以实现，因为我们不可以修改属性的类型。对象的属性也是静态类型。</li>
<li><code>job.avatars = ["Carbuncle"]</code> – 我们不能增加新的属性，因为 <code>job</code> 对象已经拥有一个类型和定义好的结构。</li>
</ul>
<pre><code class="language-ts">let job = {
  name: 'Summoner',
  level: 75,
  isExpansion: true,
  jobAbilities: ['Astral Flow', 'Elemental Siphon']
};

job = {
  name: 'Blue Mage',
  level: 4,
  isExpansion: true,
  jobAbilities: ['Azure Lore', 'Burst Affinity']
}; // OK

job = {
  name: 'Corsair',
  level: 25,
  isExpansion: true
}; // Error
</code></pre>
<p>我们可以分配另一个对象，因为对象是由 <code>let</code> 声明的，但必须是一模一样的形式。</p>
<p>停下来思考一下：有多少次你在前端的工作中，在没有检查的情况下，像这样重复对象结构？有多少次你犯过如<code>data.descrption</code>这样的拼写错误，几天之后你发现问题？如果没有发生过，我保证这种问题迟早会发生。</p>
<p>让我们看看如何指定具体类型：</p>
<pre><code class="language-ts">let job: {
  name: string;
  level: number;
  isExpansion: boolean;
  jobAbilities: string[];
} = {
  name: 'Summoner',
  level: 75,
  isExpansion: true,
  jobAbilities: ['Astral Flow', 'Elemental Siphon']
};
</code></pre>
<p>对于一个简单的对象来说，这样可能有一点复杂了，所以我们可以使用<code>类型别名</code>（Type Aliases）。</p>
<h4 id="aliasesintypescript">TypeScript 中的类型别名</h4>
<pre><code class="language-ts">type Job = {
  name: string;
  level: number;
  isExpansion: boolean;
  jobAbilities: string[];
};

let Summoner: Job = {
  name: 'Summoner',
  level: 75,
  isExpansion: true,
  jobAbilities: ['Astral Flow', 'Elemental Siphon']
};

let BlueMage: Job = {
  name: 'Blue Mage',
  level: 4,
  isExpansion: true,
  jobAbilities: ['Azure Lore', 'Burst Affinity']
};
</code></pre>
<p>使用类型别名可以定义可复用的常见类型。在 React、DOM 和其他的库中有很多这种即用型定义类型。</p>
<h3 id="functionsintypescript"> TypeScript 中的函数</h3>
<p>TS 中的函数语法和 JS 类似，但是你可以指定参数类型和返回类型。</p>
<pre><code class="language-ts">type Enemy = {
  name: string;
  hp: number;
  level: number;
  exp: number;
};

let attack = (target: Enemy) =&gt; {
  console.log(`Attacking to ${target.name}`);
};

attack = 'Hello Enemy'; // Error
</code></pre>
<p>在示例中我使用了箭头函数，你也可以使用普通的函数声明。JS 和 TS 函数的两大不同是：</p>
<ul>
<li>你需要指定传入参数的类型，如 <code>target: Enemy</code>。</li>
<li><code>attack</code>变量已经设定了返回类型，之后就不能修改。</li>
</ul>
<p>函数的类型可以这样声明：</p>
<pre><code class="language-ts">let attack = (target: Enemy): void =&gt; {
  console.log(`Attacking to ${target.name}`);
};
</code></pre>
<p>当返回值为空的时候可以用<code>void</code>类型，也不需要指定特定类型。</p>
<pre><code class="language-ts">// let attack = (target: Enemy): number =&gt; {
let attack = (target: Enemy) =&gt; {
  return target.hp - 2;
};
</code></pre>
<p><code>any</code>和<code>void</code>类型有一些不同：</p>
<pre><code class="language-ts">let attack = (target: Enemy): void =&gt; {
  console.log(`Attacking to ${target.name}`);
};

attack = (target: Enemy): number =&gt; {
  return target.hp - 2;
};

// lizard has 200hp
console.log(attack(lizard)); // 198
</code></pre>
<p>上面的示例没有报错：即便你认为将 <code>attack</code>从<code>(target: Enemy) =&gt; void</code>变成了 <code>(target: Enemy) =&gt; number</code>，类型实际上还是 <code>void</code>。</p>
<p>尝试一下如果首先使用 <code>number</code>来定义函数会怎么样：</p>
<pre><code class="language-ts">let attack = (target: Enemy) =&gt; {
  return target.hp - 2;
};

attack = (target: Enemy) =&gt; {
  console.log(`Attacking to ${target.name}`);
}; // Error

let attackResult = attack(lizard);
</code></pre>
<p><code>Type '(target: Enemy) =&gt; void' is not assignable to the type '(target: Enemy) =&gt; number'</code>. <code>Type 'void' is not assignable to the type 'number'</code>，所以在这个情况下 <code>void</code> 和 <code>any</code>类似。</p>
<p><code>attackResult</code>的类型为 <code>number</code>，没有必要重新指定，TS 会通过函数的返回类型完成推论。</p>
<h4 id="optionalparametersintypescript">TypeScript 中的可选参数</h4>
<p>使用 <code>?</code>来定义 TS 函数中的可选参数：</p>
<pre><code class="language-ts">let heal = (target: Player | Enemy, spell: Spell, message?: string) =&gt; {
  if (message) console.log(message);
  return target.hp + spell.power;
};

heal(player1); // Error
heal(player1, cure, 'Healing player1'); // OK
heal(skeleton, cure); // OK
</code></pre>
<p>第一个函数调用不成功是因为我们必须至少传入两个参数，后面两次调用成功。 <code>message</code>是一个可选参数，如果没有传入的话，就被定义为<code>undefined</code>。</p>
<p>这个示例转换为 JS 为：</p>
<pre><code class="language-ts">let heal = (target, spell, message) =&gt; {
  if (message) console.log(message);
  return target.hp + spell.power;
};

heal(player1); // Error
heal(player1, cure, 'Healing player1'); // OK
heal(skeleton, cure); // OK
</code></pre>
<p>两个函数的基本行为一致，只是 JS 的问题会在运行时显示出来，第一个调用出错的原因是不可以从一个没有定义的值获取 <code>power</code>。</p>
<p>从示例中我们可以发现，TS 的函数更安全，因为你不需要依赖外部环境，也清楚知道需要传入什么参数。</p>
<p>这对于其他使用这个函数的开发者说也是一样的，他们知道需要使用什么参数、形式以及会返回什么参数。</p>
<h3 id="typescriptenums">TypeScript 枚举（Enum）</h3>
<p>使用枚举，我们可以定义一个常量集合。</p>
<pre><code class="language-ts">enum BattleMenu {
  ATTACK,
  MAGIC,
  ABILITIES,
  ITEMS,
  DISENGAGE
}

enum Equipment {
  WEAPON = 0,
  HEAD = 1,
  BODY = 2,
  HANDS = 3,
  LEGS = 4
}

console.log(BattleMenu.ATTACK, Equipment.WEAPON);
// 0 0
</code></pre>
<p>枚举默认是自动序列化的，示例中的两种声明方式对等。</p>
<p>也可以使用枚举来存储字符串，我常在 React 中使用枚举来存储路径：</p>
<pre><code class="language-ts">enum Routes {
  HOME = '/',
  ABOUT = '/about',
  BLOG = '/blog'
}
</code></pre>
<h3 id="typescriptgenerics"> TypeScript 泛型（Generics）</h3>
<pre><code class="language-ts">const getPartyLeader = (memberList: Player[]) =&gt; {
  return memberList[0];
};

const partyLeader = getPartyLeader(partyA);
</code></pre>
<p>上面代码想要实现一个 <code>getPartyLeader</code> 函数，返回党魁（数组的第一位）。</p>
<p>如果我们想要函数支持除 <code>Player</code>以外的类型呢？根据我们所知的信息，可以采取这样的做法：</p>
<pre><code class="language-ts">const getPartyLeader = (memberList: Player[] | Enemy[]) =&gt; {
  return memberList[0];
};

const partyLeader = getPartyLeader(partyA);
// Player[] | Enemy[]
</code></pre>
<p>现在我们可以传入 <code>Player</code>组也可以传入 <code>Enemy</code>组，但是 <code>PartyLeader</code>常数可以为两组中任意一种类型，所以使用 <code>Player[] | Enemy[]</code>。</p>
<p>如果我们想要使用指定类型的话，也可以使用泛型：</p>
<pre><code class="language-ts">const getPartyLeader = &lt;T&gt;(memberList: T[]) =&gt; {
  return memberList[0];
};

const partyLeader = getPartyLeader(partyA); // Player
</code></pre>
<p><code>partyA</code> 数组内是<code>Player</code>类型， <code>partyLeader</code> 就为 <code>Player</code>类型。 让我们查看语法：</p>
<ul>
<li><code>T</code> 是通常定义泛型的方法，但是你可以采用任意你喜欢的方式。</li>
</ul>
<p>和使用 <code>any</code>一样， T 接收任意类型。所以我们可以修改传入的参数类型：</p>
<pre><code class="language-ts">type Player = {
  name: string;
  hp: number;
};

type Enemy = {
  name: string;
  hp: number;
};

type Spell = {
  name: string;
  power: number;
};

const getPartyLeader = &lt;T extends { hp: number }&gt;(memberList: T[]) =&gt; {
  return memberList[0];
};

const playerPartyLeader = getPartyLeader(partyOfPlayers); // Ok
const enemyPartyLeader = getPartyLeader(partyOfEnemies); // Ok
const whatAreYouTrying = getPartyLeader(spellList); // Error
</code></pre>
<p>我们只能传入包含<code>hp</code>属性的对象。</p>
<h3 id="tuplesintypescript">TypeScript 中的元祖（Tuples）</h3>
<p>正如我们之前看到的，数组可以包含不同的类型但不受位置限制。元组可以补充这一点。</p>
<pre><code class="language-ts">type Weapon = {
  name: string;
  damage: number;
};

type Shield = {
  name: string;
  def: number;
};

const sword: Weapon = {
  name: 'Onion Sword',
  damage: 10
};

const shield: Shield = {
  name: 'Rusty Shield',
  def: 5
};

let equipment: [Weapon, Shield, boolean];

equipment = [sword, shield, true]; // OK
equipment[2] = false; // OK

equipment = [shield, sword, false]; // Error
equipment[1] = true; // Error
</code></pre>
<p>这样我们就拥有了类数组的类型，它关心类型的放置位置。</p>
<h3 id="classesintypescript"> TypeScript 中的类</h3>
<p>由于从 ES6 开始 JS 中添加了类，TS 和 JS 的类大同小异：</p>
<pre><code class="language-ts">class Job {
  public name: string;
  private level: number;
  readonly isExpansion: boolean;

  constructor(name: string, level: number, isExpansion: boolean) {
    this.name = name;
    this.level = level;
    this.isExpansion = isExpansion;
  }
}

const whiteMage = new Job('White Mage', 75, false);

console.log(whiteMage.name); // "White Mage"
console.log(whiteMage.level); // Error
console.log(whiteMage.isExpansion); // false

whiteMage.name = 'Blue Mage'; // Ok
whiteMage.level = 50; // Error
whiteMage.isExpansion = true; // Error
</code></pre>
<p>在 TS 类中，你可以访问类属性的修饰符（modifiers）：</p>
<ul>
<li><strong>public</strong> - 可以自由访问属性和方法，这时 TS 类的默认值</li>
<li><strong>private</strong> - 只能从声明它的类访问</li>
<li><strong>protected</strong> - 限制声明类和子类访问</li>
<li><strong>readonly</strong> - 标记属性为不可变</li>
</ul>
<h3 id="interfacesintypescript">TypeScript 中的接口（interfaces）</h3>
<p>和 <code>类型别名</code>（type aliases)相同，我们可以使用<code>接口</code>（interfaces）来定义类：</p>
<pre><code class="language-ts">interface Enemy {
  name: string;
  hp: number;
}

let attack = (target: Enemy): void =&gt; {
  console.log(`Attacking to ${target.name}`);
};
</code></pre>
<p>是不是看上去和<code>类型别名</code>很像？那应该使用哪一个呢？两种方法都可以控制不同类型的 TS，并且区别非常小：</p>
<p>你可以遵循以下规则来做取舍：</p>
<ul>
<li>如果你编写面向对象的代码，可以使用接口，如果你编写函数式代码，可以使用 aliases。</li>
<li>公共 API 库、组件类型、state、JSX 使用接口。</li>
</ul>
<p>因此，我在样板中加入了 ESLint 自动将类型别名转换为接口。</p>
<p>如果想要深入了解两者的区别，可以阅读<a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces">TS 手册中的这篇文章</a> ，但现在很多使用接口的功能都可以使用类型别名，反之亦然。</p>
<h3 id="dommanipulationintypescript">TypeScript 中的 DOM 操作</h3>
<p>虽然在 React 中直接操作 DOM 的机会不多，但是我觉得还是有必要知道 DOM 的相关知识。</p>
<h4 id="dom">如何从 DOM 检索元素</h4>
<pre><code class="language-ts">// HTMLFormElement | null
const form = document.querySelector('form');

// HTMLElement | null
const otherForm = document.getElementById('myFancyForm');

// HTMLSelectElement
const select = document.createElement('select');
</code></pre>
<p>执行<code>document.querySelector ("form")</code>时， 常量<code>form</code>被类型推论为<code>HTMLFormElement</code> 或 <code>null</code>。 但在第二个例子中，我们通过 ID 来获取 dom，TS 并不知道是什么 HTML 元素，所以推论为泛型 <code>HTMLElement</code>。</p>
<pre><code class="language-ts">const form = document.querySelector('form');

form.addEventListener('submit', (e: Event) =&gt; {
  e.preventDefault();
  console.log(e);
}); // Error
</code></pre>
<p>TS 不知道是否能够通过查询选择器在 HTML 找到元素，所以不能对一个可能为 null 的类型添加 <code>addEventListener</code> 函数，你可以这样修改：</p>
<p>我确认会找到元素：</p>
<pre><code class="language-ts">// HTMLFormElement
const form = document.querySelector('form')!;
</code></pre>
<p>使用 <code>!</code> 告诉 TS 放心，一定不会是<code>null</code>。</p>
<p>如果不为 null 运行：</p>
<pre><code class="language-ts">const form = document.querySelector('form');

form?.addEventListener('submit', (e: Event) =&gt; {
  e.preventDefault();
  console.log(e);
});
</code></pre>
<p>你可能在 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining">JS 可选链式运算符</a>中见过 “?”。</p>
<p>是铸造类型的时候了：</p>
<pre><code class="language-ts">const otherForm = document.getElementById('myFancyForm') as HTMLFormElement;

otherForm.addEventListener('submit', (e: Event) =&gt; {
  e.preventDefault();
  console.log(e);
});
</code></pre>
<p>通过 <code>HTMLFormElement</code> 告诉 TS 会找到什么类型的元素，而不是 <code>null</code>。</p>
<h2 id="dommanipulationintypescript"> 如何结合React + TypeScript</h2>
<p>让我们进入文章的第二个部分，记住，在第一个部分我们探讨了为什么使用 TypeScript，如何使用，以及这个语言的概览。</p>
<p>在这个部分，我们将学习如何在 React 中使用 TypeScript，如何解决相应的难题，以及如何使用 React 和 TypeScript 共同创建一个应用。</p>
<h3 id="setup">设置</h3>
<h4 id="createreactapp">Create React App</h4>
<p>对于 <a href="https://create-react-app.dev">CRA</a> 用户来说，你们只需要设定模板：</p>
<pre><code class="language-bash">npx create-react-app my-awesome-project --template typescript
</code></pre>
<h4 id="vite">Vite</h4>
<p>使用 <a href="https://vitejs.dev">Vite</a> 创建 TypeScript 项目和使用 CLI 一样简单，只需要选择 TypeScript 模板：</p>
<pre><code class="language-bash">npm create vite@latest my-awesome-project
</code></pre>
<h4 id="">添加到现有项目</h4>
<p>如果你想要对已经存在的 JavaScript 项目添加 TypeScript，只需要添加对应开发依赖项：</p>
<pre><code class="language-bash">npm install -D typescript
</code></pre>
<p>需要提醒你的事，如果是首次使用 TypeScript，不建议你从现有的项目着手。因为这样的话，你会不断地认为你已经有一个可以运行的项目了，而使用 TypeScript 不过是做些无所谓的工作。你没办法从中体会 TypeScript 的优势。</p>
<h3 id="typingcomponentprops">设置组件 Props 类型</h3>
<p>在 React 项目中使用 TypeScript 最常用的场景是编写组件 props。</p>
<p>想要正确地编写组件 props，必须定义清楚组件接受什么样的 props、props 类型以及是否是必要的。</p>
<pre><code class="language-ts">// src/components/AnimeDetail/Cover/index.tsx

type CoverProps = {
  url: string;
};

export default function Cover({ url }: CoverProps) {
  // ...
}
</code></pre>
<p>我们只使用 <code>url</code> prop ，类型为 <code>string</code> 并且是强制的。</p>
<p>另一个有多个 props 和可选项的例子：</p>
<pre><code class="language-ts">// src/components/AnimeDetail/StreamingList/PlatformLink/index.tsx

type PlatformLinkProps = {
  name: string;
  url?: string;
};

export default function PlatformLink({ name, url }: PlatformLinkProps) {
  // ...
}
</code></pre>
<p>使用 <code>?</code> 来定义可选参数， TypeScript 知道在这个例子中<code>url</code> 的类型是 <code>string</code>，默认值为<code>undefined</code>，即便未传入<code>url</code> ，消费组件也不会报错。</p>
<p>让我们看一个更复杂的例子：</p>
<pre><code class="language-ts">// src/components/AnimeDetail/Detail/index.tsx

type AnimeType = 'TV' | 'Movie';

type DetailProps = {
  liked: boolean;
  toggleFav: () =&gt; void;
  title: string;
  type: AnimeType;
  episodeCount: number;
  score: number;
  status: string;
  year: number;
  votes: number;
};

export default function Detail({
  liked,
  toggleFav,
  title,
  type,
  episodeCount,
  score,
  status,
  year,
  votes
}: DetailProps) {
  // ...
}
</code></pre>
<p>这次包含众多类型，包括 <code>function</code>和一个自定义类型 <code>AnimeType</code>。</p>
<p>所以总结一下，使用 TS 来编写 props：</p>
<ul>
<li>对于消费组件来说的 props 验证
<ul>
<li>不需要猜测组件需要什么</li>
<li>不需要打开组件源码来检查需要什么数据</li>
</ul>
</li>
<li>自动填充和文档记录
<ul>
<li>直接从消费组件端知道自动填充的 prop 和 value，不需要提前浏览</li>
</ul>
</li>
</ul>
<p><img src="https://blog.dastasoft.com/_next/image?url=%2Fassets%2Fposts%2Fcontent%2Ftypescript2%2Fautocomplete.webp&amp;w=1920&amp;q=75" alt="autocomplete" width="1920" height="527" loading="lazy"></p>
<p>如果是使用复杂的组件，或是从第三方库使用组件的，这一定可以派上用场。</p>
<h4 id="reactbuiltintypes"> React 内置类型</h4>
<p>在 React 和很多其他的库中，你会发现大量预置的类型，可以减轻开发者的编写负担。如以下示例：</p>
<pre><code class="language-ts">// src/components/Layout/index.tsx

type LayoutProps = {
  children: React.ReactNode;
};

export default function Layout({ children }: LayoutProps) {
  // ...
}
</code></pre>
<p>一个自定义的 React 组件，接受其他元素作为子元素，在这种情况下 <code>children</code>被定义为<code>ReactNode</code> 类型。</p>
<h5 id="reactfcreactfunctioncomponent">一个关于 React.FC 和 React.FunctionComponent 的提醒</h5>
<p>你可以能会遇到这样定义组件 props 的语法：</p>
<pre><code class="language-ts">type PlatformLinkProps = {
  name: string;
  url?: string;
};

const PlatformLink: React.FC&lt;PlatformLinkProps&gt; = ({ name, url }) =&gt; {
  // ...
};
</code></pre>
<p>使用 <code>React.FC</code>或者使用 <code>React.FunctionComponent</code>时，上面的代码生效，但是你需要知道这样使用的弊端，也就是为什么在本文中我们不这样用：</p>
<ul>
<li>必须使用函数表达式而不是函数声明，虽然这是一个很小的点，但是我的编码习惯是使用函数声明。</li>
<li>不可以使用泛型（之后会讨论）。</li>
<li>在过去，这样会导致 props 非直接地接受<code>children</code>属性，即便该组件并不使用这个属性。直到 React18 之后，这个问题才改善。</li>
</ul>
<h4 id="returntypeofareactcomponen">React 组件的返回类型</h4>
<p>最后一块拼图，组件返回什么？可以使用内置的类型：<code>React.ReactElement</code>、 <code>React.ReactNode</code>和 <code>JSX.Element</code>：</p>
<pre><code class="language-ts">export default function Favorites(): JSX.Element {
  // ...
}
</code></pre>
<p>总结一下：<strong>让 TypeScript 自行推论返回类型</strong>。如果你需要这一部分的详细介绍，我推荐<a href="https://stackoverflow.com/questions/58123398/when-to-use-jsx-element-vs-reactnode-vs-reactelement">阅读这个来自 stack overflow 的帖子</a>。</p>
<h4 id="combinationswithtemplateliterals">集合模板字面量 </h4>
<p>在<a href="https://animetrailers.dastasoft.com/">动漫预告片项目</a>中，我引入的一个自定义 UI 就是很好的示例。你可以查看<code>src/components/UI</code>中的组件，其中大部分内容都会在本文讨论：</p>
<p>让我们看一下自定义组件<code>Position</code>：</p>
<pre><code class="language-ts">// src/components/UI/Position/index.tsx

import React from 'react';

import { StyledPosition } from './StyledPosition';

type VPosition = 'top' | 'bottom';
type HPositon = 'left' | 'right';

export type PositionValues = `${VPosition}-${HPositon}`;

type PositionProps = {
  children: React.ReactNode;
  position?: PositionValues;
};

export default function Position({
  children,
  position = 'top-right'
}: PositionProps) {
  return &lt;StyledPosition position={position}&gt;{children}&lt;/StyledPosition&gt;;
}
</code></pre>
<p>Position 是一个简单的组件，可以与任何其他具有绝对位置的组件一起使用，可以通过 <code>top-left</code>, <code>top-right</code>, <code>bottom-left</code>和 <code>bottom-right</code>将组件放置在四个边上。</p>
<p>使用字面量模板来创建新的类型并不新鲜，在这里有趣的地方是结合 <code>${VPosition}-${HPositon}</code>和联合类型 <code>top</code> | <code>bottom</code> ，TypeScript 会自动生成所有组合，就可以创建我们需要的四种类型。</p>
<h4 id="howtouseexclude">如何使用 `Exclude`</h4>
<p>让我们给上面的例子添加更多值：</p>
<pre><code class="language-ts">type VPosition = 'top' | 'middle' | 'bottom';
type HPositon = 'left' | 'center' | 'right';

export type PositionValues = `${VPosition}-${HPositon}`;
</code></pre>
<p>模板会创建所有可能的组合，所以我们将拥有 <code>"top-left" | "top-center" | "top-right" | "top-left" | "center-left" | "center-right" | "bottom-left" | "bottom-center" | "bottom-right"</code>。</p>
<p>有一条比较奇怪： <code>middle-center</code>，我们只需要<code>center</code>，这时就可以使用 <code>Exclude</code> ：</p>
<pre><code class="language-ts">type PositionValues =
  | Exclude&lt;`${VPosition}-${HPositon}`, 'middle-center'&gt;
  | 'center';
</code></pre>
<p>这时 <code>PositionValues</code>会生成<code>"center" | "top-left" | "top-center" | "top-right" | "middle-left" | "middle-right" | "bottom-left" | "bottom-center" | "bottom-right"</code>。</p>
<p>使用 exclude 删除<code>middle-center</code>之后再添加<code>center</code>。</p>
<h4 id="customhtmlcomponents">自定义 HTML 组件</h4>
<p>如果你想创建一个行为类似<code>input</code>的组件，但是你又不想编写 input HTML 的所有属性和方法，你可以这样：</p>
<pre><code class="language-ts">// src/components/UI/Input/index.tsx

import React from 'react';

import styles from './StyledInput.module.css';

type InputProps = React.ComponentProps&lt;'input'&gt;;

const Input = React.forwardRef(
  (props: InputProps, ref: React.Ref&lt;HTMLInputElement&gt;) =&gt; {
    return &lt;input {...props} className={styles.StyledInput} ref={ref} /&gt;;
  }
);

export default Input;
</code></pre>
<p>使用<code>React.ComponentProps</code>，你可以指定你需要基于什么类型创建一个组件，获取一个真正的 HTML input 的功能来自定义 UI 组件。但如果你想覆盖掉一些属性甚至禁用怎么办？</p>
<h5 id="omit">忽略（Omit）</h5>
<p>让我们以 UI 组件中的 <code>Tag</code>为例：</p>
<pre><code class="language-ts">// src/components/UI/Tag/index.tsx

import React from 'react';

import { StyledTag } from './StyledTag'; // aka a styled span

type TagProps = {
  variant?: 'solid' | 'outlined';
  text: string;
} &amp; Omit&lt;React.ComponentProps&lt;'span'&gt;, 'children'&gt;;

export default function Tag({ text, variant = 'solid' }: TagProps) {
  return &lt;StyledTag variant={variant}&gt;{text}&lt;/StyledTag&gt;;
}
</code></pre>
<p>在这个例子中，我们显式地传递了一个<code>text</code>展示组件的 <code>children</code>。你或许不希望消费组件使用原始的<code>children</code>，你可以忽略掉 <code>React.ComponentProps</code>中的这个属性。</p>
<h3 id="typinghooks"> 定义 Hook 的类型</h3>
<p>现在让我们看一下如何编写 React 中一些常用的 hook。</p>
<h4 id="usestatehook">useState hook</h4>
<p>大多数情况下，<code>useState</code> 不需要额外的操作，TypeScript 会自行推论。但是如果初始值和未来值不同则需要特别声明。</p>
<pre><code class="language-ts">// src/pages/Search.tsx

export default function Search() {
  const [animeList, setAnimeList] = useState&lt;Anime[] | null&gt;(null);
  const [page, setPage] = useState(1);
  // const [page, setPage] = useState&lt;number&gt;(1)
  // ...
}
</code></pre>
<p><code>page</code>的状态可以根据初始值推论为数字，它的行为和注解里的代码一模一样。state 的 setter 也会定义为 <code>React.Dispatch&lt;React.SetStateAction&lt;number&gt;&gt;</code>， <code>number</code>来替换推论的类型。</p>
<p>如果<code>animeList</code> 没有任何显式类型的话就为 <code>null</code>。在组件获取必要的数据之前这都是正确的，但是最终会是包含 <code>Anime</code>类型集合，所以必须将这个类型显式地设置为这两个可能类型的组合。</p>
<p>除了给 useState 的初始 state 设置为 null 以外，还可以：</p>
<pre><code class="language-ts">export default function Search() {
  // const [animeList, setAnimeList] = useState&lt;Anime[] | null&gt;(null)
  const [animeList, setAnimeList] = useState&lt;Anime[]&gt;([]);
  const [anime, setAnime] = useState&lt;Anime&gt;({} as Anime);
  // ...
}
</code></pre>
<p>请仔细观察 <code>anime, setAnime</code> 代码行，它之所以生效是因为这不是一个集合，而是单个元素。<br>
这里的区别在于你对编译器没有完全诚实，你预设会得到这个对象形状（shape）的值，有一定风险。</p>
<pre><code class="language-ts">export default function Search() {
  const [anime, setAnime] = useState&lt;Anime&gt;({} as Anime);
  // ...

  return &lt;div&gt;{anime.coverURL}&lt;/div&gt;;
}
</code></pre>
<p>如果没有在可选项中提供正确的值，会在运行时报错。</p>
<h5 id="stateprops">如何将 state 作为 props 传递</h5>
<p>多数情况下，是由上至下传递 state，并且等 state 完成或者设置好了才能传递到子组件，所以需要在编写 props 的时候想好状态的类型。</p>
<pre><code class="language-ts">type FancyComponentProps = {
  anime: Anime;
  setAnime: React.Dispatch&lt;React.SetStateAction&lt;Anime&gt;&gt;;
};

const FancyComponent = ({ anime, setAnime }: FancyComponentProps) =&gt; {
  // ...
};
</code></pre>
<p>最好清楚自己传递的是什么类型。如果你觉得困难的话，可以使用 IDE 的提示。</p>
<p><img src="https://blog.dastasoft.com/_next/image?url=%2Fassets%2Fposts%2Fcontent%2Ftypescript2%2Ftype-intellisense.webp&amp;w=1920&amp;q=75" alt="type intellisense" width="1578" height="276" loading="lazy"></p>
<h4 id="usereducerhook">useReducer hook</h4>
<p>你已经具备正确定义 <code>useReducer</code>所需的所有知识。</p>
<p>下面的例子中我简化了代码，正式代码会在泛型部分讲解：</p>
<pre><code class="language-ts">// src/hooks/useFetch.ts

const enum ACTIONS {
  LOADING,
  FETCHED,
  ERROR
}

type State = {
  data?: Anime[];
  loading: boolean;
  error?: Error;
};

type Action =
  | { type: ACTIONS.LOADING }
  | { type: ACTIONS.FETCHED; payload: Anime }
  | { type: ACTIONS.ERROR; payload: Error };

const initialState: State = {
  loading: true,
  error: undefined,
  data: undefined
};

const fetchReducer = (state: State, action: Action): State =&gt; {
  switch (action.type) {
    case ACTIONS.LOADING:
      return { ...initialState };
    case ACTIONS.FETCHED:
      return { ...initialState, data: action.payload, loading: false };
    case ACTIONS.ERROR:
      return { ...initialState, error: action.payload, loading: false };
    default:
      return state;
  }
};

const [state, dispatch] = useReducer(fetchReducer, initialState);
</code></pre>
<p>和往常一样， <code>status</code> 和 <code>dispatch</code> 来自于使用 <code>useReducer</code>时的 <code>reducer function</code> 以及一个 <code>initial state</code>。你不需要额外编写其他内容，但是你必须编写<code>state</code> 和 <code>actions</code>，因为 state 和 dispatch 的行为是根据它们来的。</p>
<h5 id="initialstate">initialState</h5>
<p>我们可以简化<code>initial state</code> 这个部分。 不是创建一个<code>State</code>类型，而是使用 <code>typeof initialState</code> 来定义。</p>
<pre><code class="language-ts">const initialState: State = {
  loading: true,
  error: undefined,
  data: undefined
};

const fetchReducer = (state: typeof initialState, action: Action) =&gt; {
  // ...
};
</code></pre>
<p>这样写的弊端是无法控制未来的 <code>data</code>和<code>error</code>的值，如果 state 的类型始终保持一致的话，没有问题，除此之外可以使用自定义 <code>State</code>类型。</p>
<h5 id="actions">Actions</h5>
<p>你可以使用组合（union）来处理 reducer 的 action 部分，你也可以选择使用枚举（Emun），这样写的话比在多个地方写字符串要更不容易出错。</p>
<h5 id="reducerfunction">reducer function</h5>
<p>你只需要指定传入函数的参数的类型，这个已经在上一部分完成。</p>
<h5 id="props">作为 props 传递</h5>
<p>另外，如果你需要从 useReducer 传出 prop，你必须编写对应的消费组件 props。</p>
<ul>
<li><code>state</code> 必须是你定义在 <code>initialState</code> 的类型，或者是例子中的自定义 <code>State</code>类型。</li>
<li><code>dispatch</code> 是 <code>React.Dispatch&lt;Action&gt;</code> 中自定义 <code>Action</code>类型。</li>
</ul>
<h4 id="usecontext">useContext</h4>
<p>示例项目中的上下文用于管理你喜欢动漫的列表，你可以在应用不同地方切换它们的 state。 <code>useContext</code> 并不是一个难点，使用它的方法就是上述内容的结合——让我们一起看代码：</p>
<pre><code class="language-ts">// src/context/FavContext.tsx

type FavContextType = {
  favList: Favorite[];
  // setFavList: React.Dispatch&lt;React.SetStateAction&lt;Favorite[]&gt;&gt;
  toggleFav: (id: number, favorite: Favorite) =&gt; void;
};

export const FavContext = createContext({} as FavContextType);

export const FavContextProvider = ({ children }: FavContextProviderProps) =&gt; {
  const [favList, setFavList] = useState&lt;Favorite[]&gt;([]);

  const toggleFav = (id: number, favorite: Favorite) =&gt; {
    /* ... */
  };

  // ...

  return (
    &lt;FavContext.Provider value={{ favList, toggleFav }}&gt;
      {children}
    &lt;/FavContext.Provider&gt;
  );
};
</code></pre>
<p><code>useContext</code>和 <code>useState</code>定义类型的规则相同。在我们的示例中，初始值应该为 null，但是我们使用了一个小技巧，在<code>createContext</code>添加<code>as</code>，并且定义了一个对象，包含一个<code>favourite animes</code>数组，和负责切换的函数。</p>
<p>注解部分是你根据场景需要的特定 setter。</p>
<p>接下来的代码都是你在 <code>useState</code> 中学过的内容。在 <code>Favorite</code> 类型中，useState 会推断必要的类型，这些类型可以直接在消费组件中访问。</p>
<pre><code class="language-ts">// src/components/AnimeDetail/index.tsx

const { favList, toggleFav } = useContext(FavContext);
</code></pre>
<h4 id="userefhook">useRef hook</h4>
<p>可以通过两种方式来使用 <code>useRef</code>，我们将分别讲解。</p>
<h5 id="dom">DOM 引用</h5>
<p>其中一个方式是使用<code>useRef</code>保持一个 DOM 元素的引用 。</p>
<p>在示例项目中，通过持有对动画列表中最后一项的可观察对象的引用，你会发现它可以无限滚动。这让你知道用户何时在视口中查看该项目并触发新的获取。</p>
<p>让我们查看使用 useRef 来引用 DOM 一个更简短的示例，你也可以<a href="https://github.com/dastasoft/animetrailers/blob/main/src/components/AnimeList/index.tsx">查看 useRef + observer 的完整版本</a>：</p>
<pre><code class="language-ts">const myDomReference = useRef&lt;HTMLInputElement&gt;(null);

useEffect(() =&gt; {
  if (myDomReference.current) myDomReference.current.focus();
}, []);
</code></pre>
<p>一个典型的情况是当页面加载的时候，你希望自动聚焦在输入框，只需要指定好引用的 DOM 元素，在这个示例中就是<code>HTMLInputElement</code>。</p>
<p>使用上面代码需要注意的是：</p>
<ul>
<li>钩子会返回一个只读的 <code>current</code> 属性。</li>
<li>你不需要手动编写<code>current</code>，React 会通过<code>React.RefObject&lt;HTMLInputElement&gt;</code>处理。</li>
<li>如果 DOM 元素永远指向现在，你可以将初始值设定为 <code>null!</code> 以避开 if 检查。</li>
</ul>
<h5 id="">可变引用</h5>
<p><code>useRef</code> 的第二个使用场景是在渲染之间保持可变值。例如，在你需要为组件的每个实例提供一个唯一变量的情况下，该变量在渲染之间存在并且不会触发重新渲染。</p>
<pre><code class="language-ts">const isFirstRun = useRef(true);

useEffect(() =&gt; {
  if (isFirstRun) {
    // ...
    isFirstRun.current = false;
  }
}, []);
</code></pre>
<p>使用上面代码，你需要注意的是：</p>
<ul>
<li>现在你可以改变<code>current</code>的值。</li>
<li>React 提供的 <code>React.MutableRefObject&lt;boolean&gt;</code>现在是<code>RefObject</code>内部的<code>MutableRefObject</code>（可变引用对象）。</li>
</ul>
<h3 id="forwardingref">传递 ref</h3>
<p>如果在某些时候你需要像 useRef 部分那样传递对 HTML 元素的引用，那么为该组件编写 props 会略有不同：</p>
<pre><code class="language-ts">// src/components/AnimeGrid/Card/index.tsx

const Card = React.forwardRef(
  (
    { id, coverURL, title, status, score, type, year }: CardProps,
    ref: React.Ref&lt;HTMLImageElement&gt;
  ) =&gt; {
    // ...
  }
);
</code></pre>
<p>要传递引用，需要用<code>React.forwardRef</code>打包组件，这将与组件的常规 props 一起注入 <code>ref</code>（是包装在 <code>React.Ref</code> 类型中的任何 HTML 元素）。</p>
<p>这样我们就知道我们传递的 HTML 元素的类型，如果你的使用场景不是这样，可以使用泛型。</p>
<h3 id="howtousetypescriptgenericsinreact">如何在 React 中使用 TypeScript 范型</h3>
<p>假设我们想通过包装现有的 HTML 元素来创建自定义 UI 组件，但像大多数组件库一样为它提供一组自定义属性。</p>
<p>这些组件库也提供一些灵活性，如哪一个 HTML 元素被渲染由 <code>as</code>属性控制 – <code>Text</code> UI 组件的示例就是这样。</p>
<p>Text UI 组件用来显示一组尺寸和颜色的任意文本，同时我们也希望使用者选择他们需要的 HTML 元素，而不是只限定于 <code>p</code> 或 <code>span</code>。</p>
<p>在这个示例中，你不能提前知道用户会选择什么元素传入组件，所以你需要使用泛型来推断它们使用了什么类型。</p>
<p>所以组件的 prop 如下：</p>
<pre><code class="language-ts">// src/components/UI/Text/index.tsx

type TextOwnProps&lt;T extends React.ElementType&gt; = {
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
  variant?: 'base' | 'primary' | 'secondary';
  as?: T | 'div';
};

type TextProps&lt;T extends React.ElementType&gt; = TextOwnProps&lt;T&gt; &amp;
  React.ComponentPropsWithoutRef&lt;T&gt;;

export default function Text&lt;T extends React.ElementType = 'div'&gt;({
  size = 'md',
  variant = 'base',
  children,
  as = 'div'
}: TextProps&lt;T&gt;) {
  // ...
}
</code></pre>
<p>让我们仔细查看上面代码：</p>
<ul>
<li>我们使用<code>T</code>来命名泛型，你也可以使用任意字母。</li>
<li>T 扩展自<code>React.ElementType</code>，即 HTML 元素的最通用的类型。所以我们知道传递给组件的任何东西都是基于 HTML 元素，而不是所有可能的 HTML 元素的手动类型的组合。</li>
<li><code>TextProps</code> 的第二个类型有两个用途：</li>
<li>根据 HTML 元素的类型，我们需要额外的属性。当消费组件使用 Text 组件作为<code>label</code>时，我们希望检查并建议与为<code>span</code>时不同的属性。为此，我们需要使用 <code>React.ComponentProps</code>。在这种情况下，我们不需要引用，因此我们显式使用<code>ComponentPropsWithoutRef</code>类型。</li>
<li><code>React.ComponentProps</code>同时也提供 <code>children</code> prop，所以不需要在 <code>TextOwnProps</code>引入。</li>
<li>你也不需要处理<code>Omit</code> 或者其他排除技术，因为 <code>children</code> 并没有被 <code>TextOwnProps</code> props 修改或者覆盖。</li>
</ul>
<p>通过这个例子，我们有一个非常灵活的组件，它的类型正确并且提供了良好的开发者体验。</p>
<p>在示例项目中，你可以测试不同的自定义 UI 组件，来检查上述模式的实现。</p>
<h3 id="typingacustomusefetchhook"> 定义自定义 useFetch Hook 类型</h3>
<p>在示例项目中，我编写了一个简单的钩子来获取数据，并且利用<code>localStorage</code>来暂时缓存数据，这样就不会超出 API 的限制。这没什么大不了的，但我认为它是本文中解释的所有内容的完整示例。</p>
<p>让我们一起看看这个钩子——但是我更推荐你查看<a href="https://github.com/dastasoft/animetrailers/blob/main/src/hooks/useFetch.t">实际文件</a>，以及理解这本文章不同部分的讲解。</p>
<pre><code class="language-ts">// src/hooks/useFetch.ts

type State&lt;T&gt; = {
  data?: T;
  loading: boolean;
  error?: Error;
};

function useFetch&lt;T = unknown&gt;(
  url?: string,
  { initialFetch, delayFetch }: Options = { initialFetch: true, delayFetch: 0 }
): State&lt;T&gt; {
  // ...
}
</code></pre>
<ul>
<li>钩子接受一个泛型，你并不能提前知道你将处理什么类型的数据。</li>
<li>钩子接受 <code>url</code> 来确定从哪里获取资源，以及一个选项来决定是否需要初始获取，以及两次获取之间有没有延迟。</li>
<li>如果不设置 <code>options</code> 拥有默认值。</li>
<li>钩子返回一个类的 <code>State</code>，由消费组件通过泛型指定。</li>
<li>状态类型定义由消费组件提供的可选数据类型：如加载标志变量，或者发生错误时返回的 error。</li>
</ul>
<p>让我们看一下消费组件的使用情况：</p>
<pre><code class="language-ts">// src/pages/AnimeDetail.tsx

const { data, loading, error } = useFetch&lt;JikanAPIResponse&lt;RawAnimeData&gt;&gt;(
  getAnimeFullById(Number(id))
);
</code></pre>
<ul>
<li><code>getAnimeFullById</code> 返回终端的 URL。</li>
<li><code>useFetch</code>会返回 <code>JikanAPIResponse</code>类型的<code>data</code>，根据情况不同，返回的数据也不同。在我们的示例中为 <code>RawAnimeData</code>。</li>
</ul>
<h2 id="conclusion">总结</h2>
<p>本文探索了 TypeScript 能够解决的一些常见痛点。特别是当你和团队一起工作，并且完全理解每一个组件的输入和输出、钩子以及上下文，TypeScript 非常有用。</p>
<p>使用 TypeScript 意味着代码更加可靠、记录更完善以及可读性更强。同时也更不容易出错并且更好管理。</p>
<p>编写代码不仅仅是创建一个有效的算法。你也和别人一起工作（即便你是独立开发者，你也要发表你的作品，寻求他人的帮助和协作），在这些场景中，组员之间的沟通是关键。</p>
<p>我喜欢将 TypeScript 类比为人类的 Babel：我们通过 Babel 来优化 CPU 的使用，同时也需要一个类似的 Babel 来指导和扩展团队合作。</p>
<p>还剩下一个问题，<strong>什么时候需要使用 TypeScript</strong>?</p>
<ul>
<li>如果你和别人协作，或者你计划发表代码，你可能希望代码可读性更好，更能够清晰表达你的想法。</li>
<li>如果你在编写一个大型项目。</li>
</ul>
<p>所有大项目都是由小项目组成，所以注意这里“大型”的定义。</p>
<p>这篇文章相当长，如果你读到这里，我对你的付出和热情表示感谢。我的初衷并不是文章的曝光，而是解释清楚为什么。</p>
<p>希望你喜欢这篇文章，如果你已经从 JS 转换成 TS，或者两个都在使用，或者思考过是否使用但是暂时不考虑以及其他任何情况——<strong>我非常期待听到你的分享</strong>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 为初学者解答 StackOverflow 上最热门的 TypeScript 问题 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：The Most Asked TypeScript Questions on StackOverflow – Answered for Beginners [https://www.freecodecamp.org/news/the-top-stack-overflowed-typescript-questions-explained/] ，作者：Emmanuel Ohans [https://www.freecodecamp.org/news/author/emmanuel/] “我讨厌 StackOverflow 网站” ——从未有开发者这样说过。 虽然在谷歌上搜索答案很有帮助，但更需要的是真正理解你遇到的偶发问题的解决方案。 在这篇文章中，我将探讨七个 StackOverflow 上最常见的 TypeScript 问题。 我花了几个小时来研究这些问题。 我希望你对在 TypeScript 中可能面临的常见问题有更深入的了解。 如果你刚刚学习 TypeScript，这也是有意义的——有什么比熟悉你未来遇到的问题更好的方法呢？ 让我们马上进入正题。 目录  1. ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/the-top-stack-overflowed-typescript-questions-explained/</link>
                <guid isPermaLink="false">62eb87e58d13aa0845c63b22</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ StackOverflow ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Thu, 04 Aug 2022 06:18:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/08/pexels-sora-shimazaki-5935794--2-.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/the-top-stack-overflowed-typescript-questions-explained/">The Most Asked TypeScript Questions on StackOverflow – Answered for Beginners</a>，作者：<a href="https://www.freecodecamp.org/news/author/emmanuel/">Emmanuel Ohans</a></p><!--kg-card-begin: markdown--><p><em>“<em>我讨厌 StackOverflow 网站</em>”</em>  ——从未有开发者这样说过。</p>
<p>虽然在谷歌上搜索答案很有帮助，但更需要的是真正理解你遇到的偶发问题的解决方案。</p>
<p>在这篇文章中，我将探讨七个 StackOverflow 上最常见的 TypeScript 问题。</p>
<p>我花了几个小时来研究这些问题。</p>
<p>我希望你对在 TypeScript 中可能面临的常见问题有更深入的了解。</p>
<p>如果你刚刚学习 TypeScript，这也是有意义的——有什么比熟悉你未来遇到的问题更好的方法呢？</p>
<p>让我们马上进入正题。</p>
<h2 id="">目录</h2>
<ol>
<li><a href="https://chinese.freecodecamp.org/news/the-top-stack-overflowed-typescript-questions-explained/#1-what-is-the-difference-between-interfaces-vs-types-in-typescript">TypeScript 中的接口（interfaces）与类型（Types）之间有什么区别？</a></li>
<li><a href="https://chinese.freecodecamp.org/news/the-top-stack-overflowed-typescript-questions-explained/#2-in-typescript-what-is-the-exclamation-mark-bang-operator">在 TypeScript 中，什么是 ! 操作符？</a></li>
<li><a href="https://chinese.freecodecamp.org/news/the-top-stack-overflowed-typescript-questions-explained/#3-what-is-a-d-ts-file-in-typescript">TypeScript 中的 “.d.ts” 文件是什么？</a></li>
<li><a href="https://chinese.freecodecamp.org/news/the-top-stack-overflowed-typescript-questions-explained/#4-how-do-you-explicitly-set-a-new-property-on-window-in-typescript">如何在 TypeScript 中明确设置 window 的新属性？</a></li>
<li><a href="https://chinese.freecodecamp.org/news/the-top-stack-overflowed-typescript-questions-explained/#5-are-strongly-typed-functions-as-parameters-possible-in-typescript">强类型函数作为参数在 TypeScript 中是否可行？</a></li>
<li><a href="https://chinese.freecodecamp.org/news/the-top-stack-overflowed-typescript-questions-explained/#6-how-to-fix-could-not-find-declaration-file-for-module-">如何修复无法找到模块的声明文件？</a></li>
<li><a href="https://chinese.freecodecamp.org/news/the-top-stack-overflowed-typescript-questions-explained/#7-how-do-i-dynamically-assign-properties-to-an-object-in-typescript">如何在 TypeScript 中为对象动态分配属性？</a></li>
</ol>
<p><strong><strong>注意：</strong></strong> 你可以得到这份手册的 <a href="https://www.ohansemmanuel.com/cheatsheet/top-7-stack-overflowed-typescript-questions">PDF 或 ePub</a> 版本，以方便参考或在你的 Kindle 或平板电脑上阅读。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-51.png" alt="image-51" width="600" height="400" loading="lazy"></p>
<p>这是 <a href="https://www.ohansemmanuel.com/cheatsheet/top-7-stack-overflowed-typescript-questions">PDF 或 Epub 版本的手册下载地址</a>。</p>
<h2 id="1-what-is-the-difference-between-interfaces-vs-types-in-typescript">TypeScript 中的接口（interfaces）与类型（Types）之间有什么区别？</h2>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-52.png" alt="Typescript 中的 Interfaces（接口） 与 Types（类型） 的对比  " class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>Typescript 中的 Interfaces（接口） 与 Types（类型） 的对比  </figcaption>
</figure>
<p>interfaces vs types（技术上来说，是 type alias）是一个充满争议的问题。</p>
<p>在开始使用 TypeScript 时，你可能会发现有选择上很困惑。这篇文章消除这种困惑，并帮助你选择适合你的方式。</p>
<h2 id="tldr">TL;DR</h2>
<p>在许多情况下，你可以交替使用 interface 或者 type alias。</p>
<p>interface 的大部分功能可以通过 type aliases 来实现, 只是你不能通过重新声明类型（re-declaring）来给它增加新的属性（properties）。你必须使用一个交叉类型（intersection type）。</p>
<h2 id="typesinterfaces">为什么开始时会对类型（Types）与接口（Interfaces）产生混淆？</h2>
<p>每当我们面临多种选择时，大多数人都会开始面对<a href="https://en.wikipedia.org/wiki/The_Paradox_of_Choice">选择悖论（paradox of choice）</a>。</p>
<p>在这种情况下，只有两个选项。</p>
<p>这有什么好困惑的？</p>
<p>好吧，这里的主要混淆源于这两个选项在大多数方面是如此<strong>势均力敌</strong>。</p>
<p>这使得你很难做出一个明确的选择，尤其是当你刚刚开始使用 Typescript 的时候。</p>
<h2 id="typealiasinterface">类型别名（Type Alias）与接口（Interface）的一个基本例子</h2>
<p>让我们通过 interface 和 type alias 的例子快速了解一下。</p>
<p>考虑下面的 <code>Human</code> type 的写法：</p>
<pre><code class="language-ts">// type 
type Human = {
  name: string 
  legs: number 
  head: number
}

// interface 
interface Human {
  name: string 
  legs: number 
  head: number
}
</code></pre>
<p>这些都是表示 <code>Human</code> type，通过 type alias 或者 interface。</p>
<h2 id="typealiasinterfaces">类型别名（Type Alias）和接口（Interfaces）之间的区别</h2>
<p>以下是 type alias 和 interface 的主要区别：</p>
<blockquote>
<p>关键区别：接口（Interfaces）只能描述 object shapes。类型别名（Type aliases）可用于其他类型（other types），如 primitives、unions 和 tuples。</p>
</blockquote>
<p>type alias 可以表示的数据类型中是相当灵活的。从 basic primitives（基本的基元）到 复杂的 unions（联合）和 tuples（元组），如下所示：</p>
<pre><code class="language-ts">// primitives 
type Name = string 

// object 
type Male = {
  name: string
}

type Female = {
  name: string 
}

// union
type HumanSex = Male | Female

// tuple
type Children = [Female, Male, Female]
</code></pre>
<p>不像 type aliases，你只能用一个 interface 来表示 object types（对象类型）。</p>
<h3 id="">关键区别：一个接口可以通过多次声明来进行扩展</h3>
<p>请思考以下例子：</p>
<pre><code class="language-ts">interface Human {
  name: string 
}

interface Human {
  legs: number 
}
</code></pre>
<p>上面的两个声明将变成：</p>
<pre><code class="language-ts">interface Human {
  name: string 
  legs: number 
}
</code></pre>
<p><code>Human</code>将被视为一个单一的 interface：两个声明的成员的组合。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-53.png" alt="type 'Humans' 中需要 `legs`这个属性（Property）" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>type 'Humans' 中需要 `legs`这个属性（Property）</figcaption>
</figure>
<p>看<a href="https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgBIFcC2cTIN4BQyxyIcmEAXMgM5hSgDmBAvgaJLIihtroSWQAbCIxrUQWAEbRWBAgHoFyMAAtgNZNCgB7KJp3owyGQjjoaKAOQixV5JgvGIADw3GCCHSDrJV1XhxkAF58IhIyCmorAHlVHE0AUUw+dAghK1YgA">这个 TypeScript 演示代码</a>。</p>
<p>这种情况不适合使用 type aliases。</p>
<p>使用 type aliases，以下将导致错误：</p>
<pre><code class="language-ts">type Human = {
    name: string 
}
  
type Human =  {
    legs: number 
}

const h: Human = {
   name: 'gg',
   legs: 5 
}  
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-54.png" alt="重复标识符（Duplicate identifier）`Human` 错误" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>重复标识符（Duplicate identifier）`Human` 错误</figcaption>
</figure>
<p>看这个 <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAEgrgWwIYDsoF4oG8BQV9QpIIQBcUAzsAE4CWKA5lDgL57OiSyKob64EoAGwgMK5FIgBGEaszY4AxgHsUVKAAty8ZGkwD8REuQDkDBiYA07YaPFQArPPw5XroA">TypeScript 演示代码</a>。</p>
<p>使用 type aliases 的话，你就不得不使用一个交叉类型（intersection type）：</p>
<pre><code class="language-ts">type HumanWithName = {
    name: string 
}
  
type HumanWithLegs =  {
    legs: number 
}

type Human  = HumanWithName &amp; HumanWithLegs

const h: Human = {
   name: 'gg',
   legs: 5 
}  
</code></pre>
<p>看这个 <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAEgrgWwIYDsDqBLYALAckhaAXigG8AoKKqFAiALigGdgAnDFAcynIF9KeoSLESpMOADIROTKCTICqAG2lNGKRACMIrHv3JDo8ZCioljYrHjpQAZCJPjsUmeXIBjAPYoWUbIwtTEgpqWkJGAHJOTgiAGkUVGUYAVj0qNwygA">TypeScript 演示代码</a>。</p>
<h3 id="">小小的区别：类型别名和接口都可以被扩展，但语法不同</h3>
<p>对于 interfaces，你使用<code>extends</code>关键字。对于 types，你必须使用一个交叉（intersection）。</p>
<p>思考一下下面的例子：</p>
<h4 id="">类型别名扩展了一个类型别名</h4>
<pre><code class="language-ts">
type HumanWithName = {
  name: string 
}

type Human = HumanWithName &amp; {
   legs: number 
   eyes: number 
}
</code></pre>
<h4 id="">类型别名扩展了一个接口</h4>
<pre><code class="language-ts">interface HumanWithName {
  name: string 
}

type Human = HumanWithName &amp; {
   legs: number 
   eyes: number 
} 
</code></pre>
<h4 id="">接口扩展了一个接口</h4>
<pre><code class="language-ts">interface HumanWithName {
  name: string 
}

interface Human extends HumanWithName {
  legs: number 
  eyes: number 
}
</code></pre>
<h4 id="">接口扩展了一个类型别名</h4>
<pre><code class="language-ts">type HumanWithName = {
  name: string
}

interface Human extends HumanWithName {
  legs: number 
  eyes: number 
}
</code></pre>
<p>正如你所看到的，这并不是选择一个而不是另一个的特别理由。然而，语法是不同的。</p>
<h3 id="">小区别：类只能实现静态已知的成员</h3>
<p>class 可以同时实现 interfaces 或者 type aliases。然而，不能实现（implement）或扩展（extend）一个联合类型（union type）。</p>
<p>请看下面的例子：</p>
<h4 id="">类实现了一个接口</h4>
<pre><code class="language-ts">interface Human {
  name: string
  legs: number 
  eyes: number 
}

class FourLeggedHuman implements Human {
  name = 'Krizuga'
  legs = 4
  eyes = 2
}
</code></pre>
<h4 id="">类实现了一个类型别名</h4>
<pre><code class="language-ts">type Human = {
  name: string
  legs: number 
  eyes: number 
}

class FourLeggedHuman implements Human {
  name = 'Krizuga'
  legs = 4
  eyes = 2
}
</code></pre>
<p>这两段代码作都没有任何错误。然而，下面的情况却失败了：</p>
<h4 id="">类实现了一个联合类型</h4>
<pre><code class="language-ts">type Human = {
    name: string
} | {
    legs: number
    eyes: number
}

class FourLeggedHuman implements Human {
    name = 'Krizuga'
    legs = 4
    eyes = 2
}
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-55.png" alt="class 只能实现（implement）一个对象类型（object type）或具有静态已知成员的对象类型（intersection of object types with statically known members）的交叉（intersection）" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>class 只能实现（implement）一个对象类型（object type）或具有静态已知成员的对象类型（intersection of object types with statically known members）的交叉（intersection）</figcaption>
</figure>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAEgrgWwIYDsoF4oG8BQV9QpIIQBcUAzsAE4CWKA5jgL5QA+2eBANhAxeRSIARhGpd8EEBAGERYljhwBjbkgoUoAMQD2cagBk+DCABN4yNLQRheJFME0XUnAoWLRMAcgDSdAF5wDEheElC8-BhQACxhUjJRAEwsQA">TypeScript playground</a>。</p>
<h2 id="">类型别名与接口的总结</h2>
<p>你的想法可能不同，但只要有可能，我都坚持使用 type aliases，因为它们的灵活性和语法更简单。也就是说，除非我特别需要一个接口（interface）的功能，否则我选择 type aliases。</p>
<p>在大多数情况下，你也可以根据你的个人偏好来决定，但要与你的选择保持一致，至少在一个特定的项目中。</p>
<p>我必须补充一点，在需要考虑性能的情况下，interface 比较检查可能比 type aliases 更快。但我还没有发现使用 type aliases，导致性能问题。</p>
<h2 id="2-in-typescript-what-is-the-exclamation-mark-bang-operator">在 TypeScript 中，什么是 ! 操作符？</h2>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-56.png" alt="TypeScript 中的 bang 运算符是什么" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>TypeScript 中的 bang 运算符是什么</figcaption>
</figure>
<h2 id="tldr">TL;DR</h2>
<p>这个<code>！</code> 在技术上被称为 <strong><strong>非空的断言操作符(non-null assertion operator)</strong></strong>。如果 TypeScript 编译器警告一个值是 <code>null</code>或 <code>undefined</code>，你可以使用<code>！</code>操作符来断言该值不是 <code>null</code> 或 <code>undefined</code>。</p>
<p>个人观点：尽可能避免这样做。</p>
<h2 id="nonnullassertionoperator">什么是非空断言操作符（Non-Null Assertion Operator）？</h2>
<p><code>null</code> 和 <code>undefined</code> 是有效的 JavaScript 值。</p>
<p>上面的声明对所有 TypeScript 应用程序也是如此。</p>
<p>然而，TypeScript 更进一步。</p>
<p><code>null</code> 和 <code>undefined</code> 是同样有效的类型。例如，考虑下面的情况：</p>
<pre><code class="language-ts">// 明确为 null
let a: null 

a = null
// 以下代码将产生错误
a= undefined 
a = {}


// 明确为 undefined
let b: undefined 
// 以下代码将产生错误
b = null 
b = {}
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-57.png" alt="Error：除了 null 和 undefined 之外，其他的值不能被分配" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>Error：除了 null 和 undefined 之外，其他的值不能被分配</figcaption>
</figure>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/DYUwLgBAhgXBB2BXYwICg1QgXgc4aA9IRGABYgQBmA9ijQO4CW8A5tAM4dOvwC2IeGA4RmKCAE8mIYABMIIAE6KaijplyJ4skFRYh5mHBADeAXwxpQkAEZwtOvfAPpipCtTrBGLdlC48-ILCokziUjLySipqaDbGSOJxxuZoQA">TypeScript playground</a>。</p>
<p>在某些情况下，TypeScript 编译器无法判断某个值是否被定义，即不是 <code>null</code>或 <code>undefined</code>。</p>
<p>例如，假设你有一个值<code>Foo</code>。</p>
<p><code>Foo!</code>产生一个类型为<code>Foo</code>的值， 不能为 <code>null</code> 和 <code>undefined</code>。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-58.png" alt="Foo! 从 Foo 的类型中排除了 `null`和 `undefined`" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>Foo! 从 Foo 的类型中排除了 `null`和 `undefined`</figcaption>
</figure>
<p>你基本上是在对 TypeScript 编译器说：<em>我确信 <code>Foo</code>不会是 <code>null</code> 或 <code>undefined</code></em>。</p>
<p>让我们来探讨一个简单的例子。</p>
<p>在标准的 JavaScript 中，你可以用 <code>.concat</code> 方法将两个字符串连接起来：</p>
<pre><code class="language-ts">const str1 = "Hello" 
const str2 = "World"

const greeting = str1.concat(' ', str2)
// Hello World
</code></pre>
<p>编写一个简单的产生重复字符串函数，以自己为参数调用 <code>.concat</code>：</p>
<pre><code class="language-ts">function duplicate(text: string | null) {
  return text.concat(text);
}
</code></pre>
<p>注意，参数 <code>text</code> 被打成了 <code>string | null</code>。</p>
<p>在严格模式下，TypeScript 会在这里警告，因为用 <code>null</code> 调用 <code>concat</code> 会导致意外的结果。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-59.png" alt="用 `null` 调用 `concat` 的结果" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>用 `null` 调用 `concat` 的结果</figcaption>
</figure>
<p>TypeScript 将报错：<code>Object is possibly 'null'.(2531)</code>。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-60.png" alt="Typescript error: Object is possibly null（对象可能为空）" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>Typescript error: Object is possibly null（对象可能为空）</figcaption>
</figure>
<p>反过来说，一个相当懒的方法是，使用非空的断言操作符来消除编译器的错误：</p>
<pre><code class="language-ts">function duplicate(text: string | null) {
  return text!.concat(text!);
}
</code></pre>
<p>注意<code>text</code>变量后面的感叹号——<code>text!</code>。</p>
<p><code>text</code>类型代表<code>string | null</code>。</p>
<p><code>text!</code> 只代表<code>string</code>，也就是把<code>null</code>或 <code>undefined</code> 从变量类型中删除。</p>
<p>结果是什么？你已经消除了 TypeScript 的错误。</p>
<p>然而，这是一个愚蠢的修复。</p>
<p><code>duplicate</code>确实可以在 <code>null</code> 的情况下被调用，这可能会导致意外的结果。</p>
<p>请注意，如果<code>text</code>是一个可选的属性，下面的例子也是成立的：</p>
<pre><code class="language-ts">// text could be "undefined"
function duplicate(text?: string) {
  return text!.concat(text!);
}
</code></pre>
<h2 id=""><code>！</code>运算符的陷阱（以及如何不使用它）</h2>
<p>当作为一个新用户使用 TypeScript 时，你可能觉得自己在打一场会失败的仗。</p>
<p>这些错误对你来说毫无意义。</p>
<p>你的目标是消除错误，尽可能迅速地继续你的生活。</p>
<p>然而，你应该小心使用非空断言操作符（！）。</p>
<p>消除一个 TypeScript 错误并不意味着消除一个潜在的问题。</p>
<p>正如你在前面的示例中看到的那样，你会失去所有相关的 TypeScript 安全性，以防止 <code>null</code> 和 <code>undefined</code> 的错误用法。</p>
<p>那么，你应该怎么做？</p>
<p>如果你写 React，考虑一个你可能熟悉的例子：</p>
<pre><code class="language-ts">const MyComponent = () =&gt; {
   const ref = React.createRef&lt;HTMLInputElement&gt;();
 
   //compilation error: ref.current is possibly null
   const goToInput = () =&gt; ref.current.scrollIntoView(); 

    return (
       &lt;div&gt;
           &lt;input ref={ref}/&gt;
           &lt;button onClick={goToInput}&gt;Go to Input&lt;/button&gt;
       &lt;/div&gt;
   );
};
</code></pre>
<p>在上面的示例中（对于那些不编写 React 的人），在 <code>React</code> 心智模型（mental mode）中，<code>ref.current</code> 在用户单击按钮时肯定会可用。</p>
<p><code>ref</code> 对象在 UI 元素被渲染后不久就会被设置。</p>
<p>TypeScript 不知道这一点，所以你可能被迫在这里使用非空断言操作符。</p>
<p>实际上，开发者对 TypeScript 编译器说，我知道我在做什么，你（TypeScript 编译器）不知道。</p>
<pre><code class="language-ts">const goToInput = () =&gt; ref.current!.scrollIntoView();
</code></pre>
<p>注意感叹号<code>！</code>。</p>
<p>这 "修复" 了这个错误。</p>
<p>但是，如果将来有人从输入中删除了 <code>ref</code>，而又没有自动测试来捕捉这一点，你现在就有一个错误。</p>
<pre><code class="language-ts">// before
&lt;input ref={ref}/&gt;

// after
&lt;input /&gt;
</code></pre>
<p>TypeScript 将无法发现以下一行的错误：</p>
<pre><code class="language-ts">const goToInput = () =&gt; ref.current!.scrollIntoView();
</code></pre>
<p>通过使用非空（non-null）断言操作符，TypeScript 编译器将认为 <code>null</code>和<code>undefined</code>对于相关的值来说是不可能的。在这种情况下，<code>ref.current</code>。</p>
<h3 id="1">解决方案 1：寻找替代修复方法</h3>
<p>你应该对第一行找到一个替代的修复方法。</p>
<p>例如，通常你可以像这样明确地检查 <code>null</code> 和 <code>undefined</code> 值。</p>
<pre><code class="language-ts">// before 
const goToInput = () =&gt; ref.current!.scrollIntoView();

// now 
const goToInput = () =&gt; {
  if (ref.current) {
   //Typescript会认为 ref.current肯定是 
   //可以在这个条件下中使用
     ref.current.scrollIntoView()
  }
};

//  或者（使用逻辑与运算符）
const goToInput = () =&gt; ref.current &amp;&amp; ref.current.scrollIntoView();
</code></pre>
<p>众多工程师会争论这个事实，即使代码更啰嗦。</p>
<p>这是对的。</p>
<p>但是你应该选择详细而不是可能破坏代码，并将代码推送到生产环境。</p>
<p>这是个人喜好。你选择的道路可能会有所不同。</p>
<h3 id="2">解决方案 2：明确地抛出一个错误</h3>
<p>在其他修复方法不能解决问题的情况下，非空断言运算符似乎是唯一的解决方案，我通常建议你在这样做之前抛出一个错误。</p>
<p>下面是一个例子（用伪代码）：</p>
<pre><code class="language-ts">function doSomething (value) {
   // 出于某种原因，TS认为该值可能是  
   // null 或者 undefined，但你不同意
   
  if(!value) {
    // 明确地断言这就是情况 
    // 抛出一个错误或在某个地方记录这个错误，你可以追踪
    throw new Error('uexpected error: value not present')
  } 

  // 继续使用非空的断言运算符
  console.log(value)
}
</code></pre>
<p>我发现自己有时会这样做的一个实际案例是在使用<code>Formik</code>时。</p>
<p>除了事情发生了变化，我确实认为<code>Formik</code>在许多情况下是不好的类型。</p>
<p>如果你已经完成了 Formik 的验证，并确定你的值存在，那么这个例子可能会有类似的效果。</p>
<p>这里有一些伪代码：</p>
<pre><code class="language-ts">&lt;Formik 
  validationSchema={...} 
  onSubmit={(values) =&gt; {
   // 你确定 values.name 应该存在，因为你有验证了
   // 但TypeScript不知道这一点

   if(!values.name) {
    throw new Error('Invalid form, name is required')  
   } 
   console.log(values.name!)
}}&gt;


&lt;/Formik&gt;
</code></pre>
<p>在上面的伪代码中，<code>values</code> 可以这样定义：</p>
<pre><code class="language-ts">type Values = {
  name?: string
}
</code></pre>
<p>但是在你点击<code>onSubmit</code>之前，你已经添加了一些验证，显示一个 UI 表单错误，让用户在继续提交表单之前输入一个<code>name</code>。</p>
<p>还有其他方法可以解决这个问题。但如果你确定一个值存在，但又不能完全传达给 TypeScript 编译器，可以使用非空断言操作符。</p>
<p>但也通过可以添加你自己的断言来抛出一个你可以追踪的错误。</p>
<h2 id="implicitassertion">隐性断言（Implicit Assertion）是什么</h2>
<p>尽管运算符的名字是非空断言运算符，但实际上没有断言（assertion）。</p>
<p>你主要是在断言（作为开发者）这个值存在。</p>
<p>TypeScript 编译器并没有断言这个值存在。</p>
<p>所以，如果你必须这样做，你可以继续添加你的断言（例如，在前面的章节中讨论的）。</p>
<p>另外，请注意，使用非空断言运算符不需要更多的 JavaScript 代码来发出（emitted）事件。</p>
<p>如前所述，TypeScript 在这里没有做断言。</p>
<p>因此，TypeScript 不会通过一些代码来检查这个值是否存在。</p>
<p>如果这个值存在，JavaScript 代码会发出（emitted）一个事件。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-62.png" alt="image-62" width="600" height="400" loading="lazy"></p>
<h2 id="">总结</h2>
<p>TypeScript 2.0 发布了 <strong><em>non-null assertion operator (非空断言操作符)</em></strong>。 是的，它已经存在了一段时间（<a href="https://github.com/microsoft/TypeScript/releases/tag/v2.0.3">发布于 2016 年</a>）。在撰写本文时，TypeScript 的最新版本是 <code>v4.7</code>。</p>
<p>如果 TypeScript 编译器警告一个值是 <code>null</code> 或 <code>undefined</code>，你可以使用 <code>！</code> 操作符来断言上述值不是 <code>null</code> 或 <code>undefined</code>。</p>
<p>只有在你确定是这样的情况下才这样做。</p>
<p>甚至更好的是，继续添加你自己的断言，或尝试找到一个替代的解决方案。</p>
<p>有些人可能会说，如果你每次都需要使用 <strong><em>non-null assertion operator（非空断言操作符）</em></strong>，那就说明你通过 TypeScript 控制你的应用程序状态的能力很差。</p>
<p>我同意这个观点。</p>
<h2 id="3-what-is-a-d-ts-file-in-typescript">TypeScript中的 “.d.ts” 文件是什么？</h2> 
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-63.png" alt="d.ts 文件是什么" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>d.ts 文件是什么</figcaption>
</figure>
<h2 id="tldr">TL;DR</h2>
<p><code>.d.ts</code>文件被称为类型声明文件。它们的存在只有一个目的：描述一个现有模块（module）的类型特征（shape），它们只包含用于类型检查的类型信息。</p>
<h2 id="typescriptdts">TypeScript 中的<code>.d.ts</code>文件介绍</h2>
<p>学习了 TypeScript 的基础知识后，你就可以获得超能力。</p>
<p>至少我是这么认为的。</p>
<p>你会自动得到潜在错误的警告，并在你的代码编辑器中得到自动完成的功能。</p>
<p>虽然看起来很神奇，但计算机没有使用魔法。</p>
<p>那么，TypeScript 的诀窍是什么呢？</p>
<p>用更清晰的语言，TypeScript 怎么知道这么多？ 它如何判断哪个 API 正确与否？ 在某个对象或类上哪些方法可用，哪些不可用？</p>
<p>答案不是魔法。</p>
<p>TypeScript 靠的是类型（type）。</p>
<p>有时，你不编写这些类型（types），但它们存在。</p>
<p>它们存在于称为声明文件的文件中。</p>
<p>这些是以 <code>.d.ts</code> 结尾的文件。</p>
<h2 id="dts">一个关于".d.ts "文件的简单例子</h2>
<p>想一下下面的 TypeScript 代码：</p>
<pre><code class="language-ts">// valid 
const amount = Math.ceil(14.99)

// error: Property 'ceil' does not exist on type 'Math'.(2339)
const otherAmount = Math.ciil(14.99)
</code></pre>
<p>参考 <a href="https://www.TypeScriptlang.org/play?#code/MYewdgzgLgBAhgWxAVzLAvDAsnKALAOmAFMBLAGwAoBGAFgIE4GBKAKFdElhH2ICcAgklQZsuQsFIUa9Jm1ZA">TypeScript playground（训练场）</a>。</p>
<p>第一行代码是完全有效的，但第二行则不尽然。</p>
<p>TypeScript 很快就发现了这个错误：<code>Property 'ciil' does not exist on type 'Math'.(2339)</code>。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-64.png" alt="Typescript 发现访问属性 `ciil` 的错误" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>Typescript 发现访问属性 `ciil` 的错误</figcaption>
</figure>
<p>TypeScript 是如何知道 <code>Math</code> 对象上不存在 <code>ciil</code> 的？</p>
<p><code>Math</code> 对象不是我们实现的一部分。这是一个标准的内置对象。</p>
<p>那么，TypeScript 是如何解决这个问题的呢？</p>
<p>答案是有 <strong><em>declaration files</em></strong> 来描述这些内置对象。</p>
<p>将声明文件视为包含与某个模块相关的所有类型信息。它不包含具体实现，仅包含类型信息。</p>
<p>这些文件有一个 <code>.d.ts</code> 结尾。</p>
<p>你的实现文件将有<code>.ts</code>或<code>.js</code>结尾，代表 TypeScript 或 JavaScript 文件。</p>
<p>这些声明文件没有实现。他们只包含类型信息，并且有一个<code>.d.ts</code>文件结尾。</p>
<h2 id="">内置类型定义</h2>
<p>在实践中理解这一点的一个好方法是建立一个全新的 TypeScript 项目，并探索顶级对象的类型定义文件，如 <code>Math</code>。</p>
<p>让我们这样做。</p>
<p>创建一个新的目录，并将其命名为任何合适的名字。</p>
<p>我创建一个新文件夹 <code>dts</code>：</p>
<pre><code class="language-ts">cd dts
</code></pre>
<p>现在初始化一个新的项目：</p>
<pre><code class="language-ts">npm init --yes
</code></pre>
<p>初始化 TypeScript：</p>
<pre><code class="language-ts">npm install TypeScript --save-dev
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-65.png" alt="安装 TypeScript" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>安装 TypeScript</figcaption>
</figure>
<p>这个目录应该包含 2 个文件和一个子目录：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-66.png" alt="安装后的文件" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>安装后的文件</figcaption>
</figure>
<p>在你喜欢的代码编辑器中打开该文件夹。</p>
<p>如果你去查看 <code>node_modules</code>中的<code>TypeScript</code>目录，你会发现一堆开箱即用的类型声明文件。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-67.png" alt="image-67" width="600" height="400" loading="lazy"></p>
<p>TypeScript 目录中的类型声明文件，是在安装 TypeScript 后出现的。</p>
<p>默认情况下，TypeScript 将包括所有 DOM API 的类型定义，例如认为<code>window</code>和<code>document</code>。</p>
<p>当你检查这些类型声明文件时，你会注意到命名惯例是很简单的。</p>
<p>它是这样的：<code>lib.[something].d.ts</code>。</p>
<p>打开<code>lib.dom.d.ts</code>声明文件，查看所有与浏览器 DOM API 相关的声明。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-68.png" alt="image-68" width="600" height="400" loading="lazy"></p>
<p>DOM 声明文件，正如你所看到的，这是个相当巨大的文件。</p>
<p>DOM 中的所有 API 也是如此。</p>
<p>真棒啊！</p>
<p>现在，如果你看一下<code>lib.es5.d.ts</code>文件，你会看到<code>Math</code>对象的声明，包含<code>ceil</code>属性。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-69.png" alt="声明文件中的 Math 对象" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>声明文件中的 Math 对象</figcaption>
</figure>
<p>下次你想，哇，TypeScript 真了不起。请记住，这种美妙的很大一部分是归功于鲜为人知的英雄：类型声明文件。</p>
<p>这不是魔术，是类型声明文件。</p>
<h2 id="typescript">TypeScript 的外部类型定义</h2>
<p>那些没有内置的 API 怎么办？</p>
<p>有许多<code>npm</code>包可以做任何你想做的事情。</p>
<p>有没有办法让 TypeScript 也能理解上述模块的相关类型关系？</p>
<p>嗯，答案是肯定的。</p>
<p>通常有两种方式，一个库的作者可以做到这一点。</p>
<h3 id="">类型打包</h3>
<p>在这种情况下，库的作者已经将类型声明文件作为包的一部分打包（Bundled）在一起。</p>
<p>你通常不需要做任何事情。</p>
<p>你只需继续在你的项目中安装库，从库中导入所需的模块，看看 TypeScript 是否会自动为你解析类型</p>
<p>记住，这不是魔术。</p>
<p>库的作者已经将类型声明文件包含在包的分发中。</p>
<h3 id="definitelytypedtypes">DefinitelyTyped（@types）</h3>
<p>想象一下，一个公共资源库（central public repository）为成千上万的库托管声明文件？</p>
<p>这个资源库已经存在了。</p>
<p><a href="https://github.com/DefinitelyTyped/DefinitelyTyped/">DefinitelyTyped repository</a>是一个集中式的仓库，存储了成千上万个库的声明文件。</p>
<p>说实话，绝大多数常用的库都在 <strong><em>DefinitelyTyped</em></strong> 上有声明文件。</p>
<p>这些类型定义文件会自动发布到<code>npm</code>下的<code>@types</code>范围。</p>
<p>例如，如果你想安装<code>react</code>npm 包的类型文件，你可以这样做：</p>
<pre><code class="language-ts">npm install --save-dev @types/react
</code></pre>
<p>如果你发现自己使用的模块的类型不是 TypeScript 自动解析的，可以尝试直接从 DefinitelyTyped 安装类型。</p>
<p>看看那里是否存在这些类型。比如说：</p>
<pre><code class="language-ts">npm install --save-dev @types/your-library
</code></pre>
<p>你以这种方式添加的定义文件将被保存到 <code>node_modules/@types</code> 目录下。</p>
<p>TypeScript 会自动找到这些。所以，你不需要采取额外的步骤。</p>
<h2 id="">如何编写你自己的声明文件</h2>
<p>在不常见的情况下，如果一个库没有捆绑它的类型，并且在 DefinitelyTyped 上没有类型定义文件，你可以编写你自己的声明文件。</p>
<p>深入了解编写声明文件超出了本文的范围，但你可能会遇到的一个情况是在没有声明文件（declaration file）的情况下，如何消除关于某个特定模块（particular module）的错误。</p>
<p>声明文件（Declaration files）都有一个<code>.d.ts</code>结尾。</p>
<p>所以要创建你的声明文件，就要创建一个以<code>.d.ts</code>结尾的文件。</p>
<p>例如，假设我在项目中安装了库<code>untyped-module</code>。</p>
<p><code>untyped-module</code>没有引用的类型定义文件，所以 TypeScript 在我的项目中对此进行警告。</p>
<p>为了消除这个警告，我可以在我的项目中创建一个新的<code>untyped-module.d.ts</code>文件，内容如下:</p>
<pre><code class="language-ts">declare module "some-untyped-module";
</code></pre>
<p>这将声明该模块为<code>any</code>类型。</p>
<p>我们不会得到任何 TypeScript 对该模块的支持，但你已经消除了 TypeScript 的警告。</p>
<p>理想的下一步包括在模块的公共资源库中打开一个问题，包括一个 TypeScript 声明文件，或者自己写一个合适的文件。</p>
<h2 id="">总结</h2>
<p>下次你想，哇，TypeScript 真了不起。请记住，这种了不起的成就有很大一部分是由于幕后的英雄：类型声明文件（type declaration files）。</p>
<p>现在你明白它们是如何工作的了吧！</p>
<h2 id="4-how-do-you-explicitly-set-a-new-property-on-window-in-typescript">如何在 TypeScript 中明确设置 window 的新属性？</h2>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-70.png" alt="在 window 对象上设置一个新属性（new property）？" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>在 window 对象上设置一个新属性（new property）？</figcaption>
</figure>
<h2 id="tldr">TL;DR</h2>
<p>为<code>Window</code>对象扩展（extend）现有的接口声明。</p>
<h2 id="typescriptwindow">TypeScript 中的 window 简介</h2>
<p>知识建立在知识之上。</p>
<p>这是对的。</p>
<p>在本节中，我们将建立在前两节的知识基础上：</p>
<ul>
<li><a href="https://blog.ohansemmanuel.com/interfaces-vs-types-in-typescript/">TypeScript 中的接口与类型对比</a></li>
<li><a href="https://blog.ohansemmanuel.com/what-is-a-dts-file-in-typescript/">什么是 TypeScript 中的 d.t.s 文件？</a>？</li>
</ul>
<p>准备好了吗？</p>
<p>首先，我必须说，在我使用 TypeScript 的早期，这是一个我在谷歌上一遍又一遍搜索的问题。</p>
<p>我从来没有理解它。我也懒得理会，我只是在网上搜索。</p>
<p>这绝不是掌握一门学问的正确心态。</p>
<p>让我们来讨论一下这个问题的解决方案。</p>
<h2 id="">理解问题</h2>
<p>这里的问题实际上很容易推理。</p>
<p>思考以下 TypeScript 代码：</p>
<pre><code class="language-ts">window.__MY_APPLICATION_NAME__ = "freecodecamp"

console.log(window.__MY_APPLICATION_NAME__)
</code></pre>
<p>TypeScript 会很快让你知道<code>__MY_APPLICATION_NAME__</code>不存在于 <code>Window &amp; typeof globalThis</code> 类型。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-71.png" alt="该属性不存在于 Window 上的报错" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>该属性不存在于 Window 上的报错</figcaption>
</figure>
<p>查看 <a href="https://www.freecodecamp.org/news/p/a31cc449-928c-4453-a83d-ce30ef79f986/%20https://www.typescriptlang.org/play?#code/O4SwdgJg9sB0D68CyBNeBBACpgMgSQGF0AVPAeQDl4L0kBRRAAgF5GAiAMwCcBTHgYygQBAQwC2ABzYAoaYLABnKABsesZVADmAClCQYCZGiy5CJclRr1EASln2gA">TypeScript playground</a>。</p>
<p>好吧，TypeScript。</p>
<p>我们明白了。</p>
<p>仔细观察，记得上一节关于<a href="https://blog.ohansemmanuel.com/what-is-a-dts-file-in-typescript/">声明文件（declaration files）</a>，所有现有的浏览器 API 都有一个声明文件。这包括内置对象，如<code>window</code>。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-72.png" alt="默认的 Window 接口声明" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>默认的 Window 接口声明</figcaption>
</figure>
<p>如果你看看<code>lib.dom.d.ts</code>声明文件，你会发现<code>Window</code>接口的描述。</p>
<p>用通俗的话说，这里的错误是：<code>Window</code>接口描述了我对<code>window</code>对象及其用法的理解。该接口没有指定某个<code>__MY_APPLICATION_NAME__</code>属性。</p>
<h2 id="">如何修复该错误</h2>
<p>在类型（types）与接口（interface）部分，我解释了如何扩展一个接口。</p>
<p>让我们在这里应用这些知识。</p>
<p>我们可以扩展（extend）<code>Window</code> 接口声明 (interface declaration)，使其知道<code>__MY_APPLICATION_NAME__</code>属性。</p>
<p>下面是方法：</p>
<pre><code class="language-ts">// before
window.__MY_APPLICATION_NAME__ = "freecodecamp"

console.log(window.__MY_APPLICATION_NAME__)

// now 
interface Window {
  __MY_APPLICATION_NAME__: string
}

window.__MY_APPLICATION_NAME__ = "freecodecamp"

console.log(window.__MY_APPLICATION_NAME__)
</code></pre>
<p>没有错误了！</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-74.png" alt="解决问题的方案" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>解决问题的方案</figcaption>
</figure>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgOqgCYHsDuyDeAUMsgPqkCyAmqQIIAK9AMgJIDCtAKiwPIBypPrQoBRcgC5kAZzBRQAc0IBfQoRyZcAOnLU6jVh279BwsaWQBeZACIYUCBARYMjuAFsADtdVOQUrAA2EJoBWPIAFOog2DjalDQMzOxcvAJCouQAlEA">TypeScript playground</a>。</p>
<p>记住，类型（types）和接口（interfaces）的一个关键区别是，接口可以通过多次声明来扩展（extended）。</p>
<p>我们在这里所做的是再一次声明了 <code>Window</code>接口，因此扩展了（extending）接口声明。</p>
<h3 id="">一个现实世界的解决方案</h3>
<p>我在 TypeScript playground 解决了这个问题，向你展示了最简单的解决方案，这就是关键所在。</p>
<p>不过，在现实世界中，你不会在你的代码中扩展接口。</p>
<p>那么，你应该怎么做呢？</p>
<p>也许，给它一个猜测？</p>
<p>是的，你很接近……，也可能是正确的。</p>
<p>创建一个类型定义文件！</p>
<p>例如，创建一个<code>window.d.ts</code>文件，内容如下:</p>
<pre><code class="language-ts">interface Window {
  __MY_APPLICATION_NAME__: string
}
</code></pre>
<p>然后就可以了。</p>
<p>你已经成功地扩展了<code>Window</code>接口并解决了问题。</p>
<p>如果你继续给`__MY_APPLICATION_NAME__属性分配了错误的值类型，你现在已经启用了强类型检查。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/image-75.png" alt="image-75" width="600" height="400" loading="lazy"></p>
<p>对新定义的属性进行错误的赋值，将会有警告。</p>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgOqgCYHsDuyDeAUMsgPqkCyAmqQIIAK9AMgJIDCtAKiwPIBypPrQoBRcgC5kAZzBRQAc0IBfQoRyZcAOnLU6jVh279BwsaWQBeAsWQg4AWwiSARDCgQICLBk8OADs7Kql4gUlgANhCa4VjyABTqINg42pQ0DMzsXLwCQqLkAJSqxUA">TypeScript playground</a>。</p>
<h2 id="">总结</h2>
<p>在<a href="https://stackoverflow.com/questions/12709074/how-do-you-explicitly-set-a-new-property-on-window-in-typescript">旧的 stack overflow 帖子</a>，你会发现基于旧的 TypeScript 版本会方案更复杂。</p>
<p>在新版 TypeScript 中，该解决方案更容易。</p>
<p>现在你知道了。😉</p>
<h2 id="5-are-strongly-typed-functions-as-parameters-possible-in-typescript">强类型函数作为参数在 TypeScript 中是否可行？</h2>
<h2 id="tldr">TL;DR</h2>
<p>这个问题不需要过多的解释。简短的回答是肯定的。</p>
<p>函数可以被强类型化--甚至作为其他函数的参数。</p>
<h2 id="">简介</h2>
<p>我必须说，与本文的其他部分不同，在我早期的 TypeScript 时代，我从未真正发现自己在寻找这个。</p>
<p>然而，这并不是最重要的。</p>
<p>这是一个经过精心研究的问题，所以让我们来回答它吧！</p>
<h2 id="typescript">如何在 TypeScript 中使用强类型的函数参数</h2>
<p>这个<a href="https://stackoverflow.com/questions/14638990/are-strongly-typed-functions-as-parameters-possible-in-typescript">stack overflow post</a> 上的公认答案是正确的，在一定程度上。</p>
<p>假设你有一个函数: <code>speak</code>：</p>
<pre><code class="language-ts">function speak(callback) {
  const sentence = "Hello world"
  alert(callback(sentence))
}
</code></pre>
<p>它接收一个 <code>callback</code>，在内部用一个 <code>string</code> 来调用。</p>
<p>为了打这个字，继续用一个函数类型的别名来表示 <code>callback</code>。</p>
<pre><code class="language-ts">type Callback = (value: string) =&gt; void
</code></pre>
<p>并敲下 <code>speak</code> 函数，如下所示：</p>
<pre><code class="language-ts">function speak(callback: Callback) {
  const sentence = "Hello world"
  alert(callback(sentence))
}
</code></pre>
<p>另外，你也可以将该保持类型内联（type inline）：</p>
<pre><code class="language-ts">function speak(callback: (value: string) =&gt; void) {
  const sentence = "Hello world"

  alert(callback(sentence))
}
</code></pre>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABAZwA4FMCGBrAFBTAG0ICNMJsAuRXANyJHWuSgCcYwBzASkQF4AfIlpwYAE14BvAFCJEEBCxTowUFRHT9EAIgAS6YnEQB3OK0Jjt02YiLpWUfEVLk8yFWsjpu3aQF9rQOkgA">TypeScript playground</a>。</p>
<p>这就是了！</p>
<p>你使用了一个强类型的函数作为参数。</p>
<h2 id="">如何处理没有返回值的函数</h2>
<p>例如，参考的堆栈溢出帖子中接受的答案说  <em>回调参数的类型必须是</em> "function that accepts a number and returns type any. (接收数字并返回任何类型的函数)"</p>
<p>这部分是正确的，但是返回类型不一定是<code>any</code>。</p>
<p>事实上，不要使用 <code>any</code>。</p>
<p>如果你的函数返回一个值，请继续并适当地输入它：</p>
<pre><code class="language-ts">// 回调返回一个对象
type Callback = (value: string) =&gt; { result: string }
</code></pre>
<p>If your callback returns nothing, use <code>void</code> not <code>any</code>:</p>
<pre><code class="language-ts">// 回调不返回一个对象
type Callback = (value: string) =&gt; void
</code></pre>
<p>注意，你的函数类型的签名（signature of your function type）应该是:</p>
<pre><code class="language-ts">(arg1: Arg1type, arg2: Arg2type) =&gt; ReturnType
</code></pre>
<p>其中<code>Arg1type</code>是参数<code>arg1</code>的类型，<code>Arg2type</code>是参数<code>arg2</code>的类型，而<code>ReturnType</code>是你的函数的返回类型。</p>
<h2 id="">总结</h2>
<p>JavaScript 中传递数据的主要手段是函数。</p>
<p>TypeScript 不仅允许你指定函数的输入和输出，而且你还可以将函数作为其他函数的参数。</p>
<p>去吧，放心地使用它们。</p>
<h2 id="6-how-to-fix-could-not-find-declaration-file-for-module-">如何修复无法找到模块的声明文件......？</h2>
<p>对于 TypeScript 初学者来说，这是一个常见的挫折来源。</p>
<p>然而，你知道如何解决这个问题吗？</p>
<p>是的，你知道！</p>
<p>我们在 <code>what is  d.ts</code> 一节中看到了这个问题的解决方案。</p>
<h2 id="tldr">TL;DR</h2>
<p>创建一个声明文件，例如 <code>untyped-module.d.ts</code>，其内容如下：<code>declare module "ste-untyped-module";</code> 注意，这将明确地将模块类型为<code>any</code>。</p>
<h2 id="">解释解决方案</h2>
<p>如果你不记得如何解决这个问题，你可以重新阅读写声明文件一节。</p>
<p>根本原因，你有这个错误是因为相关的库没有它的类型，并且在 <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/">DefinitelyTyped</a> 上没有一个类型定义文件。</p>
<p>这就给你留下了一个解决方案：编写你自己的声明文件。</p>
<p>例如，如果你在项目中安装了库<code>untyped-module</code>，<code>untyped-module</code>没有引用的类型定义文件，所以 TypeScript 会警告。</p>
<p>为了消除这个警告，在你的项目中创建一个新的<code>untyped-module.d.ts</code>文件，内容如下：</p>
<pre><code class="language-ts">declare module "some-untyped-module";
</code></pre>
<p>这将声明该模块为 <code>any</code> 类型。</p>
<p>你不会得到任何 TypeScript 对该模块的支持，但你已经消除了 TypeScript 的警告。</p>
<p>下一步包括在模块的公共仓库中开一个 issue，以包括一个 TypeScript 声明文件，或者自己写一个像样的文件（超出本文的范围）。</p>
<h2 id="7-how-do-i-dynamically-assign-properties-to-an-object-in-typescript">如何在TypeScript中为对象动态分配属性？</h2>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-76.png" alt="在 Typescript 中动态地给对象分配属性" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>在 Typescript 中动态地给对象分配属性</figcaption>
</figure>
<h2 id="tldr">TL;DR</h2>
<p>如果你不能在声明时定义变量类型，请使用 <code>Record</code> utility 类型或对象索引签名。</p>
<h2 id="">简介</h2>
<p>请思考以下例子：</p>
<pre><code class="language-ts">const organization = {}

organization.name = "Freecodecamp"
                                                                                                            
</code></pre>
<p>这段看似无害的代码在动态分配<code>name</code>给<code>organization</code>对象时抛出一个 TypeScript 错误。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-80.png" alt="动态添加新属性时出现的 Typescript 错误" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>动态添加新属性时出现的 Typescript 错误</figcaption>
</figure>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/MYewdgzgLgBCBOBzAhmAlgL2VN4YF4YBvAXwCgyEV0sdwA6MZAWwFMCYAiAMXlddAATASwAOnCjCnSZsufIWKlylarXqZFIA">Typescript playground</a>。</p>
<p>如果你是 TypeScript 的初学者，会感到困惑，也许是有道理的，那就是看似简单的东西在 TypeScript 中怎么会成为问题？</p>
<h2 id="">理解问题</h2>
<p>一般来说，TypeScript 在声明变量时确定其类型，并且这个确定的类型不会改变，也就是在你的应用程序中应该保持不变。</p>
<p>在考虑类型缩小或使用 any 类型时，此规则有例外，但这是要记住的一般规则。</p>
<p>在前面的示例中，<code>organization</code> 对象声明如下：</p>
<pre><code class="language-ts">const organization = {}
</code></pre>
<p>没有为 <code>organization</code> 变量指定明确的类型，所以 TypeScript 根据声明推断 <code>organization</code> 的类型为 <code>{}</code>，即字面的空对象（iteral empty object）。</p>
<p>例如，如果你添加一个类型别名（type alias），你可以在 <code>organization</code> 的类型上进行尝试:</p>
<pre><code class="language-ts">type Org = typeof organization
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-81.png" alt="探索字面对象类型" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>探索字面对象类型</figcaption>
</figure>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/MYewdgzgLgBCBOBzAhmAlgL2VN4YF4YBvAXwCgyoBPABwFMYB5JAma+kAMziVU21xgKCFOiw5wAOjDIAtg0IAiAGLw6dUABMNcmoooxDR4ydNnzFy1es3bd4xSA">TypeScript playground</a>。</p>
<p>当你试图在这个空对象的字面意义上引用<code>name</code> prop 时：</p>
<pre><code class="language-ts">organization.name = ...
</code></pre>
<p>TypeScript 大喊。</p>
<blockquote>
<p><code>{}</code>类型上不存在 <code>name</code> 属性。</p>
</blockquote>
<p>当你理解了这个问题后，这个错误确实该发生。</p>
<p>让我们来解决这个问题。</p>
<h2 id="">如何解决该错误</h2>
<p>有许多方法可以解决这里的 TypeScript 错误。让我们考虑一下这些：</p>
<h3 id="1">1. 在声明时明确地输入对象</h3>
<p>这是最容易推理的解决方案。</p>
<p>在你声明对象的时候，去输入它。此外，给它分配所有相关的值。</p>
<pre><code class="language-ts">type Org = {
    name: string
}

const organization: Org = {
    name: "Freecodecamp"
}
</code></pre>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBA8gTgcygXigbwFBW1AdgQwFsIAuKAZ2DgEtcEMBfDDAYwHtdKo3F9dqAXvmDUOZeElSYceIqSgAiAGJwIEdgBN1RMAsbMDQA">TypeScript playground</a>。</p>
<p>这就消除了所有的警告。</p>
<p>有时，对象的属性确实需要在声明时之后添加。</p>
<p>然而，如果必须动态地添加对象的属性，这并不总是可行的。</p>
<h3 id="2">2. 使用一个对象索引签名</h3>
<p>偶尔，对象的属性确实需要在比声明时更晚的时间被添加。</p>
<p>在这种情况下，你可以利用对象索引签名，如下所示：</p>
<pre><code class="language-ts">type Org = {[key: string] : string}

const organization: Org = {}

organization.name = "Freecodecamp"
</code></pre>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBA8gTgcygXigbwNoGsIgFxQDOwcAlgHYIC6UBxZlAvgFDMDGA9ucVB4gIblSAL37BSXAvCSo0LZnwSCRYieQB05fgFtoqAEQAxOBAicAJmZ1h9rO0A">TypeScript playground</a>。</p>
<p>在声明 <code>organization</code> 变量时，你继续并将其显式键入到以下 <code>{[key: string] : string}</code>。</p>
<p>为了进一步解释语法，你可能习惯于具有固定属性类型的对象类型：</p>
<pre><code class="language-ts">type obj = {
  name: string
}
</code></pre>
<p>但你也可以用 <code>name</code> 代替 <code>variable type(变量类型)</code>。</p>
<p>例如，如果你想在 <code>obj</code> 上定义任何字符串属性：</p>
<pre><code class="language-ts">type obj = {
 [key: string]: string
}
</code></pre>
<p>请注意，语法类似于你在标准 JavaScript 中使用变量对象属性的方式：</p>
<pre><code class="language-ts">const variable = "name" 

const obj = {
   [variable]: "Freecodecamp"
}
</code></pre>
<p>TypeScript 等效项（equivalent）称为对象索引签名。</p>
<p>另外，请注意，你可以使用其他原语(other primitives)键入 <code>key</code>：</p>
<pre><code class="language-ts">// number 
type Org = {[key: number] : string}

// string 
type Org = {[key: string] : string}

//boolean
type Org = {[key: boolean] : string}
</code></pre>
<h3 id="3recordutilitytype">3. 使用 Record utility type</h3>
<p>这里的解决方案非常简洁：</p>
<pre><code class="language-ts">type Org = Record&lt;string, string&gt;

const organization: Org = {}


organization.name = "Freecodecamp"
</code></pre>
<p>除了使用类型别名（type alias），你还可以内联类型（inline the type）。</p>
<pre><code class="language-ts">const organization: Record&lt;string, string&gt; = {}
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-82.png" alt="使用 Record utility type" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>使用 Record utility type</figcaption>
</figure>
<p>查看 <a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBA8gTgcygXigJQgYwPZwCYA8AzsHAJYB2CANFCeVQHwBQzOFJUuCAhhWQC8ewMtgoAuWIhRQA3gF9Wzbn0HDRFAHQUeAW2ioARADE4ELNjxY9YQ0tZA">TypeScript playground</a>。</p>
<p><code>Record</code> utility type 有以下签名：<code>Record&lt;Keys, Type&gt;</code>。</p>
<p>它允许你约束一个对象类型，其属性是<code>Keys</code>，属性值是<code>Type</code>。</p>
<p>在我们的例子中，<code>Keys</code> 代表 <code>string</code>，<code>Type</code> 也代表<code>string</code>。</p>
<h2 id="">总结</h2>
<p>除了原语（primitives）之外，你必须处理的最常见的类型可能是对象类型。</p>
<p>如果你需要动态构建对象，请利用 Record utility type 或使用对象索引签名来定义对象上允许的属性。</p>
<p>请注意，你可以获得 <a href="https://www.ohansemmanuel.com/cheatsheet/top-7-stack-overflowed-typescript-questions">PDF 或 ePub</a>，该手册的版本以便于参考，或在你的 Kindle 或平板电脑上阅读。</p>
<p>谢谢你阅读本文！</p>
<h2 id="typescript">想要一本免费的 TypeScript 书籍吗？</h2>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/07/image-78.png" alt="构建强类型多态的 React 组件书籍" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>构建强类型多态的 React 组件书籍</figcaption>
</figure>
<p><a href="https://www.ohansemmanuel.com/books/how-to-build-strongly-typed-polymorphic-react-components">免费获取这本书</a>。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 面向初学者的 TypeScript 完全指南 ]]>
                </title>
                <description>
                    <![CDATA[ 在过去的几年里，TypeScript变得越来越流行，许多工作现在都要求开发人员了解TypeScript。 但不要担心--如果你已经了解JavaScript，你将能够迅速掌握TypeScript。 即使你不打算使用TypeScript，学习它也会让你对JavaScript有更好的理解--让你成为更好的开发者。 在本文中，你将了解到：  * 什么是TypeScript，为什么要学习它？  * 如何使用 TypeScript 设置项目  * TypeScript的所有主要概念（类型、接口、泛型、类型转换，以及更多...）  * 如何在React中使用TypeScript 我还制作了一个PDF 格式TypeScript手册 [https://doabledanny.gumroad.com/l/typescript-cheat-sheet-pdf]和海报 [https://doabledanny.gumroad.com/l/typescript-cheat-sheet-poster] ，将这篇文章总结为一页，便于你快速查找和修改概念/语法。 PDF 格式 TypeScript 手册什 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/</link>
                <guid isPermaLink="false">620f801b230413063576089d</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Fri, 18 Feb 2022 03:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/02/Cheat-Sheet-Poster--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/learn-typescript-beginners-guide/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Learn TypeScript – The Ultimate Beginners Guide</a>
      </p><!--kg-card-begin: markdown--><p>在过去的几年里，TypeScript变得越来越流行，许多工作现在都要求开发人员了解TypeScript。</p>
<p>但不要担心--如果你已经了解JavaScript，你将能够迅速掌握TypeScript。</p>
<p>即使你不打算使用TypeScript，学习它也会让你对JavaScript有更好的理解--让你成为更好的开发者。</p>
<p>在本文中，你将了解到：</p>
<ul>
<li>什么是TypeScript，为什么要学习它？</li>
<li>如何使用 TypeScript 设置项目</li>
<li>TypeScript的所有主要概念（类型、接口、泛型、类型转换，以及更多...）</li>
<li>如何在React中使用TypeScript</li>
</ul>
<p>我还制作了一个<a href="https://doabledanny.gumroad.com/l/typescript-cheat-sheet-pdf">PDF 格式TypeScript手册</a>和<a href="https://doabledanny.gumroad.com/l/typescript-cheat-sheet-poster">海报</a>，将这篇文章总结为一页，便于你快速查找和修改概念/语法。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/01/TypeScript-Cheat-Sheet--DARK-.png" alt="PDF 格式 TypeScript 手册" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>PDF 格式 TypeScript 手册</figcaption>
</figure>
<h2 id="typescript">什么是TypeScript</h2>
<p>TypeScript是JavaScript的超集，意味着它能做JavaScript所做的一切，但有一些附加功能。</p>
<p>使用TypeScript的主要原因是为JavaScript添加静态类型。静态类型意味着变量的类型在程序中的任何时候都不能被改变。它可以防止大量的bug！</p>
<p>另一方面，JavaScript是一种动态类型的语言，意味着变量可以改变类型。这里有一个例子：</p>
<pre><code class="language-ts">// JavaScript
let foo = "hello";
foo = 55; // foo 的类型已从字符串变为数字 - 没问题

// TypeScript
let foo = "hello";
foo = 55; // 错误 - foo 无法从字符串变为数字
</code></pre>
<p>TypeScript不能被浏览器理解，所以它必须由TypeScript编译器（TSC）编译成JavaScript，我们很快会讨论这个问题。</p>
<h2 id="typescript">TypeScript值得吗</h2>
<h3 id="typescript">为什么要使用TypeScript</h3>
<ul>
<li>研究表明，TypeScript可以发现15%的常见错误。</li>
<li>可读性--更容易看到代码应该做什么。而在团队中工作时，更容易看到其他开发人员的意图。</li>
<li>它很受欢迎--了解TypeScript将使你能够申请到更多好工作。</li>
<li>学习TypeScript会让你对JavaScript有更好的理解，并有新的视角。</li>
</ul>
<p><a href="https://www.doabledanny.com/why-typescript-over-javascript">下面是我写的一篇短文，展示了TypeScript如何防止烦人的Bug</a>。</p>
<h3 id="typescript">TypeScript的缺点</h3>
<ul>
<li>TypeScript的编写时间比JavaScript长，因为你必须指定类型，所以对于较小的单独项目，可能不值得使用它。</li>
<li>TypeScript必须进行编译--这可能需要时间，特别是在大型项目中</li>
</ul>
<p>但是，你必须花更多的时间来写更精确的代码和编译，这将使你的代码中的错误减少得更多。</p>
<p>对于许多项目--尤其是中大型项目--TypeScript将为你节省大量的时间和麻烦。</p>
<p>如果你已经知道JavaScript，TypeScript也不会太难学。它是你武器库中的一个重要工具。</p>
<h2 id="typescript">如何设置TypeScript项目</h2>
<h3 id="nodetypescript">安装Node和TypeScript编译器</h3>
<p>首先，确保你的机器上全局安装了<a href="https://nodejs.org/en/download/">Node</a>。</p>
<p>然后通过运行以下命令在你的机器上全局安装TypeScript编译器。</p>
<pre><code class="language-bash">npm i -g typescript
</code></pre>
<p>检查安装是否成功（如果成功，它将返回版本号）：</p>
<pre><code class="language-bash">tsc -v
</code></pre>
<h3 id="typescript">如何编译TypeScript</h3>
<p>打开你的文本编辑器，创建一个TypeScript文件（例如，index.ts）。</p>
<p>编写一些JavaScript或TypeScript。</p>
<pre><code class="language-ts">let sport = 'football';

let id = 5;
</code></pre>
<p>现在我们可以用以下命令将其编译成JavaScript：</p>
<pre><code class="language-bash">tsc index
</code></pre>
<p>TSC将把代码编译成JavaScript，并在一个名为index.js的文件中输出。</p>
<pre><code class="language-js">var sport = 'football';
var id = 5;
</code></pre>
<p>如果你想指定输出文件的名称：</p>
<p><code>tsc index.ts --outfile file-name.js</code></p>
<p>如果你想让TSC自动编译你的代码，每当你做了一个改动，请添加 "watch "（缩写 w）标志：</p>
<p><code>tsc index.ts -w</code></p>
<p>TypeScript的一个有趣之处在于，当你在编码时，它会在文本编辑器中报告错误，但它总是会编译你的代码，无论是否有错误。</p>
<p>例如，下面的内容会使TypeScript立即报告错误：</p>
<pre><code class="language-ts">var sport = 'football';
var id = 5;

id = '5'; // Error: Type 'string' is not assignable to 
type 'number'.
</code></pre>
<p>但如果我们尝试用<code>tsc index</code>来编译这段代码，尽管有错误，但代码仍然可以编译。</p>
<p>这是TypeScript的一个重要特性：它假定开发者知道得更多。即使有一个TypeScript错误，它也不会妨碍你编译代码。它告诉你有一个错误，但这取决于你是否对它做了什么。</p>
<h3 id="ts">如何设置ts配置文件</h3>
<p>ts 配置文件应该在你项目的根目录下。在这个文件中，我们可以指定根文件、编译器选项以及我们希望 TypeScript 在检查我们项目时有多严格。</p>
<p>首先，创建 ts 配置文件：</p>
<p><code>tsc --init</code></p>
<p>你现在应该在项目根部有一个<code>tsconfig.json</code>文件。</p>
<p>这里有一些需要注意的选项（如果使用带有TypeScript的前端框架，这些东西大部分都是为你准备的）。</p>
<pre><code class="language-json">{
    "compilerOptions": {
        ...
        /* Modules */
        "target": "es2016", // 改为 “ES2015” 以编译为 ES6
        "rootDir": "./src", // 从何处编译
        "outDir": "./public", // 编译到何处（通常是要部署到网络服务器的文件夹）
        
        /* JavaScript Support */
        "allowJs": true, // 允许编译 JavaScript 文件
        "checkJs": true, // 检查 JavaScript 文件类型并报告错误
        
        /* Emit */
        "sourceMap": true, // 为生成的 JavaScript 文件创建源映射文件（利于调试）
         "removeComments": true, // 不要生成注释
    },
    "include": ["src"] // 确保只编译 src 中的文件
}
</code></pre>
<p>编译所有内容并观察变化：</p>
<p><code>tsc -w</code></p>
<blockquote>
<p>注意：当输入文件在命令行中被指定时（例如，<code>tsc index</code>），<code>tsconfig.json</code>文件被忽略。</p>
</blockquote>
<h2 id="typescript">TypeScript中的类型</h2>
<h3 id="">原始类型</h3>
<p>在JavaScript中，原始数据类型是指不属于对象且没有方法的数据。有7种原始数据类型。</p>
<ul>
<li>string</li>
<li>number</li>
<li>bigint</li>
<li>boolean</li>
<li>undefined</li>
<li>null</li>
<li>symbol</li>
</ul>
<p>基元（Primitives）是不可变的（immutable）：它们不能被改变。重要的是，不要将基元本身与分配给基元值的变量相混淆。变量可以被重新分配一个新值，但现有值不能像对象、数组和函数那样被改变。</p>
<p>这是一个例子：</p>
<pre><code class="language-js">let name = 'Danny';
name.toLowerCase();
console.log(name); // Danny - 字符串方法没有改变字符串

let arr = [1, 3, 5, 7];
arr.pop();
console.log(arr); // [1, 3, 5] - 数组方法改变了数组

name = 'Anna' // 赋值给基元一个新值（而不是改变值）
</code></pre>
<p>在JavaScript中，所有的原始值（除了null和undefined）都有对应的对象，这些对象包裹着原始值。这些包装对象是String、Number、BigInt、Boolean和Symbol。这些包装对象提供了允许原始值被操纵的方法。</p>
<p>回到TypeScript，我们可以在声明一个变量后添加<code>: type</code>（称为 "类型注解 "或 "类型签名"）来设置我们希望变量的类型。例子。</p>
<pre><code class="language-ts">let id: number = 5;
let firstname: string = 'danny';
let hasDog: boolean = true;

let unit: number; // Declare variable without assigning a value
unit = 5;
</code></pre>
<p>但通常最好不要明确说明类型，因为TypeScript会自动推断变量的类型（类型推论）：</p>
<pre><code class="language-js">let id = 5; // TS 知道它是一个数字
let firstname = 'danny'; // TS 知道它是一个字符串
let hasDog = true; // TS 知道它是布尔值

hasDog = 'yes'; // ERROR
</code></pre>
<p>我们也可以将一个变量设定为能够成为联合类型。<strong>联合类型是一个可以被分配到多个类型的变量</strong>：</p>
<pre><code class="language-ts">let age: string | number;
age = 26;
age = '26';
</code></pre>
<h3 id="">引用类型</h3>
<p>在JavaScript中，几乎“所有东西”都是一个对象。事实上（而且令人困惑的是），如果用<code>new</code>关键字定义的话，字符串、数字和布尔都可以成为对象:</p>
<pre><code class="language-javascript">let firstname = new String('Danny');
console.log(firstname); // String {'Danny'}
</code></pre>
<p>但是，当我们谈论JavaScript中的引用类型时，我们指的是数组、对象和函数。</p>
<h4 id="">注意事项：原始类型与引用类型</h4>
<p>对于那些从未研究过原始类型与引用类型的人来说，让我们来讨论一下其根本区别。</p>
<p>如果一个基元类型被分配给一个变量，我们可以认为该变量是<strong>包含</strong>基元值的。每个基元值都存储在内存中的一个唯一位置。</p>
<p>如果我们有两个变量x和y，并且它们都包含原始数据，那么它们就完全相互独立。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/01/image-66.png" alt="X和Y都包含唯一的独立原始数据" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>X和Y都包含唯一的独立原始数据</figcaption>
</figure>
<pre><code class="language-js">let x = 2;
let y = 1;

x = y;
y = 100;
console.log(x); // 1 (even though y changed to 100, x is still 1)
</code></pre>
<p>而引用类型则不是这样。引用类型指向的是存储对象的一个内存位置。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/image-67.png" alt="Reference types memory locations" width="600" height="400" loading="lazy"></p>
<p>point1和point2包含一个对存储对象的地址的引用。</p>
<pre><code class="language-js">let point1 = { x: 1, y: 1 };
let point2 = point1;

point1.y = 100;
console.log(point2.y); // 100 (point1和point2多指向的是存储点对象的同一个内存地址)
</code></pre>
<p>这是对主要类型与参考类型的一个快速概述。如果你需要更深入的解释，请看这篇文章。<a href="https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0">原始类型与引用类型</a>。</p>
<h4 id="typescript">TypeScript 中的数组</h4>
<p>在TypeScript中，你可以定义一个数组可以包含哪些类型的数据：</p>
<pre><code class="language-ts">let ids: number[] = [1, 2, 3, 4, 5]; // 只能包含数字
let names: string[] = ['Danny', 'Anna', 'Bazza']; // 只能包含字符串
let options: boolean[] = [true, false, false]; 只能包含真或假
let books: object[] = [
  { name: 'Fooled by randomness', author: 'Nassim Taleb' },
  { name: 'Sapiens', author: 'Yuval Noah Harari' },
]; // 只能包含对象
let arr: any[] = ['hello', 1, true]; // 这基本上是将 TypeScript 还原为 JavaScript

ids.push(6);
ids.push('7'); // 错误：“字符串”类型的参数不能分配给“数字”类型的参数。
</code></pre>
<p>你可以使用联合类型来定义包含多种类型的数组：</p>
<pre><code class="language-ts">let person: (string | number | boolean)[] = ['Danny', 1, true];
person[0] = 100;
person[1] = {name: 'Danny'} // Error - person array can't contain objects
</code></pre>
<p>如果你用一个值初始化一个变量，没有必要明确说明类型，因为TypeScript会推断出它的类型：</p>
<pre><code class="language-ts">let person = ['Danny', 1, true]; // This is identical to above example
person[0] = 100;
person[1] = { name: 'Danny' }; // Error - person array can't contain objects
</code></pre>
<p>一种特殊类型的数组可以在TypeScript中定义。元组（Tuples）。<strong>元组是一个具有固定大小和已知数据类型的数组，</strong> 它们比普通数组更严格。</p>
<pre><code class="language-js">let person: [string, number, boolean] = ['Danny', 1, true];
person[0] = 100; // 错误 - 索引 0 的值只能是字符串
</code></pre>
<h4 id="typescript">TypeScript中的对象</h4>
<p>TypeScript中的对象必须有所有正确的属性和值类型：</p>
<pre><code class="language-ts">// 使用特定对象类型方法声明名为 person 的变量
let person: {
  name: string;
  location: string;
  isProgrammer: boolean;
};

// 将 person 赋值为给一个具有所有必要属性和值类型的对象
person = {
  name: 'Danny',
  location: 'UK',
  isProgrammer: true,
};

person.isProgrammer = 'Yes'; // 错误：应为布尔值


person = {
  name: 'John',
  location: 'US',
}; 
// 错误：缺少 isProgrammer 属性
</code></pre>
<p>当定义一个对象的签名时，你通常会使用一个 <strong>接口（interface）</strong>。如果我们需要检查多个对象是否具有相同的特定属性和价值类型，这一点很有用：</p>
<pre><code class="language-ts">interface Person {
  name: string;
  location: string;
  isProgrammer: boolean;
}

let person1: Person = {
  name: 'Danny',
  location: 'UK',
  isProgrammer: true,
};

let person2: Person = {
  name: 'Sarah',
  location: 'Germany',
  isProgrammer: false,
};
</code></pre>
<p>我们也可以用函数签名来声明函数属性。我们可以使用老式的普通JavaScript function(<code>sayHi</code>)，或者ES6的箭头函数（<code>sayBye</code>）来做这件事：</p>
<pre><code class="language-ts">interface Speech {
  sayHi(name: string): string;
  sayBye: (name: string) =&gt; string;
}

let sayStuff: Speech = {
  sayHi: function (name: string) {
    return `Hi ${name}`;
  },
  sayBye: (name: string) =&gt; `Bye ${name}`,
};

console.log(sayStuff.sayHi('Heisenberg')); // Hi Heisenberg
console.log(sayStuff.sayBye('Heisenberg')); // Bye Heisenberg
</code></pre>
<p>注意在<code>sayStuff</code>对象中，<code>sayHi</code>或<code>sayBye</code>可以被赋予一个箭头函数或一个普通的JavaScript函数--TypeScript并不关心。</p>
<h4 id="typescript">TypeScript中的函数</h4>
<p>我们可以定义函数参数的类型，以及函数的返回类型：</p>
<pre><code class="language-ts">// 定义一个名为 circle 的函数，该函数接收一个数字类型的 diam 变量，并返回一个字符串
function circle(diam: number): string {
  return 'The circumference is ' + Math.PI * diam;
}

console.log(circle(10)); // The circumference is 31.41592653589793
</code></pre>
<p>同样的函数，但使用ES6箭头函数：</p>
<pre><code class="language-ts">const circle = (diam: number): string =&gt; {
  return 'The circumference is ' + Math.PI * diam;
};

console.log(circle(10)); // The circumference is 31.41592653589793
</code></pre>
<p>请注意，没有必要明确说明`circle'是一个函数；TypeScript会推断它。TypeScript也会推断出函数的返回类型，所以也不需要说明。不过，如果函数很大，有些开发者喜欢明确说明返回类型，以使其清晰明了。</p>
<pre><code class="language-ts">// 使用显式类型
const circle: Function = (diam: number): string =&gt; {
  return 'The circumference is ' + Math.PI * diam;
};

// 推断类型--TypeScript 认为 circle 是一个总是返回字符串的函数，因此无需明确说明。
const circle = (diam: number) =&gt; {
  return 'The circumference is ' + Math.PI * diam;
};
</code></pre>
<p>我们可以在一个参数后面加一个问号，使其成为可选参数，不一定需要传入此参数。还注意到下面<code>c</code>是一个联合类型，可以是数字或字符串：</p>
<pre><code class="language-ts">const add = (a: number, b: number, c?: number | string) =&gt; {
  console.log(c);

  return a + b;
};

console.log(add(5, 4, 'I could pass a number, string, or nothing here!'));
// I could pass a number, string, or nothing here!
// 9
</code></pre>
<p>一个不返回任何东西的函数被说成是返回void--完全没有任何返回值。下面，已经明确说明了void的返回类型。但是，这也是没有必要的，因为TypeScript会推断出它。</p>
<pre><code class="language-ts">const logMessage = (msg: string): void =&gt; {
  console.log('This is the message: ' + msg);
};

logMessage('TypeScript is superb'); // This is the message: TypeScript is superb
</code></pre>
<p>如果我们想声明一个函数变量，但不定义它（说清楚它的作用），<strong>就使用一个函数签名。</strong> 下面，函数<code>sayHello</code>必须跟在冒号后面的签名：</p>
<pre><code class="language-ts">// 声明变量 sayHello，并赋予它一个接收字符串且不返回任何内容的函数签名。
let sayHello: (name: string) =&gt; void;

// 定义函数，满足其签名
sayHello = (name) =&gt; {
  console.log('Hello ' + name);
};

sayHello('Danny'); // Hello Danny
</code></pre>
<h3 id="">动态(任意)类型</h3>
<p>使用<code>any</code>类型，我们基本上可以将TypeScript还原成JavaScript：</p>
<pre><code class="language-ts">let age: any = '100';
age = 100;
age = {
  years: 100,
  months: 2,
};
</code></pre>
<p>建议尽量避免使用 “any” 类型，因为它妨碍了TypeScript的工作--并可能导致错误。</p>
<h3 id="">类型别名</h3>
<p>类型别名可以减少代码的重复，使我们的代码保持干燥。下面，我们可以看到<code>PersonObject</code>类型别名已经防止了重复，并且作为一个单一的真理来源，说明一个人的对象应该包含哪些数据。</p>
<pre><code class="language-ts">type StringOrNumber = string | number;

type PersonObject = {
  name: string;
  id: StringOrNumber;
};

const person1: PersonObject = {
  name: 'John',
  id: 1,
};

const person2: PersonObject = {
  name: 'Delia',
  id: 2,
};

const sayHello = (person: PersonObject) =&gt; {
  return 'Hi ' + person.name;
};

const sayGoodbye = (person: PersonObject) =&gt; {
  return 'Seeya ' + person.name;
};
</code></pre>
<h3 id="dom">DOM和类型转换</h3>
<p>TypeScript并不像JavaScript那样可以访问DOM。这意味着，每当我们试图访问DOM元素时，TypeScript从不确定它们是否真的存在。</p>
<p>下面的例子显示了这个问题：</p>
<pre><code class="language-ts">const link = document.querySelector('a');

console.log(link.href); // 错误： 对象可能为“空”。TypeScript 无法确定锚标记是否存在，因为它无法访问 DOM
</code></pre>
<p>通过非空断言操作符（！），我们可以明确地告诉编译器，一个表达式的值不是“空”或“未定义”。当编译器不能确定地推断出类型，但我们比编译器拥有更多的信息时，这就很有用。</p>
<pre><code class="language-ts">//  在这里，我们告诉TypeScript，我们确定这个锚点标签存在
const link = document.querySelector('a')!;

console.log(link.href); // www.freeCodeCamp.org
</code></pre>
<p>请注意，我们不必说明<code>link</code>变量的类型。这是因为TypeScript可以清楚地看到（通过类型推论）它是<code>HTMLAnchorElement</code>类型。</p>
<p>但是，如果我们需要通过它的类或id来选择一个DOM元素呢？ TypeScript不能推断出类型，因为它可能是任何东西。</p>
<pre><code class="language-ts">const form = document.getElementById('signup-form');

console.log(form.method);
// 错误： 对象可能为“空”。
// 错误： “HTMLElement” 类型中不存在属性 “method”。
</code></pre>
<p>以上，我们得到了两个错误。我们需要告诉TypeScript，我们确定<code>form</code>存在，而且我们知道它是<code>HTMLFormElement</code>的类型。我们通过类型转换来实现这一点：</p>
<pre><code class="language-ts">const form = document.getElementById('signup-form') as HTMLFormElement;

console.log(form.method); // post
</code></pre>
<p>使用TypeScript，感到工作上的愉悦！</p>
<p>TypeScript也有一个内置的事件对象。所以，如果我们在表单中添加一个提交事件监听器，如果我们调用任何不属于Event对象的方法，TypeScript会给我们一个错误。看看TypeScript有多酷--它可以在我们犯了拼写错误时告诉我们：</p>
<pre><code class="language-ts">const form = document.getElementById('signup-form') as HTMLFormElement;

form.addEventListener('submit', (e: Event) =&gt; {
  e.preventDefault(); // prevents the page from refreshing

  console.log(e.tarrget); // 错误： 事件类型中不存在属性 "tarrget"。你是指 “target” 吗？
});

</code></pre>
<h2 id="typescript">TypeScript中的类</h2>
<p>我们可以在一个类中定义每一块数据具体的类型：</p>
<pre><code class="language-ts">class Person {
  name: string;
  isCool: boolean;
  pets: number;

  constructor(n: string, c: boolean, p: number) {
    this.name = n;
    this.isCool = c;
    this.pets = p;
  }

  sayHello() {
    return `Hi, my name is ${this.name} and I have ${this.pets} pets`;
  }
}

const person1 = new Person('Danny', false, 1);
const person2 = new Person('Sarah', 'yes', 6); // 错误：“字符串”类型的参数不能分配给“布尔”类型的参数。

console.log(person1.sayHello()); // Hi, my name is Danny and I have 1 pets
</code></pre>
<p>然后我们可以创建一个<code>people</code>数组，其中只包括由<code>Person</code>类构建的对象：</p>
<pre><code class="language-ts">let People: Person[] = [person1, person2];
</code></pre>
<p>我们可以在类的属性中添加访问修饰语。TypeScript也提供了一个新的访问修饰符，叫做<code>只读（readonly）</code>。</p>
<pre><code class="language-ts">class Person {
  readonly name: string; // 该属性不可变，只能被读取
  private isCool: boolean; // 只能访问或修改这个类中的方法
  protected email: string; // 可以访问和修改这个类和子类
  public pets: number; // 可从任何地方（包括类之外）访问或修改

  constructor(n: string, c: boolean, e: string, p: number) {
    this.name = n;
    this.isCool = c;
    this.email = e;
    this.pets = p;
  }

  sayMyName() {
    console.log(`Your not Heisenberg, you're ${this.name}`);
  }
}

const person1 = new Person('Danny', false, 'dan@e.com', 1);
console.log(person1.name); // Fine
person1.name = 'James'; // Error: read only
console.log(person1.isCool); // Error: private property - only accessible within Person class
console.log(person1.email); // Error: protected property - only accessible within Person class and its subclasses
console.log(person1.pets); // Public property - so no problem
</code></pre>
<p>我们可以通过这样构建类（constructing class）的属性来使我们的代码更加简洁：</p>
<pre><code class="language-ts">class Person {
  constructor(
    readonly name: string,
    private isCool: boolean,
    protected email: string,
    public pets: number
  ) {}

  sayMyName() {
    console.log(`Your not Heisenberg, you're ${this.name}`);
  }
}

const person1 = new Person('Danny', false, 'dan@e.com', 1);
console.log(person1.name); // Danny
</code></pre>
<p>以上述方式编写，属性会在构造函数中自动分配--省去了我们把它们全部写出来的麻烦。</p>
<p>注意，如果我们省略了访问修饰符，默认情况下，该属性将是公共的（public）。</p>
<p>类也可以被扩展（extend），就像在普通的JavaScript中一样：</p>
<pre><code class="language-ts">class Programmer extends Person {
  programmingLanguages: string[];

  constructor(
    name: string,
    isCool: boolean,
    email: string,
    pets: number,
    pL: string[]
  ) {
    // The super call must supply all parameters for base (Person) class, as the constructor is not inherited.
    super(name, isCool, email, pets);
    this.programmingLanguages = pL;
  }
}
</code></pre>
<p><a href="https://www.typescriptlang.org/docs/handbook/2/classes.html">查看 TypeScript 官方文档了解更多关于类的信息</a>。</p>
<h2 id="typescript">TypeScript中的模块</h2>
<p>在JavaScript中，一个模块只是一个包含相关代码的文件。功能可以在模块之间被导入和导出，使代码保持良好的组织。</p>
<p>TypeScript也支持模块。TypeScript文件将被编译成多个JavaScript文件。</p>
<p>在<code>tsconfig.json</code>文件中，改变以下选项以支持现代的导入和导出：</p>
<pre><code class="language-ts"> "target": "es2016",
 "module": "es2015"
</code></pre>
<p>（不过，对于Node项目来说，你很可能需要<code>"模块": "CommonJS"</code> - Node还不支持现代的导入/导出。）</p>
<p>现在，在你的HTML文件中，将脚本的导入改为模块类型：</p>
<pre><code class="language-html">&lt;script type="module" src="/public/script.js"&gt;&lt;/script&gt;
</code></pre>
<p>我们现在可以使用ES6导入和导出文件了:</p>
<pre><code class="language-ts">// src/hello.ts
export function sayHi() {
  console.log('Hello there!');
}

// src/script.ts
import { sayHi } from './hello.js';

sayHi(); // Hello there!
</code></pre>
<p>注意：总是作为一个JavaScript文件导入，即使在TypeScript文件中。</p>
<h2 id="typescript">TypeScript中的接口</h2>
<p>接口定义了一个对象：</p>
<pre><code class="language-ts">interface Person {
  name: string;
  age: number;
}

function sayHi(person: Person) {
  console.log(`Hi ${person.name}`);
}

sayHi({
  name: 'John',
  age: 48,
}); // Hi John
</code></pre>
<p>你也可以使用一个类型别名来定义一个对象类型：</p>
<pre><code class="language-ts">type Person = {
  name: string;
  age: number;
};

function sayHi(person: Person) {
  console.log(`Hi ${person.name}`);
}

sayHi({
  name: 'John',
  age: 48,
}); // Hi John
</code></pre>
<p>或者可以匿名地定义一个对象类型：</p>
<pre><code class="language-ts">function sayHi(person: { name: string; age: number }) {
  console.log(`Hi ${person.name}`);
}

sayHi({
  name: 'John',
  age: 48,
}); // Hi John
</code></pre>
<p>接口与类型别名非常相似，在许多情况下，你可以使用这两者。关键的区别是，类型别名不能被重新打开以添加新的属性，而接口总是可以扩展的。</p>
<p>下面的例子取自<a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces">TypeScript docs</a>。</p>
<p>扩展一个接口:</p>
<pre><code class="language-ts">interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}

const bear: Bear = {
  name: "Winnie",
  honey: true,
}
</code></pre>
<p>通过交集扩展一个类型:</p>
<pre><code class="language-ts">type Animal = {
  name: string
}

type Bear = Animal &amp; {
  honey: boolean
}

const bear: Bear = {
  name: "Winnie",
  honey: true,
}
</code></pre>
<p>在现有接口上添加新字段：</p>
<pre><code class="language-ts">interface Animal {
  name: string
}

// 重新打开 Animal interface 添加新字段
interface Animal {
  tail: boolean
}

const dog: Animal = {
  name: "Bruce",
  tail: true,
}
</code></pre>
<p>这里有一个关键的区别：一个类型在被创建后不能被改变。</p>
<pre><code class="language-ts">type Animal = {
  name: string
}

type Animal = {
  tail: boolean
}
// ERROR: Duplicate identifier 'Animal'.
</code></pre>
<p>作为一条经验法则，TypeScript文档推荐使用接口来定义对象，直到你需要使用一个类型的功能。</p>
<p>接口也可以定义函数签名：</p>
<pre><code class="language-ts">interface Person {
  name: string
  age: number
  speak(sentence: string): void
}

const person1: Person = {
  name: "John",
  age: 48,
  speak: sentence =&gt; console.log(sentence),
}
</code></pre>
<p>你可能想知道为什么在上面的例子中我们要使用一个接口而不是一个类。</p>
<p>使用接口的一个好处是，它只被TypeScript使用，而不是JavaScript。这意味着它不会被编译，也不会给你的JavaScript增加臃肿。类是JavaScript的特性，所以它会被编译。</p>
<p>另外，类本质上是一个<strong>对象工厂</strong>（也就是说，一个对象应该是什么样子的蓝图（blueprint），然后实现），而接口是一个仅用于<strong>类型检查的结构</strong>。</p>
<p>虽然一个类可能有初始化的属性和方法来帮助创建对象，但一个接口基本上定义了一个对象可以拥有的属性和类型。</p>
<h3 id="">有类的接口</h3>
<p>我们可以通过实现一个接口来告诉一个类，它必须包含某些属性和方法：</p>
<pre><code class="language-ts">interface HasFormatter {
  format(): string;
}

class Person implements HasFormatter {
  constructor(public username: string, protected password: string) {}

  format() {
    return this.username.toLocaleLowerCase();
  }
}

// 必须是实现 HasFormatter 接口的对象
let person1: HasFormatter;
let person2: HasFormatter;

person1 = new Person('Danny', 'password123');
person2 = new Person('Jane', 'TypeScripter1990');

console.log(person1.format()); // danny
</code></pre>
<p>确保<code>people</code>是一个实现<code>HasFormatter</code>的对象数组（确保每个人都有一样的 format method）:</p>
<pre><code class="language-ts">let people: HasFormatter[] = [];
people.push(person1);
people.push(person2);
</code></pre>
<h2 id="typescript">TypeScript中的字面类型</h2>
<p>除了一般的类型<code>string</code>和<code>number</code>之外，我们还可以在类型位置上引用特定的字符串和数字：</p>
<pre><code class="language-js">// 每个位置都有一个字面类型的联合类型
let favouriteColor: 'red' | 'blue' | 'green' | 'yellow';

favouriteColor = 'blue';
favouriteColor = 'crimson'; // ERROR: Type '"crimson"' is not assignable to type '"red" | "blue" | "green" | "yellow"'.
</code></pre>
<h2 id="">泛型</h2>
<p>泛型允许你创建一个可以在多种类型上工作的组件，而不是单一的类型，<strong>这有助于使组件更容易重复使用</strong>。</p>
<p>让我们通过一个例子来告诉你这意味着什么......</p>
<p>addID "函数接受任何对象，并返回一个新的对象，该对象具有所有的属性和值，加上一个 "id "属性，其值在0到1000之间。简而言之，它给任何对象一个ID。</p>
<pre><code class="language-ts"> const addID = (obj: object) =&gt; {
  let id = Math.floor(Math.random() * 1000);

  return { ...obj, id };
};

let person1 = addID({ name: 'John', age: 40 });

console.log(person1.id); // 271
console.log(person1.name); // ERROR: Property 'name' does not exist on type '{ id: number; }'.
</code></pre>
<p>正如你所看到的，当我们试图访问<code>name</code>属性时，TypeScript给出了一个错误。这是因为当我们传入一个对象到<code>addID</code>时，我们没有指定这个对象应该有什么属性,所以TypeScript不知道这个对象有什么属性（它没有 "捕获 "它们）。所以，TypeScript知道返回的对象的唯一属性是`id'。</p>
<p>那么，我们怎样才能向<code>addID</code>传递任何对象，但仍然告诉TypeScript这个对象有哪些属性和值呢？我们可以使用一个 <em>generic</em>,  其中<code>T</code>被称为 <em>type parameter</em> :</p>
<pre><code class="language-ts">// &lt;T&gt; is just the convention - e.g. we could use &lt;X&gt; or &lt;A&gt;
const addID = &lt;T&gt;(obj: T) =&gt; {
  let id = Math.floor(Math.random() * 1000);

  return { ...obj, id };
};
</code></pre>
<p>这有什么作用？现在，当我们把一个对象传给<code>addID</code>时，我们已经告诉TypeScript捕捉类型--所以<code>T</code>变成我们传入的任何类型。<code>addID</code>现在会知道我们传入的对象有哪些属性。</p>
<p>但是，我们现在有一个问题：任何东西都可以被传入<code>addID</code>，TypeScript将捕获类型，并报告没有问题：</p>
<pre><code class="language-ts">let person1 = addID({ name: 'John', age: 40 });
let person2 = addID('Sally'); // Pass in a string - no problem

console.log(person1.id); // 271
console.log(person1.name); // John

console.log(person2.id);
console.log(person2.name); // ERROR: Property 'name' does not exist on type '"Sally" &amp; { id: number; }'.
</code></pre>
<p>当我们传入一个字符串时，TypeScript没有发现问题。它只在我们试图访问<code>name</code>属性时报告了一个错误。因此，我们需要一个约束：我们需要告诉TypeScript只接受对象，通过使我们的通用类型<code>T</code>成为<code>object</code>的扩展。</p>
<pre><code class="language-ts">const addID = &lt;T extends object&gt;(obj: T) =&gt; {
  let id = Math.floor(Math.random() * 1000);

  return { ...obj, id };
};

let person1 = addID({ name: 'John', age: 40 });
let person2 = addID('Sally'); // 错误：“字符串”类型的参数不能分配给“对象”类型的参数。
</code></pre>
<p>这个错误被直接抓住了--完美......好吧，不完全是。在JavaScript中，数组是对象，所以我们仍然可以通过传入数组来解决这个问题：</p>
<pre><code class="language-ts">let person2 = addID(['Sally', 26]); // Pass in an array - no problem

console.log(person2.id); // 824
console.log(person2.name); // Error: Property 'name' does not exist on type '(string | number)[] &amp; { id: number; }'.
</code></pre>
<p>我们可以通过说对象参数应该有一个带有字符串值的名称属性来解决这个问题：</p>
<pre><code class="language-ts">const addID = &lt;T extends { name: string }&gt;(obj: T) =&gt; {
  let id = Math.floor(Math.random() * 1000);

  return { ...obj, id };
};

let person2 = addID(['Sally', 26]); // ERROR: argument should have a name property with string value
</code></pre>
<p>类型也可以传递给<code>&lt;T&gt;</code>，如下所示 - 但这在大多数情况下是不必要的，因为TypeScript会推断出它。</p>
<pre><code class="language-ts">// 在下文中，我们明确说明了括号中的参数类型。
let person1 = addID&lt;{ name: string; age: number }&gt;({ name: 'John', age: 40 });
</code></pre>
<p><strong>在参数和返回类型提前未知的情况下，泛型允许你在组件中实现类型安全。</strong></p>
<p><strong>在TypeScript中，当我们想描述两个值之间的对应关系时，可以使用泛型。</strong> 在上面的例子中，返回类型与输入类型相关。我们用一个_generic_来描述这个对应关系。</p>
<p>另一个例子。如果我们需要一个接受多种类型的函数，使用泛型比使用<code>any</code>类型更好。下面显示了使用`any'的问题：</p>
<pre><code class="language-ts">function logLength(a: any) {
  console.log(a.length); // No error
  return a;
}

let hello = 'Hello world';
logLength(hello); // 11

let howMany = 8;
logLength(howMany); // undefined（但没有 TypeScript 错误--我们当然希望 TypeScript 能告诉我们，我们试图访问一个数字的 length 属性！）
</code></pre>
<p>我们可以尝试使用泛型：</p>
<pre><code class="language-ts">function logLength&lt;T&gt;(a: T) {
  console.log(a.length); // 错误：TypeScript 无法确定 `a` 是一个具有 length 属性的值
  return a;
}
</code></pre>
<p>至少我们现在得到了一些反馈，我们可以用它来约束我们的代码。</p>
<p>解决方案：使用一个扩展了接口的泛型，确保传入的每个参数都有一个长度属性：</p>
<pre><code class="language-ts">interface hasLength {
  length: number;
}

function logLength&lt;T extends hasLength&gt;(a: T) {
  console.log(a.length);
  return a;
}

let hello = 'Hello world';
logLength(hello); // 11

let howMany = 8;
logLength(howMany); // 错误：数字没有 length 属性
</code></pre>
<p>我们也可以写一个函数，参数是一个元素数组，这些元素都有一个 length 属性：</p>
<pre><code class="language-ts">
interface hasLength {
  length: number;
}

function logLengths&lt;T extends hasLength&gt;(a: T[]) {
  a.forEach((element) =&gt; {
    console.log(element.length);
  });
}

let arr = [
  'This string has a length prop',
  ['This', 'arr', 'has', 'length'],
  { material: 'plastic', length: 30 },
];

logLengths(arr);
// 29
// 4
// 30
</code></pre>
<p>泛型是TypeScript的一个很好的功能！</p>
<h3 id="">带有接口的泛型</h3>
<p>当我们不知道一个对象中的某个值会是什么类型时，我们可以使用一个泛型来传递类型：</p>
<pre><code class="language-ts">// The type, T, will be passed in
interface Person&lt;T&gt; {
  name: string;
  age: number;
  documents: T;
}

// 我们必须输入 `documents` 的类型，这里是字符串数组
const person1: Person&lt;string[]&gt; = {
  name: 'John',
  age: 48,
  documents: ['passport', 'bank statement', 'visa'],
};

// 同样，我们实现了 `Person` 接口，并输入了 `documents` 类型--这里是字符串
const person2: Person&lt;string&gt; = {
  name: 'Delia',
  age: 46,
  documents: 'passport, P45',
};
</code></pre>
<h2 id="typescriptenums">TypeScript中的枚举（Enums）</h2>
<p>枚举（Enums）是TypeScript带给JavaScript的一个特殊功能。枚举允许我们定义或声明相关值的集合，可以是数字或字符串，作为一组命名的常量。</p>
<pre><code class="language-ts">enum ResourceType {
  BOOK,
  AUTHOR,
  FILM,
  DIRECTOR,
  PERSON,
}

console.log(ResourceType.BOOK); // 0
console.log(ResourceType.AUTHOR); // 1

// To start from 1
enum ResourceType {
  BOOK = 1,
  AUTHOR,
  FILM,
  DIRECTOR,
  PERSON,
}

console.log(ResourceType.BOOK); // 1
console.log(ResourceType.AUTHOR); // 2
</code></pre>
<p>默认情况下，枚举（enums）是基于数字的--它们将字符串值存储为数字。但是它们也可以是字符串：</p>
<pre><code class="language-ts">enum Direction {
  Up = 'Up',
  Right = 'Right',
  Down = 'Down',
  Left = 'Left',
}

console.log(Direction.Right); // Right
console.log(Direction.Down); // Down
</code></pre>
<p>当我们有一组相关的常量时，枚举（Enums）很有用。例如，与其在整个代码中使用非描述性的数字，枚举可以通过描述性的常量使代码更具可读性。</p>
<p>枚举（Enums）也可以防止bug，因为当你输入枚举的名称时，智能感知（intellisense）会弹出，并给出可以选择的可能选项列表。</p>
<h2 id="typescript">TypeScript严格模式</h2>
<p>建议在<code>tsconfig.json</code>文件中启用所有严格的类型检查操作。这将导致TypeScript报告更多的错误，但将有助于防止许多错误悄悄进入你的应用程序。</p>
<pre><code class="language-ts"> // tsconfig.json
 "strict": true
</code></pre>
<p>让我们讨论一下严格模式所做的几件事：没有隐含的any类型定义，以及严格的空值检查（null checks）。</p>
<h3 id="any">没有隐含的any</h3>
<p>在下面的函数中，TypeScript已经推断出参数<code>a</code>是<code>any</code>类型。正如你所看到的，当我们传入一个数字到这个函数，并试图通过打印<code>name</code>属性值时，没有报告错误。这不太好。</p>
<pre><code class="language-ts">function logName(a) {
  // No error??
  console.log(a.name);
}

logName(97);
</code></pre>
<p>在打开 "noImplicitAny "选项的情况下，如果我们没有明确说明<code>a</code>的类型，TypeScript将立即标记一个错误：</p>
<pre><code class="language-ts">// ERROR: Parameter 'a' implicitly has an 'any' type.
function logName(a) {
  console.log(a.name);
}
</code></pre>
<h3 id="nullchecks">严格的空值检查（null checks）</h3>
<p>当 "strictNullChecks "选项为<code>false</code>时，没有启用，TypeScript会忽略 <code>null</code>和 <code>undefined</code>。这可能在运行时出现意外的错误。</p>
<p>当<code>strictNullChecks</code>设置为 <code>true</code>时，变量被定义为<code>null</code>和<code>undefined</code>类型，如果你把它们赋给一个期望有具体数值的变量（例如，<code>string</code>），你会得到一个类型错误（type error）。</p>
<pre><code class="language-ts">let whoSangThis: string = getSong();

const singles = [
  { song: 'touch of grey', artist: 'grateful dead' },
  { song: 'paint it black', artist: 'rolling stones' },
];

const single = singles.find((s) =&gt; s.song === whoSangThis);

console.log(single.artist);
</code></pre>
<p>上面，<code>singles.find</code>不能保证它能找到这<code>song</code>，但我们写的代码就好像它总是能找到。</p>
<p>通过设置<code>strictNullChecks</code>为true，TypeScript将引发一个错误，因为我们在尝试使用<code>single</code>之前没有保证它的存在：</p>
<pre><code class="language-ts">const getSong = () =&gt; {
  return 'song';
};

let whoSangThis: string = getSong();

const singles = [
  { song: 'touch of grey', artist: 'grateful dead' },
  { song: 'paint it black', artist: 'rolling stones' },
];

const single = singles.find((s) =&gt; s.song === whoSangThis);

console.log(single.artist); // ERROR: Object is possibly 'undefined'.
</code></pre>
<p>TypeScript基本上是在告诉我们在使用<code>single</code>之前要确保它的存在。我们需要先检查它是否为 <code>null</code>或 <code>undefined</code>：</p>
<pre><code class="language-ts">if (single) {
  console.log(single.artist); // rolling stones
}
</code></pre>
<h2 id="typescripttypenarrowing">TypeScript中的type narrowing</h2>
<p>在TypeScript程序中，<strong>变量可以从一个不太精确的类型转移到一个更精确的类型。</strong> 这个过程称为类型缩小（type narrowing）。</p>
<p>下面是一个简单的例子，显示了当我们使用if语句和<code>typeof</code>时，TypeScript如何将不太具体的<code>string | number</code>的类型缩小到更具体的类型：</p>
<pre><code class="language-ts">function addAnother(val: string | number) {
  if (typeof val === 'string') {
    // 在这个代码块中，TypeScript 将 `val` 视为字符串，所以我们可以在 `val` 上使用字符串方法，TypeScript 不会对我们大喊大叫。
    return val.concat(' ' + val);
  }

  // 这里 TypeScript 知道 `val` 是一个数字
  return val + val;
}

console.log(addAnother('Woooo')); // Woooo Woooo
console.log(addAnother(20)); // 40
</code></pre>
<p>另一个例子：下面，我们定义了一个名为 <code>allVehicles</code>的联合类型，它可以是 <code>Plane</code>或 <code>Train</code>的类型。</p>
<pre><code class="language-ts">interface Vehicle {
  topSpeed: number;
}

interface Train extends Vehicle {
  carriages: number;
}

interface Plane extends Vehicle {
  wingSpan: number;
}

type PlaneOrTrain = Plane | Train;

function getSpeedRatio(v: PlaneOrTrain) {
  // 这里，我们想要返回 topSpeed/carriages 或 topSpeed/wingSpan
  console.log(v.carriages); // ERROR: 'carriages' doesn't exist on type 'Plane'
}
</code></pre>
<p>由于函数<code>getSpeedRatio</code>要处理多种类型，我们需要一种方法来区分<code>v</code>是<code>Plane</code>还是<code>Train</code>。我们可以通过给这两种类型一个共同的区分属性来做到这一点，该属性有一个字面的字符串值：</p>
<pre><code class="language-ts">// 现在，所有列车的类型属性都必须等于 “Train”
interface Train extends Vehicle {
  type: 'Train';
  carriages: number;
}

// 现在，所有列车的类型属性都必须等于 “Plane”
interface Plane extends Vehicle {
  type: 'Plane';
  wingSpan: number;
}

type PlaneOrTrain = Plane | Train;
</code></pre>
<p>现在，我们和TypeScript可以缩小<code>v</code>的类型了：</p>
<pre><code class="language-ts">function getSpeedRatio(v: PlaneOrTrain) {
  if (v.type === 'Train') {
    // TypeScript 现在知道 `v` 绝对是 `Train`。它已将类型从较不具体的 `Plane | Train` 类型缩小为更具体的 `Train` 类型
    return v.topSpeed / v.carriages;
  }

  // 如果不是 Train，TypeScript 就会缩小范围，认为 `v` 必须是 Plane - 聪明！
  return v.topSpeed / v.wingSpan;
}

let bigTrain: Train = {
  type: 'Train',
  topSpeed: 100,
  carriages: 20,
};

console.log(getSpeedRatio(bigTrain)); // 5
</code></pre>
<h2 id="typescriptreact">TypeScript与React</h2>
<p>TypeScript 完全支持 React 和 JSX。这意味着我们可以在三个最常见的React框架中使用TypeScript：</p>
<ul>
<li>create-react-app (<a href="https://create-react-app.dev/docs/adding-typescript/">TS setup</a>)</li>
<li>Gatsby (<a href="https://www.gatsbyjs.com/docs/how-to/custom-configuration/typescript/">TS setup</a>)</li>
<li>Next.js (<a href="https://nextjs.org/learn/excel/typescript">TS setup</a>)</li>
</ul>
<p>如果你需要更多定制的React-TypeScript配置，你可以设置<a href="https://webpack.js.org/">Webpack</a>（一个模块打包器--module bundler）并自己配置<code>tsconfig.json</code>。但大多数时候，一个框架会完成这项工作。</p>
<p>例如，要用TypeScript设置create-react-app，只需运行：</p>
<pre><code class="language-ts">npx create-react-app my-app --template typescript

# or

yarn create react-app my-app --template typescript
</code></pre>
<p>在src文件夹中，我们现在可以创建以<code>.ts</code>（普通TypeScript文件）或<code>.tsx</code>（TypeScript with React）为扩展名的文件，用TypeScript编写我们的组件。然后，这将编译成JavaScript文件存储在public文件夹中。</p>
<h3 id="typescriptreactprops">使用 TypeScript 的 React props</h3>
<p>下面，我们说<code>Person</code>应该是一个React功能组件（functional component），它接收一个props对象，其props <code>name</code> 应该是一个字符串，<code>age</code>应该是一个数字。</p>
<pre><code class="language-tsx">// src/components/Person.tsx
import React from 'react';

const Person: React.FC&lt;{
  name: string;
  age: number;
}&gt; = ({ name, age }) =&gt; {
  return (
    &lt;div&gt;
      &lt;div&gt;{name}&lt;/div&gt;
      &lt;div&gt;{age}&lt;/div&gt;
    &lt;/div&gt;
  );
};

export default Person;
</code></pre>
<p>但大多数开发者更愿意使用一个接口（interface ）来指定<code>prop type</code>：</p>
<pre><code class="language-tsx">interface Props {
  name: string;
  age: number;
}

const Person: React.FC&lt;Props&gt; = ({ name, age }) =&gt; {
  return (
    &lt;div&gt;
      &lt;div&gt;{name}&lt;/div&gt;
      &lt;div&gt;{age}&lt;/div&gt;
    &lt;/div&gt;
  );
};
</code></pre>
<p>然后我们可以把这个组件（component）导入到<code>App.tsx</code>中。如果我们没有提供必要的props，TypeScript会给出一个错误。</p>
<pre><code class="language-tsx">import React from 'react';
import Person from './components/Person';

const App: React.FC = () =&gt; {
  return (
    &lt;div&gt;
      &lt;Person name='John' age={48} /&gt;
    &lt;/div&gt;
  );
};

export default App;
</code></pre>
<p>下面是几个例子，说明我们可以有哪些prop types：</p>
<pre><code class="language-tsx">interface PersonInfo {
  name: string;
  age: number;
}

interface Props {
  text: string;
  id: number;
  isVeryNice?: boolean;
  func: (name: string) =&gt; string;
  personInfo: PersonInfo;
}
</code></pre>
<h3 id="typescriptreacthooks">使用TypeScript编写React hooks</h3>
<h4 id="usestate">useState()</h4>
<p>我们可以通过使用角括号声明一个状态变量应该是什么类型。下面，如果我们省略了角括号，TypeScript会推断出<code>cash</code>是一个数字（number）。所以，如果想让它也成为空值，我们必须指定：</p>
<pre><code class="language-tsx">const Person: React.FC&lt;Props&gt; = ({ name, age }) =&gt; {
  const [cash, setCash] = useState&lt;number | null&gt;(1);

  setCash(null);

  return (
    &lt;div&gt;
      &lt;div&gt;{name}&lt;/div&gt;
      &lt;div&gt;{age}&lt;/div&gt;
    &lt;/div&gt;
  );
};
</code></pre>
<h4 id="useref">useRef()</h4>
<p><code>useRef</code>返回一个可变的对象（mutable object），在组件的生命周期内存在。我们可以告诉TypeScript这个ref对象应该指向什么，下面我们说这个prop应该是一个<code>HTMLInputElement</code>：</p>
<pre><code class="language-tsx">const Person: React.FC = () =&gt; {
  // Initialise .current property to null
  const inputRef = useRef&lt;HTMLInputElement&gt;(null);

  return (
    &lt;div&gt;
      &lt;input type='text' ref={inputRef} /&gt;
    &lt;/div&gt;
  );
};
</code></pre>
<p>关于React与TypeScript的更多信息，请查看这些<a href="https://react-typescript-cheatsheet.netlify.app/">awesome React-TypeScript cheatsheets</a>。</p>
<h2 id="">有用的资源和进一步阅读</h2>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html">The official TypeScript docs</a></li>
<li><a href="https://www.youtube.com/watch?v=2pZmKW9-I_k&amp;list=PL4cUxeGkcC9gUgr39Q_yD6v-bSyMwKPUI&amp;ab_channel=TheNetNinja">The Net Ninja's TypeScript video series</a> (awesome!)</li>
<li><a href="https://www.youtube.com/watch?v=Z5iWr6Srsj8&amp;ab_channel=BenAwad">Ben Awad's TypeScript with React video</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html">Narrowing in TypeScript</a> (TS的一个非常有趣的功能，你应该学习一下)</li>
<li><a href="https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads">Function overloads</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Glossary/Primitive">Primitive values in JavaScript</a></li>
<li><a href="https://www.w3schools.com/js/js_object_definition.asp">JavaScript objects</a></li>
</ul>
<h2 id="">感谢你阅读本文</h2>
<p>希望这对你有用。如果你走到这里，你现在知道了TypeScript的主要基础知识，可以开始在你的项目中使用它。</p>
<p>同样，你也可以下载我的<a href="https://doabledanny.gumroad.com/l/typescript-cheat-sheet-pdf">one-page TypeScript cheat sheet PDF</a> 或者 <a href="https://doabledanny.gumroad.com/l/typescript-cheat-sheet-poster">订购实物海报</a>。</p>
<p>更多关于我的信息，你可以在<a href="https://mobile.twitter.com/doabledanny">推特</a>和 <a href="https://www.youtube.com/channel/UC0URylW_U4i26wN231yRqvA">YouTube</a>上关注我。</p>
<p>干杯！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 在 catch 语句中进行类型收敛 ]]>
                </title>
                <description>
                    <![CDATA[ 如果你有使用过像 Java、C++、或者 C# 这类编程语言的经验，你通常会透过抛出异常来做错误处理，然后使用一连串的 catch  语句来捕获它们。虽然已经证明有其他更好的方式来处理错误，但因为抛异常这种方式悠久的历史以及对编程习惯的广泛影响，我们依旧会在 JavaScript 当中继续使用它。 当然，这种方式的错误处理在 JavaScript 和 TypeScript 中是行之有效的，但如果你像下面这个例子一样照搬在其他编程语言的使用习惯，在  catch 语句里定义错误类型的话。 try {   // something with Axios, for example } catch(e: AxiosError) { //         ^^^^^^^^^^ Error 1196 💥 } 你将收获一个 TS1196 错误： Catch clause ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/typescript-catch/</link>
                <guid isPermaLink="false">61dda4ee6161280665ed7fe2</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 开发者小蓝 ]]>
                </dc:creator>
                <pubDate>Wed, 12 Jan 2022 05:30:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/01/danielle-macinnes-IuLgi9PWETU-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>如果你有使用过像 Java、C++、或者 C# 这类编程语言的经验，你通常会透过抛出异常来做错误处理，然后使用一连串的 <code>catch</code> 语句来捕获它们。虽然已经证明有其他更好的方式来处理错误，但因为抛异常这种方式悠久的历史以及对编程习惯的广泛影响，我们依旧会在 JavaScript 当中继续使用它。</p>
<p>当然，这种方式的错误处理在 JavaScript 和 TypeScript 中是行之有效的，但如果你像下面这个例子一样照搬在其他编程语言的使用习惯，在 <code>catch</code> 语句里定义错误类型的话。</p>
<pre><code class="language-typescript">try {
  // something with Axios, for example
} catch(e: AxiosError) {
//         ^^^^^^^^^^ Error 1196 💥
}
</code></pre>
<p>你将收获一个 <code>TS1196</code> 错误：<br>
<code> Catch clause variable type annotation must be ‘any’ or ‘unknown’ if specified.</code></p>
<blockquote>
<p><code>catch</code> 语句变量类型只能声明为 <code>any</code> 或者 <code>unknown</code>.</p>
</blockquote>
<h3 id="">之所以这样，有下面几条理由：</h3>
<h4 id="1">1 . 你可以抛出任何类型</h4>
<p>在 JavaScript 中， 你可以抛出任何的表达式。通常来说，我们会抛出 “异常” （ 在 JavaScript 中我们叫做 错误 Error ），但也不能排除我们会抛出其他任何类型的可能性：</p>
<pre><code class="language-typescript">throw "What a weird error"; // 👍
throw 404; // 👍
throw new Error("What a weird error"); // 👍
</code></pre>
<p>正因为任何合法的类型都能够被抛出，那 <code>catch</code> 语句能捕获到的类型也就不仅限于我们过去理解的错误或其子类型。</p>
<h4 id="2javascriptcatchcatch">2 . JavaScript 中只能使用一个 <code>catch</code> 语句。尽管在很早之前就有了关于<strong>多路捕获</strong>甚至是在 <code>catch</code> 语句中加入条件表达式的提案，但直到今天这些你还不能使用这些新特性。</h4>
<blockquote>
<p>了解更多 <a href="https://www.oreilly.com/library/view/javascript-the-definitive/9781449393854/ch11s06.html"> JavaScript - the definitive guide</a></p>
</blockquote>
<p>作为替代方案，我们可以通过在一个 <code>catch</code> 语句内部使用 <code>instanceof </code> 或者 <code>typeof </code> 类型检查来实现。</p>
<pre><code class="language-typescript">try {
  myroutine(); // There's a couple of errors thrown here
} catch (e) {
  if (e instanceof TypeError) {
    // A TypeError
  } else if (e instanceof RangeError) {
    // Handle the RangeError
  } else if (e instanceof EvalError) {
    // you guessed it: EvalError
  } else if (typeof e === "string") {
    // The error is a string
  } else if (axios.isAxiosError(e)) {
    // axios does an error check for us!
  } else {
    // everything else  
    logMyErrors(e);
  }
}
</code></pre>
<blockquote>
<p>注：上面代码已经是在 typescript 中进行 <strong>收敛异常捕获</strong> 的最佳实践。</p>
</blockquote>
<p>正是由于能够抛出任何可能的值，同时我们对一个 <code>try</code> 语句只能使用有且只有一个 <code>catch</code>，那么这个 <code>e</code> 的类型范围如此的宽泛，就一点都不奇怪了。</p>
<h4 id="3">3 . 无法预测的事情总在发生</h4>
<p>那么你可能又要问了，在上面的例子里面，既然你知道所有可能发生的异常，我们就不能定义一个恰到好处的<strong>联合类型</strong>来表达它吗？<br>
理论上是可以的，不过在实践中我们发现，根本无法穷举所有的异常类型。</p>
<p>除了一些用户自己定义的错误和异常，变量的类型不匹配，或者你的一个函数未定义，都会让系统抛出一个内存错误。更不用说，一个简单的方法超出了调用栈的最大数量，都会引起臭名昭著的 <strong>stack overflow</strong>。</p>
<p>异常的类型范围如此之宽广，而 <code>catch</code> 语句又只有一个，还要应对各种不确定的意外，就造成了这个 <code>e</code> 只能是 <code>any</code> 或 <code>unknown</code> 的局面。</p>
<h3 id="promise">Promise 的拒绝行为有什么不一样呢？</h3>
<p>正确答案是：一毛一样。<br>
typescript 只允许你去定义完成态（fullfilled）的值类型，对于拒绝行为（rejection），要么是你主动抛出了错误，或者系统发生了一场。</p>
<pre><code class="language-typescript">const somePromise = () =&gt; new Promise((fulfil, reject) =&gt; {
  if (someConditionIsValid()) {
    fulfil(42);
  } else {
    reject("Oh no!");
  }
});

somePromise()
  .then(val =&gt; console.log(val)) // val is number
  .catch(e =&gt; {
    console.log(e) // e can be anything, really.
  })
</code></pre>
<p>如果你使用 <code>asnyc/await</code> 模式的话，它的表现会更符合上面的表达：</p>
<pre><code class="language-typescript">try {
  const z = await somePromise(); // z is number
} catch(e) {
  // same thing, e can be anything!
}
</code></pre>
<h3 id="">写在最后</h3>
<p>很显然，对于从其他编程语言转移过来的开发者来讲，JavaScript 和 typescript 的错误处理并不十分友好。</p>
<p>我们能够做的，就是充分了解此间的差异，并坚信在不久的将来， typescript 团队给我们的类型检查能力，会让我们的错误处理足够的好。</p>
<!--kg-card-end: markdown--><p>原文链接：<a href="https://fettblog.eu/typescript-typing-catch-clauses/">https://fettblog.eu/typescript-typing-catch-clauses/</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ TypeScript 速成课 ]]>
                </title>
                <description>
                    <![CDATA[ Typescript 是 JavaScript 的类型超集，旨在简化大型 JavaScript 应用程序的开发。Typescript 加入了常见的概念例如 类（classes），泛型（generics），接口（interfaces）和静态类型（static types）并允许开发人员使用静态检查和代码重构等工具。 为什么在意 Typescript： 现在问题仍然是为什么你应该优选使用 Typescript。这有一些关于为什么 JavaScript 开发者应该考虑学习 Typescript 的原因。 静态类型： JavaScript 是动态类型的，这意味着直到在运行时实例化时，它不知道变量的类型，这可能导致项目中的问题和错误。Typescript 加入了对 JavaScript 静态类型支持如果你正确的使用它处理由变量类型的错误设定引起的错误。你仍然可以完全控制输入代码的严格程度，或者甚至根本不使用类型。 更好的 IDE 支持： Typescript 相比 JavaScript 一个更大的优势是更好的 IED 支持包括了来自 Typescript 编译器智能，实时的提示，调试 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/a-crash-course-in-typescript/</link>
                <guid isPermaLink="false">6014b68c6183a705401562e8</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ ZhichengChen ]]>
                </dc:creator>
                <pubDate>Fri, 29 Jan 2021 07:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/01/javier-quesada-qYfwGVNJqSA-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Typescript 是 JavaScript 的类型超集，旨在简化大型 JavaScript 应用程序的开发。Typescript 加入了常见的概念例如 类（classes），泛型（generics），接口（interfaces）和静态类型（static types）并允许开发人员使用静态检查和代码重构等工具。</p>
<h2 id="typescript">为什么在意 Typescript：</h2>
<p>现在问题仍然是为什么你应该优选使用 Typescript。这有一些关于为什么 JavaScript 开发者应该考虑学习 Typescript 的原因。</p>
<h3 id="">静态类型：</h3>
<p>JavaScript 是动态类型的，这意味着直到在运行时实例化时，它不知道变量的类型，这可能导致项目中的问题和错误。Typescript 加入了对 JavaScript 静态类型支持如果你正确的使用它处理由变量类型的错误设定引起的错误。你仍然可以完全控制输入代码的严格程度，或者甚至根本不使用类型。</p>
<h3 id="ide">更好的 IDE 支持：</h3>
<p>Typescript 相比 JavaScript 一个更大的优势是更好的 IED 支持包括了来自 Typescript 编译器智能，实时的提示，调试以及更多功能。这里还有一大堆扩展进一步  提升你的 Typescript 开发体验。</p>
<h3 id="ecmascript">应用新的 ECMAScript 特性：</h3>
<p>Typescript 使你可以使用最新的 ECMAScript 功能，并将它们转换到你选择的 ECMAScript 目标。这意味着你可以使用最新的工具开发应用程序，而无需担心浏览器支持。</p>
<h2 id="">什么时候你该使用它：</h2>
<p>到目前为止，我们应该知道为什么 Typescript 是有用的以及如何改善我们的开发体验。但它并不是解决所有问题的方法，当然也不能阻止你自己编写可怕的代码。那么让我们来看看你应该在哪里使用 Typescript。</p>
<h3 id="">当你拥有一个很大的代码库时：</h3>
<p>Typescript 是大型代码库的一个很好的补充，因为它可以帮助你防止许多常见错误。这尤其适用于多个开发人员工作在同一项目之中。</p>
<h3 id="">当你项目成员早已知道静态类型语言时：</h3>
<p>另一个明显使用 Typescript 的场景是当你和你的团队已经知道静态类型的语言像 Java 和 C# 不想改为编写 JavaScript。</p>
<h2 id="">设置/建立：</h2>
<p>要设置 typescript，我们只需要使用 npm 包管理器安装它并创建一个新的 Typescript 文件。</p>
<pre><code>npm install -g typescript
</code></pre>
<p>安装完成之后我们可以继续探寻 Typescript 提供给我们的语法和功能特性。</p>
<h2 id="">类型：</h2>
<p>现在让我们来看看 Typescript 所提供的类型：</p>
<h3 id="number">数值（Number）：</h3>
<p>Typescript 所有的值类型都是浮点数。所有的数字包括二进制和十六进制都是数值类型。</p>
<pre><code class="language-typescript">let num: number = 0.222;
let hex: number = 0xbeef;
let bin: number = 0b0010;
</code></pre>
<h3 id="string">字符串（String）：</h3>
<p>与其他语言一样，Typescript 使用 String 数据类型来保存文本数据。</p>
<pre><code class="language-typescript">let str: string = 'Hello World!';
</code></pre>
<p>你还可以用反引号``来应用多行字符串并嵌入表达式。</p>
<pre><code class="language-typescript">let multiStr: string = `A simplemultiline string!`

let expression = 'A new expression'let expressionStr: string = `Expression str: ${ expression }`
</code></pre>
<h3 id="boolean">布尔类型（Boolean）</h3>
<p>Typescript 支持所有的基本数据类型，布尔类型，值必须为 true 或者 false。</p>
<pre><code class="language-typescript">let boolFalse: boolean = false;
let boolTrue: boolean = true;
</code></pre>
<h2 id="">指定类型</h2>
<p>现在我们已经有了基本的数据类型，我们可以看看你如何在 Typescript 中指定类型。基本上，你只需要在名称和冒号后面写出变量的类型。</p>
<h3 id="">单一类型：</h3>
<p>这里例子为我们如何为变量指定字符串数据类型</p>
<pre><code class="language-typescript">let str: string = 'Hello World';
</code></pre>
<p>所有其他数据类型也是这样使用。</p>
<h3 id="">多类型：</h3>
<p>你仍然可以通过<code>|</code>操作符为你的变量指定多个数据类型：</p>
<pre><code class="language-typescript">let multitypeVar: string | number = 'String';
multitypeVar = 20;
</code></pre>
<p>这里我们使用|为变量分配两种类型。现在我们可以在其中存储字符串和数值。</p>
<h2 id="">类型检测：</h2>
<p>现在让我们看看我们如何检查我们的变量是否具有正确的类型。我们有多种选择，但在这里我只展示了两个最常用的选项。</p>
<h3 id="typeof">Typeof：</h3>
<p><code>typeof</code>仅仅知道基本类型。这意味着它只能检查变量是否是我们上面定义的数据类型之一。</p>
<pre><code class="language-typescript">let str: string = 'Hello World!';
</code></pre>
<pre><code class="language-typescript">if (typeof str === number) {
  console.log('Str is a number');
} else {
  console.log('Str is not a number');
}
</code></pre>
<p>在此示例中，我们创建一个字符串类型变量并使用 typeof 命令检查 str 是否为 Number 类型（始终为 false）。然后我们打印是否是数值。</p>
<h3 id="instanceof">Instanceof：</h3>
<p>instanceof 运算符与 typeof 几乎相同，只是它还可以检查 JavaScript 尚未定义的自定义类型。</p>
<pre><code class="language-typescript">class Human {
  name: string;

  constructor(data: string) {
    this.name = data;
  }
}

let human = new Human('Gabriel');

if (human instanceof Human) {
  console.log(`${human.name} is a human`);
}
</code></pre>
<p>在这里，我们创建一个自定义类型，我们稍后将在本文中讨论，然后创建它的实例。之后，我们检查它是否真的是 Human 类型的变量，如果是，则在控制台中打印。</p>
<h2 id="">类型断言：</h2>
<p>有时我们还需要将变量转换为特定的数据类型。这经常发生在你已经指定了一个泛型类型像 any 并且你想使用它具体的类型的方法。</p>
<p>有很多选择可以解决这个问题，但在这里我只分享其中两个。</p>
<h3 id="as">As 关键字</h3>
<p>通过在变量名之后使用 <code>as</code> 关键字跟随具体的数据类型来转换变量的类型。</p>
<pre><code class="language-typescript">let str: any = 'I am a String';
let strLength = (str as string).length;
</code></pre>
<p>这里我们将 str 变量转换为字符串，以便我们可以使用 length 属性（如果你的 TSLINT 设置允许，甚至可以在没有转换的情况下工作）。</p>
<h3 id="">&lt;&gt; 操作符：</h3>
<p>我们也可以使用<code>&lt;&gt;</code>运算符，它与 <code>as</code> 关键字具有完全相同的效果，只有语法差异。</p>
<pre><code class="language-typescript">let str: any = 'I am a String';
let strLength = (&lt;string&gt;str).length;
</code></pre>
<p>此代码块与上面的代码块具有完全相同的功能。它只是语法不同。</p>
<h2 id="">数组：</h2>
<p>Typescript 中的数组是相同对象的集合，可以用两种不同的方式创建。</p>
<h3 id="">创建数组</h3>
<h4 id="">使用 []：</h4>
<p>我们可以通过指定类型后跟<code>[]</code>来定义数组对象，以表示它是一个数组。</p>
<pre><code class="language-typescript">let strings: string[] = ['Hello', 'World', '!'];
</code></pre>
<p>在这个例子中，我们创建一个字符串数组，它包含三个不同的字符串值。</p>
<h4 id="">使用泛型数组:</h4>
<p>我们还可用指定 Array<type> 定义泛型数组</type></p>
<pre><code class="language-typescript">let numbers: Array&lt;number&gt; = [1, 2, 3, 4, 5];
</code></pre>
<p>这里我们创建一个数值数组，它包含 5 个不同的数字。</p>
<h3 id="">多（混合）类型数组</h3>
<p>此外，我们还可以使用<code>|</code>操作符将多个类型分配给单个数组。</p>
<pre><code class="language-typescript">let stringsAndNumbers: (string | number)[] = ['Age', 20];
</code></pre>
<p>此例中我们创建了一个数值可以包含字符串和数值。</p>
<h3 id="">多维数组：</h3>
<p>Typescript 还允许我们定义多维数组，这意味着我们可以将数组保存在另一个数组中。我们可以通过使用多个[]运算符来创建一个多维数组。</p>
<pre><code class="language-typescript">let numbersArray: number[][] = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]];
</code></pre>
<p>这里我们创建一个包含另一个数字数组的数组。</p>
<h2 id="tupels">元组（Tupels）</h2>
<p>元组基本类似数组但有一点不同。我们可以定义每个位子上储存数据的类型。这意味着我们可以通过方括号内的枚举来限制固定索引位置的类型。</p>
<pre><code class="language-typescript">let exampleTuple: [number, string] = [20, 'https://google.com'];
</code></pre>
<p>在此列中，我们定义了一个简单的元组，在索引 0 位置上指定为数值类型，在索引为 1 位置上指定为字符串类型。这意味着如果我们尝试在此索引上放置另一种数据类型，则会抛出错误。</p>
<p>以下是非法元组的示例：</p>
<pre><code class="language-typescript">const exampleTuple: [string, number] = [20, 'https://google.com'];
</code></pre>
<h2 id="enums">枚举（Enums）：</h2>
<p>与大多数其他面向对象编程语言一样，Typescript 中的枚举允许我们定义一组命名常量。 Typescript 还提供基于数值和基于字符串的枚举。使用 enum 关键字定义 Typescript 中的枚举。</p>
<h3 id="">数值枚举：</h3>
<p>首先，我们将查看数值枚举，其中我们将键值与索引匹配。</p>
<pre><code class="language-typescript">enum State {
  Playing = 0,
  Paused = 1,
  Stopped = 2,
}
</code></pre>
<p>上面，我们定义了数值枚举将 Playing 初始化为 0，Paused 为 1 等等。</p>
<pre><code class="language-typescript">enum State {
  Playing,
  Paused,
  Stopped,
}
</code></pre>
<p>我们也可以将初始化器留空，而 Typescript 会从零开始自动索引它。</p>
<h3 id="">字符串枚举</h3>
<p>定义字符串枚举也十分简单，我们只需要在定义的每个枚举值后初始化字符串值。</p>
<pre><code class="language-typescript">enum State {
  Playing = 'PLAYING',
  Paused = 'PAUSED',
  Stopped = 'STOPPED',
}
</code></pre>
<p>这里我们通过使用字符串初始化我们的状态来定义字符串枚举。</p>
<h2 id="objects">对象（Objects）：</h2>
<p>Typescript 中的对象是包含一组键值对的实例。这些值可以是变量，数组甚至函数。它也被视为表示非基本类型的数据类型。</p>
<p>我们可以使用大括号创建一个对象：</p>
<pre><code class="language-typescript">const human = { firstName: 'Frank', age: 32, height: 185 };
</code></pre>
<p>这里我们创建了一个 human 对象包含三个不同的键值对。</p>
<p>我们可以为对象加入方法：</p>
<pre><code class="language-typescript">const human = {
  firstName: 'Frank',
  age: 32,
  height: 185,
  greet: function() {
    console.log('Greetings stranger!');
  },
};
</code></pre>
<h2 id="">自定义类型</h2>
<p>Typescript 还允许我们自定义类型，以便于我们后续重用。要创建自定义类型，我们只需要使用<code>type</code>关键字并定义我们的类型。</p>
<pre><code class="language-typescript">type Human = {
  firstName: string;
  age: number;
  height: number;
};
</code></pre>
<p>在此示例中，我们定义了一个名为 Human 包含三个属性的自定义类型。现在让我们看看如何创建这种类型的对象。</p>
<pre><code class="language-typescript">const human: Human = {
  firstName: ‘Franz’,
  age: 32,
  height: 185
}
</code></pre>
<p>在这里，我们创建自定义类型的实例并设置所需的属性。</p>
<h2 id="">方法参数和返回类型：</h2>
<p>Typescript 允许我们为方法参数和返回值指定数据类型。现在让我们看一下使用 Typescript 定义函数的语法。</p>
<pre><code class="language-typescript">function printState(state: State): void {
  console.log(`The song state is ${state}`);
}

function add(num1: number, num2: number): number {
  return num1 + num2;
}
</code></pre>
<p>这里我们有两个示例函数，它们都具有定义类型的参数。我们还看到在结束括号后定义返回类型。</p>
<p>现在我们可以像普通的 JavaScript 一样调用我们的函数，但编译器会检查我们是否为函数提供了正确的参数。</p>
<pre><code>add(2, '5')
// 错误第二个参数类型为数值
</code></pre>
<h2 id="">可选属性：</h2>
<p>Typescript 允许我们为方法(注：接口等同样可以定义可选属性)定义可选属性。我们通过 <code>?</code> 操作符定义。</p>
<pre><code class="language-typescript">function printName(firstName: string, lastName?: string) {
  if (lastName) console.log(`Firstname: ${firstName}, Lastname: ${lastName}`);
  else console.log(`Firstname: ${firstName}`);
}
</code></pre>
<p>在这个例子中，lastName 是一个可选参数，这意味着当我们不提供调用函数时，我们不会从编译器中获得错误。</p>
<pre><code class="language-typescript">printName('Gabriel', 'Tanner');
printName('Gabriel');
</code></pre>
<p>这表示 2 个示例都被视为正确的。</p>
<h2 id="">默认值：</h2>
<p>我们使用可选属性的第二种方法是为它指定一个默认值。我们可以通过直接在函数头部赋值来实现。</p>
<pre><code class="language-typescript">function printName(firstName: string, lastName: string = 'Tanner') {
  console.log(`Firstname: ${firstName}, Lastname: ${lastName}`);
}
</code></pre>
<p>在此例我中我们 lastName 赋予了默认值这意味着我们不必每次调用方法时提供它。</p>
<h2 id="interfaces">接口（Interfaces）：</h2>
<p>Typescript 中的接口用于定义与我们的代码以及项目之外的代码的契约。接口只包含我们的方法和属性的声明，但不实现它们。实现方法和属性是实现接口的类的责任。</p>
<p>让我们看个例子让定义更加清晰：</p>
<pre><code class="language-typescript">interface Person {
  name: string;
}

const person: Person = {
  name: 'Gabriel',
};

// 不能指定为Person接口
const person2: Person = {
  names: 'Gabriel',
};
</code></pre>
<p>这里我们声明了一个接口包含一个 name 属性，它需要在实现接口时实现。这就是为什么 person2 变量会抛出异常</p>
<h2 id="">可选属性</h2>
<p>在 Typescript 中，有时并不是所有接口属性都是必需的。可以使用 <code>?</code> 运算符在属性后面将其设置为可选。</p>
<pre><code class="language-typescript">interface Person {
  name: string;
  age?: number;
}

const person: Person = {
  name: 'Frank',
  age: 28,
};

const person2: Person = {
  name: 'Gabriel',
};
</code></pre>
<p>在这里，我们创建一个具有一个普通和一个可选属性的接口，该属性是使用 <code>?</code> 运算符。这就是我们两个人初始化都有效的原因。</p>
<h2 id="">只读属性</h2>
<p>我们的接口中一些属性也应该只在首次创建对象时<s>修改</s>赋值。我们可以通过将 readonly 关键字放在我们的属性名称之前来指定此功能。</p>
<pre><code class="language-typescript">interface Person {
  name: string;
  readonly id: number;
  age?: number;
}

const person: Person = {
  name: 'Gabriel',
  id: 3127831827,
};

person.id = 200; // 不可为id赋值因为它是只读的
</code></pre>
<p>在此示例中，id 属性是只读的，在创建对象后无法更改。</p>
<h2 id="barrelsmodules">模块（<s>Barrels</s> Modules）：</h2>
<p>Barrels 允许我们在一个更方便的模块中汇总多个导出模块。</p>
<p>我们仅需要创建一个新文件，它将导出我们项目中的多个模块<br>
(译者注：根据 ECMAScript 定义一个文件定义一个模块，此处可能表示<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#Aggregating_modules">模块聚合</a>(类似库等的入口文件))。</p>
<pre><code class="language-typescript">export * from './person';
export * from './animal';
export * from './human';
</code></pre>
<p>之后我们可以通过这个便利的单一导入语句引入这些模块。</p>
<pre><code class="language-typescript">import { Person, Animal, Human } from 'index';
</code></pre>
<h2 id="generics">泛型（Generics）：</h2>
<p>泛型允许我们创建兼容广泛类型而不是单一类型的组件。这使得我们的组件“ 开放”和复用。</p>
<p>现在你可能想知道为什么我们不只是使用任何（<code>any</code>）类型来使组件接受多种类型而不是单一类型。让我们看一个例子更好地了解。</p>
<p>我们想要一个简单的假函数（dummy function），它返回传入的参数：</p>
<pre><code class="language-typescript">function dummyFun(arg: any): any {
  return arg;
}
</code></pre>
<p>然而 any 是通用的，某种程度它能接受所有类型参数但有一个很大的区别。我们丢失了我们传入的参数是什么类型以及返回值是什么类型。</p>
<p>所以让我们来看看，我们如何接受所有类型并知道它返回值的类型。</p>
<pre><code class="language-typescript">function dummyFun&lt;T&gt;(arg: T): T {
  return arg;
}
</code></pre>
<p>这里我们使用泛型参数 T，因此我们可以捕获变量类型并在以后使用它。我们还使用它作为返回参数类型，它允许我们在检查代码时看到相应的类型。</p>
<p>更多详细介绍你可以查看<a https:="" chinese.freecodecamp.org="" news="" a-crash-course-in-typescript="" href="">Charly Poly</a>关于<a href="https://medium.com/@wittydeveloper/typescript-generics-and-overloads-999679d121cf">Generics and overloads</a>的文章</p>
<h2 id="accessmodifiers">访问修饰符（Access Modifiers）：</h2>
<p>访问修饰符控制我们类成员的可访问性。 Typescript 支持三种访问修饰符 - 公共的（public），私有的（private）和受保护的（protected）。</p>
<h3 id="">公共的：</h3>
<p>公共成员可以在任何地方访问，没有任何限制 这也是标准修饰符，这意味着你不需要使用 public 关键字为变量添加前缀。</p>
<h3 id="">私有的：</h3>
<p>私有成员只能在其定义的类中能访问。</p>
<h3 id="">受保护的：</h3>
<p>保护成员只能在其定义的类及其子类中访问。</p>
<h2 id="tslint">TSLINT：</h2>
<p>TSLINT 是 Typescript 的标准 linter，可以帮助我们编写干净，可维护和可读的代码。它可以使用我们自己的 lint 规则，配置和格式化程序进行自定义。</p>
<h3 id="">设置：</h3>
<p>首先我们需要安装 Typescript 和 tslint，我们可以全局安装和局部安装：</p>
<pre><code class="language-bash">npm install tslint typescript --save-dev
npm install tslint typescript -g
</code></pre>
<p>之后，我们可以使用 TSLINT CLI 在我们的项目中初始化 TSLINT。</p>
<pre><code class="language-bash">tslint --init
</code></pre>
<p>现在我们有了 tslint.json 文件，我们可以开始配置我们的规则了。</p>
<h3 id="">配置：</h3>
<p>TSLINT 允许使用配置我们自己的规则并自定义代码的外观。默认情况下，tslint.json 文件看起来像这样，只使用默认规则。</p>
<pre><code class="language-json">{
  "defaultSeverity": "error",
  "extends": ["tslint:recommended"],
  "jsRules": {},
  "rules": {},
  "rulesDirectory": []
}
</code></pre>
<p>我们可以通过将它们放在 rules 对象中来添加其他规则。</p>
<pre><code class="language-json">"rules": {
 "no-unnecessary-type-assertion": true,
 "array-type": [true, "array"],
 "no-double-space": true,
 "no-var-keyword": true,
 "semicolon": [true, "always", "ignore-bound-class-methods"]
},
</code></pre>
<p>有关所有可用规则的概述，你可以查看<a href="https://palantir.github.io/tslint/rules/">官方文档</a>。</p>
<h2 id="">结语</h2>
<p>希望这篇文章你帮助你理解 Typescript 的基础以及如何在项目中使用。</p>
<p>如果你觉得这篇文章有用，请考虑推荐并与其他开发人员共享。</p>
<p>如果你有任何问题和反馈，可以在下面评论区告诉我。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ TypeScript 高级类型清单（附 demo） ]]>
                </title>
                <description>
                    <![CDATA[ TypeScript 是一种类型化的语言，允许你指定变量的类型，函数参数，返回的值和对象属性。 你可以把本文看做一个带有示例的 TypeScript 高级类型备忘单 让我们开始吧！  * Intersection Types(交叉类型)  * Union Types(联合类型)  * Generic Types(泛型) * 泛型函数     * 泛型接口     * 多参数的泛型类型          * Utility Types * Partial     ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/advanced-typescript-types-cheatsheet/</link>
                <guid isPermaLink="false">5fd089d339641a0517d51b70</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 古月 ]]>
                </dc:creator>
                <pubDate>Mon, 07 Dec 2020 08:36:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/12/jessica-ruscello-OQSCtabGkSY-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>TypeScript 是一种类型化的语言，允许你指定变量的类型，函数参数，返回的值和对象属性。</p>
<p>你可以把本文看做一个带有示例的 TypeScript 高级类型备忘单</p>
<p>让我们开始吧！</p>
<ul>
<li><a href="#intersection-types%E4%BA%A4%E5%8F%89%E7%B1%BB%E5%9E%8B">Intersection Types(交叉类型)</a></li>
<li><a href="#union-types%E8%81%94%E5%90%88%E7%B1%BB%E5%9E%8B">Union Types(联合类型)</a></li>
<li><a href="#generic-types%E6%B3%9B%E5%9E%8B">Generic Types(泛型)</a>
<ul>
<li><a href="#%E6%B3%9B%E5%9E%8B%E5%87%BD%E6%95%B0">泛型函数</a></li>
<li><a href="#%E6%B3%9B%E5%9E%8B%E6%8E%A5%E5%8F%A3">泛型接口</a></li>
<li><a href="#%E5%A4%9A%E5%8F%82%E6%95%B0%E7%9A%84%E6%B3%9B%E5%9E%8B%E7%B1%BB%E5%9E%8B">多参数的泛型类型</a></li>
</ul>
</li>
<li><a href="#utility-types">Utility Types</a>
<ul>
<li><a href="#partial">Partial</a></li>
<li><a href="#required">Required</a></li>
<li><a href="#readonly">Readonly</a></li>
<li><a href="#pick">Pick</a></li>
<li><a href="#omit">Omit</a></li>
<li><a href="#extract">Extract</a></li>
<li><a href="#exclude">Exclude</a></li>
<li><a href="#record">Record</a></li>
<li><a href="#nonnullable">NonNullable</a></li>
</ul>
</li>
<li><a href="#mapped-types-%E6%98%A0%E5%B0%84%E7%B1%BB%E5%9E%8B">Mapped Types( 映射类型)</a></li>
<li><a href="#type-guards%E7%B1%BB%E5%9E%8B%E4%BF%9D%E6%8A%A4">Type Guards(类型保护)</a>
<ul>
<li><a href="#typeof">typeof</a></li>
<li><a href="#instanceof">instanceof</a></li>
<li><a href="#in">in</a></li>
</ul>
</li>
<li><a href="#conditional-types%E6%9D%A1%E4%BB%B6%E7%B1%BB%E5%9E%8B">Conditional Types(条件类型)</a></li>
</ul>
<h2 id="intersectiontypes">Intersection Types(交叉类型)</h2>
<p>交叉类型是一种将多种类型组合为一种类型的方法。 这意味着你可以将给定的类型 A 与类型 B 或更多类型合并，并获得具有所有属性的单个类型。</p>
<pre><code class="language-typescript">type LeftType = {
    id: number;
    left: string;
};

type RightType = {
    id: number;
    right: string;
};

type IntersectionType = LeftType &amp; RightType;

function showType(args: IntersectionType) {
    console.log(args);
}

showType({ id: 1, left: 'test', right: 'test' });
// Output: {id: 1, left: "test", right: "test"}
</code></pre>
<p>如你所见，<code>IntersectionType</code>组合了两种类型-<code>LeftType</code>和<code>RightType</code>，并使用<code>＆</code>符号形成了交叉类型。</p>
<h2 id="uniontypes">Union Types(联合类型)</h2>
<p>联合类型使你可以赋予同一个变量不同的类型</p>
<pre><code class="language-typescript">type UnionType = string | number;

function showType(arg: UnionType) {
    console.log(arg);
}

showType('test');
// Output: test

showType(7);
// Output: 7
</code></pre>
<p>函数<code>showType</code>是一个联合类型函数，它接受字符串或者数字作为参数。</p>
<h2 id="generictypes">Generic Types(泛型)</h2>
<p>泛型类型是复用给定类型的一部分的一种方式。 它有助于捕获作为参数传递的类型 T。</p>
<blockquote>
<p>优点: 创建可重用的函数，一个函数可以支持多种类型的数据。 这样开发者就可以根据自己的数据类型来使用函数</p>
</blockquote>
<h3 id="">泛型函数</h3>
<pre><code class="language-typescript">function showType&lt;T&gt;(args: T) {
    console.log(args);
}

showType('test');
// Output: "test"

showType(1);
// Output: 1
</code></pre>
<p>如何创建泛型类型:需要使用<code>&lt;&gt;</code>并将 <code>T</code>(名称可自定义)作为参数传递。<br>
上面的 🌰 栗子中，<br>
我们给 <code>showType</code> 添加了类型变量 <code>T</code>。<code>T</code>帮助我们捕获用户传入的参数的类型(比如：number/string)之后我们就可以使用这个类型</p>
<p>我们把 <code>showType</code> 函数叫做泛型函数，因为它可以适用于多个类型</p>
<h3 id="">泛型接口</h3>
<pre><code class="language-typescript">interface GenericType&lt;T&gt; {
    id: number;
    name: T;
}

function showType(args: GenericType&lt;string&gt;) {
    console.log(args);
}

showType({ id: 1, name: 'test' });
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType&lt;number&gt;) {
    console.log(args);
}

showTypeTwo({ id: 1, name: 4 });
// Output: {id: 1, name: 4}
</code></pre>
<p>在上面的栗子中，声明了一个 <code>GenericType</code> 接口，该接口接收泛型类型 <code>T</code>, 并通过类型 <code>T</code>来约束接口内 <code>name</code> 的类型</p>
<blockquote>
<p>注:泛型变量约束了整个接口后，在实现的时候，必须指定一个类型</p>
</blockquote>
<p>因此在使用时我们可以将<code>name</code>设置为任意类型的值，示例中为字符串或数字</p>
<h3 id="">多参数的泛型类型</h3>
<pre><code class="language-typescript">interface GenericType&lt;T, U&gt; {
    id: T;
    name: U;
}

function showType(args: GenericType&lt;number, string&gt;) {
    console.log(args);
}

showType({ id: 1, name: 'test' });
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType&lt;string, string[]&gt;) {
    console.log(args);
}

showTypeTwo({ id: '001', name: ['This', 'is', 'a', 'Test'] });
// Output: {id: "001", name: Array["This", "is", "a", "Test"]}
</code></pre>
<p>泛型类型可以接收多个参数。 在上面的代码中，我们传入两个参数：<code>T</code>和<code>U</code>，然后将它们用作<code>id</code>,<code>name</code>的类型。 也就是说，我们现在可以使用该接口并提供不同的类型作为参数。</p>
<h2 id="utilitytypes">Utility Types</h2>
<p>TypeScript 内部也提供了很多方便实用的工具，可帮助我们更轻松地操作类型。 如果要使用它们，你需要将类型传递给<code>&lt;&gt;</code></p>
<h3 id="partial">Partial</h3>
<ul>
<li><code>Partial&lt;T&gt;</code></li>
</ul>
<p>Partial 允许你将<code>T</code>类型的所有属性设为可选。 它将在每一个字段后面添加一个<code>?</code>。</p>
<pre><code class="language-typescript">interface PartialType {
    id: number;
    firstName: string;
    lastName: string;
}

/*
等效于
interface PartialType {
  id?: number
  firstName?: string
  lastName?: string
}
*/

function showType(args: Partial&lt;PartialType&gt;) {
    console.log(args);
}

showType({ id: 1 });
// Output: {id: 1}

showType({ firstName: 'John', lastName: 'Doe' });
// Output: {firstName: "John", lastName: "Doe"}
</code></pre>
<p>上面代码中声明了一个<code>PartialType</code>接口，它用作函数<code>showType()</code>的参数的类型。 为了使所有字段都变为可选，我们使用<code>Partial</code>关键字并将<code>PartialType</code>类型作为参数传递。</p>
<h3 id="required">Required</h3>
<ul>
<li><code>Required&lt;T&gt;</code></li>
</ul>
<blockquote>
<p>将某个类型里的属性全部变为必选项</p>
</blockquote>
<pre><code class="language-typescript">interface RequiredType {
    id: number;
    firstName?: string;
    lastName?: string;
}

function showType(args: Required&lt;RequiredType&gt;) {
    console.log(args);
}

showType({ id: 1, firstName: 'John', lastName: 'Doe' });
// Output: { id: 1, firstName: "John", lastName: "Doe" }

showType({ id: 1 });
// Error: Type '{ id: number: }' is missing the following properties from type 'Required&lt;RequiredType&gt;': firstName, lastName
</code></pre>
<p>上面的代码中，即使我们在使用接口之前先将某些属性设为可选，但<code>Required</code>被加入后也会使所有属性成为必选。 如果省略某些必选参数，TypeScript 将报错。</p>
<h3 id="readonly">Readonly</h3>
<ul>
<li><code>Readonly&lt;T&gt;</code></li>
</ul>
<p>会转换<t>类型的所有属性，以使它们无法被修改</t></p>
<pre><code class="language-typescript">interface ReadonlyType {
    id: number;
    name: string;
}

function showType(args: Readonly&lt;ReadonlyType&gt;) {
    args.id = 4;
    console.log(args);
}

showType({ id: 1, name: 'Doe' });
// Error: Cannot assign to 'id' because it is a read-only property.
</code></pre>
<p>我们使用<code>Readonly</code>来使<code>ReadonlyType</code>的属性不可被修改。 也就是说，如果你尝试为这些字段之一赋予新值，则会引发错误。</p>
<p>除此之外，你还可以在指定的属性前面使用关键字<code>readonly</code>使其无法被重新赋值</p>
<pre><code class="language-typescript">interface ReadonlyType {
    readonly id: number;
    name: string;
}
</code></pre>
<h3 id="pick">Pick</h3>
<ul>
<li><code>Pick&lt;T, K&gt;</code></li>
</ul>
<p>此方法允许你从一个已存在的类型 <code>T</code>中选择一些属性作为<code>K</code>, 从而创建一个新类型</p>
<p>即 抽取一个类型/接口中的一些子集作为一个新的类型</p>
<p><code>T</code>代表要抽取的对象<br>
<code>K</code>有一个约束: 一定是来自<code>T</code>所有属性字面量的联合类型<br>
新的类型/属性一定要从<code>K</code>中选取，</p>
<pre><code class="language-typescript">/**
    源码实现
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick&lt;T, K extends keyof T&gt; = {
    [P in K]: T[P];
};
</code></pre>
<pre><code class="language-typescript">interface PickType {
    id: number;
    firstName: string;
    lastName: string;
}

function showType(args: Pick&lt;PickType, 'firstName' | 'lastName'&gt;) {
    console.log(args);
}

showType({ firstName: 'John', lastName: 'Doe' });
// Output: {firstName: "John"}

showType({ id: 3 });
// Error: Object literal may only specify known properties, and 'id' does not exist in type 'Pick&lt;PickType, "firstName" | "lastName"&gt;'
</code></pre>
<p><code>Pick</code> 与我们前面讨论的工具有一些不同，它需要两个参数</p>
<ul>
<li><code>T</code>是要从中选择元素的类型</li>
<li><code>K</code>是要选择的属性(可以使使用联合类型来选择多个字段)</li>
</ul>
<h3 id="omit">Omit</h3>
<ul>
<li><code>Omit&lt;T, K&gt;</code></li>
</ul>
<p><code>Omit</code>的作用与<code>Pick</code>类型正好相反。 不是选择元素，而是从类型<code>T</code>中删除<code>K</code>个属性。</p>
<pre><code class="language-typescript">interface PickType {
    id: number;
    firstName: string;
    lastName: string;
}

function showType(args: Omit&lt;PickType, 'firstName' | 'lastName'&gt;) {
    console.log(args);
}

showType({ id: 7 });
// Output: {id: 7}

showType({ firstName: 'John' });
// Error: Object literal may only specify known properties, and 'firstName' does not exist in type 'Pick&lt;PickType, "id"&gt;'
</code></pre>
<h3 id="extract">Extract</h3>
<ul>
<li><code>Extract&lt;T, U&gt;</code></li>
</ul>
<blockquote>
<p>提取<code>T</code>中可以赋值给<code>U</code>的类型--取交集</p>
</blockquote>
<p><code>Extract</code>允许你通过选择两种不同类型中的共有属性来构造新的类型。 也就是从<code>T</code>中提取所有可分配给<code>U</code>的属性。</p>
<pre><code class="language-typescript">interface FirstType {
    id: number;
    firstName: string;
    lastName: string;
}

interface SecondType {
    id: number;
    address: string;
    city: string;
}

type ExtractType = Extract&lt;keyof FirstType, keyof SecondType&gt;;
// Output: "id"
</code></pre>
<p>在上面的代码中，<code>FirstType</code>接口和<code>SecondType</code>接口，都存在 <code>id:number</code>属性。 因此，通过使用<code>Extract</code>，即提取出了新的类型 <code>{id:number}</code>。</p>
<h3 id="exclude">Exclude</h3>
<blockquote>
<p><code>Exclude&lt;T, U&gt;</code> --从 <code>T</code> 中剔除可以赋值给 <code>U</code> 的类型。</p>
</blockquote>
<p>与<code>Extract</code>不同，<code>Exclude</code>通过排除两个不同类型中已经存在的共有属性来构造新的类型。 它会从<code>T</code>中排除所有可分配给<code>U</code>的字段。</p>
<pre><code class="language-typescript">interface FirstType {
    id: number;
    firstName: string;
    lastName: string;
}

interface SecondType {
    id: number;
    address: string;
    city: string;
}

type ExcludeType = Exclude&lt;keyof FirstType, keyof SecondType&gt;;

// Output; "firstName" | "lastName"
</code></pre>
<p>上面的代码可以看到，属性<code>firstName</code>和<code>lastName</code> 在<code>SecondType</code>类型中不存在。 通过使用<code>Extract</code>关键字，我们可以获得<code>T</code>中存在而<code>U</code>中不存在的字段。</p>
<h3 id="record">Record</h3>
<ul>
<li><code>Record&lt;K,T&gt;</code></li>
</ul>
<p>此工具可帮助你构造具有给定类型<code>T</code>的一组属性<code>K</code>的类型。将一个类型的属性映射到另一个类型的属性时，<code>Record</code>非常方便。</p>
<pre><code class="language-typescript">interface EmployeeType {
    id: number;
    fullname: string;
    role: string;
}

let employees: Record&lt;number, EmployeeType&gt; = {
    0: { id: 1, fullname: 'John Doe', role: 'Designer' },
    1: { id: 2, fullname: 'Ibrahima Fall', role: 'Developer' },
    2: { id: 3, fullname: 'Sara Duckson', role: 'Developer' },
};

// 0: { id: 1, fullname: "John Doe", role: "Designer" },
// 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
// 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }
</code></pre>
<p><code>Record</code>的工作方式相对简单。 在代码中，它期望一个<code>number</code>作为类型，这就是为什么我们将 0、1 和 2 作为<code>employees</code>变量的键的原因。 如果你尝试使用字符串作为属性，则会引发错误,因为属性是由<code>EmployeeType</code>给出的具有 ID，fullName 和 role 字段的对象。</p>
<h3 id="nonnullable">NonNullable</h3>
<ul>
<li><code>NonNullable&lt;T&gt;</code></li>
</ul>
<blockquote>
<p>-- 从 <code>T</code> 中剔除 <code>null</code> 和 <code>undefined</code></p>
</blockquote>
<pre><code class="language-typescript">type NonNullableType = string | number | null | undefined;

function showType(args: NonNullable&lt;NonNullableType&gt;) {
    console.log(args);
}

showType('test');
// Output: "test"

showType(1);
// Output: 1

showType(null);
// Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.

showType(undefined);
// Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.
</code></pre>
<p>我们将类型<code>NonNullableType</code>作为参数传递给<code>NonNullable</code>，<code>NonNullable</code>通过排除<code>null</code>和<code>undefined</code>来构造新类型。 也就是说，如果你传递可为空的值，TypeScript 将引发错误。</p>
<p>顺便说一句，如果将<code>--strictNullChecks</code>标志添加到<code>tsconfig文件</code>，TypeScript 将应用非空性规则。</p>
<h2 id="mappedtypes">Mapped Types( 映射类型)</h2>
<p>映射类型允许你从一个旧的类型，生成一个新的类型。</p>
<p>请注意，前面介绍的某些高级类型也是映射类型。<br>
如:</p>
<pre><code class="language-ts">/*
Readonly， Partial和 Pick是同态的，但 Record不是。 因为 Record并不需要输入类型来拷贝属性，所以它不属于同态：
*/
type Readonly&lt;T&gt; = {
    readonly [P in keyof T]: T[P];
};
type Partial&lt;T&gt; = {
    [P in keyof T]?: T[P];
};
type Pick&lt;T, K extends keyof T&gt; = {
    [P in K]: T[P];
};

Record;
</code></pre>
<pre><code class="language-typescript">type StringMap&lt;T&gt; = {
    [P in keyof T]: string;
};

function showType(arg: StringMap&lt;{ id: number; name: string }&gt;) {
    console.log(arg);
}

showType({ id: 1, name: 'Test' });
// Error: Type 'number' is not assignable to type 'string'.

showType({ id: 'testId', name: 'This is a Test' });
// Output: {id: "testId", name: "This is a Test"}
</code></pre>
<p><code>StringMap&lt;&gt;</code>会将传入的任何类型转换为字符串。 就是说，如果我们在函数<code>showType()</code>中使用它，则接收到的参数必须是字符串-否则，TypeScript 将引发错误。</p>
<h2 id="typeguards">Type Guards(类型保护)</h2>
<p>类型保护使你可以使用运算符检查变量或对象的类型。 这是一个条件块，它使用<code>typeof</code>，<code>instanceof</code>或<code>in</code>返回类型。</p>
<blockquote>
<p>typescript 能够在特定区块中保证变量属于某种确定类型。可以在此区块中放心地引用此类型的属性，或者调用此类型的方法</p>
</blockquote>
<h3 id="typeof">typeof</h3>
<pre><code class="language-typescript">function showType(x: number | string) {
    if (typeof x === 'number') {
        return `The result is ${x + x}`;
    }
    throw new Error(`This operation can't be done on a ${typeof x}`);
}

showType("I'm not a number");
// Error: This operation can't be done on a string

showType(7);
// Output: The result is 14
</code></pre>
<p>什么代码中，有一个普通的 JavaScript 条件块，通过<code>typeof</code>检查接收到的参数的类型。</p>
<h3 id="instanceof">instanceof</h3>
<pre><code class="language-typescript">class Foo {
    bar() {
        return 'Hello World';
    }
}

class Bar {
    baz = '123';
}

function showType(arg: Foo | Bar) {
    if (arg instanceof Foo) {
        console.log(arg.bar());
        return arg.bar();
    }

    throw new Error('The type is not supported');
}

showType(new Foo());
// Output: Hello World

showType(new Bar());
// Error: The type is not supported
</code></pre>
<p>像前面的示例一样，这也是一个类型保护，它检查接收到的参数是否是<code>Foo</code>类的一部分，并对其进行处理。</p>
<h3 id="in">in</h3>
<pre><code class="language-typescript">interface FirstType {
    x: number;
}
interface SecondType {
    y: string;
}

function showType(arg: FirstType | SecondType) {
    if ('x' in arg) {
        console.log(`The property ${arg.x} exists`);
        return `The property ${arg.x} exists`;
    }
    throw new Error('This type is not expected');
}

showType({ x: 7 });
// Output: The property 7 exists

showType({ y: 'ccc' });
// Error: This type is not expected
</code></pre>
<p>什么的栗子中，使用<code>in</code>检查参数对象上是否存在属性<code>x</code>。</p>
<h2 id="conditionaltypes">Conditional Types(条件类型)</h2>
<p>条件类型测试两种类型，然后根据该测试的结果选择其中一种。</p>
<blockquote>
<p>一种由条件表达式所决定的类型， 表现形式为 <code>T extends U ? X : Y</code> , 即如果类型<code>T</code>可以被赋值给类型<code>U</code>，那么结果类型就是<code>X</code>类型，否则为<code>Y</code>类型。</p>
<p>条件类型使类型具有了不唯一性，增加了语言的灵活性，</p>
</blockquote>
<pre><code class="language-typescript">// 源码实现
type NonNullable&lt;T&gt; = T extends null | undefined ? never : T;

// NotNull&lt;T&gt; 等价于 NoneNullable&lt;T,U&gt;

// 用法示例
type resType = NonNullable&lt;string | number | null | undefined&gt;; // string|number
</code></pre>
<p>上面的代码中， <code>NonNullable</code>检查类型是否为 <code>null</code>，并根据该类型进行处理。 正如你所看到的，它使用了 JavaScript 三元运算符。</p>
<p>感谢阅读。</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/react-typescript-how-to-set-up-types-on-hooks/">The React TypeScript Cheatsheet – How To Set Up Types on Hooks</a>，作者：<a href="https://www.freecodecamp.org/news/author/ibrahima92/">Ibrahima Ndaw</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 为何新手可用 TypeScript？ ]]>
                </title>
                <description>
                    <![CDATA[ TypeScript 官网 [https://www.typescriptlang.org/zh/]开宗明义 —— > TypeScript 是适用于任何规模应用的 JavaScript 其中奥妙，且看我一个边干边学的 JS 码农娓娓道来~ 安装简单 因为 TypeScript 官方支持 ECMAScript 最新标准 [https://tc39.es/ecma262/]和进入 Stage 3 的提案 [https://jscig.github.io/#proposals?stage=3]，绝大多数项目的代码可以直接编译，polyfill 和运行时工具函数全内置，不像 Babel 那样需要自己找运行时补丁库或提案编译插件。 TypeScript npm install typescript -D Babel npm install @babel/core @babel/preset-env @babel/cli -D npm install @babel/polyfill import '@babel/polyfill'; 配置方便 TypeScript TypeScrip ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/why-should-you-use-typescript/</link>
                <guid isPermaLink="false">5fa14b6c5f583f0565090d88</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ TechQuery ]]>
                </dc:creator>
                <pubDate>Tue, 03 Nov 2020 07:21:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/04/photo-1593642532454-e138e28a63f4.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a href="https://www.typescriptlang.org/zh/" rel="noopener">TypeScript 官网</a>开宗明义 ——</p><blockquote>TypeScript 是适用于任何规模应用的 JavaScript</blockquote><p>其中奥妙，且看我一个边干边学的 JS 码农娓娓道来~</p><h2 id="-"><strong>安装简单</strong></h2><p>因为 TypeScript 官方支持 <a href="https://tc39.es/ecma262/" rel="noopener">ECMAScript 最新标准</a>和<a href="https://jscig.github.io/#proposals?stage=3" rel="noopener">进入 Stage 3 的提案</a>，绝大多数项目的代码可以直接编译，polyfill 和运行时工具函数全内置，不像 Babel 那样需要自己找<strong><strong>运行时补丁库</strong></strong>或<strong><strong>提案编译插件</strong></strong>。</p><h3 id="typescript"><strong>TypeScript</strong></h3><pre><code class="language-shell">npm install typescript -D</code></pre><h3 id="babel"><strong>Babel</strong></h3><pre><code class="language-shell">npm install @babel/core @babel/preset-env @babel/cli -D
npm install @babel/polyfill</code></pre><pre><code class="language-javascript">import '@babel/polyfill';</code></pre><h2 id="--1"><strong>配置方便</strong></h2><h3 id="typescript-1"><strong>TypeScript</strong></h3><p>TypeScript 默认不需要配置，在写 <code>.ts(x)</code> 文件的过程中，TS 编译器会在缺少配置的地方提示你需要添加什么配置，此时在项目根目录新建一个 <code>tsconfig.json</code> 文件，支持 <strong><strong>TS 语言服务协议</strong></strong>的代码编辑器还能提示配置文件的选项，全程按指导操作即可，官方文档都很少需要看~</p><h3 id="babel-1"><strong>Babel</strong></h3><p>Babel 虽然编译标准语法只需一个 preset 包，但更多的<strong><strong>常用提案语法</strong></strong>、<strong><strong>框架语法糖</strong></strong>就需要一大堆插件了，而且所有配置选项都没有代码提示，必须各种查文档，有时文档也不甚详细，容易踩坑……</p><pre><code class="language-json">{
  "presets": ["@babel/preset-env"]
}</code></pre><h2 id="--2"><strong>类型可选</strong></h2><p>TypeScript 的核心卖点是<strong><strong>类型系统</strong></strong>，这让很多从 JavaScript、PHP、Python 等<strong><strong>脚本语言</strong></strong>入门编程的程序员非常畏惧，认为是非常大的<strong><strong>学习成本</strong></strong>。</p><p>但 TS 的类型系统与 C/C++、Java、C# 等传统<strong><strong>强类型语言</strong></strong>从设计理念上非常不同 ——</p><blockquote>TS 代码不需要<em><em>处处声明类型</em></em>，而只需程序员在编译器推导无效的少许地方<strong><strong>标注类型</strong></strong></blockquote><p>而 TS <strong><strong>类型推导</strong></strong>的基础数据则是 JS、DOM、Node.js 等标准/技术中的 <strong><strong>API 类型定义</strong></strong>，编译器循着<strong><strong>语法结构</strong></strong>的上下文，自然能推导出大多数变/常量的类型，以便帮程序员<strong><strong>检查潜在错误</strong></strong>、<strong><strong>显示代码提示</strong></strong>。</p><h2 id="--3"><strong>开发轻松</strong></h2><h3 id="node-js-"><strong>Node.js 运行时</strong></h3><p>虽然 Node.js 创始人又另起炉灶搞了第一个<strong><strong>全功能 TypeScript 运行时环境</strong></strong> <a href="https://www.denojs.cn/" rel="noopener">Deno</a>，但并不意味着 Node.js 不能方便地运行 TS 代码，因为我们有 <a href="https://github.com/TypeStrong/ts-node" rel="noopener"><code>ts-node</code></a> ——</p><pre><code class="language-shell">// 全局安装运行时
npm install ts-node -g
// 执行一个 TS 脚本
ts-node index.ts</code></pre><h3 id="parcel-"><strong>Parcel 零配置打包</strong></h3><p>如果你用 <a href="https://zh.parceljs.org/" rel="noopener">Parcel</a> 这种<strong><strong>零配置打包器</strong></strong>开发 Web 前端项目，你甚至完全不用安装包括 TypeScript、LESS、SCSS 在内的任何常见<strong><strong>预编译语言</strong></strong>的编译器。在项目首次启动时，Parcel 会自动帮你安装好项目依赖的所有代码文件类型对应的编译器！你只需在启动项目时装好 Parcel，然后就按 <strong><strong>HTML、CSS、JS 原生语法</strong></strong>写逻辑、引用外置资源即可 ——</p><pre><code class="language-shell">npm install parcel-bundler -D</code></pre><h2 id="-ts-"><strong>总结：TS，真香！</strong></h2><p>我的 JavaScript 生涯从 ES 3 入门，后来用 ES 5 结合 JSDoc 开发小框架，再到 ES 6+ 配合 ESDoc 开发 <a href="https://github.com/EasyWebApp/WebCell/tree/master" rel="noopener">WebCell v1</a>，最终用 TS + TSDoc 重写出 <a href="https://web-cell.dev/" rel="noopener">WebCell v2</a>。在充分利用 <a href="https://tech-query.me/programming/javascript-in-one-word/">JavaScript 这门“无所不能”的语言</a>强大灵活性的同时，<strong><strong>更简洁的 ECMAScript 新语法</strong></strong>和<strong><strong>更规范的 TypeScript 类型系统</strong></strong>，为开源库和日常项目开发带来突飞猛进的效率提升 —— TS，真香！</p><h2 id="--4"><strong>彩蛋</strong></h2><p>再分享一个 TypeScript <strong><strong>JSX 类型推导</strong></strong>的新玩法：</p><blockquote><a href="https://github.com/TechQuery/CommanderJSX" rel="noopener">CommanderJSX</a> —— 用 JSX 声明<strong><strong>命令行参数</strong></strong>，并支持<strong><strong>回调传参类型推导</strong></strong></blockquote><p>欢迎访问我的<a href="https://tech-query.me/">更多文章</a>。</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
