简体   繁体   English

如何在 Typescript 中定义具有多个重载的通用 getter 方法?

[英]How do you define a generic getter method in Typescript with multiple overloads?

I'm trying to define a method that operates as a getter, taking an optional parameter.我正在尝试定义一个作为 getter 操作的方法,采用可选参数。 The getter provides access to an object of type T , and should return either the entire object, or a property on that object. getter 提供对T类型的 object 的访问,并且应该返回整个 object 或该 object 上的属性。

The challenge is that I am trying to defined the method in two places, first in an interface, and second in the actual implementation.挑战在于我试图在两个地方定义方法,第一个在接口中,第二个在实际实现中。

Here's my approach:这是我的方法:


// Getter defines both overloads
interface StoreGetter {
    <T>(): T;
    <T, K extends keyof T>(prop: K): T[K];
}

// Store has a generic type, and exposes that type and properties on that type
interface Store<T> {
    get: StoreGetter;

    // Either one works individually
    // get: <T>() => T;
    // get: <T, K extends keyof T>(prop: K) => T[K];
}

export function makeStore<T>(initial: T): Store<T> {
    let value: T = initial;

    // Apparently, you can only define overloads via a function declaration
    // function get<T>(): T;
    // function get<T, K extends keyof T>(prop: K): T[K];
    function get(prop?: keyof T) {
        if (typeof prop !== 'undefined') {
            return value[prop];
        }
        return value;
    }

    return {
        get,
    };
}

const store = makeStore({
    text: '',
    items: [],
    num: 1
});

// Argument of type '"text"' is not assignable to parameter of type 'never'.(2345):
store.get('text') 

// Object is of type 'unknown'.(2571)
store.get().

Unfortunately, the two definitions seem to clobber each other.不幸的是,这两个定义似乎相互冲突。

How can I define this method with overloads, and have correct type inference for both calls?如何使用重载定义此方法,并为两个调用提供正确的类型推断?

After many failed attempts, I've discovered one configuration that produces the expected inferences:经过多次失败的尝试,我发现了一种产生预期推论的配置:

interface StoreGetter<T> {
    (): T;
    <K extends keyof T>(props: K): T[K];
}

interface Store<T> {
    get: StoreGetter<T>;
    set: (val: any | T) => void;
}

export function makeStore<T>(initial: T): Store<T> {
    let value: T = initial;
    let listeners: Function[] = [];

    function get(): T;
    function get<K extends keyof T>(prop: K): T[K];

    function get(prop?: keyof T): T | T[keyof T] {
        if (typeof prop !== 'undefined') {
            return value[prop];
        }
        return value;
    }

    return {
        get,
        set: (val: any) => {
            value = {
                ...value,
                ...val,
            };
            listeners.forEach(fn => fn(value));
        }
    };
}

const store = makeStore({
    text: '',
    items: [],
    num: 1
});

// Both work with type inference
store.get('text').toUpperCase
store.get().items

Still hoping to find a way to do it with an inline/anonymous function.仍然希望找到一种使用内联/匿名 function 的方法。

On a positive note, this approach works seamlessly in a declarations file (eg, store.d.ts), enabling the use of a single declaration:积极的一点是,这种方法可以在声明文件(例如 store.d.ts)中无缝工作,从而可以使用单个声明:

interface StoreGetter<T> {
  (): T;
  <K extends keyof T>(props: K): T[K];
}

interface Store<T> {
  get: StoreGetter<T>;
}

export function makeStore<T>(initial: T): Store<T>;

export function useStore<T>(store: T, prop?: string): [T|any, (newState: T|any) => void];

And then in a separate JS file:然后在一个单独的 JS 文件中:

const store = makeStore({
  keypresses: 0,
  text: '',
  arrows: [],
});

// Both inferred:
store.get('keypresses').toFixed
store.get().arrows.push

This produces the expected annotations in VS code:这会在 VS 代码中生成预期的注释:

VS Code 显示关键建议

VS Code 建议无参数重载

键重载的 VS Code 建议

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM