Есть ли способ использовать строковые литералы объекта, реализующего интерфейс с индексными типами, свойства в другом интерфейсе? - PullRequest
0 голосов
/ 20 апреля 2019

Я пытаюсь построить конструктор форм для React. Для первой версии функции включают в себя абстрагирование состояния формы, возврат его при отправке формы и проверку обязательных полей.

Для последней функции мне нужно определить интерфейс без известных имен свойств, но объекты свойств должны иметь определенную форму. Поэтому я использую типы индексов. Но поскольку мне нужно связать поле со свойством состояния, я бы хотел, чтобы средство проверки типов проверило, существует ли это имя свойства. Кто-нибудь знает способ сделать это?

Сейчас он не проверяет, существует ли имя свойства, он просто использует строку, которая во время выполнения должна соответствовать правому свойству в состоянии.

Я использую последнюю версию TypeScript (на момент написания в любом случае), которая является 3.4.4.

Вот что у меня сейчас:

interface Form {
  (defaultState: FormState, fields: any[], options: FormOptions): (newState: FormState) => JSX.Element
}

interface FormState {
  [key: string]: FormStateItem
}

interface FormStateItem {
  value: any
  required: boolean
}

interface FormOptions {
  title: string
  buttonText: string
  formClass?: string
  wrapperClass?: string
  showTitle?: boolean
}

interface Field<T> extends FieldFunction<T>, FieldOptions<T> {}

interface FieldFunction<T> {
  (value: T, callback: FieldCallback<T>): JSX.Element
}

interface FieldOptions<T> {
  id: string
  setId: (id: string) => Field<T>
}

interface FieldCallback<T> {
  (newValue: T): void
}

interface FieldWrapper {
  <T>(options: any): Field<T>
}

interface FieldBuilder {
  <T>(callback: FieldCallback<T>): Field<T>
}

Я хочу, чтобы функция setId проверяла тип свойства FormState как строковый литерал, если это возможно. Было бы здорово, если бы это можно было проверить во время компиляции, а не во время выполнения.

Если я не задал этот вопрос правильно, или он мог бы быть лучше, дайте мне знать. Я не задавал вопрос здесь раньше. : P

Edit:

Я выяснил, как позволить проверке типов проверять, является ли пользователь набора библиотек правильным свойством состояния в setId, используя сопоставленные типы. Однако TypeScript не может определить типы при реализации интерфейса или типа.

Итак, новый дизайн такой:

interface Form {
  <State>(
    defaultState: FormState<State>,
    fields: Field<any, State>[],
    options: FormOptions
  ): (callback: FormCallback<State>) => JSX.Element
}

interface FormCallback<State> {
  (newState: FormState<State>): void
}

type FormState<State> = { [K in keyof State]: FormStateItem<any> }

type FormStateItem<T> = T

interface FormOptions {
  title: string
  buttonText: string
  formClass?: string
  wrapperClass?: string
  titleClass?: string
  showTitle?: boolean
}

interface Field<T, S> extends FieldFunction<T>, FieldOptions<T, S> {}

interface FieldFunction<T> {
  (
    value: T,
    callback: FieldChangeCallback<T>,
    validationCallback: FieldChangeCallback<T>,
    state: FieldItemState
  ): JSX.Element
}

interface FieldOptions<T, S> {
  id: keyof S
  setId: (id: keyof S) => Field<T, S>
  required: boolean
  validationCallback: FieldValidationCallback<T>
}

interface FieldChangeCallback<T> {
  (newValue: T): void
}

interface FieldWrapper {
  <T, S>(
    options: FieldWrapperOptions,
    parse: Parser<T>,
    validationCallback: FieldValidationCallback<T>
  ): Field<T, S>
}


interface FieldWrapperOptions {
  name: string
  required: boolean
  className?: string
  placeholder?: string
  wrapperClass?: string
  labelClassName?: string
  successText?: string
  invalidText?: string
  emptyText?: string
}

interface FieldBuilder {
  <T, S>(
    fieldFunction: FieldFunction<T>,
    validationCallback: FieldValidationCallback<T>,
    required: boolean
  ): Field<T, S>
}

interface FieldValidationCallback<T> {
  (state: FormStateItem<T>): boolean
}

Это пример того, как он будет использоваться:

type FormExampleState = {
  name: string
  age: '' | number
}

const defaultFormState: FormExampleState = {
  name: '',
  age: ''
}

const exampleForm = form<FormExampleState>(
  defaultFormState,
  [
    text<string, FormExampleState>(
      {
        name: 'Name',
        required: true
      },
      value => value,
      state => state.length > 0
    ).setId('name'),
    text<'' | number, FormExampleState>(
      { name: 'Age', required: true },
      value => parseInt(value),
      state => state.toString.length > 0
    ).setId('age')
  ],
  {
    title: 'Form Example 1',
    buttonText: 'Send'
  }
)

/* And inside a React component */
exampleForm(newState => console.log(newState))

Итак, я предполагаю, что возникает вопрос: как я могу спроектировать это так, чтобы включить вывод типа TypeScript?

...