Typescript [ 快速入门 ]

270 阅读6分钟

前言

通过前章我们搭建的 webpack 开发环境,来实践一下 ts 的常用知识

补充 前章我们做到了 build 打包来编译 ts,生成打包文件,在这里我们再完善一下工程目录和 webpack 相关配置,让它能够启动项目,并运行起来,方便我们查看开发效果

工程目录

 webpack-ts                // 根目录
    --- pubilc             // index.html 放置位置
        --- index.html     // 创建一个 html5 的模板,里边随便写一个元素内容
    --- src                // 工程代码编写位置
        ---  index.ts      // 代码实践 ts 文件
    --- node_modules       // webpack 依赖
    --- package.json       // 依赖管理配置文件,启动命令,打包命令配置
    --- tsconfig.json      // ts 配置文件
    --- webpack.config.js  // webpack 配置文件

webpack 相关配置,安装几个插件

  • npm i -D html-webpack-plugin // 自动生成html
  • npm i -D webpack-dev-server // 自动响应浏览器更新
  • npm i -D clean-webpack-plugin // 清除dist目录旧文件
 // 引入一个包
const path = require('path');
// 引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
//引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

//webpack 中所有的配置信息都应该写在module.exports中
module.exports = {

   // 指定环境
    mode:"production",

    // 指定入口文件
    entry: "./src/index.ts",

    // 指定打包文件所在目录 
    output: {
      //指定打包文件的目录
      path: path.resolve(__dirname, 'dist'),
      //打包后文件的名字
      filename: "bundle.js",
      //告诉webpack不使用箭头函数输出
      environment: {
          arrowFunction: false
      }
    },
    
    //指定webpack打包时要使用的模块
    module: {
      rules: [
        {
          test: /\.ts$/, // 以ts结尾的文件 
          use: 'ts-loader',
          // 要排除的文件
          exclude: /node-modules/
        }
      ]
    },

    //配置Webpack 插件
    plugins: [
        new CleanWebpackPlugin(),
        new HTMLWebpackPlugin({
            // title: "这是一个自定义的title"、
            template: "./public/index.html" 
        }),
    ],

    // 用来设置引用模块,可以将这些文件识别为模块 
    resolve: {
        extensions: ['.ts', '.js']
    }
    
}

按照上述配置,就可以启动一个基于 webpack 的项目,接下来再配置一个启动命令 package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open" // 启动命令
  },

最后根目录下执行 npm start 就可以了,我这边启动后的样子是这样的,可以看到打包后的 bundle.js 会被自动引入,然后就可以实践 ts 大法了

image.png

类型

string, number, boolean

三种常用的基本数据类型

let count: number = 10
// count = "hello ts" // error : 不能将类型“string”分配给类型“number”

// ts 会根据值类型将 _count 默认声明为 number 类型 
// 其它类型同理(等价于let _count: number = 10)
let _count = 10

let str = "hello ts" // 同等于 let str: string = "hello ts"
// str = true // error : 不能将类型“boolean”分配给类型“string”

let bool: boolean = true // 同等于 let bool = true
// bool = 123 // error : 不能将类型“number”分配给类型“boolean”

any, unknow 任意类型

  • any 表示的是任意类型,一个变量设置类型为any后相当于对该变量关闭了TS的类型检测,使用ts时,不建议使用
  • unknown 任意类型,实际上就是一个类型安全的 anyany类型(尽量避免),不能直接赋值给其他变量,需要使用类型断言
// any 任意类型
let c: any

c = 123
c = "hello-ts"
c = false

// unknow 安全的任意类型
let d: unknown

d = 123
d = "hello-ts"
d = false

类型断言 as, <>, 如果要给 unknow 类型的变量赋值,则需要用到类型断言

let ss: string
// ss = d // 不能将类型“unknown”分配给类型“string”

// 如何让 unknown 类型的变量赋值给其它变量 
if (typeof d === "string") {
  ss = d
}

/**
 * 类型断言-白话说就是类型判断的语言 - 哈哈哈 
 * 可以用来告诉解析器变量的实际类型
 * 上述赋值的写法是不是太麻烦,可以这样写
 **/

ss = d as string
ss = <string>d

字面量

  • 字面量,相当于声明一个可控的多类型变量,当然也可以控制实际的值
let a: number | string // 可控类型
a = 123
a = "Hs"
// a = true // error : 不能将类型“boolean”分配给类型“string | number”

let b: "hello" | "ts" // 可控实际值,不可更改
b = "hello"
b = "ts"
// b = "heelo-ts" //  error : 不能将类型“"heelo-ts"”分配给类型“"hello" | "ts"”

array, object 数组和对象

* object

  • object 语法:{ 属性名:值类型,属性名?:值类型 }
  • 在属性名后面加上 ? 表示属性是可选的
  • 多个任意类型参数 : { [key: string]: any } ,key 自定义命名,表示属性名是一个字符串类型的任意值,any 则是任意类型
// 示例
let ee: object
ee = function () { }
ee = {}
ee = []

// 上述均没错,没有什么意义,约束不了对象的类型,所以在常用开发中,用下边的方式  

let e: { name: string, age?: number }
e = { name: "Hs" }
e = { name: "Hs", age: 18 }

// e = {}; // 没有 name 属性会报错,age 是可选的属性

// 多个参数,任意类型
let f: { name: string, age?: number, [key: string]: any }

f = { name: "Hs", age: 18, a: 1, b: 2, c: 3 }
f = { name: "Hs", a: 1, b: "str", c: true }

* array,tuple元组

  • 元组用来约束数组的结构,常用于约束长度和类型
// 定义一个类型为字符串的数组
let g: string[] = ["hello", "ts"]

// 定义一个类型为数字的数组
let h: Array<number> = [123, 456]

// tuple 元组
let i: [string, boolean] = ["Hs", true]

// i = ["Hs", true, 123] // 不能将类型“[string, true, number]”分配给类型“[string, boolean]”。
// 源具有 3 个元素,但目标仅允许 2 个

void, never

  • void 用来表示空值,以函数为例,就表示没有返回值(或返回 undefined)的函数
  • never 表示永远不会返回结果;没有值(比较少用,一般是用来抛出错误)

// 主要用于函数的返回值
let fn1 = function (): void { }

let fn2 = function (): never {
  throw new Error("报错了!");
}

Null 和 Undefined

  • TypeScript里 undefinednull 两者各自有自己的类型分别叫做 undefinednull, 和 void相似,它们的本身的类型用处不是很大:
let u: undefined = undefined;
let n: null = null;

enum 枚举

  • 枚举,主要作用就是可以定义一些带名字的常量, 使用枚举可以清晰地表达意图或创建一组有区别的用例
enum Sex {
  man = 0,
  woman = 1,
}

let j: { name: string, sex: Sex } = {
  name: 'Hs',
  sex: Sex.man
}

console.log(j.sex === Sex.man)

函数

根据Typescript 文档的介绍来描述就是函数是JavaScript应用程序的基础, 它帮助你实现抽象层,模拟类,信息隐藏和模块。 在TypeScript里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。TypeScript为JavaScript函数添加了额外的功能,让我们可以更容易地使用。

  • 函数主要约束传参的类型和返回值的类型,让方法更加严谨和清晰
  • 语法 function fn (参数:类型):函数返回值类型 {}
  • 或者 let fn = function (参数:类型):函数返回值类型 {}
  • 可以直接约束返回值结构和类型 function fn (参数:类型):返回值结构 {参数:类型} {}
// 形参声明类型 number
// 返回值为 number
let sum = function (a: number, b: number): number {
  return a + b
}
sum(1, 2)
// sum("Hello", 123) // error: 类型“string”的参数不能赋给类型“number”的参数  

// 箭头函数
let sums = (a: number, b: number) => { return a + b }
console.log(sums(1, 2))

// 嵌套函数
let sums = (a: number, b: number) => ((c: number) => { return a + b + c })
console.log(sums(1, 2)(3))

// 约束返回值结构
let fn = function (a: string, b: number): { a?: string, b: number } {
  return { b: 213 }
}

本身Javascript 并没有类的概念,ES6提供了 Class(类)这个概念,作为对象的模板, 通过class关键字,可以定义类。基本上 ES6 的 class 可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法, 不太了解的同学可以查看一下 ES6 文档 查看

那在 ts 里,又新增了一些属性和功能,用法上大同小异,我们用 typescript 的方式来实践实践

静态属性(类属性),只读属性,类方法

  • 实例属性,需要new访问 new Person().name
  • 在属性前+static 定义一个类属性,也称静态属性 Person.age
  • 在方法名前+static 定义一个类方法,可以直接 Person.sayHello
  • 在属性前+readonly 定义只读属性,不可以修改

通过上边的类型认识,这里的类型约束,相信各位已经可以看明白了

// 定义一个 Person 类
class Person {

  // 实例属性,需要new访问
  name: string = "Hisen"

  // 在属性前+static 定义一个类属性,也称静态属性  Person.age 
  static age: number = 18

  //在方法名前+static 定义一个类方法,可以直接 Person.sayHello  
  static sayHello() {
    console.log('类方法 say hello')
  }

  // 在属性前+readonly 定义只读属性,不可以修改 
  readonly sex: string = "男"

  constructor(name: string, age: number) {
    this.name = name
    Person.age = age
  }

  // 实例方法, void 类型表示该方法没有返回值  
  say(n: string): void {
    console.log(n)
  }

  //与子类同名方法比较优先级 - 看继承
  saybye(): void {
    console.log("我是父类 saybye")
  }

}

console.log("类属性", Person.age)
Person.sayHello()

类继承 extends

基于上边的 Person 类,我们定义一个子类 Child, 遵守扩展开放,修改关闭原则, 可以在子类定义方法,属性来扩展,但是不能修改父类属性和方法


class Child extends Person {
  saybye(): void {
    console.log(`我是子类 ${this.name}, 优先级高于父类`)
  }
}

// 父类接收两个参数 name, age
let White = new Child("小白", 18)
let Balck = new Child("小黑", 16)
console.log(White, Balck)

// {name: '小白', sex: '男'}  {name: '小黑', sex: '男'} 
White.say("继承:我是小白")
Balck.say("继承:我是小黑")

// 同名方法优先级,子组件优先
Balck.saybye()

super

基于上述的 Person 类,如果在子类中写了构造函数 constructor,那么在子类构造函数中必须对父类的构造函数进行调用,也就是 super 函数


class Child2 extends Person {

  count: number
  constructor(name: string, age: number, count: number) {
    super(name, age) // 调用继承父类的构造函数, 
    this.count = count
  }

}

console.log(new Child2("小绿", 15, 100))

抽象类 abstract

  • 专门用来被继承的类,常用于封装,不可更改类和方法
  • 语法:class 前+ abstract
  • 定义抽象方法,则是在方法名前+ abstract, 抽象方法没有方法体,表示约束子类必须对父类抽象方法重写
  • 抽象方法只能定义在抽象类中
// 抽象类 
abstract class Parent {
  name: string
  constructor(name: string) {
    this.name = name
  }

  // 定义一个抽象方法
  abstract sayHello(): void

}

// 子类继承继承抽象类示例
class Child3 extends Parent {
  //重写父类的抽象方法
  sayHello() {
    // do something 
  }
}

let c3 = new Child3("Hs")
console.log("抽象类", c3)

存取器与修饰符

  • 存取器,TypeScript 支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问
  • 修饰符, Typescript 新增了一些修饰符
  • public 公共属性,可以在任意位置访问(修改) 默认值
  • private 私有属性, 只能在类内部进行访问(修改),可以通过在类中添加方法使得私有属性返回到外部被访问
  • protected 受保护的属性,只能在当前类和当前类的子类中访问(修改)

注意 get/set 的命名,定义类属性和构造函数接收的参数名称不可以重复,ts 会报错,然后 get/set 监听的则是构造函数实例化的参数

// 存取器示例
class Parent2 {
  public _name: string   // 公共属性
  private age: number = 18  // 私有属性
  protected sex: string = "男"  // 受保护的属性

  constructor(name: string, age: number, sex: string) {
    this._name = name
    this.age = age
    this.sex = sex
  }

  get name(): string {
    console.log(`get 触发了${this._name}`)
    return this._name
  }

  set name(value: string) {
    console.log(`set 出发了 ${value}`)
    this.name = value
  }

}

接口 interface

接口用来定义一个类结构, 定义一个类中应该包含哪些属性和方法,同时接口也可以当成类型声明去约束结构

  • 接口中所有的属性都不能有实际的值
  • 可以重复声明同一个接口,会合并
  • 接口中定义的属性也可以使用修饰符,如 readonly 只读属性等
  • 语法:interface 自定义接口名 { 属性:类型 }

接口在函数中示例


// 定义一个接口
interface myTypeInter {
  name: string       // 必须参数
  age?: number       // 可选参数
  [key: string]: any // 接收剩余的任意类型参数 
}

// 接口在函数中做为类型声明去约束参数结构 
function interFn(inter_obj: myTypeInter): void {
  console.log(inter_obj.name)
}

interFn({ name: "hs" })
interFn({ name: "hs", age: 18 })
interFn({ name: "hs", a: 1, b: true, c: {} })
// interFn({}) // 类型“{}”缺少类型“myTypeInter”中的以下属性: name

接口在类中的示例

在类中,接口通过 implements 使用接口约束类结构, 接口中所有的方法都是抽象类,没有方法体,类也可以当作接口使用


/ 定义一个 myInter 接口
interface myInter {
  name: string       // 必须参数
  age?: number       // 可选参数
  [key: string]: any // 接收剩余的任意类型参数 
}

class myClass implements myInter {
  name: string

  constructor(name: string, age: number) {
    this.name = name
  }

  sayHello(): void { console.log(123) }
}

接口继承

一个接口可以继承多个接口,创建出多个接口的合成接口

interface childs extends myInter {
  sex: string
}
// 继承多个接口: interface childs extends 接口1,接口2 ... {}

泛型

泛型,泛指类型,我认为主要是更加灵活的约束函数或者类的结构,一种动态类型的体现

函数中泛型

  • 语法:function <约束类型变量> (参数:约束类型变量):约束类型变量 {}

一个简单的泛型实现 T 表示定义的类型约定,在函数调用时,方可确定实际类型

// 定义一个泛型
function fn<T>(n: T): T {
  return n
}
// 调用传入 string
fn<string>("hello")

// 可以理解为此时的函数变成这样
function fn<string>(n:string): string { 
    retunr n
}

// 其它类型调用同理

可传入多个泛型类型

function fn<T, K>(n: T, m: K): T {
  return n
}

fn<string, number>("hello", 666)

泛型继承

泛型可以继承接口,就是表示当前的泛型类型又加了一层约束,说白了就类似于 && 的感觉,同时满足泛型类型和接口结构的约束

  • 语法:function fn<泛型类型变量 extends 类型 | 类型2>(n: 泛型类型变量) {}
// 泛型类型继承 number或者string 类型,表示只能这两种类型
function fn3<T extends number | string>(n: T) {
  return n
}
fn3<string>("123")

  • 语法: function fn2<泛型类型变量 extends 接口>(n:泛型类型变量) {}
// 定义一个接口
interface interObj {
  length: number
}

// 泛型类型T继承接口 interObj
function fn2<T extends interObj>(n: T) {
  return n.length
}

fn2({ length: 123 }) // 对象有length 属性,符合接口标准
fn2("hello ts") // 字符串有length 属性,符合接口标准

类中泛型

  • 语法:class myClass<约束类型变量>{内部使用约束类型变量}

// 类中泛型
class myInterClass<T> {
  name: T
  constructor(name: T) {
    this.name = name
  }
}

Ts 常用的基本就是这些了,做一个简单的分享,如果对大家有所帮助的话,欢迎点赞,收藏,后边我决定分享一篇 Vue2 + ts 的工程实践

小小鼓励,大大成长,欢迎点赞收藏