Generic types & interfaces
Write TypeScript types that take a type parameter, like ApiResponse<T> and Field<T>, give them defaults, and read built-in ones like Map and Promise.
- 01Write a generic typeGive a type alias or an interface a type parameter, like
ApiResponse<T>, and fill it in withApiResponse<User>. - 02Use default type parametersWrite
<T = string>so a type works without a type argument, and know when a written one wins. - 03Read built-in genericsRead
Array<T>,Map<K, V>,Set<T>, andPromise<T>, including one generic inside another.
One reply type for every kind of data
A shop's server answers every request the same way: a status number, like 200 for "OK", and the data you asked for. Ask for products and data is a list of products. Ask for your profile and it's one user. Here are two types for those two replies:
type Product = { name: string; price: number };type User = { name: string; email: string }; type ProductsReply = { status: number; data: Product[] };type UserReply = { status: number; data: User }; const products: ProductsReply = { status: 200, data: [{ name: "Tea", price: 3 }] };const user: UserReply = { status: 200, data: { name: "Asha", email: "asha@example.com" } };console.log(products.data[0].name, user.data.name);Lines 1 and 2 describe a product and a user with type, which gives a name to a type (the Type aliases and interfaces lesson covers it). Lines 4 and 5 describe the two replies. They are the same except for data: Product[] on line 4, User on line 5. Lines 7 and 8 make one reply of each, and line 9 prints Tea Asha.
A real shop has replies for orders, reviews, and coupons too. That's one more copy each time, and if the server adds a field, you change every copy. A generic type writes the reply once, with a blank where data's type goes:
type Product = { name: string; price: number };type User = { name: string; email: string }; type ApiResponse<T> = { status: number; data: T }; const products: ApiResponse<Product[]> = { status: 200, data: [{ name: "Tea", price: 3 }] };const list = products.data;// ^?const user: ApiResponse<User> = { status: 200, data: { name: "Asha", email: "asha@example.com" } };const profile = user.data;// ^?console.log(list[0].name, profile.name);Line 4 has the new part: <T> after the type's name. As in the Generic functions lesson, T is a type parameter, a name that stands for a type. Here it belongs to the type, and data: T says data has whatever type T is.
Line 6 writes ApiResponse<Product[]>. The type in the angle brackets is the type argument: it fills in T, so this reply's data is a Product[]. The query on line 8 shows const list: Product[]. Line 9 fills in T with User, and line 11 shows const profile: User. There are no errors, and line 12 prints Tea Asha.
T: Product[] for the products, User for the user. TypeScript then reads data with that type.A moving company prints one label for every box: "Contents: ____". Before a box goes on the truck, you write "books" or "plates" in the blank. Every label has the same layout, and the movers know what each box holds without opening it.
- In real life: One printed label: "Contents: ____"
- In TypeScript: One type:
type ApiResponse<T> = { status: number; data: T } - In real life: You write "books" in the blank
- In TypeScript: You write
ApiResponse<Product[]> - In real life: Every label has the same layout
- In TypeScript: Every
ApiResponsehas astatusand adata - In real life: The movers know the box holds books without opening it
- In TypeScript: TypeScript knows
products.datais aProduct[]
A generic type is a type alias or an interface with a type parameter, like type ApiResponse<T> = { status: number; data: T }. Each time you use it, you fill in T with a type argument, like ApiResponse<User>, and TypeScript reads the type with T replaced.
Generic type aliases: a type with a blank
A type alias gets type parameters the same way: angle brackets after its name. A sign-up form is a good place for one. Every field has a value and maybe an error message, but the values differ: the email is text, the age is a number, and a checkbox is true or false.
type Field<T> = { value: T; error: string | null }; const email: Field<string> = { value: "asha@example.com", error: null };const age: Field<number> = { value: 30, error: null };const nextAge = age.value + 1;// ^?type AgeField = Field<number>;// ^?console.log(email.value, nextAge);Line 1 says a Field<T> has a value of type T and an error that is text or null (null here means "no error"). Line 3 makes an email field, Field<string>. Line 4 makes an age field, Field<number>, so age.value is a number. Line 5 adds 1 to it, and the query on line 6 shows const nextAge: number.
Line 7 gives Field<number> a name of its own. The query on line 8 shows what TypeScript reads: { value: number; error: string | null; }. It put number everywhere line 1 says T. Line 9 prints asha@example.com 31.
Unlike a function call, a generic type in an annotation doesn't work out T for you. Leave the angle brackets out and TypeScript stops you:
type Field<T> = { value: T; error: string | null }; const name: Field = { value: "Asha", error: null };console.log(name.value);Line 3 writes plain Field. TypeScript marks it, on purpose here: Generic type 'Field' requires 1 type argument(s) means "Field has one blank, and you didn't fill it in". The "(s)" is there because the same message is used for types with several blanks. Run it anyway and line 4 prints Asha: the types are gone by then.
Now try it yourself. Type a type argument for line 3 and watch the errors and the answer after ^? on line 5:
type Field<T> = { value: T; error: string | null }; const email: Field<string> = { value: "asha@example.com", error: null };const typed = email.value;// ^?console.log(typed);stringstringNo errorsField<string> means value is text. The email is text, so there are no errors, and line 5 shows const typed: string.
With string, the email fits. With number or boolean, the email on line 3 is the wrong kind of value, so TypeScript marks it, and line 5 still shows the type you asked for. string | null fits too, but then value may be null. Email isn't a type that exists, and empty brackets leave the blank unfilled.
An interface can only describe an object, but a type alias can name any type, so a generic alias can be a union, a type that is one of several:
type Maybe<T> = T | null;type OneOrMany<T> = T | T[]; type Tags = OneOrMany<string>;// ^?function toList(tags: Tags): string[] { return Array.isArray(tags) ? tags : [tags];} const coupon: Maybe<string> = null;console.log(toList("sale"), toList(["new", "sale"]), coupon);Line 1 makes Maybe<T>: a T or null. Line 2 makes OneOrMany<T>: one T or a list of them, like a tag box that takes one tag or several. Line 4 fills it in with string, and the query on line 5 shows type Tags = string | string[].
Lines 6 to 8 turn either kind into a list: Array.isArray checks whether tags is already one. Line 10 makes a coupon code that is Maybe<string>, so null fits. Line 11 prints [ 'sale' ] [ 'new', 'sale' ] null.
Generic interfaces
Interfaces take type parameters too, written the same way. A product list that loads one page at a time is a common use:
interface Page<T> { items: T[]; page: number; hasMore: boolean;} type Product = { name: string; price: number }; const firstPage: Page<Product> = { items: [{ name: "Tea", price: 3 }, { name: "Cake", price: 4 }], page: 1, hasMore: true,};const shown = firstPage.items;// ^?console.log(shown.length, firstPage.hasMore);Lines 1 to 5 declare Page<T>: a list of T items, the page number, and whether there are more pages. Line 9 says firstPage is a Page<Product>, so every item must be a product. Line 14 reads the items, and the query on line 15 shows const shown: Product[]. Line 16 prints 2 true.
Generic types and generic functions work well together. A function can take a Page<T> with its own type parameter, and TypeScript fills in that T from the page you pass:
interface Page<T> { items: T[]; page: number; hasMore: boolean;} function nextPage<T>(current: Page<T>, items: T[]): Page<T> { return { items, page: current.page + 1, hasMore: items.length > 0 };} const sizes: Page<string> = { items: ["S", "M"], page: 1, hasMore: true };const more = nextPage(sizes, ["L"]);// ^?const broken = nextPage(sizes, [42]);console.log(more.page, more.items);Line 7 declares nextPage<T>. It takes the current page and the new items, and gives back the next page with the same T. Line 11 makes a page of sizes, a Page<string>. Line 12 passes it with ["L"], so T is string, and line 13 shows const more: Page<string>.
Line 14 passes the same page with [42]. The page says T is string, so the new items must be text too. TypeScript marks the 42: Type 'number' is not assignable to type 'string' means "this list should hold text, and 42 is a number". Line 15 prints 2 [ 'L' ].
The table below compares the two ways to write a generic type. Read each row as a question, then each answer.
| Question | Generic type alias | Generic interface |
|---|---|---|
| How do you write one? | type Field<T> = { value: T; error: string | null }; | interface Field<T> { value: T; error: string | null } |
| How do you use it? | Write the type argument in angle brackets: Field<string>. | The same way: Field<string>. |
Can it be a union, like T | null? | Yes. An alias can name any type, like Maybe<T>. | No. An interface always describes an object's shape. |
| Can its type parameter have a default? | Yes: type Field<T = string>. | Yes: interface Field<T = string>. |
For an object shape, either works, and you use them the same way. Pick the one your project already uses.
Default type parameters
Most fields on a form hold text. Writing Field<string> every time is repetitive. A default type parameter says which type to use when you leave the type argument out. You write it with =, like a default value for a function parameter:
type Field<T = string> = { value: T; error: string | null }; const name: Field = { value: "Asha", error: null };// ^?const age: Field<number> = { value: 30, error: null };// ^?console.log(name.value, age.value);Line 1 says T = string: when nothing fills in T, use string. Line 3 writes plain Field, and the query on line 4 shows const name: Field<string>. Line 5 writes Field<number>, and line 6 shows Field<number>: a type argument you write wins over the default. Line 7 prints Asha 30.
type Field<T = string> gives T a default. Line 3 writes plain Field, so the default fills in string. Line 5 writes Field<number>, and the type argument you wrote wins.Press the buttons to write line 1 three ways, and watch line 3:
type Field<T> = { value: T; error: string | null }; const name: Field = { value: "Asha", error: null };// ^?const age: Field<number> = { value: 30, error: null };console.log(name.value, age.value);any1 error: line 3prints Asha 30T has no default, so every use of Field must fill it in. Line 3 writes plain Field, and TypeScript says Generic type 'Field' requires 1 type argument(s).
With No default, plain Field is an error, as before. Default string fixes it. Default number shows that the default is a type, not a guess: plain Field now means Field<number>, and the text "Asha" doesn't fit. Line 5 never changes, because it writes its own type argument.
A type can have several type parameters, separated by commas, and only some of them need defaults:
type Field<Value, Problem = string> = { value: Value; error: Problem | null }; const email: Field<string> = { value: "asha@", error: "Add the part after @" };const password: Field<string, string[]> = { value: "abc", error: ["Too short", "Add a number"] };type PasswordField = Field<string, string[]>;// ^?console.log(email.error, password.error?.length);Line 1 names two type parameters: Value for the value and Problem for the error, which is text unless you say otherwise. Line 3 fills in only Value, so the email's error is text. Line 4 fills in both: a password field whose error is a list of problems. Line 5 names that type, and the query on line 6 shows it with error: string[] | null. Line 7 prints Add the part after @ 2: the email's message and the number of password problems. The ?. reads length only when error isn't null.
Type parameters with defaults go last. Otherwise, a single type argument like Field<string> would be unclear:
type Field<Problem = string, Value> = { value: Value; error: Problem | null };TypeScript says Required type parameters may not follow optional type parameters. A required type parameter has no default, so it must be filled in; an optional one has a default. Here Value is required and comes after Problem, which is optional. Swap them, as in the example before, and the error is gone.
Generic functions can have defaults too. A default is used when TypeScript has nothing to fill in T from:
function emptyList<T = string>(): T[] { return [];} const tags = emptyList();// ^?const prices = emptyList<number>();// ^?console.log(tags, prices);Line 1 says T = string. Line 5 calls emptyList() with no arguments, so nothing tells TypeScript what T is, and line 6 shows const tags: string[]. Without the default it would be unknown[], as in the Generic functions lesson. Line 7 writes <number>, and line 8 shows const prices: number[]. Line 9 prints [] [].
Generics you already use: Array, Map, Promise
You have been using generic types since the Arrays and tuples lesson. TypeScript's own types for arrays, Maps, Sets, and promises are generic, written the same way as yours:
const tags: Array<string> = ["new", "sale"];const quantities = new Map<string, number>();quantities.set("Tea", 2);const teaCount = quantities.get("Tea");// ^?const sizes = new Set<string>(["S", "M"]);console.log(tags.length, teaCount, sizes.has("M"));Line 1 writes Array<string>, the same type as string[]. Line 2 makes a Map, which stores values by key (the Map and Set lesson covers it). Its two type arguments say the keys are text and the values are numbers. Line 3 stores 2 for Tea. Line 4 reads it back, and the query on line 5 shows number | undefined: get gives back undefined for a key that isn't there. Line 6 makes a Set of sizes. Line 7 prints 2 2 true.
The type arguments are checked like any other type:
const quantities = new Map<string, number>();quantities.set("Tea", 2);quantities.set("Cake", "1");console.log(quantities.get("Cake"));Line 3 stores the text "1". TypeScript says Argument of type 'string' is not assignable to parameter of type 'number': the Map's values must be numbers. Run it anyway and line 4 prints 1, but it's text, and adding it to other quantities would join them.
Promise<T> is the type of a promise: a value that arrives later. T is the type of what it will hold. An async function always gives back a promise (see async and await):
type User = { name: string; email: string }; async function loadUser(): Promise<User> { return { name: "Asha", email: "asha@example.com" };} const pending = loadUser();// ^?pending.then((user) => console.log(user.name));// ^?Lines 3 to 5 declare loadUser, an async function. It returns a user object, but its return type is Promise<User>, because an async function wraps what it returns in a promise. Line 7 calls it. The query on line 8 shows const pending: Promise<User>: not a user yet, but a promise of one. Line 9 uses then, which calls a function with what the promise holds when it's ready. The query on line 10 shows (parameter) user: User: TypeScript took User out of Promise<User>. It prints Asha.
Press the buttons to read the user three ways on line 8:
type User = { name: string; email: string }; async function loadUser(): Promise<User> { return { name: "Asha", email: "asha@example.com" };} async function showName() { console.log(loadUser().name);}showName();undefined1 error: line 8loadUser() gives back a Promise<User>, not a User. A promise has no name, so TypeScript says Property 'name' does not exist on type 'Promise<User>'. Run it anyway and it prints undefined.
Read it right away treats the promise as if it were the user, and TypeScript stops it. await it and Use then both wait for the user. The type argument of Promise<User> is what lets TypeScript know that what comes out is a User.
Generic types can go inside each other. A function that loads products from the server gives back a promise of a reply of a list:
type Product = { name: string; price: number };type ApiResponse<T> = { status: number; data: T }; async function loadProducts(): Promise<ApiResponse<Product[]>> { return { status: 200, data: [{ name: "Tea", price: 3 }, { name: "Cake", price: 4 }] };} async function showCount() { const reply = await loadProducts(); // ^? console.log(reply.data.length);}showCount();Line 4 says loadProducts gives back Promise<ApiResponse<Product[]>>. Read it from the outside in: a promise, holding an ApiResponse, whose data is a Product[]. Line 9 uses await, which opens the promise, and the query on line 10 shows const reply: ApiResponse<Product[]>. Line 11 reads reply.data.length and prints 2. Line 13 calls the function.
Promise<ApiResponse<Product[]>> is one generic type inside another. Read it from the outside in: line 9's await opens the Promise, .data on line 11 opens the ApiResponse, and you reach the list of 2 products.The table below lists the built-in generic types you'll meet most. Read each row as: the type, what fills in its blanks, and an example.
| The generic type | What fills in its type parameters | Example |
|---|---|---|
Array<T> | T is the type of each item. T[] is a shorter way to write the same type. | Array<string>, the same as string[]. |
Map<K, V> | K is the type of the keys, and V is the type of the values. | Map<string, number> for quantities by product name. |
Set<T> | T is the type of each value in the set. | Set<string> for the sizes a shopper picked. |
Promise<T> | T is the type of the value the promise gives you when it's ready. | Promise<User> from an async function that loads a user. |
Loading states for any page
Every page that loads data shows one of three things: a loading message, an error, or the data. A product list and a profile page load different data, but the three states are the same. One generic type describes them all:
type Product = { name: string; price: number };type User = { name: string; email: string }; type LoadState<T> = | { status: "loading" } | { status: "error"; message: string } | { status: "done"; data: T }; function productsText(state: LoadState<Product[]>) { if (state.status === "loading") return "Loading..."; if (state.status === "error") return "Sorry: " + state.message; const list = state.data; // ^? return list.length + " products";} function profileText(state: LoadState<User>) { if (state.status !== "done") return "No profile yet"; return "Hi " + state.data.name;} console.log(productsText({ status: "done", data: [{ name: "Tea", price: 3 }] }));console.log(productsText({ status: "error", message: "Offline" }));console.log(profileText({ status: "done", data: { name: "Asha", email: "asha@example.com" } }));Lines 4 to 7 declare LoadState<T>: a union of three object types, told apart by status (the Discriminated unions lesson covers this pattern). Only the "done" state has data, and its type is T.
- Line 9 takes a
LoadState<Product[]>. Lines 10 and 11 handle loading and errors. After them, only"done"is left, so line 12 may readstate.data, and line 13 showsconst list: Product[]. - Line 17 takes a
LoadState<User>. After the check on line 18, line 19 readsstate.data.name, becausedatais aUserhere. - Lines 22 to 24 print
1 products,Sorry: Offline, andHi Asha.
The same type also stops you from reading data before it has loaded:
type User = { name: string; email: string }; type LoadState<T> = | { status: "loading" } | { status: "error"; message: string } | { status: "done"; data: T }; function profileText(state: LoadState<User>) { return "Hi " + state.data.name;} console.log(profileText({ status: "loading" }));Line 9 reads state.data without checking status first. TypeScript says Property 'data' does not exist on type 'LoadState<User>': the loading and error states have no data. Line 12 passes a loading state, and when the code runs it stops with a TypeError.
type Box<T> = { value: T }; const price: Box<number> = { value: 3 };type Box<T> = { value: T }; const price: Box = { value: 3 };type Box<T> = { value: T }; const price: Box<string> = { value: 3 };type Box<T = string> = { value: T }; const label: Box = { value: "Tea" };const counts = new Map<string, number>(); counts.set("Tea", "2");const tags: Array<string> = ["new", "sale"];type Maybe<T> = T | null; const coupon: Maybe<string> = null;async function load(): Promise<number> { return 3; } load().toFixed(2);
Sort each line by what TypeScript says about it. Each line is checked on its own.
Common mix-ups
A type argument tells TypeScript what you expect. It doesn't check data that arrives while the page runs:
type ApiResponse<T> = { status: number; data: T }; const text = '{ "status": 200, "data": "7" }';const reply: ApiResponse<number> = JSON.parse(text);console.log(reply.data + 1);Line 3 is a reply as the server sent it, with data as the text "7". Line 4 parses it with JSON.parse (see the JSON lesson) and says it's an ApiResponse<number>. JSON.parse can give back anything, so TypeScript trusts you, and there are no errors. Line 5 prints 71, because "7" + 1 joins text.
The table below shows when you write a type argument. Read each row as: this code, do you need one, and why.
| When you write | Do you need a type argument? | Why |
|---|---|---|
A generic type in an annotation: const name: Field | Yes, unless T has a default. | TypeScript doesn't work out a type's argument from the value you store. |
A generic type whose T has a default: Field with T = string | No. | The default fills in T, so Field means Field<string>. |
A call to a generic function: first(names) | Usually not. | TypeScript fills in T from the arguments. |
An empty Map: new Map() | Yes: new Map<string, number>(). | With nothing to go on, TypeScript makes it Map<any, any>, and checking stops. |
- "TypeScript fills in a generic type's argument from the value, like it does for a function." No. In an annotation like
const name: Field, you write the type argument, or the type parameter needs a default. - "
ApiResponse<number>checks the server's reply." No. Types are removed before the code runs. The reply with the text"7"got through and printed71. - "A default type parameter changes to match the value." No.
T = stringmeans plainFieldis alwaysField<string>. For a number, writeField<number>. - "A
Promise<User>is a user." No. It's a promise that will hold one. Useawaitorthento get the user out. - "The
Tin one generic type is the sameTas in another." No. Each declaration has its own type parameters.TinField<T>andTinPage<T>are unrelated, like two functions that both have a parameter calledvalue. - "Generic interfaces and generic type aliases work differently." No. For object shapes, you declare and use them the same way. Only a type alias can be a union, like
Maybe<T>.
Practice exercises
Each exercise is TypeScript. When you change code, press Edit & run on the starter code: the errors and ^? answers update as you type, and the console shows what it prints. Try a guess first, then use the hints.
What type does TypeScript give count? Guess first. Then point at count, or press Edit & run and add a // ^? line under it.
type Field<T> = { value: T; error: string | null }; const quantity: Field<number> = { value: 2, error: null };const count = quantity.value;type Field<T> = { value: T; error: string | null }; const quantity: Field<number> = { value: 2, error: null };const count = quantity.value;// ^?Field<number> fills in T with number, so quantity.value is a number. The query on line 5 shows const count: number.
Three form fields, one with a mistake. Which line will TypeScript mark?
type Field<T> = { value: T; error: string | null };
const email: Field<string> = { value: "asha@example.com", error: null };
const age: Field<number> = { value: "30", error: null };
const agree: Field<boolean> = { value: true, error: null };type Field<T> = { value: T; error: string | null }; const email: Field<string> = { value: "asha@example.com", error: null };const age: Field<number> = { value: "30", error: null }; // line 4: "30" is textconst agree: Field<boolean> = { value: true, error: null };Line 4 is a Field<number>, but its value "30" is text, so TypeScript reports Type 'string' is not assignable to type 'number'. Lines 3 and 5 match their type arguments.
ProductPage and SizePage are copies with a different item type. Replace them with one generic type. What do you write instead of SizePage on line 7?
type Product = { name: string; price: number }; type ProductPage = { items: Product[]; page: number };type SizePage = { items: string[]; page: number }; const products: ProductPage = { items: [{ name: "Tea", price: 3 }], page: 1 };const sizes: SizePage = { items: ["S", "M", "L"], page: 2 };console.log(products.items.length + sizes.items.length);type Product = { name: string; price: number }; type Page<T> = { items: T[]; page: number }; const products: Page<Product> = { items: [{ name: "Tea", price: 3 }], page: 1 };const sizes: Page<string> = { items: ["S", "M", "L"], page: 2 };console.log(products.items.length + sizes.items.length);type Page<T> = { items: T[]; page: number } replaces both copies. Page<Product> fills in T with Product, and Page<string> with string. The code still prints 4.
TypeScript marks line 4. Fix it with a type argument. What goes in the angle brackets?
type User = { name: string; email: string };type ApiResponse<T> = { status: number; data: T }; const reply: ApiResponse = { status: 200, data: { name: "Asha", email: "asha@example.com" } };console.log(reply.data.name);type User = { name: string; email: string };type ApiResponse<T> = { status: number; data: T }; const reply: ApiResponse<User> = { status: 200, data: { name: "Asha", email: "asha@example.com" } };console.log(reply.data.name);ApiResponse<User> fills in T, so data must be a User, and reply.data.name is text. The error is gone, and the code prints Asha.
Lines 3 and 4 use plain Field, and TypeScript marks both. Change only line 1 so they work and line 5 still holds a number. What does the code print then?
type Field<T> = { value: T; error: string | null }; const firstName: Field = { value: "Asha", error: null };const lastName: Field = { value: "Rao", error: null };const age: Field<number> = { value: 30, error: null };console.log(firstName.value, lastName.value, age.value);type Field<T = string> = { value: T; error: string | null }; const firstName: Field = { value: "Asha", error: null };const lastName: Field = { value: "Rao", error: null };const age: Field<number> = { value: 30, error: null };console.log(firstName.value, lastName.value, age.value);T = string makes plain Field mean Field<string>, so lines 3 and 4 have no errors. Line 5 writes Field<number>, which wins over the default. The code prints Asha Rao 30.
A cart counts quantities in a Map. TypeScript reports no errors. What does the starter print? Then give the Map type arguments, and fix what TypeScript finds.
const quantities = new Map();quantities.set("Tea", 2);quantities.set("Cake", "1"); let total = 0;for (const count of quantities.values()) { total += count;}console.log(total);const quantities = new Map<string, number>();quantities.set("Tea", 2);quantities.set("Cake", 1); let total = 0;for (const count of quantities.values()) { total += count;}console.log(total);The starter prints 21: 2 + "1" joins text. With Map<string, number>, TypeScript marks "1" on line 3 with Argument of type 'string' is not assignable to parameter of type 'number'. Storing the number 1 makes the total 3.
Check your understanding
For each question, find each generic type's blanks and what fills them: a type argument you wrote, or a default. The quiz shows its code without the checker, so the error list doesn't give the answers away.
Question 1 of 8What is
Tintype ApiResponse<T> = { status: number; data: T }?Choose an answer to see the explanation.
Question 2 of 8What type does
peopleget from this reply?Read the code, then predicttype User = { name: string; email: string }; type ApiResponse<T> = { status: number; data: T }; const reply: ApiResponse<User[]> = { status: 200, data: [] }; const people = reply.data;Choose an answer to see the explanation.
Question 3 of 8Which line does TypeScript flag in these form fields?
Read the code, then predicttype Field<T> = { value: T; error: string | null }; const email: Field<string> = { value: "asha@example.com", error: null }; const phone: Field = { value: "555-0100", error: null };Choose an answer to see the explanation.
Question 4 of 8What does TypeScript say about line 3, where
Fieldhas a default?Read the code, then predicttype Field<T = string> = { value: T; error: string | null }; const age: Field = { value: 30, error: null };Choose an answer to see the explanation.
Question 5 of 8What type does
resulthave when you callloadUser()withoutawait?Read the code, then predicttype User = { name: string; email: string }; async function loadUser(): Promise<User> { return { name: "Asha", email: "asha@example.com" }; } const result = loadUser();Choose an answer to see the explanation.
Question 6 of 8What type does
cakesget from this Map?Read the code, then predictconst stock = new Map<string, number>([["Tea", 5]]); const cakes = stock.get("Cake");Choose an answer to see the explanation.
Question 7 of 8What does this reply print, and does TypeScript complain?
Read the code, then predicttype ApiResponse<T> = { status: number; data: T }; const reply: ApiResponse<number> = JSON.parse('{ "status": 200, "data": "2" }'); console.log(reply.data + 1);Choose an answer to see the explanation.
Question 8 of 8When can you use a generic type without writing a type argument?
Choose an answer to see the explanation.
Key takeaways
- A generic type is a type alias or an interface with a type parameter, like
ApiResponse<T>. Write it once, instead of one copy per kind of data. - Each use fills in
Twith a type argument, likeApiResponse<User>, and TypeScript reads the type withTreplaced. - In an annotation, you write the type argument. TypeScript doesn't work it out from the value.
- A default, like
<T = string>, is used when you leave the type argument out. A type argument you write wins. Type parameters with defaults go last. Array<T>,Map<K, V>,Set<T>, andPromise<T>are generic types too. Read nested ones, likePromise<ApiResponse<Product[]>>, from the outside in.- A type argument is something you tell TypeScript. Data that arrives while the page runs isn't checked against it.
Remember the one-liner.
A generic type is a type with a blank: write it once, and fill in the blank each time you use it.
Coming next: Generic classes. It puts type parameters on classes, for a typed stack or cache that keeps its item type.