TypeScript变量的声明
在TypeScript中定义变量需要指定标识符的类型,完整声明格式如下
var / let / const 标识符 : 数据类型 = 赋值
声明了类型后的typescript会自动进行类型检测,声明的类型可以称之为类型注解,需注意的是var声明是不推荐的。举个类型声明的例子
const message: string = "hello";
let num1:number = 123;
var boo:boolean = true
需注意的是,类型注解的大小写是有区别的
- string:TypeScript中的字符串类型
- String:JavaScript的字符串包装类的类型
在写js代码中,eslint帮助我们让我们的代码更加的规范,同理,ts也有tslint让我们的代码更加的规范。我们可以全局安装tslint,在项目中运行tslint --init 生成文件,如果我们代码写的不规范,会报一些警告和错误
npm i tslint -g
tslint --init
现在我们觉得写类型注解,每声明一个变量都要写上类型注解的话,觉得很麻烦,我们也可以使用类型推导 / 推断
// 默认情况下,会将赋值的类型作为前面标识符的类型,
// 这个过程称为类型推断 / 推断
let mes = "hello"
mes = 123 //报错
JavaScript和TypeScript的数据类型
下图就很好的表面了typescript和JavaScript的区别和关系。
由于JavaScript不断的扩展,js外层是ES6、ES7...(ECMAScript是包含着JavaScript的,是对JavaScript的一个扩展), 最外层是typescript,是包括JavaScript和ECMAScript的,但是还包含其他的东西,比如Strongly Typed(强类型),就是给标识符(变量)一个类型,也支持泛型和接口。
然后通过编译成js代码,在浏览器中运行
Typescript的类型
- 数字类型:不区分整型(int)和浮点型(float)
let num: number = 123;
num = 222;
let num1: number = 100; //十进制
let num2: number = 0b100; //二进制
let num3: number = 0o100; //八进制
let num4: number = 0x100; //十六进制
console.log(num1, num2, num3, num4); //100 4 64 256
- Boolean类型
let flag:boolean = true
flag = false
flag = 20>30
- 字符串类型
let mes:string = "hello";
let name = "小红"
let age = 17
let mes2 = `name:${name} age:${age}`
- 数组类型
// 类型注解:type annotation
let arr = [];
// 数组类型有两种写法
// 泛型
let arr1: Array<string> = []; // 不推荐
let arr2 :string[] = [] //推荐
arr1.push("hello");
我们要确定一个事实:arr是一个数组类型,但是数组类型里面存放的是什么元素呢?一个数组在ts开发中,最好存放的数据类型是固定的,存放不同的数据类型是不好的习惯。
为什么不推荐泛型的那种写法呢?因为在react的jsx中是有冲突的,在jsx中书写 <div></div>这些代码的时候,<div></div>这里有一个 < 尖括号,<string>这里也有一个 < 尖括号,编译器在解析的时候不知道怎么去解析这个,就会出错,在babel编译的时候也会有问题。
- 对象类型
这样写的话会类型推导,name:string,age:number
const info = {
name:"why",
age:18
}
但是上面是没有定义info是对象类型的,如果这样子定义info1为对象类型的话,会有一个弊端,
取值的时候就会报错,因为将info1的类型定义为object,所以object里面是没有name属性的
const info1:object = {
name:"why",
age:18
}
console.log(info1.name)
- null类型和undefined类型
// null 类型只有一个值,就是 null,如果不写类型也会帮助我们推导成null
let n1:null = null
let n2 = null
// undefined 类型也只有一个值,如果不写类型也会帮助我们推导成undefined
let n3:undefined = undefined;
let n4 = undefined
null 类型只有一个值,就是 null,如果不写类型也会帮助我们推导成null
undefined 类型也只有一个值,如果不写类型也会帮助我们推导成undefined
- symbol类型(ES6)
在定义对象属性的时候,是不能够定义两个名字相同的属性的,我们解决的话就定义不同的属性名
const message = {
title:"程序员",
title:"老师"
}
但是我们要是想定义相同的该怎么办呢? 在ES6中有一个类型,叫Symbol,它创建出来的东西是独一无二的。其实Symbol用的并不是很多,这里是其中一个应用场景。
const title1 = Symbol('title')
const title2 = Symbol('title')
const message1 = {
[title1]:"程序员",
[title2]:"老师"
}
上面的类型都是JavaScript拥有的类型,下面介绍typescript拥有的类型
- any类型
let message3: any = "str";
message3 = 123;
// 不推荐,数组中最好确定类型
const array: any[] = []
无法确定一个变量的类型,并且可能它会发生一些变化,这个时候就可以使用any类型
应用场景:
1.当进行一些类型断言(as)的时候,有一些类型断言的时候不能直接做转化,要转化为any类型再转化为其他类型,就不会报错了
2.在不想给某些JavaScript添加具体的数据类型时(跟原生的JavaScript代码一样),这种情况就比如刚开始还不熟悉ts,可以慢慢过渡,之后再重构
any类型的变量我们可以对其进行任何操作,比如获取不存在的属性和方法,对其赋任何值。但是这个时不安全的
- unknown类型
function fn1(){
return 123
}
function fn2(){
return "hello"
}
let flag1 = true
let result:any
let result1:unknown
flag1 ? result = fn1() : result = fn2();
let a:number = result
let b:boolean = result
let c:string = result1 //编译不通过,不可以赋值
let d :any = result1
我们用一个result作为结果,但是声明的时候如果没有赋值,就要写类型注解的,但是它可以赋值为number类型和string类型,我们可以用联合类型和any,但是any是不安全的,那还可以用什么呢?可以用unknown。
unknown和any的区别
- unknown类型只能赋值给any和unkown的类型
- any类型可以赋值给任何类型
其实在声明的时候不知道变量的类型,用unknown可以防止在其他地方乱用这个变量,因为用any太灵活了,就像JavaScript一样,可以任意赋值,传参的时候也是不会有任何限制,这样子会不安全。如果用unknown的话,把unknown类型的变量赋值给不是any和unkown的类型的就会报错
还要一件事,unknown类型是在typescript 3.x的时候才出现的,之前的版本只能使用any,这个类型的出现就是为了防止把变量在其他地方乱用,这样子是很不安全的
- void类型
function add (num1:number,num2:number):void{
console.log(num1+num2)
return undefined
}
add(10,20)
上面add中是传入两个数字相加,但是这里面没有返回值。我们知道在js中,如果一个函数没有返回值,默认是返回undefined的。这里面是默认返回void类型, :void 写不写都行,一般情况下是不用写的。返回void类型的话,我们是可以 return undefined 的。这个知道就行
- never类型
never 表示永远不会发生值的类型,
比如在一个函数中是死循环或者抛出异常,那么这个会返回东西吗?是不会的,那么些void类型或者写其他类型都是不合适的,我们就可以用never类型
function foo1():never{
// 死循环
while(true){
}
}
function foo2():never{
throw new Error('error')
}
那么有什么样的场景会用到这个never类型呢?可以去官网查看,官网有举例子。这里举个简单的例子
比如我封装一个核心的函数
// 封装一个核心的函数
function handleMessage(message:string | number){
switch(typeof message){
case 'string':
console.log('string方式处理message');
break;
case 'number':
console.log('number方式处理message');
break;
}
}
这个函数只能传入string和number类型的参数,我们规定好的了。比如张三来用这个函数的时候,想传入一个boolean类型的参数,传入肯定会报错,那他就找到这个函数去修改传入的类型(如下)。但是我们函数内的逻辑是没有对传入boolean类型做处理的。
function handleMessage(message:string | number | boolean){
switch(typeof message){
case 'string':
console.log('string方式处理message');
break;
case 'number':
console.log('number方式处理message');
break;
}
}
那么我们该如何才能避免这种呢?或者给修改者一些提示呢?我们可以利用never类型;
function handleMessage(message: string | number | boolean) {
switch (typeof message) {
case "string":
console.log("string方式处理message");
break;
case "number":
console.log("number方式处理message");
break;
default:
const check: never = message;
}
}
如果传入的类型没有被穷举完,就是对所有的类型一 一 判断,message是不可以赋值给never的,这样子编译就会报错。这个时候张三就知道了,我修改传入的boolean类型的时候,里面没有对boolean类型做处理,要加上对Boolean类型的处理才行。这样子就可以更加的完善了
function handleMessage(message: string | number | boolean) {
switch (typeof message) {
case "string":
console.log("string方式处理message");
break;
case "number":
console.log("number方式处理message");
break;
case 'boolean':
console.log("boolean方式处理message");
break
default:
const check: never = message;
}
}
- tuple类型-元组类型
let info:any = ["tao",18,1.78]
let age = info[1] //any类型
console.log(age.length)
上面说过定义数组类型最好确定里面存放的是什么类型的,但是比如我想存放我的个人信息呢?这样子就有不同类型了,那我用any。但是这样子会有一个弊端,比如说我取出第二个元素是一个数值,但是我们知道数值是没有length的,这样子就会有问题了。那么很简单呀,我们用对象不就好了,用键值来存储。但是我们要是想用数组类型但又想避免这种弊端呢?可以使用tuple类型。
// 数组里面有三个元素,对应的类型是string,number,number
let info:[string,number,number] = ["tao",18,1.78]
let age = info[1] //number类型
console.log(age.length) //报错
这样子定义的话,会知道每一个元素是什么类型,这样子就会避免不安全的问题
那有哪些应用场景呢?
比如在react中,有一个hook叫useState,在调用函数的时候,会返回两个值,一个是传入的初始值counter,一个是处理这个初始值的函数setCounter。这里面就用到了tuple类型。那不可以用对象返回吗?如果用对象返回的话,返回的时候只能固定键名,这样子就不具备通用性了,要是调用多次呢?
下面举个例子
function useState(state:any){
let currentState = state
const changeState = function(newState:any){
currentState = newState
}
const tuple:[any,(newState:any)=>void] = [currentState,changeState]
return tuple
}
const [counter,setCounter] = useState(10)
setCounter(100)
const [title, setTitle] = useState("abc");
setTitle("cba");
我们返回值的话,如果用any类型也是不安全的,就可以用tuple类型,这样子就可以明确的知道返回的setCounter是函数类型。但是这里还有一个问题,就是返回的counter和title都是any类型的,如果我想知道返回的是什么类型,就比如我传入的是number类型,就counter返回的是number类型,传入的title是string类型,返回的就是string类型。这个该如何做优化呢?
我们可以用泛型
//返回值
function useState<T>(state: T):[T,(newValue:T)=>void] {
let currentState = state;
const changeState = function (newState: T) {
currentState = newState;
};
const tuple: [T, (newState: T) => void] = [currentState, changeState];
return tuple;
}
const [counter, setCounter] = useState(10);
setCounter(100);
const [title, setTitle] = useState("abc");
setTitle("cba");
export {};
这样子我们就可以知道返回的counter是number类型,返回的title是string类型。这里的T,默认是将传入的类型传给T。所以可以知道返回数组的话,tuple类型是比any类型好用的
- 函数的参数与返回值的类型
// 这样子看着不好看,一般把函数注解抽取出来
const foo: () => void = () => {};
type MyFunction = () => void;
const foo1: MyFunction = () => {};
给参数加上类型注解
function add(num1: number, num2: number) {
return num1 + num2;
}
给返回值加上类型注解;但是在开发中通常情况下不会给返回值加上类型注解,因为会自动类型推导
function add(num1: number, num2: number):number {
return num1 + num2;
}
- 匿名函数的参数
// 通常情况下,在定义一个函数时,都会给类型参数加上类型注解
function foo3(message:number){
}
let arr = ['abc','cbd','lde'];
// item会根据上下文的环境推导出来类型,这个时候可以不添加类型注解
// 上下文中的函数:可以不添加类型注解
arr.forEach((item)=>{
item.split('')
})
tem会根据上下文的环境推导出来类型,这个时候可以不添加类型注解
上下文中的函数:可以不添加类型注解
- 对象类型
function printPoint(point: { x: number; y: number }) {
console.log(point.x, point.y);
}
printPoint({x:123,y:123});
{x:number,y:number} 这里面的分割符用 逗号,也可以,用分号;也可以
- 可选类型
function printPoint1(point: { x: number; y: number; z?: number }) {
// 如果没有z属性输出undefined
console.log(point.x, point.y, point.z);
}
printPoint1({ x: 123, y: 123 });
printPoint1({ x: 123, y: 123, z: 111 });
- 联合类型
TypeScript类型系统允许我们使用多种运算符,来从现在类型中构建新类型
第一种组合类型:联合类型(Union Type)
- 联合类型是由两个或者多个其他类型组成的
- 它表示可以是这些类型的其中一个类型
- 联合类型的每一个类型被称为联合成员(union‘s members)
function printID(id:string | number | boolean){
// 使用联合类型的话要特别小心
console.log(id.toUpperCase()) //报错
}
printID(123)
printID("acv")
printID(true)
比如我们定义一个函数,打印传进来的id,但是id由string类型,number类型,这样子就可以使用联合类型了。但是在使用联合类型的时候要特别小心,比如想将传进去的string类型变成大写,但是其他类型没有这个toUpperCase的方法,就会报错。
function printID(id:string | number | boolean){
// 使用联合类型的话要特别小心
// narrow 缩小
if(typeof id ==='string'){
console.log(id.toUpperCase())
}else{
console.log(id)
}
}
printID(123)
printID("acv")
printID(true)
我们可以进行类型判断,这个在官方文档中有一个专业词语叫 narrow 缩小,就是将类型的范围缩小,只是string类型才转成大写
- 可选类型和联合类型的关系
// 一个参数是可选类型的话,它其实类似于这个参数是 string | undefined 的联合类型
function foo5(message?: string) {
console.log(message);
}
foo5();
foo5(undefined);
foo5("abc");
function foo6(message: string | undefined) {
console.log(message);
}
// foo6();//不传会报错
foo6(undefined);
foo6("abc");
一个参数是可选类型的话,它其实类似于这个参数是 string | undefined 的联合类型。但是写成string和undefined 的联合类型的话必须要传undefined。
- 类型别名
在上面的代码中,我们定义对象类型和联合类型的时候,有时候类型注解会很长,这样子阅读性就不好了,而且如果有几个变量都是这种类型注解,就会有很多代码是重复的。我们具体使用type关键字来定义类型别名
// type 关键字是用来定义类型别名的(type alias)
type IDType = string | number | boolean
//可以写, ; 和不写
type pointType = {
x: number,
y: number
z?: number }
function printID(id: IDType) {
}
function printPoint1(point: pointType) {
}