TypeScript 常用基础类型详解
boolean
let isDone: boolean = false;
注意,使用构造函数 Boolean 创造的对象不是布尔值:
/**
* TS2322: Type 'Boolean' is not assignable to type 'boolean'.
* 'boolean' is a primitive, but 'Boolean' is a wrapper object.
* Prefer using 'boolean' when possible
*/
let createdByNewBoolean: boolean = new Boolean(1);
直接调用 Boolean 返回一个 boolean 类型:
let createdByBoolean: boolean = Boolean(1);
null && undefined
- 在
TypeScript中,可以使用null和undefined来定义这两个原始数据类型 - 与
void的区别是,undefined和null是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量:
let num: number;
let str: string;
// 这些类型能被赋予
num = null;
str = undefined;
// 以下不会报错
let nums: number = undefined;
let u: undefined;
let numss: number = u;
// 而 void 类型的变量不能赋值给 number 类型的变量:
let u1: void;
// Type 'void' is not assignable to type 'number'.
let num1: number = u;
tip
tsconfig.json:strict 属性值是否为 false 或者 true 会影响上面的值
void
声明一个 void 类型的变量没有什么大用,因为你只能为它赋予 undefined 和 null
let unusable: void = undefined;
void && undefined
// Parent.tsx
function Parent(): JSX.Element {
// 这里函数返回的是number类型
const getValue = (): number => {
return 2;
};
// 这里函数返回的string类型,同样可以传给子属性
// const getValue = (): string => { return 'str' };
return <Child getValue={getValue} />;
}
// Child.tsx
type Props = {
// 这里的 void 表示逻辑上不关注具体的返回值类型,number、string、undefined 等都可以
getValue: () => void;
};
function Child({ getValue }: Props) {
return <div>{getValue()}</div>;
}
unknown
function test(input: unknown): number {
if (Array.isArray(input)) {
// Pass: 这个代码块中,类型守卫已经将input识别为array类型
return input.length;
}
/**
* Error: 这里的 input 还是 unknown 类型,静态检查报错。如果入参是 any,则会放弃检查直接成功,带来报错风险
* TS2339: Property 'length' does not exist on type 'unknown'.
*/
return input.length;
}
never
never 是指没法正常结束返回的类型,一个必定会报错或者死循环的函数会返回这样的类型。
// throw error 返回值是never
function foo(): never {
throw new Error('error message');
}
// 这个死循环的也会无法正常退出
function foo(): never {
while (true) {}
}
// Error: 这个无法将返回值定义为never,因为无法在静态编译阶段直接识别出
function foo(): never {
let count = 1;
while (count) {
count++;
}
}
无法把其他类型赋给 never。
let n: never;
let o: any = {};
// Error: 不能把一个非 never 类型赋值给 never 类型,包括 any
n = o;
数组定义的一些方式
let list: number[] = [1, 2, 3];
let list1: Array<number> = [1, 2, 3];
let list2: any[] = [1, true, 'free'];
类型断言
/** 方式一 */
let someValue: any = 'this is a string';
let strLength: number = (<string>someValue).length;
/** 方式二 在 TypeScript 里使用 JSX 时,只有 as 语法断言是被允许的。 */
let someValue1: any = 'this is a string';
let strLength1: number = (someValue1 as string).length;
带有任意数量的其它属性
[propName: number]: number|string|Array<any>; :必须包含前面其他对象 value 的类型
tip
一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
type a = {
a: 1;
b: 2;
[x: string]: number;
};
interface SquareConfig {
color: string;
width: number;
test: Array<any>;
[propName: number]: number | string | Array<any>;
// TS2413: Numeric index type 'string | number | any[]' is not assignable to string index type 'string | number'.
// [key: string]: number|string;
}
接口
readonly
一些对象属性只能在对象刚刚创建的时候修改其值。 可以在属性名前用 readonly 来指定只读属性
interface Point {
readonly x: number;
readonly y: number;
}
TypeScript 具有 ReadonlyArray<T> 类型,它与 Array<T> 相似, 只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
上面代码的最后一行,可以看到就算把整个 ReadonlyArray 赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写
a = ro as number[];
额外的属性检查
ts 会提示报错 error: 'colour' not expected in type 'SquareConfig'
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}
let mySquare = createSquare({ colour: 'red', width: 100 });
- 解决方法:如果
SquareConfig带有上面定义的类型的color和width属性,并且还会带有任意数量的其它属性,那么我们可以这样定义它:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
函数类型
interface SearchFunc {
// 函数的参数 (source,subString) 函数的返回值: boolean
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
let result = source.search(subString);
return result > -1;
};
/** 对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。 */
let mySearchOne: SearchFunc;
mySearchOne = function (src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
};
/**
* 函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。如果你不想指定类型,TypeScript的类型系统会推断出参数类型,因为函数直接赋值给了 SearchFunc类型变量。
*/
let mySearchTwo: SearchFunc;
mySearchTwo = function (src, sub) {
let result = src.search(sub);
return result > -1;
};
混合类型
一个对象可以同时做为函数和对象使用,并带有额外的属性。
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) {};
counter.interval = 123;
counter.reset = function () {};
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
剩余参数
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + ' ' + restOfName.join(' ');
}
let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie');
泛型
下面来创建第一个使用泛型的例子:identity 函数。 这个函数会返回任何传入它的值。 不用泛型的话,这个函数可能是下面这样:
function identity(arg: number): number {
return arg;
}
或者,我们使用 any 类型来定义函数:
function identity(arg: any): any {
return arg;
}
使用
any类型会导致这个函数可以接收任何类型的arg参数, 这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。 如果我们传入一个数字,我们只知道任何类型的值都有可能被返回。
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了 **类型变量 **,它是一种特殊的变量,只用于表示类型而不是值。
function identity<T>(arg: T): T {
return arg;
}
我们给
identity添加了类型变量T。T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这允许我们跟踪函数里使用的类型的信息。我们把这个版本的
identity函数叫做泛型,因为它可以适用于多个类型。 不同于使用any,它不会丢失信息, 像第一个例子那像保持准确性,传入数值类型并返回数值类型。
定义了泛型函数后,可以用两种方法使用。
- 第一种是,传入所有的参数,包含类型参数:
/** 这里我们明确的指定了T是string类型,并做为一个参数传给函数,使用了<>括起来而不是()。 */
let output = identity<string>('myString'); // type of output will be 'string'
- 第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定 T 的类型:
let output = identity('myString'); // type of output will be 'string'
示例
function loggingIdentity<T>(arg: T[]): T[] {
// Array has a .length, so no more error
console.log(arg.length);
return arg;
}
function loggingIdentity<T>(arg: Array<T>): Array<T> {
// Array has a .length, so no more error
console.log(arg.length);
return arg;
}
// 普通类型定义
type Dog<T> = { name: string; type: T };
// 普通类型使用
const dog: Dog<number> = { name: 'ww', type: 20 };
// 类定义
class Cat<T> {
private type: T;
constructor(type: T) {
this.type = type;
}
}
// 类使用
const cat: Cat<number> = new Cat<number>(20); // 或简写 const cat = new Cat(20)
// 函数定义
function swipe<T, U>(value: [T, U]): [U, T] {
return [value[1], value[0]];
}
// 函数使用
swipe<Cat<number>, Dog<number>>([cat, dog]); // 或简写 swipe([cat, dog])
枚举
- 数字枚举:当没有给枚举定值时,默认
X = 0, Y = 1, Z = 2 - 当
A = 1时,后面自增长,B = 2, C = 3
enum E1 {
X,
Y,
Z,
}
enum E2 {
A = 1,
B,
C,
}
/** 增长示例 */
enum E {
A = 1,
B = 100,
C,
D,
E = 200,
F,
}

tip
当其中某个枚举没赋予具体值时,会自动识别为数值。
但当除第一个枚举外,其他枚举要想自动识别为数值,要保证其上一个枚举是一个数值, 或者上一个枚举已经被自动识别成了数值。
enum E {
a,
b = '中国',
c = 100,
d,
e,
f = '103',
g = 104,
h,
}
联合类型
在 JavaScript 中,你可能希望属性为多种类型之一,如字符串或者数组。这正是 TypeScript 中联合类型能派上用场的地方 (它使用 | 作为标记,如 string | number)。关于联合类型,一个常见的用例是一个可以接受字符串数组或单个字符串的函数
function formatCommandline(command: string[] | string) {
let line = '';
if (typeof command === 'string') {
line = command.trim();
} else {
line = command.join(' ').trim();
}
}
export type ButtonSize = 'lg' | 'md' | 'sm';
keyof
用于获取某种类型的所有键,其返回类型是 联合类型
interface Person {
name: string;
age: number;
location: string;
}
const test: Person = { name: '11', age: 11, location: '22' };
type K1 = keyof Person; // "name" | "age" | "location"
const tests: K1 = 'name';
const test1: Person[] = [{ name: '11', age: 11, location: '22' }];
/** 为什么会有 number 是否是继承造成的 */
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
/** type K2s = keyof []; // 等同于 K2 */
type K2s = keyof []; // number | "length" | "push" | "concat" | ...
const test1s: K2 = 10 || 'length' || 'push';
const test2: { [x: string]: Person } = {
1: { name: '11', age: 11, location: '22' },
};
// 索引签名参数类型必须为 "string" 或 "number"。
type K3 = keyof { [x: string]: Person }; // string | number
const test2s: K3 = 10 || '333';
type K4 = keyof boolean;
const test3: K4 = 'valueOf';
let K2: keyof number = 'valueOf' || 'toFixed';
// 期望用户输入的属性是对象上已存在的属性
function prop(obj, key) {
return obj[key];
}
function propTs(obj: object, key: string) {
return obj[key];
}
function propTss<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
console.log(propTss(['a'], 0));
console.log(propTss(['a'], 1));
prop函数的作用是用于获取某个对象中指定属性的属性值。 因此我们期望用户输入的属性是对象上已存在的属性, 那么如何限制属性名的范围呢?可以利用keyof操作符:- 在以下代码中,我们使用了
TypeScript的泛型和泛型约束。 - 首先定义了
T类型并使用extends关键字约束该类型必须是object类型的子类型, - 然后使用
keyof操作符获取T类型的所有键,其返回类型是联合类型, - 最后利用
extends关键字约束K类型必须为keyof T联合类型的子类型
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
穿插类型
type TestOne = {
a: string;
};
type TestTwo = {
b: string;
};
type TestThree = {
c?: string;
};
const myTest: TestOne & TestTwo & TestThree = { a: '1', b: '2' };
const myTest1: TestOne & TestTwo & TestThree = { a: '1', b: '2', c: '3' };
/** error: d 不包含在 TestOne & TestTwo & TestThree 类型中 */
const myTest2: TestOne & TestTwo & TestThree = { a: '1', b: '2', c: '3', d: 4 };
/** error: b 在 TestOne & TestTwo & TestThree 是必须拥有的 */
const myTest3: TestOne & TestTwo & TestThree = { a: '1' };
extends
interface TestOnes {
a: string;
}
interface TestTwos extends TestOnes {
d?: string;
}
const myTests: TestTwos = { a: '1', d: '2' };
当联合类型无法做出判断时 我们编写一个 函数类型,逻辑很简单:给的类型是 x 时返回 a 则返回 b。
type AB<T> = T extends 'x' ? 'a' : 'b';
type A = AB<'x'>;
// 得到 type A = 'a'
看上去没什么问题,那是因为我们传入了一个明确的判断值,肯定为
true。 假设我们传入不确定的值,例如一个联合类型'x' | 'y'会怎么样呢?判断逻辑可能是true,也可能是false。 其实TypeScript也不知道该怎么办,于是乎它就把两个结果的值都返回给我们了:
type AB<T> = T extends 'x' ? 'a' : 'b';
type All = AB<'x' | 'y'>; // 非确定条件,可能是 'x' 或 'y'
// 得到 type All = 'a' | 'b';
我们得到了一个 联合类型 包含所有返回值的。 官方的解释是:此时做了 推迟解析条件类型 的处理。
推迟解析条件类型的额外效果
type Other = 'a' | 'b';
type Merge<T> = T extends 'x' ? T : Other; // T 等于匹配的类型,然后加上 Other 联合类型一起返回
type Values = Merge<'x' | 'y'>;
// 得到 type Values = "x" | "a" | "b"; 当为 x 返回 x, 当为 y 时返回 Other
利用此特性写的 Filter 类型定义
type Filter<T, U> = T extends U ? never : T;
type Values = Filter<'x' | 'y' | 'z', 'x'>;
// 得到 type Values = "y" | "z"
Partial
将某个类型里的属性全副变为可选项。
// 实现:全副变可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
例子
interface Animal {
canFly: boolean;
canSwim: boolean;
}
// 变可选,能够只赋值局部属性
let animal: Partial<Animal> = {
canFly: false,
};
ReadOnly
它接管一个泛型 T,用来把它的所有属性标记为只读类型
// 实现:全副变只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
let person: Readonly<Person> = {
name: 'jacky',
age: 24,
};
person.name = 'jack'; // Cannot assign to 'name' because it is a read-only property.
Required
将某个类型里的属性全副变为必选项。
// 实现:全副变必选
type Required<T> = {
[P in keyof T]-?: T[P];
};
interface Person {
name?: string;
age?: number;
}
// Property 'age' is missing in type '{ name: string; }' but required in type 'Required<Person>'.
let person: Required<Person> = {
name: 'jacky',
// 没写 age 属性会提醒谬误
};
Record
// 实现:K 中所有属性值转化为 T 类型
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// Record 生成的类型具备类型 K 中存在的属性,值为类型 T
interface DatabaseInfo {
id: string;
}
type DataSource = 'user' | 'detail' | 'list';
const x: Record<DataSource, DatabaseInfo> = {
user: { id: '1' },
detail: { id: '2' },
list: { id: '3' },
};
示例
/** 对象的值类型 */
interface DatabaseInfo {
id: string;
}
/** 对象的键类型 */
type DataSource = string;
type RecordTest = Record<DataSource, DatabaseInfo>;
const x: RecordTest = {
false: { id: '1' },
undefined: { id: '2' },
null: { id: '3' },
};
const x1: {
user: { id: string };
detail: { id: string };
list: { id: string };
} = {
user: { id: '1' },
detail: { id: '2' },
list: { id: '3' },
};
type EventType = string;
type RecordUnknown = Record<EventType, unknown>;
const test: RecordUnknown = {
Array: [],
a: 1,
b: 2,
c: 3,
1: [],
};
Exclude
// 实现:如果 T 中的类型在 U 不存在,则返回,否则不返回
type Exclude<T, U> = T extends U ? never : T;
// 将某个类型中属于另一个的类型移除掉
interface Programmer {
name: string;
age: number;
isWork: boolean;
isStudy: boolean;
}
interface Student {
name: string;
age: number;
isStudy: boolean;
}
type ExcludeKeys = Exclude<keyof Programmer, keyof Student>;
// type ExcludeKeys = "isWork"
Omit
// 实现:去除类型 T 中蕴含 K 的键值对。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 作用于 Pick 相同,还是间接看代码容易了解
interface Animal {
canFly: boolean;
canSwim: boolean;
}
let person1: Pick<Animal, 'canSwim'> = {
canSwim: true,
};
let person2: Omit<Animal, 'canFly'> = {
canSwim: true,
};
ReturnType
// 实现:获取 T 类型(函数)对应的返回值类型
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
// 获取函数返回值类型
function bar(x: string | number): string | number {
return 'hello';
}
// string | number
type FooType = ReturnType<typeof bar>;
遍历属性 in
in 只能用在类型的定义中,可以对枚举类型进行遍历 keyof 返回泛型 T 的所有键枚举类型,key 是自定义的任何变量名,中间用 in 链接,外围用[]包裹起来(这个是固定搭配), 冒号右侧 number 将所有的 key 定义为 number 类型。 总结起来 in 的语法格式如下:
[ 自定义变量名 in 枚举类型 ]: 类型
示例如下
type DynamicProps<T> = {
[P in keyof T]: Array<T[P]> | T[P];
};
type Person = {
name: string;
age: number;
};
/**
* @description 这个类型可以将任何类型的键值转化成number类型
* @author wfd
* @date 2020/10/9 13:30
* @example 相当于将 T (假设 T 是对象) 通过 keyof T 将会把 T 的每一个键拿出来
[key in keyof T] ===> 相当于将 每一个键遍历出来
最后形成如下:
{
T 对象中的 key 值 1: number,
T 对象中的 key 值 2: number,
}
*/
type TypeToNumber<T> = {
[key in keyof T]: number;
};
/**
* @description
TypeToNumber<Person> 相当于转换成了
{
name: number
age: number
}
* @author wfd
* @date 2020/10/9 13:28
*/
const obj: TypeToNumber<Person> = { name: 10, age: 10 };
const obj1: { name: number; age: number } = { name: 10, age: 10 };
/** 直接用 Person 类型则 name 属性无法用字符串,因为 Person 中的 name 属性的类型是 string */
const obj2: Person = { name: 10, age: 10 };
/** 下面这种实现的效果将会和 Person 一致 */
type TypeOne<T> = {
[P in keyof T]: T[P];
};
const objTwo: TypeOne<Person> = { name: '10', age: 10 };
const objTwos: { name: string; age: number } = { name: '10', age: 10 };
/**
* @description 相当于转换成了如下形式
{
T 对象中的 key 值 1: Array<T 对象中的第一个 value 对应的类型>,
T 对象中的 key 值 1: Array<T 对象中的第二个 value 对应的类型>,
}
* @author wfd
* @date 2020/10/9 13:37
*/
type TypeTwo<T> = {
[P in keyof T]: Array<T[P]>;
};
/**
* @description TypeTwo<Person> 相当于转换成了
{name: Array<string>, age: Array<number>}
* @author wfd
* @date 2020/10/9 13:38
*/
const objThree: TypeTwo<Person> = { name: ['10'], age: [10] };
const objThrees: { name: Array<string>; age: Array<number> } = {
name: ['10'],
age: [10],
};
使用 interface 还是 type 来定义类型
从用法上来说两者本质上没有区别,大家使用 React 项目做业务开发的话,主要就是用来定义 Props 以及接口数据类型。
但是从扩展的角度来说,type 比 interface 更方便拓展一些,假如有以下两个定义:
type Name = { name: string };
interface IName {
name: string;
}
想要做类型的扩展的话,type 只需要一个&,而 interface 要多写不少代码。
type Person = Name & { age: number };
interface IPerson extends IName {
age: number;
}
另外 type 有一些 interface 做不到的事情,比如使用|进行枚举类型的组合,使用 typeof 获取定义的类型等等。
不过 interface 有一个比较强大的地方就是可以重复定义添加属性,比如我们需要给 window 对象添加一个自定义的属性或者方法,那么我们直接基于其 Interface 新增属性就可以了。
declare global {
interface Window {
MyNamespace: any;
}
}
总体来说,大家知道 TS 是类型兼容而不是类型名称匹配的,所以一般不需用面向对象的场景或者不需要修改全局类型的场合,我一般都是用 type 来定义类型。

还没有留言,来发表第一条吧~