前言

能用 Javascript 写的东西,终将会用 TypeScript 书写

本文思维导图

TypeScript基础 - 图1

TypeScript的意义

  • 避免一些类型或者一些不是我们预期希望的代码结果错误

JavaScript中 很多错误是运行时才会抛出来的,而 TypeScript可以做到出现错误时在编辑器里就能获知,提高了开发效率

以下JavaScriptTypescript都由 jsts代替

TypeScript优缺点

优点

  • js开发中,进行前后端联调时,需要去查看接口文档上的字段类型,而 ts会自动识别(类型推断),节省时间
  • 编辑器中提示错误,避免代码在运行时隐式转换踩坑

    缺点

  • 学习成本

    • 类型概念、interface接口、class类、enum枚举、generic泛型
  • 与一些其他的库的融洽性

    js & ts 对比

    运行流程

    js 运行流程

  1. 将 js 代码转为 js-AST
  2. 将 AST 转换为字节码
  3. 运行时计算字节码

    ts 运行流程

  4. 将 ts 编译为 ts-AST

  5. 进行 AST 代码上类型检查
  6. 检查完后转换为 js

剩下三步就是 js 的运行流程

  1. 将 js 代码转为 js-AST
  2. 将 AST 转换为字节码
  3. 运行时计算字节码

    其他区别

    | 类型系统特性 | JavaScript | TypeScript | | —- | —- | —- | | 类型是如何绑定? | 动态 | 静态 | | 是否存在类型隐式转换? | 是 | 否 | | 何时检查类型? | 运行时 | 编译时 | | 何时报告错误 | 运行时 | 编译时 |

类型绑定

  • js 是动态绑定语言,只有运行程序时才能知道类型,在程序运行之前 js 完全不知道
  • ts 在编译时就已经知道什么类型,如果没有定义类型,也会自动推导类型

    类型转换

  • js 会有隐式转换

  • ts 不会

    • :隐式转换——比如各种奇葩的 1+ “1”之类的

      何时检查类型

  • js 运行时

  • ts 编译时

    何时报告错误

  • js 执行后

  • ts 写代码时

    主要使用模式

    显式注解类型

    1. let name: string = "zzz";
    2. let age: number = 18;
    3. let hobby: string[] = ["code", "gogogo"]
    也就是声明变量时带上类型注解

    推导类型

    1. let name = "zzz";//string
    2. let age= 18;//number
    3. let hobby= ["code", "gogogo"]//string数组
    它会自动推导是什么类型,当你在 vscode 中将鼠标放上去就能看见

    开玩

    安装

    npm i -g typescript全局安装

    执行

    tsc index.ts
    执行之后,就会编译生成一个index.js,也就是上文提到的ts转换为js的那一步。

    ts-node

    每次都自己手动编译的话——麻烦,这里介绍一个插件ts-node,直接运行.ts 文件,并且不会编译出来一个新的 js 文件使得目录看起来非常冗余

    实际上,用像 cra 等脚手架的话,这些完全不需要自己操作,具体使用只需要知道 ts 的语法就行了

安装
npm i ts-node
执行
ts-node index.ts

基础知识

基础静态类型

前面几个比较简单就只举一个例子

  • boolean

    1. const status: boolean = false; // 显示注解一个boolean g类型
    2. const status1 = true; // 不显示注解,ts会自动推导出来类型
  • number

  • string
  • null
  • undefined
  • void (表示无效的意思

一般只用在函数上,表示该函数没有返回值

  1. function fn(): void {} // 正确
  2. function testFn(): void {
  3. return 1; // 报错,不接受返回值存在
  4. }
  5. function fn1(): void { return undefined} // 显示返回undefined类型,也是可以的
  6. function fn2(): void { return null} // 显示返回null类型也可以,因为 null == undefined
  • never (表示永远不会有值或永远执行不完的类型

甚至 null、undefined也不能返回

  1. const test: never = null; // 错误
  2. const test1: never = undefined // 错误
  3. function a(): never { // 正确,因为死循环了,一直执行不完
  4. while(true) {}
  5. }
  6. function b(): never { // 正确,因为递归,永远没有出口
  7. b()
  8. }
  9. function c(): never { // 正确 代码报错了,执行不下去
  10. throw new Error()
  11. }
  • any (表示任意

全都用这个的话——就是和 js 定义类型一样。

  • unknown (表示未知,和any很像

区别在于:

  • 未知,代表你可以容纳任何类型——因为你也不知道你能装什么,索性都装了。但是,别人就不一样了,别人知道自己要装什么,你是未知类型,不是我要装的—— 别人赋值给你,行!你赋值给别人,不行!
  • 而any,就是橡皮泥,啥形状都行。

对象静态类型

  • object也就是{}

    1. const list: object = {} // 空对象
    2. const list1: object = null; // null对象
    3. const list: object = [] // 数组对象
    4. const list: {} = {}
    5. list.name = 1 // 报错
    6. list.toString()

    对象类型常与后面说到的接口一起使用

  • 数组 && [ ]

    1. const list: [] = []; // 定义一个数组类型
    2. const list1: number[] = [1,2] // 定义一个数组,里面值必须是number
    3. const list2: object[] = [null, {}, []] // 定义一个数组里面必须是对象类型的
    4. const list3: Array<number> = [1,2,3] // 泛型定义数组必须是number类型,泛型后面说
    1. class ClassPerson = {}
    2. const person: ClassPerson = new Person();
    3. person.xxx = 123; // 这行代码报错,因为当前类中不存在该xxx属性
  • 函数

    1. const fn: () => string = () => "zzz" // 定义一个变量必须是函数类型的,返回值必须是string类型

    函数类型注解

    函数是不会自动类型推导的。——但你可以显式地定义注解类型。

    1. function testFnQ(a:number, b:number) :string {
    2. return a + b
    3. }
    4. testFnQ(1,2)

    参数类型设为number,返回类型为string
    ps. 表示无返回值的 void已经在上面说了。
    参数是对象的情况:

    1. function testFnQ(obj : {num: number}) {
    2. return obj.num
    3. }
    4. testFnQ({num: 18})

    元组Tuple

    表示一个已知数组的数量和类型的数组,定义数组中每一个值的类型(不常用

    1. const arr: [string, number] = ["zzz", 1]

    枚举Enum

    使用场景

    控制有些取值是在一些特定的范围的常量,比如

  • 一周七天

  • 方向东南西北或上下左右。

可以设置默认值,不设置则为索引。

简单使用

  1. enum e {
  2. a,
  3. b = "B",
  4. c = "C",
  5. d
  6. }
  7. // e["a"] 0
  8. // e["b"] B
  9. // e["d"] 报错

乍一看,有点像json。但是他对于默认值的设置是没有那么灵活的——如上,如果c设置了默认值,d就不知道”C”递增后是啥了,但如果c=10,那么d就会输出11。
还可反查:

  1. enum e {
  2. a,
  3. b = 9,
  4. c = 10,
  5. d
  6. }
  7. // e[0] a
  8. // e[9] b
  9. // e[10] c
  10. // e[11] d

深入了解

众所周知,原本的js中不像python是没有这枚举类型的,然后我就有些好奇,它编译后的样子。
ts

  1. enum Direction {
  2. Up
  3. Down,
  4. Left,
  5. Right,
  6. }

js

  1. var Direction;
  2. (function(Direction){
  3. Direction[Direction["Up"]= 0] = "Up";
  4. Direction[Direction["Down"]= 1] ="Down;
  5. Direction[Direction["Left"]= 2] ="Left";
  6. Direction[Direction["Right"] =3] = "Right";
  7. })(Direction ||(Direction = {}))

有点妙哈…通过这样达到双向赋值的效果:
Direction["Up"]= 0,然后赋值运算符返回的是被赋予的值,也就是说Direction[Direction["Up"]= 0] = "Up";也就等于Direction[0] = "Up";

接口Interface

关于接口,我觉得了解鸭子类型也许对你有所帮助。

六个字概述接口作用就是:方便复用代码。
没用接口前:

  1. const testObj: { name: string, age: number } = { name: "okk", age: 18 }
  2. const testObj1: { name: string, age: number } = { name: "joo", age: 18 }

可以发现很多重复的代码——用上接口即可改善:

  1. interface Types {
  2. name: string,
  3. age: number
  4. }
  5. const testObj:Types = { name: "okk", age: 18 }
  6. const testObj1: Types= { name: "joo", age: 18 }

修饰符readonly

只读,不可更改。

? 可选修饰符

  1. interface Types {
  2. name: string,
  3. age: number,
  4. height?:number
  5. }

接口定义、使用后,name、age属性是必填的,而height则不是。

继承extends

接口也是可以继承的——和 Class 一样

  1. interface ParentType {
  2. name: string,
  3. age: number,
  4. height?:number
  5. }
  6. interface Child extends ParentType {
  7. weight:number
  8. }

扩展propName

  1. interface ParentType {
  2. name: string,
  3. age: number,
  4. height?:number,
  5. [propName:string]:any //propName字段必须是 string类型 or number类型。 值是any类型
  6. }

如果接口中没有第四个属性,那么定义对象时,是不能私自增加不存在的属性的。

声明类型别名 Type

别名类型只能定义是:基础静态类型、对象静态类型、元组、联合类型。不能定义接口。

  1. type Type1 = string;
  2. type Type12 = string | number
  3. const name: type1 = "zzz"
  4. const age: type12 = 18
  5. const c: type12 = "zzz"

关于 Type 和 interface 的区别

名字

首先从这两个的中文名称入手:

  • interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述。
  • type (类型别名),顾名思义,类型别名只是给类型起一个新名字。它并不是一个类型,只是一个别名而已

    使用与属性

    其次是他们的使用与属性:

  • 定义出来的Type 类型不允许重名

  • 接口是可以重名的,效果就是合并——当然后续接口定义里面的同名属性的类型是要与之前保持一致的,不然编译器会报错
  • type支持表达式;interface不支持 ```typescript const count: number = 123 type testType = typeof count

const count: number = 123

interface testType { [name: typeof count]: any // 报错 }

  1. - type 支持类型映射,interface不支持
  2. ```typescript
  3. type keys = "name" | "age"
  4. type KeysObj = {
  5. [propName in keys]: string
  6. }
  7. const PersonObj: KeysObj = { // 正常运行
  8. name: "zzz",
  9. age: "18"
  10. }
  11. interface testType {
  12. [propName in keys]: string // 报错
  13. }

还有显而易见的一点:type定义时使用 = ,interface没有😏

其实我总感觉大部分时候使用都是看心情😛

联合类型 |

就是或——满足其中一个即可。
函数参数尽量不直接使用联合类型(至少要进行类型保护),函数参数类型本身就不能自动推导,如有调用访问参数类型上的方法,系统编译时并不知道有没有该方法,或者该方法长啥样。

类型保护

typeof

判断一下,参数是不是这类型,是就往下走。

  1. function fun(params: string | number) {
  2. if (typeof params == "string") {
  3. console.log(params.split)
  4. }
  5. if (typeof params == "number") {
  6. console.log(params.toFixed)
  7. }
  8. }

in

看看参数里面有没有想操作的东西,有就操作。

  1. interface a {
  2. name: string
  3. }
  4. interface b {
  5. age: string
  6. }
  7. function fun(params: a | b) {
  8. if ("name" in params) {
  9. console.log(params.name)
  10. }
  11. if ("age" in params) {
  12. console.log(params.age)
  13. }
  14. }

as断言

可以用来手动指定一个值的类型,后接一个接口
当 ts 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法

  1. interface Cat {
  2. name: string;
  3. run(): void;
  4. }
  5. interface Fish {
  6. name: string;
  7. swim(): void;
  8. }
  9. function isFish(animal: Cat | Fish) {
  10. if (typeof (animal as Fish).swim === 'function') {
  11. return true;
  12. }
  13. return false;
  14. }

主要是用于“欺骗”编辑器——“这个animal类型就是Fish啦,往下走就好了”。但是实际运行时会不会出错呢…就看你自己写的对不对了,骗进去了发现真的没有方法就出问题了。

感叹号+点 !.断言

!. 的意思是断言,告诉 ts 该对象里一定有某个值

  1. const inputRef = useRef<HTMLEInputlement>(null);
  2. // 定义了输入框,初始化是 null,但是你在调用他的时候相取输入框的 value,这时候 dom 实例一定是有值的,所以用断言
  3. const value: string = inputRef.current!.value;
  4. // 这样就不会报错了

交叉类型 &

就是与——类型必须存在。每个接口中的所有属性都要传入,如果有同名属性,类型却不一样,类型则都会变成never

⭐泛型

有时想要函数传入的所有参数类型相同:可以都是number,也可以都是string——只要都是就行。如果一个个写,非常麻烦。就可以用上泛型来解决这问题。

  1. function fun<T>(a: T, b: T) {
  2. console.log(a, b)
  3. }
  4. fun<number>(1, "zzz") // 这时报错,因为在调用的使用类型是number,只能都传入number类型
  5. fun<boolean>(true, false)
  6. fun<string>("ooo", "kkk")
  7. fun<any>("jjj",111) //如果你又想让他们两不一样,用 any 就好了

有时我们想让他们不一样,但是又想约束一下范围。——使用 extends

  1. function f<T1 extends number | string, T2 extends number | string>(a: T1, b: T2) {
  2. console.log(a, b)
  3. }
  4. f<number, string>(18, "zzz")
  5. f<string, number>("zzz", 18)

还有需要确保参数有某个属性的情况,就可以配合 接口 使用

  1. interface IWithLength {
  2. lenght:number,
  3. }
  4. function echoLength<T extends IWithLength>(arg:T):T{
  5. console.log(arg.length)
  6. return arg
  7. }

接口、类都是可以配合泛型使用的~

模块

  1. import a,{b} from './c'
  2. export default xx
  3. export const xxx

就是 导入和导出

类 Class

学了C++的应该对下面前三个属性很熟悉😜

public

默认的就是public,哪里都能访问

private

只能在当前类里面访问——且只有类里面的方法才能访问以及对其进行操作。

protected

只能在类和它的子类中访问

implements

就是 继承——可以继承父类(也可以用extends),也可以继承接口

  1. interface Radio{
  2. switchradio():void
  3. }
  4. //继承Radio接口之后,也就意味着告诉Car要实现这个switchRadio功能
  5. class Car implements Radio{
  6. switchRadio(){}
  7. }

如果继承多个就用逗号隔开。

abstract

定义抽象类——不能实例化

类中泛型

类也是可以使用泛型的

  1. class Queue<T> {
  2. private data = []
  3. push(item:T){
  4. return this.data.push(item)
  5. }
  6. pop():T{
  7. return this.data.shift()
  8. }
  9. }
  10. const queue = new Queue<number>()

命名空间

项目总有可能会有重名的变量——我们可以使用 namespace 定义一个命名空间来装各种变量,该暴露的就暴露,没暴露的——也就是内部变量,也就没有重名的风险。

  1. namespace SomeNameSpaceName {
  2. const x = {}//未暴露的
  3. export interface xx {//暴露接口
  4. name: string
  5. }
  6. }

引入

两种方法:

  1. /// <reference path="./namespace0.ts" />

注意是三条斜杠只能放在最顶端

  1. import { xx } from "./xxx.ts"

声明文件

简单使用:
jQery.d.ts

  1. declare var jQuery:(selector: string) => any

一般声明为.d.ts后缀的文件,所有文件都可以访问到,如果不行则需要手动去配置一下tsconfig.json"include":["**/*"]
另外一般第三方库都会有写好的声明文件以供开发者使用,而不是像我上面那样操作。一般命名类似于@types/jquery,使用npm安装即可。

总结

看到这里了,话说我想问一下,现在还有地方是用 js 而不用 ts 的吗?
好了,阅读本文应该能让用过 TS 的稍微的复习一下简单的使用和基础语法,希望没用过的同学也能就此入门~ 下一篇我将带来 ts 中实用类型的相关知识~

🌊如果有所帮助,欢迎点赞关注,一起进步⛵