前言
通过前章我们搭建的 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 大法了
类型
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里
undefined
和null
两者各自有自己的类型分别叫做undefined
和null
, 和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 的工程实践
小小鼓励,大大成长,欢迎点赞收藏