对参数使用一些类型约束,同时恢复 return 值的参数类型

Use some type constraints on a parameter, while restoring the parameter type for the return value

我有一个函数 createModule,它只是 return 它的参数:

function createModule(obj) {
  return obj
}

return 值必须具有从参数推断出的类型:

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
}

现在,我想在 SET_P1 的参数 state 中添加一些自动完成功能。我宁愿检查 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> 类型。推断类型丢失。如何为 return 值的类型恢复 createModule 参数的确切类型?

另请参阅:示例 in the playground

编辑:我 obtained something 使用从我的示例派生的代码。我什至不明白它是如何工作的。以及为什么 namespaced 的类型在没有 as const.

的情况下被推断为 true 而不是 boolean

因为我无法在 mutations 回调中重现 state 参数的强类型推断,我将忽略它并假设你需要自己注释它编译器知道它是什么。

下面我将VuexModule简化为只讲Sstate属性的类型:

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

好的,我们开始:


如果您希望能够手动指定 Mod1State 作为 S 类型参数,但允许编译器 推断 T 类型参数来自 S 和传递给 createModule() 的值,那么你想要部分类型参数推断,即 currently not supported as of TS3.7. The two workarounds I know of, as in the answer to ,要么使用柯里化函数来允许一个函数的类型参数在返回的函数允许推断其类型参数时指定,如下所示:

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 是强类型),但都不是特别好(它们使用起来很麻烦)。


另一方面,如果您希望编译器只推断 ST,有一种方法可以做到。在这种情况下,只有 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-bounded quantification 中受限于自身的功能。

好的,希望对您有所帮助;祝你好运!

Link to code

我在这里粘贴了一个似乎有效的解决方案。我不太确定为什么以及如何。

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