4

例如,请参阅此代码:

var a
a.set('my-custom-value',55)
a.

在第 3 行中,如何让 IDE 了解第 2 行中的值,并为使用该库的最终用户定义的字符串提供自动完成功能?

我希望在我输入后a.我会看到“我的自定义值”作为 VSCode 的自动完成选项。

我看到它yargs是这样工作的。如果我定义了一个选项或一个位置参数,IDE 稍后会为我提供我选择的内容。

4

4 回答 4

4

这可以通过asserts返回类型来完成,这是可能的,因为 Typescript 支持控制流类型缩小:

class Foo {
    setKey<K extends PropertyKey, V>(k: K, v: V): asserts this is Record<K, V> {
        (this as any)[k] = v;
    }
}

let foo: Foo = new Foo();
// foo. completes as foo.setKey

foo.setKey('bar', 23);
// now foo. completes as foo.bar or foo.setKey

游乐场链接

于 2020-09-07T02:06:50.393 回答
2

它实际上是可能的!有点。我们可以使用控制流类型缩小(感谢@kaya3)和泛型类型(T)来跟踪已设置的键。但是,由于类型缩小只能添加键,不能删除它们。

set这是一个带有and的工作示例地图get:(
如果您不想要单独的get函数,请参阅@kaya3 的答案以获得更简单的示例)

// Tracking map.
class TMap<V, T extends Record<string, V> = {}> {
    public static create<V>(): TMap<V> {
        return new TMap({});
    }

    private _data: T;
    private constructor(data: T) {
        this._data = data;
    }

    public set<K extends string>(key: string extends K ? never : K, val: V): asserts this is TMap<V, Record<K, V> & T> {
        (this._data as Record<K, V>)[key] = val;
    }

    public get<K extends keyof T>(key: K): V {
        return this._data[key];
    }
}

const a: TMap<number> = TMap.create();
a.set('my-custom-value', 55);
// a.get tooltip: get(key: "my-custom-value"): number
a.set('my-other-key', 22);
a.set('my-other-key', 33);

// a.get tooltip: get(key: "my-custom-value" | "my-other-key"): number
const x = a.get('my-custom-value');
const y = a.get('my-other-key');

const unknownString: string = 'mystery-key';
// a.set(unknownString, 101); // Not allowed b/c we don't know the exact string value.

游乐场链接

key: string extends K ? never : K是一种 hack,可防止您向地图提供非特定信息string,这会杀死关键跟踪。


使用写时复制的旧笨重示例:

游乐场链接

于 2020-09-07T00:29:01.607 回答
1

为此,您需要构造所需的对象类型。以下代码允许您这样做。

class Builder<T extends object = {}> {
  private _obj: any
  constructor(t: T) {
    this._obj = { ...t }
    Object.keys(t).forEach((key) => (this as any)[key] = (t as any)[key])
  }

  public set<K extends string, V>(key: K, value: V): Builder<SetResult<T, K, V>> & SetResult<T, K, V> {
    (this._obj)[key] = value
    return new Builder<SetResult<T, K, V>>(this._obj) as Builder<SetResult<T, K, V>> & SetResult<T, K, V>
  }
}
type SetResult<T, K extends string, V> = T & { [k in K]: V }

以这种方式使用代码:

const a = new Builder({})
  .set('abc', 55)
  .set('def', 'name')
  .set('my_complex_variable', { ID: '1234', exists: false })

console.log(a.abc)
console.log(a.def)
console.log(a.my_complex_variable.ID)

它的工作方式非常简单。对函数的每次调用set都会返回一个新Builder对象,其中包含所有先前的set调用。请注意,您访问的每个字段也是强类型的:abc是数字,def是字符串。该类型有一些相当讨厌的转换any,但如果需要,您可能会清理它

这是工作代码的链接

编辑:我刚刚注意到,如果您使用带有连字符的变量名,如果您使用.. 您仍然可以对该字段进行类型检查,但您必须使用索引器访问它:

const a = new Builder({}).set('my-variable', 12)
console.log(a['my-variable'])
于 2020-09-07T01:24:58.193 回答
0

如果你知道你要设置什么值,你可以尝试这样的事情

let a: { value?: number } = {}
a.value = 55

但是 value 将始终是 type number | undefined,因为 typescript 编译器不知道您是否在代码中的任何位置设置了 value。

于 2020-09-06T23:52:30.297 回答