TypeScript 学习分享
TypeScript 介绍
TypeScript 扩展了JavaScript语法,任何已经存在的JavaScript程序,可以不加任何改动,在TypeScript环境下运行。TypeScript只是向JavaScript添加了一些新的遵循ES6规范的语法,以及基于类的面向对象编程的这种特性。
- 微软开发的一门编程语言
- JavaScript的超集
- 遵循最新的ES6规范
快速开始
搭建TypeScript开发环境
其实也就安装TypeScript的编译器,作用是把TS代码转换成JS代码
npm install -g typescript
tsc --version // 返回ts的版本
构建第一个TypeScript文件
mkdir test && cd test && vim hello.ts
function hello(person: string) {
return `Hello: ${
person}`;
}
let user = "Captain";
console.log(hello(user))
tsc hello.ts
node hello.js
用Webpack构建Ts开发环境
创建项目ts-demo
mkdir ts-demo // 创建文件夹
yarn init -y // 生成package.json 配置
新建文件 webpack.dev.js、index.html、.babelrc、tsconfig.json
新建文件webpack.dev.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const resolve = url => path.resolve(__dirname, url)
module.exports = {
mode: 'development',
entry: resolve('./src/index.tsx'),
output: {
filename: 'js/[name].[contenthash:5].js',
chunkFilename: 'js/[name].chunk.[contenthash:5].js',
path: resolve('./dist'), // 对应一个绝对路径,此路径是你希望一次性打包的目录
publicPath: '', // 静态文件的
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
// 创建 import 或 require 的别名
alias: {
'@': resolve('./src'),
}
},
module: {
rules: [{
test: /\.(jsx?|tsx?)$/,
loader: 'babel-loader',
include: resolve('./src'),
}]
},
plugins: [
new HtmlWebpackPlugin({
// 生成页面title标题, <%=htmlWebpackPlugin.options.title%>在html这样使用
title: 'ts-demo',
// 生成html的文件名
filename: 'index.html',
// 指定生成的文件所依赖的模板
template: resolve('./index.html'),
})
]
}
新建文件index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%=htmlWebpackPlugin.options.title%></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
新建文件.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"corejs": "3",
"useBuiltIns": "usage"
}
],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": []
}
新建文件tsconfig.json
{
// 编译选项
"compilerOptions": {
"baseUrl": "./", // baseUrl用于设置解析非相对模块名称的基本目录,相对模块不会受到baseUrl的影响
"outDir": "./dist", // outDir用来指定输出文件夹,值为一个文件夹路径字符串,输出的文件都将放置在这个文件夹
"rootDir": "./src", // rootDirs可以指定一个路径列表,在构建时编译器会将这个路径中的内容都放到一个文件夹中
"module": "esnext", // module用来指定要使用的模板标准
"target": "esnext", // target用于指定编译之后的版本目录
"lib": ["esnext", "dom"], // lib用于指定要包含在编译中的库文件
"sourceMap": true, // socuceMap用来指定编译时是否生成.map文件
"allowJs": true, // allowJs用来指定是否允许编译JS文件,默认false,即不编译JS文件
"jsx": "react", // 指定jsx代码用于的开发环境:'preserve','react-native',or 'react
"allowSyntheticDefaultImports": true, // allowSyntheticDefaultImports用来指定允许从没有默认导出的模块中默认导入
"experimentalDecorators": true, // experimentalDecorators用于指定是否启用实验性的装饰器特性
"moduleResolution": "node", // moduleResolution用于选择模块解析策略,有"node"和"classic"两种类型
"resolveJsonModule": true, // 导入json文件
"importHelpers": true, // importHelpers指定是否引入tslib里的复制工具函数,默认为false
"esModuleInterop": true, // esModuleInterop通过导入内容创建命名空间,实现CommonJS和ES模块之间的互操作性
"strict": true, // strict用于指定是否启动所有类型检查,如果设为true这回同时开启下面这几个严格检查,默认为false
"paths": {
// paths用于设置模块名到基于baseUrl的路径映射
"@/*": ["src/*"]
}
},
// exclude可以指定不需要编译的路径列表
"exclude": [
"node_modules",
"dist",
"scripts",
"webpack",
],
// include可以指定要编译的路径列表
"include": ["./src/*"],
// files可以配置一个数组列表
"files":[],
// extends可以通过指定一个其他的tsconfig.json文件路径,来继承这个配置文件里的配置,继承来的文件的配置会覆盖当前文件定义的配置
"extends":""
}
安装依赖
yarn add -D webpack webpack-cli babel-loader @babel/core @babel/preset-react @babel/preset-env @babel/preset-typescript html-webpack-plugin webpack-dev-server @types/react @types/react-dom
yarn add react react-dom
package.json文件配置
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js"
}
运行项目
yarn dev
基本概念
基础类型
// 布尔值
let isDone: boolean = false;
// 数字
let decLiteral: number = 6;
// 字符串
let name: string = "bob";
// 空值
// JavaScript 没有空值(Void)的概念,在 TypeScirpt 中,可以用 void 表示没有任何返回值的函数:
function alertName(): void {
alert('My name is Tom');
}
// 声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:
let unusable: void = undefined;
// Null 和 Undefined
let u: undefined = undefined;
let n: null = null;
// 与 void 的区别是,undefined 和 null 是所有类型的子类型。
let num: number = undefined;
let str: string = null;
// 下面会报错
let u: void;
let num: number = u; // Type 'void' is not assignable to type 'number'.
任意值
任意值(Any)用来表示允许赋值为任意类型。
// 如果是 any 类型,则允许被赋值为任意类型。
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
// 在任意值上访问任何属性都是允许的:
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
// 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
let something;
something = 'seven';
something = 7;
something.setName('Tom');
类型断言
类型断言用于告诉编译器 我非常清楚该字段的类型
// 尖括号写法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as 写法,在TypeScript里使用JSX时,只有 as语法断言是被允许的。
let someValue: any = "string";
let strLength: number = (someValue as string).length;
类型推论
如果没有明确的指定类型,那么 TypeScript 会依照类型推论规则推断出一个类型。
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7; //Type 'number' is not assignable to type 'string'.
//以上代码等价于
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
// 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7; //不会报错
联合类型
联合类型表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
类型别名
类型别名用来给一个类型起个新名字。
type placementType = 'top' | 'right' | 'bottom' | 'left';
let placement: placementType = 'center'; // Type '"center"' is not assignable to type 'placementType'.
数组
// 数组
let list: number[] = [1, 2, 3];
let list: string[] = ['1', '2', '3'];
let list: any[] = ['1', 2, '3'];
// 数组泛型
let list: Array<number> = [1, 2, 3];
let list: Array<string> = ['1', '2', '3'];
let list: Array<any> = ['1', 2, '3'];
元组
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let tuple: [string, number] = ['hello', 10];
// 在元组中访问一个越界的元素,会报错
tuple[3] = 'world'; // Index '3' is out-of-bounds in tuple of length 2.
enum枚举
提高代码可维护性,统一维护某些枚举值
// 默认从 0 开始
enum Fruits {
apple,
pear,
banana
}
// 修改从 1 开始
enum Fruits {
apple = 1,
pear,
banana
}
// 反查
console.log(Fruits.apple, Fruits[1]);
接口(Interface)
接口(Interfaces)可用于对类(Class)的一部分行为进行抽象,也可以用于对「对象的形状」进行描述。
interface Person {
readonly id: number; //只读属性
name: string; //确定属性
age?: number; //可选属性
[propName: string]: any; //任意属性,一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性
}
let tom: Person = {
id: 123321,
name: 'Tom',
gender: 'male'
};
tom.id = 321123; // error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
类型检测
typeof 操作符可以用来获取一个变量或对象的类型
interface Person {
name: string;
age: number;
}
const xiaoli: Person = {
name: "xiaoli", age: 23 };
type XL = typeof xiaoli; // Person
keyof
keyof 与 Object.keys 略有相似,只不过 keyof 取 interface 的键
interface Point {
x: number;
y: number;
}
type keys = keyof Point; // "x" | "y"
in
keyof是取类型的key的联合类型, in是遍历类型的key
type Keys = 'a' | 'b' | 'c';
type Obj = {
[ T in Keys]: string;
}
// in 遍历 Keys,并为每个值赋予 string 类型
// type Obj = {
// a: string,
// b: string,
// c: string
// }
函数(Function)
一个函数有输入和输出,在 TypeScript 中需要对其进行约束
// 函数声明
function sum(x: number, y: number): number {
return x + y;
}
sum(1, 2) // 3
sum(1, 2, 3); // error TS2346: Supplied parameters do not match any signature of call target.
sum(1); // error TS2346: Supplied parameters do not match any signature of call target.
// 函数表达式
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
用接口定义函数的形状
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
可选参数
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
参数默认值
TypeScript 会将添加了默认值的参数识别为可选参数
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
剩余参数
剩余参数只能是最后一个参数
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
函数重载
为同一个函数提供多个函数类型定义来进行函数重载
interface Animal {
name: string
age: number
}
function pickAnimal(x: Animal, y: string): number;
function pickAnimal(x: number): Animal;
function pickAnimal(x: number | Animal, y?: string): number | Animal {
if (typeof x == "object") {
return 1
}
else if (typeof x == "number") {
return {
name: 'xiaoqiang', age: 1 }
}
}
let pickAnimal1 = pickAnimal({
name: '小强', age: 1 }, 'XXXX');
let pickAnimal2 = pickAnimal(15);
类(Class)
访问修饰符
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
抽象类
abstract 用于定义抽象类和其中的抽象方法。
- 抽象类不允许被实例化
- 抽象类中的抽象方法必须被子类实现
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal('Jack'); //error TS2511: Cannot create an instance of the abstract class 'Animal'.
class Cat extends Animal {
public eat() {
console.log(`${
this.name} is eating.`);
}
}
let cat = new Cat('Tom'); //error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.
类的类型
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${
this.name}`;
}
}
let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
类与接口
一个类只能继承自另一个类,但是一个类可以实现(implements)多个接口
类实现接口
interface Alarm {
alert();
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
一个类可以实现多个接口:
interface Alarm {
alert();
}
interface Light {
lightOn();
lightOff();
}
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
接口继承接口
接口与接口之间可以是继承关系,一个接口可以继承多个接口:
interface Alarm {
alert();
}
interface LightableAlarm extends Alarm {
lightOn();
lightOff();
}
接口继承类
接口也可以继承类,同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现:
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {
x: 1, y: 2, z: 3};
泛型(Generics)
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性,可以理解为类型的变量。
// 例子
function createArray(length: number, value: any): Array<any> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = Number(value);
}
return result;
}
createArray(3, '1'); // [1, 1, 1]
// 使用泛型
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = Number(value);
}
return result;
}
createArray<string>(3, '1'); //error TS2322: Type 'number' is not assignable to type 'T'.
多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
} // error TS2339: Property 'length' does not exist on type 'T'.
// 对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity(7); // error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.
泛型接口
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
return x + y; };
TS中一些内置的类型
Partial 将类型的属性变成可选
功能是将类型的属性变成可选,注意这是浅Partial
type Partial<T> = {
[P in keyof T]?: T[P]
};
例子说明
interface UserInfo {
id: string;
name: string;
}
type NewUserInfo = Partial<UserInfo>;
// interface NewUserInfo {
// id?: string;
// name?: string;
// }
Required将类型的属性变成必选
type Required<T> = {
[P in keyof T]-?: T[P]
};
例子说明
interface UserInfo {
id?: string;
name?: string;
}
type NewUserInfo = Required<UserInfo>;
// interface NewUserInfo {
// id: string;
// name: string;
// }
Readonly将类型的属性变成只读
type Readonly<T> = {
readonly [P in keyof T]: T[P]
};
例子说明
interface UserInfo {
id: string;
name: string;
}
type NewUserInfo = Readonly<UserInfo>;
// interface NewUserInfo {
// readonly id: string;
// readonly name: string;
// }
Mutable将类型的属性变成可修改
type Mutable<T> = {
-readonly [P in keyof T]: T[P]
};
例子说明
interface UserInfo {
readonly id: string;
readonly name: string;
}
type NewUserInfo = Readonly<UserInfo>;
// interface NewUserInfo {
// id: string;
// name: string;
// }
Pick 从某个类型中挑出一些属性出来
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
例子说明
interface UserInfo {
id: string;
name: string;
}
type NewUserInfo = Pick<UserInfo, 'name'>; // {name: string;}
Exclude从一个联合类型中排除掉属于另一个联合类型的子集
type Exclude<T, U> = T extends U ? never : T;
例子说明
interface A {
show: boolean,
hidden: boolean,
status: string
}
interface B {
show: boolean,
name: string
}
type outPut = Exclude<keyof A, keyof B> // 'hidden' | 'status
Extract:跟Exclude相反,从从一个联合类型中取出属于另一个联合类型的子集
type Extract<T, U> = T extends U ? T : never;
例子说明
interface A {
show: boolean,
hidden: boolean,
status: string
}
interface B {
show: boolean,
name: string
}
type outPut = Extract<keyof A, keyof B> // 'show'
Omit从某个类型中剔除某些属性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
例子说明
interface UserInfo {
id: string;
name: string;
}
type NewUserInfo = Omit<UserInfo, 'name'>; // {id: string;}
Record获得根据 K 中所有可能值来设置 key 以及 value 的类型
type Record<K extends string | number | symbol, T> = {
[P in K]: T; }
例子说明
interface UserInfo {
id: string;
name: string;
}
type CurRecord1 = Record<'a' | 'b' | 'c', UserInfo>; // { a: UserInfo; b: UserInfo; c: UserInfo; }
type CurRecord2 = Record<string, any>; // { [key: string ]: any }
ReturnType 用来得到一个函数的返回值类型
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any
例子说明
type Func = (value: number) => string;
type A = ReturnType<Func> // string