TypeScript 类、泛型的使用实践记录 | 青训营
1 深入浅出TypeScript--从入门到应用
TypeScript是JavaScript的超集,具有可选的类型并编译为纯JavaScript。从技术上讲,TypeScript就是具有静态类型的JavaScript。
1.1 为什么要学习TS
- 那么,向JavaScript添加静态类型的原因是什么?
- 避免经典的错误'undefine' is not a function
- 在不严重破坏代码的情况下,重构代码更容易(动态类型的自由特性经常会导致错误,这些错误不仅会降低程序员的工作效率,还会由于增加新代码行的成本增加而使开发陷入停顿)
- 使大型、复杂的应用程序源码更易阅读
- TS带来了类型安全;下一代JS特性;有完善的工具链
- 一些学习教程:
1.2 TS基础
1.2.1 boolean number string
- boolean 表示逻辑值:true和false
- number 双精度64位浮点值,可以用来表示整数和分数
- string 一个字符系列,使用单引号(')或双引号(")来表示字符串类型。反引号(`)来定义多行文本和内嵌表达式
// number
let binaryLiteral:number = 0b1010; // 二进制
let octalLiteral:number = 0o744; // 八进制
let decLiteral:number = 6; // 十进制
let hexLiteral:number = 0xf00d; // 十六进制
let name:string = 'Runoob';
let words:string = `您好,今年是${ name }发布 ${ years + 1 }周年`;
let flag:boolean = true;
1.2.2 枚举enmu
- enum 枚举类型用于定义数值集合
enum Color {Red, Green, Blue};
let c: Color = Color.Blue;
console.log(c); // 输出2
1.2.3 any unknown void
- any 作为类型可以涵盖想要的任何内容。每当想要转义类型时,any都允许将任何JavaScript变量赋给它。它经常用于对尚未检查且类型未知的传入变量;
- unknown是其类型安全的对应对象,与any非常相似,但在显式类型检查之前,不允许对变量执行任何操作;
- void 在没有返回值时使用,例如,用作不返回任何值的函数的返回类型。
function hello(): void{
alert('Hello Runoob');
}
1.2.4 never
- Never类型表示的是那些用不存在的值的类型,例如:将引发异常的函数
1.2.5 数组
var sites:string[];
sites = ['Google','Runoob', 'Taobao'];
1.2.6 元组类型tuple
- 元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。
let x:[string, number];
x = ['Runoob', 1]' // 运行正常
x = [1, 'Runoob']; // 报错
console.log(x[0]); // 输出 Runoob
1.2.7 Map对象
- Map对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或原始值)都可以作为一个键或一个值。
- Map是ES6中引入的一种新的数据结构。
let myMap = new Map();
let myMap1 = new Map([
['key1','value1'],
['key2','value2']
]);
- Map相关的函数与属性:
- map.clear():移除Map对象的所有键/值对
- map.set():设置键值对,返回该Map对象
- map.get():返回键对应的值,如果不存在,则返回undefined
- map.has():返回一个布尔值,用于判断Map中是否包含键对应的值
- map.delete():删除map中的元素,删除成功返回true,失败返回false
- map.size:返回Map对象键值对的数量
- map.keys():返回一个Iterator对象,包含了Map对象中每个元素的键
- map.values():返回一个新的Iterator对象,包含了Map对象中每个元素的值
let nameSiteMapping = new Map()
// 设置Map对象
nameSiteMapping.set('Google', 1)
nameSiteMapping.set('Runoob', 2)
nameSiteMapping.set('Taobao', 3)
// 获取键对应的值
console.log(nameSiteMapping.get('Runoob')) // 2
// 判断Map中是否包含键对应的值
console.log(nameSiteMapping.has('Taobao')) // true
// 返回Map对象键值对的数量
console.log(nameSiteMapping.size) // 3
// 删除 Runoob
console.log(nameSiteMapping.delete('Runoob')) // true
// 移除Map对象所有的键值对
nameSiteMapping.clear()
console.log(nameSiteMapping) // Map(0) {}
1.2.8 TypeScript接口
- 接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。
- TypeScript接口定义如下:
interface interface_name{
}
- 实例
interface IPerson{
firstName:string,
lastName:string,
sayHi: ()=>string
}
var customer:IPerson = {
firstName: "Tom",
lastName: "Hanks",
sayHi: ():string => {return "Hi there}
}
console.log("Customer 对象")
console.log(customer.firstName)
console.log(customer.lastName)
console.log(customer.sayHi())
var employee:IPerson = {
firstName:"Jim",
lastName:"Blakes",
sayHi: ():string=>{return "Hello!!"}
}
1.2.9 命名空间
- 命名空间的目的就是解决重名问题
eg.班上有两名交小明的学生,为了区分他们,在使用名字之外,需要使用一些额外的信息,比如他们的姓(王小明、李小明),或者其他 - 命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,在不同名字空间中的含义是互不相干的。这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中。
- TypeScript中命名空间使用namespace来定义,语法格式如下:
namespace SomeNameSpaceName{
export interface ISomeInterfaceName{}
export class SomeClassName{}
}
当需要在外部可以调用SomeNameSpaceName中的类和接口,则需要在类和接口添加export关键字。
要在另一个命名空间调用语法格式为:SomeNameSpaceName.SomeClassName;
如果一个命名空间在一个单独的TypeScript文件中,则应使用三斜杠///引用它,语法格式如下:
<reference path = "SomeFileName.ts"/>
- 案例
// IShape.ts
namespace Drawing {
export interface IShape {
draw()
}
}
// Circle.ts
/// <reference path = "IShape.ts" />
namespace Drawing {
export class Circle implements IShape {
public draw() {
console.log('Circle is drawn')
}
}
}
// Triangle.ts
/// <reference path = "IShape.ts"/>
namespace Drawing {
export class Triangle implements IShape {
public draw() {
console.log('Triangle is drawn')
}
}
}
// TestShape.ts
/// <reference path = 'IShape.ts'/>
/// <reference path = 'Circle.ts'/>
/// <reference path = 'Triangle.ts'/>
function drawAllShape(shape: Drawing.IShape) {
shape.draw()
}
drawAllShape(new Drawing.Circle())
drawAllShape(new Drawing.Triangle())
使用tsc命令编译以上代码:tsc --outFile app.js TestShape.ts
1.3 TS进阶
1.3.1 联合类型
- 联合类型(Union Types)可以通过管道(|)将变量设置多种类型,赋值时可以根据设置的类型来赋值
- 注意:只能赋值指定的类型,如果赋值其他类型就会报错 语法:Type1|Type2|Type3
- 实例:
var val:string|number
val = 12
console.log("数字位"+val)
console.log("字符串为"+val)
- 实例(也可以联合类型作为函数参数使用):
function disp(name:string|string[]){
if(typeof name == 'string'){
console.log(name)
}else{
for (var i = 0; i < name.length; i++){
console.log(name[i])
}
}
}
disp("Runoob")
console.log("输出数组...")
disp(['Runoob', 'Google', 'Taobao', 'Facebook'])
// Runoob
// 输出数组....
// Runoob
// Google
// Taobao
// Facebook
1.3.2 交叉类型
1.3.3 类型断言
function getLength(arg: number | string):number{
return arg.Length // Property 'length' does not exist on type 'number'
}
function getLength(arg: number | string): number{
const str = arg as string
if(str.length){
return str.length
}else{
const number = arg as number
return number.toString().length
}
}
1.3.4 类型别名
- 定义:给类型起个别名
- 相同点:都可以定义对象或函数;都允许继承
- 差异点:interface是TS用来定义对象,type是用来定义别名方便使用;type可以定义基本类型,interface不行;interface可以合并重复生命,type不行
interface Person1{
name: string,
age: number
}
type Person2{
name: string,
age: number
}
const person1: Person1 = {
name: 'lin',
age: 18
}
const person2: Person2 = {
name: 'lin',
age: 18
}
1.3.5 泛型
- 应用场景:定义一个print函数,这个函数的功能是把传入的参数打印出来,再返回这个参数,传入参数的类型是string,函数返回类型是string.1、想支持打印number类型?2、想支持打印数据类型?任意类型? 思考:需要有一个类型解决输入输出可关联的问题
function print(arg:string):string{
console.log(arg)
return arg
}
function print(arg:string | number):string | number{
console.log(arg)
return arg
}
function print(arg:any):any{
console.log(arg)
return arg
}
function print<T>(arg:T):T{
console.log(arg)
return arg
}
- 泛型的语法是<>里面写类型参数,一般用T表示;
- 使用时有两种方法指定类型:1、定义要使用的类型;2、通过TS类型推断,自动推导类型
- (泛型的核心)泛型的作用是临时占位,之后通过传来的类型进行推导
function print<T>(arg:T):T{
console.log(arg)
return arg
}
print<string>('hello') // 定义T为string
print('hello') // TS类型推断,自动推导类型为string
- 泛型工具类型-基础操作符:
- typeof:获取类型
- keyof:获取所有键
- in:遍历枚举类型
- T<K>:索引访问
- extends:泛型约束
2 实战&工程向
2.1 TS实战
- declare:三方库需要类型声明文件
- .d.ts:声明文件定义
- @types:三方库TS类型包
- tsconfig.json:定义TS的配置
2.2 案例
2.2.1 以下代码为什么会提示错误,应该如何解决?
type User = {
id:number;
kind:string;
}
function makeCustomer<T extends User>(u: T): T{
// Error
// Type '{id: number; kind: string}' is not assignable to type 'T'
// '{id: number; kind: string;}' is assignable to the constraint of type 'T'
// but 'T' could be instaniated with a different subtype of constraint 'User'
return {
id: u.id;
kind: 'customer
}
}
出错原因:泛型T只是约束于User类型,并不是局限于User类型,所以返回结果应该还需要接收其他类型变量。
- 解决办法一:T类型兼容User类型
function makeCustomer<T extends User>(u: T): T{
return {
...u,
id: u.id,
kind:'customer'
}
- 解决方法二:返回值类型修改为User类型
function makeCustomer<T extends User>(u: T): User{
return {
id: u.id,
kind: 'customer',
}
}
function makeCustomer<T extends User>(u: T): ReturnMake<T, User>{
return {
id: u.id,
kind: 'customer',
}
}
type ReturnMake<T extends User, U> = {
[k in keyof U as K extends keyof T ? K: never]: U[K]
}
makeCustomer({id:18923323, kind: '888', price: 99})
// 1、ReturnMake工具类型,接收T、U两个泛型,T约束于User
// 2、遍历User中的Key,并使用as断言,如果K(也就是User类型的key),约束于泛型类型的key返回k,否则返回never,U[K]取键值
总结
总的来说,今天了解了为什么学TS,TS增强了类型按弃权,提高生产力,是JavaScript的超集,同时,我在进行了课程学习(包括TS基础类型、函数类型、Interface、Class类)后,还对TypeScript的基础知识进行了学习,扩充了自己对TypeScript的认知。在学习TypeScript的过程中,令我印象最深刻的知识点是TS进阶部分的泛型,可能是因为之前没有接触过,觉得泛型的产生和设计规则非常神奇。

