4 ms·
Little things like Object.keys() should return a (keyof T)[] rather than a string[] but can't due to JS edge cases. This was something that confused me when I
by BreakfastB0b 6y ago
Little things like Object.keys() should return a (keyof T)[] rather than a string[] but can't due to JS edge cases.
This was something that confused me when I first started learning Typescript but is actually not an edge case but a fundamental feature of Typescript's structural typing.
A type T is only guaranteed to be at least (a subtype of) T. Which in the case of objects means it has at least those fields, but potentially more. For instance
interface FooBar {
foo: string
bar: string
}
interface Baz {
baz: number
}
const fooBarBaz: FooBar & Baz = {
foo: 'foo',
bar: 'bar',
baz: 3
}
const printFoobar = (fooBar: FooBar) => {
for (const key of Object.keys(foobar)) {
if (key !== 'foo' && key !== 'bar') {
// If Object.keys was (keyof T)[] Typescript would claim this branch is unreachable
}
}
}
printFooBar(fooBarBaz)
It's really worth thinking about Typescript types not as nominal objects like in Java or Haskell, but contracts about minimum functionality. Embracing this in terms of function arguments and return types makes testing and composing typescript code much easier. For example, if you were writing a AWS Lambda function in Typescript that only uses the body of the incoming event.
export const handlerOne = (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
console.log(event.body)
return {
statusCode: 200,
body: event.body,
}
}
interface MinimalEvent extends Pick<APIGatewayProxyEvent, 'body'> {}
export const handlerTwo = (event: MinimalEvent): Promise<APIGatewayProxyResult> => {
console.log(event.body)
return {
statusCode: 200,
body: event.body,
}
}
handlerTwo only requires a handlerTwo({ body: '....' }) call in a test, instead of having to fill in all the extra properties of the APIGatewayProxyEvent that aren't even required. You might be tempted to use a type coercion in the test instead e.g. handlerOne({ body: '....' } as APIGatewayProxyEvent) but the problem with the coercion is that is in not checked at all by the compiler, it's essentially like temporarily using an any type. So if handlerOne is updated in the future to depend on more of the structure of APIGatewayProxyEvent Typescript will be none the wiser that the test requires updating (although granted hopefully the test would fail).
Anyway, that's a long winded explanation of why Object.keys shouldn't return (keyof T)[]. If you want to program generically over the Type of something I'd suggest making use of a runtime type library like io-ts instead which gives you a data structure representing the type to iterate over, etc.