首页 > 解决方案 > Typescript如何使用接口的属性来约束函数参数

问题描述

大家好,我正在尝试找到一种方法来约束函数参数,因此它只需要将“字符串”约束到接口属性,就像我在函数验证字段中所做的那样:

注意:这只是使问题更简单的定义打字稿代码。

索引.d.ts

export interface FieldErrors {
  errors: Array<FieldError>;
}

export type FieldsErrors<TForm> = {
  [k in keyof TForm]: FieldErrors;
}

export interface ValidateFieldsCallback<TForm> { (fieldsErrors: FieldsErrors<TForm>, values: TForm): void; };

export interface FormShape<TForm> {
  getFieldValue(fieldName: 'documentNumber' | 'userName'): void; // HOW TO FIX THIS
  validateFields(callback: ValidateFieldsCallback<TForm>): void;
  validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

例子.ts

interface SignupForm {
    documentNumber: number;
    userName: string;
}

const testForm = <FormShape<SignupForm>>{};

testForm.validateFields((values) => {
    console.log(values.documentNumber); // OK
    console.log(values.userName); // OK
    console.log(values.other); // ERROR
});

// THIS SHOULD BE FIXED
const documentNumber = testForm.getFieldValue('documentNumber');

如您所见,我可以约束validateFields回调的参数fieldsErrors类型,但我需要修复函数getFieldValue以仅接受基于接口属性的“正确”字段名称,并且还返回正确的基于类型关于接口类型也不为空。

任何帮助将不胜感激。

标签: typescriptecmascript-6

解决方案


keyof T在映射类型中使用,但keyof T它本身是一种类型,可以在接受类型的任何上下文中使用。keyof T表示 type 的所有键的联合T,这似乎正是您要寻找的。

要使返回类型与字段的类型相同,您需要添加一个类型参数getFieldValue并使用类型查询来返回该字段的类型。

export interface FormShape<TForm> {
    getFieldValue<K extends keyof TForm>(fieldName: K): TForm[K]; // Accepts only keys of TForm and returns the value of teh field
    validateFields(callback: ValidateFieldsCallback<TForm>): void;
    validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

关于阵列,可能有几种方法。如果您只想要简单的数组,您可以将 fieldname 设为数组。

export interface FormShape<TForm> {
    getFieldValue<K extends keyof TForm>(...fieldName: K[]): TForm[K][] ; 
    validateFields(callback: ValidateFieldsCallback<TForm>): void;
    validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

这确实有一个缺点,即您在每个索引上都失去了类型安全性,理想情况下,我们希望接收一个元组并返回一个元组。为此,我们可以添加几个重载:

export interface FormShape<TForm> {
    getFieldValue<K extends keyof TForm, K1 extends keyof TForm, K2 extends keyof TForm>(fieldName: [K, K1, K2]): [TForm[K], TForm[K1], TForm[K2]]; 
    getFieldValue<K extends keyof TForm, K1 extends keyof TForm>(fieldName: [K, K1]): [TForm[K], TForm[K1]]; 
    getFieldValue<K extends keyof TForm>(fieldName: [K]): [TForm[K]]; 
    validateFields(callback: ValidateFieldsCallback<TForm>): void;
    validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

另外一个不错的选择是不返回数组,而是返回一个包含作为参数传入的键的对象:

export interface FormShape<TForm> {
    getFieldValues<K extends keyof TForm>(...fieldName: K[]): { [P in K]: TForm[P] }; // Accepts only keys of TForm and returns the value of teh field
    validateFields(callback: ValidateFieldsCallback<TForm>): void;
    validateFields(fieldsNames: Array<string>, callback: ValidateFieldsCallback<TForm>): void;
}

//Usage
const documentNumber = testForm.getFieldValues('documentNumber', 'userName'); //{documentNumber: number;userName: string;}

推荐阅读