用大白话讲 TypeScript,两小时快速上手TypeScript(上) (3)

用大白话讲 TypeScript,两小时快速上手TypeScript (上)

start

  • 最近接触到一些前辈写的 TypeScript 代码,我发现,我明明非常熟悉 javascript,但是看 TypeScript 就像看天书一样,一头雾水。

  • 每次下定决心说要去深入学习 TypeScript ,但是到最后却被巨多的文字教程所劝退。

  • 我仔细分析了一下我一直没掌握 TypeScript 的原因,无外乎两点:

    1. 分不清主次; (官方教程肯定是对这个知识点尽可能全面详细的说明,但是对新手来说,入门的时候,太多细节只会让人麻木)
    2. 缺乏使用场景;(和我们大脑记忆方式有关,再好的东西,长时间不使用自然会遗忘)
  • 所以我打算写一个,只讲TypeScript常用知识的博客 ,博客目标:学习完之后能看懂别人写的 ts 代码。先入门了,再看全面的教程优化细节。

    1. 只讲常用内容

    2. 编写博客,加深记忆。

    • 这就是我思考出的破局方法。而且我在开始编写博客的时候,并没完全掌握 TypeScript ,抱着新手的视角去编写博客,应该会更加适合新手入门。祝好。
    • 能力有限,所有个人理解,可能有误,仅供参考,
  • 后续为了方便编写本文 TypeScript 统一简称为 ts

  • 本文作者: lazy_tomato

  • 编写时间:2024-03-06

1. 什么是 TypeScript ?

TypeScript官网解释:TypeScript is JavaScript with syntax for types.

英译: TypeScript 是带有类型语法的 JavaScript

在这里插入图片描述

简单来说,ts 就是在 js 的基础上扩展了类型语法,有了类型语法,有利于代码的静态分析;发现错误;做到语法提示和自动补全;

举个例子,你封装了一个函数 add,有两个参数 ab,函数的作用是返回 ab的和 。由于涉及到加法,所以你希望传入的两个参数都是数字类型的,不希望有其他类型的,此时就可以使用 ts 做类型限制。

function add(a: number, b: number) {
  return a + b
}
add(1, 2)

add('123', '123') // 类型“string”的参数不能赋给类型“number”的参数。

在这里插入图片描述

还有一个要注意的,ts 不能直接执行,需要借助官方插件 tsx,将 ts 编译成 js 再去执行。
在这里插入图片描述

  • 不过我们实际编写 ts 的时候,并没有这么麻烦。
  • 大多数的时候,比如 reactvueCocos creater 3.x 内部已经集成好了 tsx 编译环境,可以让我们直接去写 ts 即可,不需要我们再额外手动处理 tsx
  • 针对高定制化的需求,我们也可以修改对应的配置文件, 定义编译选项。(以具体的使用环境为准)

2. 如何声明类型?

如何声明类型,简单来说:使用冒号,声明类型

// 1. 给变量声明类型-- 下方foo为字符串类型
let foo:string;

// 2. 给函数的形参num 声明类型 number
// 3. 给toString函数的返回值定义类型 string
function toString(num:number):string {
  return String(num);
}

这个时候要培养一个感觉,要熟悉 ts 的写法,可能一眼看去代码量很多。但是,大多数时候,冒号后面内容无论写的再长在复杂。我们在理解代码逻辑的时候,如果因此受到困扰,可以选择先忽略这些复杂的类型。

我们再来体验一下这种感觉。

export function startMeasure(
  instance: ComponentInternalInstance,
  type: string
) {}


export function emit(
  instance: ComponentInternalInstance,
  event: string,
  ...rawArgs: any[]
) {}

不看类型限制,上方的函数,其实就是下方的函数,其实就是一个很普通的函数,仅此而已。

export function startMeasure(instance, type) {}

export function emit(instance, event, ...rawArgs) {}

ok,本节就讲这么多,有这种感觉后,至少能保证就算类型定义的再复杂,我们能看得懂基础的代码。

3. 可以声明的类型有哪些?

3.1 基本类型

上一节我们熟悉了如何声明类型,现在有一个问题:有哪些类型可以声明呢?

从上面的知识我们可以了解到, ts 是在 js 基础上扩展了类型语法,结合第 2 节的示例代码,我们大胆猜测一下。既然 js 中的自带 8 种数据类型,那么 ts 应当支持这 8 种类型吧?

答案:TypeScript 继承了 JavaScript 的类型设计,以下 8 种类型可以看作 TypeScript 的基本类型。

  1. boolean
  2. string
  3. number
  4. bigint
  5. symbol
  6. object
  7. undefined
  8. null

这 8 种类型,也不用死记硬背,对标 js 的 8 种数据类型即可。

要注意一下,类型都是用小写字母。首字母大写的NumberStringBoolean等在 JavaScript 语言中都是内置对象,而不是类型名称。

想看更详细的介绍: TypeScript 教程–阮一峰–基本类型

3.2 基于基本类型的拓展

基于基本类型的拓展(为了方便记忆,我自己定义的一个归类。)

上一节内容说到了 8 种基本类型,但是在实际使用的时候,面对不同的声明类型需求,我们需要一些更加灵活的方式。

比如:

  1. 我希望变量只能是某一个值;
  2. 我希望变量可以是多种类型中的一个;
  3. 我希望变量可以同时满足多种类型;

分别对应:

值类型

TypeScript 规定,单个值也是一种类型,称为“值类型”。

联合类型

联合类型(union types)指的是多个类型组成的一个新类型,使用符号|表示。

交叉类型

交叉类型(intersection types)指的多个类型组成的一个新类型,使用符号&表示。

1. 值类型

/* 1. 值类型 */
let flag: 5

flag = 5

// flag = 10 // 不能将类型“10”分配给类型“5”。

2. 联合类型

/* 2. 联合类型 */

// id 既可以是数字也可以是字符串
function printId(id: number | string) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase())
  } else {
    console.log(id)
  }
}

3. 交叉类型

/* 3. 交叉类型 */

// 既有 字符串的foo,又有 字符串的bar
let obj: { foo: string } & { bar: string }

obj = {
  foo: 'hello',
  bar: 'world',
}

// 交叉类型常用来为对象类型添加新属性
个人理解

上述介绍的几个类型,其实就是对基础类型的简易组合:

  • 把一个固定的值当做类型;

  • 把基本类型,做 | 或 或者 & 与 逻辑,做一个联合或者交叉。

3.3 any 类型

为了方便代码从 js 迁移到 ts,降低学习难度,在 ts 中直接编写 js 代码,不添加任何类型限制是完全兼容的。

那如果什么类型都没有提供,ts 会如何记录变量的类型呢?

借助 vscode 的代码编辑器的提示,我们可以看到,提示变量的类型为 any

function add(a, b) {
  return a + b
}

变量的类型为any

3.3.1 any (任何)

any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。

相当于,你随便给这个变量传什么值都可以,和 js 的语法很像;

为什么编辑器会自动提示 any 类型呢?我并没有给示例代码中的 addab 显式定义 any

  • 原因:对于开发者没有指定类型、TypeScript 必须自己推断类型的那些变量,如果无法推断出类型,TypeScript 就会认为该变量的类型是 any

  • 简单来说,ts 会自己推断变量的类型,如果无法推断出来,则认为该变量是 any

使用 any 类型的变量可以赋予任意值,但是与之而来的就是两个缺点:

  1. 主动放弃了对这个变量的类型限制。

    • 我们使用 ts 的目的就是增加类型限制,设置类型为 any,丢失了使用 ts 的意义。

    • 当然某些时候:变量的类型我们刚接触并不清楚,为了快速完成功能,适当的 any 也是可以的.

      • 结论: 可以用,但是不建议。
  2. 类型污染

    • any 会“污染”其他变量。它可以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错。

      let x:any = 'hello';
      let y:number;
      
      y = x; // 不报错
      
      y * 123 // 不报错
      y.toFixed() // 不报错
      
3.3.2 unknown (未知的)

为了解决 any 类型“污染”其他变量的问题,TypeScript 3.0 引入了 unknown 类型。

unknown 类型特点:
  1. unknown 可以赋值为各种类型的值。
  2. 不能直接赋值给其他类型(其他类型 除了any类型和unknown类型)的变量。
  3. 不能直接调用 unknown 类型变量的方法和属性。
  4. unknown类型变量能够进行的运算是有限的,只能进行比较运算(运算符=====!=!==||&&?)、取反运算(运算符!)、typeof运算符和instanceof运算符这几种,其他运算都会报错。
如何才能使用 unknown 类型变量呢?
let a:unknown = 1;

// 类型缩小
if (typeof a === 'number') {
  let r = a + 10; // 正确
}
个人理解

为了解决 any 的类型污染,引入 unknown 类型,可以接收任何值,不过在使用的时候,需要增加额外的判断才能使用。达到解决类型污染的问题。

简单理解就是,unknown 被限制使用的 any

3.3.3 never (从来没有)

为了保持与集合论的对应关系,以及类型运算的完整性,TypeScript 还引入了“空类型”的概念,即该类型为空,不包含任何值。

由于不存在任何属于“空类型”的值,所以该类型被称为never,即不可能有这样的值。

例如:

// 一个变量又是数字类型又是字符串类型,显然不可能,此时a就是never类型
let a: number & string

在这里插入图片描述

个人理解

其实就是为了保证类型的完整性,每个变量都有类型。针对那种不可能有值的情况,定义这个变量为 never

4. type 和 interface

我这篇文章的目的就是希望学习完毕后,能够看懂别人的 ts 代码。

我们目前已经学习了一部分内容,现在找一个 ts 代码验证一下。我打开 vue3 的源代码仓库,看看他们写的代码,看我们是否能看懂。

vue3 中的一个 ts 文件截图:

在这里插入图片描述

按常理,js 文件中最顶部的代码,就是引入一些关联模块。结合上图,我发现除了引入了一些关联模块,还有两个单词高频出现:type,interface,它们是什么意思? 我们去学习一下。

英译:

  • type:类型

  • interface: 接口

4.1 type

4.1.1 type 作用

type 命令用来 定义一个类型的别名

/*  1. type 用于定义类型的别名 */

// 案例一
type Age = number
let age: Age = 55

// 案例二
type All = number | string
let like: All = 777
like = '爱吃番茄'
4.1.2 type 注意事项
  • 别名不能重名;
  • 别名的作用域是块级作用域;

别名不能重名

别名不能重名

别名的作用域是块级作用域

别名的作用域是块级作用域

4.1.3 个人理解

type 简单来说,可以定义类型的名称,就好像我们定义一个变量存储数据一样。不过 type 定义的是类型名,存储的是类型数据。

请切记:不要被复杂的名称劝退

比如:

type TLaztTomatoLikeJavaScript = number
type ___sdmpwqmem_sawqen12321_qwmeqwmmwo = number
type SFCStyleCompileOptions = number

// 其实上面的变量,只是看起来复杂而已,它们都是代表着是一个 数字 类型。

4.2 interface

4.2.1 声明对象的类型

interface 之前,说说我自己目前的疑惑。前面提到,大多数情况下定义类型呢,我们使用 : 冒号

比如:

function toString(num:number):string {
  return String(num);
}

我有什么疑惑呢? 目前学习到的知识,声明类型:直接在变量后面加冒号 变量 :类型,那么如何声明对象的属性的类型呢?

对象中的属性名后面的冒号,代表着给这个属性赋值,那么该如何用冒号声明类型呢?

let a: string

let obj = {
  name: 'lazytomato', //  name 后面的冒号代表着给这个属性去赋值,肯定是不可以在 name 后面直接加冒号定义类型的。
}

我们可以直接给 obj 后加冒号,声明对象的类型,例如:

let obj: {
  name: string
} = {
  name: 'lazyTomato',
}

obj = {
  name: 123, // 赋值 number 类型, 会报错
}

简单介绍下,定义对象的类型的其他情况:

let obj: {
  a: string // 1. 必须包含的属性
  b?: string // 2. 可选的属性
  readonly c: string // 3. 必须包含的属性,且只读
} = {
  a: 'lazyTomato',
  c: '123',
}

// obj.c = '爱吃番茄' // 无法为“c”赋值,因为它是只读属性。

上面的示例介绍了,对象中:

  1. 普通的属性
  2. 可选的属性
  3. 只读的属性
4.2.2 简化对象的类型声明(type interface 初体验)

前一节内容,可以看到给对象的属性声明类型。但是每次都写一大串内容,肯定是不够方便的。

这个时候就需要简写形式。

有两种简化方式:

  1. type 定义别名;
  2. 定义一个 interface 接口
// 方法一 type定义别名
type myObj = {
  a: string
  b?: string
  readonly c: string
}

let obj: myObj = {
  a: 'lazyTomato',
  c: '123',
}

// 方法二 定义一个接口
interface myObj2 {
  a: string
  b?: string
  readonly c: string
}

let obj2: myObj2 = {
  a: 'lazyTomato',
  c: '123',
}

其实大家可以自己用手敲一下上面两种方式,再看下面的内容。

我讲一讲我的切实感受:

  1. type 是定义别名,所以需要有一个 =interface 很像是定义一个 class形式的对象,不需要 =

  2. type 是定义别名,所以可以定义:基础类型,以及基础类型的一些拓展 (值,联合,交叉类型)interface是定义对象,不能直接设置基础类型。

type a = number
type b = 5
type c = number | (string & boolean)

type d = {
  name: string
}

interface e {
  name: string
}

interface f number  // 这里会报错

在这里插入图片描述

4.2.3 interface 是什么

interface 介绍:interface 是对象的模板,可以看作是一种类型约定,中文译为“接口”。使用了某个模板的对象,就拥有了指定的类型结构。

  • 个人理解:以对象形式去声明类型的形式。

  • 再大白话一点,就是定义了一个对象,不过这个对象存储的是对象属性名对应属性值的类型

想看更详细的介绍: TypeScript 教程–阮一峰–interface

4.2.4 interface 写法介绍

interface 可以表示对象的各种语法,它的成员有5种形式。

  • 对象属性
  • 对象的属性索引
  • 对象方法
  • 函数
  • 构造函数

1. 对象属性

interface Point {
  x: number;
  y: number;
}

2. 对象的属性索引

interface A {
  [prop: string]: number;
}
// 定义 属性名是字符串类型, 属性值是数字

3. 对象的方法

// 写法一
interface A {
  f(x: boolean): string;
}

// 写法二
interface B {
  f: (x: boolean) => string;
}

// 写法三
interface C {
  f: { (x: boolean): string };
}

4. 函数

interface Add {
  (x:number, y:number): number;
}

5. 构造函数

interface ErrorConstructor {
  new (message?: string): Error;
}
// interface 内部可以使用`new`关键字,表示构造函数。
4.2.5 interface 注意事项
  1. interface 可以继承。

    先混个眼熟,知道可以用 extends 继承接口即可,整体形式有点类似对象的操作。

  2. 多个 interface 会自动合并

    interface a {
    name: string
    }
    interface a {
    //  name: number // 前面声明过了 name,后面再声明的时候,会报错,提示必须和前一次类型相同。
    age: number
    }
    
    let obj: a = {
    name: 'tomato',
    age: 18,
    }
    
  3. 块级作用域

    interface a {
    name: string
    }
    
    if (true) {
    interface a {
     age: number
    }
    }
    
    let obj: a = {
    name: 'tomato',
    age: 18, // 报错 对象字面量只能指定已知属性,并且“age”不在类型“a”中
    }
    
    

4.3 type 和 interface 异同

相同点:
  1. interface 命令与 type 命令作用类似,都可以表示对象类型。
不同点:
  1. type 能够表示非对象类型,而 interface 只能表示对象类型(包括数组、函数等)。

  2. interface 可以继承其他类型,type 不支持继承。

  3. 同名 interface 会自动合并,同名 type 则会报错。

  4. interface 不能包含属性映射(mapping),type 可以。

  5. this 关键字只能用于 interface

  6. type 可以扩展原始数据类型,interface 不行。

个人理解

列了一堆内容,其实很简单,两者都可以简化类型的声明。不过两者的侧重点不同。

  • type 更倾向于描述:一个组合的类型,简化成一个名称,同一作用域下保证唯一。

  • interface 更倾向于描述:对象形式的类型,支持继承,this,易扩展。

推荐:定义对象的类型,就用 interfaceinterface 做不到的用 type

4.4 小试牛刀

刚学完 typeinterface,我们再看看部分 Vue3ts 源码,看能不能看懂。

interface

export interface SFCStyleCompileOptions {
  source: string
  filename: string
  id: string
  scoped?: boolean
  trim?: boolean
  isProd?: boolean
  inMap?: RawSourceMap
  preprocessLang?: PreprocessLang
  preprocessOptions?: any
  preprocessCustomRequire?: (id: string) => any
  postcssOptions?: any
  postcssPlugins?: any[]
  /**
   * @deprecated use `inMap` instead.
   */
  map?: RawSourceMap
}

export interface CSSModulesOptions {
  scopeBehaviour?: 'global' | 'local'
  generateScopedName?: | string
    | ((name: string, filename: string, css: string) => string)
  hashPrefix?: string
  localsConvention?: 'camelCase' | 'camelCaseOnly' | 'dashes' | 'dashesOnly'
  exportGlobals?: boolean
  globalModulePaths?: RegExp[]
}

export interface SFCStyleCompileResults {
  code: string
  map: RawSourceMap | undefined
  rawResult: Result | LazyResult | undefined
  errors: Error[]
  modules?: Record<string, string>
  dependencies: Set<string>
}

type

export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode

export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode

export type TemplateChildNode =
  | ElementNode
  | InterpolationNode
  | CompoundExpressionNode
  | TextNode
  | CommentNode
  | IfNode
  | IfBranchNode
  | ForNode
  | TextCallNode
个人理解
  1. 第一,学习了 typeinterface,再看上面的代码,感觉也不难,简单来说,无论名称多么复杂,其实就是定义了一个类型,仅此而已。
  2. 第二,我在搜索素材的时候,发现大部分对象的类型,都是用 interface 定义的,而少部分需要用到交叉,联合等类型,才用 type 定义,符合我们之前对比两者差异时总结的。

end

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lazy_tomato

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值