Overview
JavaScript is a highly dynamic language. It can be tricky { 棘手的 } sometimes to capture the semantics of certain operations in a static type system. Take a simple prop
function, for instance:
|
|
It accepts an object and a key and returns the value of the corresponding property. Different properties on an object can have totally different types, and we don’t even know what obj
looks like.
So how could we type this function in TypeScript? Here’s a first attempt:
|
|
With these two type annotations in place, obj
must be an object and key
must be a string. We’ve now restricted the set of possible values for both parameters. The return type is still inferred to be any
, however:
|
|
Without further information, TypeScript can’t know which value will be passed for the key
parameter, so it can’t infer a more specific return type for the prop
function. We need to provide a little more type information to make that possible.
The keyof
Operator
Enter TypeScript 2.1 and the new keyof
operator. It queries the set of keys for a given type, which is why it’s also called an index type query. Let’s assume we have defined the following Todo
interface:
|
|
We can apply the keyof
operator to the Todo
type to get back a type representing all its property keys, which is a union of string literal types:
|
|
We could’ve also written out the union type "id" | "text" | "due"
manually instead of using keyof
, but that would’ve been cumbersome { 繁琐的 }, error-prone, and a nightmare to maintain. Also, it would’ve been a solution specific to the Todo
type rather than a generic one.
Indexed Access Types
Equipped with keyof
, we can now improve the type annotations of our prop
function. We no longer want to accept arbitrary strings for the key
parameter. Instead, we’ll require that the key actually exists on the type of the object that is passed in:
|
|
TypeScript now infers the prop
function to have a return type of T[K]
, a so-called indexed access type or lookup type. It represents the type of the property K
of the type T
. If we now access the three todo properties via the prop
method, each one will have the correct type:
|
|
Now, what happens if we pass a key that doesn’t exist on the todo
object?
The compiler complains, and that’s a good thing! It prevented us from trying to read a property that’s not there.
For another real-world example, check out how the Object.entries()
method is typed in the lib.es2017.object.d.ts type declaration file that ships with the TypeScript compiler:
|
|
The entries
method returns an array of tuples, each containing a property key and the corresponding value. There are plenty of square brackets involved in the return type, admittedly { 诚然,不可否认地 }, but there’s the type safety we’ve been looking for!