有没有一种方法可以从作为泛型传递的接口中推断特定值的类型?

时间:2019-11-06 20:45:54

标签: reactjs typescript

由于我是TS新手,我很可能会错误地处理此问题,但是我想知道我是否可以通过某种通用类型来传递看起来像{[key: string]: string | boolean}的接口?可能知道使用该泛型的函数中任何特定值的类型。

例如,我有一个类似的界面:

export interface Address {
    addressId: string;
    firstName: string;
    lastName: string;
    addressType: string;
    addressLine1: string;
    addressLine2: string;
    city: string;
    state: string;
    country: string;
    zipCode: string;
    email1: string;
    phone1: string;
    primary: boolean;
    nickName?: string;
}

我正在尝试做这样的事情:

function useForm<T>(initialValues: T) {
  const [form, setForm] = useState<T>(initialValues);

  const field = (key: keyof T) => {

    const setValue = (newValue: T[keyof T]) => {
      const newState = {
        ...form,
        [key]: newValue
      }
      setForm(newState);
    }
    const value = form[key];
    return {
      value,
      setValue
    };
  }

  return {
    form,
    field,
  };
}

我的textField的界面看起来像这样

interface TextFieldProps {
  field: {
    setValue: (value: string) => void;
    value: string;
  };
  label?: string;
  placeholder?: string;
  containerStyle?: ViewStyle;
}

在我的表单中看起来像

const {form, field} = useForm<Address>(someInitialAddress);
...
<TextField field={form("FirstName")} />
...

如果您尝试加载接口上不存在的字段,则已经抛出了类型错误,但是我想提防确保您没有将基于布尔值的值或其他内容加载到该字段中,并且当前出现此类型错误: Type '{ value: string | boolean | undefined; setValue: (newValue: string | boolean | undefined) => void; }' is not assignable to type '{ setValue: (value: string) => void; value: string; }'.

任何帮助将不胜感激,谢谢!

2 个答案:

答案 0 :(得分:0)

所以TypeScript可能还有其他一些开箱即用的功能,但是似乎这个问题已经通过现有的实用程序类型解决了。

这篇文章:https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c

您可以为此定义一个类型,例如SubType<T, A>,该类型将使用T类型的一组键(属于A类型)构造一个新类型。我很确定这就是您所要的。

SubType定义-这会将类型never分配给非A类型的属性,因此将不允许使用它们并在智能感知等方面显示编译错误。

export type SubType<A, T> = Pick<A, {
  [K in keyof A]: A[K] extends T ? K : never
}[keyof A]>;

然后您可以在setValue函数的情况下像这样使用它:

const setValue = (newValue: T[keyof SubType<T, string>]) => {
  const newState = {
    ...form,
    [key]: newValue
  }
  setForm(newState);
}

使用下面的界面Test-您应该看到以下类型检查行为:

export interface Test {
  stringKey: string;
  booleanKey: boolean;
  numberKey: number;
}

this.setValue(true); // Err true is not assignable to type string
this.setValue(5); // Err 5 is not assignable to type string
this.setValue('string'); // No Error

唯一的问题是,您仍然可以正确地传递NULL和Undefined。如果您阅读链接文章中的评论,则可以对上述解决方案进行一些调整,我认为可以解决该问题-如果有时间,我将对其进行编辑。

希望它会有所帮助-感谢您的挑战!

答案 1 :(得分:0)

这似乎可以满足我的需求:

function useForm<T>(initialValues: T) {
  const [form, setForm] = useState<T>(initialValues);

  function field<K extends keyof T>(key: K) {
    function setValue<PropType extends T[K]>(newValue: PropType) {
      const newState = {
        ...form,
        [key]: newValue,
      };
      setForm(newState);
    };
    const value = form[key];
    return {
      value,
      setValue,
    };
  };

  return {
    form,
    field,
  };
}

当这样使用时:

interface Address {
    addressId: string;
    firstName: string;
    lastName: string;
    addressType: string;
    addressLine1: string;
    addressLine2: string;
    city: string;
    state: string;
    country: string;
    zipCode: string;
    email1: string;
    phone1: string;
    primary: boolean;
    nickName?: string;
}

...
const { field, onSubmit } = useForm<Address>(someInitialAddress);
...
<TextField
  field={field('firstName')}
  label="First Name"
  placeholder="First Name"
/>

This returns the field prop as:
field: {
  setValue(value: string): void;
  value: string;
};

...

<CheckboxField
  field={field('primary')}
  label="Use as my primary address for Billing and Shipping"
/>
This returns the field prop as:
field: {
  setValue(value: boolean): void;
  value: boolean;
};