Função TypeScript que recebe um objeto sem determinadas chaves
Brincando com a ideia de criar uma função que recebe um único objeto de argumento que não deve ter algum conjunto de chaves, sem fazer as pessoas especificarem tipos manualmente. Eu estava tentando aproveitar o never de alguma forma, mas estou preso tentando entender como a inferência funciona para argumentos de função que recebem um tipo com um parâmetro genérico.
interface ReservedAttributes {
someKey: string
}
// evaluates to never if the type has a key that intersects
// with the keys in ReservedAttributes
type ValidAttributes<T> = keyof T extends Exclude<keyof T, keyof ReservedAttributes> ? T : never
// This is correctly a never, but it doesn't address this problem
type Test = ValidAttributes<{someKey : string}>
// This doesn't work because the function argument ends
// up being inferred as { [name: string] : string}
function foo1<K extends { [name: string] : string}>(attributes: ValidAttributes<K>) {
// ...
}
foo1({a: 'hi', someKey: ''})
// Roughly equivalent to the above
function foo2< K extends { [name: string] : string}, V extends ValidAttributes<K> >(attributes: V) {
// ...
}
// This one allows V to correctly evaluate to never,
// but I'm not sure how to leverage that
function foo3< K extends { [name: string] : string}, V extends ValidAttributes<K> >(attributes: K) {
// ...
}
foo3({a: 'hi', someKey: ''})
Como você resolveria este problema?
Resposta #1
Eu acho que primeiro tentaria ficar longe de genéricos e tipos condicionais e faria algo assim:
type ValidAttributes = Record<string, string> &
Partial<Record<keyof ReservedAttributes, never>>;
declare function foo(attributes: ValidAttributes): void;
foo({ a: 'hi' }); // okay
foo({ a: 'hi', someKey: '' }); // error, someKey
Neste caso, ValidAttributes é um dicionário geral com valores do tipo string, mas com as chaves de ReservedAttributes listadas como propriedades opcionais do tipo never (propriedades opcionais podem estar ausentes, e propriedades do tipo never realmente não podem estar presentes, então uma propriedade opcional do tipo never é mais ou menos uma que deve estar ausente). Isso funciona para você?
Se você precisar usar tipos condicionais em uma restrição genérica, isso pode ser feito:
type Attributes<K extends keyof any> = {
[P in K]: P extends keyof ReservedAttributes ? never: string
};
declare function foo<T>(attributes: T & Attributes<keyof T>): void;
foo({ a: 'hi' }); // okay
foo({ a: 'hi', someKey: '' }) // error
Mas é mais complicado e alcança resultados semelhantes. Espero que isso ajude; boa sorte!
Resposta #2
Uma maneira possível é ter um tipo com propriedades opcionais declaradas com o tipo void. Nenhum tipo é atribuível a void, portanto qualquer objeto que tenha esses atributos não será compatível com o tipo do argumento:
interface ReservedAttributes {
someKey: string;
}
type DisallowedAttributes<T> = { [k in keyof T]?: void };
type AllowedKeys = string; // could be restricted if necessary
function fn<K extends AllowedKeys>(t: { [k in K]: {}} & DisallowedAttributes<ReservedAttributes>) {
}
fn({ a: 'hi' }); // ok
fn({ b: 4, someKey: 'd' }); // Error: Type 'string' is not assignable to type 'void & {}'.
// Type 'string' is not assignable to type 'void'.
