例如,请参阅此代码:
var a
a.set('my-custom-value',55)
a.
在第 3 行中,如何让 IDE 了解第 2 行中的值,并为使用该库的最终用户定义的字符串提供自动完成功能?
我希望在我输入后a.
我会看到“我的自定义值”作为 VSCode 的自动完成选项。
我看到它yargs
是这样工作的。如果我定义了一个选项或一个位置参数,IDE 稍后会为我提供我选择的内容。
例如,请参阅此代码:
var a
a.set('my-custom-value',55)
a.
在第 3 行中,如何让 IDE 了解第 2 行中的值,并为使用该库的最终用户定义的字符串提供自动完成功能?
我希望在我输入后a.
我会看到“我的自定义值”作为 VSCode 的自动完成选项。
我看到它yargs
是这样工作的。如果我定义了一个选项或一个位置参数,IDE 稍后会为我提供我选择的内容。
这可以通过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
它实际上是可能的!有点。我们可以使用控制流类型缩小(感谢@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
,这会杀死关键跟踪。
使用写时复制的旧笨重示例:
为此,您需要构造所需的对象类型。以下代码允许您这样做。
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'])
如果你知道你要设置什么值,你可以尝试这样的事情
let a: { value?: number } = {}
a.value = 55
但是 value 将始终是 type number | undefined
,因为 typescript 编译器不知道您是否在代码中的任何位置设置了 value。