TypeScript(二)

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) {
 
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值