文章目录
- 一、认识TS
- 1.1 JS 存在的问题
- 1.2 TS的出现
- 1.3 TS运行环境
- 运行ts的三种方式
- 1.4 变量声明
- 1.5 类型推断
- 二、数据类型
- 2.1 JS数据类型
- (1) 数组Array
- (2) 对象Object
- (3) 其他类型
- 2.2 TS特有数据类型
- (1) any类型
- (2) unknown类型
- (3) void类型
- (4) never (了解)
- (5) tuple类型
- 三、语法细节
- 3.1 可选类型
- 3.2 联合类型
- 3.3 type与interface
- (1) 类型别名 type
- (2) 声明接口 interface
- (3) 区别
- 3.4 交叉类型
- 3.5 类型断言as
- 3.6 字面量类型
- 3.7 类型缩小 (Type Narrowing)
一、认识TS
1.1 JS 存在的问题
JS没有类型检测。
function getLength(str){
return str.length;
}
console.log(getLength("abc")); // 正确调用,结果是3
console.log(getLength(123)); // 错误的调用,结果是undefined;
第五行代码在编写时不会报错,但是在运行过程中可能会出现TypeError: Cannot read property 'length' of undefined
这类的错误。这是因为JS并不会对函数传入的参数进行限制,所以这个问题只会在运行时才被发现。进而影响后续代码的执行;
所以没有类型监测带来的问题:
- 代码不够安全、不够健壮。
- 没有类型约束,那么需要对别人传入的参数进行各种验证来保证我们代码的健壮性
- 我们去调用别人的函数,对方没有对函数进行任何的注释,我们只能去看里面的逻辑来理解这个函数需要传入什么参数, 返回值是什么类型;
1.2 TS的出现
TypeScript官网:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
TypeScript是拥有类型的JavaScript超集,它可以编译成普通、干净、完整的JavaScript代码。
TS是加强版的JS,具有以下特点:
- JavaScript所拥有的特性,TypeScript全部都是支持的,
- 紧随ECMAScript的标准,所以ES6、ES7、ES8等新语法标准,它都是 支持的;
- TS增加了类型约束
- 不必考虑兼容性问题。TS最终会被编译成JS代码,编译时可以通过tsc或babel将其转为js(始于JS,归于JS)
1.3 TS运行环境
在.ts文件里编写TS不会出错,但是编译时需要搭建对应的环境,将其编译为JavaScript,才能够在浏览器上运行。
# 全局安装最新版本的 ts
npm install typescript -g
# 检查版本,查看是否安装成功
tsc --version
运行ts的三种方式
(1) 方式一:
第一步: tsc 1.hello.ts
; 编译ts文件,生成1.hello.js文件
第二步:运行.js文件
每次修改完文件后,都要单独执行命令进行编译,然后再运行。很麻烦。
(2) 方式二:
- webpack配置(用脚手架创建文件的时候都会配置好,这里不细说)
(3) 方式三:
- 通过ts-node库 (接下来学习用这种方式来运行ts文件,)。
# 安装ts-node
npm install ts-node -g
# 安装ts-node需要的两个依赖包:tslib和@types/node
npm install tslib @types/node -g
# 运行ts文件,这种方式也不需要每次修改完之后单独执行命令进行编译,直接执行即可
ts-node hello.ts
注意:在使用ts-node运行代码时,控制台报错:Cannot find module ‘./hello.ts’
解决方法:添加export {}
// hello.ts
let message: string = "hello world"
console.log(message)
export {}
博客:使用ts-node命令运行ts文件时报错
1.4 变量声明
声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解(Type Annotation);
(指明数据类型就是添加类型注解)
var/let/const 标识符:数据类型 = 赋值
将变量message声明为string类型:
let message:string = 'hello'
message = 123 // 将其他类型赋值给message会报错:不能将类型"number"分配给类型"string"
1.5 类型推断
类型推断:声明一个变量时,如果直接进行赋值, 会根据赋值的类型推导出变量(标识符)的类型注解。
let name='tom' // 推导出name的类型为string
name=123 // 这句赋值操作会报错:不能将“number”分配给类型"string"
let num = 123
const msg = 123 // 推导出msg的类型为字面量类型123
const test:'Hello'='Hello' // 这里'Hello' 也是字面量类型
注:
- let声明的变量进行类型推导,推导出来的是通用类型(number、string之类的)
- const声明的变量进行类型推导,推导出来的是字面量类型
二、数据类型
TS是JS的一个扩展,原来JS拥有的东西,TS都有,包括JS里的八种数据类型,TS也是可以使用的。
接下来演示一下如何使用各种数据类型进行数据注解。
2.1 JS数据类型
(1) 数组Array
指定数组的类型注解,有两种写法
(1) string:[]
——数组类型,并且数组里只能存string类型数据
const msg:string[]=["123","345","567"]
msg.push('hello')
(2) Array<string>
——泛型写法,
const names :Array<string>=["tom","jerry"]
names.push(876) // 会报错
简写就是:
const name = ["tom","Lily","张三"]
name.push("jerry")
name.push(123) // 会报错
(2) 对象Object
info对象的类型就是红框里的样子
明确指定为对象类型的完整写法为:
const info:{
name:string,
age:number
}={
name:'tom',
age:18
}
还有一种写法,info:object={...}
,缺点是不能读取对象内的属性
(3) 其他类型
number类型:
// number数据类型,ts与js一致,不区分整数和浮点数
let num = 6.66;
num = 'tom' // 会报错
// ES6新增的各个进制的表示方法,ts也适用
num = 10; // 十进制
num = 0b110; // 二进制
num = 0o555; // 八进制
num = 0xf23; // 十六进制
boolean类型
let flag = true
flag =20 // 会报错
string类型
const name = "tom"
// ts也可使用ES6的模板字符串来拼接变量和字符串
const info = `my name is ${name}`
null与undefined类型
// 完整写法
let n:null = null
let u:undefined = undefined
// 简写
let n = null
let u = undefined
2.2 TS特有数据类型
(1) any类型
在某些情况下,无法确定某些变量的类型,且该变量的类型可能会发生改变时,可采用any类型。
let id:any = 987
id = 'tom'
id = {name:'why',age:10}
// 若需定义一个数组,但不确定元素类型,可定义为any:
const infos:any[] = ['123',123,{}]
这样其实是回归到原始的js了。
应用场景:
如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候我们可以使用any。
(2) unknown类型
与any作用类似,在不确定类型的情况下也可以声明为unknown类型,区别是:
unknown类型的变量,不允许进行任何操作
let num:any=123
num = '123'
console.log(num+123); // any类型数据,不报错
let result:unknown='tom'
result = 123 // 将number类型数据赋值给result不会报错
console.log(result.length); // 读取length属性,报错
因为不确定result的什么类型,读取length可能会出错,所以unknown类型数据不允许进行任何操作。
若想进行操作,则必须进行类型校验:
let result:unknown='tom'
result = 123
// 必须进行类型校验(类型缩小),才能根据缩小之后的类型,进行对应的操作
if (typeof result === 'string') {
console.log(result.length);
}
(3) void类型
- 当一个函数没有return返回值的时候,其返回值就是void类型
// 未指明返回值类型,默认
function printID(id:string){
console.log(id);
}
- 明确指明返回值类型是void;
function printID(id:string):void{
console.log(id);
return undefined // 对于返回值为void类型的函数,可以return undefined;
// return 123 会报错,不能将number类型分配给void类型
}
-
(了解)基于上下文类型推导的函数,推导出返回类型是void时,并不会强制函数一定不能返回内容
但如果是自己编写的函数,明确指定是void类型,图片里13行的代码则会报错
(4) never (了解)
never 表示永远不会发生值的类型。开发中很少定义never类型。
应用场景: 进行类型推导时,可能会自动推导出never类型
function handleMsg(msg:string | number){
switch(typeof msg){
case "string":
console.log(msg.length);
break;
case "number":
console.log(msg+10);
break;
default:
const check = msg // const check: never
}
}
msg只可能是string或number类型,default语句不可能被执行到,所以check被推导为never类型。
(5) tuple类型
tuple元组类型。元组数据结构可以存放不同的数据类型,取出来的item也是有明确的类型
const msg:(string|number)[]=['tom',123,456]
const name = msg[0] // name类型是string|number,不能明确
const info:[string,number,boolean]=["why",18,true] // 元素的类型应与声明的一一对应,比如第二个必须是number类型的数据,否则报错
const value = info[2] // value类型是boolean
- tuple和数组的区别:
- 数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。
- 元组中每个元素都有自己特定的类型,根据索引值获取到的值可以确定对应的类型;
元组类型的应用:在函数中使用最多,尤其是函数的返回值中
// 将函数会返回值声明为tuple类型
function useState(initialValue:any):[number,(newValue:number)=>void]{
let stateValue = initialValue
function setValue(newValue:any){
stateValue = newValue
}
return [stateValue,setValue]
}
const [count,setCount] = useState(10)
count() // 这行代码会报错,count类型是number,错误的使用会及时提醒
setCount(100)
但若未明确指定返回值的类型,则推导为any类型的数组:
此时count()
语句不合法,但没有错误提示,则代码是不安全的。
三、语法细节
3.1 可选类型
可以指定某个对象中某个属性是否是可选的(可以不传),语法就是加个问号?
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是必传的但没传
printPoint({ x: 123, y: 321 }) // 123 321 undefined
printPoint({ x: 123, y: 321, z: 111 }) // 123 321 111
3.2 联合类型
TS中,我们可以使用运算符从现有类型中构建新类型
联合类型(Union Type):有两个或更多其他类型组成的新类型;表示变量类型可以是这些类型中的任意一个。
// foo可以是number类型,也可以是string类型
let foo : number | string = "abc"
foo = 123
给一个联合类型的变量赋值:只要保证是联合类型中的某一个类型的值即可。拿到值之后,最好需要进行类型缩小,判断出更具体的数据类型,再进行下一步的操作。
function printId(id:string|number){
// 类型缩小
if (typeof id === 'string') {
console.log('id是',id.toUpperCase());
}else {
console.log('id是',id);
}
}
printId(123) // id是123
printId('tom') // id是TOM
3.3 type与interface
(1) 类型别名 type
某些类型名字很长,且在多处使用,可以给该类型起一个别名。
比如:
type IDtype = number | string
function printId(id:IDtype) {...}
//等价于
function printId(id:number | string) {...}
再比如:
type PointType = {
x:number,
y:number,
z?:number
}
function printPoint(point: PointType) {...}
// 等价于
function printPoint(point: {x:number,y:number,z?:number}) {...}
起别名之后,可读性会更强一点,使用也更方便一点。
(2) 声明接口 interface
interface的作用和type相似,都可以用来 给对象类型起别名。interface的使用方式为:
// 通过接口interface 创建某种对象类型----是一种声明的方式
interface PointType{
x:number,
y:number,
z:number
}
// 别名type创建某种对象类型----是一种赋值的方式
type PointType = {
x:number,
y:number,
z:number
}
接口的几乎所有特性都可以在 type 中使用。
(3) 区别
-
一、type类型的使用范围更广,接口类型只能用来声明对象
type MyNumber = number type IDtype = number | string
-
二、声明对象时,interface可以多次声明,
-
type不允许有两个相同名称的别名
type PointType = { x:number y:number } type PointType ={ // 提示标识符PointType重复 z:number }
-
interface可以多次声明同一个接口
interface PointType{ x:number y:number } interface PointType{ z:number } // 这几次声明里的条件,该类型的变量都需要满足 const point:PointType={ x:20, y:20, z:40 }
-
-
三:interface支持继承
interface IAnimal{ name:string, age:number } interface IDog extends IAnimal{ color: string } // 变量dog需要给这三个属性都赋值 const dog: IDog={ name:'tom', age:2, color:'red' }
-
四、interface可以被类实现(涉及到TS面向对象)
class Person implements IAnimal{}
总结:从代码的扩展性角度来说,如果是对象类型的声明,应使用interface,非对心类型的定义应使用type。
3.4 交叉类型
联合类型:值为多个类型中的一个即可
交叉类型:需要同时满足多个类型的条件,使用&
type MyType = string & number
上述代码的含义是MyType类型的变量既是string,又是number;不可能存在这样的数据,所以:交叉类型通常是对对象类型进行交叉的:
interface IMan{
name:string,
age:number
}
interface ICoder{
name:string
coding:()=>void
}
type NewType = IMan & ICoder
const obj: NewType={
name:'tom',
age:23,
coding(){
console.log('coding');
}
}
3.5 类型断言as
在确定具体类型时,直接使用类型断言来指明类型,减少类型缩小代码的编写。
// 当选择标签选择器时,ele和ele2的类型是确定的
const ele = document.querySelector('div') // const ele: HTMLDivElement
const ele2 = document.querySelector('img') // const ele2: HTMLImageElement
ele2.src="..." //且可以给src属性赋值
// 采用类选择器时,ele3的类型则是const ele3: Element,没那么具体了
const ele3 = document.querySelector('.img')
ele3.src="" // 会报错,提示Element不存在属性src
如果我们确定ele3一定是HTMLImageElement类型,则可以使用断言来具体类型
const ele3 = document.querySelector('.img') as HTMLImageElement
ele3.src="" // 断言之后也不会报错
TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,
// 代码本身不正确,不建议这样写。这里是为了说明应断言为更加具体或不太具体的类型
const age:number = 18
const age1 = age as any // 断言为不太具体的类型 number->any
const age2 = age1 as string // 断言为更具体地类型 any->string
console.log(age2.length);
// number断言为string类型,会报错
const age3 = age as string
3.6 字面量类型
字面量类型(literal types)
// const类型的变量可自动推断为字面量类型; let也可以声明字面量类型的变量
const name = 'tom' // name类型为"tom"
let name2:'tom' = 'tom' // name2类型为"tom"
// 字面量类型的应用通常是将多个字面量类型联合
type Direction = "left" | "right" | "up" | "down"
const d1: Direction = "left"
案例:
const info={
url:"xxx",
method:"GET"
}
function request(url:string,method:"GET"|"POST"){
console.log(url,method);
}
request(info.url,info.method) // 会报错
原因:method的类型是string,值是GET;而request函数要求参数method的类型是"GET"或者"POST"
解决:
// 方案一:类型断言
request(info.url,info.method as "GET")
// 方案二:直接让info对象类型是一个字面量类型
const info={
url:"xxx",
method:"GET"
} as const
request(info.url,info.method)
为什么xxx也可以,因为xxx本身是一个string(不懂)
3.7 类型缩小 (Type Narrowing)
类型缩小有以下几种方式:typeof、平等缩小(===,!=)、instance of、in、等。。
-
typeof
function printId(id:number|string){ if (typeof id === 'string') { console.log(id.toUpperCase()); }else{ console.log(id); } }
-
平等缩小 ,主要用于字面量类型
type Direction = "left" | "right" function turnDirection(direction:Direction){ switch(direction){ case 'left': console.log('left'); break; case 'right': console.log('right'); break default: console.log('调用默认方法'); } }
-
instanceof 检查是否为另一个值的实例
function printValue(date: Date | string) { if (date instanceof Date) { console.log(date.toLocaleDateString()); }else{ console.log(date); } }
if语句里也可以用typeof, 但是
typeof date === 'Date'
是不对的; Date 属于object 可以写成typeof date === 'object'
-
in用于确定对象是否具有某个属性