入门与基本类型
# 入门与基本类型
# TypeScript 开发环境搭建
下载 Node.js
安装 Node.js,使用 npm 全局安装 typescript
npm i -g typescript
创建一个 ts 文件,使用 tsc 对 ts 文件进行编译,进入 ts 文件所在目录。
tsc xxx.ts
# 基本类型
类型声明是 TS 非常重要的一个特点,通过类型声明可以指定 TS 中变量(参数、形参)的类型。
指定类型后,当为变量赋值时,TS 编译器会自动检查值是否符合类型声明,符合则赋值,否则报错。简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值。
语法
let 变量: 类型;
let 变量: 类型 = 值;
function fn(参数: 类型, 参数: 类型): 类型{
...
}
2
3
4
5
6
7
示例
let a: string //变量a只能存储字符串
let b: number //变量a只能存储数值
let c: boolean //变量a只能存储布尔值
a = 'hello'
a = 100 //警告:不能将类型“number”分配给类型“string”
b = 666
b = '你好' //警告:不能将类型“string”分配给类型“number”
c = true
c = 666 //警告:不能将类型“number”分配给类型“boolean”
// 参数x必须是数字,参数y也必须是数字,函数返回值也必须是数字
function demo(x:number,y:number):number{
return x + y
}
demo(100,200)
demo(100,'200') //警告:类型“string”的参数不能赋给类型“number”的参数
demo(100,200,300) //警告:应有 2 个参数,但获得 3 个
demo(100) //警告:应有 2 个参数,但获得 1 个
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 类型推断
自动类型判断,TS 拥有自动的类型判断机制。当对变量的声明和赋值是同时进行的,TS 编译器会自动判断变量的类型,所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明。
| 类型 | 例子 | 描述 |
|---|---|---|
| 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 、 Function 、Date ...... |
| array | [1,2,3] | 任意 JS 数组 |
| tuple | [4,5] | 元素,TS 新增类型,固定长度数组 |
| enum | enum{A, B} | 枚举,TS 中新增类型 |
# number
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;
2
3
4
5
# boolean
let isDone: boolean = false;
# string
let color: string = "blue";
color = 'red';
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}.
I'll be ${age + 1} years old next month.`;
2
3
4
5
6
7
8
# 字面量
// 字面量
let color: 'red' | 'blue' | 'black'; // 也可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围
let num: 1 | 2 | 3 | 4 | 5;
let a: '你好' //a的值只能为字符串“你好”
let b: 100 //b的值只能为数字100
a = '欢迎'//警告:不能将类型“"欢迎"”分配给类型“"你好"”
b = 200 //警告:不能将类型“200”分配给类型“100”
let gender: '男'|'女' //定义⼀个gender变量,值只能为字符串“男”或“女”
gender = '男'
gender = '未知' //不能将类型“"未知"”分配给类型“"男" | "女"”
2
3
4
5
6
7
8
9
10
# any
any 的含义是:任意类型,⼀旦将变量类型限制为 any ,那就意味着放弃了对该变量的类型检查。
注意点: any 类型的变量,可以赋值给任意类型的变量
// any
let d: any = 4;
d = 'hello';
d = true;
//明确的表示a的类型是any —— 显式的any
let a: any
//以下对a的赋值,均无警告
a = 100
a = '你好'
a = false
//没有明确的表示b的类型是any,但TS主动推断了出来 —— 隐式的any
let b
//以下对b的赋值,均无警告
b = 100
b = '你好'
b = false
/* 注意点:any类型的变量,可以赋值给任意类型的变量 */
let a
let x: string
x = a // 无警告
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# unknown
unknown 的含义是:未知类型。
备注 1: unknown 可以理解为⼀个类型安全的 any 备注 2: unknown 适用于:开始不知道数据的具体类型,后期才能确定数据的类型
// 设置a的类型为unknown
let a: unknown
//以下对a的赋值,均正常
a = 100
a = false
a = '你好'
// 设置x的数据类型为string
let x: string
x = a //警告:不能将类型“unknown”分配给类型“string”
2
3
4
5
6
7
8
9
10
11
若就是想把 a 赋值给 x ,可以用以下三种写法:
// 设置a的类型为unknown
let a: unknown
a = 'hello'
//第⼀种⽅式:加类型判断
if(typeof a === 'string'){
x = a
}
//第⼆种⽅式:加断言
x = a as string
//第三种⽅式:加断言
x = <string>a
2
3
4
5
6
7
8
9
10
11
12
13
14
any 后点任何的东⻄都不会报错,而 unknown 正好与之相反。
let str1: string = 'hello'
str1.toUpperCase() //无警告
let str2: any = 'hello'
str2.toUpperCase() //无警告
let str3: unknown = 'hello';
str3.toUpperCase() //警告:“str3”的类型为“未知”
// 使用断⾔强制指定str3的类型为string
(str3 as string).toUpperCase() //无警告
2
3
4
5
6
7
8
9
10
11
# never
never 的含义是:任何值都不是,简⾔之就是不能有值, undefined 、 null 、 '' 、 0 都不行!
几乎不用 never 去直接限制变量,因为没有意义,例如:
/* 指定a的类型为never,那就意味着a以后不能存任何的数据了 */
let a: never
// 以下对a的所有赋值都会有警告
a = 1
a = true
a = undefined
a = null
2
3
4
5
6
7
8
never ⼀般是 TypeScript 主动推断出来的,例如:
// 指定a的类型为string
let a: string
// 给a设置⼀个值
a = 'hello'
if(typeof a === 'string'){
a.toUpperCase()
}else{
console.log(a) // TypeScript会推断出此处的a是never,因为没有任何⼀个值符合此处的逻辑
}
2
3
4
5
6
7
8
9
10
never 也可用于限制函数的返回值
// 限制demo函数不需要有任何返回值,任何值都不⾏,像undeifned、null都不⾏
function demo():never{
throw new Error('程序异常退出')
}
2
3
4
# void
void 的含义是: 空 或 undefined ,严格模式下不能将 null 赋值给 void 类型。
let a:void = undefined
//严格模式下,该⾏会有警告:不能将类型“null”分配给类型“void”
let b:void = null
2
3
4
void 常用于限制函数返回值
// 无警告
function demo1():void{
}
// 无警告
function demo2():void{
return
}
// 无警告
function demo3():void{
return undefined
}
// 有警告:不能将类型“number”分配给类型“void”
function demo4():void{
return 666
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# object
关于 Object 与 object ,直接说结论:在类型限制时, Object 几乎不⽤,因为范围太大了,无意义。
object 的含义:任何【非原始值类型】,包括:对象、函数、数组等,限制的范围比较宽泛,⽤的少
let a:object //a的值可以是任何【非原始值类型】,包括:对象、函数、数组等
// 以下代码,是将【非原始类型】赋给a,所以均无警告
a = {}
a = {name:'张三'}
a = [1,3,5,7,9]
a = function(){}
// 以下代码,是将【原始类型】赋给a,有警告
a = null // 警告:不能将类型“null”分配给类型“object”
a = undefined // 警告:不能将类型“undefined”分配给类型“object”
a = 1 // 警告:不能将类型“number”分配给类型“object”
a = true // 警告:不能将类型“boolean”分配给类型“object”
a = '你好' // 警告:不能将类型“string”分配给类型“object”
2
3
4
5
6
7
8
9
10
11
12
13
14
Object 的含义: Object 的实例对象,限制的范围太⼤了,⼏乎不⽤。
let a:Object //a的值必须是Object的实例对象,
// 以下代码,均⽆警告,因为给a赋的值,都是Object的实例对象
a = {}
a = {name:'张三'}
a = [1,3,5,7,9]
a = function(){}
a = 1 // 1不是Object的实例对象,但其包装对象是Object的实例
a = true // truue不是Object的实例对象,但其包装对象是Object的实例
a = '你好' // “你好”不是Object的实例对象,但其包装对象是Object的实例
// 以下代码均有警告
a = null // 警告:不能将类型“null”分配给类型“Object”
a = undefined // 警告:不能将类型“undefined”分配给类型“Object”
2
3
4
5
6
7
8
9
10
11
12
13
14
实际开发中,限制⼀般对象,通常使⽤以下形式
// 限制person对象的具体内容,使⽤【,】分隔,问号代表可选属性
let person: { name: string, age?: number}
// 限制car对象的具体内容,使⽤【;】分隔,必须有price和color属性,其他属性不去限制,有没有都⾏
let car: { price: number; color: string; [k:string]:any}
// 限制student对象的具体内容,使⽤【回⻋】分隔
let student: {
id: string
grade:number
}
// 以下代码均⽆警告
person = {name:'张三',age:18}
person = {name:'李四'}
car = {price:100,color:'红⾊'}
student = {id:'tetqw76te01',grade:3}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
限制函数的参数、返回值,使⽤以下形式
let demo: (a: number, b: number) => number
demo = function(x,y) {
return x+y
}
2
3
4
限制数组,使⽤以下形式
let arr1: string[] // 该⾏代码等价于: let arr1: Array<string>
let arr2: number[] // 该⾏代码等价于: let arr2: Array<number>
arr1 = ['a','b','c']
arr2 = [1,3,5,7,9]
2
3
4
5
# array
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
2
# tuple
tuple 就是⼀个⻓度固定的数组。
let t: [string,number]
t = ['hello',123]
// 警告,不能将类型“[string, number, boolean]”分配给类型“[string, number]”
t = ['hello',123,false]
2
3
4
5
6
# enum
enum 是枚举
// 定义⼀个枚举
enum Color {
Red,
Blue,
Black,
Gold
}
// 定义⼀个枚举,并指定其初识数值
enum Color2 {
Red = 6,
Blue,
Black,
Gold
}
console.log(Color)
/*
{
0: 'Red',
1: 'Blue',
2: 'Black',
3: 'Gold',
Red: 0,
Blue: 1,
Black: 2,
Gold: 3
}
*/
console.log(Color2)
/*
{
6: 'Red',
7: 'Blue',
8: 'Black',
9: 'Gold',
Red: 6,
Blue: 7,
Black: 8,
Gold: 9
}
*/
// 定义⼀个phone变量,并设置对⻬进⾏限制
let phone: {name:string,price:number,color:Color}
phone = {name:'华为Mate60',price:6500,color:Color.Red}
phone = {name:'iPhone15Pro',price:7999,color:Color.Blue}
if(phone.color === Color.Red){
console.log('⼿机是红⾊的')
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 类型断言
有些情况下,变量的类型对于我们来说是很明确,但是 TS 编译器却并不清楚,此时,可以通过类型断言来告诉编译器变量的类型,断言有两种形式:
// 方式一
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
// 方式二
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;
2
3
4
5
6
7
# 自定义类型
自定义类型,可以更灵活的限制类型
// 性别的枚举
enum Gender {
Male,
Female
}
// 自定义⼀个年级类型(高⼀、高⼆、高三)
type Grade = 1 | 2 | 3
// 自定义⼀个学⽣类型
type Student = {
name:string,
age:number,
gender:Gender,
grade:Grade
}
// 定义两个学⽣变量:s1、s2
let s1:Student
let s2:Student
s1 = {name:'张三',age:18,gender:Gender.Male,grade:1}
s2 = {name:'李四',age:18,gender:Gender.Female,grade:2}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 编译选项
# 自动编译文件
编译文件时,使用 -w 指令后,TS 编译器会自动监视文件的变化,并在文件发生变化时对文件进行重新编译。
tsc xxx.ts -w
# 自动编译整个项目
如果直接使用 tsc 指令,则可以自动将当前项目下的所有 ts 文件编译为 js 文件。但是能直接使用 tsc 命令的前提时,要先在项目根目录下创建一个 ts 的配置文件 tsconfig.json , tsconfig.json 是一个 JSON 文件,添加配置文件后,只需只需 tsc 命令即可完成对整个项目的编译
配置选项:
# include
include:定义希望被编译文件所在的目录,默认值:["**/*"]
"include":["src/**/*", "tests/**/*"] // 所有src目录和tests目录下的文件都会被编译
# exclude
exclude:定义需要排除在外的目录,默认值:["node_modules", "bower_components", "jspm_packages"]
"exclude": ["./src/hello/**/*"] // src下hello目录下的文件都不会被编译
# extends
extends:定义被继承的配置文件
"extends": "./configs/base" // 当前配置文件中会自动包含config目录下base.json中的所有配置信息
# files
files:指定被编译文件的列表,只有需要编译的文件少时才会用到
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"tsc.ts"
] // 列表中的文件都会被TS编译器所编译
2
3
4
5
6
7
8
9
10
11
# compilerOptions
compilerOptions:编译选项是配置文件中非常重要也比较复杂的配置选项,在 compilerOptions 中包含多个子选项,用来完成对编译的配置
# target
target:设置 ts 代码编译的目标版本,可选值 ES3(默认)、ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext
"compilerOptions": {
"target": "ES6"
} // 我们所编写的ts代码将会被编译为ES6版本的js代码
2
3
# lib
lib:指定代码运行时所包含的库(宿主环境), 可选值:ES5、ES6/ES2015、ES7/ES2016、ES2017、ES2018、ES2019、ES2020、ESNext、DOM、WebWorker、ScriptHost ......
"compilerOptions": {
"target": "ES6",
"lib": ["ES6", "DOM"],
"outDir": "dist",
"outFile": "dist/aa.js"
}
2
3
4
5
6
# module
module:设置编译后代码使用的模块化系统,可选值:CommonJS、UMD、AMD、System、ES2020、ESNext、None
"compilerOptions": {
"module": "CommonJS"
}
2
3
# outDir
outDir:编译后文件的所在目录,默认情况下,编译后的 js 文件会和 ts 文件位于相同的目录,设置 outDir 后可以改变编译后文件的位置
"compilerOptions": {
"outDir": "dist"
} // 设置后编译后的js文件将会生成到dist目录
2
3
# outFile
outFile:将所有的文件编译为一个 js 文件,默认会将所有的编写在全局作用域中的代码合并为一个 js 文件,如果 module 制定了 None、System 或 AMD 则会将模块一起合并到文件之中
"compilerOptions": {
"outFile": "dist/app.js"
}
2
3
# rootDir
rootDir:指定代码的根目录,默认情况下编译后文件的目录结构会以最长的公共目录为根目录,通过 rootDir 可以手动指定根目录
"compilerOptions": {
"rootDir": "./src"
}
2
3
# allowJs
allowJs:是否对 js 文件编译
"compilerOptions": {
"allowJs": true
}
2
3
# checkJs
checkJs:是否对 js 文件进行检查
"compilerOptions": {
"checkJs": true
}
2
3
# removeComments
removeComments:是否删除注释,默认值:false
# noEmit
noEmit:不对代码进行编译,默认值:false
# sourceMap
sourceMap:是否生成 sourceMap,默认值:false
# 严格检查
- strict:启用所有的严格检查,默认值为 true,设置后相当于开启了所有的严格检查
- alwaysStrict:总是以严格模式对代码进行编译
- noImplicitAny:禁止隐式的 any 类型
- noImplicitThis:禁止类型不明确的 this
- strictBindCallApply:严格检查 bind、call 和 apply 的参数列表
- strictFunctionTypes:严格检查函数的类型
- strictNullChecks:严格的空值检查
- strictPropertyInitialization:严格检查属性是否初始化
# 额外检查
- noFallthroughCasesInSwitch:检查 switch 语句包含正确的 break
- noImplicitReturns:检查函数没有隐式的返回值
- noUnusedLocals:检查未使用的局部变量
- noUnusedParameters:检查未使用的参数
# 高级
- allowUnreachableCode:检查不可达代码,可选值:true,忽略不可达代码;false,不可达代码将引起错误
- noEmitOnError:有错误的情况下不进行编译,默认值:false
# Webpack
通常情况下,实际开发中我们都需要使用构建工具对代码进行打包,TS 同样也可以结合构建工具一起使用,下边以 webpack 为例介绍一下如何结合构建工具使用 TS。
初始化项目,进入项目根目录,执行命令,主要作用:创建 package.json 文件
npm init -y
下载构建工具
npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin
共安装了 7 个包
- webpack:构建工具 webpack
- webpack-cli:webpack 的命令行工具
- webpack-dev-server:webpack 的开发服务器
- typescript:ts 编译器
- ts-loader:ts 加载器,用于在 webpack 中编译 ts 文件
- html-webpack-plugin:webpack 中 html 插件,用来自动创建 html 文件
- clean-webpack-plugin:webpack 中的清除插件,每次构建都会先清除目录
根目录下创建 webpack 的配置文件 webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
optimization:{
minimize: false // 关闭代码压缩,可选
},
entry: "./src/index.ts",
devtool: "inline-source-map",
devServer: {
contentBase: './dist'
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
environment: {
arrowFunction: false // 关闭webpack的箭头函数,可选
}
},
resolve: {
extensions: [".ts", ".js"]
},
module: {
rules: [
{
test: /\.ts$/,
use: {
loader: "ts-loader"
},
exclude: /node_modules/
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title:'TS测试'
}),
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
根目录下创建 tsconfig.json ,配置可以根据自己需要
{
"compilerOptions": {
"target": "ES2015",
"module": "ES2015",
"strict": true
}
}
2
3
4
5
6
7
修改 package.json 添加如下构建脚本
{
// ...略...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve --open chrome.exe"
},
// ...略...
}
2
3
4
5
6
7
8
9
在 src 下创建 ts 文件,并在并命令行执行
npm run build # 对代码进行编译
# 或者执行
npm start # 来启动开发服务器
2
3
# Babel
经过一系列的配置,使得 TS 和 webpack 已经结合到了一起,除了 webpack,开发中还经常需要结合 babel 来对代码进行转换以使其可以兼容到更多的浏览器,在上述步骤的基础上,通过以下步骤再将 babel 引入到项目中。
安装依赖包:
npm i -D @babel/core @babel/preset-env babel-loader core-js
共安装了 4 个包,分别是:
- @babel/core:babel 的核心工具
- @babel/preset-env:babel 的预定义环境
- @babel-loader:babel 在 webpack 中的加载器
- core-js:core-js 用来使老版本的浏览器支持新版 ES 语法
修改 webpack.config.js 配置文件
// ...略...
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: "babel-loader",
options:{
presets: [
[
"@babel/preset-env",
{
"targets":{
"chrome": "58",
"ie": "11"
},
"corejs":"3",
"useBuiltIns": "usage"
}
]
]
}
},
{
loader: "ts-loader",
}
],
exclude: /node_modules/
}
]
}
// ...略...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
如此一来,使用 ts 编译后的文件将会再次被 babel 处理,使得代码可以在大部分浏览器中直接使用,可以在配置选项的 targets 中指定要兼容的浏览器版本。