一篇入门TypeScript

date
May 9, 2022
slug
12
status
Published
tags
Javascript
Typescript
summary
简单了解TypeScript
type
Post
Book

Basic-types

Javascript中的类型集合

ECMAScript定义Javascript语言中类型集合由原始值(primitive values)和对象(Object)组成
  • primitive values:
    • boolean
    • null
    • BigInt
    • undefined
    • string
    • number
    • symbol
  • Object

typescript中声明一个类型

//声明一个布尔类型的变量
let isDone: boolean = false
isDone = 123 //会报错

let message: string = `Hello, ${firstName}` //不会报错

undefined和null是所有类型的子集

let num: number = undefined //不会报错

当不确定变量类型时可以用any声明

let notSure: any = 4
notSure = 'maybe a string'
notSure = true

notSure.myName
notSure.getName()

Array and Tuple

声明数组类型

let arrOfNumbers: number[] = [1,2,3]
arrOfNumbers.push('string') //报错

JS内置类

function test() {
	//JS内置类
	console.log(arguments)
	let htmlCollections: HTMLAllCollection = undefined
}

声明元组

let user: [string, number] = ['viking', 20] //顺序不能反
//元组也是一个数组,可以使用数组的方法
user.push(20)
//但只能添加原来定义的类型
user.push(true) //报错

Interface

声明接口(Interface)

interface IPerson {
	name: string;
	age: number;
}

//少了属性会报错
let viking: IPerson = {
	name: 'viking'
}

//多了属性也会报错
let hiking: IPerson = {
	name: 'hiking',
	age: 10,
	id: 123
}

Interface可选属性

interface IPerson {
	name: string;
	age?: number;
}

//不会报错
let viking: IPerson = {
	name: 'viking'
}

Interface只读属性

interface IPerson {
	readonly id: number;
	name: string;
	age: number;
}
let hiking: IPerson = {
	name: 'hiking',
	age: 10,
	id: 123
}
hiking.id = 567 //报错

Function

Function声明

function add(x: number, y: number): number {
  return x + y
}
add(1, '123') // 因为y必须是number类型,所以报错
let isString: string = add(1,2) //因为add()的返回值必须是number类型,所以报错

Function可选参数

function add(x: number, y: number, z?:number): number {
	return x + y + z
}
add(1,2) //因为z可选,所以不会报错


//可选参数后面不能再加必选参数,所以报错
function add1(x: number, y: number, z?: number, h: number): number {
  return x + y + z
}

声明函数表达式

const add = (x: number, y: number, z?:number): number => {
	return x + y + z
}
//add的类型是(x: number, y: number, z?:number) => number

let add1: string = add //报错,因为add的类型不是string类型
let add2: (x: number, y: number, z?:number)=>number = add //不会报错

Interface描述函数类型

const add = (x: number, y: number, z?:number): number => {
	return x + y + z
}

interface ISum {
  (x: number, y: number, z?:number): number
}

let add2: ISum = add //不会报错

类型推论、联合类型和类型断言

什么是类型推论?

let str = 'str' //str被推测为string类型
str = 123 //报错,因为str被推测为string类型

联合类型

let numberOrString: number | string
//不会报错
numberOrString = 123
numberOrString = 'abc'
当一个联合类型的变量没有确定是哪个类型的时候,只能访问此联合类型所有类型的共有属性或方法
let numberOrString: number | string
numberOrString.length //会报错,因为number类型上没有length属性
numberOrString.toString() //不会报错,因为toString()方法是共有方法

类型断言

类型断言只能够欺骗TypeScript编译器,无法避免运行时的错误。
function getLength(input: string | number): number {
  const str = input as string //欺骗编译器input是string类型
  if (str.length){
    return str.length
  }else {
    console.log(str.length) //undefined
    return str.toString().length
  }
}

类型保护

类型保护就是TypeScript通过JavaScript中的instanceoftypeof运算符的用法,推导出在条件块中的变量类型,从而允许你使用更小范围下的对象类型。
function getLength2(input: string | number): number {
  if (typeof input === 'string'){
		//此时input已经被推断为stirng类型
    return input.length
  } else {
		//此时input已经被推断为number类型
    return input.toString().length
  }
}
关于classinstanceof的例子:
class Foo {
  foo = 123;
  common = '123';
}

class Bar {
  bar = 123;
  common = '123';
}

function doStuff(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.foo); // ok
    console.log(arg.bar); // Error
  }
  if (arg instanceof Bar) {
    console.log(arg.foo); // Error
    console.log(arg.bar); // ok
  }
}

Class

默认是Public修饰,所以不作解释

Private

private用来修饰成私有属性,私有属性只能在类的内部访问
class Animal {
  name: string;
  constructor(name) {
    this.name = name
  }
  private run() {
    return `${this.name} is running`
  }
}
const snake = new Animal('lily')
console.log(snake.run()) //报错

Protected

protected可以被继承,在派生类访问。但父类和子类都不能通过实例访问
class Animal {
  name: string;
  constructor(name) {
    this.name = name
  }
  protected run() {
    return `${this.name} is running`
  }
}
const snake = new Animal('lily')
console.log(snake.run()) //报错,实例不能调用protected修饰的方法

class Cat extends Animal{
	//不会报错,子类可以调用父类protected修饰的方法。
  run() {
    return 'Meow, '+ super.run()
  }
}

Readonly

readonly修饰只读属性
class Animal {
  readonly name: string;
  constructor(name) {
    this.name = name
  }
  protected run() {
    return `${this.name} is running`
  }
}
const snake = new Animal('lily')
snake.name = '123' //报错,因为name是readonly

Class and Implement

类和接口

有时候不同的类有共同的特性,需要声明规范这个共有特性时又不方便找一个父类去继承。用接口就可以实现这样的功能。
interface Radio{
  switchRadio(trigger: boolean): void
}

class Car implements Radio{
  //报错,因为没有实现switchRadio方法
}

class Phone {
  switchRadio(trigger: boolean) {

  }
}

多个接口

interface Radio{
  switchRadio(trigger: boolean): void;
}
interface Battery {
  checkBatteryStatus(): void;
}

class Phone implements Radio,Battery {
  switchRadio(trigger: boolean) {}
  checkBatteryStatus(){}
}

接口也可以继承

interface Radio{
  switchRadio(trigger: boolean): void;
}
interface RadioWithBattery extends Radio{
  checkBatteryStatus(): void;
}
class Car implements RadioWithBattery {
	//报错,因为没有实现switchRadio方法
	checkBatteryStatus(){}
}
class Phone implements RadioWithBattery {
  switchRadio(trigger: boolean) {}
  checkBatteryStatus(){}
}

枚举

数字枚举

enum direction {
	//数字枚举会自动从0开始赋值
  Up,
  Down,
  Right,
  Left
}
console.log(direction.Up) //0
console.log(direction.Down) //1

//也可以通过下标来访问
console.log(direction[0]) //Up

自定义赋值

从第一项递增
enum direction {
  Up = 10,
  Down, //11
  Right, //12 
  Left //13
}
如果自定义赋值,则下标访问也要用对应的值
enum direction {
  Up = 10,
  Down, //11
  Right, //12 
  Left //13
}
console.log(direction[1]) //undefined
console.log(direction[11]) //Down
值可以相同,但

字符串枚举

enum direction {
  Up = 'Up',
  Down = 'Down',
  Right = 'Right',
  Left = 'Left'
}
console.log(direction.Up) //Up
字符串和数值枚举混用,字符串后面必须初始化值。
enum direction {
  Up = 'Up',
  Down = 'Down',
  Right = 'Right',
  Left //报错
}
enum direction {
	Left, //不报错
  Up = 'Up',
  Down = 'Down',
  Right = 'Right',
	LeftDown = 
}
console.log(direction.Left) //0

常量枚举

const enum direction {
  Up = 'Up',
  Down = 'Down',
  Right = 'Right',
  Left = 'Left'
}
常量枚举可以提高性能
/* const_enum.ts */
const enum direction {
  Up = 'Up',
  Down = 'Down',
  Right = 'Right',
  Left = 'Left'
}
const value = 'Up';
if(value === direction.Up){
  console.log('go up!!')
}
/* const_enum.js */
//编译后的js文件
var value = 'Up';
if (value === "Up" /* Up */) {
    console.log('go up!!');
}
并不是所有枚举都能用作常量枚举。计算值枚举就不能用作常量枚举。

泛型(generics)

在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

简单例子

function echo(arg) {
  return arg
}

const result = echo(true) //此时result的类型是any,没有准确定义返回值的类型。
//因为我输入的是boolean类型,我希望能够准确定义返回值也是boolean类型。
const result1 = echo('123') //这里我希望result1的类型也能准确定义为string类型,但事实上不是。
/* 使用泛型来实现 */
function echo<T>(arg: T): T {
	return arg
}
const result = echo(true) //此时result的类型是true,也就是boolean类型。和输入值的类型是一致的。

多个类型

function swap<T,U>(tuple: [T, U]): [U,T] {
  return [tuple[1], tuple[0]]
}

const result = swap(['string', 123]) //此时result的返回值为[number,string]

泛型约束

当我们想要限制函数去处理带有length属性的所有类型时,我们就需要用到泛型约束。简单例子:
function echoWithLength<T>(arg: T): T {
  console.log(arg.length) //会报错,因为T类型没有length属性
  return arg
}
const arrNumber = echoWithLength([1, 2, 3])
function echoWithLength<T>(arg: T[]): T[] {
  console.log(arg.length) //此时不会报错
  return arg
}
const arrNumber = echoWithLength([1, 2, 3])
const str = echoWithLength('str') //会报错,因为str不能赋值到[]
通过泛型约束来解决:
interface IWithLength {
  length: number
}

function echoWithLength<T extends IWithLength>(arg: T): T {
  console.log(arg.length)
  return arg
}

const arrNumber = echoWithLength([1, 2, 3])
const obj = echoWithLength({ length: 10, id: 1000 }) //不会报错,因为有length属性
const str = echoWithLength('str')
const num = echoWithLength(123) //报错,因为number类型没有length属性

泛型类

泛型类允许定义类时约束类的类型定义:
/* 没有约束类型,编译前不会报错,编译时会报错 */
class Queue {
  private data = []
  push(item) {
    return this.data.push(item)
  }
  pop() {
    return this.data.shift()
  }
}
const queue = new Queue()
queue.push(1)
queue.push('str')
console.log(queue.pop().toFixed())
console.log(queue.pop().toFixed()) //因为string类型没有toFixed()函数
/* 通过泛型约束 */
class Queue<T> {
  private data: = []
  push(item: T) {
    return this.data.push(item)
  }
  pop(): T {
    return this.data.shift()
  }
}
const queue = new Queue<number>()
queue.push(1)
queue.push('str') //报错,因为push函数约束了参数为number类型。

泛型接口

interface KeyPair<T,U> {
  key: T,
  value: U
}
const kp1: KeyPair<number, string> = { key:1, value: 'string'}
const kp2: KeyPair<string, number> = { key:'1', value: 1 }
泛型接口也可以这样用:
/* 两者效果一样 */
let arr: number[] = [1,2,3]
let arr1: Array<number> = [1,2,3]

类型别名

顾名思义就是给一个类型起个新的名字
type stringOrnumber = string | number
//都不会报错
let num: stringOrnumber = 1
let str: stringOrnumber = 'str'

字面量

字面量类型用来约束取值只能是某几个中的一个。
type direction = 'Up' | 'Down' | 'Right' | 'Left'
let toWhere: direction = 'Up'
实用例子
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick'

交叉属性

可以对接口进行拓展
interface IName {
  name: string
}
type IPerson = IName & { age: number }
let person: IPerson = { name: 'lewis', age: 123}

声明文件

当使用第三方库时,我们需要引进它的声明文件,才能获得对应的代码补全、接口提示功能。

什么是声明语句

假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script> 标签引入 jQuery,然后就可以使用全局变量 $ 或 jQuery 了。
我们通常这样获取一个 id 是 foo 的元素:
$('#foo');
// or
jQuery('#foo');
但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西:
jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.
这时,我们需要使用 declare var 来定义它的类型:
declare var jQuery: (selector: string) => any;

jQuery('#foo');
上例中,declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
jQuery('#foo');

什么是声明文件

通常我们会把声明语句放到一个单独的文件(jQuery.d.ts)中,这就是声明文件:
/* jQuery.d.ts */
declare var jQuery: (selector: string) => any;
/* index.ts */
jQuery('#foo');
声明文件必需以 .d.ts为后缀。

© LewisWong 2021 - 2025