TS:如何定义 return 可重用链接方法的类型

TS: How to define return typing of reusable chained methods

我有以下代码:

const mixed = {
  validations: [] as any[],
  formattings: [] as any[],
  exceptions: [] as any[],
  required(message?: string) {
    this.validations.push({
      name: 'required',
      message: message || config.messages.mixed.required,
      test: (value: string) => {
        return value && value.trim() ? true : false
      },
    })

    return this
  },
  // other methods that will be reusable
}

const string = () => ({
  ...mixed,
  maxWords(maxWords: number, message?: string) {
    type Params = { maxWords: number }

    this.validations.push({
      name: 'maxWords',
      params: { maxWords },
      message: message || config.messages.string.maxWords,
      test: (value: string, { maxWords }: Params) => {
        const wordCount = value.trim().split(' ').length

        return wordCount <= maxWords
      },
    })

    return this
  },

  trim() {
    this.formattings.push({
      name: 'trim',
      format: (value: string) => {
        return value.trim()
      },
    })

    return this
  },
})

const number = () => ({
  ...mixed,
  positive(message?: string) {
    this.validations.push({
      name: 'positive',
      message: message || config.messages.string.maxWords,
      test: (value: string) => {
        // make a validation
      },
    })

    return this
  },
})

const schema = {
  string() {
    return string()
  },
  number() {
    return number()
  },
  // file, date, etc..
}

const form = {
  name: schema.string().required().trim().maxWords(3),
  age: schema.number().required().positive(),
}

一切都在执行中完美运行,问题是我正在尝试构建一个表单验证库,我需要智能感知处理所有方法。

here's the problem

这里是TS Playground所以你可以实时测试它。

问题出在每个函数的 returns 的输入中

如果在接口中声明方法类型或class,则可以使用required(message?: string): this。精简示例:

interface Validation<T> {
  message: string
  test: (value: T) => boolean
}

interface FormField<T> {
  validations: Validation<T>[];
  required(message: string): this;
}

const mixed: FormField<unknown> = {
  validations: [],
  required(message) {
    this.validations.push({
      message,
      test(value: string) {
        return value.trim()
      }
    })
    return this
  }
}

interface StringField extends FormField<string> {
  maxWords(maxWords: number, message: string): this;
}
const string = (): StringField => ({
  ...mixed as StringField, // this is a bit ugly, would work better if mixed() was a function or superclass
  maxWords(maxWords, message) {
    this.validations.push({
      message,
      test: value => value.trim().split(' ').length <= maxWords,
    })
    return this
  }
})

interface NumberField extends FormField<number> {
  positive(message: string): this;
}

const number = (): NumberField => ({
  ...mixed as NumberField,
  positive(message) {
    this.validations.push({
      message,
      test: value => value > 0,
    })

    return this
  },
})

const form = {
  name: string().required('missing name').maxWords(3, 'too few words'),
  age: number().required('missing age').positive('must be born already'),
}

(TypeScript playground)