typescript 不常用但是很好用的类型声明方式

338 阅读4分钟

基础的泛型声明

function identity<Type>(arg: Type): Type {
    return arg;
}
var test = identity({
   name: 'tom',
   age: 18
});
test.name;        // string
test.age;         // number
var test2: string = identity('aa');            正确
var test3: number = identity('aa');            //错误

实现vu3里钩子的函数类型定义

type Ref<T> = { value: T };
function ref<T>(params?: T): Ref<T | never> {
  return {} as any;
}
function reactive<T>(params: T): T {
  return {} as any;
}


const data = reactive({ name: 'tomg' });
data.name;
const $el = ref<{ name: string, age: 14 }>();
$el.value;
$el.value.age;
const $user = ref({ name: '111111', age: 1 });
$user.value.name;


防止获取object时候输入错误key

function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
    return obj[key];
}
let x = { a: 1, b: '2', c: 3, d: 4 };
getProperty(x, 'b');       // 正确使用
getProperty(x, 'e');       // 会提示值不存在

前置不定量参数


type JSTypes = 'string' | 'function' | 'number' | 'symbol' | 'object' | 'undefined' | 'boolean' | 'bigint'

type JSTypeMap = {
    'string': string
    'function': Function,
    'number': number,
    'symbol': Symbol,
    'object': Object,
    'undefined': undefined,
    'boolean': 'boolean',
    'bigint': BigInt
}
type ArgsType<T extends JSTypes[]> = {
    [key in keyof T]: JSTypeMap[T[key]]
}
declare function addImpl<T extends JSTypes[]>(...args: [
    ...T,
    (...args: ArgsType<T>) => void
]) : void;


addImpl('string', 'number', function(a, b) {
    
})

创建new Class

function create<Type, Param>(c: { new (...argv: Param[]): Type }, argv: Param[]): Type {
    return new c(...argv);
}
class Test {
    name: string = '';
}
var $test: Test = create(Test);
$test.name;

模板字符串类型

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;

type Lang = "en" | "ja" | "pt";

type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;

基础映射类型

type OptionsFlags<Type> = {
    [Property in keyof Type]: boolean;
};

映射修饰符

添加-或前缀来删除或添加这些修饰符+

type CreateMutable<Type> = {
    -readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
    readonly id: string;
    readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
-----
// 通过-?,删除修饰符,使所有字段都必须有
type Concrete<Type> = {
    [Property in keyof Type]-?: Type[Property];
};
type MaybeUser = {
    id: string;
    name?: string;
    age?: number;
};
type User = Concrete<MaybeUser>;

键重映射通过类型

type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
interface Person {
    name: string;
    age: number;
    location: string;
}
type LazyPerson = Getters<Person>;
// 通过Getters输出的类型
type LazyPerson = { 
    getName: () => string; 
    getAge: () => number; 
    getLocation: () => string; 
}
// ------
//`never`通过条件类型生成来过滤键
type RemoveKindField<Type> = {
    [Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
interface Circle {
    kind: "circle";
    radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
// 通过RemoveKindField输出的类型
type KindlessCircle = { 
    radius: number; 
}

练习


type CreateClassProperty<Type> = {
    [Property in keyof Type as `$${string & Property}Change`]: (
        fn: (value: Type[Property]) => void
    ) => void
} & {
    [Property in keyof Type as `set${Capitalize<string & Property>}`]: (value: Type[Property]) => void
} & Readonly<Type>
function çreateClass<T>(props: T): CreateClassProperty<T> {
    // ... 忽略拓展逻辑
    return props as any
}

const user = çreateClass({
    name: 'tom',
    age: 14
})
user.$nameChange(name => console.log(name));
user.$ageChange(age => console.log(age));
user.setAge(123);
user.age

根据输入值判断获取类型返回值

interface IdLabel {
    id: number /* some fields */;
}
interface NameLabel {
    name: string /* other fields */;
}
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel;
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
    throw "unimplemented";
}
let a: NameLabel = createLabel("typescript");
let b: IdLabel = createLabel(666);

多类型合并

type AllKeys<T> = T extends any ? keyof T : never
type PickType<T, K extends AllKeys<T>> = T extends { [k in K]: any }
    ? T[K]
    : undefined
type PickTypeOf<T, K extends string | number | symbol> = K extends AllKeys<T>
    ? PickType<T, K>
    : never
type Merge<T extends object> = {
    [k in AllKeys<T>]: PickTypeOf<T, k>
}
// 演示
// error: 类型 "{ name: string; }" 中缺少属性 "age",但类型 "Merge<{ name: string; } | { age: number; }>" 中需要该属性。
const ok: Merge<{name:string} | {age: number}> = {
    name: '',
}
// success
const ok: Merge<{name:string} | {age: number}> = {
    name: '',
    age: 14
}

参考: dev.to/lucianbc/un…

多类继承

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
  ) => void
    ? I
    : never;

type Constructor<T = {}> = new (...args: any[]) => T;
function Extend<T extends Constructor[]>(...classList: T): Constructor<UnionToIntersection<InstanceType<T[number]>>> {
    let Class = class {};
    classList.forEach(fn => {
        Class = class extends fn {
            constructor(...argv: any[]) {
                super(...argv);
            }
        }
    })
    return Class as any;
}
class Name {
    constructor(name: string) { }
    getName() {

    }
}
class Age {
    constructor(age: number) { }
    sx = 'tom';
    /**获取年龄 */
    getAge() {
        return this.sx
    }
}

class People extends Extend4(Name, Age) {
    dq() {
        return 'dq'
    }
}
const zs = new People();
zs.getName;
zs.getAge;
zs.sx;
console.log(zs, zs.sx, zs.getAge(), zs.dq())

条件类型约束

type MessageOf<T extends { message: unknown }> = T["message"];
interface Email {
    message: string;
}
type EmailMessageContents = MessageOf<Email>;
----------------
// 设置如果不存在就换成其他类型
type MessageOf2<T> = T extends { message: unknown } ? T["message"] : string;
interface Dog {
  bark(): void;
}
type DogMessageContents = MessageOf2<Dog>;
//   DogMessageContents: string

分配条件类型

type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>;
// StrArrOrNumArr类型是: string[] | number[]
// 通常,分配性是所需的行为。为避免这种行为,您可以`extends`用方括号将关键字的每一侧括起来
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never;
type StrOrNumArr = ToArrayNonDist<string | number>;
// 此时StrOrNumArr类型就变成了(string | number)[]

将object内具体的值声明为类型

const People = [
    { name: '张三', age: 18 },
    { name: '李四', age: 32 },
    { name: '王五', age: 24 }
] as const;
type PeopleName = {
    readonly [key in typeof People[number]['name']]: string;
};
type PeopleAge = {
    readonly [key in typeof People[number]['age']]: number;
};
type PeopleNameValue = typeof People[number]['name'];
// PeopleNameValue类型为"张三" | "李四" | "王五"
type PeopleAgeValue = typeof People[number]['age'];
// PeopleAgeValue类型为18 | 32 | 24

修改object 的this执行,实现vue结构的this执行

type PageType<D, M> = {
  data: D;
  methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
}
function Page<D,M>(param: PageType<D, M>): D & M {
  let data: object = param.data || {};
  let methods: object = param.methods || {};
  return { ...data, ...methods } as D & M;
}

Page({
  data: {
      name: '张三',
      age: 14
  },
  methods: {
      say() {
          this.name;
      }
  }
})

实现类似react ui库material-ui中makeStyles的类型声明

/**
 * 简单的定义css类型
 * 项目中使用建议换成csstype
 * @link https://www.npmjs.com/package/csstype
 */
interface StyleAttribute {
    width: string
    height: string
}
type PropsFunc<Props extends object, T> = (props: Props) => T;
/**
 * Partial是ts内置的类型转换,将所有必须的输入项换成不必须的,与之相反的是Required
 * @link https://www.typescriptlang.org/docs/handbook/utility-types.html
 */
type StyleRules<Props extends object = {}, ClassKey extends string = string> = Record<
    ClassKey, 
    Partial<StyleAttribute> | PropsFunc<Props, Partial<StyleAttribute>>
>
function maskStyle<
    ClassKey extends string, 
    Props extends {}
>(params: StyleRules<Props, ClassKey>): StyleRules<Props, ClassKey> {
    // 这里进行各种处理
    return {} as any;
}
var styles = maskStyle({ 
    name: { width: '' },  
    theme: () => {
        return { width: '', height: '' }
    }
})

继续上一个换成传入function方式定义

interface ThemeData {
    width: string,
    height: string
}
type MaskStyleFunction<ClassKey extends string, Props extends {}> = (param) => StyleRules<Props, ClassKey>
function maskStyle2<ClassKey extends string, Props extends {}>(style: MaskStyleFunction<ClassKey, Props>): () => Record<ClassKey, string> {
    // 这里进行各种处理
    return {} as any;
}
var useStyles = maskStyle2((theme: ThemeData) => {
    return {
        name: {
            width: theme.width
        },  
        theme: () => {
            return { width: '', height: '' }
        }
    }
})

const styles = useStyles();
styles.name;

实用程序类型

/**
 * 官方文档地址
 * @link https://www.typescriptlang.org/docs/handbook/utility-types.html
 */
// 将全部变为可不填写
type Partial_Test = Partial<Todo>;
// 将全部变为必须填写
type Required_Test = Required<Todo>;
// 将全部变为只读
type Readonly_Test = Readonly<Todo>;
// 让第一个传入的值都变为第二个传入参数的类型
type Record_Test = Record<"first1"|"first2", Todo>;
// 在第一个值中不包含第二个传入的字段全部删掉,组成新的类型
type Pick_Test = Pick<Todo, 'title' | 'completed'>;
// 与Omit相反,在第一个值中删除第二个参数包含的字段,组成新的类型
type Omit_Test = Omit<Todo, 'title'>;
// 排除第二个传值中包含的类型
type Exclude_Test = Exclude<"a" | "b" | "c", "a">
type Exclude2_Test = Exclude<string | number | (() => void), Function|number>;
// 与Exclude相反,返回第二个传值中包含的类型
type Extract_Test = Extract<"a" | "b" | "c", "b" | "c">;
// 去除类型中的null和undefined
type NonNullable_Test = NonNullable<string | null | undefined>;
// Parameters提取function的参数组成一个新的type
declare function f1(arg: { a: number; b: string }): Todo;
type Parameters_Test = Parameters<typeof f1>;
type Parameters_Test2 = Parameters<(s: string) => void>;
// ReturnType是获取function的返回值组成一个新的type
type ReturnType_Test = ReturnType<() => string>;
type ReturnType_Test1 = ReturnType<typeof f1>;
// ConstructorParameters用于获取构造函数内的值并生成一个新的type
class TestConstructorParameters { constructor(name: string,age: number) {} }
type ConstructorParameters_Test = ConstructorParameters<typeof TestConstructorParameters>;
type InstanceType_tset = InstanceType<typeof TestConstructorParameters>;
// ThisParameterType用于提取function的this类型,如果没有则用位置this
function toHex(this: Number) {
  return this.toString(16);
}
function numberToString(n: ThisParameterType<typeof toHex>) {
  return toHex.apply(n);
}
// OmitThisParameter不太明白,可以具体查看文档
// ThisType在上述的【修改object 的this执行,实现vue结构的this执行】中也有相关代码
// 充当上下文this类型的标记

const stringValue = 'adAwdcawd';
// 将字符串转为大写
type Uppercase_test = Uppercase<typeof stringValue>;
// 将字符串转为小写
type Lowercase_test = Lowercase<typeof stringValue>;
// 将首字母转为大写
type Capitalize_test = Capitalize<typeof stringValue>;
// 将首字母转为小写
type Uncapitalize_test = Uncapitalize<"AcfCff">;
// 获取promise的返回值类型,4.5版本新增
type A = Awaited<Promise<string>>;

在Function上创建静态属性和方法

interface jQuery {
    (): void;
    ajax: (config: any) => void
    get: (config: any) => void
}
const jQuery: jQuery = () => {

}
jQuery.ajax = (data: any) => {
    
}
jQuery.get = (data: any) => {
    
}

只做参考学习使用