前言
TS的编译环境
- 安装命令
npm install typescript -g
- 查看版本
tsc --version
ts入门案例:通过
tsc _文件名_
,最终生成对应的.js文件。
let message: string = "Hello"
console.log(message);
// TS中的每个文件都是一个独立的模块,如果不加上模块,默认就是在全局环境
// 当我tsc生成js文件时,就会有两个相同的message,那么就会报错
// 写上 export{} 后,就表示这个文件是一个独立的模块
export { }
- TS中的每个文件都是一个独立的模块,如果不加上模块,默认就是在全局环境
- 当我tsc生成js文件时,就会有两个相同的message,那么就会报错
- 写上
export{}
后,就表示这个文件是一个独立的模块
由于浏览器不认识ts文件,所以需要通过tsc命令生成js代码,这样浏览器才能识别,上述是第一种编译方法
使用ts-node
- 安装ts-node
- npm install ts-node -g
- 另外ts-node需要依赖 tslib 和 @types/node 两个包:
- npm install tslib @types/node -g
- 现在,我们可以直接通过 ts-node 来运行TypeScript的代码:
- ts-node math.ts
在项目中,我们使用脚手架搭建的时候,webpack就会帮我们配置好对应的loader,所以不用着急。
变量的声明
不推荐var,推荐let;注意类型不要写成大写的,大写的是类!!!
let message: string = '消息'
const name: string = 'coder'
const age: number = 18
console.log(message, name, age);
export { }
变量的类型推导(推断)
- 声明一个标识符时,如果有直接进行赋值的话,TS会根据赋值的类型,自动推导出标识符的类型注解;
- 这个过程称之为:类型推导
- let进行类型推导,推导出来的是通用类型;
- const进行类型推导,推导出来的是字面量类型(后续会说);
数据类型:
number类型
数字类型是我们开发中经常使用的类型, TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为 number类型
。
学习过ES6应该知道, ES6新增了二进制和八进制的表示方法,而TypeScript也是支持二进制、八进制、十六进制的表示:
boolean类型
boolean类型只有两个取值: true和false,非常简单
string类型
string类型是字符串类型,可以使用单引号或者双引号表示:
Array类型
数组类型的定义也非常简单,有两种方式:
Array<string>
事实上是一种泛型的写法,我们会在后续中学习它的用法;
// 明确的指定数组的类型注解:有2种写法:并且在真实开发中,数组中一般存放相同的类型!!!
// 1. string[] : 数组类型,并且数组中存放的是字符串类型
let names: string[] = ['abc', 'cba', 'nba']
// 2. Array<string> : 数组类,并且指定存放什么类型,这个是泛型的写法,后续会说
let name: Array<string> = ['aaa', 'bbb', 'ccc']
export { }
明确的指定数组的类型注解:有2种写法:并且在真实开发中,数组中一般存放相同的类型!!!
string[]
: 数组类型,并且数组中存放的是字符串类型;Array<string>
: 数组类,并且指定存放什么类型,这个是泛型的写法,后续会说
object类型
我们在开发中,会使用type来指定对象类型,在这里我们暂时先用类型推断实现,并且我们是不会用这种写法定义对象类型的:
因为指定:object
表示是一个空对象类型,因此我们通过info.name
取值的时候,是取不到的!!!需要我们明确指定类型;
Symbol类型(用的很少)
null和undefined类型
在 JavaScript 中, undefined 和 null 是两个基本数据类型。
在TypeScript中,它们各自的类型也是undefined和null,也就意味着它们既是实际的值,也是自己的类型:
let n: null = null
let u: undefined = undefined
函数方面的类型
函数的参数类型
函数是JavaScript非常重要的组成部分, TypeScript允许我们指定函数的参数和返回值的类型。
参数的类型注解
- 声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型:
function sum(num1: number, num2: number) {
return num1 + num2
}
sum(123, 321)
export { }
如果我们这里参数不指定类型,ts会类型推断吗?
答案是不会!因为函数是被调用的,我们可以传递任意类型的值,可以是字符串、数字,所以这里需要我们手动指定类型;
函数的返回值类型
我们也可以添加返回值的类型注解,这个注解出现在函数列表的后面:
和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型:
- 某些第三方库处于方便理解,会明确指定返回类型,看个人喜好;
function sum(num1: number, num2: number): number {
return num1 + num2
}
sum(123, 321)
export { }
匿名函数的参数
匿名函数与函数声明会有一些不同:
- 当一个函数出现在TypeScript可以确定该函数会被如何调用的地方时;
- 该函数的参数会自动指定类型;
const names = ['aaa', 'bbb', 'ccc']
// forEach接收函数的3个参数:每一项值、索引、数组本身
// 匿名函数是否需要加类型注解呢?最好不要,因为匿名函数会根据forEach传递过来函数的类型进行推断
names.forEach(function (item, index, arr) {
})
export { }
我们并没有指定item的类型,但是item是一个string类型:
- 这是因为TypeScript会根据forEach函数的类型以及数组的类型推断出item的类型;
- 这个过程称之为上下文类型(contextual typing) ,因为函数执行的上下文可以帮助确定参数和返回值的类型;
一般情况下,手动定义的函数,都是没有上下文的,就需要手动指定类型;而匿名函数是传递给别人的,别人在使用匿名函数的时候,会给出数值,TS就会进行推断。
对象类型
如果我们希望限定一个函数接受的参数是一个对象,这个时候要如何限定呢?
- 这个时候可以使用对象类型;
// 对象类型和函数类型的结合使用
// 传进来一个point对象,该对象里面的参数是x、y并且是number类型,并且一定可以取到
function printCoordinate(point: { x: number, y: number }) {
console.log("x坐标:", point.x);
console.log("y坐标:", point.y);
}
printCoordinate({ x: 30, y: 40 })
export { }
但是我们一般使用type来定义对象类型:
type PointType = { x: number, y: number }
function printCoordinate(point: PointType) {
console.log("x坐标:", point.x);
console.log("y坐标:", point.y);
}
在这里我们使用了一个对象来作为类型:
- 在对象我们可以添加属性,并且告知TypeScript该属性需要是什么类型;
- 属性之间可以使用
,
或者;
来分割,最后一个分隔符是可选的:(point: { x: number;y: number })
- 每个属性的类型部分也是可选的,如果不指定,那么就是any类型;
可选类型
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?
type PointType = { x: number, y: number, z?: number }
function printCoordinate(point: PointType) {
console.log("x坐标:", point.x);
console.log("y坐标:", point.y);
}
printCoordinate({ x: 30, y: 40 })
printCoordinate({ x: 10, y: 20, z: 30 })
export { }
调用printCoordinate
,这两个都不会报错,因为z设置了可选类型,可以传,也可以不传;
TypeScript扩展类型
any类型
在某些情况下,我们确实无法确定一个变量的类型, 并且可能它会发生一些变化,这个时候我们可以使用any类型(类似于Dart语言中的dynamic类型)。
any类型有点像一种讨巧的TypeScript手段:
- 我们可以对any类型的变量进行任何的操作,包括获取不存在的属性、方法;
- 我们给一个any类型的变量赋值任何的值,比如数字、字符串的值;
let id: any = 123
id = "aaaaa"
id = {
name: '哈哈',
age: 123
}
const infos: any[] = [123, "abc", {}]
如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候我们可以使用any:
- 包括在Vue源码中,也会使用到any来进行某些类型的适配;
unknown类型
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。
- 和any类型有点类似,但是unknown类型的值上做任何事情都是不合法的;
可以给unknown类型赋任何类型的值,但是不可以对他进行操作,比如不能取长度,因为我不知道你是什么类型,我怎么知道你有没有length这个属性?
他的应用场景是:必须进行类型缩小(校验类型),就可以使用了
let foo: unknown = 'aaa'
// 必须要校验类型,typeof实现:类型缩小;
if (typeof foo === "string") {
console.log(foo.length);
}
void类型
void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型:
function sum(n1: number, n2: number) {
// 没有return返回,而是直接log打印,那么默认就是void类型
console.log(n1 + n2);
}
function sum(n1: number, n2: number):void {
console.log(n1 + n2);
return undefined;
}
如果返回值是void类型,那么我们也可以返回undefined,TS编译器是允许我们这样做的。
这个函数我们没有写任何类型,那么它默认返回值的类型就是void的,我们也可以显示的来指定返回值是void:
定义函数类型的时候,需要使用=>
,某些时候,使用:
type LyricInfoType = {time:number,text:string}
function parseLyric(lyric:string):LyricInfoType[]{
const lyricInfos:LyricInfoType[] = []
return lyricInfos
}
export {}
实际上,parseLyric
函数,本质上也是一个对象(因为函数和对象具有相同特性),它本身也是有类型的:
应用场景:用来指定函数类型的返回值是void;
const name: stirng = "aaa"
const foo = () => {
}
-----------------------
const foo:()=>void = () =>{
}
这样看会更直观一点:foo函数是一个没有参数、 没有返回值的函数,然后这个函数返回的是一个无参箭头函数;这种代码阅读性非常的差,我们会通过类型别名来实现:
type FooType =() => void
const foo: FooType =() =>{
...
}
应用场景:定义一个要求传入函数类型时,会使用到;
这里指定delay的参数,接收的必须是一个函数:
function delay(fn:()=>void){
fn()
}
delay(()=>{
})
同时也可以接收多个函数作为参数type xxx = (...args:any[])=>void
type ExecFnType = (...args: any[]) => void
// 定义一个函数,并且接收的参数也是一个函数,而且这个函数的类型必须是ExecFnType
function delayExecFn(fn:ExecFnType){
fn("why",18)
}
// 执行上面函数,并且传入一个匿名函数
delayExecFn((name,age)=>{
})
总结:也就是说,void用来指定是一个函数类型;在某些参数中需要传入函数时,会用到!
在进行类型推导的时候,如果很不巧,TS推导出这个函数的返回值是一个void,虽然明面上好像不可以返回内容,但是实际上,TS不会强制函数,一定不可以返回,还是可以返回的(不建议),但是仅限于类型推导出返回值是void;
never类型
开发中很少实际的去定义never类型,某些情况下会自动进行类型推导,得出never类型;
never 表示永远不会发生值的类型,比如一个函数:
- 如果一个函数中是一个死循环或者抛出一个异常,那么这个函数会返回东西吗?
- 不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型;
// 1.抛异常
function foo(): never {
throw new Error("111");
}
foo()
// 2.永远不会发生值
function parseLyric() {
return []
}
never有什么样的应用场景呢?
- 这里我们举一个例子,但是它用到了联合类型,后面我们会讲到:
- 以及开发框架的时候或者封装工具的时候,都是会用到never的。
function handleMessage(message: string | number) {
switch (typeof message) {
case "string":
console.log(message.length);
break
case "number":
console.log(message);
break
default:
const check = message
}
}
实际上default中的语句,是永远不会被执行到的,因为一旦不是string或number类型,就根本不会执行这个函数;那么此时这个check就是never类型。
这里如果不实现boolean的case的话,message的值就会被赋值,但是never的意思是不可能有值的,所以在其他时候在扩展工具时,对于一些没有处理的case,此时使用never就可以提醒开发人员,书写对应的逻辑代码,不实现逻辑则会报错。
function handleMessage(message: string | number | boolean) {
switch (typeof message) {
case "string":
console.log(message.length);
break
case "number":
console.log(message);
break
default:
const check:never = message
}
}
tuple类型
tuple是元组类型,很多语言中也有这种数据类型,比如Python、 Swift等
那么tuple和数组有什么区别呢?
- 首先, 数组中通常建议存放相同类型的元素, 不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中)
- 其次, 元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型;
元组中每个索引的值都有特定的类型:
// 数组:
const info1: any[] = ['why', 18]
// 使用对象类型(用的最多)
const info2 = {
name: 'why',
age: 18
}
// 元组:我能百分百确定第一项的类型是string,第二项的类型是number
const tInfo: [string, number] = ['why', 18]
元组数据结构中,可以存放不同的数据类型,取出来的item也是具有明确数据类型的。
那么tuple在什么地方使用的是最多的呢?
- tuple通常可以作为返回的值,在使用的时候会非常的方便;
function useState(initialState: number): [number, (newValue: number) => void] {
let stateValue = initialState
function setValue(newValue: number) {
stateValue = newValue
}
return [stateValue, setValue];
}
const [count, setCount] = useState(10)
export { }
指定这个函数的返回值是一个元组,并且第一项是number类型,第二项是一个函数,并且这个函数还有一个参数,类型是number类型。当我们使用useState方法时,我们就会知道count是一个number类型,而setCount是一个具有number参数的函数。