TypeScript学习--day1

一、介绍

TypeScript是JS的超集,为JS添加了类型支持。

1.1 为什么添加类型支持

JS代码的错误大部分是类型错误,增加改Bug时间,影响开发效率。

静态类型:编译期做类型检查 

动态类型:执行期做类型检查

TS--静态类型编程语言,代码执行时发现错误

JS--动态类型编程语言,代码编译时发现错误

 1.2 TS的优势

  1. 减少改Bug时间,提升开发效率
  2. 类型系统提升代码的可维护性,重构代码更容易
  3. 支持ECMAScript语法
  4. 类型推断机制,自动根据代码逻辑推断数据类型

二、使用

2.1 安装使用

npm i -g typescript

tsc -v --验证是否安装成功

2.2 初体验

创建hello.ts文件

tsc hello.ts --将TS编译为JS

node hello.js --执行JS代码

2.3 简化运行步骤

每次修改代码都要编译成JS在执行,太麻烦

npm i -g ts-node

ts-node hello.ts --在内部隐式转换成JS,不生成JS文件

三、常用类型

3.1 类型注解

let age: number = 18

约定类型必须和赋值类型一致

3.2 常用基础类型

3.2.1 JS已有类型

基础类型  Undefined | Number | Bigint | String |  Boolean | Symbol

引用类型 Object(Array) | Function

3.2.2 TS新增类型

联合类型、自定义类型、接口、元组、字面量、枚举、void、any

3.3 原始类型

number / string / boolean / null / undefined / symbol

这些类型按照JS中类型名称书写

let age: number = 18
let isLoading: boolean = false

3.4 数组类型

两种写法

let numbers: number[] = [1, 3, 5]
let strings: Array<string> = ['a', 'b', 'c']

如果一个数组有两个或以上类型,即联合类型

let mergeArr: (number | string)[] = [1,'a',3,'b']

//如果不添加括号 表示这个变量可以是number也可以是string数组
let x: number | string[] = ['a','b']
let y: number | string[] = 10

3.5 类型别名

当同一类型被多次使用时,类型别名可以简化书写

关键字type

type CustomArray = (number | string)[]

let arr1: CustomArray = [1, 'a', 3]
let arr2: CustomArray = ['x', 'y', 2]

3.6 函数类型

函数类型指的是参数类型和返回值类型

3.6.1 单独指定参数、返回值的类型

//函数声明
function add(n1: number, n2: number):number{
    return n1 + n2
}

//函数表达式
const add = (n1: number, n2:number):number => {
    return n1 + n2
}

3.6.2 同时指定参数、返回值的类型

const add: (n1: number, n2: number) => number =  (n1, n2) => {
    return n1 + n2
}

3.6.3 如果函数没有返回值,声明为void

function greet(name: string): void{
 console.log('Hello', name)
}

3.6.4 可选参数

参数名后面加问号,可选参数只能出现在列表最后,也就是可选参数后面不能再出现必选参数

function introduction(name: string, age?: number): void{
    console.log(`我叫${name}`)
    if(age){
        console.log(`我今年${age}岁`)
    }
}

3.7 对象类型

  1. 直接使用{}来描述对象结构,属性采用属性名:类型的形式;方法采用方法名():返回值类型的形式
  2. 如果方法有参数,就在方法名后面的小括号指定参数类型greet(name:string):void
  3. 在一行代码中指定对象的多个属性类型时,使用;分隔
let person: {
    name: string, 
    age: number,
    // sayHi():void
    sayHi: () => void
} = {
    name:'Augustine',
    age:18,
    sayHi(){
        console.log(`hi! ${this.name}`)
    }
}

可选属性

给可选属性名后加问号,与函数可选参数类似

3.8 接口

3.8.1 用法

当一个对象类型被多次使用,一般使用接口来表述对象便于复用

interface IPerson {
    name: string, 
    age: number,
    sayHi: () => void
} 

let person1: IPerson = {
    name:'Mary',
    age:19,
    sayHi() {
        console.log(`${this.name}, Hi!`)
    },
}

type和interface

interface只能为对象指定类型

type可以为任意类型指定别名

 3.8.2 继承

如果两个接口有相同的属性或方法,可以将公共属性或方法抽离出来,通过继承实现复用

interface Point2D {x: number; y: number}
interface Point3D {x: number; y: number; z: number}

//可以写为

interface Point2D {x: number; y: number}
interface Point3D extends Point2D {z: number}

3.9 元组

使用number[]的缺点是无法确切知道数组长度

元组(tuple)确切知道包含多少个元素,以及特定索引对应的类型,少一个多一个都不行

let position [number, number] = [1, 2]

3.10 类型推论

TS中没有明确指出类型的地方,类型推论机制可以帮助提供类型

两种情况:

声明变量并初始化值

决定函数返回值

类型注解能省则省,提高开发效率

3.11 类型断言

指定更加具体的类型

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法

getElementById返回值的类型是HTMLElement,只包含所有标签的公共属性和方法

比如<a>可以拿到.id属性 但不能识别.href

通过as实现类型断言 后面跟一个更具体的类型

const aLink = document.getElementById('link') as HTMLAnchorElement

3.12 字面量类型

let str1 = 'hello' //类型为string
const str2 = 'hello' //类型为hello

常量的值不能变化,这里的“hello”就是一个字面量类型,即某个特定的字符串也可以成为TS中的类型,除此之外任意的JS字面量都可以作为类型使用

使用场景:表示一组明确的可选值

比如在贪吃蛇游戏里的方向只能是up、down、left、right中的一种

function selectDirection(direction: 'up' | 'down' | 'left' | 'right'){
    console.log(direction)
}

3.13 枚举类型

描述一个值,并定义一组命名常量,这个值可以是这组常量里任何一个。

  1. enum关键字定义枚举
  2. 枚举名称大写字母开头
  3. 枚举的多个值之间通过逗号分隔
  4. 使用这个枚举用枚举名称做类型注解
enum Direction{Up, Down, Left, Right}

function selectDirection(direction: Direction){
    console.log(direction)
}
//.访问枚举成员
selectDirection(Direction.Up)

枚举成员的默认值是从0开始自增的数字(数字枚举),也可以自行给枚举成员初始化值

字符串枚举:枚举成员的值是字符串,每个成员必须有初始值

enum Direction{
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT'
}

//编译后的js代码
//生成一个立即执行函数并传入Direction(作为对象)
var Direction;
(function (Direction) {
    Direction["Up"] = "UP";
    Direction["Down"] = "DOWN";
    Direction["Left"] = "LEFT";
    Direction["Right"] = "RIGHT";
})(Direction || (Direction = {}));

一般推荐字面量+联合类型组合方式,因为枚举会被编译成函数,带来的开销更大。

3.14 any类型

不推荐使用,因为会让TS失去类型保护的优势

当一个值的类型为any,可以对该值进行任意操作,并且不会有代码提示。

隐式any情况:(不建议这样做)

  1. 声明变量不提供类型和初始值
  2. 函数参数不加类型

3.15 typeof

第一种用法与JS相同 获取数据类型

第二种用法:在类型注解中引用变量或属性的类型(简化类型书写),不能查询函数调用的类型

let p :{ x: number, y: number}
function formatPoint(point: typeof p){
    console.log(point)
}
formatPoint({x:2, y:5})

四、高级类型

4.1 class类

TS支持ES6中class关键字,用class创建的类也作为一种类型存在

4.1.1 基本使用

class Person{
    name: string
    age: number
    gender = 'male'
    //有初始值的属性可以省略类型注解

    constructor(name: string, age: number, gender?: 'male' | 'female'){
        //构造函数里不能指定返回值类型注解
        this.name = name
        this.age = age
        if(gender){
            this.gender = gender
        }
        
    }
}

const bob = new Person('bob', 18)
console.log(bob)

4.1.2 实例方法

class Circle{
    x: number
    y: number
    radius: number
    constructor(x: number, y: number, radius: number){
        this.x = x
        this.y = y
        this.radius = radius
    }
    //设置计算属性
    get area() {
        return Math.PI * this.radius ** 2;
      }
    //方法的类型注解与普通函数相同
    scale(n: number):void{
        this.radius *= n
    }
}

const c = new Circle(1, 2, 3)
console.log(c.area)//28.274333882308138
c.scale(3)
console.log(c.area)//254.46900494077323

4.1.3 类继承

extends (JS自带)

子类继承父类,子类的实例对象可以访问父类的属性和方法

class Animal{
    eat(){
        console.log('eat')
    }
}

class Dog extends Animal{
    bark(){
        console.log('woof')
    }
}

const d = new Dog()
d.eat()
d.bark()

implements (TS特有)

强制类遵循特定的接口定义。当一个类实现了一个接口时,它必须实现接口中定义的所有属性和方法。

interface Shape {
    calculateArea(): number;
}

class Circle implements Shape {
    radius: number;

    constructor(radius: number) {
        this.radius = radius;
    }

    calculateArea(): number {
        return Math.PI * this.radius ** 2;
    }
}

let circle = new Circle(5);
console.log(circle.calculateArea()); // 输出圆的面积,约为 78.54

4.1.4 类成员可见性

可见性修饰符

public - 公有的,被修饰的属性或方法可以被任何地方访问(默认)

protected - 受保护的,仅对其声明所在类和子类中可见(在子类的方法内部可以通过this访问,实例对象不可见)

private - 私有的,只在当前类中可见,实例对象以及子类不可见

只读修饰符

readonly - 表示只读,用来防止在构造函数之外对属性进行赋值,不能修饰方法

  1. 使用readonly指定属性值一定要声明属性值的类型
  2. 可以用来声明接口的属性和对象属性

4.2 类型兼容性

两种类型系统

结构化类型系统(Structural Type System):按照属性和方法是否相同来比较两个类是否相同

标明类型系统(Nominal Type System):只看两个类的名称是否相同,即便内部结构完全相同也不能认为是同一种类型

TS采用结构化类型系统,而像C++、java采用标明类型系统。

4.2.1 类与接口的类型兼容性

class Point{
    x: number
    y: number
}
class Point2D{
    x: number
    y: number
}

//产生类型兼容性 不会报错
const p: Point = new Point2D()

更准确的说法:对于对象类型,如果A的成员是B的子集,则B可以赋值给A

class Point{
    x: number
    y: number
}
class Point3D{
    x: number
    y: number
    z: number
}

//产生类型兼容性 不会报错
const p: Point = new Point3D()

接口之间、类与接口之间的兼容性与类之间的相同。

4.2.2 函数的类型兼容性

考虑:参数个数、参数类型、返回值类型

参数个数-参数少的可以赋值给参数多的

参数类型-相同位置的参数类型要相同或兼容

interface Point2D{
    x: number
    y: number
}
interface Point3D{
    x: number
    y: number
    z: number
}

type F2 = (p: Point2D) => void
type F3 = (p: Point3D) => void

let f2: F2
let f3: F3

f3 = f2
f2 = f3 //不兼容

返回值类型-如果是基本类型,两个类型要相同;如果是对象类型,成员多的可以赋值给成员少的

4.3 交叉类型

&表示,组合多个类型为一个类型(常用于对象)

interface Person{
    name: string
}
interface Contact{
    phone: string
}

type PersonDetail = Person & Contact
let obj: PersonDetail = {
    name: 'alice',
    phone:'123456'
}

交叉类型与接口继承的不同:

接口继承中同名属性的类型不同会报错,而交叉类型对于同名属性类型会进行重载

interface A{
    fn: (value: number) => string
}
interface B{
    fn: (value: string) => string
}

type C = A & B
//则C中fn为
//fn: (value: string | number) => string

 4.4 泛型

4.4.1 泛型函数与泛型接口

在保证类型安全的前提下(不使用any)让函数支持多种类型的调用 ,从而实现复用,常用于:函数、接口、class

函数名称后加<>,它可以捕获调用函数时提供的数据类型,可以将其作为函数参数和返回值的类型

function id<Type>(value: Type):Type{
    return value
}
//在<>中指定具体的类型 就会被函数声明时指定的类型变量Type捕获到
const num = id<number>(10)
const str = id<string>('a')

数组在TS中就是一个泛型接口,使用数组时TS会根据数组的不同类型,来自动将类型变量设置为相应的类型

const str = ['a', 'b', 'c']
str.forEach(item => {
    //item 类型为 string
})

const nums = [1, 2, 3]
nums.forEach(item => {
    //item 类型为number
})

const mixArr = [1, 'a', 'w']
mixArr.forEach(item => {
    //item 类型为string | number
})

4.4.2 泛型类

class GenericClass<Type>{
    defaultValue: Type
    add: (x: Type, y: Type) => Type
}

const myNumClass = new GenericClass<number>()
myNumClass.defaultValue = 10

const myStrClass = new GenericClass<string>()
myStrClass.defaultValue = 'a'

4.4.3 泛型约束

因为事先不知道到底是哪种类型,所以直接访问泛型变量的属性或函数会报错,就像这样:

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length); //类型T上不存在属性length
    return arg;
}

所以使用接口对泛型进行约束,只允许函数传入符合这个接口规则的变量:

interface Lengthwise {
    length: number;
}

//传入的变量必须包含length属性
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

4.4.4 泛型工具类型

Partial<Type>--构造一个类型,将Type的所有属性设置为可选

interface Props{
    id: string
    children: number[]
}
type ParticalProps = Partial<Props>

下图为ParticalProps的真实类型

Readonly<Type>--构造一个类型,将Type的所有属性设置只读

interface Props{
    id: string
    children: number[]
}
type ReadonlyProps = Readonly<Props>

 Pick<Type, Keys> 从type中选择一组属性来构造新类型

  1. Type表示选择谁的属性,keys表示选择哪几个属性
  2. keys只能传入Type中包含的属性
interface Props{
    id: string
    title: string
    children: number[]
}
type PickProps = Pick<Props, 'id' | 'title'>

 Record<Keys, Type>--构造一个对象类型,属性键为keys,属性类型为type

type RecordObj = Record<'a' | 'b' | 'c', string[] | number>
//同义于
//type RecordObj = {
//     a: string[]
//     b: string[]
//     c: string[]
// }
let obj: RecordObj = {
    a: ['1'],
    b: ['2'],
    c: ['3']
}

4.5 索引签名类型

无法确定对象中有哪些属性时就需要索引签名

//表示key如果为string类型的属性名都可以出现在该对象中,并且属性值为number
interface AnyObject{
    [key: string]: number
}

数组对应的泛型接口,也使用了索引签名,下面模拟了原生的数组接口

//只要是number类型的键都可以出现在数组中
interface MyArray<T>{
    [n: number]: T
}

4.6 映射类型

4.6.1 基于联合类型创建映射

基于旧类型(联合类型)创建新类型,减少重复书写

type PropKeys = 'x' | 'y' | 'z'
type Type1 = { x: number; y: number; z:number}

//使用映射
type Type2 = {[key in PropKeys]: number}
  1. 映射类型基于索引签名类型
  2. key in PropKeys表示key必须和PropKeys联合类型中相同
  3. 使用映射类型创建的新对象类型Type2和Type1的结构完全相同
  4. 映射类型只能在类型别名中使用,不能在接口中使用

 4.6.2 基于对象类型创建映射

 也可以根据对象类型创建新类型

type Props = { a: number, b: string, c: boolean}
type Type3 = {[key in keyof Props]: number}
  1. keyof 是获取该对象所有键的联合类型即 'a' | 'b' | 'c'
  2. 然后 key in keyof Props表示key必须是Props和所有键名称相同

 4.6.3 使用映射实现Partical<Type>

 泛型工具类型都是基于映射类型实现的

//Partial<Type>实现 -- 让所有类型变为可选
type Partial<T> = {
    [P in keyof T]?: T[P]
}
  1. []后面添?表示将所有属性设置为可选
  2. T[P]表示获取T中每个键对应的类型 (索引查询)

 索引查询的用法

type Props = { a: number, b: string, c: boolean}
type TypeA = Props['a']//number
type TypeB = Props['a'|'b'] //number | string
type TypeC = Props[keyof Props] //number | string | boolean

  • 14
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值