TypeScript起步
安装TypeScript
npm i -g typescript
生成ts配置文件tsconfig.json
tsc --init
- tsconfig.json
{
"compilerOptions": {
"target": "ES2016",
"module": "commonjs",
"lib": ["ES2016"],
"outDir": "./dist",
},
"include": ["./src"], //要编译的目录
}
tsconfig.json配置汇总
{
"compilerOptions": {
"module": "commonjs", /*编译结果使用什么模块化标准*/
"removeComments": true, /* 编译结果中移除注释 */
"noImplicitUseStrict": true, /*编译结果中移除 use strict*/
"esModulesInterop": true, /*启用es模块化交互非es模块导出*/
"noEmitOnError": true, /*错误时就不生成编译结果*/
"moduleResolution": node, /*使用node解析*/
"strictPropertyInitialization": true, /*类的属性进行更加严格的检查*/
}
}
@types/node
-
@types是一个ts官方的类型库,其中包含了很多对js代码的类型描述。
-
JQuery:用js写的,没有类型检查
-
安装@types/jquery,为jquery库添加类型定义
npm i -D @types/node
使用第三方库
- ts-node:将ts代码在内存种完成编译,同时完成运行,显示到命令行中,不会生成编译js的文件。
npm i -g ts-node
ts-node src/index.ts
- nodemon 用于监控文件的变化
npm i -g nodemon
- 启动并监控index.ts,但是什么文件都在监控
nodemon --exec ts-node src/index.ts
* /
"scripts": {
"start":"nodemon --watch src -e ts --exec ts-node src/index.ts"
},
类型约束
- TS是一个
可选的静态类型系统
哪些可以进行类型约束
变量、函数的参数、函数的返回值
如何进行约束
在变量、函数的参数、函数的返回值的后面加上:类型
变量
let a:string = '1';
函数的参数和返回值
function sum(a:number,b:number):number{
return a + b;
}
f2重命名 f12转到定义
function add(a:number,b:number):number{
return a + b;
}
const num:number = add(0,3);
类型推导
如何知道ts是否成功推导,代码中出现...就是没有推导成功。
function add(a:number,b:number){
return a + b;
}
const num = add(0,3);
let name = 'st';
-
any:表示任何类型
-
编译后的结果就是类型约束没有了
基本类型约束
-
number
-
string
-
boolean
-
数组
-
对象
-
null和undefined
function isOdd(n:number):boolean{
return n % 2 === 0;
}
- 数组
//1
const nums:number[] = [1,2];
//2
const nums:Array<number> = [1,2];
- 对象
//只能约束一层
let obj:object;
- 获取对象的每一项的值
function printValue(obj:object){
const vals = Object.values(obj);
vals.forEach(item=>console.log(item));
}
printValue({
a:'1',
b:'2',
c:'3'
})
- null和undefined是其他类型的子类型,可以赋值给其他类型。但是又会引发类型问题。可以在tsconfig.json的
compilerOptions中添加strictNullChecks:true,可以获得更严格的空类型检查,之后就只能赋值给自己。
let str:string = null;
let num:number = undefined;
其他类型
- 联合类型
- void类型
- never类型
- 字面量类型
- 元组类型(Tuple)
- any类型
联合类型
要约束的可能是string或者undefined。多种类型任选其一。
let name: string|undefined;
if(typeof name === 'string'){
//类型保护,可以确定类型,typeof可以触发类型保护
name.slice();
} else {
}
- 类型保护:当对某个变量进行类型判断之后,在判断的语句块中便可以确定它的类型 typeof可以触发简单类型保护
ovid类型
通常用于约束函数的返回值,表示这个函数不返回任何值。
function print():void{
console.log("1")
}
never类型
通常用于约束函数的返回值,表示函数永远不可能结束。
function throwError(msg:string):never{
throw new Error(msg);
//下面这行代码永远不会执行,也就是函数永远不可能结束
console.log('sss')
}
字面量类型
使用一个值进行约束
let a:'A';
a = 'A';
let gender:'男'|'女';
let arr:[];//arr永远只能取值一个空数组
let use:{
name:string,
age:number
}
元祖类型
一个固定的长度的数组,并且数组中每一项的类型确定。
//数组arr必须两项并且第一项是string,第二项是number
let arr:[string,number];
any类型
可以绕过类型检查,因此any类型就可以赋值给任何类型。
类型别名
想约束一个对象里面的属性,可以用之前的字面量约束,如:
let u:{
name: string,
age: number,
gender: "男"| "女"
}
function getUser():{
name: string,
age: number,
gender: "男"| "女"
}(){
}
getUser函数会返回一个user类型的对象,这样的话,对函数返回值的约束,像上面这样写,就很丑陋。 于是就出现了类型别名。
type 类型别名 = 类型;
type User = {
name: string, age: number, gender: '男' | '女'
}
let u:User;
u = {
name:'12',
age:12,
gender:'男'
}
function get(users:User){
console.log(users)
}
get(u)
函数的相关约束
- 函数重载:在函数实现之前,对函数调用的多种情况进行声明
- 可选参数:可以在某些参数名后面加上问号,表示该参数可以不用传递。
/**
* 返回a和b的和
* @param a
* @param b
*/
function combin(a:number,b:number):number;
/**
* 返回a拼接b
* @param a
* @param b
*/
function combin(a:string,b:string):string;
function combin(a:number|string,b:number|string):number|string{
if(typeof a === 'number' && typeof b === 'number'){
return a * b;
} else if(typeof a === 'string' && typeof b === 'string'){
return a + b;
}
throw new Error('两个参数类型不一');
}
let test = combin(1,2);
上面这种写法,就是函数的重载。
- 可选参数
只能出现在末尾
function sum(a:string,b:number,?:number){
}
- 默认参数
function sum(a:string,b:number,c:number=0){
}
扩展类型
- 类型别名
- 枚举
- 接口
- 类
枚举
- 枚举通常用于约束某个变量的取值范围,字面量和联合类型配合使用,也可以达到同样的目的。
- 枚举会出现在编译结果中,因此我们可以在后面代码中拿到枚举的值。
最佳实践:
- 尽量不要在一个枚举中既出现字符串枚举字段,又出现数字字段
- 使用枚举时,尽量使用枚举字段名称,而不要使用真实的值
字面量的问题
- 在类型约束时,会产生重复代码
- 逻辑含义和真实的值产生了混淆,会导致当修改真实值的时候,产生大量的修改。
- 字面量类型不会出现在编译结果中
枚举的使用
enum 枚举名{
枚举字段 = 值1,
枚举字段 = 值2,
....
}
使用字面量
type Gender = '男'|'女';
let gender:Gender;
gender = '女';
function getGender(gender:Gender){
}
使用枚举
enum Gender {
male = "男",
female = "女"
}
let gender: Gender;
gender = Gender.male
function getGender(gender: Gender){
console.log(gender);
}
getGender(gender); // '男'
- 枚举会出现在编译结果中
因此我们可以在后面代码中拿到枚举的值。
enum Gender {
male = "男",
female = "女"
}
Object.values(Gender).forEach(i=>console.log(i))
// 男
// 女
枚举的规则
- 枚举的字段值可以是字符串或数字
- 数字枚举的值会自动自增
- 被数字枚举约束的变量,可以直接赋值为数字,不建议这样写
- 数字枚举和字符串的编译结果有差异
- 数字枚举的值会自动自增
enum Level {
level,
level2,
level3,
}
console.log(Level.level); // 0
console.log(Level.level2); // 1
console.log(Level.level3); // 2
enum Level {
level = 3,
level2,
level3,
}
let l: Level = Level.level;
console.log(Level.level); // 3
console.log(Level.level2); // 4
console.log(Level.level3); // 5
- 被数字枚举约束的变量,可以直接赋值为数字,不建议这样写
enum Level {
level = 3,
level2,
level3,
}
let l: Level = Level.level2;
l = 3;
console.log(l); // 3
- 数字枚举和字符串的编译结果有差异
enum Level {
level = 3,
level2,
level3,
}
let l: Level = Level.level2;
l = 3;
console.log(l); // 3
数字枚举编译结果如下,循环遍历要注意。
var Level;
(function (Level) {
Level[Level["level"] = 3] = "level";
Level[Level["level2"] = 4] = "level2";
Level[Level["level3"] = 5] = "level3";
})(Level || (Level = {}));
/*
{
'level': 3,
'level2': 4,
'level3': 5,
'3': 'level',
'4': 'level2',
'5': 'level3',
}
*/
let l = Level.level2;
l = 3;
console.log(l);
接口:interface
TS中的接口用于约束类、对象和函数的标准。 契约(标准)的形式:
- API文档,弱标准
- 代码约束,强标准
接口和类型别名一样,不会出现在编译结果中
接口约束对象
// 接口
interface User{
name: string,
age: number
}
// 接口约束对象
const u:User = {
name: 'vmax',
age: 23
}
// 类型别名
type User = {
name: string,
age: number,
sayHello():void
}
目前来看接口和类型别名没有什么区别, 但是他们约束类的时候就存在区别了。约束类,使用接口。
接口约束函数
- 接口约束对象中的函数
interface User {
name: string,
age: number,
// 接口约束函数
//sayHello: () => void
sayHello():void
}
// 接口约束对象
const u: User = {
name: 'vmax',
age: 23,
sayHello() {
console.log(this.name + this.age);
}
}
u.sayHello();
- 接口直接约束函数
// 接口约束函数
// interface Contition {
// (n: number): boolean
// }
// 等同上面的接口约束函数
type Contition = { // 定界符,若对象中没有其它属性,定界符
(n: number): boolean
}
function sum(n: number[], callback:Contition){
let s = 0;
n.forEach((n)=>{
if(callback(n)){
s += n;
}
})
return s;
}
const res = sum([1,2,3,4], (n)=>n%2!==0);
console.log(res); // 4
接口可以继承
interface A {
a: string;
}
interface B extends A {
b: number
}
const test:B = {
a: '112',
b: 121
}
- 接口继承多个接口, 实现多种接口的组合
interface A {
a: string;
}
interface B {
b: number
}
interface C extends A , B{
c: boolean
}
const test:C = {
a: 'sdf',
b: 1212,
c: true
}
-
使用类型别名可以实现类似的效果,需要通过
&符号,称之为交叉类型。 -
但是略有差异
- 子接口不能覆盖父接口的成员
- 交叉类型会将相同成员的类型进行合并
type A = {
a: string;
}
type B = {
b: number;
}
type C = {
c: boolean;
} & A & B;
const test: C = {
a: 'sdf',
b: 1212,
c: true
}
readonly修饰符关键字
只读的修饰符,不能修改。不再编译结果中;
interface User {
readonly id: string;
readonly arr: ReadonlyArray<string>;
}
const u:User = {
id: '123',
arr: ['12']
}
类
ts中类的属性表示需要用属性列表
TS中通过构造函数this.xxx = xxx会报错,需要通过属性列表来进行实现。
class Person {
// 属性列表
name: string
age: number
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
const per = new Person('vmax', 18);
console.log(per);
- 可以在配置文件中配置
strictPropertyInitialization: true,对象属性初始就会进行更加严格的检查,若在构造函数中少写了属性,就会进行提示。
属性默认值
class Person {
// 属性列表
name: string
age: number
//属性默认值
gender: '男'| '女' = '男'
constructor(name: string, age: number){
this.name = name;
this.age = age;
}
}
const per = new Person('vmax', 18);
console.log(per);
访问修饰符
控制类中的某个成员(属性和方法)的访问权限
- public: 默认的访问修饰符,公开的,所有的代码都可以访问
- private: 私有的,只有在类中使用
- protected:
class Person {
// 只读属性
readonly id: number // 不能改变
// 属性列表
name: string
age: number
// 默认属性
gender: '男'| '女' = '男'
// 可选属性
pid?: string
// 私有属性
private publishNumber: number = 3; // 当天一共可以发布多少篇文章
private currentNumber: number = 0; // 当前可以发布的文章数量
constructor(name: string, age: number){
this.id = Math.random();
this.name = name;
this.age = age;
}
publish(title: string){
if(this.currentNumber < this.publishNumber){
this.currentNumber++;
console.log('可以发布文章'+title);
} else {
console.log('发布文章达到上限');
}
}
}
属性简写
如果某个属性通过构造函数的参数传递,并且不做任何处理,可以简写,加一个修饰符。
class Person {
// 只读属性
readonly id: number // 不能改变
// 默认属性
gender: '男'| '女' = '男'
// 可选属性
pid?: string
// 私有属性
private publishNumber: number = 3; // 当天一共可以发布多少篇文章
private currentNumber: number = 0; // 当前可以发布的文章数量
// 属性简写,如果某个属性通过构造函数的参数传递,并且不做任何处理,可以简写,加一个修饰符
constructor(public name: string, public age: number){
this.id = Math.random();
}
}
访问器
用于控制属性的读取和赋值
- java中处理方式
class Person {
// 只读属性
readonly id: number // 不能改变
// 属性简写,如果某个属性通过构造函数的参数传递,并且不做任何处理,可以简写,加一个修饰符
constructor(public name: string, private _age: number){
this.id = Math.random();
}
setAge(value: number){
if(value < 0){
this._age = 0
} else {
this._age = value;
}
}
getAge(): number {
return Math.floor(this._age);
}
}
const person = new Person('vmax', 1.5);
console.log(person.getAge());
- ts/js
class Person {
// 只读属性
readonly id: number // 不能改变
// 属性简写,如果某个属性通过构造函数的参数传递,并且不做任何处理,可以简写,加一个修饰符
constructor(public name: string, private _age: number){
this.id = Math.random();
}
set age(value: number){
if(value < 0){
this._age = 0
} else {
this._age = value;
}
}
get age(): number {
return Math.floor(this._age);
}
}
const person = new Person('vmax', 1.5);
person.age = 24;
console.log(person.age);
泛型
有时,书写某个函数时,会丢失一些信息(多个位置的类型应该保持一致或有关联的信息)
function take(arr: any[], n: number):any[]{
if(n > arr.length){
return arr;
}
const newArray: any[] = [];
for(let i = 0; i < n; i++){
newArray.push(arr[i]);
}
return newArray;
}
take([1,2,3], 2);
泛型:是指附属于函数、类、接口、类型别名之上的类型
在函数中使用泛型
在函数名后,写<泛型名称>,编译结果不会出现。
泛型相当于是一个变量,在定义时,无法预先知道具体的类型,可以用该变量来代替,只有调用时,才知道类型。
调用时也可以不写泛型,ts会根据传递的参数推断出来。
如果无法完成推导,并且又没有传递具体的类型,默认为空对象。
泛型可以设置默认值。
function take<T>(arr: T[], n: number): T[] {
if (n > arr.length) {
return arr;
}
const newArray: T[] = [];
for (let i = 0; i < n; i++) {
newArray.push(arr[i]);
}
return newArray;
}
// 调用的时候确定类型 string -> T
const res = take<string>(['12', '23', '222'], 2);
console.log(res);
// 调用时也可以不写泛型,ts会根据传递的参数推断出来
const res = take(['12', '23', '222'], 2);
console.log(res);
在类、接口、类型别名使用泛型
在名称后面写上<泛型名称>
类型别名的泛型
// 判断数组中某一项是否满足条件
type callback<T> = (n: T, i: number) => boolean;
function filter<T>(arr: T[], callback: callback<T>): T[] {
const newArray: T[] = [];
arr.forEach((item, i) => {
if (callback(item, i)) {
newArray.push(item);
}
});
return newArray;
}
const arr = [2, 3, 46, 123, 234234];
console.log(filter(arr, (n) => n % 2 !== 0));
接口使用泛型
// 判断数组中某一项是否满足条件
interface callback<T> {
(n: T, i: number): boolean;
}
function filter<T>(arr: T[], callback: callback<T>): T[] {
const newArray: T[] = [];
arr.forEach((item, i) => {
if (callback(item, i)) {
newArray.push(item);
}
});
return newArray;
}
const arr = [2, 3, 46, 123, 234234];
console.log(filter(arr, (n) => n % 2 !== 0));
类中使用泛型
export class ArrayHelper<T> {
constructor(private arr: T[]) { }
take(n: number): T[] {
if (n >= this.arr.length) {
return this.arr;
}
const newArray: T[] = [];
for (let i = 0; i < n; i++) {
newArray.push(this.arr[i]);
}
return newArray;
}
private getRandom(min: number, max: number): number {
const dec = max - min;
return Math.floor(Math.random() * dec) + min;
}
suffle() {
for (let i = 0; i < this.arr.length; i++) {
const targetIndex = this.getRandom(0, this.arr.length);
const temp = this.arr[i];
this.arr[i] = this.arr[targetIndex];
this.arr[targetIndex] = temp;
}
}
}
泛型约束
用于限制泛型的取值。
interface hasNameProperty {
name: string;
}
/**
* 将obj的name属性的首字母大写
* @param obj
*/
function nameToUpperCase<T extends hasNameProperty>(obj: T): T {
obj.name = obj.name.split(' ')
.map(item => { return item[0].toUpperCase() + item.substr(1) })
.join('');
return obj;
}
const o = {
name: "vmax js",
age: 18
}
const newO = nameToUpperCase(o);
console.log(newO.name); // VmaxJS
多泛型
// 将两个类型不一样的数组进行混合
function mixinArray<T, K>(arr1: T[], arr2: K[]): (T | K)[] {
const res: (T | K)[] = [];
for (let i = 0; i < arr1.length; i++) {
res.push(arr1[i]);
res.push(arr2[i]);
}
return res;
}
console.log(mixinArray([1, 2, 3], ['a', 'b', 'c']));
模块化
TS中如何书写模块化语句
TS中,导入和导出模块,统一使用ES6的模块化标准。
注意::导入的时候不要加ts后缀,因为编译结果中不会有ts文件。
编译结果使用的是什么模块化标准
可以监听文件改动并生成编译结果文件。
tsc --watch
是可以配置的,在tsconfig.json中进行配置:
{
"compilerOptions": {
"module": "commonjs", /*编译结果使用什么模块化标准*/
"removeComments": true, /* 编译结果中移除注释 */
"noImplicitUseStrict": true, /*编译结果中移除 use strict*/
"esModulesInterop": true, /*启用es模块化交互非es模块导出*/
"noEmitOnError": true, /*错误时就不生成编译结果*/
}
}
结论:
- 如果编译结果配置的是模块化标准是ES6:没有区别
- 如果配置的是commonjs,导出的声明会变成exports的属性,默认的导出会变成exports的default属性。
解决默认导入的报错
我们使用的模块是通过
module.exports = {}导出的那么就要采用下面的方法。
- 第一种解决办法
import * as fs from 'fs';
fs.readFileSync('./')
- 第二种
import {readFileSync} from 'fs';
fs.readFileSync('./')
- 第三种,改tsconfig.json配置
{
"compilerOptions": {
"esModulesInterop": true, /*启用es模块化交互非es模块导出*/
}
}
如何在TS中书写commonjs模块化代码
在TS中书写commonjs模块化代码,并获得类型检查。
导出:
export = {
}
导入:
import xx = require('./');
模块解析
模块解析:应该从什么位置寻找模块
TS中,有classic(不建议使用)和node解析策略(相对路径和非相对路径)
类型兼容性
对象类型
采用鸭子辨形法来判断两个类型约束是否可以赋值。
类型断言:as,我们确定是什么类型就可以直接断言
interface Duck {
sound: "gagaga",
swin(): void
}
const person = {
name: "vmax",
age: 18,
sound: "gagaga" as "gagaga",
swin(){
console.log(this.name+' '+ this.sound);
}
}
//TS采用鸭子辨形法来判断两个类型约束是否可以赋值。
const duck:Duck = person;
console.log(duck.swin())
当直接使用对象字面量赋值,会进行更加严格的判断。有什么就写什么,你都字面量赋值了,那你肯定知道要用什么。
- duck进行类型定义的时候,有什么属性就只能使用什么。所以不是duck上面的就会报错。
函数类型
- 关注函数的参数和返回值,参数不能多,可以少
- 返回值:定义了返回类型,那一定要返回,不要返回,就随意。