前言
近几年来前端的发展趋势一度离不开ts静态类型,包括我自己在用了之后就在也没回头过,在开发的体验中确实能在静态编译的时候帮我们避免很多问题,可能对于纯前端开发人员来说学习ts有一定的成本,但是我认为如果最后得到的收益是大于我们付出的成本的那么未尝不可一试,好了话不多说进入正题
介绍
我们可以在typescript
官网提供的ts编写乐园进行案例编写,以下我们讲的案例都可以在上面自己试一试(毕竟好记性不如烂笔头,多试试总没坏处)
代码编写入口:www.typescriptlang.org/play?#code/…
基础类型
首先介绍typescript
提供的基础类型,其实基础类型和我们在js
中日常使用的typeof
返回的类型很相似
number
boolean
string
undefined
null
any
never
void
大概是以上几种类型我们可以看到有几种在js
中也很常见,没见到的也不要急接下来我们依次介绍他们的作用以及使用场景
//我们创建了一个n并且给的是number的类型,那也就是说n只能赋值number的类型
//这样在定义之后就防止之后因为类型错因为如果赋值其他类型ts会自动产生警告提示
let n:number = 1
n = '12' //Error
n = 2 //Ok
let flag:boolean = true
flag = false //Ok
flag = 1 //Error
let s:string = '零湖冲'
s = '疯清扬' // Ok
s = 1 //Error
let out:undefined; //类型为undefined
let nu:null = null //类型为null
let an:any = null //any表示可以设置任意类型,在工作中尽量还是避免使用
//never类型表示的是那些永不存在的值的类型
//例如,never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
//当它们被永不为真的类型保护所约束时
let nev:never
//某种程度上来说,void类型像是与any类型相反,它表示没有任何类型
//当一个函数没有返回值时,你通常会见到其返回值类型是void
let vo:void = undefined
可以看到如果赋值字符串会提示需要number类型这样在之后的流程中能避免我们因为类型赋值错误导致有些方法不能使用
![]()
Typescript定义类型的方式
interface
type
Interface
先介绍一下interface
,之后在说说type
和两者之间的区别,以下是官网对interface
的定义,可能读起来有些晦涩不过不要紧我们看几个例子就明白了
TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
//基础操作
//1.声明Props接口
interface Props {
name:string
}
//2.创建getName函数形参使用Props类型
function getName(data:Props){
let name = data.name
}
//3.调用getName按照接口声明的类型传参
getName({name:'零湖冲'})
//继承类型并且可以新增类型参数
interface userInfo extends Props{
age:number // 新增age类型
}
function getUser(data:userInfo){
let name = data.name
let age = data.age
}
getUser({name:'任盈盈',age:100})
以上是整个接口使用的过程
具体说一下细节在函数内部使用data.name
的时候如果在编辑器中当你输入完data
会自动提示你当前可以使用的参数
在调用getName
传参数的时候如果你不传参数或者是传的类型不符合当前定义的类型都会有警告提示
ts中有一些可选的操作符号比如
!非空断言操作符
、?可选参数操作符
、?.运算符
、??空值合并运算符
我们依次介绍
//!非空断言操作符
//可以用于断言操作对象是非 null 和非 undefined 类型
//这个例子中因为声明的类型有undefined的可能
//所以正常调用cb方法会有警告但是加上!之后就代表告诉类型忽略undefined和null
type fun = () => void; // type下面会介绍
function fn(cb: fun | undefined) {
const num1 = cb(); // Error
const num2 = cb!(); //Ok
}
//?可选参数操作符
//非常好理解正常我们定义一个name的接口参数如果不传会提示error
//但是加上?之后也就是说这个name是可以传或不传都不会有提示
interface Props {
name?: string
}
function getName(data: Props) {
let name = data.name
}
getName({name:'疯清扬'}) // Ok
getName({}) //Ok
//?.运算符(在ECMAScript中已经实现此运算符)
//在编写过程中如果遇到null或者undefined的情况会自动停止返回false
//举个🌰假如data是我们从接口获取到的参数里面有value并且value里面有name
/**
{
value:{
name:'零湖冲'
}
}
**/
let res = data
//在我们想使用name的时候可能以前会这样写,写一堆繁琐的校验
let name = res && res.value && res.value.name // Error
//现在可以这样子写,如果其中有那一步没有正常返回不会因为获取不到而导致异常问题
let name = res?.value?.name // Ok
//??空值合并运算符
//当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。
//这个我觉得主要可以解决js中0的逻辑或判断问题举个🌰
//可以看到除了0其他都和逻辑或很相似
let aa = 0 ?? '林平之' // 返回0
let aa = 0 || '岳灵珊' // 返回岳灵珊
let aa = null ?? '岳不群' // 返回岳不群
let aa = undefined ?? '宁中则' // 返回宁中则
Type
介绍一下对type
的定义,给类型起一个新名字、可以作用于原始值(基本类型)、联合类型、元组、交叉类型、类型映射以及其它任何你需要手写的类型
//给类型换一个新的名字&作用🌧于原始值
type num = number
type n1 = num
let n:n1 = 11 //Ok
let n:n1 = '12' //Error
type str = string
//声明一个联合类型
type ns = str | num
let ns1:ns = '任盈盈' // Ok
let ns2:ns = 1 // Ok
//元组类型,限定数组的个数以及数组类型
type res = [string, number]
let data:res = ['12',12] //Ok
let data:res = ['12',false] //number
//交叉类型,类似interface的继承
interface user {
name: string;
}
type userInfo = user & {
age: number;
}
let user: userInfo = { name: '东方不败', age: 1 } //Ok
//类型映射,这个很简单就是把A上面的类型都映射到B类型上举个🌰
type keysList = "name" | "sex"
type copeKey = {
[key in keysList]: string
}
let res: copeKey = {name: "东方不败",sex: "女"} //Ok
具体总结一下
interface
和type
的区别,具体区别不止这些大家可以自己试一哈
1.都可以用来描述对象或函数和其他基础类型
2.interface
可以实现继承,type
不行,但是type
可以使用交叉类型
3.type
可以使用in
进行类型映射,interface
不可以实现
4.interface
可以定义多次接口会合并,但是type
不可以
泛型
在像
C#
和Java
这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。---- 来自官网的介绍
我们来看几个例子就明白了其实说白了就是为了描述类型的一种方式
//泛型是使用<>这种符号声明里面传的是当前需要的类型举个🌰
//调用的时候我们传了类型,这也就说明泛型里用的类型我们可以在外面动态传入
function identity<T>(arg: T): T {
return arg;
}
identity<string>('任我行') // Ok
identity<number>(12) // Ok
//我们可以传入多个类型(是不是很简单)
function identity<T, U, S>(arg: T, name: U, f: S): T {
return arg;
}
identity<number, string, boolean>(123, '张无忌', false) //Ok
我们看看怎么定义复杂类型对象
type arrList = Array<any>
let arr:arrList = [] // Ok
//如果需要定义数组内部的内容Array<>是泛型的方式
type arrList = Array<string>
let arr:arrList = ['左冷禅'] //Ok
//还可以这样子定义数组内容
type arrList = string[]
let arr:arrList = ['左冷禅'] //Ok
//数组的内部类型如果是复杂的类型可以这样
type obj = { name: string }
type arrList = obj[]
let arr: arrList = [{ name: '1' }] //Ok
//或者这样使用泛型的方式
type obj = { name: string }
type arrList = Array<obj>
let arr: arrList = [{ name: '1' }] //Ok
//规定变量可以接收的值
type curr = 'age' | 'name'
function getCurr(val:curr){
return val
}
getCurr('age') //Ok
getCurr('name') //Ok
Typescript中的高级类型工具
ts
中有几个高级类型支持类型直接的扩展以及转化等操作我们依次介绍
keyof
extends
Partial
Required
Pick
Record
keyof&extends
//keyof是索引类型查询的语法类似Object.keys(),取的值为键举个🌰
interface users {
age: number,
name: string
}
type formtUser = keyof users
function getUser(val: formtUser) {
return val
}
getUser('name') // Ok
getUser('age') //Ok
//来看看具体实际用途我们使用ts实现一个根据key获取对象内容的函数
//这样写法有两个问题
//1.无法确认返回类型
//2.无法对K做约束
const data = {
age: 101,
name: '令狐冲'
}
function getData(o: object, name: string) {
return o[name]
}
getData(data,'name')
//我们可以使用泛型的方法来规定输入的内容和返回的内容
//extends代表条件类型,可以理解为T继承的类型来自Object
//另一个K继承来自T的键(上面说过keyof 可以获取键)
function getData<T extends Object, K extends keyof T>(o: T, name: K):T[K] {
return o[name]
}
getData(data1,'name')
//单独介绍一下extends条件类型,类似于js中的三元运算符,举🌰
T extends U ? X : Y
type flag<T> = T extends true ? true : false
type f1 = flag<number> // false
type f2 = flag<false> // false
type f3 = flag<true> // true
Partial&Required
//Partial将现有的类型全部转化为可选类型
//日常中我们可以复用其他已经定义好的类型没必要重新声明一次,举🌰
interface school {
name: string,
age: number
}
type formtSchool = Partial<school> //在这里全部转化为可选类型
function getSchool(val: formtSchool) {
}
getSchool({ name: 1 }) //Ok
//Required和Partial类型相反,它是将所有可选类型变为必选
type recordSchool = Required<formtSchool>
function getSchool(val: recordSchool) {
}
getSchool({ name: '风青杨', age: 1 }) //Ok
Pick&Record
//Pick可以继承部分想要的类型,举🌰
interface User {
age: number,
name: string,
sex: string
};
type PickUser = Pick<User, 'age' | 'sex'>
function getUser(val: PickUser) {
}
getUser({ age: 1, sex: '女' }) //Ok 我们只继承了age和sex
//Record是类型映射,简单的说就是把一个类型映射到另一个类型的key上,这样讲可能有些难懂我们来看个🌰
type types = 'a' | 'b'
type data = { name: string, age: number }
type result = Record<types, data>
/*
result结果是这样
type result = {
a: data;
b: data;
}
*/
定义一个值的类型却不知道应该是什么类型应该咋整?
直接问问
ts
就好了,举个🌰
//当我们想定义一个时间类型,但是却不知道应该为什么类型,那么创建一个值为时间的变量ts会告诉你
type times = Date
let time:times = new Date() //Ok
let time:times = 123 //Error
结束
本篇幅到这里就结束了,讲了很多但是还差很多最重要的还是工作中去实践才能感受到
ts
带来的好处