typescript
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
TypeScript 与 JavaScript 的区别
其实就是对JavaScript的封装,把一个弱类型语言封装成一个强类型语言,后端语言话,支持模块和接口
安装typescript
npm i typescript -g
写一个ts文件并编译
初始化tsc的配置文件tsc --init
,目录下就会出现 tsconfig.json文件,之后我们再执行编译命令
let hello:string="aaaaa"tsc ./src/index.ts
也可以不编译而用在线工具查看编译结果
typescript的数据类型
数据类型
可能由于ts是对js的封装,要兼容js的各种数据类型,所以ts的数据类型也是相当复杂,比如any unknow void never等,对一个类型又做了很多的拆分。
类型 | 例子 | 描述 |
---|---|---|
number | 1, -33, 2.5 | 任意数字 |
string | ‘hi’, “hi”, hi | 任意字符串 |
boolean | true、false | 布尔值true或false |
字面量 | 其本身 | 限制变量的值就是该字面量的值 |
any | * | 任意类型 |
unknown | * | 类型安全的any |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值 |
object | {name:‘孙悟空’} | 任意的JS对象 |
array | [1,2,3] | 任意JS数组 |
tuple | [4,5] | 元素,TS新增类型,固定长度数组 |
enum | enum{A, B} | 枚举,TS中新增类型 |
变量的声明方式
我们知道,在golang 里面我们声明变量,有两种方式
//var var num int64num = 22//:=num1 := 22fmt.Println(num, num1)
在typescript里面 他把这两种方法糅合了一下,个人感觉这种声明方式有点啰嗦
let a:number=1
let b:bool
类型检测
如果我们给一个number赋值字符串,编辑器就会报错,编译也不会通过
相关类型声明实例
// 布尔类型
let isDone: boolean = false;
// 数字类型
let count: number = 10;
//string类型
let userName: string = '张三';
//数组类型
let list: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];
//枚举类型 默认从0开始 也可以指定默认值
enum Color {Red,Green,Blue
}
//调用方式
console.log(Color.Red);
//go是这样用的 非常类似
// const (
// name1 = iota
// name2
// name3
// )//字面量
let color: 'red' | 'blue' | 'green' = 'red';
if (color=="red"){console.log("red")
}//Any类型 其实就是泛类型
let anyData:any=4
anyData="jimi"
anyData=true//unknown
let safeData:unknown=4
safeData="jimi"
safeData=true//void
let noData:void=undefined
//never
let error:(()=>never)=()=>{throw new Error("error")
}
//元组
let x:[string,number]=["jimi",10]
关于any unknown never void
- any
any简单来说它是一个泛类型,可以是任何类型,既然ts是为了约束类型,为什么还要出来一个any呢?个人感觉可能是为了兼容其他的package。但是any有一个问题,它既然是任何类型,就意味着不论怎样,它都不会报错,我们举例看一下
let car:any={name:"BMW",model:"X5",year:2019,color:"black"
}
car="123"
car.Hello()
- unknow
那ts可就没有任何意义了,怎么办?即需要any类型,又不想让大家乱用导致报错,咋办?ts又推出一个类型,叫unknown,unknow允许接收任何类型,但是不允许你操作这个数据,我们简单看一下,编译也不会通过
如果你想要操作它,可以,你必须让编译器知道这是你主动去操作的,而不是误操作,比如说,你断言了类型,说实话,很鸡肋的功能。
let bike: unknown = "124";
bike = { name: "Honda", model: "X5", year: 2019, color: "black" };if (typeof bike === "object" && bike !== null) {// @ts-ignore 用于告诉 TypeScript 编译器忽略对接下来代码的类型检查console.log((bike as object).name);//转换为anyconsole.log((bike as any).name);
}
- never
如果你知道if elseif,那么就太好理解了,never就是你if elseif 都不可能命中的情况,简单举个例子
function getInfo(id:string|number){if (typeof id === "string"){}else if (typeof id === "number"){//超出所有预期,}else{throw new Error("id类型错误")}
}
- void
我们之前写html的时候为了不让a标签跳转,我们会怎么写?一般都用<a href="javascript:void(0)">
阻止默认跳转的行为,void 就代表的是啥也不做,没有任何返回值
function doNothing():void{console.log(111);
}
类型断言
//类型断言1 as语法
let unknownData:unknown="this is a string";
console.log((unknownData as string).length);
//类型断言2 尖括号
let unknownNewData:unknown="this is a string";
console.log((<string>unknownNewData).length);
React+ts环境搭建
接下来为了方便演示和输出结果,我使用react+ts来构建一个项目npx create-react-app react_ts --template typescript
,创建完毕之后,会出现react_ts目录,我们进入目录内,点进package.json文件,点击start 项目即可启动
- 也可以使用单元测试,即
react_ts/src/App.test.tsx
文件中通过test来实现单测 - 建议升级一下node
import React from 'react';test('renders learn react link', () => {console.log(1+2222);
});
类型判断
in
我们知道,一个变量可以是类型a 或者是类型b,当我们判断到底是a还是b的时候,我们可以用in来判断,比如下面代码
test('type in', () => {interface a {name: string;}interface b {age: number;}let c:a|b;c={age:80}if ("age" in c ){console.log("this is type b");}
});
typeof
typeof其实也可以说是一种类型映射,通过映射可以解析变量属于哪种类型
test('typeof', () => {let a={name:"zhangsan"}console.log(typeof a);//object
});
- instanceof 这个对于后端人员最熟悉不过了,判断变量是否属于某个类,前端语言后端工程化。
class Animal {name:string;constructor(name:string){this.name=name;}sayHi(){return `My name is ${this.name}`;}
}test('class', () => {let dog=new Animal("dog");if (dog instanceof Animal){console.log(dog.sayHi());}
});
类型交叉
ts的类型可以 交叉 可以 或,可以说是非常之(s)屌
test('type in', () => {interface a {name: string;}interface b {age: number;}let c:a|b;let d:a&bc={age:80}d={name:"zhangsan",age:80}console.log(c);console.log(d);
});
解构
看名字好像很厉害,其实就是展开的意思,不管是数组还是对象,都可以展开,举个例子
test('open', () => {//对象展开let people={name:"zhangsan",age:18,sayHi(){return "i am sayHi";}}let {name,age,sayHi}=peopleconsole.log(name);console.log(age);console.log(sayHi());//数组展开let arr=[1,2,3,4,5,6]let arr2=[...arr,7,8,9]console.log(arr2);
});
- 循环
//循环for(let i of arr){console.log(i);}
接口
其实就是php的抽象类,必须全部实现接口里面的属性和方法,才可以使用
interface IAnimal{name:string;age:number;sayHi(hello:string):string;}test("interface",()=>{let dog:IAnimal={name:"dog",age:18,sayHi(hello){return `${hello}`;}}console.log(dog);})
既然是抽象类,那肯定得有私有属性和公共属性吧,一般默认公共属性,私有属性需要用readonly修饰符,初始化之后不再允许改变
interface IAnimal{name:string;age:number;readonly sex:number;sayHi(hello:string):string;}test("interface",()=>{let dog:IAnimal={name:"dog",age:18,sex:1,sayHi(hello){return `${hello}`;}}dog.age=20;dog.sex=2;//报错 Cannot assign to 'sex' because it is a read-only propertyconsole.log(dog);})
类
直接参考php的类和相关操作就可以了
class A{private name:string="a"public b:stringstatic Db:string="db"constructor(b_name:string){this.b=b_name}getB(){return this.b}setB(b_name:string){this.b=b_name}
}test("classA",()=>{let a=new A("b")console.log(a.getB());console.log(A.Db);a.setB("bbbbb")console.log(a);
})
- 静态变量和成员变量的区别
静态变量在全局只会生成一个,也就是说只有一个内存变量,成员变量是每个对象都会生成一份数据。
类的继承
这个没啥说的,和php一样用extends继承,反正写着写着,就好像写成了php
编译选项
前面我们提了,我们可以使用tsc ./src/index.ts
来指定编译的文件,但是如果我想实时编译怎么办呢?其实就是让tsc运行在watch模式,在执行命令的时候,加一个w就可以了 tsc ./src/index.ts -w
tsconfig.json
如果我们有额外的配置,可以写在这个json文件中
- include 表示编译哪些目录下的ts文件
"include":["src/**/*", "tests/**/*"]
- exclude表示不编译哪些目录下文件
- extends 配置文件可以被继承的,下面这句的意思就是配置继承自base.json文件
"extends": "./configs/base"
- files 指定编译哪些文件
"files": ["core.ts","sys.ts","types.ts","scanner.ts","parser.ts","utilities.ts","binder.ts","checker.ts","tsc.ts"]
- 编译选项配置
{"extends": "","compileOnSave": false,"compilerOptions": { //编译选项"allowJS": false, // 允许编译器编译JS,JSX文件"checkJs": false, // 在 .js文件中报告错误。与allowJs配合使用。"allowSyntheticDefaultImports": false, //允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。默认值:module === "system" 或设置了 --esModuleInterop 且 module 不为 es2015 / esnext"allowUnreachableCode": false, //不报告执行不到的代码错误。"allowUnusedLabels": false, //不报告未使用的标签错误"alwaysStrict": false, // 在代码中注入'use strict',以严格模式解析并为每个源文件生成 "use strict"语句"charset": "utf8", //输入文件的字符集"declaration": false, // 生成声明文件.d.ts,开启后会自动生成声明文件"declarationDir": "", // 指定生成声明文件存放目录"diagnostics": false, // 显示诊断信息"extendedDiagnostics": false, //显示详细的诊段信息"experimentalDecorators":false,//启用实验性的ES装饰器"disableSizeLimit": false, //禁用JavaScript工程体积大小的限制"emitBOM": false, //在输出文件的开头加入BOM头(UTF-8 Byte Order Mark)。"forceConsistentCasingInFileNames": false, //禁止对同一个文件的不一致的引用"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度"isolatedModules":false,//将每个文件作为单独的模块(与“ts.transpileModule”类似)。"listEmittedFiles": false, //打印出编译后生成文件的名字"listFiles": false, // 编译过程中打印文件名"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置"target": "ES5", // 指定ECMAScript目标版本 "ES3"(默认), "ES5", "ES6"/ "ES2015", "ES2016", "ES2017"或 "ESNext""module": "CommonJS", // 设置程序的模块系统, "None", "CommonJS", "AMD", "System", "UMD", "ES6"或 "ES2015", "ESNext", "ES2020",只有 "AMD"和 "System"能和 --outFile一起使用,"ES6"和 "ES2015"可使用在目标输出为 "ES5"或更低的情况下。默认值:target === "ES6" ? "ES6" : "commonjs""moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入"jsx":"Preserve",//在 `.tsx`文件里支持JSX: `"React"`或 `"Preserve"`"jsxFactory":"React.createElement",//指定生成目标为react JSX时,使用的JSX工厂函数,比如 `React.createElement`或 `h`"newLine": "crlf", //当生成文件时指定行结束符: "crlf"(windows)或 "lf"(unix)。"noEmit": false, // 不输出文件,即编译后不会生成任何js文件"noEmitOnError": false, // 发送错误时不输出任何文件"noErrorTruncation": false, //不截短错误消息"noFallthroughCasesInSwitch": false, // 防止switch语句贯穿(即如果没有break语句后面不会执行)"noImplicitAny": false, // 不允许隐式的any类型,在表达式和声明上有隐含的 any类型时报错"noImplicitReturns": false, //每个分支都会有返回值,不是函数的所有返回路径都有返回值时报错"noImplicitThis": false, // 不允许this有隐式的any类型"noImplicitUseStrict": false, //模块输出中不包含 "use strict"指令"noLib": false, //不包含默认的库文件( lib.d.ts)"noResolve": false, //不把 /// <reference``>或模块导入的文件加到编译文件列表。"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用"noStrictGenericChecks": false, //禁用在函数类型里对泛型签名进行严格检查"noUnusedLocals": false, // 若有未使用的局部变量则抛错"noUnusedParameters": false, // 检若有未使用的函数参数则抛错"lib": [ //TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array","DOM","ES2015","ScriptHost","ES2019.Array"],"outDir": "./dist", // 指定输出目录"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD","preserveSymlinks": false, //不把符号链接解析为其真实路径;将符号链接文件视为真正的文件"preserveWatchOutput": false, //保留watch模式下过时的控制台输出"removeComments": true, // 删除所有注释,除了以 /!*开头的版权信息"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构"resolveJsonModule":true,//允许导入扩展名为“.json”的模块"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件"sourceMap": true, // 生成目标文件的sourceMap文件"inlineSourceMap": false, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中"inlineSources": false, // 将代码与sourcemaps生成到一个文件中,要求同时设置了 --inlineSourceMap或 --sourceMap属性"declarationMap": true, // 为声明文件生成sourceMap"types": [], // 要包含的类型声明文件名列表"typeRoots": [], // 声明文件目录,默认时node_modules/@types"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块(比如 __extends, __rest等)"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现"strict": true, // 启用所有严格类型检查选项。启用 --strict相当于启用 --noImplicitAny, --noImplicitThis, --alwaysStrict, --strictNullChecks和 --strictFunctionTypes和--strictPropertyInitialization"skipLibCheck": false, //忽略所有的声明文件( *.d.ts)的类型检查"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量.在严格的 null检查模式下, null和 undefined值不包含在任何类型里,只允许用它们自己和 any来赋值(有个例外, undefined可以赋值到 void)"strictFunctionTypes": true, // 不允许函数参数双向协变"strictPropertyInitialization": true, // 确保类的非undefined属性已经在构造函数里初始化。若要令此选项生效,需要同时启用--strictNullChecks"suppressExcessPropertyErrors": false, //阻止对对象字面量的额外属性检查"suppressImplicitAnyIndexErrors": false, //阻止 --noImplicitAny对缺少索引签名的索引对象报错"strictBindCallApply": true, // 严格的bind/call/apply检查"useDefineForClassFields": true, //详见 https://jkchao.github.io/typescript-book-chinese/new/typescript-3.7.html#usedefineforclassfields-%E6%A0%87%E8%AE%B0%E4%B8%8E-declare-%E5%B1%9E%E6%80%A7%E4%BF%AE%E9%A5%B0%E7%AC%A6"esModuleInterop": true, // 允许module.exports=xxx 导出,由import from 导入.因为很多老的js库使用了commonjs的导出方式,并且没有导出default属性"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录"paths": { // 模块名到基于 baseUrl的路径映射的列表// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置"jquery": ["node_modules/jquery/dist/jquery.min.js"]},"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错},"files": [],"include": [],"exclude": [],"references": []
}
- 常用配置参考
{"compilerOptions": {"allowJs": true, //允许编译器编译JS,JSX文件"target": "ES2015", //指定ECMAScript目标版本"useDefineForClassFields": true,"module": "ESNext", //设置程序的模块系统"moduleResolution": "Node", //模块解析策略。默认使用node的模块解析策略"strict": true, //启用所有严格类型检查选项"jsx": "preserve", //preserve模式,在preserve模式下生成代码中会保留JSX以供后续的转换操作使用"sourceMap": true, //生成目标文件的sourceMap文件"resolveJsonModule": true, //允许导入扩展名为“.json”的模块"esModuleInterop": false, //允许module.exports=xxx 导出,由import from 导入.因为很多老的js库使用了commonjs的导出方式,并且没有导出default属性"lib": [ //TS需要引用的库"ESNext","DOM"],"forceConsistentCasingInFileNames": true, //禁止对同一个文件的不一致的引用"allowSyntheticDefaultImports": true, //允许从没有设置默认导出的模块中默认导入"skipLibCheck": true, //忽略所有的声明文件( *.d.ts)的类型检查"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录"paths": { //模块名到基于 baseUrl的路径映射的列表"/@/*": ["src/*"],},"types": [ //要包含的类型声明文件名列表"vite/client","element-plus/global",]},"include": [ //包含的文件"src/**/*.ts","src/**/*.d.ts","src/**/*.tsx","src/**/*.js","src/**/*.jsx","src/**/*.vue",]
}
总结
一直都听说前端卷,看了ts之后发现是真的卷,比如数据类型就多出了any unknown never void 不知道实际过程中用处大不大,比如类型之间的|和&,当场就把我看懵逼了,已经有了interface,有了class,还有extends,你来给我讲讲你类型之间的|和&真的是必须的吗?今天刚看完typescript,听说你们又开始next.js了,前端大佬们,你们真的没事干了吗?😭😭
相关参考
react+ts部署
ts详解