这一章将对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')

typescript的字符串长度 typescript 字符串拼接_typescript的字符串长度

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

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

命名空间

命名空间需要与ts的配置文件一起讲

目录结构:

typescript的字符串长度 typescript 字符串拼接_typescript_02


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,尽管这可能并不是我们期望的

typescript的字符串长度 typescript 字符串拼接_泛型_03


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没有办法直接拿到了

typescript的字符串长度 typescript 字符串拼接_typescript_04


这就是命名空间的作用

按照这个规律,我们也可以很容易推断出跨文件的应用

// 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编译后的文件)

typescript的字符串长度 typescript 字符串拼接_前端_05


在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 = $
}