【无标题】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

基础学习教程TS


一、使用步骤

ts学习

代码如下(示例):

interface Cat {
    name: string,
    run(): void
}
interface Fish {
    name: string,
    swim(): void
}
function getName(animal: Cat | Fish): string {
    return animal.name
}
// 如果在不确定类型的时候就想访问一个类型确定的属性和方法,就需要断言
function isFish(animal: Cat | Fish): boolean {
    // 将变量animal 断言为FISH类型,那么访问其swim属性,就可以通过编译器的检查
    // 类型断言相当于欺骗编辑器,编译的时候不报错,不代表运行的时候不报错
    return (typeof (animal as Fish).swim) === 'function'
}

const tom: Cat = {
    name: 'cat',
    run() {
        console.log('123')
    }
}
function swim(animal: Cat | Fish) {
    (animal as Fish).swim
}
swim(tom)

// 上边的代码编译为js后为:
/*
var tom = {
    name: "cat",
    run: function () {
        console.log("running");
    }
};
function swim(animal) {
    animal.swim();  // 执行到这里就会报错
}
swim(tom);
//TypeError: animal.swim is not a function
*/

// 将一个父类断言为更加具体的子类
class ApiError extends Error {
    code: number = 0
}
class HttpError extends Error {
    statusCode: number = 200
}
function isApiError(error: Error) {
    return (typeof (error as ApiError).code === 'number')
}

// interface 描述函数类型
interface Isum {
    (x: number, y: number): number
}
const add: Isum = (x, y) => x + y

// 如果对象上有多个不确定属性
interface Randomkey {
    [propName1: string]: string | number
}
const obj: Randomkey = {
    name: '1',
    age: 1,
}
// 如果是数组
interface LikeArray {
    [propName: number]: string
}
// 并不是一个真的数组,数组的方法比如push 它是没有的
const arr: LikeArray = ['1', '2']
// 通过arr[0]访问

// 元组  元组类型允许表示一个已知元素数量和类型的数组各元素的类型不必相同
let tuple: [number, string] = [1, '1']
// 写错类型会报错
// let tuple1: [number, string] = ['1', '1']
// 可以对元组使用数组的方法,比如push时,不会有越界报错
let tuple1: [number, string] = [1, '2',]
tuple1.push('1') // 但是这里只能push定义的number或者string 否则报错


// 类型推断
type arrItem = number | string | boolean
const arr1: arrItem[] = [1, true, '1']

// 联合类型
let num: number | string

// 联合类型: 指可以取几种类型中的任意一种,
// 交叉类型:指把几种类型合并起来 交叉类型和interface 的extends 非常相似,都是为了实现对象形式的组合和扩展

// 交叉类型
interface Person {
    name: string
}
type Student = Person & { age: number }
const numobj: Student = {
    name: '1',
    age: 1
}

// 类型别名type
type Name = string
type NameResolver = () => string
type NameorResolver = Name | NameResolver  // 联合类型
const fn: NameorResolver = () => '1'
const str: NameorResolver = '1'

type arrItem1 = number | string  // 联合类型
const arr2: arrItem1[] = [1, 2, 3, '2',]

// type和interface 的区别
/*
 相同点: 都可定义一个对象或者函数
         都允许继承
*/
// 定义函数为例
type addType = (num1: number, num2: number) => number
interface addType1 {
    (num1: number, num2: number): number
}
const add1: addType = (num1, num2) => num1 + num2
const add2: addType1 = (num1, num2) => num1 + num2
// 继承为例
// 1.interface 继承interface
interface Person1 {
    name: string
}
interface Student1 extends Person1 {
    grade: number
}
const person: Student1 = {
    grade: 1,
    name: '1'
}
// 2.type继承type
type Person2 = { name: string }
type Student2 = Person2 & { grade: number } // 交叉类型实现
// 3.interface 继承type 
type Person3 = { name: string }
interface Student3 extends Person3 {
    grade: number
}
// 4.type继承interface
interface Student4 {
    name: string
}
type Person4 = Student4 & { grade: number } // 交叉类型

/*
 type 和interface 的不同
 interface 使用extends 继承 
 interface 是ts涉及出来用于定义对象类型的,可以对对象的形状进行描述
 interface可以合并重复声明,type重复声明会报错
 type 使用 交叉类型
 type 是类型别名,用于给各种类型定义别名,让TS写起来更简洁,清晰
 type 可以声明交叉类型,联合类型,基本类型,元组 interface不行

*/
// 合并重复声明
interface Person5 {
    name: string
}
interface Person5 { // 重复声明就会自动合并
    age: number
}
const person3: Person5 = {
    name: '1',
    age: 1
}

/*
 小结:
 这两者的区别说了这么多,其实本不该把这两个东西拿来做对比,他们俩是完全不同的概念。
 interface 是接口,用于描述一个对象
 type 是类型别名,用于给各种类型定义别名,让TS写起来更简洁,清晰
 只是平常开发中都能实现同样的功能,才会经常被混淆
 开发中:一般使用组合或交叉类型的时候,用type
        用类的extends时,用interface

 其他情况,比如定义一个函数或者对象,就看心情

*/

// 类型断言
function getLength(arg: number | string): number {
    // 注意,类型断言不是类型转换,把一个类型断言成联合类型中不存在的类型会报错
    const str = arg as string
    if (str.length) {
        return str.length
    } else {
        const number = arg as number
        return number.toString().length
    }
}

// 字面量类型
type ButtonSize = 'mini' | 'small' | 'normal' | 'large'
type Sex = '男' | '女'
// 这样就只能从这些定义的常量中取值,乱取值会报错
const sex: Sex = "男"

// -----------------------------------泛型------------------------------------------------------------------------
/*
 为什么要用泛型?
 举例:
*/
// 定义一个print函数,这个函数的功能是把传入的参数打印出来,再返回这个参数,传入的类型是string,返回的类型为string
function print(arg: string): string {
    console.log(arg)
    return arg
}
// 现在需求变了,我还需要打印number  联合类型解决
function print(arg: string | number): string | number {
    console.log(arg)
    return arg
}
// 现在需求又变了,我还需要打印string,number,甚至任何类型怎么解决,可以使用any,但是再ts中不建议用any
function print1(arg: any): any {
    console.log(arg)
    return arg
}
// 这么写的话传入的是any,返回的也是any,传入和返回并不是统一的,并且存在bug
const res: string = print1(123) // 传入number然后赋值给字符串,应该会报错但是结果并没有报错

// 通过以上的这个例子,泛型就出来了,但是泛型并不是为了解决这一个问题出来的,泛型还解决了很多其他问题

// 泛型基本使用  泛型的语法是<>里写参数类型,一般可以用T表示
// 处理函数参数
function print2<T>(arg: T): T {
    return arg
}
const res1: string = print2<string>('123')
// 使用的时候可以有两种方式指定类型
// 1.定义要使用的类型 2.TS类型推断,自动推导出类型
print2<string>('123') // 定义T为string
print2('123') // TS类型推断,自动推导类型为string

// type 和 interface 都可以定义函数类型,也用泛型写一下
type Print3 = <T>(arg: T) => T
const printFn: Print3 = function (arg) {
    return arg
}
printFn('10')
// interface 写法 
interface Iprint4<T> {
    (arg: T): T
}
function Print6<T>(arg: T): T {
    return arg
}
const myPrint: Iprint4<string> = Print6
myPrint('123')

// 给泛型添加默认参数
interface Iprint<T = number> { // 默认参数
    (arg: T): T
}
function print4<T>(arg: T) {
    console.log(arg)
    return arg
}
const mtPrint2: Iprint<string> = print4
mtPrint2('2')

// 处理多个函数参数
// 现在有这么一个函数,传入一个只有两项的元组,交换元组的第0项和第1项,返回这个元组
function swap(tuple) {
    return [tuple[1], tuple[0]]
}
// 上边的函数这么写,就是去了类型,用泛型改造一下
function swap1<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]]
}
// 传入的参数中,第0项为string,第一项作为number
// 再交换函数的返回值里,第0项为number,第一项为string
// 第0项鼠标全部为number方法,第一项为string方法
const res2 = swap1(['1', 1])
// res2[0].   输入个点自动提示上全是number方法
// res[1].    输入点自动提示上全是string方法

// 约束泛型
// 假设现在有这么一个函数,打印传入参数的长度
function printLength<T>(arg: T): T {
    console.log(arg.length)
    return arg
}
// 因为不确定T是否有length属性会报错,那么现在项约束这个泛型一定有length属性,可以与interface结合,来约束泛型
interface Ilength {
    length: number,
}
function PrintLength1<T extends Ilength>(arg: T): T {
    console.log(arg.length)
    return arg
}
// 这其中的关键就是<T extends Ilength> 让这个泛型继承接口Ilength,这样就能约束泛型
// 我们定义的变量一定要有length属性,比如字符串,数组,对象才可以通过Ts编译
const str1 = PrintLength1('1')
const arr3 = PrintLength1(['1'])
const obj1 = PrintLength1({ length: 1 })
// const boole = PrintLength1(true) 报错,不存在length


// 泛型约束接口interface
interface IkeyValue<T, U> {
    key: T,
    value: U
}
const k1: IkeyValue<number, string> = { key: 1, value: '1', }
const k2: IkeyValue<string, number> = { key: '1', value: 1 }

/*
  泛型小结
  泛型,从字面上理解,泛型就是一般的,广泛的
  泛型是指在定义函数,接口或者类的时候,不预先指定具体类型,而是再使用的时候再指定类型
  泛型中的T就像一个占位符,或者说一个变量,再使用的时候可以把定义的类型项像参数一样传入,可以原封不动的输出
*/


// -------------------------------------------------高级类型-----------------------------------------------------
// 索引类型
// 从对象中抽取一些属性的值,然后拼接成数组,可以这么写
const userInfo = {
    name: 'lin',
    age: 18
}
function getValues(userInfo: any, keys: string[]) {
    return keys.map(key => userInfo[key])
}
// 抽取指定属性的值
console.log(getValues(userInfo, ['name', 'age']))  // ['lin',18]
// 抽取obj中没有的属性
console.log(getValues(userInfo, ['sex'])) // [undefined]
/*
// 上边的代码中,虽然对象中并不包含sex属性,但是TS编译器并未报错
    此时使用TS索引类型,对这种情况做类型约束,实现动态属性的检查
    理解索引类型,需要先理解 keyof(索引查询),T[k](索引访问),和 extends(泛型约束)
*/
// keyof 索引查询
// keyof 操作符可以用于获取某种类型的所有键,其返回类型是联合类型
interface Iperson11 {
    name: string,
    age: number
}
type Test = keyof Iperson11 // 'name' | 'age'   形成了一个字符串字面量
const obj12: Test = 'name'

// T[k] 索引访问
// T[k] 表示接口T的属性K所代表的的类型
interface Iperson12 {
    name: string,
    age: number
}
let type1: Iperson12['name'] = '1'
let type2: Iperson12['age'] = 1

// extends 泛型约束
// T extends U 表示泛型变量可以通过继承某个属性,获得某些属性
interface Ilength1 {
    length: number
}
function printLength1<T extends Ilength1>(arg: T): T {
    console.log(arg.length)
    return arg
}
// 这样入参就一定要有length属性,比如str12,arr12,obj112都可以,num就不行
const str12 = printLength('lin')
const arr12 = printLength([1, 2, 3])
const obj112 = printLength({ length: 10 })

const num1 = printLength(10) // 报错,Argument of type 'number' is not assignable to parameter of type 'ILength'

// 检查动态属性
// 对索引类型的几个概念了解后,对getValue函数进行改造,实现对象上动态属性的检查
// 改造前
const userInfo1 = {
    name: 'lin',
    age: 18
}
function getValus(userInfo1: any, keys: string[]) {
    return keys.map(key => userInfo1[key])
}
// 定义泛型T,  K用于约束userInfo和keys
// 为K增加一个泛型约束,使K继承userInfo1的所有属性的联合类型,即k extends keyof T
// 改造后
function getValues1<T, K extends keyof T>(userInfo: T, keys: K[]): T[K][] {
    return keys.map(key => userInfo[key])
}
// 当获取对象中不存在的属性就会报错
// getValues1(userInfo1, ['name', 'sex'])
// getValues1(userInfo1, ['age', 'name'])

// 映射类型
// TS允许将一个类型映射成另外一个类型
// in 介绍映射类型之前,先介绍一下in操作符,用来对联合类型实现遍历
type Person6 = 'name' | 'school' | 'major'
type obj9 = {
    [p in Person6]: string
}

// Partial
// Partial<T> 将T的所有属性映射为可选的
interface Iperson1 {
    name: string,
    age: number
}
let p1: Iperson1 = {
    name: '1',
    age: 1
}
// 使用Partial改造为可选属性
type Ipartial = Partial<Iperson1>
let p2: Ipartial = {
    name: '1'
}
// Partial 原理
// Partial 的实现主要用到了in 和 keyof
// type Partial<T> = {
//     [p in keyof T]?: T[p]
// }
// [p in keyof T] 遍历T上的所有属性
// ?: 设置属性为可选的
// T[p] 设置类型为原来的类型


// Readonly
// Readonly<T> 将T的所有属性映射为只读的
interface Iperson2 {
    name: string,
    age: 18
}
type Ireadonly = Readonly<Iperson2>

let p3: Ireadonly = {
    name: 'lin',
    age: 18
}
// p3.name = 'lisi' // 报错 因为name为只读属性
/*
  Readonly 原理 和Partial几乎完全一样
  type Readonly<T> = {
      readonly [P in keyof T]:T[P]
  }

  [P in keyof T] 遍历T上的所有属性
  readonly 设置属性为只读的
  T[P] 设置类型为原来的类型
*/

// Pick
// Pick 用于抽取对象子集,挑选一组属性并组成一个新的类型,例如:
interface Iperson3 {
    name: string
    age: number
    sex: string
}
type Ipick = Pick<Iperson3, 'name' | 'age'> // 

let p4: Ipick = {
    name: 'lisi',
    age: 1
}
/*
 Pick 原理
 type Pick<T,K extends keyof T>= {
 [P in K] : T[P]
 }
 Pick 映射类型有两个参数
      第一个参数T,表示要抽取的目标对象
      第二个参数K,具有一个约束:K一定要来自T所有属性字面量的联合类型
*/

// Record
// 上面三种映射类型官方称为同态,意思是只作用于obj属性而不会引入新的属性
// Record是会创建新属性的非同态映射类型
interface Iperson4 {
    name: string
    age: number
}
type Irecord = Record<string, Iperson4>

let p5: Irecord = {
    person1: {
        name: '1',
        age: 1
    },
    person2: {
        name: '1',
        age: 1
    },
    person3: {
        name: '1',
        age: 1
    }
}

/*
 Record 原理
 type Record<K extends keyof any,T> = {
      [P in K]:T
 }
 Record 映射类型有两个参数
   第一个参数可以传入继承与any的任意值
   第二个参数,作为新创建对象的值,被传入
*/

// 条件类型
// T extends U ? X : Y
// 若类型T 可被赋值给类型U,那么结果类型就是x类型,否则就是Y类型

// Exclude
// Exclude 意思是不包含,Exclude<T,U> 会返回联合类型T 中不包含联合类型U的部分
type Test1 = Exclude<'a' | 'b' | 'c', 'a'>
type Texclud = {
    [P in Test1]: string
}
let T1: Texclud = {
    b: '1',
    c: '1'
}
// Exclude 原理
/*
 type Exclude<T,U> = T extends U ? never : T
 never 表示一个不存在的类型
 never 与其他类型的联合后,为其他类型
*/
// Extract
// Extract<T,U> 提取联合类型T和联合类型U的所有交集
type Test2 = Extract<'key1' | 'key2', 'key1'>
let t: Test2
/*
Extract 原理
type Extract<T, U> = T extends U ? T : never
与Exclude正好相反

------------------------------------------------
工具类型与封装接口限制参数类型
// 接着as.ts写的
// 原理可以查看https://juejin.cn/post/7068081327857205261#heading-96
// ------------------------------------工具类型--------------------------------------------------------------------
// Omit
// Omit<T,U> 从类型T中剔除U中的所有属性
interface Iperson5 {
    name: string,
    age: number
}
type Iomit = Omit<Iperson5, 'age'>  // 这样就删除了age属性

// NonNullable
// NonNullable<T> 用来过滤类型中的null及undefined类型
type T0 = NonNullable<string | null | undefined> // string
type T1 = NonNullable<string[] | number | null> // string[] | number

// Parameters
// Parameters 获取函数的参数类型,将每个参数类型放在一个元组中
type T2 = Parameters<() => string> // []
type T3 = Parameters<(arg: number) => void> // [arg:number]
type T4 = Parameters<(arg: number, agr2: string) => string> // [arg:number,arg2:string]

// ReturnType
// ReturnType 获取函数的返回值类型
type T5 = ReturnType<() => string> // string
type T6 = ReturnType<(a: string) => void> // void



// // 封装接口限制参数类型
// interface API {
//     '/book/detail': {
//         id: number
//     },
//     '/book/comment': {
//         id: number,
//         comment: string
//     }
// }
// function request<T extends keyof API>(URL: T, obj: API[T]) {
//     return URL
// }
// request('/book/comment', { id: 1, comment: '1' })
*/




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值