TypeScript
是JavaScript
的超集,为JavaScript
赋予了强类型语言的特性。在项目中使用TypeScript
可以将错误提早暴露在开发阶段,而且TypeScript
除保留JavaScript
原有特性外还增长了不少强大的特性,如:接口、泛型、类等等,为开发带来了极大的便利。前阵子在学习TypeScript
过程当中整理了很多笔记,在梳理过程当中就产生了该文章,同时也但愿让更多同窗了解TypeScript
,顺便帮本身加深记忆。node
TypeScript
的数据类型,包含了JavaScript
全部的数据类型。除此以外还提供了枚举、元组、any、void等类型,下面讲述在TypeScript
中如何使用这些类型。typescript
// Boolean
let bool: boolean = true;
复制代码
// String
let str: string = 'hello TypeScript';
复制代码
TypeScript
还支持二进制、八进制// Number
let decLiteral: number = 10;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
复制代码
// 第一种,能够在元素类型后面接上 [],表示由此类型元素组成的一个数组
let arr1: number[] = [1, 2, 3];
// 第二种方式是使用数组泛型,Array<元素类型>
let arr2: Array<number> = [1, 2, 3];
// 数组支持多种元素类型
let arr3: (number | string) = [1, '2', 3];
let arr4: Array<number | string> = [1, '2', 3];
复制代码
// Function
let add = (x, y) => x + y;
复制代码
// 能够这样定义,但访问、修改属性时会抛出错误,由于Object上不存在x、y属性。
let obj: Object = {x: 1, y: 2};
console.log(obj.x); // 类型“Object”上不存在属性“x”
// 完善定义
let obj1: {x: number, y: number} = {x: 1, y: 2};
console.log(obj1.x); // 1
复制代码
// Symbol
let syb: symbol = Symbol('hello');
复制代码
// null与undefined
let null: null = null;
let undefined: undefined = undefined;
复制代码
上面示例中咱们在变量后跟类型的方式,被称之为:类型注解。使用类型注解的变量必须按照类型注解声明的类型来赋值,不然TypeScript
会抛出错误。编程
void
表明没有返回值,或返回值为undefined
,一般用来声明函数的返回值类型。void
类型,那它只能被赋值为null
与undefeated
。// void
let add = (x: number, y: number): void => undefined; // 表示返回值为undefined
let unusable: void = undefined;
unusable = 'hello'; // ts会抛出错误,不能将类型“"hello"”分配给类型“void”
复制代码
any
能够被赋值为除never
外的任何类型。any
,可是不建议所有使用any
来定义类型,这样使用TypeScript
就没有任何意义了。// any
let any: any = 1;
any = '1';
any = null;
复制代码
never
类型表示的是那些永不存在的值的类型never
的函数必须存在没法达到的终点,好比:一个死循环的函数、一个老是抛出错的函数。never
只能被赋值为never
自身。// never
function loop(): never {
while(true) {}
}
function error(message: string): never {
throw new Error(message);
}
复制代码
// 元祖 tuple
let tuple: [number, string] = [1, '2'];
tuple[2] = 2; // 不能将类型“2”分配给类型“undefined”。
复制代码
// 数字枚举,默认从0开始,依次递增
enum Color {
Red,
Green,
Blue
}
console.log(Color.Red) // 0
console.log(Color.Red) // 1
console.log(Color.Red) // 2
// 自定义数字枚举的值
enum Alphabet {
A = 8,
B,
C = 2020
}
console.log(Alphabet.A) // 8
console.log(Alphabet.B) // 9
console.log(Alphabet.C) // 2020
// 类型保护
let year = Alphabet.C;
year = 'today'; // 不能将类型“"today"”分配给类型“Alphabet”
// 数字枚举支持双向映射
console.log(Alphabet.A) // 8
console.log(Alphabet[8]) // A
// js实现双向映射
var Alphabet;
(function (Alphabet) {
Alphabet[Alphabet["A"] = 8] = "A";
Alphabet[Alphabet["B"] = 9] = "B";
Alphabet[Alphabet["C"] = 2020] = "C";
})(Alphabet || (Alphabet = {}));
// 字符串枚举
enum Message {
Fail = '失败',
Success = '成功'
}
// 枚举成员类型
enum Library {
// 常量枚举
BookName,
Year = 2020,
Count = BookName + Year,
// 计算型枚举
Number = Math.random(),
Size = '如何成为掘金V6'.length // 计算型枚举后的枚举成员必需要赋值
}
复制代码
看完上面的内容,咱们已经对TypeScript
中的数据类型已经有一个大概的了解了。有同窗可能会有疑问,若是咱们须要描述一个复杂的数据类型该怎么编写类型注解呢?这就涉及到咱们下面讲的内容接口,如何使用接口来描述一个复杂的数据类型。数组
接口是TypeScript
中核心概念之一,主要用于约束对象、函数、类的结构。dom
x?: string
,来约定参数是否可选readonly x: number
,来约定参数是否只读/** * 对象类型接口 */
// 可选属性与只读属性
interface Person {
name: string;
readonly age: number;
sex: 'boy' | 'girl',
hobby?: string[]
}
let Tom: Person = {
name: 'Tom',
age: 3,
sex: 'boy'
};
Tom.age = 1; // Cannot assign to 'age' because it is a read-only property.
let Jerry: Person = {
name: 'Jerry',
age: 3,
sex: 'boy',
hobby: ['和汤姆一块儿快乐的玩耍']
}
/** * 鸭式辩型法举例 * renderList传入的参数只要知足Result接口的约定便可,多余的code、msg不会影响 * 这也是咱们提到的:一只鸟若是走起来像鸭子,叫起来像鸭子,游起来像鸭子,那么这只鸟就能被当成一只鸭子。 */
interface ListItem {
id: number;
name: string
}
interface Result {
list: ListItem[]
}
function renderList(result: Result) {
result.list.forEach((item: ListItem) => {
console.log(item.id, item.name)
})
}
const data = {
list: [{
id: 1,
name: '小红'
}, {
id: 2,
name: '小白'
}],
code: 1,
msg: ''
};
renderList(data);
/** * 函数类型接口 * 函数的参数名不须要与接口里定义的名字相匹配,但要求对应位置上的类型是兼容的 */
let add: (x: number, y: number) => number;
interface Add {
(x: number, y: number): number;
}
// 上面这两种定义式等价的
// 函数的参数名不须要与接口里定义的名字相匹配
let add1: Add = (j: number, k: number) => j + k;
// 但要求对应位置上的类型是兼容的
let add2: Add = (j: string, k: number) => j + k; // 不能将类型“(j: string, k: number) => string”分配给类型“Add”。参数“j”和“x” 的类型不兼容。
/** * 索引类型接口 * 索引类型具备一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 * 当不肯定接口中属性个数时可使用索引类型接口 * 索引类型分字符串索引和数字索引 * 字符串与数字索引能够同时使用,但数字索引必须是字符串索引返回的子类型 */
// 数字索引
interface StringArray {
[index: number]: string;
}
const myArra1: StringArray = ['1', 2]; // 不能将类型“number”分配给类型“string”。
const myArray: StringArray = ['1', '2'];
// 字符串索引
interface ListItem {
id: number;
name: string;
[x: string]: any;
}
// 这里咱们使用string去索引any
let list: ListItem[] = [{
id: 1,
name: 'aa',
year: '2019'
}]
// 字符串与数字索引能够同时使用,但数字索引必须是字符串索引返回的子类型
interface ArrayItem {
[x: string]: string;
[y: number]: number; // 数字索引类型“number”不能赋给字符串索引类型“string”
}
interface ArrayItem1 {
[x: string]: string;
[y: number]: string;
}
/** * 混合类型接口 * 混合类型接口是函数类型接口与对象类型接口的集合 */
interface Lib {
(): void;
version: string;
doSomething(): void;
}
let lib: Lib = (() => {}) as Lib; lib.version = '1.0.0'; lib.doSomething = () => {};
复制代码
TypeScript
更清楚数据的类型,这时候就能使用类型断言,表示你清楚该数据的类型格式。
<类型>
的方式来声明类型断言。as
进行类型断言。// 使用接口声明一个函数类型接口
interface Add {
(x: number, y: number): number
}
// 使用类型别名声明一个函数类型接口
type Add = (x: number, y: number) => number;
// 上面两种声明方式等价
/** * 类型别名的一些小技巧 * 在React中初始化state,设置类型比较麻烦 * 若是使用类型别名就很方便了 */
const initialState = {
page: 1,
pageCount: 15,
list: [{
id: 1,
name: 'Tom'
}]
};
type State = typeof initialState;
type State1 = {
page: number;
pageCount: number;
list: {
id: number;
name: string;
}[];
}
// State与State1二者等价
/** * 类型断言 * 类型断言有两种声明方式:1. 与JSX通常声明,2. 使用as关键字声明 * 在React中请使用第二种方式,同时也推荐使用第二种方式 */
interface Obj {
x: number;
y: number;
}
let obj = {};
obj.x = 1; // 类型“{}”上不存在属性“x”
// 1. 如JSX通常声明
let obj1 = <Obj>{};
obj1.x = 1;
// 2. 使用as关键字声明
let obj2 = {} as Obj;
obj2.x = 1;
复制代码
TypeScript
中的函数行为基本与JavaScript
保持一致,不一样的是TypeScript
支持如下功能:函数
TypeScript
规定必须在类型最宽泛的版本中实现重载,你能够理解为在类型最为宽泛的版本中须要支持以前版本的全部类型。// 约束参数类型、数量
function add(x: number, y: number) {
return x + y;
};
add(1, 2); // 3
add(1, '2'); // 类型“"2"”的参数不能赋给类型“number”的参数
add(1, 2, 3); // 应有 2 个参数,但得到 3 个。
/** * 可选参数与联合类型 * 可选参数除参数约定类型外,还会默认支持undefined * 可选参数都是联合类型 * 联合类型是指使用“|”设置支持多种类型,后续内容会详细讲解 */
function add1(x: number, y?: number) {
return y ? x + y : x;
}
add1(1); // 1
add1(1, 2); // 3
// 默认参数
function add2(x: number, y: number = 2020) {}
// 剩余参数
function add3(x: number, ...rest: number[]){
return rest.reduce((pre: number, cur: number) => pre + cur, x);
}
add2(1, 2, 3, 4, 5, 6); // 21
/** * 函数重载 * TypeScript规定必须在类型最宽泛的版本中实现重载,你能够理解为在类型最为宽泛的版本中须要支持以前版本的全部类型。 */
function add4(...rest: number[]): number;
function add4(...rest: string[]): string;
function add4(...rest: any) {
let first = rest[0]
if (typeof first === 'string') {
return rest.join(' ')
}
if (typeof first === 'number') {
return rest.reduce((pre: number, cur: number) => pre + cur)
}
}
add4(2020, 1, 17) // 2038
add4('hello','TypeScript') // hello TypeScript
add4({x: 1, y: 2}) // 类型“{ x: number; y: number; }”的参数不能赋给类型“string”的参数
复制代码
JavaScript
自己是没有类概念,在ECMAScript 6以前都是使用函数与基于原型的继承建立可复用组件。 JavaScript
在ECMAScript 6引入了class关键字,可用于基于类的面向对象编程。可是class的本质仍是函数+原型,此处很少作讲解。TypeScript
的类包含JavaScript
的类,除此以外还新增类一些新特性如:成员修饰符、抽象类、多态等等。oop
TypeScript
有哪些成员修饰符以及特性?
TypeScript
类的属性必须具备初始值。TypeScript
类属性默认为public,公共成员/** * 继承与成员修饰符 */
interface PersonBase {
name: string;
age: number;
sex: 'boy' | 'girl';
}
class Person {
name: string;
age: number;
readonly sex: string; // 只读属性,不可被更改
constructor({ name, age, sex }: PersonBase) {
this.name = name;
this.age = age;
this.sex = sex;
}
}
class Man extends Person {
private wife: string; // 私有成员不能被外部调用
protected propertyList?: string[]; // 受保护成员,只能在类及其子类中访问。
static weight: number = 120; // 只能经过类名访问,不能经过子类访问
constructor(personBase: PersonBase) {
super(personBase);
this.wife = 'Jilly';
this.propertyList = ['house', 'car'];
}
private getWife() {
return this.wife;
}
getWeight() {
return this.weight; // Property 'weight' is a static member of type 'Man'
}
}
class Son extends Man {
constructor(personBase: PersonBase) {
super(personBase);
}
}
const Tom = new Man({
name: 'Tom',
age: 18,
sex: 'boy'
});
const Child = new Son({
name: 'child',
age: 3,
sex: 'boy'
});
Child.sex = 'girl'; // Cannot assign to 'sex' because it is a read-only property.
console.log(Tom.propertyList); // 属性“propertyList”受保护,只能在类“Man”及其子类中访问。
console.log(Child.getWife()); // 属性“getWife”为私有属性,只能在类“Man”中访问。
console.log(Man.weight); // 只能经过类名访问,不能经过子类访问
复制代码
abstract
关键字定义抽象类与抽象方法。/** * 抽象类与多态 * 抽象类只能被继承,不能被实例化。 * 抽象类中的抽象方法不包含具体实现而且必须在派生类中实现 */
// 实现一个抽象类
abstract class Animal {
eat() {
console.log('eat');
}
// 实现一个抽象方法,抽象方法不包含具体实现而且必须在派生类中实现
abstract sleep(): void;
}
const pig = new Animal(); // 没法建立抽象类的实例
class Dog extends Animal {
name: string;
constructor(name: string){
super();
this.name = name;
}
sleep() {
console.log('dog sleep')
}
}
let dog = new Dog('wang wang');
dog.sleep()
// 非抽象类“Cat”不会实现继承自“Animal”类的抽象成员“sleep”
class Cat extends Animal {
}
复制代码
/** * 类与接口 * 接口与接口之间可相互继承,使用extends关键字继承。 * 继承多个接口须要用“,”分割 * 类使用接口时,须要使用implements关键字 * 接口与类可相互继承 */
interface Animal {
eat(): void;
}
// 接口继承接口
interface Felidae extends Animal {
run(): void;
}
interface Pets {
cute: boolean;
}
// 同时继承多个接口
interface Cat extends Felidae, Pets {}
// 类使用接口
class Tom implements Cat {
cute = true;
eat(){};
run(){};
}
// 接口继承类
interface Jack extends Tom {}
复制代码
/** * 泛型 * 不预先确认数据格式,具体的类型格式在使用时才能确认。 * 泛型不能用于类的静态成员 */
// 一个简单的泛型函数
function log<T>(msg: T): T {
return msg;
}
// 实现一个泛型接口
interface Log {
<T>(msg: T): T
}
// 上面两种实现方式等价
// 两种使用方式
log<string>('hello'); // 第一种,告诉泛型函数传入的参数是什么类型
log('hello'); // 第二种,经过TypeScript类型推断来肯定类型
let myLog: Log = <T>(msg: T) => msg;
myLog<string>('hello');
myLog('hello');
class Cat<T> {
static eat(name: T) {} // 静态成员不能引用类类型参数
}
复制代码
类型保护机制,可以保证TypeScript
变量在特定的区块内属于特定的类型,所以,在类型保护区块中,能够放心的引用对应类型的属性和方法。学习
/** * 类型保护机制 * 类型保护机制能肯定在指定区块内,可使用对应类型的方法和属性 * 可以使用instanceof、typeof、in、hasOwnProperty或组合判断等方式来肯定区块 */
class Pig {
edible: boolean = true;
}
class Cat {
cute: boolean = true;
}
function getAnimal(animal: Pig | Cat) {
const Animal = animal instanceof Pig ? new Pig() : new Cat();
if (Animal instanceof Pig) {
console.log(Animal.edible); // true
} else {
console.log(Animal.cute); // true
}
}
复制代码
&
将多个类型合并为一个类型。|
来设置多个类型, 赋值时能够根据设置的类型来赋值。/** * 交叉类型与联合类型 * 交叉类型会将多个类型合并为一个类型,能够认为是多个类型的集合。 * 交叉类型使用&将多个类型合并为一个类型。 * 联合类型会将变量设置多种类型,赋值时能够根据设置的类型来赋值。 * 联合类型使用|来设置多个类型, 赋值时能够根据设置的类型来赋值。 */
interface Dog {
run: () => void;
}
interface Cat {
jump: () => void;
}
// 交叉类型
let pet: Dog & Cat = {
run: () => {},
jump: () => {}
};
// 联合类型
let Sex: 'boy' | 'girl';
let Status: 'success' | 'fail' | 'loading'
复制代码
keyof T
为索引类型查询操做符,T你能够理解为任何类型与值。T[K]
为索引访问操做符,与获取对象类似,存在返回值的类型,不存在会抛出错误。/** * 索引类型 */
let obj = {
a: 1,
b: 2,
c: 3
};
// keyof T
interface Obj {
a: number;
b: string;
}
let key: keyof Obj; // "a" | "b"
// T[K]
let value: Obj['a']; // number
/** * T extends U * 这个函数的意思是,从K[]中找到T包含的索引,返回类型为T[K] * 简单的来讲就是,从keys中找到obj中包含的值,keys若是传入obj中不存在的值就会抛出错误 */
function getValue<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
return keys.map(key => obj[key]);
}
console.log(getValue(obj, ['a', 'b'])); // [1, 2]
console.log(getValue(obj, ['a', 'f'])); // 不能将类型“string”分配给类型“"a" | "b" | "c"” [1, undefined]
复制代码
/** * 映射类型 * 从旧类型中建立新类型的一种方式。新的类型以相同的形式去转换旧的类型,好比能够令每一个属性成为readonly或者可选的。 * TypeScript自身提供了不少种映射类型,如:Readonly、Partial、Pick、Record等等 * 有兴趣的同窗能够查看 node_modules/typescript/lib/lib.es5.d.ts 文件 */
interface Obj {
a: number;
b: string;
c: boolean;
}
// 只读
type ReadonlyObj = Readonly<Obj>
// 可选
type PartialObj = Partial<Obj>
// 抽离指定属性
type PickObj = Pick<Obj, 'a' | 'b'>
type RecordObj = Record<'x' | 'y', Obj>
复制代码
lib.d.ts
里增长了一些预约义的有条件类型:
Exclude<T, U>
-- 从T
中剔除能够赋值给U
的类型。Extract<T, U>
-- 提取T
中能够赋值给U
的类型。NonNullable<T>
-- 从T
中剔除null
和undefined
。ReturnType<T>
-- 获取函数返回值类型。InstanceType<T>
-- 获取构造函数类型的实例类型。/** * 条件类型 */
// T extends U ? X : Y;
type TypeName<T> =
T extends string ? "string" :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends Function ? 'function' :
'object';
type T1 = TypeName<number>; // number
type T2 = TypeName<string[]> // object
// (A | B) extends U ? X : Y;
type T3 = TypeName<number | string[]>; // "number" | "object"
// Diff的意思是从T中找出U中不存在的值,存在返回never,不存在返回该值
type Diff<T, U> = T extends U ? never : T;
type T4 = Diff<'a' | 'b' | 'c', 'a' | 'e'>
// Diff <'a', 'a', 'e'> | Diff <'b', 'a', 'e'> | Diff <'c', 'a', 'e'>
// never | 'b' | 'c'
// 'b' | 'c'
type NotNull<T> = Diff<T, undefined | null>;
type T5 = NotNull<string | number | undefined | null> // string | number
// Exclude<T, U>
// NotNullable<T>
// Extract<T, U>
type T6 = Extract<'a' | 'b' | 'c', 'a' | 'e'>; // a
// RenturnType<T>
type T7 = ReturnType<() => string> // string
复制代码
到此TypeScript三步曲之基础篇就结束了,感谢你们的阅读,若有错误欢迎斧正。优化
同时2020年的第一个flag也实现了,在2020年1月20日回家前输出该文章,虽然花了不少时间和精力,但也让我更深刻的了解了TypeScript
而且加深了印象。后续的TypeScript三步曲之配置篇与TypeScript三步曲之工程篇预计3-4月输出。ui
明天就回家过年了,在此提早祝各位掘友:新年快乐、身体健康、事事如意。