Setting things up

让我们开始编写我们的第一个TypeScript应用,为了保持简单,让我们通过使用ts-node这个 npm 包开始。
它能立即编译并执行指定的 TypeScript 文件,因此不需要单独的编译步骤。

在空目录中运行命令**npm init** 来设置 npm 项目
安装ts-node和typescript依赖

  1. npm install --save-dev ts-node typescript

在package.json中设置scripts

  1. {
  2. // ..
  3. "scripts": {
  4. "ts-node": "ts-node"
  5. },
  6. // ..
  7. }

在package.json中使用ts-node, 需要添加前缀 —

  1. npm run ts-node -- file.ts

TypeScript 还提供了一个在线playground, 可以用来编写和测试TS代码

Creating your first own types

联合类型 union type
可以返回几个指定的值,类型可以为string | number, | 表示或

  1. type Operation = 'multiply' | 'add' | 'divide'

计算器程序代码:

  1. type Operation = 'multiply' | 'add' | 'divide'
  2. type Result = number | string
  3. const calculator = (a: number, b: number, op: Operation): Result => {
  4. switch (op) {
  5. case 'multiply':
  6. return a * b
  7. case 'divide':
  8. if (b === 0) throw new Error("Can't divide by 0!")
  9. return a / b
  10. case 'add':
  11. return a + b
  12. default:
  13. throw new Error('Operation is not multiply, add or divide!')
  14. }
  15. }
  16. try {
  17. console.log(calculator(1, 5, 'divide'))
  18. } catch (e) {
  19. console.log('Something went wrong, error message: ', e.messsage)
  20. }

可以通过安装一个名为@types /-prefix 的 npm 包将相关类型添加到项目中。 例如: npm install —save-dev @types/react @types/express @types/lodash @types/jest @types/mongoose 等等。
通过安装包 @types/node 获得Node类型

  1. npm install --save-dev @types/node

安装node类型后,cannot find name ‘console’的错误就消失了
image.png
修复错误:error的类型为unknown
加if判断

  1. try {
  2. console.log(calculator(1, 5, 'divide'))
  3. } catch (e) {
  4. if (e instanceof Error)
  5. console.log('Something went wrong, error message: ', e.message)
  6. }

image.png

Improving the project

改进程序,让程序可以从命令行获取参数process.argv

  1. const multiplicator = (a: number, b: number, printText: string) => {
  2. console.log(printText, a * b)
  3. }
  4. const a: number = Number(process.argv[2])
  5. const b: number = Number(process.argv[3])
  6. multiplicator(a, b, `Multiplied numbers ${a} and ${b}, the result is:`)

在script中添加multiply

  1. "scripts": {
  2. "ts-node": "ts-node",
  3. "multiply": "ts-node multiplier.ts",
  4. // ...
  5. },

执行命令**npm run multiply 5 2**, 则能获取到参数5和2
image.png
但是当参数不为数字时,得到NaN, 修改代码来避免这个bug

  1. interface MultiplyValues {
  2. value1: number
  3. value2: number
  4. }
  5. const parseArguments = (args: Array<string>): MultiplyValues => {
  6. if (args.length < 4) throw new Error('Not enough arguments')
  7. if (args.length > 4) throw new Error('Too many arguments')
  8. if (!isNaN(Number(args[2])) && !isNaN(Number(args[3]))) {
  9. return {
  10. value1: Number(args[2]),
  11. value2: Number(args[3]),
  12. }
  13. } else {
  14. throw new Error('Provided values were not numbers!')
  15. }
  16. }
  17. const multiplicator = (a: number, b: number, printText: string) => {
  18. console.log(printText, a * b)
  19. }
  20. try {
  21. const { value1, value2 } = parseArguments(process.argv)
  22. multiplicator(
  23. value1,
  24. value2,
  25. `Multiplied ${value1} and ${value2}, the result is: `
  26. )
  27. } catch (e) {
  28. if (e instanceof Error)
  29. console.log('Error, something bad happened, message: ', e.message)
  30. }

**args: Array<string>** 表示参数的类型是字符串数组
返回值类型MultiplyValues用关键字interface声明,interface用来定义对象应该具有的形状(shape)

Exercises 9.1.-9.3.

在一个库目录**npm init**创建项目
安装ts-node和typescript

  1. npm install --save-dev ts-node typescript

新建tsconfig.json文件, 添加下列配置,使所有变量都必须有类型

  1. {
  2. "compilerOptions": {
  3. "noImplicitAny": true,
  4. }
  5. }

exercise 9.2

写一个程序,计算运动是否达标

  1. interface Result {
  2. periodLength: number
  3. trainingDays: number
  4. target: number
  5. average: number
  6. success: boolean
  7. rating: number
  8. ratingDescription: string
  9. }
  10. const calculateExercises = (
  11. trainingTime: Array<number>,
  12. target: number
  13. ): Result => {
  14. const periodLength = trainingTime.length
  15. const trainingDays = trainingTime.filter((i) => i !== 0).length
  16. const average =
  17. trainingTime.reduce((sum, item) => sum + item, 0) / periodLength
  18. const success = average >= target ? true : false
  19. let rating = 0,
  20. ratingDescription = ''
  21. if (average < 0.5) {
  22. rating = 1
  23. ratingDescription = 'keep going'
  24. } else if (average >= 0.5 && average < 1) {
  25. rating = 2
  26. ratingDescription = 'not too bad but could be better'
  27. } else {
  28. rating = 3
  29. ratingDescription = 'awesome!'
  30. }
  31. return {
  32. periodLength,
  33. trainingDays,
  34. success,
  35. rating,
  36. ratingDescription,
  37. target,
  38. average,
  39. }
  40. }
  41. console.log(calculateExercises([3, 0, 2, 4.5, 0, 3, 1], 2))

More about tsconfig

文件tsconfig.json包含您希望 TypeScript 如何在项目中工作的所有核心配置。
在tsconfig.json 文件中指定如下配置:

  1. {
  2. "complierOptions": {
  3. "target": "ES2020",
  4. "strict": true,
  5. "noUnusedLocals": true,
  6. "noUnusedParameters": true,
  7. "noImplicitReturns": true,
  8. "noFallthroughCasesInSwitch": true,
  9. "esModuleInterop": true
  10. }
  11. }

Adding express to the mix

安装express

  1. npm install express

在scripts中添加start

  1. {
  2. // ..
  3. "scripts": {
  4. "ts-node": "ts-node",
  5. "multiply": "ts-node multiplier.ts",
  6. "calculate": "ts-node calculator.ts",
  7. "start": "ts-node index.ts"
  8. },
  9. // ..
  10. }

新建index.ts

  1. const express = require('express')
  2. const app = express()
  3. app.get('/ping', (req, res) => {
  4. res.send('pong')
  5. })
  6. const PORT = 3003
  7. app.listen(PORT, () => {
  8. console.log(`Server running on port ${PORT}`)
  9. })

image.png
vscode提示可以将require改成import
如果点击Quick Fix,会自动修改成

  1. import express = require('express')

image.png
image.png
改成import后提示安装 @types/express

  1. npm i --save-dev @types/express

如果使用const … require的方式引入express, 编辑器不知道参数的类型
image.png
但是如果使用import,编辑器知道参数的类型
image.png
所以优先使用import
如果**import...from...**不可以使用,则使用**import...=require("...")**的混合形式

image.png
vscode提示有未使用的参数
将配置中的noUnusedParameters改成false

  1. "noUnusedParameters": false,

点击Quick Fix, 给未使用的参数加上下划线

  1. app.get('/ping', (_req, res) => {
  2. res.send('pong')
  3. })

npm run start 启动应用
image.png
安装ts-node-dev,让应用自动重载

  1. npm i --save-dev ts-node-dev

在scripts中添加dev

  1. {
  2. // ...
  3. "scripts": {
  4. // ...
  5. "dev": "ts-node-dev index.ts",
  6. },
  7. // ...
  8. }

运行npm run dev, 程序就能自动重载

Exercise 9.4-9.5

GET 请求和使用[查询字符串参数][query string parameters]
当get请求为http://localhost:3002/bmi?height=180&weight=72时,
使用req.query获取传递的参数

  1. import express = require('express')
  2. import calculateBmi from './bmiCalculator'
  3. const app = express()
  4. app.get('/bmi', (req, res) => {
  5. res.send(calculateBmi(Number(req.query.height), Number(req.query.weight)))
  6. })
  7. const PORT = 3003
  8. app.listen(PORT, () => {
  9. console.log(`Server running on port ${PORT}`)
  10. })

The horrors of any

在TypeScript中,每个不能被推断的未被定义类型的变量都变成隐式的any类型。 Any 是一种“通配符wildcard”类型,字面上代表whatever type。
隐式any 类型通常被认为是有问题的
这就是为什么配置规则noImplicitAny,让其存在于编译器级别,并且强烈建议在任何时候都保持开启状态。

我们已经有了noImplicitAny 配置,那么为什么编译器不产生警告隐含any 类型呢?
原因是,express Request对象的query 字段是显式给定any类型的。 我们用于将数据发布到应用的request.body 字段也是如此。

除了tsconfig.json, 我们可以使用ESLint来管理我们的代码
安装

  1. npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser

使用命令 **node_modules/.bin/eslint --init** 初始化eslint规则
在.eslintrc中添加规则

  1. {
  2. "extends": [
  3. "eslint:recommended",
  4. "plugin:@typescript-eslint/recommended",
  5. "plugin:@typescript-eslint/recommended-requiring-type-checking"
  6. ],
  7. "plugins": ["@typescript-eslint"],
  8. "env": {
  9. "node": true,
  10. "es6": true
  11. },
  12. "rules": {
  13. "@typescript-eslint/semi": ["error"],
  14. "@typescript-eslint/explicit-function-return-type": "off",
  15. "@typescript-eslint/explicit-module-boundary-types": "off",
  16. "@typescript-eslint/restrict-template-expressions": "off",
  17. "@typescript-eslint/restrict-plus-operands": "off",
  18. "@typescript-eslint/no-unused-vars": [
  19. "error",
  20. { "argsIgnorePattern": "^_" }
  21. ],
  22. "no-case-declarations": "off"
  23. },
  24. "parser": "@typescript-eslint/parser",
  25. "parserOptions": {
  26. "project": "./tsconfig.json"
  27. }
  28. }

scripts中添加脚本

  1. "scripts": {
  2. // ...
  3. "lint": "eslint --ext .ts .",
  4. },

可以通过添加注释取消对any类型的检查

  1. app.post('/calculate', (req, res) => {
  2. // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
  3. const { value1, value2, op } = req.body
  4. const result = calculator(value1, value2, op)
  5. res.send(result)
  6. })