Typescript 基础类型 | 类型断言

TypeScript 由微软开发的自由和开源的编程语言,它是JavaScript的一个超集,扩展了JavaScript的语法。

编译 ts, 命令行执行: tsc greeter.ts

| 基础类型 Basic Types

// boolean
let isDone: boolean = false;

// number
let decimal: number = 6;
let hex: number = 0xf00d;  // 十六进制
let binary: number = 0b1010;
let octal: number = 0o744;  // 八进制

// string
let color: string = "blue";
color = 'red';


// Any 任意类型- 为不确定类型的变量指定类型
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

let notSure: any = 4; // any 类型允许在编译时可选择地包含或移除类型检查。可赋任意值,可调用任意方法
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)

let prettySure: Object = 4; // Object 类型只允许赋任意值,但不能调用任意的方法,即便真有这些方法
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.

let list: any[] = [1, true, "free"]; // an array has a mix of different types
list[1] = 100;

// Void 没有任何类型
function warnUser(): void {   // do not return a value (函数没有返回值时)
    console.log("This is my warning message");
}
let unusable: void = undefined; // variables of type void can only assign undefined or null
// void 类型的变量只能赋值为 undefined 和 null,其他类型不能赋值给 void 类型的变量。

// Never  represents the type of values that never occur.
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}
function infiniteLoop(): never {
    while (true) {
    }
}
// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// unknown 类型的值不是可以随便操作的

// Object
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

// Type assertions 类型断言
let someValue: any = "this is a string";  // 'angle-bracket' syntax
let strLength: number = (<string>someValue).length;

let someValue: any = "this is a string";  // as-syntax
let strLength: number = (someValue as string).length;

Type annotations

function greeter(person: string) {
    return "Hello, " + person;
}

let user = [0, 1, 2];

document.body.innerHTML = greeter(user);

greeter 需要一个字符串类型参数,当传了数组,报错如下:

error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.

数组

// 两种定义数组的方式:
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];
// 当你使用第二种形式定义时,tslint 可能会警告让你使用第一种形式定义,如果你就是想用第二种形式,可以通过在 tslint.json 的 rules 中加入"array-type": [false]关闭 tslint 对这条的检测。

元组


// Tuple 元组 值与类型一一对应,在 2.6 及之前版本中当有越界元素出现,使用联合类型代替;2.6 之后的版本要求元组赋值必须类型和个数都对应。
let x: [string, number];   // Declare a tuple type
x = ["hello", 10]; // OK  // Initialize it
x = [10, "hello"]; // Error  // Initialize it incorrectly
console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
x[3] = "world";  // OK, 'string' can be assigned to 'string | number'
console.log(x[5].toString());  // OK, 'string' and 'number' both have 'toString'
x[6] = true;  // Error, 'boolean' isn't 'string | number'

// 数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
// let tuple: [string, number]元组类型的声明效果上可以看做等同于下面的声明:
interface Tuple extends Array<number | string> {
  0: string;
  1: number;
  length: 2;
}

枚举

数字枚举


// Enum 枚举 便利:可以由枚举的值得到名字
enum Color {Red, Green, Blue}
let c: Color = Color[1];
console.log(c);  // Green  index 默认从 0 开始 0 1 2

enum Color {Red=1, Green, Blue}
let c: Color = Color[1];
console.log(c);  // Red  index 指定从 1 开始 1 2 3

enum Color {Red=1, Green=3, Blue=5}
let c: Color = Color[1];
console.log(c);  // Red  指定 index  Color[3] = Green; Color[5] = Blue; Color[2] = undefined

const superAdmin = Color.Green;
console.log(superAdmin); // 3

// 指定部分字段,其他使用默认递增索引
enum Status {
  Ok = 200,
  Created,
  Accepted,
  BadRequest = 400,
  Unauthorized
}
console.log(Status.Created, Status.Accepted, Status.Unauthorized); // 201 202 401

// 数字枚举在定义值的时候,可以使用计算值和常量。但是要注意,如果某个字段使用了计算值或常量,那么该字段后面紧接着的字段必须设置初始值,这里不能使用默认的递增值了

const getValue = () => {
  return 0;
};
enum ErrorIndex {
  a = getValue(),
  b, // error 枚举成员必须具有初始化的值
  c
}
enum RightIndex {
  a = getValue(),
  b = 1,
  c
}
const Start = 1;
enum Index {
  a = Start,
  b, // error 枚举成员必须具有初始化的值
  c

反向映射

定义一个枚举值的时候,可以通过 Enum[‘key’]或者 Enum.key 的形式获取到对应的值 value。TypeScript 还支持反向映射,但是反向映射只支持数字枚举,字符串枚举是不支持的。

enum Status {
  Success = 200,
  NotFound = 404,
  Error = 500
}
console.log(Status["Success"]); // 200
console.log(Status[200]); // 'Success'
console.log(Status[Status["Success"]]); // 'Success'

字符串枚举

字符串枚举,字符串枚举值要求每个字段的值都必须是字符串字面量,或者是该枚举值中另一个字符串枚举成员

enum Message {
  Error = "Sorry, error",
  Success = "Hoho, success"
}
console.log(Message.Error); // 'Sorry, error'

// 枚举值中其他枚举成员的例子
enum Message {
  Error = "error message",
  ServerError = Error,
  ClientError = Error
}
console.log(Message.Error); // 'error message'
console.log(Message.ServerError); // 'error message'

异构枚举

异构枚举就是枚举值中成员值既有数字类型又有字符串类型。这种如果不是真的需要,不建议使用。因为往往我们将一类值整理为一个枚举值的时候,它们的特点是相似的。比如我们在做接口请求时的返回状态码,如果是状态码都是数值,如果是提示信息,都是字符串,所以在使用枚举的时候,往往是可以避免使用异构枚举的,重点是做好类型的整理。

enum Result {
  Faild = 0,
  Success = "Success"
}

null 和 undefined

let u: undefined = undefined; // 这里可能会报一个tslint的错误:Unnecessary initialization to ‘undefined’,就是不能给一个值赋undefined,但我们知道这是可以的,所以如果你的代码规范想让这种代码合理化,可以配置tslint,将”no-unnecessary-initializer”设为 false 即可

默认情况下 undefined 和 null 可以赋值给任意类型的值,也就是说你可以把 undefined 赋值给 void 类型,也可以赋值给 number 类型。当你在 tsconfig.json 的”compilerOptions”里设置了”strictNullChecks”: true时,那必须严格对待。undefined 和 null 将只能赋值给它们自身和 void 类型,

// Null and Undefined
let u: undefined = undefined;
let n: null = null;
// 当指定--strictNullChecks标记,null和undefined只能赋值给void和它们各自。
// 当要传入一个 string或null或undefined,可使用联合类型string | null | undefined。

symbol

Symbol 前面不能加new关键字,直接调用即可创建一个独一无二的 symbol 类型的值。

const s = Symbol();
typeof s; // 'symbol'

const s1 = Symbol("lison");
const s2 = Symbol("lison");
console.log(s1 === s2); // false

// Symbol 不可以和其他类型的值进行运算,但是可以转为字符串和布尔类型值:
let s = Symbol("lison");
console.log(s.toString()); // 'Symbol(lison)'
console.log(Boolean(s)); // true
console.log(!s); // false

Symbol 可作为属性名,但这个属性不会被for…in遍历到,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()获取到:

const name = Symbol("name");
const obj = {
  [name]: "lison",
  age: 18
};
for (const key in obj) {
  console.log(key);
}
// => 'age'
console.log(Object.keys(obj));
// ['age']
console.log(Object.getOwnPropertyNames(obj));
// ['age']
console.log(JSON.stringify(obj));
// '{ "age": 18 }'

虽然这么多方法都无法遍历和访问到 Symbol 类型的属性名,但是 Symbol 类型的属性并不是私有属性。我们可以使用Object.getOwnPropertySymbols方法获取对象的所有symbol类型的属性名。 还可以使用ES6 新提供的 Reflect 对象的静态方法Reflect.ownKeys,它可以返回所有类型的属性名,所以 Symbol 类型的也会返回。

// 1、getOwnPropertySymbols方法
const name = Symbol("name");
const obj = {
  [name]: "lison",
  age: 18
};
const SymbolPropNames = Object.getOwnPropertySymbols(obj);
console.log(SymbolPropNames);
// [ Symbol(name) ]
console.log(obj[SymbolPropNames[0]]);
// 'lison'
// 如果最后一行代码这里报错提示:元素隐式具有 "any" 类型,因为类型“{ [name]: string; age: number; }”没有索引签名。 那可能是在tsconfig.json里开启了noImplicitAny。因为这里我们还没有学习接口等高级类型,所以你可以先忽略这个错误,或者关闭noImplicitAny。

// 2、Reflect.ownKeys 方法
const name = Symbol("name");
const obj = {
  [name]: "lison",
  age: 18
};
console.log(Reflect.ownKeys(obj));
// [ 'age', Symbol(name) ]

Symbol.for()和 Symbol.keyFor()

Symbol 包含两个静态方法,for 和 keyFor。

Symbol.for()

  • 该方法传入字符串,会先检查有没有使用该字符串调用 Symbol.for 方法创建的 symbol 值,如果有,返回该值,如果没有,则使用该字符串新创建一个

  • 使用该方法创建 symbol 值后会在全局范围进行注册,包括当前页面和页面中包含的 iframe,以及 service sorker

const s1 = Symbol("lison");
const s2 = Symbol("lison");
const s3 = Symbol.for("lison");
const s4 = Symbol.for("lison");
s3 === s4; // true
s1 === s3; // false

Symbol.keyFor()

该方法传入一个 symbol 值,返回该值在全局注册的键名

const sym = Symbol.for("lison");
console.log(Symbol.keyFor(sym)); // 'lison'

类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。以下代码虽没指定类型,但会在编译时报错:

let a = 'hello';
a = 7;
// index.ts(2,1): error TS2322: Type '7' is not assignable to type 'string'.

事实上,它等价于:

let a: string = 'hello';
a = 7;
// index.ts(2,1): error TS2322: Type '7' is not assignable to type 'string'.

TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:

let a;
a = 'hello';
a = 7;

类型断言

虽然 TypeScript 很强大,但有时它还是不如我们了解一个值的类型,这时候我们更希望 TypeScript 不要帮我们进行类型检查,而是交给我们自己来,所以就用到了类型断言。类型断言有点像是一种类型转换,它把某个值强行指定为特定类型

类型断言的用法:

  1. 在需要断言的变量前加上
  2. value as type 例如:target as string

类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的:

function toBoolean(something: string | number): boolean {
    return <boolean>something;
    // return something as tring;
}
// index.ts(2,10): error TS2352: Type 'string | number' cannot be converted to type 'boolean'.
//   Type 'number' is not comparable to type 'boolean'.