搞定TS

153 阅读13分钟

TS 变量与数据类型

TS 变量语法:

// js: (let 变量名 = 值)
let age = 18
// ts: (let 变量名:变量类型 = 值)
let age:number = 18 
// 在 ts 中,为变量指定了类型,就只能给这个变量设置相同的类型的值,否则会报错
let name: string = 'zs'
name = 200 // Error: Type '200' is not assignable to type 'string'

TS 数据类型

  1. TS 基本类型

如果变量声明和赋值是同时进行的,那么可以省略类型,因为 TS 会自动对类型进行检测

// 1.字符串类型 '' "" ``
let name: string = 'zs'

// 2. 数值类型
let age: number = 20
age = 22.22
age = -1

// 3. 布尔类型
let isApple: boolean = false
isApple = true

// 4. undefined null
let und: undefined = undefined
let nul: null = null
  1. 数组类型 需要声明数组中元素的类型
// 方式一:
let arr:string[] = ['aaa','bbb'] // 数组中的元素都是字符串
// 方式二:范型数组
let arr:Array<string> = ['aaa','bbb'] // 数组中的元素都是字符串
// 声明字符串类型的数组,不允许存入字符串意外的元素
let arr:Array<string> = ['aaa','bbb'100000] // Error:Type 'number' is not assignable to type 'string'

  1. 元组 概念:就是一个规定了元素数量和每个元素类型的数组,每个元素的类型可以不同
let tup1: [string, number, boolean] = ['aaa', 12, true] // 数组长度为3,第一个元素是字符串,第二个元素是数值,第四个元素是不二类型
// 当给元组赋值时 如果元素数量不同或者元素类型不同 都会报错
tup1 = ['bbb', 11, 'true'] // Error:Type 'string' is not assignable to type 'boolean'
// 访问元组中的元素,和元组长度
tup1[0] // 'bbb'
tup1.length // 3

为什么要有元组? 因为 TS 中数组元素类型必须相同,如果需要不同类型的元素,就可以使用元组了

特点: 声明时,要指定元素个数 声明时,要为每个元素规定类型

  1. 枚举 应用场景:性别标识 男,女,未知
// 声明语法:
enum 枚举名{
    枚举项1 = 枚举值1,
    枚举项2 = 枚举值2
}
// 枚举项一般用英文或数字,枚举值用整数类型
enum Gender{
    man : 1,
    women : 2,
    unknow : 3
}
// 使用默认枚举值:
enum 枚举名{
    枚举项1, // 0
    枚举项2, // 1
}
// 当不写枚举值时,自动生成从0开始的枚举值

// 声明枚举类型的变量
let userSex:Gender
// 变量枚举赋值
userSex = Gender.man
  1. any 类型 概念:any 代表任意类型,一般在获取 dom 时使用 我们在接受用户输入 或 第三方代码库时,还不能确定会返回什么类型的值,此时可以使用 any 类型
// 实例:
let txtName: any = document.getElementById('txtN')
  1. void 类型 概念:void 代表没有类型,一般用在无返回值的函数
// 语法:
function sayHi(): string {
  // 标识sayHi函数的返回值时string类型
  return 'hello'
}
function sayHi2(): void {
  // 表示函数没有返回值
  console.log('hello')
}
  1. never 类型 概念:never 代表不存在的值类型,常用作抛出异常或无限循环的函数返回类型(不会运行结束的函数)
// 语法:
function test(): never {
  while (true) {}
}
function test2(): never {
  throw new Error('出错了!!!')
}

补充: never 类型是 ts 中的底部类型,所有类型都是 never 类型的父类 所有的 never 类型值都可以赋给任意类型的变量

let x: never = test()
let y: string = test()

any , void , never 的区别? any 不确定是什么类型的时候使用 void 函数没有返回值的时候使用 never 函数内部抛出异常或者死循环的时候使用

TS 函数

语法

function 函数名(参数:'参数类型',参数:‘参数类型’):'返回值类型'{
    return ...
}

函数必须定义返回值类型,如果函数没有返回值,则定义返回值类型为 void 实参和形参的类型要一致 实参和形参的数量要一致

function sum(a: number, b: number): number {
  return a + b
}
let res: number = sum(1, 2)

函数可选参数(可选参数可以传也可以不传) 参数类型前加一个问号

function sum(a?: number): number {}

函数参数使用默认值参数 参数如果不传,使用默认值

function 函数名(参数1: 类型 = 默认值1, 形参2: 类型 = 默认值2): 返回值类型 {}

如果不确定传入多少个参数,可以使用剩余参数

function add(a: number, b: number, ...形参3: 类型[]): void {
  console.log(a + b)
}

剩余参数 只能 定义一个 剩余参数 只能 定义为数组 剩余参数 只能 定义定义在形参列表后面

总结:类型声明为变量设置了类型,使得变量只能存储某种类型 TS 拥有自动类型判断机制,当声明和赋值同时进行时,TS 编译器会自动判断变量的类型 所以当变量声明和赋值同时进行,可以省略变量声明

类的封装

类 - 批量创建对象

创建对象(构造函数 + new)-----------------------------------------

// 添加成员变量
function City(cName, cLevel) {
  this.cName = cName
  this.cLevel = cLevel
}
// 添加原型方法
City.prototype.about = function () {
  console.log(`城市名称:${this.cName},城市等级:${this.cLevel}`)
}

调用构造函数

let c1 = new City('北京', 1)
// 访问成员变量
console.log(c1.cName) // '北京'
// 调用方法
c1.about() // 城市名称:北京,城市等级:1

创建对象(类 class + TS)--------------------------------------------- 使用 class 关键字来定义类 对象中只要包含两个部分:属性、方法

class Person {
  /**
   * 直接定义的属性是实例属性,需要通过对象实例去访问
   * const per1 = new Person()
   * per1.name
   * 使用static开头的属性是静态属性(类属性),可以直接通过类去访问
   * Person.age
   */
  name: string = 'zs' //实例属性 (可读可写)
  readonly name: string = 'zs' //实例属性 (只读属性)
  static age: number = 18 //类属性
  /**
   * 直接定义的方法是实例方法,需要通过实例调用
   * per1.sayHello()
   * 使用static开头的方法是静态方法(类方法),可以直接通过类去调用
   * Person.sayHello()
  */
 sayHello(){ // 实例方法
   console.log('hello')
 }
 static sayHello(){ // 类方法
   console.log('hello')
 }
}

new Person 的时候会调用 constructor 函数, constructor 函数可以接收 new 传递进来的参数,给实例属性赋值

class City{
    // 成员变量
    cname:string;
    clevel:number
     // 构造函数:做初始化(成员变量必须被赋值,否则会报错)
    constructor(cName:string,cLevel:number){
        this.cname= cName // this表示当前实例对象
        this.clevel = cLevel
    }
    // 成员方法:定义在类中
    about(){
        console.log(`城市名称:${this.cName},城市等级:${this.cLevel}`)
    }
}

调用

let c1 = new City('北京', 1)
// 访问成员变量
console.log(c1.cName) // '北京'
// 调用方法
c1.about() // 城市名称:北京,城市等级:1

类的继承

// Dog类,Cat类 属性和方法相似,
class Dog {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  sayHello() {
    console.log('hello')
  }
}
class Cat {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  sayHello() {
    console.log('hello')
  }
}
// 所以可以抽象出一个父类animal类,
class Animal {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  sayHello() {
    console.log('hello')
  }
}
// Dog,Cat类继承animal类
class Dog1 extends Animal {}
class Cat1 extends Animal {}
// 使用继承后,子类将会拥有父类所有的属性和方法 (属性方法继承)
// 通过继承可以将多个类中共有的代码写在一个父类中,只需要写一次即可让所有子类同时拥有父类的属性
// 如果希望在子类中添加一些父类中没有的属性和方法,直接在子类中添加即可 (开闭原则:对修改关闭对扩展开放.子类对父类属性方法的扩展)
// 如果子类中添加了和父类中同名的方法,子类方法会覆盖父类的方法 (方法的重写)

super

// 当在子类中想要使引用父类的属性方法时,使用super关键字,super代表当前类的父类

// 子类的构造函数中必须调用父类的构造函数
;() => {
  class Animal {
    name: string
    constructor(name: string) {
      this.name = name
    }
    sayHello() {
      console.log('hello')
    }
  }
  class Dog1 extends Animal {
    age: number
    constructor(name: string, age: number) {
      super(name) // 子类调用父类构造函数
      this.age = age
    }
    sayHello(): void {
      super.sayHello() // 子类调用父类方法
    }
  }
}

抽象类

;() => {
  class Animal {
    name: string
    constructor(name: string) {
      this.name = name
    }
    sayHello() {
      console.log('hello')
    }
  }
  class Dog1 extends Animal {
    age: number
    constructor(name: string, age: number) {
      super(name) // 子类调用父类构造函数
      this.age = age
    }
    sayHello(): void {
      super.sayHello() // 子类调用父类方法
    }
  }
}
// class Animal 类可以用来创建实例对象,但是创建的实例对象没有意义,所以我们希望禁止Animal类创建实例对象
// 依次可以加 abstract 关键字,放Animal类变为抽象类,抽象类不允许创建实例对象,只能用于子类的继承

/**
 * 抽象类:
 *  抽象类与其他类的不同 只是不能用来创建对象
 *  抽象类是专门用来继承的类
 *  抽象类中可以添加抽象方法
 */
;() => {
  abstract class Animal {
    // 定义抽象类
    name: string
    constructor(name: string) {
      this.name = name
    }
    sayHello() {
      console.log('hello')
    }
  }
  class Dog1 extends Animal {
    age: number
    constructor(name: string, age: number) {
      super(name) // 子类调用父类构造函数
      this.age = age
    }
    sayHello(): void {
      super.sayHello() // 子类调用父类方法
    }
  }
  // new Animal() // Error:无法创建抽象类的实例
}

/**
 * 抽象方法:
 *   抽象方法是abstract开头的方法,没有方法体
 *   抽象方法只能定义在抽象类中,
 *   子类必须对抽象方法进行重写
 */
;() => {
  abstract class Animal {
    // 定义抽象类
    name: string
    constructor(name: string) {
      this.name = name
    }
    abstract sayHello(): void // 定义抽象方法
  }
  class Dog1 extends Animal {
    age: number
    constructor(name: string, age: number) {
      super(name) // 子类调用父类构造函数
      this.age = age
    }
    sayHello(): void {
      // 重写抽象方法
      console.log('hello')
    }
  }
}

接口

;(function () {
  // ts类型声明
  // 声明一个类型 myType
  type myType = {
    name: string
    age: number
  }
  // 定义一个myType类型的变量
  let aaa: myType = {
    name: 'zs',
    age: 12,
  }
  /**
   * 接口用来定义一个类的结构,定义了一个类中应该包含哪些属性和方法
   * 同时interface可以作为类型声明来使用(注意:type类型声明不可以重名;interface接口可以重名 重名时是将多次生命进行叠加)
   */
  interface myInterface {
    name: string
    age: number
    sayHello(): void
  }
  // 接口可以作为类型声明来规范对象结构
  let bbb: myInterface = {
    name: 'zs',
    age: 12,
    sayHello() {},
    //    gender: string // Error:“gender”不在类型“myInterface”中
    //    interface作为类型声明,定义的对象属性方法必须与interface中声明的完全一致
  }
  /**
   * 接口可以在定义类的时候限制类的结构
   * 接口中的所有属性都不能有实际值
   * 接口只定义对象的结构,而不考虑实际值
   * 在接口中所有的方法都是抽象方法(抽象类中可以定义抽象方法也可以定义普通方法,接口中只能定义抽象方法)
   * 在实现接口时,要将所有的属性都赋值,将所有的方法都实现
   */
  // 定义一个类 实现myInterface接口
  class Person implements myInterface {
    name: string
    age: number
    constructor(name: string, age: number) {
      this.name = name
      this.age = age
    }
    sayHello(): void {
      console.log('hello')
    }
    // gender:string // 定义接口之外的属性不会报错
  }

  // 注意:abstract interface只能在TS中使用,js中没有
})()

属性的封装

(function(){
    // 属性封装的目的: 让属性变得更加安全
    class Person{
        name:string;
        value:number;
        constructor(name:string,value:number){
            this.name = name;
            this.value = value
        }
    }
    let per1 = new Person('张三',100)
    per1.value = -100
    /**
     * 属性封装的解决的问题:
     * 类中定义的属性很容易被修改
     * 对于一些比较重要的数据,如果被用户修改为不合法发值,会导致计算出错
     * 因此重要属性被轻易修改是不安全的
    */

    /**
     * 属性修饰符:
     * TS中可以在属性前添加属性修饰符
     * public 修饰的属性可以在任意位子访问修改 (public是默认值,如果不写修饰符默认是public)
     * private 自由属性,只能在当前类内部进行范访问修改,子类里面不能修改访问
     *      如果项要外部修改自由属性,可以向外暴露方法,通过方法来修改私有属性
     * protected 受保护的属性,只能在当前类和子类中访问
    */
   class Person1{
       private name:string;
       private value:number;
       constructor(name:string,value:number){
           this.name = name;
           this.value = value
       }
       getValue(){
           return this.name
       }
       setValue(value:number){
           if(value>0){
            this.value = value
           }
       }
   }
    let per = new Person1('zs',12)
    per.getValue()
    per.setValue(13)
    /**
     * getter方法用来读取属性
     * setter方法用来设置属性
     *     - 它们被称为属性的存取器
    */
    class Person2 {
        private _name: string;
        private _value: number;
        constructor(name: string, value: number) {
            this._name = name;
            this._value = value
        }
        get value() {
            return this._value
        }
        set value(value: number) {
            if (value > 0) {
                this._value = value
            }
        }
    }
    let per2 = new Person2('zs', 12)
    per2.value
    per2.value = 13

    // TS中定义属性的语法糖,直接在构造函数形参中定义属性,此时构造函数内部不需要属性赋值
    class Person3{
        constructor(public name: string, public age:number){
        }
    }
    // ===
    class Person33 {
        name: string;
        age: number;
        constructor(name: string, age: number) {
            this.name = name
            this.age = age
        }
    }

})()

泛型

/**
 * 在定义函数或者类时,如果遇到类型部不确定就可以使用泛型
 * 只有在函数被调用时才能确定是什么类型
 */
function fn<T>(a: T): T {
  return a
}
// 不指定泛型,TS可以对类型进行推断
fn(10) // 参数为10,是number类型,因此泛型的T就判断为number类型
// 指定泛型
fn<string>('hello')

// T extends Inter 表示泛型T必须是Inter是实现类(子类)
interface Inter {
  length: number
}
function fn3<T extends Inter>(a: T): number {
  return a.length
}

class Myclass<T> {
  name: T
  constructor(name: T) {
    this.name = name
  }
}
const mc = new Myclass<string>('zs')

TS + Vue3 实战应用

// string类型
let type:string = '';
const type:Ref<string> = ref('')
const type = ref<string>('')

// boolean类型
const type:Ref<boolean> = ref(true)
let type1:boolean = false;

// number类型
const type:Ref<number> = ref(1)
let type1:number = 2;

// Array类型
const type:Ref<Array<number>> = ref([1,2,3])
let type1:Array<number> = [1,2,3];
const type:Ref<number[]> = ref([1,2,3])
let type1:number[] = [1,2,3];

// function类型
function fun(name:string,age:number):string{
    return name+age;
}
fun('zs',18)

// 对象 类型
function fun(point:{x:number,y:number}){
    return '' + point.x + point.y;
}
console.log('fun',fun({x:1,y:2})); 

// 对象 可选属性
function fun(point:{x:number,y?:number}){ // y为可选属性,可传可不传(不传的时候y为undefined)
    if(point.y!==undefined){
        return point.x+point.y
    }else{
        return point.x
    }
}
console.log('fun',fun({x:1,y:2}));

// 联合类型
function fun(id:string|number){
    console.log('your id is:'+id)
}
console.log(fun(12));
console.log(fun('12'));

// 类型别名 type (对于常用的复杂类型,定义类型别名)
type Options = {
    code:string,
    name:string
};
const typeOptions = ref<Options[]>([]);
typeOptions.value = [
    {
        code:'1',
        name:'a'
    },
    {
        code:'2',
        name:'b'
    },
]
type Id = string | number;
const id:Ref<Id> = ref('1')
const id1 = ref<Id>('1')

// 接口 interface(用于命名对象类型)
interface Person {
    name:string,
    age:number,
    gender?:string // 可选属性
}
const person1:Ref<Person> = ref({
    name:'张三',
    age:18
})
console.log('person1',person1);
const person2 = ref<Person>({
    name:'李四',
    age:20
})
console.log('person2',person2);

// 扩展类型别名
type Options = {
    code:string,
    name:string
};
type Options1 = Options & {
    class:string
}

// 扩展接口
interface Person {
    name:string,
    age:number,
    gender?:string // 可选属性
}
interface Animal extends Person {
    eat:string
}

// 类型别名 创建后无法修改
// 接口 创建后可以修改
interface Options {
  code:string,
}
interface Options { // 向接口中添加新字段
  name:string,
}

// 类型断言
type Options = {
    name:string
}
const options = ref({} as Options)
const options = ref(<Options>{});
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

// 字面类型(变量值只能是被给出的字面量)
let alignment:'left' | 'center' | 'right' = 'left'

// null undefined
// strictNullChecks 严格模式关闭 null和undefined可以分配给任何类型的变量
// strictNullChecks 严格模式开启 null和undefined只能赋值给any类型

// 非空断言运算符 后缀!
let x:number | null | undefined = 123;
x!.toFixed(2) // 告诉编译器 x 不可能为 null 或 undefined

// 枚举类型
enum gender{
  man='男',
  woman='女'
}
gender.man // '男'
gender.woman // '女'
const genderMap = {
  [gender.man]:'男',
  [gender.woman]:'女'
}