这是我参与更文挑战的第十二天,活动详情查看:更文挑战
类型检查机制是什么
typescript 编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为。
类型检查机制有什么作用
辅助开发,提高开发效率
类型检查机制分类
- 类型推断
- 类型兼容性
- 类型保护
类型推断
不需要指定变量的类型或者函数的返回值类型,typescript 可以根据某些规则自动地为其推断出一个类型
- 基础类型推断
- 最佳通用类型推断
- 上下文类型推断
基础类型的类型推断
使用场景:初始化一个变量的时候
// TS推断结果: let a: number
let a = 1;
// TS推断结果: let b: any[]
let b = [];
// let c: number[]
let c = [1];
// let d: string[]
let d = ["a"];
复制代码
也适用于 设置函数默认参数 的时候
// (parameter) x: number
let a1 = (x = 1) => {
return x;
};
// let c: (x?: number) => number
let c = (x = 1) => {
return x + 1;
};
复制代码
也适用于 确定 函数返回值 的时候
// let a2: (x?: number) => number
let a2 = (x = 1) => {
const res = x + 2;
return res;
};
复制代码
最佳通用类型推断
当需要从多个类型中推断出一个类型的时候,TS 就会尽可能的推断出一个兼容当前所有类型的通用类型
// let b1: (number | null)[]
let b1 = [1, null];
复制代码
1 和 null 是不兼容的两种类型,TS 就会推断出以 number 和 null 组合而成的 联合类型
注意: 在 tsconfig.json 中修改如下配置后,结果类型检查结果又会改变
...
"strictNullChecks": false,
...
复制代码
// let b1: number[]
let b1 = [1, null];
复制代码
总结
以上的类型推断,都是从右向左推断方式, 即根据表达式右侧的值推断 表达式左侧变量的类型
还有一种,从左到右的推断方式,即上下文类型推断,通常发生一个事件处理中
上下文类型推断
通常发生在事件处理中,
// ts或根据左侧时间绑定,来推断右侧事件的类型
window.onkeydown = (event: KeyboardEvent) => {
console.log(event.AT_TARGET);
};
/*
最新版本的vscode中,输入window.onkeydown = (event) => { console.log('onkeydown')},
编辑器会提示参数“event”隐式具有“any”类型,而没有进行上下文推断。
解决:加上 KeyboardEvent 类型就可以了
*/
复制代码
有时候 TS 推断不符合预期,而且开发人员比 TS 更加了解代码的时候,TS 允许开发人员手动覆盖 TS 推断(类型断言)
**
类型断言
let foo = {};
// foo这个空对象上没有bar属性, 如何解决呢
foo.bar = 1;
复制代码
interface Foo {
bar: number;
}
let foo = {} as Foo;
foo.bar = 1;
复制代码
类型断言可以增加代码的灵活性,在改造一些旧代码时非常有效,
但类型断言不能滥用,需要对上下文环境有充足的预判,
建议在申明的时候就指定所有的类型
interface Foo {
bar: number;
}
// 滥用类型断言后 没有报错
let foo = {} as Foo;
复制代码
在申明的时候就指定所有的类型
interface Foo {
bar: number;
}
/*
类型 "{}" 中缺少属性 "bar",但类型 "Foo" 中需要该属性。ts(2741)
4.ts(3, 5): 在此处声明了 "bar"。
*/
let foo: Foo = {};
复制代码
解决
interface Foo {
bar: number;
}
let foo: Foo = {
bar: 1,
};
复制代码
类型兼容性是什么:
当一个类型 Y 可以被赋值给一个类型 X 时,我么你就可以说类型 X 兼容类型 Y
X 兼容 Y : X(目标类型) = Y(源类型)
/*
配置--tyconfig.json: "strictNullChecks": false,
字符型兼容null类型,null是字符型的子类型
*/
let s: string = "a";
s = null;
复制代码
结构之间兼容:成员少的兼容成员多的
函数之间兼容:参数多的兼容参数少的
类型兼容性的作用:
因为 TS 运行我们把一些类型不同的变量相互赋值,虽然在某种程度上可能产生 不可靠的行为,但**增加了语言的灵活性, **
类型兼容性多存在于类,函数,接口中
接口兼容性
interface X {
a: any;
b: any;
}
interface Y {
a: any;
b: any;
c: any;
}
let xtest: X = { a: 1, b: 2 };
let ytest: Y = { a: 1, b: 2, c: 3 };
xtest = ytest;
// 类型 "X" 中缺少属性 "c",但类型 "Y" 中需要该属性。ts(2741)
// ytest = xtest;
复制代码
ytest 可以复制给 xtest, 但 xtest 不能复制给 ytest.Y 接口具备 X 接口的所有属性,不管是否有额外的属性,那么 Y 可以被认为是 X 类型,即 X 类型兼容 Y 类型—-鸭式辩型法 成员少的兼容成员多的
函数兼容性
两个函数是否兼容,一般发生在两个函数相互赋值的情况下,如函数作为参数的时候
目标函数, 目标类型
源函数,源类型
目标函数兼容源函数,需要满足三个条件
参数个数限制
:
目标函数的参数个数,一定要多于源函数的参数个数
情况一:函数中的参数个数是固定的
type Handler = (a: number, b: number) => void;
function hof(handler: Handler) {
return handler;
}
/*
当给 hof传入参数时,就会判断是否和 Handler类型兼容,
Handler就是目标型
给 hof传入的参数就是源类型
*/
let handler1 = (a: number) => {};
hof(handler1);
let handler2 = (a: number, b: number, c: number) => {};
/*
类型“(a: number, b: number, c: number) => void”的参数不能赋给类型“Handler”的参数。
因为目标类型--Handler只有连个参数
*/
// hof(handler2);
复制代码
情况二: 函数中具有不固定个数的参数
如果函数中具有不固定个数的参数
可选参数和剩余参数
固定参数可以兼容可选参数和剩余参数
let a = (p1: number, p2: number) => {};
let b = (p1?: number, p2?: number) => {};
let c = (...args: number[]) => {};
// a可以兼容b
a = b;
// a可以兼容c
a = c;
复制代码
可选参数不能兼容固定参数和剩余参数
/*
配置 tsconfig.js
"strictNullChecks": true,
"strictFunctionTypes": true,
*/
let a = (p1: number, p2: number) => {};
let b = (p1?: number, p2?: number) => {};
let c = (...args: number[]) => {};
// 参数“args”和“p1” 的类型不兼容
b = c;
// 参数“p1”和“p1” 的类型不兼容。
b = a;
复制代码
剩余参数可以兼容固定参数和可选参数
let a = (p1: number, p2: number) => {};
let b = (p1?: number, p2?: number) => {};
let c = (...args: number[]) => {};
c = a;
c = b;
复制代码
参数类型–参数类型一定要匹配
基础类型
type Handler = (a: number, b: number) => void;
function hof(handler: Handler) {
return handler;
}
let handlers = (c: string) => {};
// 参数“c”和“a” 的类型不兼容
hof(handlers);
复制代码
对象类型
interface Point3D {
x: number;
y: number;
z: number;
}
interface Point2D {
x: number;
y: number;
}
let p3d = (points3d: Point3D) => {};
let p2d = (points2d: Point2D) => {};
/*
结果:
在 函数的 参数是对象类型的时候, 成员多的兼容成员少的
与接口的兼容性正好相反(成员少的兼容成员多的)
*/
p3d = p2d;
// 参数“points3d”和“points2d” 的类型不兼容
p2d = p3d;
复制代码
返回值类型
目标类型函数的返回值必须与源函数的返回值类型相同,或者是 源函数返回值类型的子类型
let f = () => ({ name: "a" });
let g = () => ({ name: "b", age: 20 });
// f兼容g
f = g;
// g不兼容f
g = f;
复制代码
因为 f 的返回值类型是 g 返回值类型的子类型,而且这里也是成员少的兼容成员多的(与鸭式辩型法一致)
函数重载
函数重载包括 重载列表和函数实现
重载列表中的函数就是目标函数, 函数的实现就是源函数
程序在运行时,会查找重载列表,
在重载列表中,目标函数的参数个数要 多余 源函数的参数个数
// 目标函数
function overloadtest(a: number, b: number, c: number): number;
function overloadtest(a: string, b: string, c: string): string;
// 源函数
function overloadtest(a: any, b: any): any {}
复制代码
function overloadtest(a: number, b: number, c: number): number;
function overloadtest(a: string, b: string, c: string): string;
// 会出现错误-原因: 参数过多
function overloadtest(a: any, b: any, c: any, d: any): any;
{
}
复制代码
// 会出现错误-原因: 返回值不兼容
function overloadtest(a: number, b: number, c: number): number;
function overloadtest(a: string, b: string, c: string): string;
//
function overloadtest(a: any, b: any, c: any) {}
复制代码
枚举类型的兼容性
枚举类型和数字类型可以相互兼容
enum Fruit {
Apple,
Banana,
}
enum Color {
Red,
Yellow,
}
let fruit: Fruit.Apple = 3;
let numtest: number = Fruit.Apple;
复制代码
枚举类型互不兼容
enum Fruit {
Apple,
Banana,
}
enum Color {
Red,
Yellow,
}
// 不能将类型“Fruit.Apple”分配给类型“Color.Red”
let color: Color.Red = Fruit.Apple;
复制代码
类的兼容性
在比较两个类是否兼容时, 静态成员和构造函数式不参与比较的,如果两个类具有相同的实例成员,那么他们的实例就会相互兼容
class A {
constructor(p: number, q: number) {}
id: number = 1;
}
class B {
static s = 1;
constructor(p: number) {}
id: number = 2;
}
let aa = new A(1, 2);
let bb = new B(1);
aa = bb;
bb = aa;
复制代码
如果类中含有私有成员,只有父类和子类是可以相互兼容的
class A {
constructor(p: number, q: number) {}
id: number = 1;
private name: string = "";
}
class B {
static s = 1;
constructor(p: number) {}
id: number = 2;
private name: string = "";
}
let aa = new A(1, 2);
let bb = new B(1);
// 不能将类型“B”分配给类型“A”。 类型具有私有属性“name”的单独声明
aa = bb;
// 不能将类型“B”分配给类型“A”。 类型具有私有属性“name”的单独声明
bb = aa;
复制代码
class A {
constructor(p: number, q: number) {}
id: number = 1;
private name: string = "";
}
class C extends A {}
let aa = new A(1, 2);
let cc = new C(1, 2);
aa = cc;
cc = aa;
复制代码
泛型的兼容性
泛型变量的兼容性
// 泛型T 被接口使用后就会影响泛型的兼容性
interface Empty<T> {
value: T;
}
let objtest1: Empty<number> = {};
let objtest2: Empty<string> = {};
// 不兼容
objtest1 = objtest2;
复制代码
泛型函数的兼容性
如果两个泛型函数的定义相同,而且没有指定类型参数, 二者是相互兼容的
let log1 = <T>(x: T): T => x;
let log2 = <U>(x: U): U => x;
log1 = log2;
log2 = log1;
复制代码
enum Type {
Strong,
Week,
}
class java {
hellojava() {
console.log("hello java");
}
}
class javascript {
hellojavascript() {
console.log("hello javascript");
}
}
function getLanguage(type: Type) {
let lang = type === Type.Strong ? new java() : new javascript();
/*
类型“java | javascript”上不存在属性“hellojava”。
类型“javascript”上不存在属性“hellojava”。ts(2339)
*/
if (lang.hellojava) {
lang.hellojava();
} else {
/*
类型“java | javascript”上不存在属性“hellojava”。
类型“javascript”上不存在属性“hellojava”。ts(2339)
*/
lang.hellojavascript();
}
return lang;
}
getLanguage(Type.Strong);
复制代码
解决方式一(类型断言)
......
function getLanguage(type: Type) {
let lang = type === Type.Strong ? new Java : new Javascript;
if(!!(lang as Java).helloJava) {
(lang as Java).helloJava();
} else {
(lang as Javascript).hellojavascript();
}
return lang;
}
......
复制代码
代码可读性差,而”类型保护机制” 可以解决这个问题,因为它可以提前对类型做出预判
类型保护是什么
typescript 能够在特定区块中保证变量属于某种确定类型。可以在此区块中放心地引用此类型的属性,或者调用此类型的方法
使用方式一(instanceof)
......
function getLanguage(type: Type) {
let lang = type === Type.Strong ? new Java : new Javascript;
// instanceof判断 是否属于某个类
if(lang instanceof Java){
lang.helloJava();
}else{
lang.hellojavascript();
}
return lang;
}
......
复制代码
使用方式二(in)
......
function getLanguage(type: Type) {
let lang = type === Type.Strong ? new Java : new Javascript;
// in 判断是否属于某个对象
if('helloJava' in lang) {
lang.helloJava();
}else{
lang.hellojavascript();
}
return lang;
}
......
复制代码
使用方式三(typeof)
let x: number | string = "";
// typeof 判断基本类型
if (typeof x === "string") {
// 在此区块中是 string 类型,就拥有string类型的一些属性/方法
console.log(x.length);
} else if (typeof x === "number") {
// 在此区块中是 number 类型,就拥有number类型的一些属性/方法
console.log(x++);
}
复制代码
使用方式四(类型保护函数)
创建类型保护函数来判断对象的类型
enum Type {
Strong,
Week,
}
class Java {
helloJava() {
console.log("hello java");
}
}
class Javascript {
hellojavascript() {
console.log("hello javascript");
}
}
function isJava(lang: Java | Javascript): lang is Java {
return (lang as Java).helloJava !== undefined;
}
function getLanguage(type: Type) {
let lang = type === Type.Strong ? new Java() : new Javascript();
if (isJava(lang)) {
lang.helloJava();
} else {
lang.hellojavascript();
}
return lang;
}
getLanguage(Type.Strong);
复制代码