官网
typescriptlang.org
ts环境配置
TypeScript的编译环境
–安装命令
npm install typescript -g
–查看版本
tsc --version
TypeScript的运行环境
- 第一步:通过tsc编译TypeScript到JavaScript代码;
tsc 文件名
- 第二步:在浏览器或者Node环境下运行JavaScript代码;
化简步骤:
-
方式一:通过
webpack
,配置本地的TypeScript编译环境和开启一个本地服务,可以直接运行在浏览器上;
项目目录结构│ index.html ├─build │ webpack.config.js └─src main.ts
-
使用npm来初始化package.json文件
npm init -y
-
本地依赖TypeScript:为什么需要本地依赖TypeScript
- 因为我们之后是通过webpack进行编译我们的TypeScript代码的,并不是通过tsc来完成的。(tsc使用的是全局安装的TypeScript依赖)
- 那么webpack会在本地去查找TypeScript的依赖,所以我们是需要本地依赖TypeScript的;
npm install typescript
-
初始化
tsconfig.json
文件
tsc --init
-
配置tslint来约束代码
npm install tslint -g
检测代码规范,在项目中初始化tslint的配置文件:tslint.json
tslint --init
-
项目环境的Webpack
- 安装webpack相关的依赖(本地安装)
npm install webpack webpack-cli webpack-dev-server -D
- 在package.json中添加启动命令
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "serve": "cross-env NODE_ENV=development webpack-dev-server --mode=development --config build/webpack.config.js" },
- 添加webpack的其他相关依赖
- 依赖一:cross-env: 这个插件的作用是可以在webpack.config.js中通过 process.env.NODE_ENV 来获取当前是开发还是生产环境
npm install cross-env -D
- 依赖二:ts-loader: 需要解析.ts文件,所以需要依赖对应的loader:ts-loader
npm install ts-loader -D
- html-webpack-plugin: 编译后的代码需要对应的html模块作为它的运行环境,所以我们需要使用html-webpack-plugin来将它插入到对应的模板中
npm install html-webpack-plugin -D
- 依赖一:cross-env: 这个插件的作用是可以在webpack.config.js中通过 process.env.NODE_ENV 来获取当前是开发还是生产环境
- 配置webpack.config.js文件
const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/main.ts", output: { filename: "build.js" }, resolve: { extensions: [".tsx", ".ts", ".js"] }, module: { rules: [ { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ } ] }, devtool: process.env.NODE_ENV === "production" ? false : "inline-source-map", devServer: { contentBase: "./dist", stats: "errors-only", compress: false, host: "localhost", port: 8080 }, plugins: [ new HtmlWebpackPlugin({ template: "./index.html" }) ] };
- 安装webpack相关的依赖(本地安装)
-
-
方式二:通过
ts-node库
,为TypeScript的运行提供执行环境- 安装ts-node
npm install ts-node -g
- 另外ts-node需要依赖 tslib 和 @types/node 两个包:
npm install tslib @types/node -g
- 现在,我们可以直接通过 ts-node 来运行TypeScript的代码:
ts-node 文件名
- 安装ts-node
编译ts代码为js代码时,如果出现同名变量会报错,可以通过以下代码解决
export {} //直接赋值粘贴,内部不用写内容
变量的声明
var/let/const 标识符: 数据类型 = 赋值;
类型注解就是指数据类型
在tslint中并不推荐使用var来声明变量:主要原因和ES6升级后let和var的区别是一样的,var是没块级作用域的,会引起很多的问题
函数返回值注解:默认是为void,们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型
let foo = 'foo'
此时的foo为string类型
默认情况下进行赋值时,会将赋值的值的类型,作为前面标识符的类型
这个过程叫做类型推导/推断
string
是TypeScript中的字符串类型
String
是JavaScript的字符串包装类的类型
JavaScript和TypeScript的数据类型
Js数据类型
- JavaScript类型:
number、boolean、string、Array、Object、Symbol、null和undefined
-
数组类型的定义:
const names: string[] = ["a", "b"]
const names2: Array<string> = ["a", "b"]
-
对象类型:
const myInfo: Object = { name: "hahah", age:18 }
从myinfo中我们不能获取数据,也不能设置数据
myInfo["name"] = "lalala"
console.log(myInfo["age"])
console.log(myInfo.age)
都会报错 -
Symbol类型:
可以通过symbol来定义相同的名称,因为Symbol函数返回的是不同的值const s1: symbol = Symbol("title") const s2: symbol = Symbol("title") const person = { [s1]: "hahaha", [s2]: "lalala" }
-
ts数据类型
-
TypeScript类型
any、unknown、void、never、tuple
-
any类型:
无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型let a: any = "lalala" a = 123; //不报错
-
unknown类型:
用于描述类型不确定的变量,一般用于不知道函数返回值类型
let result: unknown result = foo()
unknown类型和any类型的区别:unknown类型只能赋值给any和unknown类型,any类型可以赋值给任意类型
-
never类型
never 表示永远不会发生值的类型,比如一个函数是一个死循环或者抛出一个异常,那么这个函数不会返回东西 -
tuple类型
- 数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中)
const tuple:[T, newState: ()=> void]
其中()=>void
表示函数类型 ,一般有以下两种写法:
const foo: ()=>void = ()=>{}
-
type MyFunction = ()=>void const foo: MyFunction = ()=>{}
- tuple通常可以作为函数返回的值
return [currentState, changeState]
- 数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中)
-
对象类型
- 属性之间可以使用
,
或者;
来分割,最后一个分隔符是可选的 - 每个属性的类型部分也是可选的,如果不指定,那么就是
any类型
; - **可选类型:**对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个
?
function Print(point: {x: number, y: number, z?:number}){...}
----- 其实上,可选类型可以看做是 类型 和 undefined 的联合类型 函数的可选类型需要在必传参数的后面
联合类型
- 联合类型是由两个或者多个其他类型组成的类型
- 表示可以是这些类型中的任何一个值
- 缩小联合:TypeScript可以根据我们缩小的代码结构,推断出更加具体的类型
function print(id: number | string){ if(typeof id === 'string'){ console.log("你的id是:", id.toUpperCase()); }else{ console.log("你的id是:", id) } }
类型别名:对于联合类型
和对象类型
,可以起一个别名来实现多次使用。
//对象类型
type Point = {
x: number,
y: number
}
function print(point: Point){
console.log(point.x, point.y);
}
//联合类型
type ID = number | string
function print(id: ID){
console.log(id);
}
枚举类型
枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型。一般字符都是大写。
枚举类型默认是有值的,默认值是从0开始的整数类型
enum Direction{
LEFT,
RIGHT,
TOP,
BOTTOM
}
function turnDirection(direction: Direction){
switch(direction){
case Direction.LEFT:
console("...left")
break;
case Direction.RIGHT:
...
}
}
泛型
参数类型不确定的时候可以使用泛型,即将类型参数化
泛型使用的两种方式:
- 方式一:通过 <类型> 的方式将类型传递给函数;
function foo<Type>(arg: Type): Type{
return arg
}
- 方式二:通过类型推到,自动推导出我们传入变量的类型
foo("abc")
//推导出来的主要是字面量类型
- 当传入多个类型时:
function foo<T, E>(a1: T, a2: E) {}
- 平时在开发中我们可能会看到一些常用的名称:
- T:Type的缩写,类型
- K、V:key和value的缩写,键值对
- E:Element的缩写,元素
- O:Object的缩写,对象
不仅函数可以使用泛型,还可以编写泛型接口和泛型类。
泛型约束:
举个栗子:只要是拥有length的属性都可以作为我们的参数类型
- 平时在开发中我们可能会看到一些常用的名称:
interface ILength {
length: number
}
function getLength<T extends ILength>(args:T) {
return args.length
}
console.log(getLength("abc"))
console.log(getLength(["abc", "cba"]))
console.log(getLength({length: 100, name:"why"}))
类型断言as
TypeScript只允许类型断言转换为 更具体 或者 不太具体(any/unknown) 的类型版本,此规则可防止不可能的强制转换
const el = document.getElementById('my-img') as HTMLImageElement
el.src = "url地址"
非空类型断言
非空断言使用的是!
,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测
function print(message?: string){
console.log(message!.toUpperCase()))
//由于可选类型,为传入的message有可能是为undefined的,这个时候是不能执行方法的。但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言。
}
可选链的使用
可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性
- 可选链使用可选链操作符
?.
- 它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行
console.log(info.friend?.girlFriend?.name);
空值合并操作符(??
)是一个逻辑操作符,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数
可选链不能用于赋值。
字面量类型
let message = "hello"
message为string类型
const message = "hello"
message为: "hello"类型
字符串本身也可以作为类型,叫做字面量类型
字面量类型的意义就是必须结合联合类型,从普通的数据类型到可以使用字符串作为类型。
type Alignment = 'left' | 'right' |'center'
let align: Alignment = 'left'
align = 'right'
align = 'center'
const 不可以随意修改值,除非是对象类型(引用)
字面量赋值:TypeScript在字面量直接赋值的过程中,为了进行类型推导会进行严格的类型限制,但是如果我们是将一个 变量标识符 赋值给其他的变量时,会进行freshness擦除操作。
interface IPerson{
name:string,
eating: ()=>void
}
const obj = {
name: "why",
age:18,
eating: function(){
}
}
const p: IPerson = obj;
//如果直接将obj的对象赋值给p会报错
类型缩小
typeof
检查返回的值typeof是一种类型保护
function print(id: number | string){
if(typeof id === 'string'){
console.log("你的id是:", id.toUpperCase());
}else{
console.log("你的id是:", id)
}
}
平等缩小(=、!)
可以使用Switch或者相等的一些运算符来表达相等性(比如===, !==, ==, and != )
type Direction = 'left' | 'right' | 'center'
function turnDirection(direction: Direction){
switch (direction) {
case 'left':
console.log("...left");
break;
case 'right':
console.log("...right");
break;
case 'center':
console.log("...center");
break;
default:
console.log("...default");
}
}
instanceof
JavaScript 有一个运算符来检查一个值是否是另一个值的实例
function print(date: Date | string){
if(date instanceof Date){
console.log(date.toLocaleString);
}else{
console.log(date);
}
}
实例是new的
in
用于确定对象是否具有带名称的属性:in运算符.
如果指定的属性在指定的对象或其原型链中,则in 运算符返回true
type Fish = {swim: ()=>void}
type Dog = {run: ()=>void}
function move(animal: Fish | Dog) {
if ('swim' in animal){
animal.swim()
}else{
animal.run()
}
}
这里的animal是一个常量,并非实例.
函数
剩余参数:将一个不定数量的参数放到一个数组中
function sum(...nums: number[]){
let total = 0
for(const num of nums){
total+=num
}
return total
}
const result = sum(10,20,30)
console.log(result)
函数重载
一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现
注意,有实现体的函数,是不能直接被调用的
function sum(a1: number, a2:number): number;
function sum(a1: string, a2:string): string;
//没有函数体
function sum(a1: any, a2: any): any {
return a1+a2
}
console.log(sum(20,30));
console.log(sum("aaa","bbb"));
在可能的情况下,尽量选择使用联合类型来实现
接口
索引类型
interface lang{
[name: string]: number
}
const langues: lang = {
"Java": 1995,
"JavaScript": 1996,
"C": 1972
}
函数类型
interface Calc{
(num1: number, num2: number): number
}
const add: Calc = (num1, num2) =>{
return num1+num2
}
除非特别的情况,还是推荐使用类型别名来定义函数
type Calc = (num1: number, num2: number) => number
interface和type区别
- 如果是定义非对象类型,通常推荐使用type
- interface 可以重复的对某个接口来定义属性和方法;
- 如果接口重名,会叠加接口内容
- 而type定义的是别名,别名是不能重复的
模块化开发
TypeScript支持两种方式来控制我们的作用域:
- 模块化:每个文件可以是一个独立的模块,支持ES Module,也支持CommonJS;
- 命名空间:通过namespace来声明一个命名空间
export namespace Time {
export function format(time: string){
return '2022-xx-xx'
}
}
export namespace Price {
export function format(price: number) {
return 'xxxx.xx'
}
}
类型查找
.d.ts
文件,它是用来做类型的声明(declare)。它仅仅用来做类型检测,告知typescript我们有哪些类型
typescript会在以下三个地方查找类型声明
-
内置类型声明;
在安装typescript的环境中会带有的 -
外部定义类型声明;
方式一:在自己库中进行类型声明(编写.d.ts文件),比如axios
方式二:通过社区的一个公有库DefinitelyTyped
存放类型声明文件- 该库的GitHub地址:
https://github.com/DefinitelyTyped/DefinitelyTyped/
- 该库查找声明安装方式的地址:
https://www.typescriptlang.org/dt/search?search=
- 比如安装react的类型声明:
npm i @types/react --save-dev
- 该库的GitHub地址:
-
自己定义类型声明;
以下情况需要自己定义声明文件:- 情况一:我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件;比如
lodash
- 情况二:我们给自己的代码中声明一些类型,方便在其他地方直接进行使用
声明模块的语法:declare module '模块名' {}
在声明模块的内部,我们可以通过export
导出对应库的类、函数等
declare module "lodash" { export function join(args: any[]): any; }
- 情况一:我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件;比如
declare文件
在开发vue的过程中,默认是不识别我们的.vue文件的,那么我们就需要对其进行文件的声明
declare module '*.vue' {
import { DefineComponent} from 'vue'
const component: DefineComponent
export default component
}
如在开发中我们使用了 jpg 这类图片文件,默认typescript也是不支持的,也需要对其进行声明
declare module '*.jpg' {
const src: string
export default src
}
ajax的使用:
- 引入jQuery
CDN地址:https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
- 命名空间声明
declare namespace $ {
function ajax(setting: any): void
}
- 使用
$.ajax({
url: "http://123.207.32.32:8000/home/multidata",
success: (res: any) => {
console.log(res);
}
})
tsconfig.json文件
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"jsx": "preserve", // 指定 jsx 格式
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
}