编译TypeScript
我们知道typescript只有编译程javascript时,才能在node或者浏览器上运行,那么如何编译typescript呢?
- 通过
tsc
命令。
- 如果想要执行该命令,我们需要全局安装typescript。这样就可以使用该命令编译ts文件了。他会生成对应名字的js文件,然后我们可以通过node或者浏览器运行该js文件。
- 通过
ts-node
库,来将ts文件直接在node中进行运行。
- 安装ts-node库
npm install ts-node -g
。
- 并且它还依赖两个其他的库
tslib
,@types/node
。所以我们可以我们也需要安装npm install tslib @types/node -g
- 通过webpack配置相关loader来编译ts文件。 需要安装一下这些库
"html-webpack-plugin": "^5.3.2", "ts-loader": "^9.2.3", "typescript": "^4.3.5", "webpack": "^5.44.0", "webpack-cli": "^4.7.2", "webpack-dev-server": "^3.11.2"
并且需要执行tsc --init
, 来生成'tsconfig.json'
文件,不然编译时会报错。 在package.json文件中配置一下脚本。
"scripts": { "build": "webpack", "serve": "webpack serve" }
webpack的配置
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: "development", entry: "./src/main.ts", output: { path: path.resolve(__dirname, "./dist"), filename: "bundle.js" }, devServer: { }, resolve: { extensions: [".ts", ".js", ".cjs", ".json"] }, module: { rules: [ { test: /\.ts$/, loader: 'ts-loader' } ] }, plugins: [ new HtmlWebpackPlugin({ template: "./index.html" }) ] }
执行npm run serve
运行项目即可。
TypeScript类型
TypeScript是JavaScript的一个超级。js中存在的类型在ts中都存在,并且ts也扩展了自己的类型,下面我们就来看一下吧。需要注意的是基本类型的值可以赋值给对应的包装类型,但是包装类型不能赋值给对应的基本类型。
const message1: String = 'HelloWorld'// 这里是可以的 const message2: string = new String('Hello World') //这里会报错
any
类型:unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。
unknown类型只能赋值给any和unknown类型 在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型(类似于Dart语言中的dynamic类型)。
我们给一个any类型的变量赋值任何的值,比如数字、字符串的值
unknown
类型
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。
unknown类型只能赋值给any和unknown类型。any类型可以赋值给任意类型。所以unknown相比于any更安全。
function foo() { return 'abc' } function bar() { return 123 } // unknown类型只能赋值给any和unknown类型 // any类型可以赋值给任意类型 let flag = true // 接收的返回值可能是string, 也可能是number。 let result: unknown // 最好不要使用any if (flag) { result = foo() } else { result = bar() }
void
类型
- void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型。
- 我们可以将null和undefined赋值给void类型,也就是函数可以返回null或者undefined。
- 函数我们没有写任何类型,那么它默认返回值的类型就是void的,我们也可以显示的来指定返回值是void。
function sum(num1: number, num2: number): void { console.log(num1 + num2) return 'pp' // 如果指定了函数没有返回值,这里会报错。但是可以返回undefined, null。 } sum(20, 30)
never
类型
never 表示永远不会发生值的类型。
比如一个函数:如果一个函数中是一个死循环或者抛出一个异常,那么这个函数会返回东西吗?
不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型。
tuple
类型
tuple是元组类型,很多语言中也有这种数据类型,比如Python、Swift等。
那么tuple和数组有什么区别呢?
- 首先,数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中)。
- 其次,元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型。
- 其实他就是固定长度和类型的数组。
const info: [string, number, number] = ["zh", 20, 0] const name = info[0] console.log(info.slice(0, 1))
大致了解了tuple,那他将用在什么场景呢?通过下面例子来看一看吧。
简单实现一个useState hook函数,我们都知道useState hook,但会一个数组,其中包括两个元素,第一个元素是传入的值,第二个元素是一个函数,所以我们就可以使用tuple来对其进行约束。
function useState<T>(state: T) { let currentState = state const changeState = (newState: T) => { currentState = newState } // 约束返回值 const tuple: [T, (newState: T) => void] = [currentState, changeState] return tuple } const [counter, setCounter] = useState(10); setCounter(1000) const [title, setTitle] = useState("abc")
函数的类型约束
函数是JavaScript非常重要的组成部分,TypeScript允许我们指定函数的参数和返回值的类型。
函数类型的定义
type FooFnType = () => void function bar(fn: FooFnType) { fn() }
参数的类型注解
- 声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型。
- 当我们在定义函数参数的时候,必选参数不能位于可选参数后。
// 会报错 function foo(n1?: number, n2: string) { return n1 + n2 } foo(9, 'pp') // 但是对于定义对象类型的属性约束的时候,我们可以将可选参数写在任意位置,只是代表该属性可以不提供而已 type objType = { name?: string age: number }
- 当出现在高阶函数中的传入的函数参数,我们不需要指定类型注解,因为该函数的参数会自动指定类型
const names = ["abc", "cba", "nba"] // item根据上下文的环境推导出来的, 这个时候可以不添加的类型注解 // 上下文中的函数: 可以不添加类型注解 names.forEach(function(item) { })
- 当传入的类型是一个对象的时候
- 我们可以这样指定
function printPoint(point: { x: number; y: number }) { console.log(point.x) console.log(point.y) } printPoint({ x: 123, y: 321 })
- 我们也可以指定对象属性的可选性,通过
?
来标识
// 这里表示我们可以不传入z属性。 function printPoint(point: {x: number, y: number, z?: number}) { console.log(point.x) console.log(point.y) console.log(point.z) } printPoint({x: 123, y: 321}) printPoint({x: 123, y: 321, z: 111})
返回值的类型注解
- 声明函数时, 可以在函数列表的后面添加返回值类型注解。
- 和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型。 函数重载
函数重载的定义:
允许创建多个具有不同实现的同名函数。对重载函数的调用会运行其适用于调用上下文的具体实现,即允许一个函数调用根据上下文执行不同的任务。
- 多个函数定义使用相同的函数名称
- 函数参数的数量或类型必须有区别 在ts中实现函数重载和其他编程语言不同,它需要先定义函数的重载类型,然后再实现函数。
下面这是错误的实现重载的方式
// 错误实现重载函数 function p(n1: number, n2: number) { return n1 + n2 } function p(n1: string, n2: string) { return n1 + n2 } console.log(p(20, 30))
正确实现重载的方式
// 定义函数的重载类型 // 函数的重载: 函数的名称相同, 但是参数不同的几个函数, 就是函数的重载 function add(num1: number, num2: number): number; // 没函数体 function add(num1: string, num2: string): string; // 实现重载函数的内部逻辑 function add(num1: any, num2: any): any { if (typeof num1 === 'string' && typeof num2 === 'string') { return num1.length + num2.length } return num1 + num2 } const result = add(20, 30) const result2 = add("abc", "cba")
联合类型
有时候一个变量,他可以被赋值为多种类型的值,我们就可以使用|
来分割每种类型。
const id: number|string|boolean = 2; console.log('id', id)
其实上面讲到的可选类型,可以看做是 类型
和 undefined
的联合类型
message?: string ===> message: string | undefined
交叉类型
交叉类型表示需要满足多个类型的条件。我们就可以使用 &
来分割每种类型。
interface ISwim { swimming: () => void } interface IFly { flying: () => void } type MyType1 = ISwim | IFly type MyType2 = ISwim & IFly // 如果只实现了一个接口的方法,我们可以直接调用该方法,ts自动推断了类型。 const obj1: MyType1 = { // 这里可以实现两个接口,或者只实现其中一个接口 // flying() {}, swimming() {}, } const obj2: MyType2 = { // 两个接口的方法都得实现 swimming() {}, flying() {}, }
在开发中,我们进行交叉时,通常是对对象类型进行交叉的。合并对象
interface Colorful { color: string } interface IRun { running: () => void } type NewType = Colorful & IRun const obj: NewType = { color: 'red', running: () => {}, }
类型别名
有时候,我们写对象或者比较复杂的类型时。例如传入给一个函数作为参数,看起来比较不好理解,而且复杂。当多个地方都用到同样的对象结构的时候,我们需要写很多遍。所以,这时候我们就可以使用type
来为对象定义一个类型别名。让其可复用且容读。
type IDType = string | number | boolean type PointType = { x: number y: number z?: number } function printId(id: IDType) { } function printPoint(point: PointType) { }
类型断言
有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)。一般用在类中比较多。
class Person { } class Student extends Person { studying() { } } function sayHello(p: Person) { // 将Person类型转为Student类型 (p as Student).studying() } const stu = new Student() sayHello(stu)
TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换。
非空类型断言!
当我们编写下面的代码时,在执行ts的编译阶段会报错:这是因为传入的message有可能是为undefined的,这个时候是不能执行方法的;
function printMessageLength(message?: string) { console.log(message.length) } printMessageLength()
但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言:非空断言使用的是 !
,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测。如果没有传值,执行是依旧报错。
function printMessageLength(message?: string) { console.log(message!.length) } printMessageLength() // 依旧报错,只有我们调用函数都传入值的时候,才不会报错。
这时候,我们就可以使用es11中可选链使用可选链操作符 ?.
, 它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行。
function printMessageLength(message?: string) { console.log(message?.length) } printMessageLength() // 不会报错,返回undefined。
既然讲到了?.
,那我们就再来看看??
的作用吧?
他是es11新增的。空值合并操作符(??)是一个逻辑操作符,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。其实他的效果和||一样,但是||的使用本身用在if判断中的,所以以后可以用??来代替||做短路运算。
let message: string | null = 'Hello World' const content1 = message ?? '你好1' const content2 = message ? message : '你好2' const content3 = message || '你好3' console.log(content1) console.log(content2) console.log(content3)
字面量类型
除了前面我们所讲过的类型之外,也可以使用字面量类型(literal types)。这个类型再能赋值指定的字面量作为值。 他一般和联合类型使用才有意义。
// 字面量类型的意义, 就是必须结合联合类型 type Alignment = 'left' | 'right' | 'center' let align: Alignment = 'left' align = 'right' align = 'center'
类型缩小
什么是类型缩小呢?
我们可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径。在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小。而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards)。
常见的类型保护有如下几种:
- typeof
// 1.typeof的类型缩小 type IDType = number | string function printID(id: IDType) { if (typeof id === 'string') { console.log(id.toUpperCase()) } else { console.log(id) } }
- 平等缩小(比如===、!==)
type Direction = 'left' | 'right' | 'top' | 'bottom' function printDirection(direction: Direction) { // 1.if判断 if (direction === 'left') { console.log(direction) }else if() { ... } // 2.switch判断 switch (direction) { case 'left': console.log(direction) break case 'right': console.log(direction) break case 'top': console.log(direction) break case 'bottom': console.log(direction) break } }
- instanceof, 一般用于类中的判断
class Student { studying() {} } class Teacher { teaching() {} } function work(p: Student | Teacher) { if (p instanceof Student) { p.studying() } else { p.teaching() } } const stu = new Student() work(stu)
- in
// 4. in type Fish = { swimming: () => void } type Dog = { running: () => void } function walk(animal: Fish | Dog) { if ('swimming' in animal) { animal.swimming() } else { animal.running() } // (animal as Fish).swimming() } const fish: Fish = { swimming() { console.log('swimming') }, } walk(fish)