扩展Typescript索引对象以一般访问索引对象(字典)的值的类型



我想在Typescript中扩展默认的索引匿名类型,但似乎找不到正确的语法来获取索引对象的值的类型,如果可能的话?

问题是:

编辑:

为了更好地解释这个问题,我重新设计了这些例子。

// How do we pass the value for T as a dictionary through to the iterator?.
interface Object {
where<T = { [key: string]: any}, K = keyof T>(this: T, iterator: (v: any /* this should be valueof T, not any */, key?: K) => boolean | void): T;
// This works, but you have to specify the type when calling it, this is exactly what I'm trying to avoid.
whereAsArray<S, T = { [key: string]: S }, K = keyof T>(this: T, iterator: (v: S /* this should be valueof T, not S*/, key?: K) => boolean | void): S[] /* this should be valueof T[], not S */;
}
// This problem is agnostic of the implementation. Included here only so that the code runs. 
(function Setup() {
if (typeof Object.prototype.where != 'function') { Object.defineProperty(Object.prototype, 'where', { value: function (iterator: (v: any, key?: string) => any) { const keys: string[] = Object.keys(this); const result: any = {}; let i: number; for (i = 0; i < keys.length; i++) { const key = keys[i]; if (this.hasOwnProperty(key)) { const res = iterator(this[key], key); if (res === true) { result[key] = (this[key]); } } } return result; }, writable: true, configurable: true, enumerable: false }); }
if (typeof Object.prototype.whereAsArray != 'function') { Object.defineProperty(Object.prototype, 'whereAsArray', { value: function (iterator: (v: any, key?: string) => any) { const keys: string[] = Object.keys(this); const result: any[] = []; let i: number; for (i = 0; i < keys.length; i++) { const key = keys[i]; if (this.hasOwnProperty(key)) { const res = iterator(this[key], key); if (res === true) { result.push(this[key]); } } } return result; }, writable: true, configurable: true, enumerable: false }); }
})();
(function Test() {
// Typescript knows the type of x, it is explicitly typed here as an object keyed on string, storing only numbers; a dictionary of string=>number.
// Typescript enforces this everywhere you use this dictionary, as shown below:
const x: { [key: string]: number } = {};
x["foo"] = 1;
x["bar"] = 2;
// The code can currently be used like this, and works.. But, if you hover the 'i' variable, you will see that the type is any, 
// because I can't figure out how to extract the type of the "value of" T in the interface?
const results = x.where((i, _k) => i !== 1); 
console.log(results);
// This works. But we have to specify <number> for TS to figure this out. Question is, is it possible to have TS figure this out from the signature?
// Having to type the function calls should not be necessary.
const results2 = x.whereAsArray<number>((i, _k) => i === 1);
console.log(results2);
})();

游乐场链接:

打字游戏场

我将此作为单独的答案发布,因为您已经更改了问题。这个的打字其实很简单。

type ValueOf<T> = T[keyof T];
interface Object {
where<T>(this: T, iterator: (v: ValueOf<T>, key: string) => boolean | void): T; // should become Partial<T> if we are not dealing with a dictionary
whereAsArray<T>(this: T, iterator: (v: ValueOf<T>, key: string) => boolean | void): ValueOf<T>[];
}

我们不需要也不想要键的泛型,因为这些函数适用于所有键,而不是特定键。

请注意,当定义iterator回调时,应根据需要列出其他参数。当使用回调调用where方法时,不需要包含所有参数。但是,如果您在回调定义中使它们是可选的,那么您将无法在回调的主体中实际使用它们,因为它们可能是undefined

打字游戏场链接

我已经解释了在Object.prototype上定义这些方法的许多潜在问题,但我确实想引起您的注意。我们的where返回与原始对象相同的类型T。这本质上不是一个问题,但它与您的实现不匹配,因为数组是作为对象而不是数组返回的。然而,Typescript期望返回一个数组,从而导致运行时错误。

const z = [1, 2, 3];
const filtered: number[] = z.where(v => v > 1);
// the type is number[] but it's actually an object
console.log(filtered);
// so calling array methods on it seems fine to TS, but is a runtime error
console.log(filtered.map(n => n - 1));

您可以继承第一个类型参数上的类型:

打字游戏场链接

interface Object {
whereAsArray <T, K = keyof T, V = T[keyof T] > (this: T, iterator: (v: V, key?: K) => boolean | void): T[];
}
(function Setup() {
if (typeof Object.prototype.whereAsArray != 'function') {
Object.defineProperty(Object.prototype, 'whereAsArray', {
value: function(iterator: (v: any, key ? : string) => any) {
const keys: string[] = Object.keys(this);
const result: any[] = [];
let i: number;
for (i = 0; i < keys.length; i++) {
const key = keys[i];
if (this.hasOwnProperty(key)) {
const res = iterator(this[key], key);
if (res === true) {
result.push(this[key]);
}
}
}
return result;
},
writable: true,
configurable: true,
enumerable: false
});
}
})();
(function Test() {
const x: {
[key: string]: number
} = {};
x["foo"] = 1;
x["bar"] = 2;
const results = x.whereAsArray((i, _k) => i === 1);
console.log(results);
})();

好的,这样您就有了一个包含字典的对象。该对象有一个方法dictionaryKeys(),它返回字典的键数组,还有一个方法dictionaryWhere(),它根据键和值进行过滤,我想它会返回字典的子集?

您可以使用typescriptRecord实用程序类型,但缺少的重要一点是通用T应该应用于对象,而不是单个方法。

interface DictionaryObject<T> {
dictionaryKeys(): string[];
dictionaryWhere(iterator: (v: T, key: string) => boolean): Record<string, T>;
}
declare const x: DictionaryObject<string>
let k: string[] = x.dictionaryKeys();         
let v: Record<string, string> = x.dictionaryWhere((i, k) => k === "foo"); 

游乐场链接

实施:

class MyDictionary<T> {
private _values: Record<string, T> = {};
constructor(object: Record<string, T> = {}) {
this._values = object;
}
dictionaryKeys(): string[] {
return Object.keys(this);
}
dictionaryWhere(iterator: (v: T, key: string) => boolean): Record<string, T> {
return Object.fromEntries(
Object.entries(this._values).filter(
([key, value]) => iterator(value, key)
)
)
}
}

注意:tsconfig至少需要es2017才能使用Object.fromEntries

编辑:

我误解了这个问题,没有意识到你试图扩展内置的Object.prototype。这是个坏主意,因为它会产生意想不到的后果。在我看来,你应该创建接受对象的函数,而不是扩展原型。

并非所有对象都是字符串键控字典。数组是一种类型的对象。这就是为什么要为密钥获取类型(string | number)[]的原因。如文件中所述,

keyof和T[K]与索引签名交互。索引签名参数类型必须为"string"或"number">如果你有一个类型字符串索引签名,T的键将是字符串|数字(而不是只有字符串,因为在JavaScript中您可以访问对象属性通过使用字符串(对象["42"](或数字(对象[42]((。

如果您已经必须使用let k = x.dictionaryKeys<string>();传递显式泛型,我真的不明白为什么这比let k = Object.keys(x) as string[];好。

您可以使用一个基于对象键入键数组的函数,由于@jcalz链接的线程中的原因,您需要小心使用该函数。

const typedKeys = <T extends Record<string, any> & Record<number, never>>(obj: T): (keyof T)[] => {
return Object.keys(obj);
}

链接

如果我们不包括Record<number, never>,我们实际上仍然可以传递数字键控对象(由于在幕后实现的方式(,并且我们会得到糟糕的结果。

const typedKeys = <T extends Record<string, any>>(obj: T): (keyof T)[] => {
return Object.keys(obj);
}
const a = {
1: "",
2: "",
};
const b = {
one: "",
two: "",
};
const aKeys = typedKeys(a); // we might expect a problem passing in an object with numeric keys, but there is no error
console.log( aKeys );  // BEWARE: typescript says the type is `(1|2)[]`, but the values are `["1", "2"]`
const bKeys = typedKeys(b);
console.log( bKeys ); // this one is fine

链接

您可以使用一个函数来显式声明键类型。在这里,泛型描述的是键而不是对象,所以为了清楚起见,我将其称为K。我们使用作为K[]来将类型缩小为string的子集。由于Object.keys返回字符串,我们不能返回任何不可分配给string[]的内容。

const typedKeys = <K extends string>(obj: Record<K, any> & Record<number | symbol, never>): K[] => {
return Object.keys(obj) as K[];
}
const b = {
one: "",
two: "",
};

const bKeys = typedKeys(b); // type is ("one" | "two")[], with no generic, the type is infered to these specific keys
const stringKeys = typedKeys<string>(b); // type is string[]
const badKeys = typedKeys<number>(b); // error type 'number' does not satisfy the constraint 'string'

链接

最新更新