泛型函数参数类型中的类型推断

时间:2021-06-08 14:31:10

标签: typescript

我想要“实用”的 createModel 函数,它可以推断类型,并且在我使用它时不需要编写类型。

Playground Link

? 代码


 // - here A extends (s: S, p?: any) => S - we want to return our "model" type  as result 
export function createModel<S, A extends (s: S, p?: any) => S>({ 
    model,
    actions,
}: {
    model: S,
    actions: A
}) {
    return {
       model,
       actions
    }
}

type TModel = {
    name: string
    isActive: boolean
}

const model: TModel = {
    name: '',
    isActive: false
}

const R = createModel({
    model: model,
    actions: (model, p) => ({ // - model is TModel model here and its what i expect.
        ...model,
        meta: p,  // - here i return invalid model field, and haven't error, i expect TypeError
    }),
})



// if i delete spread,  it's works  - i have TypeError
const R = createModel({
    model: model,
    actions: (model, p) => ({ // - Type '{ meta: any; }' is missing the following properties from type 'TModel': name, isActive
        meta: p, 
    }),
})




// if try to return primitive (for example - number), it's works  - i have TypeError
export function createModel<S, A extends (s: S, p?: any) => number>({  ......

const R = createModel({
    model: model,
    actions: (model, p) => ({ // - Type '{ meta: any; name: string; isActive: boolean; }' is not assignable to type 'number'.
        ...model,
        meta: p, 
    }),
})

实际行为

在“action”函数中,我有这个函数返回的类型,这不依赖于 createModel 中指定的类型。

预期行为

在“action”函数中,我返回了我在 createModel 函数的泛型中定义的类型。

1 个答案:

答案 0 :(得分:0)

这与 Typescript 的工作方式有关。 Typescript 不会检查对象是否完全是您想要的类型,而是检查它是否与您想要的类型兼容

假设我们有这个接口:

interface IFoo {
    boo: string;
    loo: number;
}

任何具有字符串属性 boo 和数字属性 loo 的对象,对于 Typescript 来说都是 IFoo 类型。

例如:


// This works
const obj1: IFoo = {
    boo: "Bla bla",
    foo: 2
}

// This works too (we just added a property, so it's still compatible)!
const obj2: IFoo = {
    boo: "Bla bla",
    foo: 2,
    goo: [1,2,3]
}

// This doesn't work though (Missing property)!
const obj3: IFoo = {
    boo: "Bla bla"
}

在这里,您实际上所做的是创建了一个具有 model 属性和额外属性 meta 的对象。因此,Typescript 正确地不会抛出 TypeError 因为类型是兼容的(就像前面提到的 obj2 一样)。

const R = createModel({
    model: model,
    actions: (model, p) => ({ // - model is TModel model here and its what i expect.
        ...model,
        meta: p,  // - here i return invalid model field, and haven't error, i expect TypeError
    }),
})

如果您想实现它,有一种解决方法可以实现您想要的(以及对此功能的解释)here

type ValidateStructure<T, Struct> = 
  T extends Struct ? 
  Exclude<keyof T, keyof Struct> extends never ? T : never : never;

export function createModel<S, T extends S>({ 
    model,
    actions,
}: {
    model: S,
    actions: (s: S, p?: any) => ValidateStructure<T, S>
}) {
    return {
       model,
       actions
    }
}

我们还应该说,您不需要定义额外的通用类型 A。您可以将类型直接放在您的界面上。

export function createModel<S>({ 
    model,
    actions,
}: {
    model: S,
    actions: (s: S, p?: any) => S
}) {
    return {
       model,
       actions
    }
}