TS 快速上手.md
TS 快速上手
安装 TS
npm install -g typescript
测试安装是否成功
tsc -v
第一个 TS 程序
编写 TS 程序
index.ts ts文件不能再浏览器直接执行,需要通过手动或者自动编译为js
但是如果ts文件中没有ts语法而是js语法的话不会报错
(
() => {
// str这个参数是string字符串类型
function sayHi(str:string){
return "字符串" + str ;
}
let test ="are you ready" ;
console.log(sayHi(test));
}
)()
/**
* 总结
* 1、ts文件中如果如果直接书写js语法的代码,那么在html文件中直接引入ts文件,在谷歌浏览器中是可以直接使用的。
* 2、如果ts文件中使用了ts的语法代码,那么就需要把这个ts文件编译成js文件,在HTML文件中引入js的文件来使用。
* 3、ts文件中的函数形参,如果使用了某个类型进行修饰,那么最终在编译的js文件中是没有这个类型的。
* 4、ts文件中的变量使用的是let进行修饰的,编译的js文件修饰符就是var
* 5、形如这样的函数: (function {// code})(); 这样是匿名函数的自执行
*/
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>01-ts的第一次</title>
</head>
<script src="index.ts"></script>
<body>
</body>
</html>
手动编译代码
关键指令
tsc 文件名
vscode 自动编译
1). 生成配置文件tsconfig.json
tsc --init
2). 修改tsconfig.json配置
"outDir": "./js",
"strict": false,
3). 启动监视任务:
终端 -> 运行任务 -> 监视tsconfig.json
类型注解
function greeter (person:string){
return "Hello , " + person;
}
let user = 123 ;
// 限定user类型为字符串,否则报错不兼容
console.log(greeter(user));
类似地,尝试删除 greeter
调用的所有参数。 TypeScript 会告诉你使用了非期望个数的参数调用了这个函数。 在这两种情况中,TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解。
要注意的是尽管有错误,greeter.js
文件还是被创建了。 就算你的代码里有错误,你仍然可以使用 TypeScript。但在这种情况下,TypeScript 会警告你代码可能不会按预期执行。
接口
让我们继续扩展这个示例应用。这里我们使用接口来描述一个拥有 firstName
和 lastName
字段的对象。 在 TypeScript
里,只在两个类型内部的结构兼容,那么这两个类型就是兼容的。 这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以,而不必明确地使用 implements
语句。
interface Person {
firstName: string
lastName: string
}
function greeter (person: Person) {
return 'Hello, ' + person.firstName + ' ' + person.lastName
}
let user1 = {
firstName: 'Yee',
lastName: 'Huang'
}
console.log(greeter(user1))
类
TypeScript 支持 JavaScript 的新特性,比如支持基于类的面向对象编程。
让我们创建一个 User
类,它带有一个构造函数和一些公共字段。因为类的字段包含了接口所需要的字段,所以他们能很好的兼容。
还要注意的是,我在类的声明上会注明所有的成员变量,这样比较一目了然。
class User {
fullName: string
firstName: string
lastName: string
constructor (firstName: string, lastName: string) {
this.firstName = firstName
this.lastName = lastName
this.fullName = firstName + ' ' + lastName
}
}
interface Person {
firstName: string
lastName: string
}
function greeter (person: Person) {
return 'Hello, ' + person.firstName + ' ' + person.lastName
}
let user = new User('Yee', 'Huang')
console.log(greeter(user))
总结
常用语法
基本类型
// 1、布尔值
let isDone: boolean = true ;
isDone=false;
console.log(isDone); // false
// 2、数字
/**
* 和js一样,ts里面所有数字都是浮点数,这些浮点数类型都是number,
* 不仅仅支持十进制还支持二进制八进制十六进制
*/
let a1:number=10 ; // 十进制
let a2:number=0b0101; // 二进制
let a3:number=0o12; // 八进制
let a4:number=0xa; // 十六进制
console.log(a1,a2,a3,a4); // 10 5 10 10
// 3、字符串
/**
* string 标识文本数据类型,和js一样,使用 "" 双引号和''单引号
*/
let na1:string="Jack";
na1="rose";
let ag1:number=88;
const ionfo = `my name is ${na1} , I am ${ag1} years old! `;
console.log(ionfo);
// my name is rose , I am 88 years old!
// 4、undefined 和 null
/**
* 默认情况下,null和undefined是所有类型的子类型,可以把null和undefined赋值给number类型的变量
*/
let u:undefined=undefined;
let n:null=null;
console.log(u,n);
// undefined null
// 5、数组
/**
* 两种方式定义数组
* 1、元素类型接上[]
* 2、数组泛型 Array<元素类型>
*/
let list1: number[] = [1,2,3,4];
let list2: Array<number> = [1,2,3];
console.log(list1,list2);
// [ 1, 2, 3, 4 ] [ 1, 2, 3 ]
// 6、元组
/**
* 元组类型:标识一个已知元素数量和类型的数组,各元素类型不必相同
* 比如 定义一对值分别为string,number类型的元祖
*/
let t1:[string,number];
t1=["hello",23]; // 第一个元素字符串,第二个数值类型
// t1=[23,"3"]; // Type 'string' is not assignable to type 'number'.ts(2322)
console.log(t1);
// 7、枚举类型enum
/**
* 一组数值赋予友好的名字
*/
enum Color {
Red,
Green,
Blue
}
// 枚举数值默认从0开始依次递增
// 根据特定的名称得到对应的枚举数值
let myColor: Color = Color.Green // 0
console.log(myColor, Color.Red, Color.Blue) // 1 0 2
// 全部手动赋值
enum Color1 {Red = 1, Green = 2, Blue = 4}
let c: Color1 = Color1.Green;
let c1: Color1 = Color1.Red;
let c2: Color1 = Color1.Blue;
console.log(c,c1,c2); // 2 1 4
/**
* 枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为 2,
* 但是不确定它映射到 Color 里的哪个名字,我们可以查找相应的名字
*/
enum Color2 {Red = 1, Green, Blue}
let colorName: string = Color2[2]
console.log(colorName) // 'Green'
// 8、any类型
let notSure: any = 4
notSure = 'maybe a string'
notSure = false // 也可以是个 boolean
console.log(notSure);
// 9、object类型
/**
* object 表示非原始类型,也就是除 number,string,boolean之外的类型。
* 使用 object 类型,就可以更好的表示像 Object.create 这样的 API。
*/
function fn2(obj:object):object {
console.log('fn2()', obj)
return {}
// return undefined
// return null
}
console.log(fn2(new String('abc')))
// console.log(fn2('abc') // error
console.log(fn2(String))
// 10、联合类型
/**
* 联合类型(Union Types)表示取值可以为多种类型中的一种
* 需求1: 定义一个一个函数得到一个数字或字符串值的字符串形式值
*/
function toString2(x: number | string) : string {
return x.toString()
}
console.log(toString2(3)); // 3
// 定义一个一个函数得到一个数字或字符串值的长度
function getLength(x: number | string) {
// return x.length // error
if (x.length) { // error
return x.length
} else {
return x.toString().length
}
}
console.log(getLength("dslkdf")); // 6
// 11、类型断言
/**
* 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。
* 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。类型断言有两种形式。 其一是“尖括号”语法, 另一个为 as 语法
*/
/*
类型断言(Type Assertion): 可以用来手动指定一个值的类型
语法:
方式一: <类型>值
方式二: 值 as 类型 tsx中只能用这种方式
*/
/* 需求: 定义一个函数得到一个字符串或者数值数据的长度 */
function getLength1(x: number | string) {
if ((<string>x).length) {
return (x as string).length
} else {
return x.toString().length
}
}
console.log(getLength('abcd'), getLength1(1234))
// 11、类型推断
/**
* 类型推断: TS会在没有明确的指定类型的时候推测出一个类型
* 有下面2种情况: 1. 定义变量时赋值了, 推断为对应的类型. 2. 定义变量时没有赋值, 推断为any类型
*/
/* 定义变量时赋值了, 推断为对应的类型 */
let b9 = 123 // number
// b9 = 'abc' // error
/* 定义变量时没有赋值, 推断为any类型 */
let b10 // any类型
b10 = 123
b10 = 'abc'
函数
基本实例
// 命名函数
function add(x, y) {
return x + y
}
// 匿名函数
let myAdd = function(x, y) {
return x + y;
}
函数类型
为函数定义类型
TypeScript 能够根据返回语句自动推断出返回值类型
function add(x: number, y: number): number {
return x + y
}
// x,y 数值类型;函数返回值类型:number
let myAdd = function(x: number, y: number): number {
return x + y
}
书写完整函数类型
let myAdd2: (x: number, y: number) => number =
function(x: number, y: number): number {
return x + y
}
可选参数和默认参数
TypeScript 里的每个函数参数都是必须的。 这不是指不能传递 null
或 undefined
作为参数,而是说编译器检查用户是否为每个参数都传入了值。编译器还会假设只有这些参数会被传递进函数。 简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。
JavaScript 里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是 undefined
。 在TypeScript 里我们可以在参数名旁使用 ?
实现可选参数的功能。 比如,我们想让 lastName
是可选的:
在 TypeScript 里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是 undefined
时。 它们叫做有默认初始化值的参数。 让我们修改上例,把firstName
的默认值设置为 "A"
。
function buildName(firstName: string='A', lastName?: string): string {
if (lastName) {
return firstName + '-' + lastName
} else {
return firstName
}
}
console.log(buildName('C', 'D'))
console.log(buildName('C'))
console.log(buildName())
剩余参数
在 TypeScript 里,你可以把所有参数收集到一个变量里:
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( ...
)后面给定的名字,你可以在函数体内使用这个数组。
function info(x: string, ...args: string[]) {
console.log(x, args)
}
info('abc', 'c', 'b', 'a')
函数重载
函数重载: 函数名相同, 而形参不同的多个函数
在JS中, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的 但在TS中, 与其它面向对象的语言(如Java)就存在此语法
/*
函数重载: 函数名相同, 而形参不同的多个函数
需求: 我们有一个add函数,它可以接收2个string类型的参数进行拼接,也可以接收2个number类型的参数进行相加
*/
// 重载函数声明
function add (x: string, y: string): string
function add (x: number, y: number): number
// 定义函数实现
function add(x: string | number, y: string | number): string | number {
// 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 x + y
if (typeof x === 'string' && typeof y === 'string') {
return x + y
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y
}
}
console.log(add(1, 2))
console.log(add('a', 'b'))
// console.log(add(1, 'a')) // error
类
传统的 JavaScript 程序我们会使用函数
和基于原型的继承
来创建可重用的组件;
他们用的是基于类的继承
并且对象是由类构建出来的,使用 TypeScript,我们允许开发者现在就使用这些特性,并且编译后的 JavaScript 可以在所有主流浏览器和平台上运行。
基本实例
class Greeter {
// 声明属性
message: string ;
// 构造函数
constructor(message: string) {
this.message = message ;
}
// 普通方法
greet(): string {
return "Helo" + this.message ;
}
}
// 创建类的实例
const greeter = new Greeter("world");
// 调用实例方法
console.log(greeter.greet());
继承
/*
类的继承
*/
class Animal {
run (distance: number) {
console.log(`Animal run ${distance}m`)
}
}
class Dog extends Animal {
cry () {
console.log('wang! wang!')
}
}
const dog = new Dog()
dog.cry()
dog.run(100) // 可以调用从父中继承得到的方法
公共、私有与保护的修饰符
默认public
在上面的例子里,我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解,就会注意到我们在之前的代码里并没有使用 public
来做修饰;例如,C# 要求必须明确地使用 public
指定成员是可见的。 在 TypeScript 里,成员都默认为 public
。
你也可以明确的将一个成员标记成 public
。 我们可以用下面的方式来重写上面的 Animal
类:
理解protected
当成员被标记成 private
时,它就不能在声明它的类的外部访问。
理解protected
protected
修饰符与 private
修饰符的行为很相似,但有一点不同,protected
成员在派生类中仍然可以访问。例如:
/*
访问修饰符: 用来描述类内部的属性/方法的可访问性
public: 默认值, 公开的外部也可以访问
private: 只能类内部可以访问
protected: 类内部和子类可以访问
*/
class Animal {
public name: string
public constructor (name: string) {
this.name = name
}
public run (distance: number=0) {
console.log(`${this.name} run ${distance}m`)
}
}
class Person extends Animal {
private age: number = 18
protected sex: string = '男'
run (distance: number=5) {
console.log('Person jumping...')
super.run(distance)
}
}
class Student extends Person {
run (distance: number=6) {
console.log('Student jumping...')
console.log(this.sex) // 子类能看到父类中受保护的成员
// console.log(this.age) // 子类看不到父类中私有的成员
super.run(distance)
}
}
console.log(new Person('abc').name) // 公开的可见
// console.log(new Person('abc').sex) // 受保护的不可见
// console.log(new Person('abc').age) // 私有的不可见
readonly 修饰符
你可以使用 readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
class Person {
readonly name: string = 'abc'
constructor(name: string) {
this.name = name
}
}
let john = new Person('John')
// john.name = 'peter' // error
存取器
TypeScript
支持通过 getters/setters
来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
class Person {
firstName: string = 'A'
lastName: string = 'B'
get fullName () {
return this.firstName + '-' + this.lastName
}
set fullName (value) {
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
}
const p = new Person()
console.log(p.fullName)
p.firstName = 'C'
p.lastName = 'D'
console.log(p.fullName)
p.fullName = 'E-F'
console.log(p.firstName, p.lastName)
静态属性
到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。 在这个例子里,我们使用 static
定义 origin
,因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候,都要在 origin
前面加上类名。 如同在实例属性上使用 this.xxx
来访问属性一样,这里我们使用 Grid.xxx
来访问静态属性。
/*
静态属性, 是类对象的属性
非静态属性, 是类的实例对象的属性
*/
class Person {
name1: string = 'A'
static name2: string = 'B'
}
console.log(Person.name2)
console.log(new Person().name1)
抽象类
抽象类做为其它派生类的基类使用。 它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。 abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。
/*
抽象类
不能创建实例对象, 只有实现类才能创建实例
可以包含未实现的抽象方法
*/
abstract class Animal {
abstract cry ()
run () {
console.log('run()')
}
}
class Dog extends Animal {
cry () {
console.log(' Dog cry()')
}
}
const dog = new Dog()
dog.cry()
dog.run()
接口
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。我们使用接口(Interfaces)来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)
接口初探
需求: 创建人的对象, 需要对人的属性进行一定的约束
interface IPerson {
id: number
name: string
age: number
sex: string
}
const person1: IPerson = {
id: 1,
name: 'tom',
age: 20,
sex: '男'
}
类型检查器会查看对象内部的属性是否与IPerson接口描述一致, 如果不一致就会提示类型错误。
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。
interface IPerson {
id: number
name: string
age: number
sex?: string
}
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ?
符号。
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
const person2: IPerson = {
id: 1,
name: 'tom',
age: 20,
// sex: '男' // 可以没有
}
函数类型
它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
/*
接口可以描述函数类型(参数的类型与返回的类型)
*/
interface SearchFunc {
(source: string, subString: string): boolean
}
const mySearch: SearchFunc = function (source: string, sub: string): boolean {
return source.search(sub) > -1
}
console.log(mySearch('abcd', 'bc'))
类实现的接口
/*
类类型: 实现接口
1. 一个类可以实现多个接口
2. 一个接口可以继承多个接口
*/
interface Alarm {
alert(): any;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
一个类实现多个接口
class Car2 implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
接口继承接口
interface LightableAlarm extends Alarm, Light {
}
泛型
定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型
引入
下面创建一个函数, 实现功能: 根据指定的数量 count
和数据 value
, 创建一个包含 count
个 value
的数组 不用泛型的话,这个函数可能是下面这样
function createArray(value:any,count:number):any[]{
const arr:any[]=[];
let index = 0;
for(index;index < count ; index ++){
arr.push(value);
}
return arr ;
}
const arr1 = createArray(11,3) ;
console.log(arr1); // [ 11, 11, 11 ]
const arr2 = createArray("33",3)
console.log(createArray("33",3)); // [ '33', '33', '33' ]
console.log(createArray("334",3)); // [ '334', '334', '334' ]
// toFixed(x)方法 Number 四舍五入为指定小数位数的数字。 x 必需。规定小数的位数,
console.log(arr1[0].toFixed(3)); // 11.000
// split(separator,howmany)方法 用于把一个字符串分割成字符串数组,separator:正则表达式
// howmany:该参数可指定返回的数组的最大长度
console.log(arr2[0].split("")); // 33 => [ '3', '3' ]
使用any
类型会导致这个函数可以接收任何类型的arg
参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。如果我们传入一个数字,我们只知道任何类型的值都有可能被返回
使用函数泛型
function createArray <T> (value:T,count:number){
const arr:Array<T> = [];
let index = 0 ;
for(index ; index < count ; index ++){
arr.push(value);
}
return arr ;
}
const arr1 = createArray<number>(11,3);
const arr2 = createArray<string>("11",3);
console.log(arr1[0].toFixed(3)); // 11.000
// console.log(arr1[0].split("")); // Property 'split' does not exist on type 'number'.ts(2339)
console.log(arr2[0].split("")); // [ '1', '1' ]
// console.log(arr2[0].toFixed(4)); // Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?ts(2551)
多个泛型参数的函数
function swap <K, V> (a: K, b: V): [K, V] {
return [a, b]
}
const result = swap<string, number>('abc', 123)
console.log(result[0].length, result[1].toFixed())
泛型接口
在定义接口时, 为接口中的属性或方法定义泛型类型
在使用接口时, 再指定具体的泛型类型
// function createArray(value:any,count:number):any[]{
// const arr:any[]=[];
// let index = 0;
// for(index;index < count ; index ++){
// arr.push(value);
// }
// return arr ;
// }
// const arr1 = createArray(11,3) ;
// console.log(arr1); // [ 11, 11, 11 ]
// const arr2 = createArray("33",3)
// console.log(createArray("33",3)); // [ '33', '33', '33' ]
// console.log(createArray("334",3)); // [ '334', '334', '334' ]
// // toFixed(x)方法 Number 四舍五入为指定小数位数的数字。 x 必需。规定小数的位数,
// console.log(arr1[0].toFixed(3)); // 11.000
// // split(separator,howmany)方法 用于把一个字符串分割成字符串数组,separator:正则表达式
// // howmany:该参数可指定返回的数组的最大长度
// console.log(arr2[0].split("")); // 33 => [ '3', '3' ]
// function createArray <T> (value:T,count:number){
// const arr:Array<T> = [];
// let index = 0 ;
// for(index ; index < count ; index ++){
// arr.push(value);
// }
// return arr ;
// }
// const arr1 = createArray<number>(11,3);
// const arr2 = createArray<string>("11",3);
// console.log(arr1[0].toFixed(3)); // 11.000
// // console.log(arr1[0].split("")); // Property 'split' does not exist on type 'number'.ts(2339)
// console.log(arr2[0].split("")); // [ '1', '1' ]
// // console.log(arr2[0].toFixed(4)); // Property 'toFixed' does not exist on type 'string'. Did you mean 'fixed'?ts(2551)
// function swap <K,V> (a: K , b: V):[K,V]{
// return [a,b];
// }
// const result = swap<string,number>("abc",123);
// console.log(result[0].length , result[1].toFixed()); // 3 123
interface IbaseCRUD<T> {
data: T[];
add: (t: T) => void;
getById: (id: number) => T
}
class User {
id?: number;
name: string;
age: number;
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class UserCRUD implements IbaseCRUD<User> {
data: User[] = [];
add(user: User): void {
// 扩展运算符:一个数组||类数组||字符串转为用逗号分隔的序列
user = { ...user, id: Date.now() };
this.data.push(user);
console.log("保存user", user.id);
}
getById(id: number): User {
// find 返回符合条件的数值,不改变原数组值
return this.data.find(item => item.id === id);
}
}
const userCRUD = new UserCRUD();
userCRUD.add(new User("TOM", 11));
userCRUD.add(new User("TOM1", 121));
console.log(userCRUD.data);
泛型类
在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
GenericNumber
类的使用是十分直观的,并且你可能已经注意到了,没有什么去限制它只能使用number
类型。 也可以使用字符串或其它更复杂的类型。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
与接口一样,直接把泛型类型放在类后面,可以帮助我们确认类的所有属性都在使用相同的类型。
我们在类那节说过,类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
泛型约束
你应该会记得之前的一个例子,我们有时候想操作某类型的一组值,并且我们知道这组值具有什么样的属性。 在 loggingIdentity
例子中,我们想访问arg
的length
属性,但是编译器并不能证明每种类型都有length
属性,所以就报错了。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
相比于操作any所有类型,我们想要限制函数去处理任意带有.length
属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。
为此,我们定义一个接口来描述约束条件。 创建一个包含 .length
属性的接口,使用这个接口和extends
关键字来实现约束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3); // Error, number doesn't have a .length property
我们需要传入符合约束类型的值,必须包含必须的属性:
loggingIdentity({length: 10, value: 3});