写在前面
感谢大家百忙之中点击进入这篇文章,因缘和合,我们从网络的万水千山之隔遥聚于此,也相信看到这里的同学有着相似的特点--强烈的求知欲望和持续的学习热情。
可能你们之中会有疑惑,我js用的好好的,为什么前端家庭要融入ts这一成员呢,其一增加学习成本,其二加大人力投入,那ts又有什么好处呢?这里首先与大家分享几个场景
- 当我们调用一些封装好的公共组件或函数时,这些组件或方法没有任何注释,为了搞清参数类型,我们是否需要硬着头皮去梳理代码逻辑?
- 当我们接手别人的代码并在此基础上进行迭代时,想死的心都有了,是否准备协调测试开发重构代码呢?
- 当前端开发时明明已经定义好的接口,等到联调时却发生错误(Cannot read property 'length' of undefined ),我们根据提示信息逐步排查错误,最后发现服务返回参数类型有误,是否会怒气上涌呢?
综上归根结底,javascript是一门动态弱类类型语言,对变量类型的十分宽容,如果我们长期在没有类型判断的思维下开发,很容易养成不良的编程习惯。由此,2014年faceBook推出Flow,而微软推出了typescript,致力于解决静态类型检查,逝者如斯夫,不舍昼夜,随着时间的发展,typescript显然更受市场的欢迎,Angular 和 vue 开始全面使用 ts 重构代码
类型
Js类型
string number boolean nullundefinedsymbol object array function
Ts新增类型
枚举类型 元组类型 any类型 void类型 Never类型
基本类型
const str:string = " sun " // string
const num:number = 123 // Number
const flag:boolean = true // Boolean
const n:null = null // null
const a:undefined|null = unefined // null || undefined
const sentence: string = `Hello, my name is ${ str }` // 模版字符串
symbol
const name = Symbol("name");
const obj = {
[name] : "sjx",
age:18
}
对象类型
Array
const arr1:number[] = [ 1,2,3 ]; // 数组元素为number类型
const arr3:(string | number)[] = [ 1,"sun",3 ]; // 元素为 number or string 类型
// 采用泛型的书写形式
const arr2:Array<number> = [1,2,3];
Object
interface NameInfo { name: string, age: number } const obj: NameInfo = { name: "sjx ", age : 18, }
interface后文会有所介绍,心急的小伙伴小伙伴可以直接点击interface一节
复杂数组的书写
interface IPeople {
name:string,
age:number,]
data:IPeople[]
}
const peoples:IPeople[] = [{
name:'sjx',
age:18,
data:[
{
name:'qy',
age:'17'
}
]
}]
Ts新增类型
枚举
其目的是我们可自定义一些带名字的常量,这里主要是为了有一个清晰的概念,后文我们详细介绍枚举这一类型
enum Status { success, failed, pending }
元组
可以理解为数组的一种特殊形式,在定义元组时,数组的长度跟元素的类型已经确定,切书写顺序不能紊乱
const tuple:[ string,number,boolean ] = [ "sun", 12, true ]
any
typeScript中,任意类型都被可以被归档为any类型,const type:any = "string" const type;any = ['1',2]上述代码中我们可以看出,使用any类型,无需任何形式的检查,可以很容易地编写类型正确但在运行时有问题的代码。
如果我们使用
any类型,就无法使用 TypeScript 提供的大量的保护机制。
void类型
当一个函数没有返回值时,我们可以使用
void类型const consText = (text:string):void => console.log(text)
never类型
表示的是那些永不存在的值的类型, 抛出异常,或根本就不会有返回值的函数表达式
let x: never = msg => { throw new Error(msg) }
枚举
了解枚举之前我们先看如上一段代码,当多条分支需要判断时,我们可以通过封装一个策略对象缓存每个条件对应的方法,根据不同的判断条件执行相应的方法进行优化,此处我们就可以通过常量枚举贮存相应策略
initRole(role:number){ const func1 = ():void => {console.log()}, func2 = ():void => {}, func3 = ():void => {console.log("555")}; enum Status{ a = func1(), b = func1(), c = func2(), d = func2(), e = func3() } console.log(Status[role]) }如上,我们代码分工是不是更加明确了?接下来,我们了解一下枚举共有哪些类型
数字枚举
enum Status { success, // 0 failed, // 1 pending // 2 }数字的枚举的原理为反向映射,即
status[ status['success']=1 ] = 'success'当我们打印
Status[success],输出0,当我们打印Status[0],输出success数字枚举还可以自定义枚举值
enum Status { success = 1, // 1 failed, // 2 pending // 3 }
字符串枚举
enum Status { UPLOADING = "request_loading", SUCCESS = "request_success", }当我们使用公共状态管理工具(Vuex,Redux、Mobx)时,需要将状态标识抽离,此时可以利用字符串枚举贮存所有的状态
异构枚举
字符串枚举和数字枚举混合使用,就是异构枚举,但不推荐使用
enum Status { UPLOADING = 0, SUCCESS = "request_success", }// 对应 es5 的代码如下 var Status; (function (Status) { Status[Status["UPLOADING"] = 0] = "UPLOADING"; Status["SUCCESS"] = "request_success"; })(Status || (Status = {}));通过对比可以发现,字符串枚举是没有反向映射的
常量枚举
常量枚举使用后不会编译出一个枚举对象,只是做一个贮存状态的作用
const enum Status { UPLOADING = 200, SUCCESS = 100, FAILED = 401, } console.log(Status.SUCCESS )当我们封装axios、fetch时,可以用常量枚举来贮存状态码
接口
接口提供了一种用以说明一个对象应该具有哪些方法的手段, typescript中接口是一个非常灵活的概念,常用于对【对象的元素类型】进行描述
对象
定义一组对象
interface INameInfo { name : string, age : number, } const nameInfo:INameInfo = { name:'sjx', hobby:'hhh' }可选|只读属性
interface Person { readonly name: string; // readonly 该参数只读 age?: number; // ? 该参数非必传参数 }
数组
interface INameInfo {
name : string,
age : number,
}
const list:NameInfo[] = [{
name:'sjx',
hobby:'hhh'
},
{
name:'sjx',
age:18,
hobby:'hhh'
}]
字符串索引签名
索引接口时,当我们定义的对象传入多余的属性时,会提示tyepscript的错误,此时需要用到字符串索引签名
interface NameInfo{ name: string, [id:string]: any } const obj:NameInfo = { name:'sjx', age:18, hobby:'hhh' }任意的字符串去索引 NameInfo ,可以得到任意的结果 any
函数
函数参数校验
interface NameInfo{
name: string,
age?: number
}
// 参数必须是一个对象,且必须有 name 属性
const showInfo = (data:NameInfo) => console.log(data.name)
定义函数体
函数体使用
type定义type showType = (data:NameInfo) => void // 参数索引符合 NameInfo 接口,且返回值类型为 void const showInfo:showType = (data) => { console.log(data.name) }
函数重载
typescript允许使用
function定义多个函数名相同的函数,根据传入参数的不同,调用不同的处理方法,返回不同的结果function showInfo(...args: string[]): string function showInfo(...args: number[]): number function showInfo(...args:[]):any { const first = args[0] if ( typeof first === "string") { return args.join("") } else { return args.reduce((pre,cur) => cur+pre, 0) } }
接口继承
接口继承使用
extends字段interface PeopleType{ name: string, age: number } interface Sun extends PeopleType{ // Sun 继承 PeopleType hobby: string } const fund:Sun = { name:'sjx', age:18, hobby:'chi' }
绕过多余属性检查
什么是多余属性检查?
如图,
IName定义了name,age,当我们根据IName接口索引新建对象peopleOne时,发现该peopleOne定义了hobby字段,此时产生错误提示。解决方式主要为类型断言、索引类型、类型兼容性
索引类型
interface NameInfo{
name: string,
age?: number,
[prop: string]: any
}
类型断言
手动指定一个值的类型
showInfo({ name:'sjx', hobby:'eat' } as IName)
类型兼容性
const obj = { name:'sjx', hobby:'chi' } showInfo(obj)
高阶类型
联合类型
一个值可以是几种类型之一
let tsUnion: number | string = 1;
交叉类型
将多个类型合为一个类型
interface TypeA { name: string } interface TypeB { age: number } let c: TypeA & TypeB = { name:'sjx', age:18 };
类型断言
手动指定一个值的类型 值 as 类型(建议)
type func = (target: string|number ) => number const getLength:func = target=> { if ((target as string)) return (target as string).length return target.toString().length }
变量后使用 !
变量后使用 !:表示类型推断排除null、undefined
/** * @description: 给输入的数值添加前缀 */ const getSp = ( num: number|null ): string => { const getSnap = ( prefix: string ) => prefix + num!.toFixed().toString() num = num || 0.1 return getSnap("lison-") }
类型保护
使用类型断言
if ((arg as IHobby).sport) { return (arg as IHobby).sport }typeOf
const getLength = (arg: number | string):any => { return typeof arg === "string" && arg.length }instanceOf
const getLength = (arg: number | any[]):any => { return (arg instanceof Array) && arg.length } // “instanceof”表达式的左侧必须是“any”类型、对象类型或类型参数。自定义
const isCat = (params:IDog|ICat):params is ICat => params.hasOwnProperty("color") const animal = (data:IDog|ICat):any => { if(isCat(data)) return data.color }
(字符串|数字)字面量
字符串
type Name = "sjx"|"zqm"|"hwh"|"lj" const name: Name = "sjx" const getName = (name: Name): void => console.log(name)数字
type Age = 18 | 22 interface IAge < T > { name: T, age: Age, // 根据接口定义age时,只能取值 18 或 22 } const person:IAge <string>= { name:'sjx', age:18 }
React函数式组件
import React from 'react'
interface Props {
name ?: string
}
const InitPage:React.FC<Props> = props => {
return (
<div>
初始化首页{props.name}
</div>
)
}
InitPage.defaultProps={
name:'sjx'
}
export default InitPage
React类组件
import React, { Component } from 'react' interface Props{ age?: number|undefined } interface State{ name: string|undefined, } export default class index extends Component<Props,State> { state:State = { name:"sjx" } static defaultProps = { age: 18 } render() { const {name} = this.state,{age} = this.props return ( <div> 姓名:{name} 年龄:{age} 年龄18一朵花 </div> ) } }
高阶组件
import React, { Component } from 'react' function ShowCompoment(com){ return class extends Component{ render(){ const {loading} = this.props return loading ? <div>loading....</div> :<com {...props}/> } } }使用Ts后
import React, { Component } from 'react' interface ShowLoading { loading : boolean } function ShowCompoment<P>(Com:React.ComponentType<P>){ return class extends Component<P & ShowLoading>{ render(){ const {loading} = this.props return loading ? <div>loading....</div> :<Com {...this.props as P}/> } } } export default ShowCompoment
Com:React.ComponentType: 指定被包装组件的类型,属于React预定义的类型
P: 被包装组件的属性类型
ShowCompoment:传入被包装组件的属性类型
被包装组件没有loading属性,需要定义loading属性。
interface ShowLoading { loading : boolean }使用交叉泛型,让被包裹的组件有loading属性
Component<P & ShowLoading>s
请求
请求方法
import ajax from "@utils/request" interface ILogin { name:string, pwd:number, [prop: string]: any } const login = (data:ILogin) => ajax.post("/test/login",data)组件请求
import { getPermission } from '@server/user' import asyncRequest from '@utils/resCatch' interface IReqLogin { order: 9, path:string, type:string, code:string, childs:IReqLogin[] } useEffect(() => { const resData = { name:'sjx', pwd:123446 } asyncRequest(async()=>{ const res:IReqLogin = await getPermission(resData) setMenu(res) }) }, [])