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

У меня есть функция createModule, которая просто возвращает свой параметр:

function createModule(obj) {
  return obj
}

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

interface Mod1State {
  p1: string
}

const mod1 = createModule({
  namespaced: true,
  state: {
    p1: "abc"
  } as Mod1State,
  mutations: {
    SET_P1(state, p1: string) {
      state.p1 = p1
    }
  }
} as const)

// 'mod1' must be of type: '{ namespaced: true, state: Mod1State, mutations: { SET_P1(state: any, p1: string): void } }'

Пока все просто:

function createModule<T>(obj: T): T {
  return obj
}

Теперь я хотел бы добавить автозаполнение в параметр state из SET_P1. И я бы предпочел проверить свойство state вместо его приведения.

    SET_P1(state, p1: string) {
      // Here, 'state' should be of type Mod1State
    }

Вот что я пробовал:

function createModule<S, T extends WithState<S> = WithState<S>>(obj: VuexModule<T, S>): T {
  return obj
}

interface WithState<S> {
  state?: S
}

type VuexModule<T extends WithState<S>, S = T["state"]> = T & {
  namespaced?: boolean
  state?: S
  mutations?: {
    [K: string]: (state: S, payload: any) => void
  }
}

Это работает только если я удалю as const (я не понимаю, почему):

const mod1 = createModule<Mod1State>({
  namespaced: true,
  state: { // Good: the type of 'state' is checked
    p1: "abc"
  },
  mutations: {
    SET_P1(state, p1: string) { // Good: 'state' is of type 'Mod1State'
      state.p1 = p1
    }
  }
})

Но mod1 теперь относится к типу WithState<Mod1State>. Предполагаемый тип теряется. Как восстановить точный тип параметра createModule для типа возвращаемого значения?

See also: the example in the playground .

EDIT: I obtained something with a code взято из моего примера. Я даже не понимаю, как это работает. И почему тип namespaced выводится как true вместо boolean без as const.


person Paleo    schedule 03.12.2019    source источник
comment
Предполагается ли, что пример может быть запущен? Если я скопирую и вставлю этот код, я получу неявную ошибку (с noImplicitAny). Также я не могу воспроизвести, что state имеет тип Mod1State (это все еще any).   -  person ford04    schedule 03.12.2019
comment
Я не могу воспроизвести Good: 'state' is of type 'Mod1State'; Я получаю неявную ошибку. see.   -  person jcalz    schedule 03.12.2019
comment
@jcalz это было точное время ????   -  person ford04    schedule 03.12.2019
comment
@jcalz @ford04 Спасибо, извините, я только что отредактировал. Я понимаю, что мой пример работает только без as const. Это еще одна проблема, потому что мне нужно as const.   -  person Paleo    schedule 03.12.2019


Ответы (2)


Поскольку я не могу воспроизвести строгую предполагаемую типизацию параметра state в обратном вызове mutations, я проигнорирую это и предполагаю, что вам нужно аннотировать его самостоятельно, если вы хотите, чтобы компилятор знал, что это такое.

В дальнейшем я собираюсь упростить VuexModule, чтобы говорить только о S, типе свойства state:

type VuexModule<S> = {
  state?: S,
  namespaced?: boolean
  mutations?: {
    [K: string]: (state: S, payload: any) => void
  }
}

Хорошо, поехали:


Если вы хотите иметь возможность вручную указать Mod1State в качестве параметра типа S, но позволить компилятору выводить параметр типа T как из S, так и из значения, переданного в createModule(), тогда вам требуется частичный вывод параметра типа , который в настоящее время не поддерживается начиная с TS3.7. Два известных мне обходных пути, как в ответе на этот вопрос заключается в том, чтобы либо использовать каррированную функцию, позволяющую указать параметр типа одной функции, в то время как возвращаемая функция позволяет вывести параметр своего типа, например:

const createModule = <S>() => <T extends VuexModule<S>>(t: T) => t;

const mod1 = createModule<Mod1State>()({
  namespaced: true,
  state: {
    p1: "abc"
  } as Mod1State,
  mutations: {
    SET_P1(state: Mod1State, p1: string) { // you need to annotate this
      state.p1 = p1
    }
  }
} as const)

mod1.mutations.SET_P1 // okay
mod1.mutations.GET_P2 // error

или, чтобы передать фиктивный параметр типа S в функцию и использовать его вместо ручного указания параметра типа:

const createModule = <S, T extends VuexModule<S>>(s: S, t: T) => t;

const mod1 = createModule(null! as Mod1State, {
  namespaced: true,
  state: {
    p1: "abc"
  } as Mod1State,
  mutations: {
    SET_P1(state: Mod1State, p1: string) { // you need to annotate this
      state.p1 = p1
    }
  }
} as const)
mod1.mutations.SET_P1 // okay
mod1.mutations.GET_P2 // error

Любой способ работает (mod1 строго типизирован), но ни один из них не особенно хорош (они громоздки в использовании).


Если, с другой стороны, вы хотите, чтобы компилятор просто выводил как S, так и T, есть способ сделать это. В данном случае есть только параметр T, но мы заставляем компилятор вывести тип для S и убедиться, что T ему соответствует. Это выглядит так:

const createModule = <T extends VuexModule<T["state"]>>(t: T): T => t;

const mod1 = createModule({
  namespaced: true,
  state: {
    p1: "abc"
  },
  mutations: {
    SET_P1(state: Mod1State, p1: string) { // you need to annotate this
      state.p1 = p1
    }
  }
} as const)
mod1.mutations.SET_P1 // okay
mod1.mutations.GET_P2 // error

Это работает, потому что T ограничен функцией самого себя в так называемой F-ограниченной количественной оценке< /а>.

Хорошо, надеюсь, это поможет; удачи!

Ссылка на код

person jcalz    schedule 03.12.2019
comment
Интересно. Спасибо. Я пытаюсь использовать третье решение (PureInfer), но с утверждением типа state: { p1: "abc" } as Mod1State. Без as const. Но я не могу вывести параметр state в SET_P1. Если бы я мог, я думаю, это был бы хороший компромисс. - person Paleo; 03.12.2019
comment
Я не мог вывести параметр обратного вызова; это может быть невозможно, поскольку это зависит от деталей того, когда происходит контекстный вывод типа параметров функции по сравнению с тем, когда происходит вывод параметра типа, над которым я не сильно контролирую. - person jcalz; 03.12.2019
comment
If you can refactor createModule() you can get better inference, like (e.g.,) это - person jcalz; 03.12.2019
comment
Большое спасибо за помощь. Может быть, я что-то нашел, но это удачная догадка, я не уверен, что понимаю, как работает мой код. Я отредактировал свой вопрос, добавив ссылку (вниз), потому что ссылка на игровую площадку слишком велика для комментария. Можете ли вы подтвердить правильность моего кода? Думаете, можно упростить? - person Paleo; 03.12.2019

Я вставляю сюда решение, которое, кажется, работает. Я не совсем уверен, почему и как.

interface Mod1State {
  p1: string
}

function createModule<
  T extends WithState,
  S = T["state"]
>(obj: T & VuexModule<S>): T {
  return obj
}

interface WithState {
  state?: any | (() => any)
}

interface VuexModule<S> {
  namespaced?: boolean
  state?: S | (() => S)
  mutations?: {
    [K: string]: (state: S, payload: any) => void
  }
}

const mod1 = createModule({
  namespaced: true,
  state: {
    p1: "abc"
  } as Mod1State,
  mutations: {
    SET_P1(state, p1: string) { // Good: 'state' is of type 'Mod1State'
      state.p1 = p1
    }
  }
})
// Good: 'mod1' has the correct type,
// including 'namespaced: true' instead of 'namespaced: boolean'

Playground

person Paleo    schedule 04.12.2019