沉尘臣 發表於 2021-6-28 09:18:00

TypeScript入门教程-阮一峰

<p>从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。</p>
<p>http://ts.xcatliu.com/</p>
<p>什么是TypeScript?</p>
<p>TypeScript是一种添加了类型系统的 JavaScript,适用于任何规模的项目。</p>
<p>我们都知道JavaScript是一种弱类型的语言。而TypeScript增强了它的类型。</p>
<p>由于JavaScript 是一门非常灵活的编程语言,这将导致:</p>
<ul>
<li>它没有类型约束,一个变量可能初始化时是字符串,过一会儿又被赋值为数字。</li>
<li>由于隐式类型转换的存在,有的变量的类型很难在运行前就确定。</li>
<li>基于原型的面向对象编程,使得原型上的属性或方法可以在运行时被修改。</li>
<li>函数是 JavaScript 中的一等公民,可以赋值给变量,也可以当作参数或返回值。</li>
</ul>
<p>这种灵活性就像一把双刃剑,一方面使得 JavaScript 蓬勃发展,无所不能,从 2013 年开始就一直蝉联最普遍使用的编程语言排行榜冠军;另一方面也使得它的代码质量参差不齐,维护成本高,运行时错误多。</p>
<p>而 TypeScript 的类型系统,在很大程度上弥补了 JavaScript 的缺点。它是静态语言。</p>
<p>而JavaScript是动态语言,如下面的代码直到运行时才会报错:</p>
<div class="cnblogs_code">
<pre>let foo = 1<span style="color: rgba(0, 0, 0, 1)">;
foo.split(</span>' ');</pre>
</div>
<p>同样是这段代码,在TypeScript下编译时就会报错了。</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">let foo <span class="token operator">= <span class="token number">1<span class="token punctuation">;
foo<span class="token punctuation">.<span class="token function">split<span class="token punctuation">(<span class="token string">' '<span class="token punctuation">)<span class="token punctuation">;</span></span></span></span></span></span></span></span></span></span></code></pre>
<p>你可能会奇怪,这段 TypeScript 代码看上去和 JavaScript 没有什么区别呀。没错!大部分 JavaScript 代码都只需要经过少量的修改(或者完全不用修改)就变成 TypeScript 代码,这得益于 TypeScript 强大的[类型推论][],即使不去手动声明变量&nbsp;<code>foo</code>&nbsp;的类型,也能在变量初始化时自动推论出它是一个&nbsp;<code>number</code>&nbsp;类型。</p>
<p>完整的 TypeScript 代码是这样的:</p>
<div class="cnblogs_code">
<pre>let foo: number = 1<span style="color: rgba(0, 0, 0, 1)">;
foo.split(</span>' ');</pre>
</div>
<p>TypeScript 是弱类型:</p>
<p>类型系统按照「是否允许隐式类型转换」来分类,可以分为强类型和弱类型。</p>
<p>以下这段代码不管是在 JavaScript 中还是在 TypeScript 中都是可以正常运行的,运行时数字&nbsp;<code>1</code>&nbsp;会被隐式类型转换为字符串&nbsp;<code>'1'</code>,加号&nbsp;<code>+</code>&nbsp;被识别为字符串拼接,所以打印出结果是字符串&nbsp;<code>'11'</code>。</p>
<div class="cnblogs_code">
<pre>console.log(1 + '1');</pre>
</div>
<p>作为对比,Python是强类型的。需要进行强制类型转换。</p>
<p>这样的类型系统体现了 TypeScript 的核心设计理念:在完整保留 JavaScript 运行时行为的基础上,通过引入静态类型系统来提高代码的可维护性,减少可能出现的 bug。</p>
<p>TypeScript非常适合于大型项目。在中小型项目中推行 TypeScript 的最大障碍就是认为使用 TypeScript 需要写额外的代码,降低开发效率。但事实上,由于有[类型推论][],大部分类型都不需要手动声明了。TypeScript 还可以和 JavaScript 共存。这意味着如果你有一个使用 JavaScript 开发的旧项目,又想使用 TypeScript 的特性,那么你不需要急着把整个项目都迁移到 TypeScript,你可以使用 TypeScript 编写新文件,然后在后续更迭中逐步迁移旧文件。如果一些 JavaScript 文件的迁移成本太高,TypeScript 也提供了一个方案,可以让你在不修改 JavaScript 文件的前提下,编写一个[类型声明文件][],实现旧项目的渐进式迁移。</p>
<p>事实上,就算你从来没学习过 TypeScript,你也可能已经在不知不觉中使用到了 TypeScript——在 VSCode 编辑器中编写 JavaScript 时,代码补全和接口提示等功能就是通过 TypeScript Language Service 实现的。</p>
<p>一些第三方库原生支持了 TypeScript,在使用时就能获得代码补全了,比如 Vue 3.0</p>
<p>有一些第三方库原生不支持 TypeScript,但是可以通过安装社区维护的类型声明库(比如通过运行&nbsp;<code>npm install --save-dev @types/react</code>&nbsp;来安装 React 的类型声明库)来获得代码补全能力——不管是在 JavaScript 项目中还是在 TypeScript 中项目中都是支持的:</p>
<ul>
<li>2016-05:<code>@types/react</code>&nbsp;发布,TypeScript 可以开发 React 应用了。</li>
<li>2016-05:<code>@types/node</code>&nbsp;发布,TypeScript 可以开发 Node.js 应用了。</li>
<li>2020-09:Vue 发布了 3.0 版本,官方支持 TypeScript。</li>
</ul>
<p><span style="font-size: 15px"><strong>安装TypeScript</strong></span></p>
<p>TypeScript 的命令行工具安装方法如下:</p>
<div class="cnblogs_code">
<pre>npm install -g typescript</pre>
</div>
<p>以上命令会在全局环境下安装&nbsp;<code>tsc</code>&nbsp;命令,安装完成之后,我们就可以在任何地方执行&nbsp;<code>tsc</code>&nbsp;命令了。</p>
<p>编译一个 TypeScript 文件很简单:</p>
<div class="cnblogs_code">
<pre>tsc hello.ts</pre>
</div>
<p>我们约定使用 TypeScript 编写的文件以&nbsp;<code>.ts</code>&nbsp;为后缀,用 TypeScript 编写 React 时,以&nbsp;<code>.tsx</code>&nbsp;为后缀。</p>
<p><span style="font-size: 15px"><strong>Hello TypeScript</strong></span></p>
<p>我们从一个简单的例子开始。</p>
<p>将以下代码复制到&nbsp;<code>hello.ts</code>&nbsp;中:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sayHello(person: string) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> 'Hello, ' +<span style="color: rgba(0, 0, 0, 1)"> person;
}

let user </span>= 'Tom'<span style="color: rgba(0, 0, 0, 1)">;
console.log(sayHello(user));</span></pre>
</div>
<p>然后执行</p>
<div class="cnblogs_code">
<pre>tsc hello.ts</pre>
</div>
<p>这时候会生成一个编译好的文件&nbsp;<code>hello.js</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sayHello(person) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> 'Hello, ' +<span style="color: rgba(0, 0, 0, 1)"> person;
}
</span><span style="color: rgba(0, 0, 255, 1)">var</span> user = 'Tom'<span style="color: rgba(0, 0, 0, 1)">;
console.log(sayHello(user));</span></pre>
</div>
<p>上述例子中,我们用&nbsp;<code>:</code>&nbsp;指定&nbsp;<code>person</code>&nbsp;参数类型为&nbsp;<code>string</code>。但是编译为 js 之后,并没有什么检查的代码被插入进来。</p>
<p>这是因为&nbsp;TypeScript 只会在编译时对类型进行静态检查,如果发现有错误,编译的时候就会报错。而在运行时,与普通的 JavaScript 文件一样,不会对类型进行检查。</p>
<p>如果我们需要保证运行时的参数类型,还是得手动对类型进行判断:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sayHello(person: string) {
    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span> person === 'string'<span style="color: rgba(0, 0, 0, 1)">) {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> 'Hello, ' +<span style="color: rgba(0, 0, 0, 1)"> person;
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Error('person is not a string'<span style="color: rgba(0, 0, 0, 1)">);
    }
}

let user </span>= 'Tom'<span style="color: rgba(0, 0, 0, 1)">;
console.log(sayHello(user));</span></pre>
</div>
<p><code>let</code>&nbsp;是 ES6 中的关键字,和&nbsp;<code>var</code>&nbsp;类似,用于定义一个局部变量,可以参阅&nbsp;let 和 const 命令。</p>
<p>下面尝试把这段代码编译一下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sayHello(person: string) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> 'Hello, ' +<span style="color: rgba(0, 0, 0, 1)"> person;
}

let user </span>= ;
console.log(sayHello(user));</span></pre>
</div>
<p>编辑器中会提示错误,编译的时候也会出错。</p>
<p>但是还是生成了 js 文件。</p>
<p>这是因为&nbsp;TypeScript 编译的时候即使报错了,还是会生成编译结果,我们仍然可以使用这个编译之后的文件。</p>
<p>如果要在报错的时候终止 js 文件的生成,可以在&nbsp;<code>tsconfig.json</code>&nbsp;中配置&nbsp;<code>noEmitOnError</code>&nbsp;即可。关于&nbsp;<code>tsconfig.json</code>,请参阅官方手册(中文版)。</p>
<h1>基础</h1>
<p>本部分介绍了 TypeScript 中的常用类型和一些基本概念,旨在让大家对 TypeScript 有个初步的理解。具体内容包括:</p>
<ul>
<li>原始数据类型</li>
<li>任意值</li>
<li>类型推论</li>
<li>联合类型</li>
<li>对象的类型——接口</li>
<li>数组的类型</li>
<li>函数的类型</li>
<li>类型断言</li>
<li>声明文件</li>
<li>内置对象</li>
</ul>
<p><strong>原始数据类型</strong></p>
<p>JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。 原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol 和 ES10 中的新类型 BigInt。 本节主要介绍前五种原始数据类型在 TypeScript 中的应用。</p>
<p>布尔值</p>
<p>数值</p>
<p>字符串</p>
<p>空值</p>
<p>Null 和 Undefined</p>
<h1>任意值</h1>
<p>任意值(Any)用来表示允许赋值为任意类型。</p>
<p><span style="text-decoration: underline">什么是任意值类型?</span></p>
<p>如果是一个普通类型,在赋值过程中改变类型是不被允许的。但如果是&nbsp;<code>any</code>&nbsp;类型,则允许被赋值为任意类型。</p>
<p><span style="text-decoration: underline">任意值的属性和方法:</span></p>
<p>在任意值上访问任何属性都是允许的。</p>
<p>也允许调用任何方法。</p>
<p>可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。</p>
<p><span style="text-decoration: underline">未声明类型的变量</span></p>
<p>变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型。</p>
<h1>类型推论</h1>
<p>如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。</p>
<p><span style="text-decoration: underline">什么是类型推论</span></p>
<p>以下代码虽然没有指定类型,但是会在编译的时候报错:</p>
<div class="cnblogs_code">
<pre>let myFavoriteNumber = 'seven'<span style="color: rgba(0, 0, 0, 1)">;
myFavoriteNumber </span>= 7;</pre>
</div>
<p>TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。</p>
<h1>联合类型</h1>
<p>联合类型(Union Types)表示取值可以为多种类型中的一种。</p>
<p><span style="text-decoration: underline">简单的例子</span></p>
<div class="cnblogs_code">
<pre>let myFavoriteNumber: string |<span style="color: rgba(0, 0, 0, 1)"> number;
myFavoriteNumber </span>= 'seven'<span style="color: rgba(0, 0, 0, 1)">;
myFavoriteNumber </span>= 7;</pre>
</div>
<p><span style="text-decoration: underline">访问联合类型的属性或方法</span></p>
<h1>对象的类型——接口</h1>
<p>在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。</p>
<p><span style="text-decoration: underline">什么是接口</span></p>
<p>在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。</p>
<p>TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。</p>
<p><span style="text-decoration: underline">简单的例子</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface Person {
    name: string;
    age: number;
}

let tom: Person </span>=<span style="color: rgba(0, 0, 0, 1)"> {
    name: </span>'Tom'<span style="color: rgba(0, 0, 0, 1)">,
    age: </span>25<span style="color: rgba(0, 0, 0, 1)">
};</span></pre>
</div>
<p>上面的例子中,我们定义了一个接口&nbsp;<code>Person</code>,接着定义了一个变量&nbsp;<code>tom</code>,它的类型是&nbsp;<code>Person</code>。这样,我们就约束了&nbsp;<code>tom</code>&nbsp;的形状必须和接口&nbsp;<code>Person</code>&nbsp;一致。</p>
<p><span style="text-decoration: underline">可选属性</span></p>
<p>有时我们希望不要完全匹配一个形状,那么可以用可选属性。</p>
<p><span style="text-decoration: underline">任意属性</span></p>
<p>有时候我们希望一个接口允许有任意的属性,可以使用如下方式。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface Person {
    name: string;
    age</span>?<span style="color: rgba(0, 0, 0, 1)">: number;
    : any;
}</span></pre>
</div>
<p><span style="text-decoration: underline">只读属性</span></p>
<p>有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性。</p>
<h1>数组的类型</h1>
<p>在 TypeScript 中,数组类型有多种定义方式,比较灵活。</p>
<p><span style="text-decoration: underline">「类型 + 方括号」表示法</span></p>
<p><span style="text-decoration: underline">数组泛型</span></p>
<p><span style="text-decoration: underline">用接口表示数组</span></p>
<p>接口也可以用来描述数组:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface NumberArray {
    : number;
}
let fibonacci: NumberArray </span>= ;</pre>
</div>
<p><code>NumberArray</code>&nbsp;表示:只要索引的类型是数字时,那么值的类型必须是数字。</p>
<p>虽然接口也可以用来描述数组,但是我们一般不会这么做,因为这种方式比前两种方式复杂多了。</p>
<p>不过有一种情况例外,那就是它常用来表示类数组。</p>
<p><span style="text-decoration: underline">类数组</span></p>
<p>类数组(Array-like Object)不是数组类型,比如&nbsp;<code>arguments</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sum() {
    let args: number[] </span>=<span style="color: rgba(0, 0, 0, 1)"> arguments;
}</span></pre>
</div>
<p>上例中,<code>arguments</code>&nbsp;实际上是一个类数组,不能用普通的数组的方式来描述,而应该用接口:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> sum() {
    let args: {
      : number;
      length: number;
      callee: Function;
    } </span>=<span style="color: rgba(0, 0, 0, 1)"> arguments;
}</span></pre>
</div>
<p>在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有&nbsp;<code>length</code>&nbsp;和&nbsp;<code>callee</code>&nbsp;两个属性。</p>
<p>事实上常用的类数组都有自己的接口定义,如&nbsp;<code>IArguments</code>,&nbsp;<code>NodeList</code>,&nbsp;<code>HTMLCollection</code>&nbsp;等:</p>
<p>关于内置对象,可以参考内置对象一章。</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/2008nmj/p/14943063.html
頁: [1]
查看完整版本: TypeScript入门教程-阮一峰