当前位置:网站首页>typescript学习日记,从基础到进阶(第二章)

typescript学习日记,从基础到进阶(第二章)

2022-08-11 05:25:00 Mino.66

这一章将对typescript的内容进行一些补充

配置

tsc 文件名
// 执行这种命令时一般情况下并不会走ts的配置文件

tsc
// 单独执行tsc时便能够走ts的配置文件

联合类型

联合类型指的是多种类型组合而成的类型,它们之间是或的关系,通常是下面的形式

type TypeA = number | string
// 等号后面

因此,联合类型对象的具体类型一般只能在程序执行的时候才能确定,这时候如果不进行类型保护,TS就会报错

// 学生
interface Student {
    
  name: string;
  job: string;
  study: () => {
    }
}

// 老师
interface Teacher {
    
  name: string;
  job: string;
  teach: () => {
    }
}

const work = (person: Student | Teacher) => {
    
  person.study()
}

类型“Student | Teacher”上不存在属性“study”。
  类型“Teacher”上不存在属性“study”。ts(2339)

从代码逻辑上分析很简单,因为person可能是老师,老师并没有study方法,因此会报错

如何进行类型保护呢?TS提供了下面的工具

const work = (person: Student | Teacher) => {
    
  (person as Student).study()
}

这样一来TS就不会报错,但是显然,这样并没有解决根本的问题,person依旧可能是老师类型。只是我们强制让TS把它当作学生类型,正确的做法应该是下面这样的

const work = (person: Student | Teacher) => {
    
  if ('study' in person) {
    
    person.study()
  } else {
    
    person.teach()
  }
}
// 从这里可以看出TS还是蛮智能的

in可以,和in有着类似功能的自然也可以,如typeof,instanceof等

需要自行判断需要使用as的场景,TS初衷还是让我们更容易发现一些潜在的问题

枚举

枚举本质上就是TS为我们提供了一个提高代码语义化的工具
一般,在开发中,我们都会遇到与下面场景类似的情况

const f = (data: number) => {
    
  if (data === 1) {
    
    console.log('do something')
  } else if (data === 2) {
    
    console.log('do something')
  } else if (data === 3) {
    
    console.log('do something')
  }
}

这样的代码并不利于阅读和维护,所以更常见的做法是下面这样

const dataType = {
    
  LOGIN: 1,
  LOGOUT: 2,
  REGISTER: 3,
}

const f2 = (data: number) => {
    
  if (data === dataType.LOGIN) {
    
    console.log('LOGIN')
  } else if (data === dataType.LOGOUT) {
    
    console.log('LOGOUT')
  } else if (data === dataType.REGISTER) {
    
    console.log('REGISTER')
  }
}

现在,TS为我们提供了枚举工具,我们可以省去一些代码

enum dataType {
    
  LOGIN,
  LOGOUT,
  REGISTER,
}

不过需要注意,enum默认是从下标0开始的,因此要满足我们的需求应该是下面的写法

enum dataType {
    
  LOGIN = 1,
  LOGOUT,
  REGISTER,
}

任何一个位置的赋值,后面都会顺序+1

enum dataType {
    
  LOGIN,
  LOGOUT = 3,
  REGISTER,
}
console.log('dataType: ', dataType)

// 输出
dataType:  {
    
  '0': 'LOGIN',
  '3': 'LOGOUT',
  '4': 'REGISTER',
  LOGIN: 0,
  LOGOUT: 3,
  REGISTER: 4
}

从输出结果可以看出,直接使用下标进行索引也是合法的

console.log('dataType[4]: ', dataType[4]);

// 输出
dataType[4]:  REGISTER

泛型

泛型是一个提高typescript灵活性的工具

泛型是Typescript中的一个难点,不过本节只会简单的开个头

泛型的常规使用就是在尖括号中添加泛型名称

function f<T>() {
    

}

此时T就是一个泛型,那么这个T有什么用呢?

例子中是声明了一个函数,那么我们可以用它来限制参数的类型,或者返回值的类型

function f<T>(params: T) {
    
}

现在,增加一个参数

function f<T>(params: T, params2: T) {
    
}

这时f函数的调用,就会要求两个参数的类型一致,但它并不关注它们具体是什么类型,只要是一样的类型就可以了,因此下面的使用都会是合法的

f(1, 2)
f('1', '2')
f([1, 2], [3])

例子是用函数进行举例的,在class中使用是一样的道理
当然,我们在使用的时候更多的会与接口进行搭配

interface Person {
    
  name: string;
  age: number;
}
const f3: <T extends Person>(params: T, params2: T) => void = (params, params2) => {
    }
const person1 = {
    
  name: 'John',
  age: 1,
  job: 'student'
}
const person2 = {
    
  name: 'Tom',
  age: 2,
  job: 'teacher'
}
f3(person1, person2)

泛型支持多个一起使用

type FunType = <T, F, P>(params: T, params2: F, params3: P) => void
interface FunInterface {
    
  <T, F, P>(params: T, params2: F, params3: P): void
}
const f4: FunType = (a, b, c) => {
    }
const f5: FunInterface = (a, b, c) => {
    }

上面代码只是用了两种写法

还有与keyof的搭配用法,不仅能够根据接口限制入参类型,还能让TS准确推断出返回值类型

const person3 = {
    
  name: 'Tony',
  age: 3,
}
function f6<T extends keyof Person>(key: T) {
    
  return person3[key]
}
const res = f6('age')

在这里插入图片描述

这样使用不仅很好的限制了key的类型(即name、age,如果用string的话用户可能输入其它的字符串,不属于person3),还能够让TS准确的判断返回值的类型

从上面代码也可以看出来,泛型的写法非常的灵活,这里也只是简单介绍一下,在后面还会进行详细的探讨

命名空间

命名空间需要与ts的配置文件一起讲
目录结构:
在这里插入图片描述
main.js是main.ts编译后的文件
main.ts内容:

const f1 = () => {
    
  console.log('this is f1')
}

const f2 = () => {
    
  console.log('this is f2')
}

const f3 = () => {
    
  f1()
  f2()
}

在index中引入js文件,然后执行f3

<body>
  <script src="../src/main.js"></script>
  <script> f3() </script>
</body>

此时我们打开浏览器时代码能够正常运行,但是我们会发现在全局作用域中,我们能够拿到f1和f2,尽管这可能并不是我们期望的
在这里插入图片描述
TS提供了命名空间,可以帮助我们解决这个问题,用法如下

namespace Main {
    
  const f1 = () => {
    
    console.log('this is f1')
  }

  const f2 = () => {
    
    console.log('this is f2')
  }

  export const f3 = () => {
    
    f1()
    f2()
  }
}

然后重新编译ts文件,并且对html中的调用做一些修改

<body>
  <script src="../src/main.js"></script>
  <script> Main.f3() </script>
</body>

此时,我们在浏览器的控制台中可以发现,因为我们没有在命名空间中导出相关内容,所以f1和f2没有办法直接拿到了
在这里插入图片描述
这就是命名空间的作用
按照这个规律,我们也可以很容易推断出跨文件的应用

// components.ts
namespace Components {
    
  export const f1 = () => {
    
    console.log('this is f1')
  }

  export const f2 = () => {
    
    console.log('this is f2')
  }
}

// main.ts
namespace Main {
    
  export const f3 = () => {
    
    Components.f1()
    Components.f2()
  }
}

这样理论上是能直接用的,但是这样会生成两个js文件,要只生成一个文件自然需要修改ts的配置

// 需要更改下面两个配置
"module": "amd",
"outFile": "./dist/page.js",

注意,这里改了导出的文件名,所以要在index.html中修改引入,这样就能正常使用了

当然,命名空间还支持子命名空间,并无太大区别,直接从父命名身上就能拿到子命名

描述文件(.d.ts)

首先确定ts的配置文件

"module": "amd",
"outFile": "./dist/page.js",

目录结构(page.js是ts编译后的文件)
在这里插入图片描述
在index.html中,我们引入jQuery

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script> -->
  <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</head>
<body>
  <div id="container"></div>
  <script src="./page.js"></script>
</body>
</html>

在main.ts中,我们使用jQuery

$('#container').text('hello world');

这时TS会报错,因为找不到$相关的说明,此时,需要我们编写一个描述文件jquery.d.ts

declare const $

这时,TS就不会再报错,当然,我们可以进行更详细的描述

interface LinkFunc {
    
  text: (params: string) => void;
  val: (params: string) => void;
}

declare const $: (params: string) => LinkFunc

同时,declare是支持对同一个变量重复使用的

// main.ts
$('#container').text('hello world');
$('#container').val('hello world');

new $.fn.init()

// jquery.d.ts
interface LinkFunc {
    
  text: (params: string) => void;
  val: (params: string) => void;
}

declare function $(params: string): LinkFunc
declare function $(params: string): LinkFunc

declare namespace $ {
    
  namespace fn {
    
    class init {
    

    }
  }
}

如果是用模块导入的方式时,这时候我们需要修改一些内容

// mian.ts
import $ from 'jquery'

$('#container').text('hello world');
$('#container').val('hello world');

new $.fn.init()


// jquery.d.ts
declare module 'jquery' {
    
  interface LinkFunc {
    
  text: (params: string) => void;
  val: (params: string) => void;
  }

  function $(params: string): LinkFunc
  function $(params: string): LinkFunc

  namespace $ {
    
    namespace fn {
    
      class init {
    

      }
    }
  }
  export = $
}

这就是这一章的全部内容,本来第二章是一个实例的,但是思考之后还是把后面几章的内容加在一个章节里面发出来比较好
最后贴出我的notion地址

https://seasoned-quasar-8c8.notion.site/70ff7b5909ce492fa10d3634855f3ca6

原网站

版权声明
本文为[Mino.66]所创,转载请带上原文链接,感谢
https://blog.csdn.net/luoluoyang23/article/details/124532486