3、TypeScript
基础语法
3.1、变量声明
我们已经多次强调、在TypeScript
中定义变量需要指定标识符的类型、完整的格式如下
var/let/const 标识符:数据类型=赋值;
声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解(Type Annotation)
比如我们声明一个message,完整的写法如下:
//注意:这里的string是小写的,和String是有区别的
//string是TypeScript中定义的字符串类型,String是ECMAScript中定义的一个类
let message:string="Hello World"
如果我们给message赋值其他类型的值,那么就会报错
3.2、声明变量的关键字
在TypeScript定义变量(标识符)和ES6之后一致,可以使用var
、let
、const
来定义。
var myname:string="why";
let myage:number="20";
const myheight:number=1.88;
当然,在tslint
中并不推荐使用var
来声明变量:
可见,在TypeScript中并不建议再使用var
关键字了,主要原因和ES6升级后let
和var
的区别是一样的,var
是没有块级作用域的,会引起很多的问题,这里不再展开探讨
3.3、标识符的类型推导
在开发中时、为方便起见我们并不会在声明每一个变量时都写上对应的数据类型,我们更希望可以通过TypeScript本身的特性帮助我们推断出对应的变量类型。
那什么是类型推导?
声明一个标识符时,如果有直接的进行赋值、会根据赋值的类型推导出标识符的类型注解,这个过程被称之为类型推导
Let
进行类型推导,推导出来的通用类型
const
进行类型推导,推导出来的是字面量类型,某一个值也可以被当成标识符的类型
什么场景下使用?
1、声明变量并初始化时,2、决定函数的返回值时
// 变量 age 的类型被自动推断为:number
let age = 18
// 函数返回值的类型被自动推断为:number
function add(num1: number, num2: number) {
return num1 + num2
}
类型推断的好处?
由于类型推论的存在,这些地方,类型注解可以省略不写
能省略类型注解的地方就省略(偷懒,充分利用TS类型推论的能力,提升开发效率)
如果不知道类型,可以通过鼠标放在变量名称上,利用VSCode
的提示来查看类型
官方的说法?
记住、我们并不总是需要编写显式的类型注释,在许多情况下,TypeScript甚至可以推断(或者算出)为我们提供的类型,即使我们省略它们
3.4、JavaScript
和TypeScript
的数据类型
我们常说、TypeScript是JavaScript的一个超集
3.5、number
类型
数字类型是我们开发中经常使用的类型,TypeScript
和JavaScript
一样,不区分整数类型(int
)和浮点型(double),统一为number类型
let num =100
num=20
num=6.6
学习过ES6应该知道,ES6新增了二进制和八进制的表示方法,而TypeScript也是支持二进制、八进制、十六进制的表示
num=100; //十进制
num=0b110; //二进制
num=0o555; //八进制
num=0xf23; //十六进制
3.6、boolean
类型
boolean
类型只有两个取值:true和false,非常简单
//boolean类型的表示
let flag:boolean=true;
flag=false;
flag=20>30;
3.7、string
类型
string类型是字符串类型,可以使用单引号或者双引号表示:
同时也支持Es6的模版字符串来拼接变量和字符串
3.8、Array
类型
数组类型的定义也非常的简单,有两种方式
//正常的写法
const names:string[]=["abc","cba","cba"]
//泛型写法
const name2:Array<string> =["abc","cba","mba"]
nases.push("why")
name2.push("why")
如果添加其他类型到数组中,那么会报错:
3.9、Object
类型
//1、没有经过类型推导的Ts写法
let info1:{
name:string
age:Number
height:Number
} = {
name: "why",
age: 18,
height: 1.88
}
console.log(info.name)
console.log(info.age)
//2、经过类型推导的简写形式
let info2 = {
name: "why",
age: 18,
height: 1.88
}
console.log(info.name)
console.log(info.age)
//3、Type关键字的使用
type InfoType={
name:String
age:number
}
let infoType:InfoType={
name:"侯茂昌",
age:18
}
//4、直接告诉他是一个对象类型
//但是这样做非常的不好、这个的写法从ts角度来说他不是一个错误的ts语法
//但是从开发的角度并不会这样做、因为当你给一个东西指定为一个object类型的时候
//意味这object表示是一个空对象类型
let infoObject:object={
name:"why",
age:18
}
//获取值
console.log(infoObject.name) //会报错
//获取值
console.log(infoObject["name"]) //会报错
//设置值
infoObject["name"]="hmc" //会报错
export {}
3.1、Symbol
类型
在Es5当中、如果我们是不可以在对象中添加相同的属性名称的,比如以下的做法
const person={
identity:"程序员"
identity:"老师"
}
通常我们 的做法是定义两个不同的属性名字、比如identity1、identity2
但是我们也可以通过symbol来定义相同的名称、因为Symbol函数返回的是不同的值
const s1:symbol=Symbol("title")
const s2:symbol=Symbol("title")
const:person={
[s1]:"程序员"
[s2]:"老师"
}
3.2、null和undefined类型
在JavaScript中,undefined和null是两个基本数据类型
在TypeScript中、他们各自的类型也是undefined和null,也就意味这他们既是实际的值、也是自己的类型
let n:null=null
let u:undefined=undefined
3.3、函数的参数类型
函数是JavaScript非常重要的组成部分,TypeScript允许我们指定函数的参数和返回值的类型
参数的类型注解
声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型:
function greet(name:string){
console.log("Hello"+name.toUpperCase())
}
greet(123)
greet("abc","cba")
3.4、函数的返回值类型
我们也可以添加返回值的类型注解,这个注解出现在函数列表的后面:
// 在定义一个TypeScript中的函数时
// 返回值类型可以明确的指定, 也可以自动进行类型推导
function sum(num1: number, num2: number): number {
return num1 + num2
}
和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型:
某些第三方库处于方便理解,会明确指定返回类型,看个人喜好
3.5、匿名函数的参数
什么是匿名函数?
结论:一个函数作为一个参数传递给了另一个参数
匿名函数是否需要添加类型注解呢?
结论:最好不要加类型注解
·如下代码
const names=["abc","cba","nba"]
//item,index.arr最好不要添加类型注解、有可能会一不小心添加错了、他自己会根据上下文进行类型推导
names.forEach(funcation(item,index,arr)){
console.log(item,index,arr)
})
const names=["abc","cba","nba"]
names.forEach(item=>{
console.log(item.toUppercase())
})
我们并没有指定item的类型,但是item是一个string类型
这是因为TypeScript会根据forEach函数的类型以及数组的类型推断出item的类型;
这个过程称之为上下文类型(contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型;
3.6、对象类型
如果我们希望限定一个函数接受的参数是一个对象,这个时候要如何限定呢?
function printCoordinate(point:{x:number,y:number}){
console.log("x坐标",ponint.x)
console.log("y坐标",ponint.y)
}
printCoordinate({x:10,y:30})
上述代码,我们使用了一个对象来作为类型
在对象中我们可以添加属性,如{ x:number , y:number},并且告知TypeScript该属性需要是什么类型,属性之间可以使用 , 或者 ; 来分割,最后一个分隔符是可选的;每个属性的类型部分也是可选的,如果不指定,那么就是any类型;
3.7、对象类型参数的可选类型
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?
function printCoordinate(point:{x:number,y:number,z?:number}){
console.log("x坐标:",point.x);
console.log("y坐标:",point.y)
if(ponit.z){
console.log("z坐标:",point.z)
}
}
printCoordinate({x:10,y:30})
printCoordinate({x:20,y:30,z:40})
3.8、any
类型
在某些情况下我们无法确定一个变量的类型、并且可能会发生一些变化、这个时候我们可以使用any类型,有点类似于Dart语言中的dynamic类型
any类型有点像一种讨巧的TypeScript手段
我们可以对any类型的变量进行任何操作、包括获取不存在的方法、属性
我们可以给一个any类型的变量赋值任何的值、比如 数字、字符串、boolean的值
let a:any="why"
a=123
a=true
const aArray : any[]={"why",18,1.88}
如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者引入一些第三方库时,缺失了类型注解,这个时候我们可以使用any
包括在Vue源码中,也会使用到any来进行某些类型的适配
3.9、unknown
类型
在TypeScript中,unknown
类型是一种非常特殊的顶级类型,它表示的是所有类型的联合,但与any
类型不同的是,unknown
提供了更强的类型安全性。当一个值被标记为unknown
时,编译器会严格限制对该值的操作:
村夫解释:
nnknown是TypeScript中比较特殊的一种类型、他通常用于描述类型不确定的变量、
跟any有点类似,但是unknown类型上做任何事情都是不合法的?
直接访问其属性报错
不能直接访问其属性或方法,因为编译器无法确定未知类型的对象具体包含哪些属性和方法。
let value: unknown = { message: "Hello" };
// 错误:Property 'message' does not exist on type 'unknown'.
console.log(value.message);
不能将其作为函数参数传递给需要特定类型的位置,除非目标参数接受unknown
或者使用类型断言转换成兼容类型。
使用typeof
或者instanceof细化类型 、进行类型缩小
function printMessage(message: string) {
console.log(message);
}
// 错误:Argument of type 'unknown' is not assignable to parameter of type 'string'.
printMessage(value);
就是防止sb瞎写、用之前必须先使用关键字进行类型缩小、这样更加安全一点、any类型
3.1、Ts中的类型 void类型
void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型
function sum(num1: number, num2: number){
console.log(num1 + num2)
}
这个函数我们并没有给他写任何类型、那么它默认返回值的类型就是void的、我们也可以显示的来指定返回值是void类型
function sum(num1: number, num2: number): void {
console.log(num1 + num2)
}
这里还有一个注意事项:
我们可以将undefined赋值给void类型,也就是函数可以返回undefined
function sum(num1: number, num2: number): void {
console.log(num1 + num2)
return undefined
}
当基于上下文的类型推导(Contextual Typing)推导出返回类型为 void 的时候,并不会强制函数一定不能返回内容
type FnType=()=>void
const foo:FnType=()=>{
return 123
}
const names=["abc","cba","nba"]
names.forEach(item=>{
item.length
})
const names = ["abc", "cba", "nba"]
// 了解即可: 基于上下文类型推导的函数中的返回值如果是void类型, 并且不强制要求不能返回任何的东西
names.forEach((item: string, index: number, arr: string[]) => {
console.log(item)
return 123
})
小总结
1、在TS中如果一个函数没有任何的返回值, 那么返回值的类型就是void类型
2、如果返回值是void类型, 那么我们也可以返回undefined(TS编译器允许这样做而已)
3、上述的匿名函数的类型是经过上下文进行类型推导推导出的void类型、并没有写死,你会发现也是可以返回数据的、但是会被忽略掉、并有没有任何意义(了解即可)
3.2、Ts中的类型 never类型
1、never类型的应用场景
1、开发中很少实际去定义never类型,某些情况下会自动进行类型推导出never
2、开发框架(工具)的时候可能会用到never
3、封装一些类型工具的时候可以使用never,(后面会讲一些类型体操的题目:never)
2、什么是never类型
never表示永远不会发生值的类型,比如一个函数
- 如果一个函数中是一个死循环或者抛出一个异常,那么这个函数会返回东西吗?
- 不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型;
//一、实际开发中只有进行类型推导时, 可能会自动推导出来是never类型, 但是很少使用它
//1、一个函数是死循环
funcation loopFun():never{
while(true){
console.log("-------")
}
}
foo()
//2、抛出异常
function loopErr():never{
throw new Error()
} //----------最新的版本可能有变化、本来经过类型推导应该推导出来的是never类型、但是现在看却是void类型的
//3.解析歌词工具
function parseLyric(){
return [] //这个函数的返回值就是 function parseLyric():never[],表示这个里边永远不会放任何东西
}
注意:实际开发中我们并不会使用这个never类型,只有进行类型推导时可能会推导出来是never类型
// 二、在封装框架/工具库的时候可以使用一下never类型 (之前尤雨溪、知乎上回答了TypeScript里面的never类型到底有什么用,尤大回复了一个跟官方案例差不多的答案)
// handleMessage 这个方法是A工作人员开发的、B开发要用、我传一个 true,发现由于类型限制报错、那么直接在工具类上加一个联合类型|boolean,好了调用不报错了
// 但是如果我们没有处理这个case的、通过const check:never=message 检测出我们并没有处理这种参数的case运算
function handleMessage(message: string | number | boolean) {
switch (typeof message) {
case "string":
console.log(message.length)
break
case "number":
console.log(message)
break
case "boolean":
console.log(Number(message))
break
default: //其他时候在扩展工具的时候, 对于一些没有处理的case, 可以直接报错(check就报红线了、加一个case分支处理下就好了)
const check: never = message
}
}
handleMessage("aaaa")
handleMessage(1234)
// 另外同事调用这个函数
handleMessage(true)
业务开发、基本很少定义never 类型、但是写框架,工具类可能会用到
3.3、Ts中的类型 tuple类型
发音:tepou
元组类型在某些编程语言里边它是有的比如Python、swift等、所谓元组是多个元素组合在一起的意思。
元组类型和数组类型还是有点相似的、区别在于他可以存放不同的数据类型、并且每个位置的数据类型都是明确的
题目:假如我们现在需要保存我们个人信息 why 18 1.88,我想要用一种数据结构把他保存起来
// 1.使用数组类型
// 不合适: 数组中最好存放相同的数据类型, 获取值之后不能明确的知道对应的数据类型
const info1: any[] = ["why", 18, 1.88]
const value = info1[2]
console.log()
// 2.使用对象类型(最多)
// 我为了保存值、我必须写对应的key,增加了我们的带码量、其实也算不上大的缺点(总不能一行也不写就实现功能)
const info2 = {
name: "why",
age: 18,
height: 1.88
}
// 3.使用元组类型
// 元组数据结构中可以存放不同的数据类型, 取出来的item也是有明确的类型
// 元祖类型、即有类型同时有没有强制增加一些key、通过编译后就没有key了,所以元祖类型相当与是一种介于数组和介于对象之间的一种,综合一点保存多种数据的一种类型,
// 如果你现在有一些数据、并且这些数据、你现在不希望用对象存储,同时你觉得,他的数据类型也不一样,有不想用数组来保存数据、这个时候就可以使用元祖类型
const info3: [string, number, number] = ["why", 18, 1.88]
const value2 = info3[2]
//4.tuple通常可以作为返回的值,在使用的时候会非常的方便; 有泛型的写法暂时用any代替
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)
console.log(count)
setCount(100)
面试题:元组类型和数组类型有什么区别?
首先,数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中)
其次,元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型;
const info:(string|number)[]=["why",18,1.88]
const item1=info[0] //不确定的类型
const tInfo:[string,number,number]=["why",18,1.88]
const item2=tInfo[0] //一定是string类型