TypeScript 学习手册

TypeScript 学习手册

JavaScript 是一门动态弱类型语言,对变量的类型非常宽容,而且不会在这些变量和他们的调用者之间建立结构化的契约。
TypeScript是拥有类型系统的JavaScript的超集,可以编译成纯JavaScript。

思维方式决定了编程习惯
编程习惯奠定了工程质量
工程质量划定了能力边界

第一章 前言

强/弱类型语言、动/静态类型语言

  • 强类型语言与弱类型语言
    在强类型语言中,当一个对象从调用函数传递到被调用函数时,其类型必须与被调用函数中声明的类型兼容。
    通俗定义:强类型语言不允许更改变量的数据类型,除非进行强制类型转换。

  • 静态类型语言和动态类型语言
    静态类型语言:在编译阶段确定所有变量的类型,如C++。
    动态类型语言:在执行阶段确定所有变量的类型,如JavaScript。

静态类型语言 动态类型语言
对类型极度严格 对类型非常管送
立即发现错误 Bug可能隐藏数月甚至数年
运行时性能号 运行时性能差
自文档化 可读性差

动态类型语言的支持者认为:

  • 性能时可以改善的(VB引擎),而语言的灵活性更重要
  • 隐藏的错误可以通过单元测试发现
  • 文档可以通过工具生成

总结

因为JavaScript是动态类型语言,只有在执行阶段才能确定所有变量的类型,这使得我们在编写程序的时候,编辑器无法有效提示错误。

如以下错误,JavaScript就无法提示错误:

// 示例 1
const message = 'Hello World'
message.toLowerCase()    // 无法得知mesaage是否存在toLowerCase() 方法
message()                // 无法得知message() 是否是错误的写法

message.tollowerCase()   // TypeScript 提示: Property 'tollowerCase' does not exist on type '"Hello World"'. Did you mean 'toLowerCase'?

// 示例 2
const user = {
    name:"wuyanzu",
    age:18
}

// TypeScript 提示: Property 'location' does not exist on type '{ name: string; age: number; }'.
// JavaScript 提示:未定义location
user.location       

配置TypeScript环境

安装vscode、node.js、npm,在vscode终端中,安装TypeScript

PS C:\Users\wuyanzu > npm i typescript -g

added 1 package in 2s

执行ts、js文件

tsc helloworld.ts  
node helloworld.js

解决ts和js冲突问题(编译后,函数名重复问题):

 tsc --init            // 生成tsconfig.json文件,并注释掉"strict": true,  

实现自动编译:

tsc --watch                    // ts文件发生变化时,js文件自动编译 

tsc --noEmitOnError --watch    // ts发生错误时,不自动编译

变量类型声明

//TypeScript 可以推断出变量的类型,因此这时可以不用显示地声明明确变量的类型
let msg = 'hello world'
console.log(typeof(msg))   // "string"  
msg = 'hello wu'
msg = 18  // Type 'number' is not assignable to type 'string'.

严格模式

// 配置文件 tsconfig.json

"strict": true,  
"noImplicitAny": true, 
"strictNullChecks": true,   

降级编译

// 配置文件 tsconfig.json

  "target": "es2016",     // 配置JavaScript适配的ea版本

第二章 数据类型

字符串、数字、布尔

let str: string = 'hello world'
let num: number = 100
let bool: boolean = true

数组

// 数组声明的两种方式
let arr1: number[] = [1, 2, 3]
arr1 = ['a']          // Type 'string' is not assignable to type 'number'.

let arr2: Array<number> = [4, 5, 6]
arr2 = ['a']          // Type 'string' is not assignable to type 'number'.

any

// any 不进行任何类型检查
let obj1 = {   // 系统推断出obj1的类型为"object" 
    x: 0
}
console.log(typeof(obj1))   //  "object" 
obj1.foo()   // Property 'foo' does not exist on type '{ x: number; }'.


let obj2: any = {
    x: 0
}
obj2.foo()

函数

function sayHi (name: string): void{   // string为参数类型注释,void为返回值类型注释
    console.log("Hello, " + name)
}
sayHi("wuyanzu")     // "Hello, wuyanzu" 

函数的参数类型声明、可传可不传参数

function allStarsVote(cn: {x: number, y?: number}) {   // ?:表示该参数可传可不传
    console.log('Uzi 的票数为: ' + cn.x)
    console.log('XiaoHu 的票数为: ' + cn.y)
    //console.log('XiaoHu 的票数为: ' + cn.y?.toPrecision(2))   // 在可不传参数调用方法时,可使用?.方式,等同于若为undefined则跳过
}

allStarsVote({x: 123, y: 456})  // "Uzi 的票数为: 123"   "XiaoHu 的票数为: 456"   

allStarsVote({x: 123})   // "Uzi 的票数为: 123"   "XiaoHu 的票数为: undefined" 

上下文类型

// 上下文类型,TypeScript会根据上下文推断出变量类型
const allStars =  ['xiaohu', 'xiaoming', 'uzi']

allStars.forEach((item) => {
    console.log(item.toLocaleUpperCase())    // TypeScript 可以根据上下文推断出item的类型
})

联合类型

// 示例 1
function welcomePeople(x: string[] | string){
    if (Array.isArray(x)){
        console.log("Hello, " + x.join(' and '))
    }else{
        console.log("welcome long traveler" + x)
    }
}

welcomePeople(['Uzi', 'Xiaohu'])
welcomePeople('Uzi')

// 示例 2
function sayHi(name: 'wu' | 'zhang' | 'li'){

}
sayHi('chen')      // Argument of type '"chen"' is not assignable to parameter of type '"wu" | "zhang" | "li"'.
sayHi('li')

// 示例 3
function compare(a: string, b: string): -1 | 0 | 1 {
    return a ===b ? 0 : a > b ? 1 : -1
}

类型别名

type IdType = number | string      // 使用type定义一个类型
function sayHi(x: IdType){
    console.log(x)
}
sayHi(6)
sayHi('Uzi')

接口

interface IdType {
    x: number
    y: number
}

function printCoord(pt: IdType){
    console.log(pt)
}

printCoord({
    x: 100,
    y: 200
})

类型别名、接口的扩展和添加字段

// 案例1 接口扩展
interface A {
    name: string
}

interface B extends A {
    honey: boolean
}

const foo1: B = {
    name: 'winie',
    honey: true
}

// 案例2 类型别名扩展
type C = {
    name: string
}

type D = C & {
    honey: boolean
}

const foo2: D = {
    name: 'winie',
    honey: true
}

// 案例3 向现有的类型添加字段(interface)

interface E {
    name: string
}

interface E {
    honey: boolean
}

const foo3: E = {
    name: 'winie',
    honey: true
}

// 案例4 向现有的类型添加字段(类型别名)   类型别名无法通过重名的类型别名添加字段,只能通过扩展方式添加
type F = {
    name: string
}

type F = C & {         // Duplicate identifier 'F'.
    honey: boolean
}

类型断言

与类型注释相同,类型断言由编辑器删除,不会影响代码的运行时行为

const myCanvas1 = document.getElementById('main_canvas') as HTMLCanvasElement

//const myCanvas2 = document.getElementById('main_canvas')  // 报错了

const x = 'hello' as number  // neither type sufficiently overlaps with the other. 

const y = ('hell0' as unknown) as number

文字类型

let s1 =  'hello world'    // s1可以表示任何可能的字符串(重新赋值)
const s2 = 'hello world'   // s2只能表示成字符串'hello world'

推断引起的异常

function handleRequest(url: string, method: 'POST' | 'GET' | 'GUESS'){

}

const req = {
    url:"www.google.com",
    method:'GET'
}

//handleRequest(req.url, req.method)   // Argument of type 'string' is not assignable to parameter of type '"POST" | "GET" | "GUESS"'.

// 原因:req.method被推断为string方法,不满足 type '"POST" | "GET" | "GUESS"

// 解决方式1:

handleRequest(req.url, req.method as 'GET')  

// 解决方式2:

const req2 = {
    url:"www.google.com",
    method:'GET' as 'GET'
}

handleRequest(req2.url, req2.method)

// 解决方式3:
const req3 = {
    url:"www.google.com",
    method:'GET' as 'GET'
} as const

handleRequest(req3.url, req3.method)

null 和 undefined 类型

null 表示不存在

undefined 表示未初始化的值

枚举

enum Color {Red, Green, Blue}
let c: Color = Color.Green;
console.log(c)    // 1
console.log(Color) 


{
  "0": "Red",
  "1": "Green",
  "2": "Blue",
  "Red": 0,
  "Green": 1,
  "Blue": 2
} 

bigint、symbol

Symbols是不可改变且唯一的

let sym2 = Symbol("key");
let sym3 = Symbol("key");

sym2 === sym3; // false, symbols是唯一的

第三章 条件判断

===
===== 

类型谓词

类型谓词是一种特殊的返回类型,它向Typescript编译器发出一个特定值是什么类型的信号。类型谓词总是附加到接受单个参数并返回布尔值的函数。类型谓词表示为 argumentName is Type

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

这里的 pet is Fish 就是类型谓词,pet 必须是函数的参数。当 isFinis() 调用并且返回 true 的时候,ts就会把该变量的类型范围缩小为某一具体类型。

type Fish = {
  name: string,
  swim: () => void
}

type Brid = {
  name:string,
  fly: () => void
}

const isFish = (pet: Fish | Brid): pet is Fish => {
  return (pet as Fish).swim !== undefined
}

const getSmallPet = (): Fish | Brid => {
  let brid: Brid = {
    name: "sbrid",
    fly(){

    }
  }

  let fish: Fish = {
    name:"sfish",
    swim(){

    }
  }

  return true ? brid : fish;
}

let pet = getSmallPet();

if (isFish(pet)){
  pet.swim();
} else {
  pet.fly();
}

const zoo: (Fish | Brid)[] = [getSmallPet(),getSmallPet()];
const underWater: Fish[] = zoo.filter(isFish);
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];

xxx

11

第三章 函数

type GreetFunction = (arg: string) => void    // 定义一个函数类型

function greeter(fn:GreetFunction, arg1: string){
    fn(arg1)
}

function printToConsole(arg: string){
    console.log(arg)                         // "hello world" 
}

greeter(printToConsole,"hello world")        // greeter(printToConsole("a"),"hello world") 的写法是错误的,第一个参数只能是GreetFunction函数类型

泛型

function firstElement<Type>(arr: Type[]): Type | undefined{
    return arr[0]
}

firstElement(['a', 'b', 'c'])
firstElement<number>([1, 2, 3])

泛型限制条件 extends

// 下文的extends并非真正的继承,而是简单的限制条件

function longest<Type extends { length: number }>(a: Type, b: Type){
    if (a.length >= b.length){
        return a
    }else{
        return b
    }
}

longest([1, 2], [2, 3, 4])
longest("wuyanzu", "liangchaowei")
longest(10, 100)         // Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

编写优秀通用函数的准则

  • 可能的情况下,使用类型参数本身,而不是对其进行约束
  • 总是尽可能少地使用类型参数
  • 如果一个类型的参数只出现在一个地方,请重新考虑你是否真的需要它

函数参数

当为回调写一个函数类型时,永远不要写一个可选参数,除非你打算在不传递该参数的情况下调用函数。

函数重载签名、实现签名

// 原始类型
let bool: boolean = true
let num: number | undefined | null = 123
let str: string = 'abc'
// str = 123  Type '123' is not assignable to type 'string'.

// 数组
let arr1: number[] = [1, 2, 3]
let arr2: Array<number | string> = [1, 2, 3, '4']

// 元组
let tuple: [number, string] = [0, '1']
// tuple.push(2)   强烈不推荐
// console.log(tuple)
// tuple[2]

// 函数
let add = (x: number, y: number) => x + y
let compute: (x: number, y: number) => number
compute = (a, b) => a + b

// 对象
let obj: { x: number, y: number } = { x: 1, y: 2 }
obj.x = 3

// symbol
let s1: symbol = Symbol()
let s2 = Symbol()
// console.log(s1 === s2)

// undefined, null
let un: undefined = undefined
let nu: null = null
num = undefined
num = null

// void
let noReturn = () => {}

// any
let x
x = 1
x = []
x = () => {}

// never
let error = () => {
    throw new Error('error')
}
let endless = () => {
    while(true) {}
}

// 数字枚举
enum Role {
    Reporter = 1,
    Developer,
    Maintainer,
    Owner,
    Guest
}
// console.log(Role.Reporter)
// console.log(Role)

// 字符串枚举
enum Message {
    Success = '恭喜你,成功了',
    Fail = '抱歉,失败了'
}

// 异构枚举  容易混乱,不推荐使用
enum Answer {
    N,
    Y = 'Yes'
}

// 枚举成员
// Role.Reporter = 0
enum Char {
    // const member
    a,
    b = Char.a,
    c = 1 + 3,
    // computed member
    d = Math.random(),
    e = '123'.length,
    f = 4
}

// 常量枚举  会被编译阶段移除
const enum Month {
    Jan,
    Feb,
    Mar,
    Apr = Month.Mar + 1,
    // May = () => 5
}
let month = [Month.Jan, Month.Feb, Month.Mar]

// 枚举类型
enum E { a, b }
enum F { a = 0, b = 1 }
enum G { a = 'apple', b = 'banana' }

let e: E = 3
let f: F = 3
// console.log(e === f)

let e1: E.a = 3
let e2: E.b = 3
let e3: E.a = 3
// console.log(e1 === e2)  无法比较,类型不同
// console.log(e1 === e3)  可以比较,类型相同

let g1: G = G.a
let g2: G.a = G.a

总结:将程序中不容易记忆的硬编码,或者是未来可能改变的常量抽取出来,定义成枚举类型,提高程序的可读性和可维护性。

你可能感兴趣的:(前端框架)