TypeScript语言

课程概述

  • 提高代码的可靠程度
  • 解决JS自有类型存在问题

类型系统

强类型与弱类型(类型安全)

  • 实参类型和形参类型必须相同
  • 强类型有更强的类型约束,弱类型中几乎没有什么约束
  • 强类型语言中不允许有任意的隐式类型转换,弱类型是允许的
  • 变量类型允许随时改变的特点,不是强弱类型的差异

静态类型与动态类型(类型检查)

  • 静态类型:一个变量声明时它的类型就是明确的,声明过后,它的类型就不允许修改了。
  • 动态类型:变量是没有类型的,变量中存放的值是有类型的

JavaScript类型系统特征

  • 缺乏类型系统的可靠性(弱类型、动态类型)
  • 早期设计简单、便捷
  • JS没有编译环节
  • 弱类型/动态类型
  • 大规模应用下,“优势”变短板

弱类型的问题

  • 异常需要等到运行时才能发现
  • 函数功能可能发生改变
  • 对象索引器的错误用法

强类型的优势

  • 强类型代码错误更早暴露
  • 强类型代码更智能,编码更准确
  • 重构更牢靠
  • 减少了代码层面的不必要的类型判断

Flow JavaScript类型检查器

类型注解

  1. function sum (a: number , b:number){
  2. return a + b
  3. }
  • 使用步骤
    • 安装 flow-bin
    • 代码头部 //@flow
    • Flow init
    • yarn flow

移除类型注解

  • yarn flow-remove-types . -d dist
  • yarn add @babel/core @babel/cli @babel/prebel-flow --dev

类型推断

类型注解

  • 值类型
  • 函数返回值

原始类型

  1. /**
  2. * 原始类型
  3. * @flow
  4. */
  5. const a: string = 'foobar'
  6. const b: number = Infinity // NaN // 100
  7. const c: boolean = false // true
  8. const d: null = null
  9. const e: void = undefined
  10. const f: symbol = Symbol()

数组类型

  1. /**
  2. * 数组类型
  3. * @flow
  4. */
  5. const arr1: Array<number> = [1, 2, 3]
  6. const arr2: number[] = [1, 2, 3]
  7. // 元组
  8. const foo: [string, number] = ['foo', 100]

对象类型

  1. /**
  2. * 对象类型
  3. *
  4. * @flow
  5. */
  6. const obj1: { foo: string, bar: number } = { foo: 'string', bar: 100 }
  7. const obj2: { foo?: string, bar: number } = { bar: 100 }
  8. const obj3: { [string]: string } = {}
  9. obj3.key1 = 'value1'
  10. obj3.key2 = 'value2'

函数类型

  1. /**
  2. * 函数类型
  3. * @flow
  4. */
  5. function foo (callback: (string, number) => void) {
  6. callback('string', 100)
  7. }
  8. foo(function (str, n) {
  9. // str => string
  10. // n => number
  11. })

特殊类型

  1. /**
  2. * 特殊类型
  3. *
  4. * @flow
  5. */
  6. // 字面量类型
  7. const a: 'foo' = 'foo'
  8. const type: 'success' | 'warning' | 'danger' = 'success'
  9. // ------------------------
  10. // 声明类型
  11. type StringOrNumber = string | number
  12. const b: StringOrNumber = 'string' // 100
  13. // ------------------------
  14. // Maybe 类型
  15. const gender: ?number = undefined
  16. // 相当于
  17. // const gender: number | null | void = undefined

Mixed 与 Any

  1. /**
  2. * Mixed 强类型
  3. * Any 弱类型
  4. * @flow
  5. */
  6. // string | number | boolean | ....
  7. function passMixed (value: mixed) {
  8. if (typeof value === 'string') {
  9. value.substr(1)
  10. }
  11. if (typeof value === 'number') {
  12. value * value
  13. }
  14. }
  15. passMixed('string')
  16. passMixed(100)
  17. // ---------------------------------
  18. function passAny (value: any) {
  19. value.substr(1)
  20. value * value
  21. }
  22. passAny('string')
  23. passAny(100)

运行环境API

  1. /**
  2. * 运行环境 API
  3. * @flow
  4. */
  5. const element: HTMLElement | null = document.getElementById('app')

类型小结

Typescript

原始类型

  1. // 原始数据类型
  2. const a: string = 'foobar'
  3. const b: number = 100 // NaN Infinity
  4. const c: boolean = true // false
  5. // 在非严格模式(strictNullChecks)下,
  6. // string, number, boolean 都可以为空
  7. // const d: string = null
  8. // const d: number = null
  9. // const d: boolean = null
  10. const e: void = undefined
  11. const f: null = null
  12. const g: undefined = undefined
  13. // Symbol 是 ES2015 标准中定义的成员,
  14. // 使用它的前提是必须确保有对应的 ES2015 标准库引用
  15. // 也就是 tsconfig.json 中的 lib 选项必须包含 ES2015
  16. const h: symbol = Symbol()
  17. // Promise
  18. // const error: string = 100

标准库声明

中文错误消息

  1. yarn tsc --locale zh-CN

作用域问题

  1. // 作用域问题
  2. // 默认文件中的成员会作为全局成员
  3. // 多个文件中有相同成员就会出现冲突
  4. // const a = 123
  5. // 解决办法1: IIFE 提供独立作用域
  6. // (function () {
  7. // const a = 123
  8. // })()
  9. // 解决办法2: 在当前文件使用 export,也就是把当前文件变成一个模块
  10. // 模块有单独的作用域
  11. const a = 123
  12. export {}

Object类型

  1. // Object 类型
  2. export {} // 确保跟其它示例没有成员冲突
  3. // object 类型是指除了原始类型以外的其它类型
  4. const foo: object = function () {} // [] // {}
  5. // 如果需要明确限制对象类型,则应该使用这种类型对象字面量的语法,或者是「接口」
  6. const obj: { foo: number, bar: string } = { foo: 123, bar: 'string' }
  7. // 接口的概念后续介绍

数组类型

  1. // 数组类型
  2. export {} // 确保跟其它示例没有成员冲突
  3. // 数组类型的两种表示方式
  4. const arr1: Array<number> = [1, 2, 3]
  5. const arr2: number[] = [1, 2, 3]
  6. // 案例 -----------------------
  7. // 如果是 JS,需要判断是不是每个成员都是数字
  8. // 使用 TS,类型有保障,不用添加类型判断
  9. function sum (...args: number[]) {
  10. return args.reduce((prev, current) => prev + current, 0)
  11. }
  12. sum(1, 2, 3) // => 6

元组类型

  1. // 元组(Tuple)
  2. export {} // 确保跟其它示例没有成员冲突
  3. const tuple: [number, string] = [18, 'zce']
  4. // const age = tuple[0]
  5. // const name = tuple[1]
  6. const [age, name] = tuple
  7. // ---------------------
  8. const entries: [string, number][] = Object.entries({
  9. foo: 123,
  10. bar: 456
  11. })
  12. const [key, value] = entries[0]
  13. // key => foo, value => 123

枚举类型

  1. // 枚举(Enum)
  2. export {} // 确保跟其它示例没有成员冲突
  3. // 用对象模拟枚举
  4. // const PostStatus = {
  5. // Draft: 0,
  6. // Unpublished: 1,
  7. // Published: 2
  8. // }
  9. // 标准的数字枚举
  10. // enum PostStatus {
  11. // Draft = 0,
  12. // Unpublished = 1,
  13. // Published = 2
  14. // }
  15. // 数字枚举,枚举值自动基于前一个值自增
  16. // enum PostStatus {
  17. // Draft = 6,
  18. // Unpublished, // => 7
  19. // Published // => 8
  20. // }
  21. // 字符串枚举
  22. // enum PostStatus {
  23. // Draft = 'aaa',
  24. // Unpublished = 'bbb',
  25. // Published = 'ccc'
  26. // }
  27. // 常量枚举,不会侵入编译结果
  28. const enum PostStatus {
  29. Draft,
  30. Unpublished,
  31. Published
  32. }
  33. const post = {
  34. title: 'Hello TypeScript',
  35. content: 'TypeScript is a typed superset of JavaScript.',
  36. status: PostStatus.Draft // 3 // 1 // 0
  37. }
  38. // PostStatus[0] // => Draft

函数类型

  1. // 函数类型
  2. export {} // 确保跟其它示例没有成员冲突
  3. function func1 (a: number, b: number = 10, ...rest: number[]): string {
  4. return 'func1'
  5. }
  6. func1(100, 200)
  7. func1(100)
  8. func1(100, 200, 300)
  9. // -----------------------------------------
  10. const func2: (a: number, b: number) => string = function (a: number, b: number): string {
  11. return 'func2'
  12. }

任意类型

  1. // 任意类型(弱类型)
  2. export {} // 确保跟其它示例没有成员冲突
  3. function stringify (value: any) {
  4. return JSON.stringify(value)
  5. }
  6. stringify('string')
  7. stringify(100)
  8. stringify(true)
  9. let foo: any = 'string'
  10. foo = 100
  11. foo.bar()
  12. // any 类型是不安全的

隐式类型推断

  1. // 隐式类型推断
  2. export {} // 确保跟其它示例没有成员冲突
  3. let age = 18 // number
  4. // age = 'string'
  5. let foo
  6. foo = 100
  7. foo = 'string'
  8. // 建议为每个变量添加明确的类型标注

类型断言

  1. // 类型断言
  2. export {} // 确保跟其它示例没有成员冲突
  3. // 假定这个 nums 来自一个明确的接口
  4. const nums = [110, 120, 119, 112]
  5. const res = nums.find(i => i > 0)
  6. // const square = res * res
  7. const num1 = res as number
  8. const num2 = <number>res // JSX 下不能使用

接口

  1. // 接口
  2. //约束对象的结构
  3. export {} // 确保跟其它示例没有成员冲突
  4. interface Post {
  5. title: string
  6. content: string
  7. }
  8. function printPost (post: Post) {
  9. console.log(post.title)
  10. console.log(post.content)
  11. }
  12. printPost({
  13. title: 'Hello TypeScript',
  14. content: 'A javascript superset'
  15. })
  1. // 可选成员、只读成员、动态成员
  2. export {} // 确保跟其它示例没有成员冲突
  3. // -------------------------------------------
  4. interface Post {
  5. title: string
  6. content: string
  7. subtitle?: string
  8. readonly summary: string
  9. }
  10. const hello: Post = {
  11. title: 'Hello TypeScript',
  12. content: 'A javascript superset',
  13. summary: 'A javascript'
  14. }
  15. // hello.summary = 'other'
  16. // ----------------------------------
  17. interface Cache {
  18. [prop: string]: string
  19. }
  20. const cache: Cache = {}
  21. cache.foo = 'value1'
  22. cache.bar = 'value2'

类 Class

用来描述一类具体对象的抽象成员

  1. // 类(Class)
  2. export {} // 确保跟其它示例没有成员冲突
  3. class Person {
  4. name: string // = 'init name'
  5. age: number
  6. constructor (name: string, age: number) {
  7. this.name = name
  8. this.age = age
  9. }
  10. sayHi (msg: string): void {
  11. console.log(`I am ${this.name}, ${msg}`)
  12. }
  13. }

类的访问修饰符

  1. // 类的访问修饰符
  2. export {} // 确保跟其它示例没有成员冲突
  3. class Person {
  4. public name: string // = 'init name'
  5. private age: number
  6. protected gender: boolean
  7. constructor (name: string, age: number) {
  8. this.name = name
  9. this.age = age
  10. this.gender = true
  11. }
  12. sayHi (msg: string): void {
  13. console.log(`I am ${this.name}, ${msg}`)
  14. console.log(this.age)
  15. }
  16. }
  17. class Student extends Person {
  18. private constructor (name: string, age: number) {
  19. super(name, age)
  20. console.log(this.gender)
  21. }
  22. static create (name: string, age: number) {
  23. return new Student(name, age)
  24. }
  25. }
  26. const tom = new Person('tom', 18)
  27. console.log(tom.name)
  28. // console.log(tom.age)
  29. // console.log(tom.gender)
  30. const jack = Student.create('jack', 18)

类的只读属性

  1. // 类的只读属性
  2. export {} // 确保跟其它示例没有成员冲突
  3. class Person {
  4. public name: string // = 'init name'
  5. private age: number
  6. // 只读成员
  7. protected readonly gender: boolean
  8. constructor (name: string, age: number) {
  9. this.name = name
  10. this.age = age
  11. this.gender = true
  12. }
  13. sayHi (msg: string): void {
  14. console.log(`I am ${this.name}, ${msg}`)
  15. console.log(this.age)
  16. }
  17. }
  18. const tom = new Person('tom', 18)
  19. console.log(tom.name)
  20. // tom.gender = false

类与接口

  1. // 类与接口
  2. export {} // 确保跟其它示例没有成员冲突
  3. interface Eat {
  4. eat (food: string): void
  5. }
  6. interface Run {
  7. run (distance: number): void
  8. }
  9. class Person implements Eat, Run {
  10. eat (food: string): void {
  11. console.log(`优雅的进餐: ${food}`)
  12. }
  13. run (distance: number) {
  14. console.log(`直立行走: ${distance}`)
  15. }
  16. }
  17. class Animal implements Eat, Run {
  18. eat (food: string): void {
  19. console.log(`呼噜呼噜的吃: ${food}`)
  20. }
  21. run (distance: number) {
  22. console.log(`爬行: ${distance}`)
  23. }
  24. }

抽象类

  1. // 抽象类
  2. // 区别于接口,内部可以有具体的实现
  3. export {} // 确保跟其它示例没有成员冲突
  4. abstract class Animal {
  5. eat (food: string): void {
  6. console.log(`呼噜呼噜的吃: ${food}`)
  7. }
  8. abstract run (distance: number): void
  9. }
  10. class Dog extends Animal {
  11. run(distance: number): void {
  12. console.log('四脚爬行', distance)
  13. }
  14. }
  15. const d = new Dog()
  16. d.eat('嗯西马')
  17. d.run(100)

泛型 Generics

声明时不指定类型,调用时传递类型

  1. // 泛型
  2. export {} // 确保跟其它示例没有成员冲突
  3. function createNumberArray (length: number, value: number): number[] {
  4. const arr = Array<number>(length).fill(value)
  5. return arr
  6. }
  7. function createStringArray (length: number, value: string): string[] {
  8. const arr = Array<string>(length).fill(value)
  9. return arr
  10. }
  11. function createArray<T> (length: number, value: T): T[] {
  12. const arr = Array<T>(length).fill(value)
  13. return arr
  14. }
  15. // const res = createNumberArray(3, 100)
  16. // res => [100, 100, 100]
  17. const res = createArray<string>(3, 'foo')

类型声明

  1. // 类型声明
  2. import { camelCase } from 'lodash'
  3. import qs from 'query-string'
  4. qs.parse('?key=value&key2=value2')
  5. // declare function camelCase (input: string): string
  6. const res = camelCase('hello typed')
  7. export {} // 确保跟其它示例没有成员冲突