TypeScript(简称:TS)是JavaScript的超集
(JS有的TS都有)。
TypeScript = Type + JavaScript(为JS添加了类型系统
)
安装步骤:
打开VSCode终端。
输入安装命令:npm i -g typescript 敲回车,来安装(注意:需要联网)。
typescript
:就是用来解析TS的工具包。提供了tsc命令,实现了TS —> JS的转化。
npm
:用来安装前端开发中用到的包,是安装Node.js时自动安装的。
i
(install):表示安装。
-g
(–global):全局标识,可以在任意目录中使用该工具。
安装命令:npm i -g ts-node
使用方式:ts-node hello.ts
创建 hello.ts 文件,console.log(“hello TS”)
在终端进行执行:tsc hello.ts
会生成一个 hello.js 文件
// 单行
/* 多行 */
- 限定了数据类型后,后续的赋值必须符合属性类型,否则编译报错!
- 基础类型如果首字母大写, 赋值的时候可以采用实例化的形式
数值类型- number(整数和浮点数)
除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
string 单引号(推荐)或双引号或``都可以
let name: string = “bob”;
name = “smith”;
布尔值 boolean true/false值
let isDone: boolean = false;
表示此处应该有值, 但是现在没有值
申明了一个变量, 但是未赋值, 这个变量就是undefined
在TS中, 可以给已有类型的基本数据类型变量, 重新赋值undefined
let u:undefined = undefined
表示此处不应该有值, 没有值了
在TS中, 可以给已有类型的引用数据类型变量, 重新赋值null
let n: null = null
js中属于对象的太多,添加object后等于是没有添加类型限制,开发很少用
示例1:
// {}也表示对象,表示变量o必须是一个对象类型,在其中可以指定属性类型
// 以下示例表示对象o中只能有指定的属性名称和类型,以及个数
// 在属性名后面加?,表示该属性可选
let o: {
name: string,
age: number,
sex?: string
};
o = {name: '孙悟空',age: 18} // ok
o = {name: '孙悟空'} // 报错,少了一个age属性
o = {name: '孙悟空',age: 18,sex: '男'} // ok
示例2:
// 需求:在一个对象中,我只有一个属性是有要求的,其他的属性名称、类型、个数都未知
let o: {
name: string,
[propertyName: string]: any
}
/*
* 以上代码,[propertyName: string] 表示属性名是string(对象的属性名都是字符串类型)
* [propertyName: string]: any 表示该对象中的属性是任意类型,
* 如果any是string,表示该对象中的属性都必须是string类型
*/
实例3:
function getObj(obj: object):object {
// do something
return {
name:"卡卡西"
}
}
console.log(getObj({name:"佐助"}))
console.log(getObj(new String('字符串')))
指定对象中, 函数的类型
写法一: sayHi(name:string):void
const user: {
name:string
sayHi(name:string):void
}
user = {
name:'张三'
sayHi(name) {
console.log('你好,我叫' + name)
}
}
写法二: add(n1:number, n2:number) => number
const user: {
name:string
add(n1:number, n2:number) => number
}
user = {
name:'李四'
add: function(a, b){
return a+b
}
}
数组; 开发中, 数组中的元素为同一类型
语法1:类型[]
// 表示数值数组
let list: number[];
let list: Array;
let list: number[] = [1, 2, 3];
语法2:使用泛型写法,Array<元素类型>
:
// 表示字符串数组
let list: string[];
let list: Array
let list: Array = ['a', 'b', 'c'];
扩展: 申明二维数组
let arr:number[][] = [ [1,2,3],[7,8,9] ]
单独限制出入参类型
箭头函数的写法常用
// 箭头函数
let addFunc = (num1:number, num2:number):number => {
return num1 + num2
}
addFunc = function(n1, n2): number{
return n1 + n2;
}
console.log(addFunc(2, 6)) // 8
// 函数声明
function add(num1:number, num2:number):number {
return num1 + num2
}
console.log(add(2,2)) // 4
使用类型别名
type AddFun = (num1:number, num2:number):number
const add:AddFun = (num1, num2) => {
return num1 + num2
}
注意: 只适合函数表达式
// 函数表达式
let fun1 = function(){}
// 函数声明
function fun2(){}
使用接口
为了使用接口表示函数类型,需要给接口定义一个调用签名
interface ISearchFunc {
// 定义调用签名
(source:string, subString:string): boolean
}
// 定义一个函数,该类型即为上面的接口
const searchString: ISearchFunc = function (source:string, subString:string): boolean {
return source.search(subString) > -1
}
// 调用
console.log(searchString(‘道阻且长,行则将至’,‘长’)) // true
function(name: string, age: number = 18){
}
添加 ?
,接口也适用(表示属性可省略)
在入参变量名后面添加 ?
, 注意可选参数必须放在最后, 可以是一个或多个可选参数
函数体中对可选参数需使用非空校验相关逻辑
// 表示入参age, sex,可省略
function(name: string, age?: number, sex?: string){
}
任意类型(any可略) , 尽量不要使用any
设置为该类型后等于对变量关闭了TS的类型检测, 直接让它们通过编译阶段的检查
可以赋值给任意类型变量
// eg1:多次赋值均不报错
let a: any = 123
a = "str"a = true
// eg1:它可以赋值给其他任意类型,使其他类型也称为any类型
// 简单理解就是b是any类型,经过下面赋值,使a也成了any类型
let b:number = 123
b = a
未知类型的值; 编码中,尽量使用unknown代替any
和any的异同点
一般来说,这个类型并不是开发者手写的,是网络传来的,需要配和断言使用(在使用的时候需要明确这个变量的类型,可以多次指定类型)
type A = {name:string}
type B = {age:number}
// 模拟ajax传递过来的数据
let c: unknown = JSON.parse("{'name':"Tom"}")
let var1 = (c as A).name
let var2 = (c as B).age
空值 , 用于函数返回值 表示函数没有返回值(常用)
实际编码中,其实可以return null或者return undefined,但是没有意义,应该是语法上的兼容而已
function fn(): void{
// do something
return; // 或者不写return
}
语法:
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
eg:
type Code = 1|2|3|undefined
let dir:Code // 表示dir的取值只能是”1,2,3,undefined“ 四者之一
switch(dir){
case 1:
break;
case 2:
break;
case 3:
break;
case undefined:
break;
default:
console.log('如果进入该分支,表示dir的值不在”1,2,3,undefined“中, 即为never类型')}
固定长度的数组(元素类型可以不一致)
结合数组理解, 最大的区别的是元组的长度固定, 且每个索引对应的元素类型固定
对于值, 可以get, set, update, 注意变更的值只能是规定的类型, update可以用arr[0] = newValue, set可以用arr.push(newValue)
// 表示数组中只能有2个string的元素,长度不可变
let arr1: [string, string]
let arr2: [number, string]
eg1:
enum Gender {
Male,
Female
}
let tom: Gender = Gender.Male
console.log(tom) // 0
console.log(Gender.Male, Gender.Female) // 0 1
如果枚举值是数字值, 可以用以下写法
enum Color {
Red = 6,
Green = 7,
Blue = 8
}
let c:string = Color[6] // Red
把多个类型联合为一个类型 (表示取值可以为多种类型中的一种)
用 |
隔开
// 表示入参param可以是number或者string类型
// 出参为string类型
function getSomeThing(param: number|string):string {
return param+’’
}
getSomeThing(123)
getSomeThing(“字符串”)
type MyType = 1 | 2 | 3 | 4 | 5;
let a: MyType = 2;
let b: MyType = 4;
// 数组中,既可以有字符串,也可以有数字
let arr:(number|string)[]
arr = [123, ‘哈哈’]a
rr = [66, 88, 99]
用来获取一个变量或对象的类型
interface Person {
name: string;
age: number;
}
const test: Person = { name: "Tom", age: 20 };
type Sys = typeof test; // type Sys = Person
在上面代码中,我们通过 typeof 操作符获取 test 变量的类型并赋值给 Sys 类型变量,之后我们就可以使用 Sem 类型:
const Vivo: Sys = { name: "Lili", age: 3 }
也可以对嵌套对象执行相同的操作:
const kakaxi = {
name: "kakaxi",
age: 27,
address: {
province: '湖南',
city: '长沙'
}
}
type Kakaxi = typeof kakaxi;
/* 以下即为Kakaxi类型
type Kakaxi = {
name: string;
age: number;
address: {
province: string;
city: string;
};
}
*/
此外, typeof 操作符除了可以获取对象的结构类型之外,它也可以用来获取函数对象的类型,比如
function toArray(x: number): Array {
return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]
同java中的类型转换
主要用于当 TypeScript 推断出来类型并不满足你的需求,你需要手动指定一个类型。
只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
有两种语法,关键字
as
和标签<>
两种,
- <类型>值
- 值 as 类型
由于
<>
会与JSX
语法冲突,建议统一使用as
来进行类型断言。
as
语法(推荐):
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
<>
语法:
let someValue: any = "this is a string";
let strLength: number = (someValue).length;
!
表示此处一定有值, 跳过类型检查如果编译器不能够去除 null 或 undefined,可以使用非空断言 !
手动去除。
function fixed(name: string | null): string {
function postfix(epithet: string) {
return name!.charAt(0) + '. the ' + epithet; // name 被断言为非空
}
name = name || "Bob"
return postfix("great")
}
代码解释:
第 2 行,postfix()
是一个嵌套函数,因为编译器无法去除嵌套函数的 null (除非是立即调用的函数表达式),所以 TypeScript 推断第 3 行的 name
可能为空。
第 5 行,而 name = name || "Bob"
这行代码已经明确了 name
不为空,所以可以直接给 name 断言为非空(第 3 行)。
双重断言极少有应用场景,只需要知道有这种操作即可:
interface User {
nickname: string,
admin: boolean,
group: number[]
}
const user = 'Evan' as any as User
代码解释: 最后一行,使用 as 关键字进行了两次断言,最终变量 user 被强制转化为 User 类型。
为任意类型取别名, 语法: type 别名 = 类型
使用场景: 当同一类型(复杂)被多次使用时, 可以通过类型别名, 简化对该类型的编码
需要使用关键字 type
, 别名命名, 建议首字母大写
type CustomArray = (number|string)[]
let arr1:CustomArray = [1, ‘啊’, 6]
let arr2:CustomArray = [‘x’, ‘y’, 8]
// 表示a可以是string或number类型
let a: string | number;
扩展&使用
let j:{name: string} & {age: number}
j = {
name: '卡卡西',
age: 25
}
j = {name: '鸣人'} // 报错,删了一个age属性
只能单继承, 但是可以多级继承
class A {
name:string;
constructor(name:string){
this.name = name
}
}
class B extends A {
age:number;
constructor(age:number, name:string){
// 必须先用super调用父类构造器
super(name)
this.age = age
}
}
class C extends B {
sex:string;
constructor(sex:string, age:number, name:string){
super(age,name)
this.sex = sex
}
}
const instance = new C('张三', 18, '男')
console.log(instance)
// C { name: '男', age: 18, sex: '张三' }
接口可以多继承
interface A {
id: number
}
interface B {
code: string
}
interface C extends A,B {
isFlag: boolean
}
const test:C = {
id:200,
code:'凌凌漆',
isFlag:false
}
console.log(test)
// { id: 200, code: '凌凌漆', isFlag: false }
- 实现是对接口、抽象类中规定的方法, 进行具体的编码
- 实现可以多实现
在TS中, 这三者的概念, 和Java中一样, 这里只用最直白简洁的语言进行阐述.
##抽象类
开发不常用
里面既可以有具体方法, 也可以有抽象方法
既是对子类, 公共代码的封装; 也是对子类, 需要具体编码的方法的约束
可以结合上述两者的功能, 对其理解
类比java,可看做一种强制性的规范,一种约束
interface IPerson {
firstName:string
lastName:string
}
function showFullName (person:IPerson) {
return ${person.firstName}_${person.lastName}
}
let kakaxi:IPerson {
firstName:‘旗木’
lastName:‘卡卡西’
}
console.log(showFullName(kakaxi))
// 旗木_卡卡西
和类的继承是一个概念
TS支持继承多个接口
interface point2D {
x: number
y: Number }
interface point3D {
z: number
}
interface point4D extends point2D, point3D {
w: number
}
const pointTest: point4D = {
x: 100,
y: 200,
z: 300,
w: 400
}
console.log(pointTest)
readonly 只读
可省略
interface IAnimals {
// id是只读的number类型
readonly id:number
name:string
age:number
// sex可省略
sex?:string
}
const dog:IAnimals = {
id: 1,
name: ‘来福’,
age: 2,
sex: ‘男’
}
- 接口和接口之间:继承
- 类和接口之间:实现
interface IFly {
fly:()
}
// 定义一个类,这个类的类型就是上面定义的接口
class Person implements IFly {
fly() {
console.log("飞上天,和太阳肩并肩")
}
}
const p1 = new Person()
p1.fly()
interface IFly {
fly:()
}
interface ISwim {
swim:()
}
class Person implements IFly,ISwim {
fly() {
console.log("会飞天")
},
swim() {
console.log("会游泳")
}
}
const p2 = new Person()
p2.fly()
p2.swim()
interface TMyFlyAndSwim extends IFly,ISwim {}
class Person implements TMyFlyAndSwim {
fly() {
console.log("会飞天")
},
swim() {
console.log("会游泳")
}
}
const p3 = new Person()
p2.fly()
p2.swim()
同比Java
super关键字可以调用父类的构造器,也可以调用父类中的实例函数
class Person {
name:string
age:number
gender:string
sayHi(food:string){
console.log(`我是${this.name},喜欢${food}`)
}
constructor(name:string,age:number,gender:string) {
this.name = name
this.age = age
this.gender = gender
}
}
class Student extends Person {
constructor(
name:string,
age:number,
gender:string) {
// 使用super,调用父类的构造器实例化
super(name,age,gender) {
}
// 调用父类中的方法
sayHi(food:string) {
super.sayHi(food)
}
}
const p1 = new Person("张三",25,"男")
p1.sayHi('西瓜') // 我是张三,喜欢西瓜
const s1 = new Student('小明',18,'男)
s1.sayHi('草莓') // 我是小明,喜欢草莓
父类引用,指向子类对象
不同类型的对象,针对相同的方法,产生了不同的行为
class Animal {
name:string
constructor(name:string){
this.name = name
}
run(distance:number) {
console.log(${this,name}跑了${distance}米远的距离
)
}
}
class Dog extends Animal {
constructor(name:string) {
super(name)
}
// 重写run函数
run(distance:number) {
console.log(${this,name}跑了${distance}米远的距离-----dog
)
}
}
class Pig extends Animal {
constructor(name:string) {
super(name)
}
// 重写run函数
run(distance:number) {
console.log(${this,name}跑了${distance}米远的距离======pig
)
}
}
const ani:Animal = new Animal(‘动物’)
ani.run()
const dog:Animal = new Dog(‘旺财’)
dog.run()
const pig:Animal = new Pig(‘佩奇’)
pig.run()
// 使用多态
const dog1:Animal = new Dog(‘小狗’)
dog1.run()
const pig1:Animal = new Dog(‘小猪’)
pig1.run()
用于描述类中的成员(属性、构造器、函数)的可访问性
泛型就是解决 类、接口、方法的复用性,以及对不特定数据类型的支持
函数名称后加<>
, 一般使用符号 T
占位, T代表具体的类型
入参、出参的类型一般具有关联性
function identity(…arg: T[]): T[] {
return […arg]
}
console.log(identity(1,2,3))
keyof 用于获取某种类型的所有键,其返回类型是联合类型
代码释义:
表示只能是传入OOO类型, OOO类型中的属性之一(“id” | “code” | “age”)
let obj = {
id:"100",
code:996,
age:18
}
type OOO = typeof objfunction main(obj:T, prop1: P1) {
console.log(obj[prop1]);
}
main(obj, 'id'); // '100'
main(obj, 'code'); // 996
main(obj, 'age'); // 18
泛型可以表示任意类型, 但是实际编码中, 有时候可以知道类型大致的范围,
为了避免使用出错, 给泛型限定类型的使用范围, 这就是泛型约束(缩小类型的取值范围)
eg1: 限制出入参为数组, 数组元素类型不固定
function func(param: T[]): T[] {
return param
}
func([1,2,3,4])
eg2: 传入的类型, 必须含有 length 属性
interface ILength {
length: number
}
function getId(param: T): T {
console.log(param.length)
return param
}
// 虽然字符串和数组并没有继承 ILength, 但自身拥有length属性
console.log(getId('农夫山泉'));
console.log(getId([11,33,44]));
eg2: 传入的类型是一个对象, 必须拥有name, age属性
interface IUser {
name: string
age: number
}
function func(param: T): T {
console.log(param.name)
console.log(param.age)
return param
}
interface IUser {
getId: (param: T) => T
}
Partial
Readonly
Pick
Partial 用来构造(创建)一个类型,将 Type 的所有属性设置为可选。
type Props = {
id: string
children: number[]
}
type PartialProps = Partial
解释:构造出来的新类型 PartialProps 结构和 Props 相同,但所有属性都变为可选的。
Readonly 用来构造一个类型,将 Type 的所有属性都设置为 readonly(只读)。
type Props = {
id: string
children: number[]
}
type ReadonlyProps = Readonly
解释:构造出来的新类型 ReadonlyProps 结构和 Props 相同,但所有属性都变为只读的。
let props: ReadonlyProps = {
id: ‘1’,
children: []
}
// 错误演示
props.id = ‘2’
当我们想重新给 id 属性赋值时,就会报错:无法分配到 “id” ,因为它是只读属性。
Pick
interface Props {
id: string
title: string
children: number[]
}
type PickProps = Pick
解释: