解决Duplicate function implementation
原因是两个文件的函数都出在全局范围内。在开头加export {}
,或者导出这个函数解决
参考:Duplicate function implementation
类类型的两种定义
let Ctor: new () => void = class { }
let Ctor1: { new(): void } = class { }
set和get装饰器(访问器装饰器)
- 一个属性set和get只能修饰一个
- 定义这个属性转成ES5是用Object.defineProperty定义的,enumerable默认false
Structural Type System
One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural typing”. In a structural type system, if two objects have the same shape, they are considered to be of the same type.
ts的一个核心原则是类型检查只关注值的切面(shape,不知道准确的翻译)。也叫作鸭子类型
或者结构类型
。在结构类型系统,两个对象有同样的shape,那么将会被考虑为同一种类型
鸭子类型
是美国的一种土语。简单理解就是指鹿为马高级版(所有人看这头鹿像马,那它就是马)
目前我百度到的鸭子类型是方法和属性的集合,比如对象。只关注这个对象可以做什么,比如跑和跳,而不关注它的类型
一个例子:
interface Point {
x: number;
y: number;
}
function logPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}
// logs "12, 26"
const point = { x: 12, y: 26 };
logPoint(point);
const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); // logs "33, 3"
const color = { hex: "#187ABF" };
logPoint(color); // error
虽然没有声明point的类型,但是在类型检查系统对比之后会认为point和Point有相同的shape,因此作为logPoint参数不会报错。shape比对只比对子集,因此最后一个会报错
type和interface的区别
1.Prior to TypeScript version 4.2, type alias names may appear in error messages, sometimes in place of the equivalent anonymous type (which may or may not be desirable). Interfaces will always be named in error messages
在4.2版本之前,类型别名可能会出现在错误信息中,有时会代替匿名类型(时会时不会)。而interface始终会在错误信息中显示name
也就是说这个区别在最新版本已经不是问题了
例子:
// Compiler error messages will always use
// the name of an interface:
interface Mammal {
name: string
}
function echoMammal(m: Mammal) {
console.log(m.name)
}
// e.g. The error below will always use the name Mammal
// to refer to the type which is expected:
echoMammal({ name: 12343 }) // The expected type comes from property 'name' which is declared here on type 'Mammal'
// When a type hasn't gone through any form of manipulation
// then you still get the name as a reference:
type Lizard = {
name: string
}
function echoLizard(l: Lizard) {
console.log(l.name)
}
// So this still refers to Lizard
echoLizard({ name: 12345}) // The expected type comes from property 'name' which is declared here on type 'Mammal'
// But when a a type has been transformed, for example via this
// Omit then the error message will show the resulting type
// and not the name
type Arachnid = Omit<{ name: string, legs: 8 }, 'legs'>
function echoSpider(l: Arachnid) {
console.log(l.name)
}
// 4.0.5: The expected type comes from property 'name' which is declared here on type 'Pick<{ name: string; legs: 8; }, "name">'
// 4.2.3: The expected type comes from property 'name' which is declared here on type 'Arachnid'
echoSpider({ name: 12345, legs: 8})
2.Type aliases may not participate in declaration merging, but interfaces can.
interface可以进行类型合并(相当于增加一个新属性),type(类型别名)不行
3.Interfaces may only be used to declare the shapes of object, not re-name primitives. interface只能用于声明对象的形状(我理解为属性),不能重命名原始属性(比如extends string)
可以自由使用这两种类型声明,但是如果想省事就用interface,除非必须使用type
type predicates
类型谓词,不知道更好的翻译
type Fish = { swim: () => void };
type Bird = { fly: () => void };
interface Human { jump: () => void };
declare function getSmallPet(): Fish | Bird | Human;
function isFish(pet: Fish | Bird | Human): pet is Fish {
return (pet as Fish).swim !== undefined;
}
这里不会关注函数的返回值表达式,也就是说不关注函数到底干了什么,只关注函数必须返回一个布尔值,返回true,后续的类型narrow(缩小),就会把传参(pet)当做Fish类型,否则为其它的类型
比如:
let pet = getSmallPet();
if (isFinsh(pet)) {
pet.swim();
} else if ('fly' in pet) {
// 使用in 操作符进行narrow
pet.fly
} else {
pet.jump();
}
这里通过isFinsh来判断pet是否为Finsh,所以第一个if里的pet会推断为Finsh类型,之后通过in操作符来进一步narrow,如果pet具有fly属性,那么pet就是Bird类型,最后可以推断出其它情况pet为Human类型
泛型generic
使用泛型的几个原则:
- When possible, use the type parameter itself rather than constraining it 尽可能使用类型参数而非约束它(比如使用Type[]而不是Type extends any[])
- Always use as few type parameters as possible 总是使用尽量少的类型参数
- If a type parameter only appears in one location, strongly reconsider if you actually need it 如果一个类型参数只出现一次,考虑是否真的需要它
重载overload
写好重载的几个原则:
- Always prefer parameters with union types instead of overloads when possible 在可能的情况下,总是使用联合类型代替重载
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
return x.length;
}
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]); // error ts只能将函数解析为一个重载
这个例子中两个函数只有一个参数并且具有同样的返回值,因此没必要写成重载的形式,下面的形式更好:
function len(x:any[]|string){
return x.length;
}
Indexed Access Types
可以直接用索引来获取类型
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];
可以使用联合类型、keyof或其它
type I1 = Person["age" | "name"];
type I1 = string | number
type I2 = Person[keyof Person];
type I2 = string | number | boolean
type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName];
Conditional Type(条件类型)
Distributive Conditional Type(分配条件类型)
// Distributive Conditional Types
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // StrArrOrNum => string[] | number[]
通常,分配性(distributivity)是默认的行为(desired behavior)。为了避免此行为,可以在extends的两边加上[]
type ToArray<Type> = [Type] extends [any] ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // StrArrOrNum => (string | number)[]
Mapped Types(映射类型)
基本用法
可以用Mapped Types
基于索引签名语法,用于声明尚未提前声明的属性类型
type OptionsFlag<Type> = {
[Property in keyof Type]: boolean
}
type FeatureFlags = {
darkMode: ()=>void;
newUserProfile: ()=>void;
}
type FeatureOptions = OptionsFlag<FeatureFlags>; // FeatureOptions => { darkMode: boolean; newUserProfile: boolean; }
属性类型全部变更为boolean,而不用重新声明一遍。
Mapping Modifiers
映射类型有两个操作符,+
和-
,用于增加删除readonly/?
,省略默认为+
// removes/adds readonly
type CreateMutable<Type> = {
-readonly [P in keyof Type]: Type[P];
}
type CreateUnmutable<Type> = {
readonly [P in keyof Type]: Type[P];
}
type LockedAccount = {
readonly id: string;
readonly name: string;
}
type GoUnlockedAccount = CreateMutable<LockedAccount>; // GoUnlockedAccount => { id: string, name: string }
type ResetLocked = CreateUnmutable<GoUnlockedAccount>; // ResetLocked => { readonly id: string, readonly name: string }
// removes/adds ?
type Concreate<T> = {
[P in keyof T]-?: T[P];
}
type Mutable<T> = {
[P in keyof T]?: T[P];
}
type MaybeUser = {
id: string;
name?: string;
age?: number;
}
type User = Concreate<MaybeUser>;
type Muser = Mutable<User>;
Key Remapping via as
可以通过as关键字来重新定义输入type,并且可以使用模板字符串的形式,比如:
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: T[P];
}
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
// 其中,Capitalize返回模板字符串首字母大写。定义如下,注意的是这是系统提供的,intrinsic不能用于自己定义
type Capitalize<T extends string> = intrinsic
通过条件类型生成never
关键字来过滤keys
// Remove the 'kind' property
type RemoveKindField<T> = {
[P in keyof T as (P extends 'kind' ? never : P)]: T[P]
}
interface Circle {
kind: 'circle';
radius: number;
}
type KindlessCircle = RemoveKindField<Circle>
// 其中括号内的内容可以声明为一个专门的类型(ts系统有提供)
type MyExclude<T, U> = T extends U ? never : T;
另一个例子,判断对象是否包含pii设置为true
type ExtractPII<T> = {
[P in keyof T]: T[P] extends { pii: true } ? true : false;
}
type DBFields = {
id: { format: 'incrementiong' };
name: { type: string, pii: true }
}
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
Template Literal Types(模板字符类型)
模板字符串的威力体现在基于现有字符串扩展新字符串
type PropEventSource<T> = {
on(eventName: `${string & keyof T}Changed`, callback: (newVal: any) => void): void;
}
// 通过改变返回类型添加了on函数类型,且第一个参数只能为xxxChanged
declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;
const person = makeWatchedObject({
firstName: 'Saoirse',
lastName: 'Ronan',
age: 26,
})
person.on('firstNameChanged', (newVal: any) => {
console.log(`firstName was changed to ${newVal} `)
})
更进一步,newVal
替换为和对象key相关联的类型
type PropEventSource<T> = {
on<Key extends string & keyof T>(eventName: `${Key}Changed`, callback: (newVal: T[Key]) => void): void;
}
declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>
const person = makeWatchedObject({
firstName: 'xzdry',
lastName: 'ddd',
age: 26
});
person.on('ageChanged', newAge=>{}); // newAge: number
person.on('firstNameChanged', newName=>{}); // newName: string
Intrinsic String Manipulation Types(内在字符串操作类型)
这是一些TS为了帮助字符串操作而内置于编译器(为了性能)的类型
Uppercase
字符串全部变大写
type Greeting = 'Hello world';
type ShoutyGreeting = Uppercase<Greeting> // type ShoutyGreeting = "HELLO WORLD"
type ASCIICacheKey<S extends string> = `ID-${Uppercase<S>}`;
type MainID = ASCIICacheKey<'my_app'>; // type MainID = "ID-MY_APP"
Lowercase
与Uppercase相反,Lowercase
将字符串转换成小写
Capitalize/Uncapitalize (首字母大/小写)
type LowercaseGreeting = 'hello world';
type Greeting = Capitalize<LowercaseGreeting>; // type Greeting = "Hello world"
type UncomfortableGreeting = Uncapitalize<Uppercase<LowercaseGreeting>>; // type UncomfortableGreeting = "hELLO WORLD"