TypeScript 学习笔记

最近在学 ts 顺便记录一下自己的学习进度,以及一些知识点的记录,可能不会太详细,主要是用来巩固和复习的,会持续更新

前言

想法

首先我自己想说一下自己在学ts之前,对ts的一个想法和印象,在我学习之前,包括前期的学习中,我还是认为,给变量定义类型我真的觉得有点繁琐,例如 ts中的元组

let tupleArray: [number, string] = [123, 'hello']

将一个数组定义成一个只包含一个数字和一个字符串的数组,顺序不能乱,个数不能多也不能少,而且类型也要一一对应,对于习惯js灵活性的我来说,有点不能接受,甚至是不能理解,包括ts中的接口,给变量定义了这么多条条框框,我觉得缺少灵活性,在目前的我看来有点繁琐

这也是我想学下去的原因,我不明白为什么网上这么多人拥抱了ts,用完都说好(),大家一致的好评却给我一种人云亦云的感觉,所以我决定自己一探究竟,我看看是不是真的好

优势

说一下我在了解ts时,听说到它的优势,目前只是据说,在我自己没觉得它是之前,我都保持怀疑,我会持续验证
TypeScript 学习笔记_第1张图片
在接受一个新项目的时候有些字段和函数看不懂什么意思可能牵扯的很多要搞清来龙去脉要console.log,可能还要问之前的人这个字段什么意思,这个函数是什么作用,要运行起来才能知道,现在ts就可以直接解决这个问题

正式开始

1.搭建typescript开发环境

npm install -g typescript 
nvm install v14.15.1 //使用nvm 下载node 和切换 node版本
(切换用use)

注意node的版本 不然会报错

tsc -v

查看ts版本

编译ts文件

Tsc main.ts

输入这个命令就会生成一个 main.js 文件 里面就是转译后的 js语言

2.数组和元组

//数组
let arr: number[] = [1, 2, 3, ] //数字类型数组

//元组
let tupleArray: [number, string] = [123, 'jth']

给一个数组定义类型,例如number数组,那么这个数组里的值就只能是number

元组的官方定义:元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为string和number类型的元组。

与此同时元组不可以跟类型描述不一致,包括个数,顺序,以及类型

3.函数

let add = (x: number, y: number, z?: number): number => {
    if (typeof z === 'number') {
        return x + y + z
    } else {
        return x + y
    }
}

let result = add(1, 2)

let add2: (x: number, y: number, z?: number) => number = add
//定义函数的参数数量和类型,如果需要可选参数和interface一样
//可选参数后面不能再定义确定参数

//如果想将add这个函数赋值给一个变量,必须赋值给一个一样的函数类型
// 冒号(:) 后面都是对此变量类型的定义

//例如: add2: (x: number, y: number, z?: number)
//这步结束后对函数的参数做了类型的定义,但函数返回值的类型还没定义
//如果需要对函数返回值进行定义 就不可以写成 : 这种形式,而是ts中的另一个方法 =>
// => 是申明函数类型返回值的方法 不是es6 中的箭头函数

//如何用interface 描述函数类型
//上面我们直接写了函数类型 (x: number, y: number, z?: number) => number
//interface 描述函数类型和直接写有所区别

interface ISum {
    (x: number, y: number, z?: number): number
}
//interface 描述函数类型时 返回值的类型申明不是用 => 而是 : 

let add3: ISum = add

4.类

//类 公有 私有 受保护
class Test {
    private age: number = 11;
    name: string;
    constructor(name) {
        this.name = name
    }
}
let ads = new Test('hello')
// console.log(ads.age) //此时的age 为 private属性 不可在类外访问 如果为 protected也一样不能在类外访问
// console.log(ads)
//ts中默认类的所有属性都是 public(公有)
class Animal {
    private name: string;
    constructor(name) {
        this.name = name
    }
    show() {
        console.log(`my name is ${this.name}`)
    }
}

class Snake extends Animal {
    dep: string
    constructor(name, dep) {
        super(name)
        this.dep = dep
    }
    show() {
        // console.log(`my name is ${this.name} and work in ${this.dep}`) 属性“name”为私有属性,只能在类“Animal”中访问
        super.show()
        //如果在类型中添加私有(private)属性,则不可以在 类 外使用,包括派生类(子类)
        //但可以在申明私有属性的类型内访问
    }
}

let snake = new Snake('jth', 'ali')
snake.show()

class Animal2 {
    protected name: string;
    constructor(name) {
        this.name = name
    }
    protected show() {
        console.log(`my name is ${this.name}`)
    }
}

class Snake2 extends Animal2 {
    dep: string
    constructor(name, dep) {
        super(name)
        this.dep = dep
    }
    show() {
        console.log(`my name is ${this.name} and work in ${this.dep}`)
        super.show()
        //如果在类型中添加受保护(private)属性,则不可以在类型外使用
        //但可以在申明私有属性的类型内访问,也可以在派生类中访问
    }
}

let snake2 = new Snake2('jth', 'ali')
snake.show()

//只读属性
class User {
    readonly name: string;
    readonly num: number = 8
    constructor(name) {
        this.name = name
    }
}

let dada = new User('cfh')
// dada.name = 'cfh2'// 如果属性是readonly,只可以在申明时或构造函数中被初始化
//不可以进行二次赋值
console.log(dada)


//参数属性 声明并初始化一个成员 constructor(public name: string) {}
//传统写法 constructor(theName) {this.name = theName}
class Jth1 {
    public name: string;
    constructor(name) {
        this.name = name
    }
    show() {
        console.log(`my name is ${this.name}`)
    }
    //在普通的类型中 我们需要申明一个name属性,并给name属性赋值
}
let nameClass = new Jth1('jjtthh')
nameClass.show()

class Jth {
    constructor(public name: string) { }
    show() {
        console.log(`my name is ${this.name}`)
    }
    //但参数属性可以帮我们将声明属性和初始化合并到一起
    //写法 为 public name: string
    //同样的 private 和 protected 一样
}
let jjj = new Jth('cjh')
jjj.show()

5.接口 interface

//接口 
interface Person {
    readonly id: number;
    name: string;
    age?: number; // 此时age属性可有可无
}

let obj: Person = {
    name: 'jth',
    age: 12,
    id: 1
}

//接口用来限制对象的形状,接口定义的形状是什么样对象就得什么样,
//变量不可以多也不可以少
//如果希望某个变量可有可无可以在接口的属性后面 + '?'
//如果希望某个属性只可读不能被赋值 可使用readonly
//obj.id = 2 这个时候就会报错 id属性只可读

//接口也可以描述 类类型
//TypeScript也能够用它来明确的强制一个类去符合某种契约。
//描述的类必须实现和接口中一致的属性或者方法 所以文档中的定义是强制一个类去符合某种契约
//关键字 implements

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

interface Battery {
    checkBatteryStatus(): void;
}

//接口的继承
//接口也可以继承 关键字和类继承一样 extends
interface BatteryWithRadio extends Radio {
    checkBatteryStatus(): void;
}

class CellPhone implements Radio, Battery {
    switchRadio(trigger: boolean) {

    }
    checkBatteryStatus() { }
}

class CellPhone2 implements BatteryWithRadio {
    switchRadio(trigger: boolean) {

    }
    checkBatteryStatus() { }
    //implements 之后就可以接继承的 接口
}

class Car implements Radio {
    switchRadio(trigger: boolean) {

    }
}

6.枚举 enums

//枚举
enum Direction {
    Up,
    Down,
    Left,
    Right
}
//枚举成员会被赋值从0开始 递增的数字

console.log(Direction.Up) // 0 可以看做对象获取属性值
console.log(Direction[0]) // Up 枚举的反向映射 也可以看做数组
//反向映射实现原理
// var Direction;
// (function (Direction) {
//     Direction[Direction["Up"] = 0] = "Up";
//     Direction[Direction["Down"] = 1] = "Down";
//     Direction[Direction["Left"] = 2] = "Left";
//     Direction[Direction["Right"] = 3] = "Right";
// })(Direction || (Direction = {}));

enum Direction1 {
    Up,
    Down = 10,
    Left,
    Right
}
// 枚举也可以是手动赋值 如果从第一项开始赋值 下面的会依次加一
// 如果第一项它没有初始化方法,那么它的初始值为0
console.log(Direction1.Up) // 0
console.log(Direction1[1]) // undifined

enum Direction2 {
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT'
}
//字符串枚举
const value = 'UP'
if(value === Direction2.Up) {
    console.log('true')
}

//常量枚举 提高性能
const enum Direction3 { 
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT'
}
const value1 = 'UP'
if(value === Direction3.Up) {
    console.log('true')
}
//编译后的js不会编译枚举的代码
//var value1 = 'UP';
//if (value === "UP" /* Direction3.Up */) {
    //console.log('true');
//}

7.泛型 generics

function echo1(arg) {
    return arg
}
const result1 = echo1(123)

//泛型出现解决了什么问题
//例如有一个函数echo1 是将传入的参数返回
//当我们调用echo1,传入数字 123 时,得到的结果的类型是any
//可能有人会想到如果在定义函数时给参数和函数返回结果定义类型可以解决该问题
function echo2(arg: number): number {
    return arg
}
const result2 = echo2(123)
//但是这样局限性就比较强,只能传入number类型的数据,如果是string类型就会警告

//或者有人说可以给参数和函数返回值定义any类型
function echo3(arg: any): any {
    return arg
}
const result3 = echo3(123)
//这样最后的结果类型还是any
//甚至还会出现bug
const result4: string = echo3(123)
//这里给结果添加string类型,但我们传的是number,依然成立,因为string属于any的一种

//我们需要一种方法使返回值的类型与传入参数的类型是相同的
//这时候就出现了泛型
function echo<T>(arg: T): T {
    return arg
}
// const str1: string = 'string'
const result5 = echo(true)
//根据文档的定义我说下自己对泛型的理解
//首先这里的 T 其实是一个类型变量,它不明确指出当前的参数或返回值的具体类型
//而是在调用函数时,编译器可以查看传入参数的类型
//然后把 T 设置为传入参数的类型
//这样就可以统一传入参数和返回值的类型
//写法上需要 给 echo 添加类型变量 T,并用 <> 包裹,
//这样 T 就会帮助我们捕获传入参数的类型,然后将参数和函数返回值都添加类型 T

//约束泛型
interface hasLength {
    length: number
}

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

const str = echoWithLength('str')
const obj = echoWithLength({ length: 10, width: '12' })
const arr = echoWithLength(['1', '2', '3'])

//约束泛型
//可以给类型变量继承一个interface 约束泛型
//interface约束泛型必须要有length
//只要传入的参数有length属性,不管是string 还是 对象 还是 数组
//只要有length属性都可以
//典型的 duck typing, 只要这只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子

//类和泛型
class genericsClass {
    private data = []
    push(item) {
        return this.data.push(item)
    }
    pop() {
        return this.data.shift()
    }
}
const cla1 = new genericsClass()
cla1.push(1.5)
cla1.push('str')
console.log(cla1.pop().toFixed())
console.log(cla1.pop().toFixed()) //在这里会出现一个报错
//这里的代码转译成js后,直接generics.js 文件时 会报错 toFixed is not a function
//但是ts并没有在编译前捕捉到这个错误
//是因为 toFixed 是 number 的方法,并不适用于 string
//我们预想的是只有number类型的数据才能被添加
//所有要用泛型约束一下这个类,让输入和输出的类型保持一致

//改写

class genericsClass2<T> {
    private data = []
    push(item: T) {
        return this.data.push(item)
    }
    pop(): T {
        return this.data.pop()
    }
}
const cla2 = new genericsClass2<number>()
cla2.push(1)
console.log(cla2.pop().toFixed()) //在这里会出现一个报错
// cla2.push('cjs') //这里 ts就会提示 不能添加string类型的数组


//接口和泛型
interface keyPair<T, U> {
    key: T
    value: U
}

let kp1: keyPair<number, string> = {key: 1, value: 'string'}
let kp2: keyPair<string, number> = {key: 'string', value: 1}
//泛型还可以约束接口保证属性的类型是自己约定好的

//泛型的出现解决的问题
//1.创建一个拥有特定类型的容器 用于类和接口,在使用时创建我们想要的数据类型的容器
//例如 let arr: Array = [1, 2, 3] 创建一个充满number类型数据的数组

//2.约束参数的类型
//例如在函数中 我们希望传入的参数 不一定是特定类型 比如 一定是数组或string
//我们只希望传入的参数拥有 length 属性
// 这时就可以用泛型帮我们定义参数的类型
//例如
// interface length {
//     length: number
// }
// function add(item: T): T {
//     console.log(item.length)
//     return item
// }
// const sss = add('srrrt')
// const sss1 = add({length: 10})
//传入的参数不是一个具体的类型但都具有length属性 可以是 string | 数组 | obj

//3.泛型还可以帮我们补货传入参数的类型,以确保传入参数和返回值类型的一致

8.type-alias 类型别名

//类型别名 
//type 关键字

let sum: (x: number, y: number) => number 
const result1 = sum(1, 2)
type Plus = (x: number, y: number) => number 
let sum2: Plus
const result2 = sum2(2, 3)
//可以利用type 关键字 建立一个类型别名

//字面量
const str: 'str' = 'str' // 定义类型是字面量,值就只能和字面量完全相等
const number: 1 = 1
type Direction = 'Up' | 'Down' | 'Left' | 'Right'
let toWhere: Direction = 'Down'

//交叉类型
interface jieko {
    name: string
}

type Iperson = jieko & {age: number}
let jth: Iperson = {name: 'cjh', age: 133}
//和interface的继承相似

你可能感兴趣的:(typescript,学习,javascript)