ts基础知识

1. any 类型,unknown 类型,never 类型


 TypeScript 有两个“顶层类型”(any和unknown),但是“底层类型”只有never唯一一个

1、any


1.1 基本含义


any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。变量类型一旦设为any,TypeScript 实际上会关闭这个变量的类型检查。即使有明显的类型错误,只要句法正确,都不会报错。

let x:any;
 
x = 1; // 正确
x = 'foo'; // 正确
x = true; // 正确

 TypeScript 认为,只要开发者使用了any类型,就表示开发者想要自己来处理这些代码,所以就不对any类型进行任何限制,怎么使用都可以。

从集合论的角度看,any类型可以看成是所有其他类型的全集,包含了一切可能的类型。TypeScript 将这种类型称为“顶层类型”(top type),意为涵盖了所有下层。

1.1.1 类型推断问题


对于开发者没有指定类型、TypeScript 必须自己推断类型的那些变量,如果无法推断出类型,TypeScript 就会认为该变量的类型是any。

function add(x, y) {
  return x + y;
}
 
add(1, [1, 2, 3]) // 不报错

上面示例中,函数add()的参数变量x和y,都没有足够的信息,TypeScript 无法推断出它们的类型,就会认为这两个变量和函数返回值的类型都是any。以至于后面就不再对函数add()进行类型检查了,怎么用都可以。这显然是很糟糕的情况,所以对于那些类型不明显的变量,一定要显式声明类型,防止被推断为any。

    当声明的时候没有赋值,没给类型也不会报错,所以在声明的同时要赋值,不然会存在安全隐患

1.1.2污染问题


ny类型除了关闭类型检查,还有一个很大的问题,就是它会“污染”其他变量。它可以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错

let x:any = 'hello';
let y:number;
 
y = x; // 不报错
 
y * 123 // 不报错
y.toFixed() // 不报错

上面示例中,变量x的类型是any,实际的值是一个字符串。变量y的类型是number,表示这是一个数值变量,但是它被赋值为x,这时并不会报错。然后,变量y继续进行各种数值运算,TypeScript 也检查不出错误,问题就这样留到运行时才会暴露。

污染其他具有正确类型的变量,把错误留到运行时,这就是不宜使用any类型的另一个主要原因。

2.unknown


与any含义相同,表示类型不确定,可能是任意类型,但是它的使用有一些限制,不像any那样自由,可以视为严格版的any

unknown类型跟any类型的不同之处在于,它不能直接使用。主要有以下几个限制:

1.unknown类型的变量,不能直接赋值给其他类型的变量(除了any类型和unknown类型)

let v:unknown = 123;
 
let v1:boolean = v; // 报错
let v2:number = v; // 报错

 2. 不能直接调用unknown类型变量的方法和属性。

let v1:unknown = { foo: 123 };
v1.foo  // 报错
 
let v2:unknown = 'hello';
v2.trim() // 报错
 
let v3:unknown = (n = 0) => n + 1;
v3() // 报错

3. unknown类型变量能够进行的运算是有限的,只能进行比较运算(运算符=====!=!==||&&?)、取反运算(运算符!)、typeof运算符和instanceof运算符这几种,其他运算都会报错

let a:unknown = 1;
 
a + 1 // 报错
a === 1 // 正确

4. 经过“类型缩小”,unknown类型变量才可以使用。所谓“类型缩小”,就是缩小unknown变量的类型范围,确保不会出错。

let a:unknown = 1;
 
if (typeof a === 'number') {
  let r = a + 10; // 正确
}

 unknown可以看作是更安全的any。一般来说,凡是需要设为any类型的地方,通常都应该优先考虑设为unknown类型。也视为所有其他类型(除了any)的全集,所以它和any一样,也属于 TypeScript 的顶层类型。

3.never


由于不存在任何属于“空类型”的值,所以该类型被称为never,即不可能有这样的值。

never类型的使用场景,主要是在一些类型运算之中,保证类型运算的完整性。另外,不可能返回值的函数,返回值的类型就可以写成never

如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于never类型。
 

function fn(x:string|number) {
  if (typeof x === 'string') {
    // ...
  } else if (typeof x === 'number') {
    // ...
  } else {
    x; // never 类型
  }
}

never类型的一个重要特点是,可以赋值给任意其他类型。

function f():never {
  throw new Error('Error');
}
 
let v1:number = f(); // 不报错
let v2:string = f(); // 不报错
let v3:boolean = f(); // 不报错

上面示例中,函数f()会抛错,所以返回值类型可以写成never,即不可能返回任何值。各种其他类型的变量都可以赋值为f()的运行结果(never类型)

2.类型系统


1.基本类型(继承js基本数据类型)


boolean
string
number
bigint
symbol
object
undefined
null
注意:

1.上面所有类型的名称都是小写字母,首字母大写的Number、String、Boolean等在 JavaScript 语言中都是内置对象,而不是类型名称

2.如果没有声明类型的变量,被赋值为undefined或null,它们的类型会被推断为any,如果希望避免这种情况,则需要打开编译选项strictNullChecks

2.包装对象类型


TypeScript 对五种原始类型分别提供了大写和小写两种类型:

Boolean 和 boolean
String 和 string
Number 和 number
BigInt 和 bigint
Symbol 和 symbol
大写类型同时包含包装对象和字面量两种情况,小写类型只包含字面量,不包含包装对象

const s1:String = 'hello'; // 正确
const s2:String = new String('hello'); // 正确
 
const s3:string = 'hello'; // 正确
const s4:string = new String('hello'); // 报错

String类型可以赋值为字符串的字面量,也可以赋值为包装对象。但是,string类型只能赋值为字面量,赋值为包装对象就会报错。(只使用小写类型,不使用大写类型)

3.Object 类型与 object 类型


3.1Oject类型


大写的Object类型代表 JavaScript 语言里面的广义对象。所有可以转成对象的值,都是Object类型,这囊括了几乎所有的值。(原始类型值、对象、数组、函数都是合法的Object类型)

let obj:Object;//与let obj:{}一样,只不过后者是简写
 
obj = true;
obj = 'hi';
obj = 1;
obj = { foo: 123 };
obj = [1, 2];
obj = (a:number) => a + 1;

除了undefinednull这两个值不能转为对象,其他任何值都可以赋值给Object类型

let obj:Object;
 
obj = undefined; // 报错
obj = null; // 报错

 3.2object类型

小写的object类型代表 JavaScript 里面的狭义对象,即可以用字面量表示的对象(包含对象、数组和函数,不包括原始类型的值)

let obj:object;
 
obj = { foo: 123 };
obj = [1, 2];
obj = (a:number) => a + 1;
obj = true; // 报错
obj = 'hi'; // 报错
obj = 1; // 报错

 大多数时候,我们使用对象类型,只希望包含真正的对象,不希望包含原始类型。所以,建议总是使用小写类型object,不使用大写类型Object

注意,无论是大写的Object类型,还是小写的object类型,都只包含 JavaScript 内置对象原生的属性和方法,用户自定义的属性和方法都不存在于这两个类型之中

4.undefined和null的特殊性


undefined和null既是值,又是类型

作为值,它们有一个特殊的地方:任何其他类型的变量都可以赋值为undefined或null。

JavaScript 的行为是,变量如果等于undefined就表示还没有赋值,如果等于null就表示值为空。所以,TypeScript 就允许了任何类型的变量都可以赋值为这两个值。

let age:number = 24;
 
age = null;      // 报错
age = undefined; // 报错

上面示例中,打开--strictNullChecks以后,number类型的变量age就不能赋值为undefinednull

这个选项在配置文件tsconfig.json的写法如下。

{
  "compilerOptions": {
    "strictNullChecks": true
    // ...
  }
}

 打开strictNullChecks以后,undefinednull这两种值也不能互相赋值了。undefinednull只能赋值给自身,或者any类型和unknown类型的变量

let x:any     = undefined;
let y:unknown = null;

5.联合类型

联合类型(union types)指的是多个类型组成的一个新类型,使用符号|表示。

联合类型A|B表示,任何一个类型只要属于AB,就属于联合类型A|B

let x:string|number;
 
x = 123; // 正确
x = 'abc'; // 正确

6.交叉类型

交叉类型(intersection types)指的多个类型组成的一个新类型,使用符号&表示

交叉类型的主要用途是表示对象的合成:

let obj:
  { foo: string } &
  { bar: string };
 
obj = {
  foo: 'hello',
  bar: 'world'
};

上面示例中,变量obj同时具有属性foo和属性bar

交叉类型常常用来为对象类型添加新属性。

type A = { foo: number };
 
type B = A & { bar: number };

上面示例中,类型B是一个交叉类型,用来在A的基础上增加了属性bar

7.type命令

type命令用来定义一个类型的别名。

type Age = number;
 
let age:Age = 55;

 上面示例中,type命令为number类型定义了一个别名Age。这样就能像使用number一样,使用Age作为类型。

8.typeof运算符

TypeScript 将typeof运算符移植到了类型运算,它的操作数依然是一个值,但是返回的不是字符串,而是该值的 TypeScript 类型。

const a = { x: 0 };
 
type T0 = typeof a;   // { x: number }
type T1 = typeof a.x; // number

上面示例中,typeof a表示返回变量a的 TypeScript 类型({ x: number })。同理,typeof a.x返回的是属性x的类型(number)。

这种用法的typeof返回的是 TypeScript 类型,所以只能用在类型运算之中(即跟类型相关的代码之中),不能用在值运算

type T = typeof Date(); // 报错

上面示例会报错,原因是 typeof 的参数不能是一个值的运算式,而Date()需要运算才知道结果。

另外,typeof命令的参数不能是类型。

type Age = number;
type MyAge = typeof Age; // 报错

上面示例中,Age是一个类型别名,用作typeof命令的参数就会报错。

typeof 是一个很重要的 TypeScript 运算符,有些场合不知道某个变量foo的类型,这时使用typeof foo就可以获得它的类型。

9.块级类型声明


TypeScript 支持块级类型声明,即类型可以声明在代码块(用大括号表示)里面,并且只在当前代码块有效

if (true) {
  type T = number;
  let v:T = 5;
} else {
  type T = string;
  let v:T = 'hello';
}

  10.类型的兼容

type T = number|string;
 
let a:number = 1;
let b:T = a;

变量ab的类型是不一样的,但是变量a赋值给变量b并不会报错。这时,我们就认为,b的类型兼容a的类型。

如果类型A的值可以赋值给类型B,那么类型A就称为类型B的子类型(subtype)。在上例中,类型number就是类型number|string的子类型

let a:'hi' = 'hi';
let b:string = 'hello';
 
b = a; // 正确
a = b; // 报错

上面示例中,histring的子类型,stringhi的父类型。所以,变量a可以赋值给变量b,但是反过来就会报错。

子类型继承了父类型的所有特征,所以可以用在父类型的场合。但是,子类型还可能有一些父类型没有的特征,所以父类型不能用在子类型的场合。

3.数组

TypeScript 数组有一个根本特征:所有成员的类型必须相同,但是成员数量是不确定的,可以是无限数量的成员,也可以是零成员

声明的方式:
 

let arr:number[] = [1, 2, 3];
let arr:(number|string)[];

let arr:Array<number> = [1, 2, 3];

 数组类型声明了以后,成员数量是不限制的,任意数量的成员都可以,也可以是空数组。

let arr:number[];
arr = [];
arr = [1];
arr = [1, 2];
arr = [1, 2, 3];
 
 
let arr1:number[] = [1, 2, 3];
 
arr1[3] = 4;
arr1.length = 2;
 
arr1// [1, 2]

上面示例中,数组arr无论有多少个成员,都是正确的。

这种规定的隐藏含义就是,数组的成员是可以动态变化的,数组增加成员或减少成员,都是可以的。

1.类型推断


如果数组变量没有声明类型,TypeScript 就会推断数组成员的类型。这时,推断行为会因为值的不同,而有所不同。后面,为这个数组赋值时,TypeScript 会自动更新类型推断。

const arr = [];
arr // 推断为 any[]
 
arr.push(123);
arr // 推断类型为 number[]
 
arr.push('abc');
arr // 推断类型为 (string|number)[]

但是,类型推断的自动更新只发生初始值为空数组的情况。如果初始值不是空数组,类型推断就不会更新。

// 推断类型为 number[]
const arr = [123];
 
arr.push('abc'); // 报错

 上面示例中,数组变量arr的初始值是[123],TypeScript 就推断成员类型为number。新成员如果不是这个类型,TypeScript 就会报错,而不会更新类型推断。

2.只读数组,const断言

TypeScript 允许声明只读数组,方法是在数组类型前面加上readonly关键字。arr是一个只读数组,删除、修改、新增数组成员都会报错。

const arr:readonly number[] = [0, 1];
 
arr[1] = 2; // 报错
arr.push(3); // 报错
delete arr[0]; // 报错

TypeScript 将readonly number[]number[]视为两种不一样的类型,后者是前者的子类型。

所以子类型number[]可以用于所有使用父类型的场合,反过来就不行。

let a1:number[] = [0, 1];
let a2:readonly number[] = a1; // 正确
 
a1 = a2; // 报错

 只读数组还有一种声明方法,就是使用“const 断言”。
 

const arr = [0, 1] as const;
 
arr[0] = [2]; // 报错 

上面示例中,as const告诉 TypeScript,推断类型时要把变量arr推断为只读数组,从而使得数组成员无法改变。

3.多维数组

var multi:number[][] =
  [[1,2,3], [23,24,25]];

4.元组

它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同

数组的成员类型写在方括号外面(number[]),元组的成员类型是写在方括号里面([number]

// 数组
let a:number[] = [1];
 
// 元组
let t:[number] = [1];

 使用元组时,必须明确给出类型声明(上例的[number]),不能省略,否则 TypeScript 会把一个值自动推断为数组。

// a 的类型被推断为 (number | boolean)[]
let a = [1, true];

元组成员的类型可以添加问号后缀(?),表示该成员是可选的。所有可选成员必须在必选成员之后。

let a:[number, number?] = [1];

由于需要声明每个成员的类型,所以大多数情况下,元组的成员数量是有限的,从类型声明就可以明确知道,元组包含多少个成员,越界的成员会报错。

let x:[string, string] = ['a', 'b'];
 
x[2] = 'c'; // 报错

但是,使用扩展运算符...),可以表示不限成员数量的元组。

type NamedNums = [
  string,
  ...number[]
];
 
const a:NamedNums = ['A', 1, 2];
const b:NamedNums = ['B', 1, 2, 3];

1.只读元组

元组也可以是只读的,不允许修改,有两种写法。

// 写法一
type t = readonly [number, string]
 
// 写法二
type t = Readonly<[number, string]>

  跟数组一样,只读元组是元组的父类型。所以,元组可以替代只读元组,而只读元组不能替代元组。

type t1 = readonly [number, number];
type t2 = [number, number];
 
let x:t2 = [1, 2];
let y:t1 = x; // 正确
 
x = y; // 报错

2、成员数量推断

function f(point: [number, number]) {
  if (point.length === 3) {  // 报错
    // ...
  }
}

上面示例会报错,原因是 TypeScript 发现元组point的长度是2,不可能等于3,这个判断无意义

如果包含了可选成员,TypeScript 会推断出可能的成员数量。

function f(
  point:[number, number?, number?]
) {
  if (point.length === 4) {  // 报错
    // ...
  }
}

如果使用了扩展运算符,TypeScript 就无法推断出成员数量。

const myTuple:[...string[]]
  = ['a', 'b', 'c'];
 
if (myTuple.length === 4) { // 正确
  // ...
}

一旦扩展运算符使得元组的成员数量无法推断,TypeScript 内部就会把该元组当成数组处理

 3.扩展运算符与成员数量

如果函数调用时,使用扩展运算符传入函数参数,可能发生参数数量与数组长度不匹配的报错。

const arr = [1, 2];
 
function add(x:number, y:number){
  // ...
}
 
add(...arr) // 报错

 解决这个问题的一个方法,就是把成员数量不确定的数组,写成成员数量确定的元组,再使用扩展运算符。

const arr:[number, number] = [1, 2];
 
function add(x:number, y:number){
  // ...
}
 
add(...arr) // 正确

5.symbol类型

Symbol 值通过Symbol()函数生成。在 TypeScript 里面,Symbol 的类型使用symbol表示。

let x:symbol = Symbol();
let y:symbol = Symbol();
 
x === y // false

上面示例中,变量xy的类型都是symbol,且都用Symbol()生成,但是它们是不相等的。

1.unique symbol

symbol的一个子类型unique symbol,它表示单个的、某个具体的 Symbol 值。

因为unique symbol表示单个值,所以这个类型的变量是不能修改值的,只能用const命令声明,不能用let声明。

// 正确
const x:unique symbol = Symbol();
 
// 报错
let y:unique symbol = Symbol();
 
const x:unique symbol = Symbol();
// 等同于
const x = Symbol();

 每个声明为unique symbol类型的变量,它们的值都是不一样的,其实属于两个值类型。

const a:unique symbol = Symbol();
const b:unique symbol = Symbol();
 
a === b // 报错

变量ab是两个类型,就不能把一个赋值给另一个。

const a:unique symbol = Symbol();
const b:unique symbol = a; // 报错

如果要写成与变量a同一个unique symbol值类型,只能写成类型为typeof a

const a:unique symbol = Symbol();
const b:typeof a = a; // 正确

unique symbol 类型是 symbol 类型的子类型,所以可以将前者赋值给后者,但是反过来就不行。

const a:unique symbol = Symbol();
 
const b:symbol = a; // 正确
 
const c:unique symbol = b; // 报错

 2.类型推断

let命令声明的变量,推断类型为 symbol。

// 类型为 symbol
let x = Symbol();

const命令声明的变量,推断类型为 unique symbol。

// 类型为 unique symbol
const x = Symbol();

但是,const命令声明的变量,如果赋值为另一个 symbol 类型的变量,则推断类型为 symbol。

let x = Symbol();
 
// 类型为 symbol
const y = x;

let命令声明的变量,如果赋值为另一个 unique symbol 类型的变量,则推断类型还是 symbol。

const x = Symbol();
 
// 类型为 symbol
let y = x;

6.函数类型

数的类型声明,需要在声明函数时,给出参数的类型和返回值的类型。

function hello(
  txt:string
):void {
  console.log('hello ' + txt);
}

上面示例中,函数hello()在声明时,需要给出参数txt的类型(string),以及返回值的类型(void),后者写在参数列表的圆括号后面。void类型表示没有返回值(返回值的类型通常可以不写,因为 TypeScript 自己会推断出来。)

如果变量被赋值为一个函数,变量的类型有两种写法。

// 写法一
const hello = function (txt:string) {
  console.log('hello ' + txt);
}
 
// 写法二
const hello:
  (txt:string) => void
= function (txt) {
  console.log('hello ' + txt);
};

如果函数的类型定义很冗长,或者多个函数使用同一种类型,写法二用起来就很麻烦。因此,往往用type命令为函数类型定义一个别名,便于指定给其他变量。

type MyFunc = (txt:string) => void;
 
const hello:MyFunc = function (txt) {
  console.log('hello ' + txt);
};

函数的实际参数个数,可以少于类型指定的参数个数,但是不能多于,即 TypeScript 允许省略参数。

let myFunc:
  (a:number, b:number) => number;
 
myFunc = (a:number) => a; // 正确
 
myFunc = (
  a:number, b:number, c:number
) => a + b + c; // 报错

如果一个变量要套用另一个函数类型,有一个小技巧,就是使用typeof运算符。

function add(
  x:number,
  y:number
) {
  return x + y;
}
 
const myAdd:typeof add = function (x, y) {
  return x + y;
}

1.Function类型

TypeScript 提供 Function 类型表示函数,任何函数都属于这个类型。(不建议这样使用,不然传入和传出的都是any类型)

function doSomething(f:Function) {
  return f(1, 2, 3);
}

2.箭头函数

箭头函数是普通函数的一种简化写法,它的类型写法与普通函数类似。

const repeat = (
  str:string,
  times:number
):string => str.repeat(times);

看一个例子。

type Person = { name: string };
 
const people = ['alice', 'bob', 'jan'].map(
  (name):Person => ({name})
);

上面示例中,Person是一个类型别名,代表一个对象,该对象有属性name。变量people是数组的map()方法的返回值。

map()方法的参数是一个箭头函数(name):Person => ({name}),该箭头函数的参数name的类型省略了,因为可以从map()的类型定义推断出来,箭头函数的返回值类型为Person。相应地,变量people的类型是Person[]。

至于箭头后面的({name}),表示返回一个对象,该对象有一个属性name,它的属性值为变量name的值。这里的圆括号是必须的,否则(name):Person => {name}的大括号表示函数体,即函数体内有一行语句name,同时由于没有return语句,这个函数不会返回任何值。

3.可选参数

如果函数的某个参数可以省略,则在参数名后面加问号表示。(参数名带有问号,表示该参数的类型实际上是原始类型|undefined,它有可能为undefined

function f(x?:number) {
  // ...
}
 
f(); // OK
f(10); // OK

函数体内部用到可选参数时,需要判断该参数是否为undefined

let myFunc:
  (a:number, b?:number) => number; 
 
myFunc = function (x, y) {
  if (y === undefined) {
    return x;
  }
  return x + y;
}

上面示例中,由于函数的第二个参数为可选参数,所以函数体内部需要判断一下,该参数是否为空。

4.参数默认值

设置了默认值的参数,就是可选的。如果不传入该参数,它就会等于默认值。(可选参数与默认值不能同时使用。)

function createPoint(
  x:number = 0,
  y:number = 0
):[number, number] {
  return [x, y];
}
 
createPoint() // [0, 0]

具有默认值的参数如果不位于参数列表的末尾,调用时不能省略,如果要触发默认值,必须显式传入undefined

function add(
  x:number = 0,
  y:number
) {
  return x + y;
}
 
add(1) // 报错
add(undefined, 1) // 正确

 5.参数解构

函数参数如果存在变量解构,类型写法如下。

function f(
  [x, y]: [number, number]
) {
  // ...
}
 
function sum(
  { a, b, c }: {
     a: number;
     b: number;
     c: number
  }
) {
  console.log(a + b + c);
}

参数解构可以结合类型别名(type 命令)一起使用,代码会看起来简洁一些。

type ABC = { a:number; b:number; c:number };
 
function sum({ a, b, c }:ABC) {
  console.log(a + b + c);
}

6.rest参数

rest 参数表示函数剩余的所有参数,它可以是数组(剩余参数类型相同),也可能是元组(剩余参数类型不同)。

// rest 参数为数组
function joinNumbers(...nums:number[]) {
  // ...
}
 
// rest 参数为元组
function f(...args:[boolean, number]) {
  // ...
}

下面是一个 rest 参数的例子。

function multiply(n:number, ...m:number[]) {
  return m.map((x) => n * x);
}

上面示例中,参数m就是 rest 类型,它的类型是一个数组。

rest 参数甚至可以嵌套。

function f(...args:[boolean, ...string[]]) {
  // ...
}

rest 参数可以与变量解构结合使用。

function repeat(
  ...[str, times]: [string, number]
):string {
  return str.repeat(times);
}
 
// 等同于
function repeat(
  str: string,
  times: number
):string {
  return str.repeat(times);
}

 7.readonly只读参数

如果函数内部不能修改某个参数,可以在函数定义时,在参数类型前面加上readonly关键字,表示这是只读参数。

function arraySum(
  arr:readonly number[]
) {
  // ...
  arr[0] = 0; // 报错
}

8.void类型


void 类型表示函数没有返回值。(如果返回其他值,就会报错,但是允许返回null和undefined,如果打开了strictNullChecks编译选项,那么 void 类型只允许返回undefined。如果返回null,就会报错。这是因为 JavaScript 规定,如果函数没有返回值,就等同于返回undefined。)

function f():void {
  console.log('hello');
}

 需要特别注意的是,如果变量、对象方法、函数参数的类型是 void 类型的函数,那么并不代表不能赋值为有返回值的函数。恰恰相反,该变量、对象方法和函数参数可以接受返回任意值的函数,这时并不会报错。

type voidFunc = () => void;
 
const f:voidFunc = () => {
  return 123;
};

这是因为,这时 TypeScript 认为,这里的 void 类型只是表示该函数的返回值没有利用价值,或者说不应该使用该函数的返回值。只要不用到这里的返回值,就不会报错。

const src = [1, 2, 3];
const ret = [];
 
src.forEach(el => ret.push(el));

上面示例中,push()有返回值,表示新插入的元素在数组里面的位置。但是,对于forEach()方法来说,这个返回值是没有作用的,根本用不到,所以 TypeScript 不会报错。

9.never类型

never类型表示肯定不会出现的值。它用在函数的返回值,就表示某个函数肯定不会返回值,即函数不会正常执行结束。

它主要有以下两种情况。

(1)抛出错误的函数。

function fail(msg:string):never {
  throw new Error(msg);
}

上面示例中,函数fail()会抛错,不会正常退出,所以返回值类型是never

注意,只有抛出错误,才是 never 类型。如果显式用return语句返回一个 Error 对象,返回值就不是 never 类型。

function fail():Error {
  return new Error("Something failed");
}

上面示例中,函数fail()返回一个 Error 对象,所以返回值类型是 Error。

(2)无限执行的函数。

const sing = function():never {
  while (true) {
    console.log('sing');
  }
};

注意,never类型不同于void类型。前者表示函数没有执行结束,不可能有返回值;后者表示函数正常执行结束,但是不返回值,或者说返回undefined

如果程序中调用了一个返回值类型为never的函数,那么就意味着程序会在该函数的调用位置终止,永远不会继续执行后续的代码。

10.局部类型

函数内部允许声明其他类型,该类型只在函数内部有效,称为局部类型。

function hello(txt:string) {
  type message = string;
  let newTxt:message = 'hello ' + txt;
  return newTxt;
}
 
const newTxt:message = hello('world'); // 报错

上面示例中,类型message是在函数hello()内部定义的,只能在函数内部使用。在函数外部使用,就会报错。

11.函数重载
有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为。这种根据参数类型不同,执行不同逻辑的行为,称为函数重载(function overload)。

reverse('abc') // 'cba'
reverse([1, 2, 3]) // [3, 2, 1]

 分别对函数reverse()的两种参数情况,给予了类型声明。但是,到这里还没有结束,后面还必须对函数reverse()给予完整的类型声明。

function reverse(str:string):string;
function reverse(arr:any[]):any[];
function reverse(
  stringOrArray:string|any[]
):string|any[] {
  if (typeof stringOrArray === 'string')
    return stringOrArray.split('').reverse().join('');
  else
    return stringOrArray.slice().reverse();
}

函数体内部需要判断参数的类型及个数,并根据判断结果执行不同的操作。

function add(
  x:number,
  y:number
):number;
function add(
  x:any[],
  y:any[]
):any[];
function add(
  x:number|any[],
  y:number|any[]
):number|any[] {
  if (typeof x === 'number' && typeof y === 'number') {
    return x + y;
  } else if (Array.isArray(x) && Array.isArray(y)) {
    return [...x, ...y];
  }
 
  throw new Error('wrong parameters');
}

重载声明的排序很重要,因为 TypeScript 是按照顺序进行检查的,一旦发现符合某个类型声明,就不再往下检查了,所以类型最宽的声明应该放在最后面,防止覆盖其他类型声明。

function f(x:any):number;
function f(x:string): 0|1;
function f(x:any):any {
  // ...
}
 
const a:0|1 = f('hi'); // 报错

上面声明中,第一行类型声明x:any范围最宽,导致函数f()的调用都会匹配这行声明,无法匹配第二行类型声明,所以最后一行调用就报错了,因为等号两侧类型不匹配,左侧类型是0|1,右侧类型是number。这个函数重载的正确顺序是,第二行类型声明放到第一行的位置

7.对象

1.可选属性

如果某个属性是可选的(即可以忽略),需要在属性名后面加一个问号。(可选属性等同于允许赋值为undefined

const obj: {
  x: number;
  y?: number;
} = { x: 1 };

 

/ 写法一
let firstName = (user.firstName === undefined)
  ? 'Foo' : user.firstName;
let lastName = (user.lastName === undefined)
  ? 'Bar' : user.lastName;
 
// 写法二
let firstName = user.firstName ?? 'Foo';
let lastName = user.lastName ?? 'Bar';

上面示例中,写法一使用三元运算符?:,判断是否为undefined,并设置默认值。写法二使用 Null 判断运算符??,与写法一的作用完全相同。

TypeScript 提供编译设置ExactOptionalPropertyTypes,只要同时打开这个设置和strictNullChecks,可选属性就不能设为undefined

// 打开 ExactOptionsPropertyTypes 和 strictNullChecks
const obj: {
  x: number;
  y?: number;
} = { x: 1, y: undefined }; // 报错

 

上面示例中,打开了这两个设置以后,可选属性就不能设为undefined了。

注意,可选属性与允许设为undefined的必选属性是不等价的。

type A = { x:number, y?:number };
type B = { x:number, y:number|undefined };
 
const ObjA:A = { x: 1 }; // 正确
const ObjB:B = { x: 1 }; // 报错

上面示例中,属性y如果是一个可选属性,那就可以省略不写;如果是允许设为undefined的必选属性,一旦省略就会报错,必须显式写成{ x: 1, y: undefined }

2.只读属性

属性名前面加上readonly关键字,表示这个属性是只读属性,不能修改。

interface MyInterface {
  readonly prop: number;
}

注意,如果属性值是一个对象,readonly修饰符并不禁止修改该对象的属性,只是禁止完全替换掉该对象。

interface Home {
  readonly resident: {
    name: string;
    age: number
  };
}
 
const h:Home = {
  resident: {
    name: 'Vicky',
    age: 42
  }
};
 
h.resident.age = 32; // 正确
h.resident = {
  name: 'Kate',
  age: 23 
} // 报错

如果希望属性值是只读的,除了声明时加上readonly关键字,还有一种方法,就是在赋值时,在对象后面加上只读断言a

const myUser = {
  name: "Sabrina",
} as const;
 
myUser.name = "Cynthia"; // 报错

注意,上面的as const属于 TypeScript 的类型推断,如果变量明确地声明了类型,那么 TypeScript 会以声明的类型为准。

const myUser:{ name: string } = {
  name: "Sabrina",
} as const;
 
myUser.name = "Cynthia"; // 正确

3.属性名的索引类型 

type MyObj = {
  [property: string]: string
};
 
const obj:MyObj = {
  foo: 'a',
  bar: 'b',
  baz: 'c',
};

上面示例中,类型MyObj的属性名类型就采用了表达式形式,写在方括号里面。[property: string]的property表示属性名,这个是可以随便起的,它的类型是string,即属性名类型为string。也就是说,不管这个对象有多少属性,只要属性名为字符串,且属性值也是字符串,就符合这个类型声明。

4.解构赋值

解构赋值用于直接从对象中提取属性。

const {id, name, price} = product;

上面语句从对象product提取了三个属性,并声明属性名的同名变量。

5.结构类型原则

只要对象 B 满足 对象 A 的结构特征,TypeScript 就认为对象 B 兼容对象 A 的类型,这称为“结构类型”原则(structural typing)。

type A = {
  x: number;
};
 
type B = {
  x: number;
  y: number;
};

 对象A只有一个属性x,类型为number。对象B满足这个特征,因此兼容对象A,只要可以使用A的地方,就可以使用B

const B = {
  x: 1,
  y: 1
};
 
const A:{ x: number } = B; // 正确

8.interface

1.interface对象的5种语法

  • 对象属性
  • 对象的属性索引
  • 对象方法
  • 函数
  • 构造函数

(1)对象属性

interface Point {
  x: number;
  y: number;
}

上面示例中,xy都是对象的属性,分别使用冒号指定每个属性的类型。

(2)对象的属性索引

interface A {
  [prop: string]: number;
}

上面示例中,[prop: string]就是属性的字符串索引,表示属性名只要是字符串,都符合类型要求。

(3)对象的方法

对象的方法共有三种写法。

// 写法一
interface A {
  f(x: boolean): string;
}
 
// 写法二
interface B {
  f: (x: boolean) => string;
}
 
// 写法三
interface C {
  f: { (x: boole

(4)函数

interface 也可以用来声明独立的函数。

interface Add {
  (x:number, y:number): number;
}
 
const myAdd:Add = (x,y) => x + y;

上面示例中,接口Add声明了一个函数类型。

(5)构造函数

interface 内部可以使用new关键字,表示构造函数。

interface ErrorConstructor {
  new (message?: string): Error;
}

 2.interface的继承

2.1.interface继承interface

interface 可以使用extends关键字,继承其他 interface。

interface Shape {
  name: string;
}
 
interface Circle extends Shape {
  radius: number;
}

上面示例中,Circle继承了Shape,所以Circle其实有两个属性nameradius。这时,Circle是子接口,Shape是父接口。

extends关键字会从继承的接口里面拷贝属性类型,这样就不必书写重复的属性。

interface 允许多重继承。

interface Style {
  color: string;
}
 
interface Shape {
  name: string;
}
 
interface Circle extends Style, Shape {
  radius: number;
}

上面示例中,Circle同时继承了StyleShape,所以拥有三个属性colornameradius

多重接口继承,实际上相当于多个父接口的合并

2.2interface继承type

interface 可以继承type命令定义的对象类型。i

type Country = {
  name: string;
  capital: string;
}
 
interface CountryWithPop extends Country {
  population: number;
}

上面示例中,CountryWithPop继承了type命令定义的Country对象,并且新增了一个population属性。

注意,如果type命令定义的类型不是对象,interface 就无法继承。

2.3interface继承class


interface 还可以继承 class,即继承该类的所有成员。

class A {
  x:string = '';
 
  y():boolean {
    return true;
  }
}
 
interface B extends A {
  z: number
}

上面示例中,B继承了A,因此B就具有属性xy()z

实现B接口的对象就需要实现这些属性。

const b:B = {
  x: '',
  y: function(){ return true },
  z: 123
}

上面示例中,对象b就实现了接口B,而接口B又继承了类A

3.接口合并

多个同名接口会合并成一个接口。

interface Box {
  height: number;
  width: number;
}
 
interface Box {
  length: number;
}

 

上面示例中,两个Box接口会合并成一个接口,同时有height、width和length三个属性。

4.interface与type的异同


interface命令与type命令作用类似,都可以表示对象类型。

很多对象类型既可以用 interface 表示,也可以用 type 表示。而且,两者往往可以换用,几乎所有的 interface 命令都可以改写为 type 命令。

它们的相似之处,首先表现在都能为对象类型起名。

type Country = {
  name: string;
  capital: string;
}
 
interface Coutry {
  name: string;
  capital: string;
}

上面示例是type命令和interface命令,分别定义同一个类型。

4.1interface与type的区别:


(1)type能够表示非对象类型,而interface只能表示对象类型(包括数组、函数等)。

(2)interface可以继承其他类型,type不支持继承。

继承的主要作用是添加属性,type定义的对象类型如果想要添加属性,只能使用&运算符,重新定义一个类型。

type Animal = {
  name: string
}
 
type Bear = Animal & {
  honey: boolean
}

 

上面示例中,类型BearAnimal的基础上添加了一个属性honey。上例的&运算符,表示同时具备两个类型的特征,因此可以起到两个对象类型合并的作用。

作为比较,interface添加属性,采用的是继承的写法。

interface Animal {
  name: string
}
 
interface Bear extends Animal {
  honey: boolean
}

继承时,type 和 interface 是可以换用的。interface 可以继承 type。

type Foo = { x: number; };
 
interface Bar extends Foo {
  y: number;
}

type 也可以继承 interface。

interface Foo {
  x: number;
}
 
type Bar = Foo & { y: number; };

(3)同名interface会自动合并,同名type则会报错。也就是说,TypeScript 不允许使用type多次定义同一个类型。

type A = { foo:number }; // 报错
type A = { bar:number }; // 报错

上面示例中,type两次定义了类型A,导致两行都会报错。

(4)interface不能包含属性映射(mapping),type可以

interface Point {
  x: number;
  y: number;
}
 
// 正确
type PointCopy1 = {
  [Key in keyof Point]: Point[Key];
};
 
// 报错
interface PointCopy2 {
  [Key in keyof Point]: Point[Key];
};

(5)this关键字只能用于interface

// 正确
interface Foo {
  add(num:number): this;
};
 
// 报错
type Foo = {
  add(num:number): this;
};

6)type 可以扩展原始数据类型,interface 不行。

// 正确
type MyStr = string & {
  type: 'new'
};
 
// 报错
interface MyStr extends string {
  type: 'new'
}

(7)interface无法表达某些复杂类型(比如交叉类型和联合类型),但是type可以。

type A = { /* ... */ };
type B = { /* ... */ };
 
type AorB = A | B;
type AorBwithName = AorB & {
  name: string
};

上面示例中,类型AorB是一个联合类型,AorBwithName则是为AorB添加一个属性。这两种运算,interface都没法表达。

综上所述,如果有复杂的类型运算,那么没有其他选择只能使用type;一般情况下,interface灵活性比较高,便于扩充类型或自动合并,建议优先使用

Vue 3 是一套用于构建用户界面的渐进式 JavaScript 框架,而 TypeScript 是一种静态类型检查的超集,可以与 Vue 3 配合使用,提供更强大的开发工具和编辑器支持。下面是 Vue 3 和 TypeScript 的基础知识: 1. 安装 Vue 3 和 TypeScript:你可以使用 npm 或者 yarn 来安装 Vue 3 和 TypeScript。安装 Vue 3 的命令是:`npm install vue@next`,安装 TypeScript 的命令是:`npm install --save-dev typescript`。 2. 创建 Vue 3 的项目:在安装完 Vue 3 和 TypeScript 后,你可以使用 Vue CLI 来创建一个新的 Vue 3 项目。使用命令 `vue create my-project` 创建一个新的项目,并选择 TypeScript 作为预设。 3. 使用 TypeScript 的组件:在 Vue 3 中,你可以使用 TypeScript 来为组件提供类型检查。你可以通过为组件定义 props 的类型、使用装饰器来注解组件选项、以及为组件的 data、methods、computed 等属性添加类型注解。 4. 单文件组件中的 TypeScript:在单文件组件中,你可以使用 `<script lang="ts">` 标签来指定 TypeScript。这样你就可以在单文件组件中编写 TypeScript 代码,并享受类型检查的好处。 5. 类型推断和声明:TypeScript 可以根据上下文自动推断变量的类型,但有时你可能需要手动声明类型。你可以使用 `:type` 语法来为变量、函数参数、函数返回值等声明类型。 6. 使用 Vue 3 的特性:Vue 3 提供了一些新的特性,比如 Composition API、Teleport、Suspense 等。在使用这些特性时,你可以使用 TypeScript 来提供类型检查和自动补全。 这只是 Vue 3 和 TypeScript 的一些基础知识,希望对你有所帮助!如果你有进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值