TypeScript 学习笔记 — 类型断言(二)
<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>类型断言<ul><li>语法</li><li>类型断言的用途<ul><li>联合类型可以被断言为其中一个类型</li><li>父类可以被断言为子类</li><li>任何类型都可以被断言为 <code>any</code></li><li><code>any</code> 可以被断言为任何类型</li><li>非空断言</li><li>双重断言</li><li>字面量类型 + 联合类型</li></ul></li><li>类型断言的限制</li><li>类型断言 vs 类型转换</li><li>上一篇:TypeScript 入门自学笔记(一)</li></ul></li></ul></div><p></p><h2 id="类型断言">类型断言</h2>
<p>类型断言(Type Assertion): 主要用于当 TypeScript 推断出来类型并不满足当前需求时,TypeScript 允许开发者覆盖它的推断,可以用来手动指定一个值的类型。</p>
<blockquote>
<p>类型断言是一个编译时语法,不涉及运行时。</p>
</blockquote>
<h3 id="语法">语法</h3>
<p><code>值 as 类型</code><strong>(推荐)</strong>或 <code><类型>值</code></p>
<p>形如 <code><Foo></code> 的语法在 ts 中除了表示类型断言之外,也可能是表示一个泛型,故建议在使用类型断言时,使用 <code>值 as 类型</code> 语法。</p>
<pre><code class="language-ts">let strOrNum1: string | number;
(strOrNum1! as string).toLocaleLowerCase(); // 类型断言
(<number>strOrNum1!).toFixed(2); // 下面这种不推荐使用
</code></pre>
<h3 id="类型断言的用途">类型断言的用途</h3>
<p>类型断言的常见用途有以下几种:</p>
<h4 id="联合类型可以被断言为其中一个类型">联合类型可以被断言为其中一个类型</h4>
<p>上一篇文章中介绍访问联合类型的属性和方法,当 TS 不确定一个联合类型的变量到底是哪个类型时,<strong>只能访问此联合类型的所有类型中共有的属性或方法</strong>:</p>
<pre><code class="language-ts">interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
// 接口可看作一种类型
function getName(animal: Cat | Fish) {
return animal.name;
}
</code></pre>
<p>而有时确实需要在还不确定类型的时候就访问其中一个类型<strong>特有</strong>的属性或方法,如:</p>
<pre><code class="language-ts">interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof animal.swim === 'function') {// 报错
return true;
}
return false;
}
// Property 'swim' does not exist on type 'Cat | Fish'.
// Property 'swim' does not exist on type 'Cat'.
</code></pre>
<p>上述报错可使用类型断言,将 <code>animal</code> 断言成 <code>Fish</code>,就可以解决访问 <code>animal.swim</code> 报错的问题。</p>
<pre><code class="language-ts">interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
</code></pre>
<blockquote>
<p>注意:类型断言只能够欺骗 TS 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:</p>
</blockquote>
<pre><code class="language-ts">interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function swim(animal: Cat | Fish) {
(animal as Fish).swim();
}
const tom: Cat = {
name: 'Tom',
run() { console.log('run') }
};
swim(tom);
// 编译时不会报错,但在运行时会报错 Uncaught TypeError: animal.swim is not a function`
</code></pre>
<p>原因是 <code>(animal as Fish).swim()</code> 这段代码将 <code>animal</code> 直接断言为 <code>Fish</code> ,隐藏了 <code>animal</code> 可能为 <code>Cat</code> 的情况,而 TS 编译器信任了我们的断言,故在调用 <code>swim()</code> 时没有编译错误。</p>
<p>可是 <code>swim</code> 函数接受的参数类型是 <code>Cat | Fish</code>,一旦传入的参数是 <code>Cat</code> 类型的变量,由于 <code>Cat</code> 上没有 <code>swim</code> 方法,就会导致运行时错误。</p>
<p>总之,使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误。</p>
<h4 id="父类可以被断言为子类">父类可以被断言为子类</h4>
<p>当类之间有继承关系时,类型断言也很常见:</p>
<pre><code class="language-ts">class ApiError extends Error {
code: number = 0;
}
class HttpError extends Error {
statusCode: number = 200;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
</code></pre>
<p>声明了函数 <code>isApiError</code>,用来判断传入的参数是不是 <code>ApiError</code> 类型,其参数的类型肯定得是父类 <code>Error</code>,因此该函数便可接受 <code>Error</code> 或它的子类作为参数。</p>
<p>但由于父类 <code>Error</code> 中没有 <code>code</code> 属性,故直接获取 <code>error.code</code> 会报错,需要使用类型断言获取 <code>(error as ApiError).code</code>。</p>
<p>此案例中有一个更合适的方式来判断是不是 <code>ApiError</code>,那就是使用 <code>instanceof</code>, 因为 <code>ApiError</code> 是一个 JavaScript 的类,能够通过 <code>instanceof</code> 来判断 <code>error</code> 是否是它的实例:</p>
<pre><code class="language-ts">class ApiError extends Error {
code: number = 0;
}
class HttpError extends Error {
statusCode: number = 200;
}
function isApiError(error: Error) {
if (error instanceof ApiError) {
return true;
}
return false;
}
</code></pre>
<p>但有些情况下 <code>ApiError</code> 和 <code>HttpError</code> 不是一个真正的类,而只是一个 TS 的接口(<code>interface</code>),接口是一个类型,不是一个真正的值,它在编译结果中会被删除,就无法使用 <code>instanceof</code> 来做运行时判断了:</p>
<pre><code class="language-ts">interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (error instanceof ApiError) {
return true;
}
return false;
}
// 'ApiError' only refers to a type, but is being used as a value here.
</code></pre>
<p>此时就只能用类型断言,通过判断是否存在 <code>code</code> 属性,来判断传入的参数是不是 <code>ApiError</code> :</p>
<pre><code class="language-ts">interface ApiError extends Error {
code: number;
}
interface HttpError extends Error {
statusCode: number;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
</code></pre>
<h4 id="任何类型都可以被断言为-any">任何类型都可以被断言为 <code>any</code></h4>
<p>理想情况下,每个值的类型都具体而精确,但引用一个该类型上不存在的属性或方法,就会报错:</p>
<pre><code class="language-ts">const foo: number = 1;
foo.length = 1;
// 数字类型上是没有 `length` 属性
// Property 'length' does not exist on type 'number'.
</code></pre>
<p>而有时,非常确定一段代码不会出错,如给 <code>window</code> 上添加一个属性 <code>foo</code>,但 TS 编译时会报错,提示 <code>window</code> 上不存在 <code>foo</code> 属性。:</p>
<pre><code class="language-ts">window.foo = 1;
// Property 'foo' does not exist on type 'Window & typeof globalThis'.
</code></pre>
<p>此时可以使用 <code>as any</code> 临时将 <code>window</code> 断言为 <code>any</code> 类型,在 <code>any</code> 类型的变量上,访问任何属性都是允许的。</p>
<pre><code class="language-ts">(window as any).foo = 1;
</code></pre>
<p>将一个变量断言为 <code>any</code> 可以说是解决 TypeScript 中类型问题的最后一个手段。<strong>它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 <code>as any</code>。</strong>总之,<strong>一方面不能滥用 <code>as any</code>,另一方面也不要完全否定它的作用,需要在类型的严格性和开发的便利性之间掌握平衡</strong>。</p>
<h4 id="any-可以被断言为任何类型"><code>any</code> 可以被断言为任何类型</h4>
<p>在日常的开发中,不可避免的需要处理 any类型的变量,我们可以选择无视它,也可以选择改进它,通过类型断言及时的把 <code>any</code> 断言为精确的类型,亡羊补牢,提高代码可维护性。</p>
<pre><code class="language-ts">function getCacheData(key: string): any {
return (window as any).cache;
}
</code></pre>
<p>在使用时,最好能够将调用了它之后的返回值断言成一个精确的类型,方便后续操作:</p>
<pre><code class="language-ts">function getCacheData(key: string): any {
return (window as any).cache;
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
</code></pre>
<p>调用完 <code>getCacheData</code> 之后,立即将它断言为 <code>Cat</code> 类型。明确 <code>tom</code> 的类型,后续对 <code>tom</code> 的访问就有了代码补全,提高了代码的可维护性。</p>
<h4 id="非空断言">非空断言</h4>
<pre><code class="language-ts">let ele = document.getElementById("root");
// ?. 是js语法, 叫链判断运算符, 这个值没有就不取值了
// !. 是ts语法, 断言这个值一定存在
ele?.style.background = "red"; // 报错,赋值表达式的左侧不能是可选属性访问。
ele!.style.background = "red"; // 编译通过
</code></pre>
<h4 id="双重断言">双重断言</h4>
<p>断言只能断言成一个已经存在的类型,如果不存在不能直接断言,此时可以使用双重断言,缺点就是会破坏原有的类型, 不建议使用</p>
<pre><code class="language-ts">let strOrNum1: string | number;
strOrNum1! as any as boolean;
</code></pre>
<p>既然:</p>
<ul>
<li>任何类型都可以被断言为 any</li>
<li>any 可以被断言为任何类型</li>
</ul>
<pre><code class="language-ts">interface Cat {
run(): void;
}
interface Fish {
swim(): void;
}
function testCat(cat: Cat) {
return (cat as any as Fish);
}
</code></pre>
<p>若直接使用 <code>cat as Fish</code> 肯定会报错,因为 <code>Cat</code> 和 <code>Fish</code> 互相都不兼容。</p>
<p>但是使用双重断言,则可以打破<code>要使得A能够被断言为 B,只需A 兼容 B 或B兼容A即可</code>的限制,可以使用双重断言 <code>as any as Foo</code> ,<code>将任何一个类型断言为任何另一个类型</code>。</p>
<p>但是若使用了双重断言,那么很可能会导致运行时错误。<strong>除非迫不得已,千万别用双重断言。</strong></p>
<h4 id="字面量类型--联合类型">字面量类型 + 联合类型</h4>
<pre><code class="language-ts">let username: "yya" = "yya";
let password: 123456 = 123456;
</code></pre>
<p>但是字面量类型用的比较少,常见的用法是字面量类型与联合类型结合使用:</p>
<pre><code class="language-ts">// 关键字 type、enum 都是ts提供的
type Direction = "up" | "down" | "left" | "right";
let direction: Direction = "down"; // 字面量类型限定了值,同枚举类似
</code></pre>
<h3 id="类型断言的限制">类型断言的限制</h3>
<p>类型断言是有限制的,并不是任何一个类型都可以被断言为任何另一个类型。具体来说,<strong>若 <code>A</code> 兼容 <code>B</code>,那么 <code>A</code> 能够被断言为 <code>B</code>,<code>B</code> 也能被断言为 <code>A</code>。</strong></p>
<pre><code class="language-ts">interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
let tom: Cat = {
name: 'Tom',
run: () => { console.log('run') }
};
let animal: Animal = tom;
</code></pre>
<p><code>Cat</code> 包含了 <code>Animal</code> 中的所有属性,TypeScript 只关注最终的结构有什么关系——所以同 <code>Cat extends Animal</code> 是等价的:</p>
<pre><code class="language-ts">interface Animal {
name: string;
}
interface Cat extends Animal {
run(): void;
}
</code></pre>
<p>这也是为什么 <code>Cat</code> 类型的 <code>tom</code> 可以赋值给 <code>Animal</code> 类型的 <code>animal</code> 。</p>
<p>当 <code>Animal</code> 兼容 <code>Cat</code> 时,它们就可以互相进行类型断言</p>
<pre><code class="language-ts">interface Animal {
name: string;
}
interface Cat {
name: string;
run(): void;
}
function testAnimal(animal: Animal) {
return (animal as Cat);
}
function testCat(cat: Cat) {
return (cat as Animal);
}
</code></pre>
<ul>
<li>允许 <code>animal as Cat</code> 是因为「父类可以被断言为子类」,这个前面已经学习过了</li>
<li>允许 <code>cat as Animal</code> 是因为既然子类拥有父类的属性和方法,那么被断言为父类,获取父类的属性、调用父类的方法,就不会有任何问题,故「子类可以被断言为父类」</li>
</ul>
<blockquote>
<p>要使得 <code>A</code> 能够被断言为 <code>B</code>,只需要 <code>A</code> 兼容 <code>B</code> 或 <code>B</code> 兼容 <code>A</code> 即可,这也是为了在类型断言时的安全考虑,毕竟毫无根据的断言是非常危险的。</p>
<p>综上所述:</p>
<ul>
<li>联合类型可以被断言为其中一个类型</li>
<li>父类可以被断言为子类</li>
<li>任何类型都可以被断言为 any</li>
<li>any 可以被断言为任何类型</li>
<li>要使得 <code>A</code> 能够被断言为 <code>B</code>,只需要 <code>A</code> 兼容 <code>B</code> 或 <code>B</code> 兼容 <code>A</code> 即可</li>
</ul>
</blockquote>
<p>其实前四种情况都是最后一个的特例。</p>
<h3 id="类型断言-vs-类型转换">类型断言 vs 类型转换</h3>
<p><strong>类型断言不是类型转换,它不会真的影响到变量的类型。</strong></p>
<p>类型断言只会影响编译时的类型,断言语句在编译结果中会被删除:</p>
<pre><code class="language-ts">function toBoolean(something: any): boolean {
return something as boolean;
}
toBoolean(1);
// 返回值为 1
</code></pre>
<p>若要进行类型转换,还是需要调用类型转换的方法:</p>
<pre><code class="language-ts">function toBoolean(something: any): boolean {
return Boolean(something);
}
toBoolean(1);
// 返回值为 true
</code></pre>
<p><code> </code></p>
<h3 id="上一篇typescript-入门自学笔记一">上一篇:TypeScript 入门自学笔记(一)</h3>
<p><code> </code><br>
<code> </code></p><br><br>
来源:https://www.cnblogs.com/echoyya/p/14558034.html
頁:
[1]