TypeScript开发环境搭建
- 安装node.js
- 全局安装
npm i -g typescript
- 创建一个ts文件
- 使用tsc对ts文件进行编译
tsc xxx.ts
基本类型
类型声明
语法:
let 变量: 类型;
let 变量: 类型 = 值;
function fn(参数: 类型, 参数: 类型): 类型 {
...
}
TS中的类型
类型 | 例子 | 描述 |
---|---|---|
number | 1, -33, 2.5 | 任意数字 |
string | ‘hi’, “hi”, hi | 任意字符串 |
boolean | true、false | 布尔值true或false |
字面量 | 其本身 | 限制变量的值就是该字面量的值 |
any | * | 任意类型 |
unknown | * | 类型安全的any |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值 |
object | {name:‘孙悟空’} | 任意的JS对象 |
array | [1,2,3] | 任意JS数组 |
tuple | [4,5] | 元素,TS新增类型,固定长度数组 |
enum | enum{A, B} | 枚举,TS中新增类型 |
number
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;
boolean
let isDone: boolean = false;
string
let color: string = "blue";
color = 'red';
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}.
I'll be ${age + 1} years old next month.`;
字面量
通过字面量可以确定变量的取值范围
// color只可以取'red','blue','black'其中一个值
let color: 'red' | 'blue' | 'black';
// num只可以取1,2,3,4,5其中一个值
let num: 1 | 2 | 3 | 4 | 5;
any(任意类型)
相当于该变量关闭了TS的类型检测
let d: any = 4;
d = 'hello';
d = true;
unknown(类型安全的any)
let abc:unknown = true;
let s:string = 'hellow';
// unknow类型的变量不能直接赋值给其他变量
if(typeof s == 'string') { // 只有s为string时才能够赋值
abc = s;
}
void(空值或undefined)
在函数中,表示函数没有返回值
function fun():void {
return
}
never(永远不会有值)
比如报错的函数,因为它永远不会有返回值
function error(message: string): never {
throw new Error(message);
}
object
let a: object;
a = {};
// 语法:{属性名:属性值,属性名:属性值}
// 属性名后面加一个问好,表示这个属性可选
let b:{name: string, age?: number};
b = {name: 'aaa'};
let c:{name: string, [aaa: string]: any};
c = {name: 'adf', age: 18, show: true};
// 声明d为一个对象,有两个参数类型都是number,并且返回值也是number类型
let d: (a: number, b: number)=>number;
array
语法:
- 类型[];
- Array<类型>;
// 表示字符串类型数组
let a: string[];
a = ['1', '2', '3'];
// 表示数值类型数组
let b: number[];
b = [1, 2, 3];
// 表示数值类型数组
let c:Array<number>;
c = [1, 2, 3];
tuple(元组)
元组是固定长度的数组
// 声明一个长度为4的数组,并且规定了数据类型
let a: [string, string, boolean, number];
enum(枚举)
// 枚举(把可能的情况列举出来)
enum Gender {
nan = 0,
nv = 1
}
let i:{name: string, gender: Gender};
i = {
name: '刘德华',
gender: Gender.nan
}
类型或运算
// 声明的j既可以是string类型,又可以是number类型
let j: string | number;
类型别名
可以简化类型的使用
type mytype = 1 | 2 | 3 | 4 | 5;
let a: mytype;
a = 1;
a = 6; // 报错
类型断言
告诉解析器变量的实际类型
语法:
- 变量 as 类型
- <类型>变量
a = b as string;
a = <string>b;
接口
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。我们使用接口(Interfaces)来定义对象的类型。接口是对象的状态属性和行为方法的描述
语法:
interface xxx {}
可选属性
可选属性名字定义的后面加一个 ?
符号
只读属性
在属性名前用 readonly
来指定只读属性
例:定义人这个接口,id是只读的,sex是可选的
interface People {
readonly id: number,
name: string,
age: number,
sex?: string
}
函数类型
通过接口的方式作为函数的类型来使用
interface Search {
// 定义一个调用签名
(source: string, subString: string): boolean
}
// 查找字符串函数
const searchString: Search = function(source: string, subString: string): boolean {
return source.search(subString) > -1;
}
searchString('你好','你'); // true
searchString('你好','w'); // false
类类型
TypeScript能够用接口来明确的强制一个类去符合某种契约。
-
类可以实现一个接口
// 定义接口 interface Alarm { alert(): any; } // 实现接口 class Car implements Alarm { alert() { console.log('Car alert'); } } // 创建对象 const car = new Car();
-
还可以实现多个接口
// 定义接口 interface Alarm1 { alert(): any; } // 定义接口 interface Warn { warn(): any; } // 实现接口 class Car implements Alarm,Warn { alert() { console.log('Car alert'); } warn() { console.log('Car warn'); } } // 创建对象 const car = new Car();
-
接口继承
interface error extends Alarm, Light {};
接口和接口之间叫继承,用extends关键字,类和接口之间叫实现,用implements关键字
类
TS中类的定义及使用
class Person {
name: string
age: number
sex: string
// 构造函数
constructor(name: string, age: number, sex:string = "男") {
this.name = name;
this.age = age;
this.sex = sex;
}
// 实例化方法
hello(str: string) {
console.log(`我叫${this.name},${str}`);
}
}
const person = new Person('张三', 18, '男');
person.hello('大家好呀')
类的继承
A类继承了B类,A类叫子类,B类叫基类
子类---->派生类
基类---->超类
一旦发生了继承的关系,就出现了父子类的关系
// 定义B类(父类,超类)
class BClass {
name: string
age: number
constructor(name:string , age:number) {
this.name = name;
this.age = age;
}
hello(str:string) {
console.log(`我叫${this.name},${str}`);
}
}
// 定义A类(子类)
class AClass extends BClass {
constructor(name:string , age:number) {
// 调用父类中的构造函数,使用的super
super(name, age);
}
hello(){
super.hello('我是B类的子类A类')
}
}
const B = new BClass('张三', 18);
B.hello('bbbbb');
const A = new AClass('李四', 18);
A.hello();
类和类之间有继承关系,需要使用extends关键字
子类中调用弗雷德构造函数,使用super()关键字(包括调用方法),子类中可以重写父类的方法
多态
父类型的引用指向子类型的对象,不同类型的对象针对相同的方法,产生了不同的行为
// 定义B类(父类,超类)
class BClass {
name: string
age: number
constructor(name:string) {
this.name = name;
}
hello(str:string) {
console.log(`我叫${this.name},${str}`);
}
}
// 定义A类(子类)
class AClass extends BClass {
constructor(name:string) {
// 调用父类中的构造函数,使用的super
super(name);
}
hello(str:string = '我是B类的子类A类'){
super.hello(str)
}
}
// 定义C类(子类)
class CClass extends BClass {
constructor(name:string) {
// 调用父类中的构造函数,使用的super
super(name);
}
hello(str:string = '我是B类的子类C类'){
super.hello(str)
}
}
const B = new BClass('张三');
B.hello('bbbbb');
const A = new AClass('李四');
A.hello('aaaaa');
const C = new CClass('王五');
C.hello('ccccc');
// 用父类类型创建子类的对象
const AA = new AClass('李四1号');
AA.hello('aaaaa'); // 我叫李四1号,aaaaa
const CC = new CClass('王五1号');
CC.hello('ccccc'); // 我叫王五1号,ccccc
function helloWord(B: BClass) {
B.hello('函数中调用B.hello');
}
helloWord(AA); // 我叫李四1号,函数中调用B.hello
helloWord(CC); // 我叫王五1号,函数中调用B.hello
公共、私有、受保护的修饰符
修饰符:描述类中的成员(属性,构造函数,方法)的可访问性
默认为 public
代表公共的,任何位置都可以访问类中的成员
class Person {
name: string
constructor(name:string) {
this.name = name;
}
hello() {
console.log(`我的名字叫${this.name}`);
}
}
const person = new Person('张三');
console.log(person.name); // 张三
person.hello(); // 我的名字叫张三
private
代表私有的,类中的成员使用private修饰,外部是无法访问这个成员数据的,子类中也无法访问成员数据的
class Person {
private name: string
constructor(name:string) {
this.name = name;
}
hello() {
console.log(`我的名字叫${this.name}`);
}
}
const person = new Person('张三');
console.log(person.name); // 属性“name”为私有属性,只能在类“Person”中访问
person.hello();
protected
代表受保护的,在外部是无法访问的,在派生类中仍然可以访问
readonly
对类中的属性成员进行修饰,修饰后,该属性成员,就不能在外部被随意的修改了
- 构造函数中,可以对只读的成员进行修改
- 类中的方法也不能修改readonly修饰的成员属性值
class Person {
readonly name: string
constructor(name:string) {
this.name = name;
}
hello() {
console.log(`我的名字叫${this.name}`);
this.name = '不告诉你'; // 报错:类中的方法也不能修改readonly修饰的成员属性值
}
}
const person = new Person('张三');
console.log(person);
console.log(person.name);
person.name = '不告诉你'; // name是只读属性,不可以被修改
- 构造函数中的参数一旦使用了readonly修饰后,该参数可以叫参数属性,并且在类中就有了一个该参数的属性成员
- 构造函数中的参数也可以使用public、private、protected来修饰参数
class Person {
// 构造函数中的参数一旦使用了readonly修饰后,该参数可以叫参数属性,并且在类中就有了一个该参数的属性成员
constructor(readonly name:string) {
}
}
const person = new Person('张三');
console.log(person);
console.log(person.name);
存取器
让我们可以有效控制对象中成员的访问,通过getters和setters来进行操作
class FullName {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// 读取器
get fullName() {
return this.firstName + '-' + this.lastName;
}
// 设置器
set fullName(val) {
this.firstName = val.substr(0, 1);
this.lastName = val.substr(1, 1);
}
}
const myName = new FullName('张', '三');
console.log(myName.fullName); // 张-三
myName.fullName = '李四';
console.log(myName.fullName); // 李-四
静态属性
在类中通过static修饰的属性或者方法,那么就是静态的属性及静态的方法,也成静态成员
- 不能通过实例对象直接调用静态属性来使用
class Person {
static str: string = 'hello'
constructor(str:string) {
}
static hello() {
console.log('aaaaa')
}
}
Person.str = 'hello';
// 通过类名.静态属性的方式来访问该成员数据
console.log(Person.str); // hello
// 通过类名.静态属性的方式来设置该成员数据
Person.str = '你好';
console.log(Person.str); // 你好
// 通过类名.静态方法的方式来调用内部的静态方法
Person.hello(); // aaaaa
抽象类
- 包含抽象方法,抽象方法不能有任何的具体内容的实现,也可以包含实例方法
- 抽象类是不能被实例化,是为了让子类进行实例化及实现内部的抽象方法
abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法
abstract class Animal {
abstract name:string;
abstract eat()
run() {
console.log('我在跑')
}
}
class Dog extends Animal {
// 实现抽象类中的方法
name: string = '大黄';
eat() {
console.log('狗狗爱吃骨头');
}
}
class Cat extends Animal {
name: string = "汤姆";
eat() {
console.log('猫抓老鼠吃');
}
}
const dog = new Dog();
dog.eat(); // 狗狗爱吃骨头
dog.run(); // 我在跑
console.log(dog.name); // 大黄
const cat = new Cat();
cat.eat(); // 猫抓老鼠吃
cat.run(); // 我在跑
console.log(cat.name); // 汤姆
函数
封装了一些重复使用的代码,需要的时候之间调用
函数类型
为函数定义类型
// 参数x和y为number类型,返回值为number类型
function add(x: number, y: number): number {
return x + y
}
let myAdd = function(x: number, y: number): number {
return x + y
}
书写完整函数类型
let myAdd2: (x: number, y: number) => number =
function(x: number, y: number): number {
return x + y
}
可选、默认和剩余参数
- 可选参数:函数在声明时,内部的参数用?进行修饰,那么该参数可以传入也可以不传
- 默认参数:函数在声明时,内部的参数有自己的默认值
- 剩余参数:放在函数声明的时候所有的参数的最后
// 可选参数,默认参数
function buildName(firstName: string='诸葛', lastName?: string): string {
if (lastName) {
return firstName + '-' + lastName
} else {
return firstName
}
}
console.log(buildName('诸葛', '孔明')); // 诸葛-孔明
console.log(buildName('诸葛')); // 诸葛
console.log(buildName()); // 诸葛
// 剩余参数
function info(x: string, ...args: string[]) {
console.log(x, args);
}
info('abc', 'c', 'b', 'a'); // abc ["c", "b", "a"]
函数重载
函数重载: 函数名相同, 而形参不同的多个函数
重载函数声明:当调用函数传入参数类型错误时报错
// 重载函数声明
function add (x: string, y: string): string
function add (x: number, y: number): number
// 定义函数实现
function add(x: string | number, y: string | number): string | number {
if (typeof x === 'string' && typeof y === 'string') {
return x + y
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y
}
}
console.log(add(1, 2)); // 3
console.log(add('a', 'b')); // ab
泛型
在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性
泛型使用场景
创建一个函数, 实现功能: 根据指定的数量 count
和数据 value
, 创建一个包含 count
个 value
的数组
function createArray2 <T> (value: T, count: number) {
const arr: Array<T> = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const arr3 = createArray2<number>(11.156165, 3)
console.log(arr3); // [11.156165, 11.156165, 11.156165]
console.log(arr3[0].toFixed(2)); // 11.16
const arr4 = createArray2<string>('addda', 3)
console.log(arr4); // ["addda", "addda", "addda"]
console.log(arr4[0].split('')); // ["a", "d", "d", "d", "a"]
多个泛型参数的函数
function swap <K, V> (a: K, b: V): [K, V] {
return [a, b]
}
const result = swap<string, number>('a3dbc', 123)
console.log(result[0].length, result[1].toFixed()); // 5 "123"
泛型接口
- 在定义接口时, 为接口中的属性或方法定义泛型类型
- 在使用接口时, 再指定具体的泛型类型
需求:定义一个类,用来存储用户的相关信息(id,名字,年龄),通过一个类的实例对象调用add的方法可以添加多个用户信息对象,调用getById方法可以通过id查找用户
interface IbaseCRUD <T> {
data: T[]
add: (t: T) => void
getById: (id: number) => T
}
class User {
id?: number; //id主键自增
name: string; //姓名
age: number; //年龄
constructor (name, age) {
this.name = name
this.age = age
}
}
class UserCRUD implements IbaseCRUD <User> {
data: User[] = []
add(user: User): void {
user = {...user, id: Date.now()}
this.data.push(user)
console.log('保存user', user.name)
}
getById(id: number): User {
return this.data.find(item => item.id===id)
}
}
const userCRUD = new UserCRUD()
userCRUD.add(new User('tom', 12)) // 保存user tom
userCRUD.add(new User('tom2', 13)) // 保存user tom2
console.log(userCRUD.data) // [ {name: "tom", age: 12, id: 1609853726232}, {name: "tom2", age: 13, id: 1609853726232}]
泛型类
在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型
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
}
let myGenericString = new GenericNumber<string>()
myGenericString.zeroValue = 'abc'
myGenericString.add = function(x, y) {
return x + y
}
console.log(myGenericString.add(myGenericString.zeroValue, 'test')); // abctest
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 12)); // 12
泛型约束
interface Lengthwise {
length: number;
}
// 指定泛型约束
function fn2 <T extends Lengthwise>(x: T): void {
console.log(x.length)
}
console.log(fn2('dsfjkalkdsfj')); // 12
// console.log(fn2(13213)); 报错,因为number类中没有length属性
声明文件
- 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
- 声明语句: 如果需要ts对新的语法进行检查, 需要要加载了对应的类型说明代码
- 下载声明文件:
npm install @types/jquery --save-dev
import jQuery from 'jquery'
jQuery('选择器')
内置对象
ECMAScript 的内置对象
let b: Boolean = new Boolean(1)
let n: Number = new Number(true)
let s: String = new String('abc')
let d: Date = new Date()
let r: RegExp = /^1/
let e: Error = new Error('error message')
BOM 和 DOM 的内置对象
const div: HTMLElement = document.getElementById('test')
const divs: NodeList = document.querySelectorAll('div')
document.addEventListener('click', (event: MouseEvent) => {
console.dir(event.target)
})
const fragment: DocumentFragment = document.createDocumentFragment()
TS编译选项
自动编译文件
tsc xxx.ts -w
自动编译整个项目
- 先在项目根目录下创建一个ts的配置文件tsconfig.json
- tsconfig.json是一个JSON文件,添加配置tsconfig.json文件
- 使用tsc指令,则可以自动将当前项目下的所有ts文件编译为js文件
- tsconfig.json可以使用注释
配置选项
一个*
表示任意文件,两个*
表示任意目录
include:指定那些ts文件需要被编译
- 默认值:["**/*"]
{
"include":[
// src文件下任意目录中的任意文件都会被编译
"src/**/*", "tests/**/*"
]
}
exclude:指定那些ts文件不需要被编译
- 默认值:[“node_modules”, “bower_components”, “jspm_packages”]
compilerOptions
- 编译选项是配置文件中非常重要也比较复杂的配置选项
- 在compilerOptions中包含多个子选项,用来完成对编译的配置
- 例:
"compilerOptions": {
"target": "ES6",
"lib": ["ES6", "DOM"],
"outDir": "dist",
"outFile": "dist/aa.js",
"allowJs": true,
"checkJs": true,
"removeComments": true,
}
target:设置ts代码编译的目标版本
- 可选值:ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext
lib:指定代码运行时所包含的库
- 可选值:ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost …
module:设置编译后代码使用的模块化系统
- 可选值:CommonJS、UMD、AMD、System、ES2020、ESNext、None
outDir:编译后文件的所在目录
outFile:将所有的文件合并编译为一个js文件(慎用,和module容易冲突)
allowJs:是否对js文件编译
- 默认值:false
checkJs:是否检查js代码
- 默认值:false
removeComments:是否移除注释
- 默认值:false
noEmit:不生成编译后的文件
- 默认值:false
noEmitOnError:当有错误时不生成编译文件
- 默认值:false
strict:启用所有的严格检查
- 默认值:true
alwaysStrict:严格模式对代码进行编译
- 默认值:false
noImplicitAny:禁止隐式的any类型
- 默认值:false
noImplicitThis:禁止类型不明确的this
- 默认值:false
strictNullChecks:严格的空值检查
- 默认值:false
extends:定义被继承的配置文件(不常用)
files:指定被编译文件的列表(不常用)
只有需要编译的文件少时才会用到
"files": [
"core.ts",
"sys.ts",
"tsc.ts"
]
使用webpack打包TS
下载依赖
npm add -D typescript
npm add -D webpack webpack-cli
npm add -D webpack-dev-server
npm add -D html-webpack-plugin clean-webpack-plugin
npm add -D ts-loader
npm add -D cross-env
配置打包命令
"dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
运行与打包
npm dev
npm build