简介:前几篇介绍了Vue3的Composition API,Vue-Router,pinia。Vue3能更好支持TS,因此,本节将介绍TS,详细描述了TS的优缺点,安装,如何配置自动编译、tsconfig.json的常用配置,使用webpack打包时的webpack.config.js的配置,还会详细说明TS基本数据类型、对象、类、抽象类、构造方法、接口、继承、封装、泛型。
TypeScript微软开发、跨平台的编程语言。它是JavaScript的超集,最终会被编译为JavaScript代码。
2012年10月,微软发布了首个公开版本的TypeScript,2013年6月19日,在经历了一个预览版之后微软正式发布了正式版, TypeScript的作者是安德斯海尔斯伯格,C#的首席架构师。
TypeScript扩展了JavaScript的语法,所以任何现有的JavaScript程序可以运行在TypeScript环境中。
优点:
TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可
即使没有显式的定义类型,也能够自动做出类型推论
可以定义从简单到复杂的几乎一切类型
即使 TypeScript 编译报错,也可以生成 JavaScript 文件
兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取
类型系统增加了代码的可读性和可维护性
拥有活跃的社区,并且支持ES6规范
不足:
需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等概念
短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,TypeScript 能够减少其维护成本
集成到构建流程需要一些工作量
可能和一些库结合的不是很完美
TS | JS |
---|---|
解决大型项目的代码复杂性 | 一种脚本语言,用于创建动态网页 |
可以在编译期间发现并纠正错误 | 作为一种解释型语言,只能在运行时发现错误 |
强类型,支持静态和动态类型 | 弱类型,没有静态类型选项 |
最终被编译成 JavaScript 代码,使浏览器可以理解 | 可以直接在浏览器中使用 |
支持模块、泛型和接口 | 不支持模块,泛型或接口 |
社区的支持仍在增长,而且还不是很大 | 大量的社区支持以及大量文档和解决问题的支持 |
npm install -g typescript 或者npm i -g typescript
pnpm i typescript
tsc-V //检查安装是否成功,Version 4.5.5
添加tsconfig.json文件,执行命令tsc -w可编译并监视所有文件,tsconfig.json是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译。
生成ts.config.js文件还可以用命令
tsc --init
tsconfig.json文件,是ts编译器的配置文件,根据其信息,对代码进行编译
最外层的配置
include // 指定哪些ts文件需要被编译,注:/**:任意目录 ; /*:任意文件
exclude // 不需要被编译的文件,
// 默认值:["node_modules","bower_components","jspm_packages"]
extends // 定义被继承的配置文件
files // 指定被编译文件的列表,只有需要编译的文件少时才会用到 ,用的少
compilerOptions // 编译器的选项
"target":指定编译的es版本
"module":指定使用的模块化的规范,一版用ES2015
"lib":一般使用默认值; strict (一版情况下,不用动): 开启所有严格的类型检查,会同时配置:alwaysStrict、noImplicitAny、noImplicitThis、strictNullChecks
"outDir":打包后文件的位置
"outFile": “./dist/app.js” -->所有全局作用域中的代码都会合并到一个文件中,注意:要合并的话,module必须是amd和system
"allowJs":是否对js文件进行编译,默认是false
"checkJs":是否检察js代码
"removeComments":false, //编译后是否删除注释
"noEmit": false, // 编译后不会生成任何js文件,当只想使用语法检测功能时用设置为true
"noEmitOnError": true, // 发生错误时不输出任何文件
// 所有严格检察的总开关
"strict":true
// 语法检察
"alwaysStrict": // 设置编译后的文件是否使用严格模式,默认false
"noImplicitAny":true //不允许隐式any
"noImplicThis":true // 不允许不明确类型的this
"strictNullChecks":false // 严格的检察空值
例子
{
"include": [ "./src/**/*"],
"exclude": [ "./src/hello/**/*"],
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "es2017", // 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'esnext:最新的版本'
"module": "amd", // 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node12', 'nodenext'
"outFile": "./dist/app.js", // 所有全局作用域中的代码都会合并到一个文件中,注意:要合并的话,module必须是amd和system
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJs": true, // 是否允许编译JS文件,默认是false
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定编译后的文件存放的目录,源码与编译后区分开了
"rootDir": "./src", //要编译的
"declarationMap": true, // 为声明文件生成sourceMap
"removeComments":false, //编译后是否删除注释
"noEmit": false, // 编译后不会生成任何js文件,当只想使用语法检测功能时用设置为true
"noEmitOnError": true, // 发生错误时不输出任何文件
"strict": true, // 开启所有严格的类型检查,如:alwaysStrict、noImplicitAny、noImplicitThis、strictNullChecks
...
}
}
npm init -Y //在空文件夹下生成package.json
npm install -D webpack webpack-cli typescript ts-loader //安装依赖
npm install -D html-webpack-plugin //webpack的插件,作用是自动生成index.html文件
npm install -D webpack-dev-server //webpack的启动服务
npm install -D clean-webpack-plugin //webpack插件,作用清除dist内容,保证每次Build都是最新文件
npm install -D @babel/core @babel/preset-env babel-loader core-js //安装bable,兼容不同浏览器
const path = require('path') //引入一个包
const HTMLWebpackPlugin = require('html-webpack-plugin') //引入HTML插件
const {CleanWebpackPlugin} = require('clean-webpack-plugin') //引入clear-webpack-plugin 插件
module.exports = { //webpack的所有配置信息都写在module.exports
mode:'development',// development为开发者环境,production为生产环境变量 ,还有一个为none
entry:"./src/index.ts" , //指定入口文件
output: { //指定打包文件所在目录
path:path.resolve(__dirname,'dist'), //指定打包文件的路径
filename: "bundle.js" //打包后文件的名称
},
module: { //指定webpack打包时使用的模块
rules: [{
test:/\.ts/, //test指定的是规则生成的文件
use:[ //要使用的loader
{
loader: "babel-loader", //配置bable,指定加载器
options: {
presets:[ //设置预定义的环境
"@babel/preset-env", //指定环境的插件
{ //配置信息
targets:{ //要兼容的目标浏览器
"chrome":"88"
},
"corejs":"3", //指定corejs的版本
"useBuiltIns":"usage",//使用corejs的方式"usage" 表示按需加载
}
]
}
},
'ts-loader'
],
exclude: /node_modules/, //要排除被编译的文件
}]
},
plugins: [ //配置webpack插件
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({ //自动生成html文件
template: "./src/index.html" //提供的模板
}),
],
resolve: { //用来设置引用模块
extensions: ['.ts','.js'] //可以引用ts、js文件
}
}
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack server"
},
npm run build //在dist下得到打包文件bundle.js及index.html
javascript有七种原始数据类型,boolean、number、string、null、undefined 、Symbol (ES6)和 BigInt (ES10)
typescript的基本类型有:boolean、number、string、null、undefined 、字面量(限定变量的值就是该字面量的值)、any、unknown(类型安全的any)、void(空值)、never(没有值)、object、array、tuple(元组)、enum(枚举)
类型可以约束变量
let a:boolean = true
let b:string = 'wl'
let c:number = 12
const onlyNull: null = null;
const onlyUndefined: undefined = undefined;
let a:25 //a永远是25
//使用联合类型
let b :"male"| "female"
let c:string|number|boolean //使用广泛
任意类型,一个变量设置为any,相当于对该变量关闭了TS的类型检测;使用ts时不建议使用any;
注:声明变量不指定类型,TS解析器自动判断变量是any类型(隐式any)
let d:any //显式any
let a //隐式any,自动判定a类型为any
表示未加类型的值
注:any与unknown区别:unknown是一个类型安全的any,unknown类型的变量不能直接赋值给其它变量;使用unknown类型给其它赋值,需要先判断类型
let e:unknown
let s:string
if(typeof e === "string"){ //使用unknown类型给其它赋值,需要先判断类型
s = e
}
//类型断言,告诉解析器实际类型
s = e as string //变量 as 类型
s = <string>e //<类型>变量
表示为空,以函数为例,表示没有返回值
function fn():void {
return
}
表示没有值,不会有返回结果,使用率较少
function fn2():never {
throw new Error('出错了')
}
表示一个js对象
注:{}用来指定对象中可以包含哪些属性, 语法:{属性名:属性值,属性名:属性值};在属性名后面加上?表示此属性是可选的
定义函数,设置函数结构的类型声明,语法:(形参:类型,形参:类型,…)=>返回值类型
let a:object //不常用
a = {}/[]/function () {}
let b:{name:string,age:number,height?:number} //?表示此属性是可选的
b = {name:'wl',age:123}
let c:{name:string,[propName:string]:any} //定义对象,[propName:string]:any 表示任意类型的属性
c={name:'dsk',a:12,b:'fd'}
let d:(a:number,b:number)=>number //定义函数,设置函数结构的类型声明 语法:(形参:类型,形参:类型,...)=>返回值类型
d = function (a:12,b:23):number {
return a+b
}
//如果函数的参数是对象,对对象类型参数的注解
function getNumber({one}:{one:number}){
return one
}
const one = getNumber({one:1})
两种写法:类型[ ] (如:string[ ],number[ ]) ;Array<类型>(如:Array,Array)
let e:string[]= ['1','2']
let f:number[]=[1,2]
let g: Array<string> = ['a','b']
元组,固定长度的数组,语法:[类型,类型,类型,…]
let h :[string,string,number] = ['1','2',3]
enum Gender{
Male,
Female = 1
}
let i:{gender:Gender}
i = {
gender:Gender.Female
}
高级类型,自定义保护,就不需要每个都进行类型断言
react 使用jss
//使用&表示同时
let hh :{name:string} & {age:number}
hh={name:'wl',age:2}
//类型别名
type myType = 1|2|3|4
let k :myType
要想面向对象,操作对象,首先便要拥有对象,那么下一个问题就是如何创建对象,要创建对象,必须要先定义类,所讲的类可以理解为对象的模型,程序中可以根据创建指定类型的对象,举例来说:可以通过Person类来制建人的对象,不同的类可以用来创建不同的对象。
使用class定义,对象中包含属性和方法
注:
1)、直接定义的属性是实例属性/方法,通过对象的实例去访问;
2)、readonly 开头的属性表示一个只读属性,不能被修改;
3)、属性/方法以static开头,定义的是静态属性/方法,直接通过类访问,无需创建对象;
4)、在实例方法中,this就表示当前实例;
5)、以abstract开头的是抽象类,抽象类可以被继承,但不能创建对象(实例化);
6)、抽象类可以有抽象方法,使用abstract开头定义抽象方法,抽象方法只能定义在抽象类中并且没有方法体,子类必须对方法进行重写
class Person{
static readonly name:string; //定义的实例属性,readonly表示只读不能被修改,static定义静态属性
age:number
constructor(name:string,age:number){ //构造函数
this.name=name;this.age = age
}
static sayHello(){} //定义方法,类的静态方法
}
const per = new Person('ww',1) //通过实例对象访问属性,如per.name
Person.sayHello //前有static,直接就能访问到类属性
(function(){
abstract class Animal{ //定义抽象类
name:string;
constructor(name:string){this.name = name}
abstract sayHello():void; //使用abstract开头定义抽象方法,抽象方法只能定义在抽象类中并且没有方法体,子类必须对方法进行重写
}
class dog extends Animal{
sayHello(){console.log('重写父类方法')}
}
})()
注:
extends继承父类的方法
定义一个类,在子类中添加与父类相同的方法,将覆盖父类的方法;子类覆盖父类叫方法的重写
super继承父类的属性与方法
(function(){
class Animal{
name:string;
constructoe(name:string){this.name = name}
sayHello(console.log('叫'))
}
class dog extends Animal{
gender:string
constructor(name:string,gender:string){
// 访问派生类的构造函数中的 "this" 前,必须调用 "super",初始化父类构造函数 --并把参数传给父类
super(name) //调用父类构造方法
this.gender = gender
}
sayHello{ //继承方法
super.sayHello()
}
run(){} //新增加方法
}
})()
作用:面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范。ts中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
注:
1)、接口用来定义类的结构,定义一个类中包含哪些属性方法,接口也可以当做类型声明,接口可以重复申请
2)、接口在定义类的时候限制类的结构,接口只定义对象的结构,不考虑实际值,在接口中所有的方法都是抽象方法
3)、来?标志传入的变量,这样可以传入定义的接口以外的值,否则如果直接传入对象中无接口定义的值会报错,所以建议接口定义了哪些值就传哪些值。
(function(){
//类类型接口
interface myInter{
name:string;
age?: number; //加个问号,接口属性就可以变成可传可不传了,不传默认是undefined。
sayhello():void;
}
class myclass implements myInter{ //用类实现接口,接口就是对类的限制
name:string;
constructor(name:string){this.name = name}
sayhello(){}
}
})()
ts在类的属性前添加修饰符:
1)、public:可在任意位置访问,是默认值;
2)、private:私有属性,私有属性只能在类内部访问(修改),在类内添加方法,私有属性可被访问;
3)、peotected:受保护的属性,只能在当前类和当前类的子类中被访问(修改)
注:
由于属性是在对象中设置的,属性可以任意的被修改,将会导致对象中的数据变得非常不安全。因此需要对属性进行封装。
js封装的属性存取器使用时需要调用相应的getter和setter方法;而ts封装的属性存取器使用时可直接当作变量来用就行。
加getter和setter方法只是为了对属性的值做判断,如果不需做判断则没必要使用。
(function(){
class A { //定义类时可以直接将属性定义在构造函数中(简化代码)
constructor(public name:string,private age:number){
}
get age(){ return this.name;}
set age(value:string){
if(value>=0){ this.name = value;}
}
}
const a = new A('ww',11)
a.name //可直接访问
a.age = -13 //不符合判断条件,不被执行
})()
作用:泛型就是解决类、接口、方法的复用性,以及对不特定数据类型的支持。
function fn1<T,K>(a:T,b:K){
console.log(b)
return a
}
//泛型实现接口继承
Interface inter{
length:number
}
function fun2<T extends inter>(a:T){ //T extends inter表示泛型T必须是Interface的实现类(子类)
return a.length
}
这里的T可改成其他任意值,但定义的值,和传入的参数以及返回的参数是一样的,一般默认写法是T,也是业内规范的选择。
class myclass<T>{
name:T;
constructor(name:T){this.name = name}
}
const mc = new myclass<string>('qq')
nterface ConfigFn<T> {
//参数类型 ,返回值类型
(value: T): T;
}
//接口方法
function getData<T>(value: T): T {
return value;
}
//使用接口
let myGetDate: ConfigFn<string> = getData;
console.log(myGetDate("3"));
好处:
①提供类似模块化开发的方式
②尽可能少的声明全局变量参数
③把相关的内容封装到一起去,对外提供统一的暴露接口
命名空间这个语法,很类似编程中常说的模块化思想,比如webpack打包时,每个模块有自己的环境,不会污染其他模块,不会有全局变量产生。命名空间就跟这个很类似,注意这里是类似,而不是相同。
命名空间声明的关键词是namespace, 比如声明一个namespace Home,需要暴露出去的类,可以使用export关键词,这样只有暴漏出去的类是全局的,其他的不会再生成全局污染了。
index.ts文件
namespace Home{
export class page{
constructor(){
new Components.Header()
new Components.Context()
new Components.foot()
}
}
}
命名空间实现组件化
新建components.ts文件
namespace Components{
export namespace SubComponents{ //子命名空间
export class test {}
}
export class Header{
constructor(){
const elem = document.createElement("div")
elem.innerText = "this is wl"
document.body.appendChild(elem)
}
}
export class Context{
constructor(){
const elem = document.createElement("div")
elem.innerText = "content content"
document.body.appendChild(elem)
}
}
export class foot{
constructor(){
const elem = document.createElement('div')
elem.innerText = "foot foot"
document.body.appendChild(elem)
}
}
}
注:
1)、每个类都用export导出,使用tsc编译后,生成index.js文件,将此文件引入到index.html中
<script src="./build/index.js"></script>
2)、多个文件编译成一个文件,需要在ts.config.js中进行配置,将所有文件都输入到build/index.js中,注意的问题是,开启outFile后,所有全局作用域中的代码都会合并到一个文件中,module必须是amd或system
{
"module":"amd",
"outFile": "./build/page.js"
}
3)、子命名空间
在命名空间里,再写一个命名空间
namespace Components {
export namespace SubComponents {
export class Test {}
}
//someting ...
}
在浏览器中会查看到Components.SubComponents.Test
使用es6的export导出所有的类
export class Header{
constructor(){
const elem = document.createElement("div")
elem.innerText = "this is wl"
document.body.appendChild(elem)
}
}
export class Context{... }
export class foot{...}
使用Import导入
import {Header,Context,foot} from "./importComponents"
export default class page{
constructor(){
new Header()
new Context()
new foot()
}
}
编译后,发现js文件有define开头,这是amd 规范的代码,不能直接在浏览器中运行,可以在 Node 中直接运行;这种代码在浏览器中是没办法被直接运行的,需要其他库(require.js)的支持。
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>
在index.html文件中
<script >
require(["page"],function (page) {
new page.default();
})
</script>
ts断言:有非空断言、类型断言(手动指定一个类型)
1) 忽略null和undefined类型
2) 调用函数时忽略undefined类型
3) 确定赋值断言 :
核心功能:如果遇到 null 或 undefined 就可以立即停止某些表达式的运行
1)可选元素访问
2)可选链函数的调用
操作数
运作规则:当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。
Js的逻辑或 || 运算符:
运作规则:逻辑或会在左操作数为 falsy 值时返回右侧操作数。
短路
运作规则:当空值合并运算符的左表达式不为 null 或 undefined 时,不会对右表达式进行求值。