js缺陷:对于标识符没有任何的类型缺陷,有安全隐患
ts始于js,归于js,拥有js最新的特性
类型校验适合开发大型项目
Angular、Vue3、VScode、and-design使用了ts重写
ts使用webpack搭建,或者使用ts-node插件搭建
npm install ts-node -g
npm install tslib @types/node -g
通过ts-node ...来运行ts代码
类型推导
- let 类型推导:通用类型
- const 类型推导:字面量类型
let n: null = null
let u: undefined = undefined
let hello: string = 'hello qq' // string类型
let demo1 = 'message' // string类型
const demo2 = 1.99 // 字面量类型
Array类型
let names: string[] = ['a', 'b', 'c'] // 数组类型,数组中存放的是字符串类型
let nun: Array<number> = [12,24] // 泛型,数组中存放的数字类型
Object类型
type infoType = {
name: string
age: number
}
let info: infoType = {
name: 'wqq',
age: 18
}
// 对象类型和函数类型的结合使用
type pointType = {
x: number
y: number
z?: number // 可选类型
}
function position(point: pointType) {}
Symbol类型
const title1: symbol = Symbol('title')
const title2: symbol = Symbol('title')
const person = {
[title1]:'wqq',
[title2]:'qqq'
}
函数的类型
// 明确指定参数的类型,返回值类型可以明确知道,也可以自动推导
function sum(n1: number, n2: number): number {
return n1 + n2
}
const res = sum(12, 31)
// 作为参数的匿名函数 最好不要加类型注解,
const names: string[] = ['aa', 'bb']
names.forEach((item, index, arr) => {
})
any类型
无法确定一个变量的类型,并且他可能会发生改变,就可以使用 any 类型
不限制标识符的任意类型,可以进行任意操作(获取不存在的属性、方法)
let id :any = 'id'
console.log(id.length)
unknown类型
和any类型有点相似,但是unknown类型的值做任何事情都是不合法的
必须要进行类型缩小,才能进行对应的操作
let foo: unknown = 'aaa'
if (typeof foo === 'string') { // 类型缩小
console.log(foo.length);
}
void类型
指定一个函数是没有返回值的,那么它的返回值类型就是void类型
如果返回值的void类型,也可以返回undefined
// 没有返回值,或者返回undefined
function sum(n1: number, n2: number): void {
console.log(n1 + n2);
return undefined
}
// 应用场景:用来指定函数类型的返回值是void
type fooType = () => void
const foo:fooType = () => {}
// 举个例子
// 1.要求传入的函数的类型
type ExecFnType = (...args:any[]) => void
// 2.定义一个函数,传入的参数也是一个函数,且这个函数的类型是ExecFnType
function delayExecFn(fn:ExecFnType) {
setTimeout(() => {
fn('wqq',20)
},1000)
}
// 3.执行上面的函数,传入一个匿名函数
delayExecFn((name,age) => {
console.log(name, age);
})
// 基于上下文类型推导的函数中的返回值如果是void,不强制要求不能返回任何东西
const msg = ['aa','bb']
msg.forEach(() => {
return 123
})
never类型(了解)
表示永远不会发生值的类型,很少去定义,会自动推导
用处:封装框架/封装一些类型工具的校验、类型体操的题目
tuple类型
元组数据结构中可以可以存放不同的数据类型,取出来的item也有明确的类型
// 1. 使用数组:数组中最好存在相同的类型数据,获取值之后不能明确知道对应的数据类型
const info1: any = ['wqq', 20, 1.66]
// 2. 使用对象,需要增加 key
const info2 = {
name: 'wqq',
age: 20,
height: 1.66
}
// 3. 使用元组类型,在函数中使用最多(函数返回值)
const info3: [string, number, number] = ['wqq', 20, 1.66]
function useState(initialState: number): [number, (newValue: number) => void] {
let stateValue = initialState
function setValue(newValue: number) {
stateValue = newValue
}
return [stateValue, setValue] // 返回元组
}
const [count, setCount] = useState(10)
联合类型 |
从两个或者多个其他类型组成的类型,表示可以是这些类型中的任何一个值,使用|符号
function printID(id:number|string) {
// 需要类型缩小
if(typeof id === 'string'){
console.log(id.length);
}else {
console.log(id);
}
}
printID(12)
printID('aaa')
interface和type的区别
类型别名和接口非常相似,在定义对象类型时,大部分时候可以任意选择使用
接口的几乎所有的特性都可以在type中使用
总结:声明对象类型使用 interface,其他的使用 type
1. type使用范围更广,接口类型只能用来声明对象
type NumberType = number
type IDType = string | number
2. 声明对象时,interface可以多次声明,type不允许两个相同别名同时存在
interface PointType {
x: number,
y: number,
}
interface PointType {
z?: number
}
3. interface支持继承
interface IPerson {
name: string,
}
interface QQ extends IPerson {
message: string
}
const Wqq:QQ = {
name:'qqq',
message:'hello'
}
4. interface可以被类实现
class Person implements IPerson {
...
}
type PointTYpe1 = { x: number, y: number }
interface PointType {
x: number,
y: number,
z?: number
}
function PointCoordinate(point: PointType) {
}
交叉类型 &
表示需要满足多个类型,使用 &符号,通常对对象类型进行交叉
interface IPerson {
name: string,
}
interface ICoder {
message: string,
coding:() => void
}
const info:IPerson & ICoder = {
name:'qqq',
message:'hello',
coding:() => {}
}
类型断言 as
断言只能断言成具体的类型,或者 不太具体(any unknown)的类型
// 获取dom元素(一般用法)
const imgEl = document.querySelector('.img') as HTMLImageElement
imgEl.src = 'xxx'
const age:number = 19
// const age2 = age as string 错误做法
// ts类型检测来说是正确的,但代码本身不太正确
const age3 = age as any
const age4 = age3 as string
非空类型断言 !
非空类型断言(确保friend有值的情况下,才能使用)
interface IPerson {
name: string,
friend?:{ name:string }
}
const info: IPerson = {
name:'wqq'
}
// 访问一个可能为空的属性,可以使用可选链 ?.
console.log(info.friend?.name);
// 给属性赋值
if(info.friend) {info.friend.name = 'qq'} // 类型缩小
info.friend!.name = 'www' // 非空类型断言
字面量类型
// 将多个字面量类型联合起来
type Direction = 'right' | 'left' | 'top' | 'down'
const d1: Direction = 'down'
// 例子
type methodType = 'get' | 'post'
function request(url: string, method: methodType) {}
request('ww', 'get')
// ts细节
const info1 = {
url: 'www',
method: 'get'
}
request(info1.url,info1.method) // 错误的做法,info.method获取的类型是string
// 1. 进行类型断言
request(info1.url, info1.method as 'get')
// 2. 定义类型
const info2: { url: string, method: 'get' } = {
url: 'www',
method: 'get'
}
request(info2.url, info2.method)
// 3. 字面量推理
const info3 = {
url: 'www',
method: 'get'
} as const
request(info3.url, info3.method)
类型缩小
// 1. typeof(用的最多)
function foo(value: string | number) {
if (typeof value === "string") {
console.log(value.length);
} else {
console.log(value);
}
}
// 2. === 方向类型判断
type Direction = 'left' | 'right' | 'top'
function SwitchDirection(direction: Direction) {
if (direction === "left") {
console.log('左边');
} else if (direction === 'right') {
console.log('右边');
} else {
console.log('上边');
}
}
// 3. instanceof: 传入一个日期,打印日期
function printDate(date: string | Date) {
if (typeof date === "string") {
console.log(date);
} else {
date.getDate()
}
// 实现2:判断是不是某一个类的实例
if (date instanceof Date) {
date.getDate()
} else {
console.log(date);
}
}
// 4. in 判断是否有某一个属性
interface ISwim { swim: () => void }
interface IRun { run: () => void }
const fish: ISwim ={ swim:() => {} }
const dog: IRun = { run:() => {} }
function move(animal: ISwim | IRun) {
if('swim' in animal){
animal.swim()
}else if ('run' in animal){
animal.run()
}
}
函数
函数类型表达式
格式:(参数列表)=> 返回值,对于传入的多余参数会被忽略掉
type fooType = (n1: number) => number
const foo: fooType = (arg: number): number => {
return 123
}
// 练习
type CalcType = (n1: number, n2: number) => number
function calc(calcFn: CalcType) {
const n1 = 10
const n2 = 20
const res = calcFn(n1, n2)
}
function sum(n1: number, n2: number) {
return n1 + n2
}
calc(sum)
// 匿名函数会自动进行类型推导
calc(function (n1, n2) {
return n1 * n2
})
很多类型不报错,取决于它内部的规则
interface IPerson {
name:string
age:number
}
const p = {
name:'ww',
age: 21,
height:1.66
}
const info:IPerson = p
调用签名
格式:(参数列表):返回值
开发中如何选择:
- 只是描述函数类型本身(函数可以被调用,使用函数表达式)
- 函数作为对象可以被调用,同时也有其他属性,使用函数调用签名
interface IBar {
name: string
age: number
(n1: number): number // 调用签名
}
const bar: IBar = function (n1: number) {
return 123
}
bar.name = 'ww'
bar.age = 12
bar(123)
构造签名(了解)
class Person {} // Person是类也是构造函数
interface IPerson {
new () : Person
}
function foo(fn:IPerson) {
return new fn()
}
foo(Person)
参数的细节
可选参数的类型:指定类型 | undefined
有默认值的情况,类型注解可以省略,可以接受一个undefined的值
function foo(x=100, y?: number) { // 默认值、可选类型
if (y !== undefined) {
console.log(y + 10);
}
}
foo(12)
foo(42,undefined)
foo(12, 32)
function bar(...arg:any[]) {} // 剩余参数
bar(12,'ww')
函数的重载(了解)
表示函数可以以不同的方式进行调用
// 1. 编写重载签名
function add(arg1: number, arg2: number): number
function add(arg1: string, arg2: string): string
// 2. 编写通用的函数实现
function add(arg1: any, arg2: any): any {
return arg1 + arg2
}
add(12, 21)
add('ww', 'qq')
add({name: 'www'}, {age: 18}) // 有实现体的函数不能被直接调用
函数的重载-联合类型对比
可以使用联合类型,尽量使用联合类型实现
// 1. 函数的重载
function getLength(arg:string):number
function getLength(arg:any[]):number
function getLength(arg) {
return arg.length
}
getLength('123')
getLength(['aa','bb','cc'])
// 2. 联合类型实现
function getLength1(arg:string | any[]) {
return arg.length
}
getLength1('123')
getLength1(['aa','bb','cc'])
this类型
在没有对this进行特殊配置的情况下,this是any类型
"noImplicitThis": ture this在上下文不能正确的推导,必须明确指定
- 作为第一个参数,名字必须加this
- 后续传入的参数是从第二个开始,编译出来的代码,this类型会被抹除
tsc --init 初始化配置文件
function foo() {
console.log(this); // 需配置"noImplicitThis": false
}
function bar(this:{name:string},info:{name:string}) {
console.log(this, info);
}
bar.call({name:'wqq'},{name:'zzz'})
this的内置工具
function bar(this: { name: string }, info: { name: string }) {
console.log(this, info);
}
type BarType = typeof bar
// 1. ThisParameterType: 获取BarType中this的类型
type BarThisType = ThisParameterType<BarType>
// 2. OmitThisParameter: 删除this类型,剩余的函数类型
type PureType = OmitThisParameter<BarType>
// 3. ThisType: 用于绑定一个上下文的this
interface IState {
name: string,
age: number
}
interface IStore {
state: IState,
eating: () => void
}
const store: IStore & ThisType<IState>= {
state: {
name: 'www',
age: 21
},
eating: function () {
console.log(this.name);
}
}
面向对象
class Person {
// 要声明成员属性
name!: string // 不需要初始化
age = 0
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
// 实例对象:instance
const p1 = new Person('ww', 21)
const p2 = new Person('qq', 12)
成员修饰符
class Person {
protected name: string // protected:仅在类的自身和子类中可见,受保护的属性和方法
private age : number // private:私有的,在类的内部才能访问
readonly message:string // 只读属性
constructor(name: string, age: number) {
this.name = name
this.age = age
}
public eating() { // public:公有的,默认就是公有的
console.log('哈哈哈');
}
}
参数属性 (语法糖)
把一个构造函数的参数转成一个同名同值的类属性
class Person {
// 语法糖
constructor(public name: string,public age: number) {
this.name = name
this.age = age
}
}
抽象类 abstract
没有具体实现的方法(没有方法体)就是抽象方法,抽象方法不能被实例化
- 抽象方法,必须存在于抽象类中
- 抽象类是使用abstract声明的类
抽象类的特点:
- 抽象类是不能被实例的话(也就是不能通过new创建)
- 抽象类可以包含抽象方法,也可以包含有实现体的方法
- 抽象方法的类,必须是一个抽象类
- 抽象方法必须被子类实现,否则该类必须是一个抽象类
abstract class Shape {
abstract getArea()
}
class Circle extends Shape {
constructor(public radius: number) {
super();
}
getArea() {
return this.radius ** 2 * Math.PI
}
}
// 通用函数
function calcArea(shape:Shape) {
return shape.getArea()
}
calcArea(new Circle(12))
ts对于类型检测使用的是鸭子类型:只关心属性和行为,不关心你具体对应的类型
class Person {
constructor(public name: string, public age: number) {}
}
class Dog {
constructor(public name: string, public age: number) {}
}
function printPerson(p:Person) {
console.log(p.name,p.age);
}
printPerson(new Person('wqq',12))
printPerson({name:'qqq',age:12}) // 创建了字面量,将字面量赋值给了Person
printPerson(new Dog('旺财',2))
类具有的特性
- 可以创建类对应的实例
- 可以作为这个实力的类型
- 也可以当作有构造签名的函数
索引签名(了解)
interface ICollection {
[index: string]: number // 索引签名
length: number
}
接口被类实现
interface qq {
name: string
age: number
}
// 1.直接作为类型
const p: qq = {
name: 'wqq',
age: 21
}
// 2.接口被类实现
class Person implements qq {
name: string
age: number
}
枚举类型
enum Direction {
TOP, // 可设置值 TOP = 'top'
LEFT,
RIGHT
}
const d1:Direction = Direction.TOP
enum Color {
red= 1 << 1
}
function turnDirection(direction:Direction) {
switch (direction) {
case Direction.TOP:
console.log('向上移动');
break
}
}
// 监听键盘点击
turnDirection(Direction.TOP)
泛型
将类型参数化的过程
function useState<Type>(initialState: Type): [Type, (newValue: Type) => void] {
let stateValue = initialState
function setValue(newValue: Type) {
stateValue = newValue
}
return [stateValue, setValue] // 返回元组
}
const [count, setCount] = useState(10)
const [banner, setBanner] = useState<any[]>([])
可以传入多个类型
function foo<T,E>(arg1:T,arg2:E) {}
foo(12,21)
foo('aa',12)
泛型接口
interface Person<T = number> { // 设置默认值
name: T,
age: number
}
const qq: Person<string> = {
name: 'wqq',
age: 21
}
const ww: Person = {
name: 21,
age: 21
}
泛型类
class Point<T = number> {
x:T
y:T
constructor(x:T,y:T) {
this.x = x
this.y = y
}
}
const p1 = new Point(12,21)
const p2 = new Point('32','42')
泛型约束
interface TLength {
length: number
}
// 没有必要使用泛型
function getLength(arg: TLength) {
return arg.length
}
getLength('1213')
getLength(['aa', 'bb', 'cc'])
getLength({length: 1222})
// 获取传入的内容,这个内容必须有length属性
// Type相当于是一个变量,用于记录本次调用的类型,所以在整个函数的执行周期中,一直保留着参数的类型
function getInfo<T extends TLength>(arg:T):T {
return arg
}
getInfo('1213')
getInfo(['aa', 'bb', 'cc'])
getInfo({length: 1222})
传入的类型,obj当中的key的其中之一
function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {
return obj[key]
}
const info = {
name: 'ww',
age: 21,
height: 1.66
}
const names = getObjectProperty(info, 'age')
映射类型(了解)
基本使用
interface IPerson {
name:string
age:number
}
type MapType<T> = {
// [property in keyof T] :boolean
readonly [property in keyof T]?: T[property] // 拷贝的同时变成了既是可选又是只读的
}
type NewPerson = MapType<IPerson>
模块化
对babel、swc或者esbuild更好的提示,告诉它们什么样的导入可以被安全的移除
// 可以使用type前缀,表明是一个类型导入
import {type IDType, type IPerson} from "./type.ts";
import type {IDType, IPerson} from "./type.ts";
命名空间
export namespace price {
function format(price) {
return '$' + price
}
}
自定义类型声明
declare const names:string
declare function foo(): void
declare class Person {
name:string
age:number
constructor(name:string,age:number)
}
// 声明模块
declare module 'lodash' {
export function join(args:any[]) :any;
}
// 声明文件模块
declare module '*.png'
declare module '*.jpg'
declare module '*.svg'
// 类型声明
declare namespace $ {
export function ajax(settings: any): any;
}
类型分发
当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成分发
type toArray<T> = T extends any? T[]: never
type NuumArray = toArray<number>
type NumAndStrArray = toArray<number|string> // 分发类型: toArray<number | string>
类型体操
interface Hello {
name: string,
age: number,
message?: string
}
type p1 = Partial<Hello> // 1. Hello都变成可选的
type p2 = Required<Hello> // 2. Hello都变成必选的
type p3 = Readonly<Hello>
type t1 = '成都' | '上海'
type p4 = Record<t1, Hello>
const person: p4 =
{
'成都': {
name: 'xx',
age: 10
},
'上海': {
name: 'xxx',
age: 20
}
},
// 1.1类型体操
type WQPartial<T> = {
[P in keyof T]?: T[P]
}
// 1.2类型体操
type WQRequired<T> = {
[P in keyof T]-?: T[P]
}
// 1.3类型体操
type WQReadonly<T> = {
readonly [P in keyof T]: T[P]
}