【FEM】TypeScript Fundamentals, v3

139 阅读10分钟

课程地址:frontendmasters.com/courses/typ… 课件:www.typescript-training.com/course/fund…

1 Intro

聊了下 ts的现状,和为啥要类型

1.1 什么是 typescript

TypeScript is an open source, typed syntactic superset of JavaScript

  • Compiles to readable JS
  • Three parts: Language, Language Server and Compiler
  • Kind of like a fancy linter

1.2 Why developers want types

It has the potential to move some kinds of errors from runtime to compile time 编译时发现错误

It serves as the foundation for a great code authoring experience 写代码更好的自动化体验

2 Hello TypeScript

2.1 项目的剖析

一个很简单的ts项目,有下面三个文件

package.json # Package manifest

tsconfig.json # TypeScript compiler settings

src/index.ts # "the program"

package.json (view source)

{
  "name": "hello-ts",
  "license": "NOLICENSE",
  "devDependencies": {
    "typescript": "^4.3.2"
  },
  "scripts": {
    "dev": "tsc --watch --preserveWatchOutput"
  }
}

这里的话

  • 我们只有ts这一个依赖
  • 我们有一个开发脚本(这是您从项目根调用 yarn dev-hello-ts 时运行的)
    • 它以“监视”模式运行TypeScript编译器(监视源代码更改,并自动重建)。

以下是关于TS编译器最简单的配置文件:

tsconfig.json (view source)

{
  "compilerOptions": {
    "outDir": "dist", // where to put the TS files
    "target": "ES3" // which level of JS support to target
  },
  "include": ["src"] // which files to compile
}

以下是我们打算编译的原始(TypeScript)源代码:

src/index.ts (view source)

/**
 * Create a promise that resolves after some time
 * @param n number of milliseconds before promise resolves
 */
 function timeout(n: number) {
  return new Promise((res) => setTimeout(res, n))
}
 
/**
 * Add three numbers
 * @param a first number
 * @param b second
 */
export async function addNumbers(a: number, b: number) {
  await timeout(500)
  return a + b
}
 
//== Run the program ==//
;(async () => {
  console.log(await addNumbers(3, 4))
})()

请注意,当您将鼠标悬停在本网站的某些代码点上时,您将获得相当于“VScode工具提示”的信息。这是我们学习TypeScript如何理解我们的代码的最重要工具之一!

在上面的例子中,我们使用导出关键字来演示使用TypeScript模块。使用模块可以在本地工作,但“尝试”链接不会在TypeScript playground中运行,因为它不支持多个文件。以下是没有模块导出的相同代码的示例,因此您可以在TypeScript playground中运行代码!

2.2 Running the compiler

yarn dev

请注意,在“hello-ts”项目中

出现了一个•/dist文件夹,里面是一个index.js文件。

2.3 Changing target language level

{
  "compilerOptions": {
      "outDir": "dist",
-       "target": "ES3"
+       "target": "ES2015"
  },
  "include": ["src"]
}

再看看那个dist/index.js文件-它现在干净多了!你注意到发生了什么变化了吗?

你能找到一个Promise构造函数吗?也许是yield关键字?

2.4 文件

A good way to think of TS files:

  • .ts files contain both type information and code that runs
  • .js files contain only code that runs
  • .d.ts files contain only type information

2.5 模块类型

node packages/hello-ts/dist/index.js

如果我们之间node运行 编译好的js文件,不行的

似乎,至少对于最新版本的Node js和我们项目目前的设置方式,我们不能按原样直接运行这个程序。

Node期望CommonJS模块,因此我们必须告诉TypeSeript输出此类代码。

让我们在我们的tsconfig文件中添加一个新属性:

修改tsconfig

"compilerOptions": {
"outDir": "dist",
+ "module": "CommonJS",

这个时候编译出来的js文件,他就会用commonjs的模块方式 再次node就OK了

3 variable and values

3.1 Variable Declarations & Inference

ts 会 推断类型,当你没有显示声明的时候

let

let age = 6
age = "not a number"

Type 'string' is not assignable to type 'number'. 这里第二句会报错

const

如果我们,用的是const声明呢?

const age = 6

注意这里我们声明的是, age:6, 而不是 age:number

请注意,这个变量的类型不是数字,而是6。TS能够在这里做出更具体的假设,因为:

•常量变量声明无法重新分配

•分配给年龄的初始值是一个数字,这是一个不可变的值类型

因此,在这个项目中,年龄将永远是6岁。

3.2 Literal Types

The type 6 is called a literal type. If our let declaration is a variable that can hold any number, the const declaration is one that can hold only 6 — a specific number.

3.3 隐式的any类型 和 类型注释

有时,我们需要在初始化变量之前声明变量,例如下面的结束时间:

// between 500 and 1000
const RANDOM_WAIT_TIME =
  Math.round(Math.random() * 500) + 500
 
let startTime = new Date()
let endTime
      
let endTime: any
 
setTimeout(() => {
  endTime = 0
  endTime = new Date()
}, RANDOM_WAIT_TIME)

这里的endTime就是any类型。 如果你需要更加严格的管理类型,就应该在声明的时候带上类型

let endTime: Date

3.4 函数参数和返回值

函数的参数,你不声明也是默认any

function add(a: number, b: number): number {
    return a + b
}
const result = add(3, 4)

关键是,我们确保正确的输入和正确的输出

4 Objects, Arrays and Tuples

4.1 Object

一般我们关心对象:

  • 存在(或可能存在)的属性的名称
  • 这些属性的类型
let car: {
    make: string
    model: string
    year: number
}


``/** * Print information about a car to the console * @param car - the car to print */
function printCar(car: {
make: string
model: string
year: number
}) {
console.log(`${car.make} ${car.model} (${car.year})`)
}``

可选属性

可能某些属性是可选值

car: {
    make: string
    model: string
    year: number
    chargeVoltage?: number
}

image.png

使用的时候

// Works
printCar({
  make: "Honda",
  model: "Accord",
  year: 2017,
})
// Also works
printCar({
  make: "Tesla",
  model: "Model 3",
  year: 2020,
  chargeVoltage: 220,
})

** 过度属性检查

字面量引起的

image.png

这样我们是有错误的对吧

来看这个

// @errors: 2345
function printCar(car: {
  make: string
  model: string
  year: number
  chargeVoltage?: number
}) {
  // implementation removed for simplicity
}

const myCar = {
  make: "Tesla",
  model: "Model 3",
  year: 2020,
  chargeVoltage: 220,
  color: "RED", // <0------ EXTRA PROPERTY
}


printCar(myCar)

// 注意函数里面是无法找到color的,但是

myCar.color // ok

在这种情况下,在printcar函数的主体中,我们无法访问颜色属性,因为它不是参数类型的一部分。因此,我们正在定义这个对象上的一个属性,我们没有希望以后安全访问!

这里给了三种方式修复

  • 从对象中删除颜色属性
  • 向函数参数类型添加颜色:字符串
  • 创建一个变量来保存此值,然后将变量传递到printCar函数中

补充:这里的核心是,ts是一个结构类型的检查系统 TypeScript 的核心原则之一是它是一个结构类型系统。*
*简而言之,只要您提供具有所需属性的类型,TypeScript 就可以拥有额外的属性,让我们看一个例子:

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

const meaningfulPoint = {
  x: 1,
  y: 2,
  meaningOfLife: 42,
}; // Type is { x: number, y: number, meaningOfLife: number }

const point: Point = meaningfulPoint; // OK - Structural Typing Works

但是字面量不行🙅

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

const point: Point = { x: 1, y: 2, meaningOfLife: 42 }; // ERROR

什么意思?

当 TypeScript 在赋值中遇到对象字面量或作为参数传递给函数时,它会触发称为过度属性检查的操作。****

与结构类型相反,它检查对象是否具有确切的属性。如前所述,对象字面量作为参数传递给函数也是如此:

索引签名

有时我们需要为字典表示一种类型,其中一致类型的值可以通过键检索。

让我们考虑以下电话号码集合:

const phones: {
  [k: string]: {
    country: string
    area: string
    number: string
  }
} = {}
 
phones.fax

4.2 Array

const fileExtensions = ["js", "ts"]

image.png

4.3 Tuple

有时,我们可能想使用多元素、有序的数据结构,其中每个项目的位置都有一些特殊的含义或约定。这种结构通常被称为元组。

image.png

元祖就是你定义好长度,有序,规定死的数组

const numPair: [number, number] = [4, 5]

Limitations

image.png

5 结构类型与名义类型

5.1 什么是类型检查

类型检查可以被认为是一项试图评估兼容性或类型等价性问题的任务:

什么时候进行呢? 变量赋值,返回...

5.2 Static vs dynamic

将类型系统排序为静态或动态与是否在编译时或运行时执行类型检查有关。

TypeScript的类型系统是静态的。

Java、C#、C++都属于这个类别。请记住,推理仍然可以在静态类型系统中发生-TypeSeript、Scala和Haskell都有某种形式的静态类型检查。

动态类型系统在运行时执行其“类型等价”评估。JavaScript、Python、Ruby、Perl和PHP属于这一类。

5.3 Nominal vs structural

TypeScript类型系统是结构性的

结构类型系统都是关于结构或形状的。让我们看看TypeScript的例子:

class Car {
  make: string
  model: string
  year: number
  isElectric: boolean
}
 
class Truck {
  make: string
  model: string
  year: number
  towingCapacity: number
}
 
const vehicle = {
  make: "Honda",
  model: "Accord",
  year: 2017,
}
 
function printCar(car: {
  make: string
  model: string
  year: number
}) {
  console.log(`${car.make} ${car.model} (${car.year})`)
}
 
printCar(new Car()) // Fine
printCar(new Truck()) // Fine
printCar(vehicle) // Fine

函数printCar不关心其参数来自哪个构造函数,它只关心它是否有:

  • make property that’s of type string
  • model property that’s of type string
  • year property that’s of type number

传递给它的参数符合这些要求,printCar ishappy。

5.4 鸭子类型

“鸭子打字”的名字来自“鸭子测试”。

“如果它看起来像一只鸭子,像鸭子一样游泳,像鸭子一样呱呱叫,那么它可能是一只鸭子。

在实践中,这与结构类型非常相似,但“鸭子类型”通常用于描述动态类型系统。

5.5 强类型、弱类型

这些术语虽然经常使用,但没有商定的技术定义。在TypeScript的上下文中,那些说“强”的人通常意味着“静态”。

6 联合和交叉类型

联合和交叉类型

联合和交集类型在概念上可以被认为是逻辑布尔运算符(AND OR),因为它们与类型有关。让我们以这组两个重叠的项目集为例:

image.png

6.1 TypeScript中的联合类型

Union types in TypeScript can be described using the | (pipe) operator.

function flipCoin(): "heads" | "tails" {
    if (Math.random() > 0.5) return "heads"
    return "tails"
}
const outcome = flipCoin()
function maybeGetUserInfo():
  | ["error", Error]
  | ["success", { name: string; email: string }] {
  if (Math.random() > 0.5) {
    return [
      "success",
      { name: "Mike North", email: "mike@example.com" },
    ]
  } else {
    return [
      "error",
      new Error("The coin landed on TAILS :("),
    ]
  }
}
/// ---cut---
const outcome = maybeGetUserInfo()
const [first, second] = outcome
first.split
//     ^|
second.name
//      ^|

image.png

6.2 Narrowing with type guards

image.png

6.3 Intersection Types in TypeScript

TypeScript中的交叉类型可以使用&(ampersand)运算符来描述。

例如,如果我们有一个Promise,其中添加了额外的startrime和endTime属性呢?

image.png

7 Interfaces and Type Aliases

7.1 Type aliases

Type aliases help to address this, by allowing us to:

  • define a more meaningful name for this type
  • declare the particulars of the type in a single place
  • import and export this type from modules, the same as if it were an exported value

image.png

重要的是要意识到Usercontactinfo这个名字只是为了方便我们。

这仍然是一个结构型系统!!!!

image.png

语法

type UserContactInfo = {
    name: string
    email: string
}

1.这是一个罕见的场合,我们在赋值运算符(=)的右侧看到类型信息

2.我们正在使用Titlecase来格式化别名的名称。这是一个共同的惯例

3.正如我们在下面看到的,我们只能在给定范围内声明一次给定名称的别名。这有点像let或const变量声明的工作原理

7.2 Inheritance in type aliases

您可以使用交叉(&)类型创建将现有类型与新行为相结合的类型别名。

image.png

7.3 Interfaces

接口是定义对象类型的方式。“对象类型”可以被认为是,“可以想象,类的实例看起来像这样)。

例如,字符串编号不是对象类型,因为它使用联合类型运算符。

image.png

7.4 接口的继承

image.png

在那里,那更好。虽然TypeScript(和JavaScript)不支持真正的多重继承(从多个基类扩展),但此实现关键字使我们能够在编译时验证类的实例是否符合一个或多个“合同”(类型)。请注意,扩展和实现都可以一起使用:

继承也可以和type 类型别名使用,但是不建议

7.5 Open Interfaces

TypeScript接口是“开放的”,这意味着与类型别名不同,您可以在同一范围内拥有多个声明:

image.png

再次声明接口,可以做补充

一个用法,用于window扩展

image.png

7.6 选择interface还是type

在许多情况下,类型别名或接口都可以,但是......

  1. If you need to define something other than an object type (e.g., use of the | union type operator), you must use a type alias
  2. If you need to define a type to use with the implements heritage term, it’s best to use an interface
  3. If you need to allow consumers of your types to augment them, you must use an interface.

=> 是否让用户增强

7.7 递归类型

image.png

8 JSON types

A JSON value MUST be an
- object
- array
- number
- string,
or one of the following three literal names:
- false
- true
- null

image.png

9 Functions

9.1 Callable types

image.png

9.2 void

image.png

9.3 Construct signatures

image.png

9.4 Function overloads

image.png

9.5 this

image.png

9.6 函数类型最佳实践

举例,主要是你要有相关返回对应到类型

10 类

10.1 Class Fields

image.png

10.2 访问修饰符关键字

publicprivate and protected

keywordwho can access
publiceveryone (this is the default)
protectedthe instance itself, and subclasses
privateonly the instance itself

JS private #fields

image.png

readonly

image.png

Param propertie

class Car {
  make: string
  model: string
  year: number
  constructor(make: string, model: string, year: number) {
    this.make = make
    this.model = model
    this.year = year
  }
}

可以改造到construct里面

image.png

class Car {
    constructor(public make: string) {}
}
  1. super()
  2. param property initialization
  3. other class field initialization
  4. anything else that was in your constructor after super()

11 Top and bottom types

11.1 类型描述允许值的集合

11.2 top types

top type (symbol: ) is a type that describes any possible value allowed by the system. To use our set theory mental model, we could describe this as {x| x could be anything }

TypeScript provides two of these types: any and unknown.

any

image.png

unknow

Values with an unknown type cannot be used without first applying a type guard

image.png

Practical use of top types

你会遇到顶级类型经常派上用场的地方。特别是,如果您将项目从JavaSeript转换为TypeScript,那么能够增量添加越来越强大的类型非常方便。在你有机会给予他们一些关注之前,很多事情都是有的。

未知非常适合在运行时收到的值(例如,您的数据层)。通过迫使这些值的消费者在使用它们之前进行一些轻微的验证,错误会更早地被发现,并且通常可以在更多的上下文中浮出水面。

11.3 Bottom type: never

详尽的条件

image.png

现在,将条件保持原样,让我们将Boat添加为车辆类型:

image.png

image.png

12 Type guards and narrowing

12.1 类型收缩

interface CarLike {
  make: string
  model: string
  year: number
}

let maybeCar: unknown

// the guard
function isCarLike(
  valueToTest: any
): valueToTest is CarLike {
  return (
    valueToTest &&
    typeof valueToTest === "object" &&
    "make" in valueToTest &&
    typeof valueToTest["make"] === "string" &&
    "model" in valueToTest &&
    typeof valueToTest["model"] === "string" &&
    "year" in valueToTest &&
    typeof valueToTest["year"] === "number"
  )
}

// using the guard
if (isCarLike(maybeCar)) {
  maybeCar
  // ^?
}

注意危险:

image.png

12.2 asserts

我们可以采取另一种方法来消除对条件的需求。密切关注assertsIsCarLike的返回类型:

// the guard
function assertsIsCarLike(
    valueToTest: any
): asserts valueToTest is CarLike {
    ...+
}

从概念上讲,幕后发生的事情非常相似。通过使用这种特殊语法来deseribe返回类型,我们通知TypeScript,如果assertsIscaruike抛出错误,则应将其视为表示valueToTest与CarLike不等价

因此,如果我们越过断言并在下一行继续执行代码,类型会发生变化

从未知到CarLike

类型的guard是个双刃剑,要小心

13 无效值

13.1 null

Nul1的意思是:有一个值,而该值什么都不是。虽然有些人认为空不是JS语言的重要组成部分,但我发现表达“无”结果的概念是有用的(有点像空数组,但不是数组)。

这没有什么在很大程度上是一个定义的值,当然是一种存在——而不是缺乏信息。

13.2 undefined

undefined意味着该值不可用(尚未?)

13.3 void

主要用于函数,表示无返回值

13.4 非空断言

image.png

image.png

泛型

定义类型参数

image.png