feat(curriculum): expand and reorganize initial typescript lessons (#65928)

Co-authored-by: Jeevankumar S <110320697+Jeevankumar-s@users.noreply.github.com>
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
This commit is contained in:
Jessica Wilkins
2026-02-20 15:04:12 -08:00
committed by GitHub
parent 120c5aa583
commit 5177bce626
12 changed files with 1672 additions and 147 deletions
+6
View File
@@ -3639,6 +3639,12 @@
"In these lessons, you will learn what TypeScript is and how to use it."
]
},
"lecture-understanding-type-composition": {
"title": "Understanding Type Composition",
"intro": [
"In these lessons, you will learn how to work with union types, interfaces, void types and more."
]
},
"lecture-working-with-generics-and-type-narrowing": {
"title": "Working with Generics and Type Narrowing",
"intro": [
@@ -1,125 +1,124 @@
---
id: 67d3018062fe6ba92b7d8299
title: How Do the Different Types Work in TypeScript?
title: How Do Primitive Types Work in TypeScript?
challengeType: 19
dashedName: how-do-the-different-types-work-in-typescript
dashedName: how-do-primitive-types-work-in-typescript
---
# --description--
You've already seen one in the previous lesson: `string[]`, which represents an array of strings. But how does that actually work?
In this lesson, you will learn how to work with basic primitive types and how type annotations work for variables.
For the primitive data types `string`, `null`, `undefined`, `number`, `boolean`, and `bigint`, TypeScript offers corresponding type keywords. In our example, we are using these types to annotate our variables:
For the primitive data types `string`, `null`, `undefined`, `number`, `boolean`, and `bigint`, TypeScript offers corresponding type keywords.
```js
let str: string = "Naomi";
let num: number = 42;
let bool: boolean = true;
let nope: null = null;
let nada: undefined = undefined;
For this lesson, we will focus on the commonly used primitives and not go into much detail for `bigint`. The `bigint` data type is a numeric data type in JavaScript to represent numbers larger than the standard integer type.
Let's take a look at each of these primitive types starting with `string`. Here is how you can type a string in TypeScript:
```ts
let firstName: string = "Angie";
```
Now, we have explicitly declared `str` as `string` - which might seem confusing because we assigned it a string value already, but this annotation ensures that we cannot reassign a different value type (like a number) to that variable.
Unlike vanilla JavaScript, the variable `firstName` is expecting a `string` type. So if you try to reassign it with a value of a number, then you will get an error like this:
But what about arrays and objects? Well, you've already seen the syntax for an array! You can define an array of specific type with two different syntaxes:
```js
const arrOne: string[] = ["Naomi"];
const arrTwo: Array<string> = ["Camperchan"];
```ts
let firstName: string = "Angie";
firstName = 9 // Type 'number' is not assignable to type 'string'.
```
Fundamentally, these two syntaxes are the same, and the choice between the two is generally a matter of preference.
The next type we will look at is the `number` type. Here is an example:
Objects get a bit more complicated. You can define the exact structure of an object:
```js
const obj: { a: string, b: number } = { a: "Naomi", b: 42 };
```ts
let age: number = 16;
```
This syntax means that property `a` must always be a string, property `b` must always be a number, and you cannot add or remove properties.
You can assign integer and floats to the `number` type. But if you were to try and reassign a value that is a `bigint` type, then you will get an error like this:
But maybe you don't want to restrict the properties? Maybe you want an object with any keys, but the values all need to be strings. There are two ways you can do this:
```ts
let age: number = 16;
```js
const objOne: Record<string, string> = {};
const objTwo: { [key: string]: string } = {};
// Type 'bigint' is not assignable to type 'number'.
age = 123n;
```
Like the array types, these are fundamentally similar. With these object types, you must define both the type of the keys and the type of the values. Keys must always be strings, but you can define custom string types to further restrict those keys.
The next type we will look at is the `boolean` type. Here are some examples:
In addition to these types, TypeScript offers four other helpful types. The first is `any`, which indicates that a value can have any type. This is effectively the Konami Code of TypeScript - it tells the compiler to stop caring about the type of that variable and let you do whatever.
The second is `unknown`, which is generally preferred over `any`. The `unknown` type tells TypeScript that you do care about the type of the value, but you don't actually know what it is. If you then try to perform type-specific actions (like a subtraction operator, or the `slice()` method to perform a specific string operation), TypeScript will expect you to narrow down the type of that value first. You'll learn more about type narrowing in an upcoming lesson.
The third is `void`. This is a special type that you'll typically only use when defining functions. It's effectively the opposite of `any` - it represents the absence of any type at all. Functions which don't have a return value, such as `console.log()`, have a return type of `void`.
Finally, there is the `never` type. This is probably something you won't use often - it represents a type that will never exist. For example, passing a mock object to a function in your test suite might be a good use of `never` to indicate that such an object would never really be passed to the function.
Expanding on these types, you have access to the `type` keyword. This keyword is like `const`, but instead of declaring a variable you can declare a type:
```js
type myString = string;
```ts
let isLoggedIn: boolean = true;
let isAdmin: boolean = false;
```
This might not seem terribly useful on its own, but when coupled with union types it becomes powerful. A union type allows you to combine two or more types into one. Here's an example:
The last two types are the `null` and `undefined` types.
```js
type stringOrNumber = string | number;
As you recall from earlier JavaScript lessons, `null` represents an intentional absence of a value. `undefined` means a variable has been declared but has not been assigned a value.
You will see examples of how to work with these types later on when you learn about function types and union types.
Now that you have seen how type annotations work with variables, the question arises on if you need to always explicitly type out your variables.
If you have a simple type annotation like this, then you might not need to use an explicit type.
```ts
let firstName: string = "Angie";
```
Our `stringOrNumber` type matches values that are a string and values that are a number. You can then combine your type with other types, like an array:
Reason being is because TypeScript will try to automatically infer the types based on its value. So if you write this, TypeScript will understand it is a string.
```js
const stuff: stringOrNumber[] = ["a", 2, "c", 1000];
let firstName = "Angie";
```
You can also define more strict types that include only specific values:
```js
type bot = "camperchan" | "camperbot" | "naomi";
type digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
```
You could then combine those types to create more specific restrictions on an object:
```js
const artificialIntelligence: Record<bot, digit> = { camperchan: 5 }
```
Though if you need more control over the structure of an object, chances are you'll reach for our final type: `interface`. Interfaces are effectively classes, but for types. They can implement or extend other interfaces, are specifically object types, and are generally preferred unless you need a specific feature offered by a type declaration.
```js
interface wowie {
zowie: boolean;
method: () => void;
}
```
Finally, functions can also be given type signatures. In the previous lesson, you saw how to define the type of a particular parameter:
```js
const getRandomValue = (array: string[]) => {
return array[Math.floor(Math.random() * array.length)];
}
```
But you can also define the return type of the function.
```js
const getRandomValue = (array: string[]): string => {
return array[Math.floor(Math.random() * array.length)];
}
```
In this example, we've told TypeScript that the function should return a string. If we try to return anything else, TypeScript will provide a compiler error to let us know.
And that covers the basics of TypeScript's type system. It's pretty complex, and has a lot of moving parts, but it can often help to think of it as mirroring JavaScript's types.
You should explicitly type your variables when TypeScript cannot clearly infer the type or when you want to enforce a specific type for situations like function parameters or complex objects.
# --questions--
## --text--
Which of the following is NOT a primitive data type in TypeScript?
Which of the following is the correct way to explicitly add a type annotation to a number variable?
## --answers--
```ts
let age: number = 16;
```
---
```ts
let age<> number = 16;
```
### --feedback--
Refer back to the beginning of the lesson for number types.
---
```ts
let age>> number = 16;
```
### --feedback--
Refer back to the beginning of the lesson for number types.
---
```ts
let age == number = 16;
```
### --feedback--
Refer back to the beginning of the lesson for number types.
## --video-solution--
1
## --text--
Which of the following is NOT a primitive data type?
## --answers--
@@ -127,15 +126,7 @@ Which of the following is NOT a primitive data type in TypeScript?
### --feedback--
Primitive data types are the most basic data types available in TypeScript.
---
`number`
### --feedback--
Primitive data types are the most basic data types available in TypeScript.
Refer back to the beginning of the lesson.
---
@@ -143,84 +134,56 @@ Primitive data types are the most basic data types available in TypeScript.
### --feedback--
Primitive data types are the most basic data types available in TypeScript.
Refer back to the beginning of the lesson.
---
`array`
`object`
---
`number`
### --feedback--
Refer back to the beginning of the lesson.
## --video-solution--
4
3
## --text--
What is the purpose of the `unknown` type in TypeScript?
When should you explicitly type your variables in TypeScript?
## --answers--
It allows any type of value to be assigned without type checking.
When you are working with `number` and `bigint` types.
### --feedback--
Unlike `any`, `unknown` requires type narrowing before specific operations can be performed.
Refer back to the end of the lesson.
---
It indicates that you don't know the type and need to narrow it before use.
---
It represents the absence of any type at all.
When you are working with `null` and `undefined` types.
### --feedback--
Unlike `any`, `unknown` requires type narrowing before specific operations can be performed.
Refer back to the end of the lesson.
---
It represents a type that will never exist.
When TypeScript cannot clearly infer the type.
---
You should always have explicit types in your code.
### --feedback--
Unlike `any`, `unknown` requires type narrowing before specific operations can be performed.
Refer back to the end of the lesson.
## --video-solution--
2
## --text--
Which keyword is used to declare a custom type in TypeScript?
## --answers--
`interface`
### --feedback--
This keyword is used to define a contract for object shapes, not to declare custom types.
---
`type`
---
`custom`
### --feedback--
This is not a valid keyword in TypeScript.
---
`define`
### --feedback--
This is not a valid keyword in TypeScript.
## --video-solution--
2
3
@@ -0,0 +1,225 @@
---
id: 698f515e99f9a25a3462f97f
title: How do Function Types Work?
challengeType: 19
dashedName: how-do-function-types-work
---
# --description--
In this lesson, you will learn how to work with function types. Let's start with parameter type annotations.
Here is an example of a basic function:
```ts
function circleArea(radius) {
return Math.PI * radius * radius;
}
```
Right now, TypeScript would display a message of `Parameter 'radius' implicitly has an 'any' type`. The `any` type indicates that a value can have any type. You will learn more about this type in a future lesson.
To get rid of the error and explicitly type the parameter, you can type it like this:
```ts
function circleArea(radius: number) {
return Math.PI * radius * radius;
}
```
If you try to call the `circleArea` function with a different type that is not a number, TypeScript will display an error like this:
```ts
function circleArea(radius: number) {
return Math.PI * radius * radius;
}
// Argument of type 'string' is not assignable to parameter of type 'number'
circleArea("3");
```
If you try to provide more arguments then the function expects, TypeScript will display an error:
```ts
function circleArea(radius: number) {
return Math.PI * radius * radius;
}
// Expected 1 arguments, but got 2.
circleArea(3, 4);
```
If you provide too few arguments, then TypeScript will also display an error:
```ts
function circleArea(radius: number) {
return Math.PI * radius * radius;
}
// Expected 1 arguments, but got 0.
// An argument for 'radius' was not provided.
circleArea();
```
You can make parameters optional by the using the `?` symbol near the parameter like this:
```ts
function area(radius: number, height?: number): number {
const baseArea = Math.PI * radius * radius;
if (height !== undefined) {
const sideArea = 2 * Math.PI * radius * height;
return baseArea + sideArea;
}
return baseArea;
}
```
The conditional check inside of the function is there in case the `height` argument is not provided.
If you want to type your return values, you can add return type annotations like this:
```ts
function circleArea(radius: number): number {
return Math.PI * radius * radius;
}
```
The return type annotation goes after the parameter list. Similar to variable type annotations, most of the time TypeScript will be smart enough to infer the return type. So use an explicit return type annotation when you want to make the function's intent clearer or prevent unintended changes to the return value.
The next example we will look at will deal with asynchronous functions and promises. If your function is going to return a promise, you can use the `Promise` type like this:
```ts
async function getCityTemperature(city: string): Promise<number> {
const response = await fetch(`https://api.example.com/weather?city=${city}`);
const data = await response.json();
return data.temperature;
}
```
In this situation, if you try to remove the return type, TypeScript might infer the return as `Promise<any>`. So this would be a good situation to explicitly type the return value.
Up till this point, we have only looked at named function examples. But how do types work with anonymous functions?
Here is an example using an anonymous function in the callback passed to a `forEach`:
```ts
const languages = ["JavaScript", "Python", "TypeScript"];
languages.forEach((lang) => {
console.log(`${lang.toUpperCase()} has ${lang.length} characters`);
});
```
The `lang` parameter does not have a type annotation. Instead, TypeScript uses contextual typing which means it infers the type from the context in which it appears.
# --questions--
## --text--
Which of the following TypeScript types indicates that a value can have any type?
## --answers--
`any`
---
`unknown`
### --feedback--
Refer back to the beginning of the lesson.
---
`optional`
### --feedback--
Refer back to the beginning of the lesson.
---
`generic`
### --feedback--
Refer back to the beginning of the lesson.
## --video-solution--
1
## --text--
What happens if you provide too many arguments in a function call?
## --answers--
TypeScript will ignore the extra arguments.
### --feedback--
Refer back to the middle of the lesson where this was discussed.
---
Nothing will happen.
### --feedback--
Refer back to the middle of the lesson where this was discussed.
---
TypeScript will display an error message.
---
TypeScript will use all of the arguments.
### --feedback--
Refer back to the middle of the lesson where this was discussed.
## --video-solution--
3
## --text--
What is contextual typing?
## --answers--
It infers the type from the context in which it appears.
---
It always defaults variables to the type `any`.
### --feedback--
Refer to the end of the lesson.
---
It forces you to declare the type explicitly every time.
### --feedback--
Refer to the end of the lesson.
---
It automatically changes a variable's type to match the first value assigned.
### --feedback--
Refer to the end of the lesson.
## --video-solution--
1
@@ -0,0 +1,309 @@
---
id: 698f53374f7ad3a078dafbc0
title: How Do Types Work with Objects and Arrays?
challengeType: 19
dashedName: how-do-types-work-with-objects-and-arrays
---
# --description--
In this lesson, you will learn how to add types for objects and arrays.
Let's take a look at arrays first.
Here is an example of an array of programming languages:
```js
const programmingLanguages = ["Java", "C++", "Python"];
```
If you wanted to explicitly type that array as an array of strings, then you can type it as `string[]`:
```ts
const programmingLanguages:string[] = ["Java", "C++", "Python"];
```
As you learned earlier, TypeScript will be smart enough to infer the type as an array of strings if you just write this:
```js
const programmingLanguages = ["Java", "C++", "Python"];
```
But explicitly typing it will help in situations if you attempt to add another type to the array like this:
```ts
// Type 'number' is not assignable to type 'string'.(2322)
const programmingLanguages:string[] = ["Java", 9, "Python"];
```
Another way to type arrays is to use this syntax:
```ts
const programmingLanguages:Array<string> = ["Java", "C++", "Python"];
```
Fundamentally, these two syntaxes are the same, and the choice between the two is generally a matter of preference.
Now, let's take a look at typing objects.
Here is an example of an object literal:
```js
const developer = {
firstName: "Jessica",
isEmployed: true
}
```
To explicitly type the object properties, you can use this syntax:
```ts
const developer: { firstName: string, isEmployed: boolean } = {
firstName: "Jessica",
isEmployed: true
}
```
If you try to add or remove properties from the object, then you will get an error like this:
```ts
// Property 'isEmployed' is missing in type '{ firstName: string; }' but required in type '{ firstName: string; isEmployed: boolean; }'
const developer: { firstName: string, isEmployed: boolean } = {
firstName: "Jessica",
}
```
You can make object properties optional by using the `?` after the property name like this:
```ts
const developer: { firstName: string, isEmployed?: boolean } = {
firstName: "Jessica",
}
```
TypeScript will no longer display an error because the `isEmployed` property has been marked as optional.
So far, all of the object examples have had restrictive properties. That means we explicitly listed every property the object is allowed to have:
```ts
const developer: { firstName: string, isEmployed: boolean } = {
firstName: "Jessica",
isEmployed: true
}
```
But what if you don't know all of the property names ahead of time? What if you want an object that can have any number of properties but all of the values must follow a specific type?
Well, there are two ways to accomplish this. The first way is to use a `Record` which is a built-in generic utility type in TypeScript.
Here is the basic syntax:
```ts
Record<Keys, ValueType>
```
The first type represents the type of the keys while the second type represents the type of the values.
Here is an example of using a `Record` for a `userRoles` object:
```ts
const userRoles: Record<string, string> = {
admin: "full-access",
editor: "limited-access",
viewer: "read-only"
};
```
This `userRoles` object can have any string keys, and all of the values must be strings. If you try to assign a non-string value, youll get an error:
```ts
// Type 'number' is not assignable to type 'string'
const userRoles: Record<string, string> = {
admin: 1
};
```
The second way to write the same idea is with an index signature:
```ts
const obj: { [key: string]: string } = {};
```
The syntax above says that any property with a string key must have a string value. Here is an example for a `settings` object:
```ts
const settings: { [key: string]: string } = {
theme: "dark",
language: "en",
layout: "grid"
};
```
Just like earlier, if you have incorrect value types, TypeScript will display an error message:
```ts
// Type 'boolean' is not assignable to type 'string'
const settings: { [key: string]: string } = {
darkMode: true
};
```
Both `Record` and index signatures are fundamentally the same. So choosing between them is mostly a matter of preference.
# --questions--
## --text--
Which of the following is the correct way to type an array of strings?
## --answers--
```ts
const programmingLanguages:strings[] = ["Java", "C++", "Python"];
```
### --feedback--
Refer back to the beginning of the lesson.
---
```ts
const programmingLanguages:stringArr[] = ["Java", "C++", "Python"];
```
### --feedback--
Refer back to the beginning of the lesson.
---
```ts
const programmingLanguages:string[] = ["Java", "C++", "Python"];
```
---
```ts
const programmingLanguages:Record string[] = ["Java", "C++", "Python"];
```
### --feedback--
Refer back to the beginning of the lesson.
## --video-solution--
3
## --text--
Which of the following is the correct way to mark an object property as optional?
## --answers--
```ts
const developer: { firstName: string, isEmployed<<>>: boolean } = {
firstName: "Jessica",
}
```
### --feedback--
Refer back to the earlier object section where optional properties were discussed.
---
```ts
const developer: { firstName: string, isEmployed:: boolean } = {
firstName: "Jessica",
}
```
### --feedback--
Refer back to the earlier object section where optional properties were discussed.
---
```ts
const developer: { firstName: string, isEmployed?: boolean } = {
firstName: "Jessica",
}
```
---
```ts
const developer: { firstName: string, isEmployed[]: boolean } = {
firstName: "Jessica",
}
```
### --feedback--
Refer back to the earlier object section where optional properties were discussed.
## --video-solution--
3
## --text--
Which of the following is the correct way to use a `Record` for an object?
## --answers--
```ts
const userRoles: Record(string, string) = {
admin: "full-access",
editor: "limited-access",
viewer: "read-only"
};
```
### --feedback--
Refer back to the last part of the lesson.
---
```ts
const userRoles: Record<string, string> = {
admin: "full-access",
editor: "limited-access",
viewer: "read-only"
};
```
---
```ts
const userRoles: Record<<string, string>> = {
admin: "full-access",
editor: "limited-access",
viewer: "read-only"
};
```
### --feedback--
Refer back to the last part of the lesson.
---
```ts
const userRoles: Record[string, string] = {
admin: "full-access",
editor: "limited-access",
viewer: "read-only"
};
```
### --feedback--
Refer back to the last part of the lesson.
## --video-solution--
2
@@ -0,0 +1,206 @@
---
id: 698f4f9ff24a150af1b1fa06
title: How Do The any, never, unknown and void Types Work?
challengeType: 19
dashedName: how-do-the-any-never-unknown-and-void-types-work
---
# --description--
In prior lessons, you have learned how to work with primitive types as well as union types, and interfaces.
But TypeScript has some special types that you should be aware of. Let's start by looking at the `any` type.
The `any` type is used to represent any type of value.
```ts
let randomValue: any;
randomValue = 42;
randomValue = "Hello";
randomValue = true;
randomValue = { name: "Alice" };
```
With this type, you have to be careful because it can be easily overused and misused. You don't want to type everything with the `any` type just to silence TypeScript error messages. That defeats the purpose of using TypeScript and adding type safety in the first place.
A safer counterpart to the `any` type would be the `unknown` type. `unknown` is similar to `any` but with the `unknown` type, you have to do a type check for the variable before using it.
Here is an example:
```ts
function doubleValue(value: unknown) {
if (typeof value === "number") {
console.log(value * 2);
} else if (typeof value === "string") {
console.log(value + value);
}
}
doubleValue(10);
doubleValue("Hi ");
doubleValue(true);
```
In this example, the type checks ensure that operations are only performed on values of the correct type. Values that don't match the expected types are ignored.
The next type we will look at is the `never` type. This type represents something that will never happen.
In most situations, you will not be writing out type annotations using `never`. Instead, you will most likely see the `never` type show up in error messages like this:
```ts
function processValue(value: string | number) {
if (typeof value === "string") {
console.log("String value:", value.toUpperCase());
} else if (typeof value === "number") {
console.log("Number value:", value * 2);
} else {
console.log(value);
}
}
```
Inside the `else` clause of the example above, the `value` has a `never` type. This is because the `else` branch is now impossible to reach and there's no remaining type left. If you tried to call the function with a value other than `string` or `number` it would show an error like this:
```ts
function processValue(value: string | number) {
if (typeof value === "string") {
console.log("String value:", value.toUpperCase());
} else if (typeof value === "number") {
console.log("Number value:", value * 2);
} else {
console.log(value);
}
}
// Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
processValue(true);
```
The last type we will look at is the `void` type. When you have a function that doesn't return a value, that would be a `void` type.
Here is an example:
```ts
type Status = "loading" | "success" | "error";
type Handler = {
status: Status;
onChange: (newStatus: Status) => void;
};
```
In this example, the `onChange` property for the `Handler` type doesn't return anything which is why `void` is being used.
# --questions--
## --text--
Which TypeScript type allows you to store any kind of value but should be used carefully to avoid losing type safety?
## --answers--
`void`
### --feedback--
Refer back to the beginning of the lesson.
---
`any`
---
`unknown`
### --feedback--
Refer back to the beginning of the lesson.
---
`null`
### --feedback--
Refer back to the beginning of the lesson.
## --video-solution--
2
## --text--
What is the purpose of the `never` type in TypeScript?
## --answers--
It represents a value that can be any type.
### --feedback--
Refer back to the middle of the lesson where the `never` type was taught.
---
It represents a function return type that is optional.
### --feedback--
Refer back to the middle of the lesson where the `never` type was taught.
---
It represents something that will never happen.
---
It represents a variable that can hold any type.
### --feedback--
Refer back to the middle of the lesson where the `never` type was taught.
## --video-solution--
3
## --text--
Which type would you use for a function that doesn't return a value?
## --answers--
`null`
### --feedback--
Refer back to the end of the lesson.
---
`void`
---
`any`
### --feedback--
Refer back to the end of the lesson.
---
`unknown`
### --feedback--
Refer back to the end of the lesson.
## --video-solution--
2
@@ -0,0 +1,200 @@
---
id: 698f5a372871b5dfb9e01698
title: What are Union Types and How Do They Work?
challengeType: 19
dashedName: what-are-union-types-and-how-do-they-work
---
# --description--
In the prior lessons, you learned how to work with primitive types like `string`, and `boolean`. But TypeScript allows you to build new types from existing ones. One example of this would be the union type.
A union type is a type that combines two or more other types.
Here is the basic syntax:
```ts
type1 | type2 | type3
```
Let's take a look at some examples to better understand the concept.
Here is a variable called `id` with a union type for `string` or `number`:
```ts
let id: string | number = "user_783";
```
The value can be updated to a number which is still valid for the union type:
```ts
let id: string | number = 1024;
```
If you tried to assign another value that isn't a `string` or `number`, then TypeScript will display an error message:
```ts
// Type 'string[]' is not assignable to type 'string | number'.
let id: string | number = ["Java", "Python"];
```
When working with union types, it is important to use methods that work for all of the types, otherwise TypeScript will throw an error like this:
```ts
function getId(id: string | number) {
// Property 'toUpperCase' does not exist on type 'string | number'.
return id.toUpperCase();
}
```
There is a solution for this which involves narrowing the union. But you will learn more about that as well as how union types work with type aliases in future lessons.
Union types are also helpful when working with `null` and `undefined` values.
Here is an example where a variable might contain a `string` value, or it might be `null` if no value has been set yet:
```ts
let username: string | null;
// Type 'number' is not assignable to type 'string | null'.
username = 42;
```
In this case, `username` can either be a `string` or `null`, but nothing else. You might use `null` when you want to intentionally represent the absence of a value.
Here is another example using `undefined`:
```ts
let score: number | undefined;
score = undefined;
// Type 'string' is not assignable to type 'number | undefined'.
score = "high";
```
In this example, `score` can either be a `number` or `undefined`. You might use `undefined` when a value has not been assigned yet.
Using union types with `null` and `undefined` makes it clear when a value might be missing, while still allowing TypeScript to catch invalid assignments.
# --questions--
## --text--
Which of the following is the correct way to create a union type?
## --answers--
```ts
let id: string >> number;
```
### --feedback--
Refer back to the beginning of the lesson.
---
```ts
let id: string | number;
```
---
```ts
let id: string ? number;
```
### --feedback--
Refer back to the beginning of the lesson.
---
```ts
let id: string << number;
```
### --feedback--
Refer back to the beginning of the lesson.
## --video-solution--
2
## --text--
Which assignment would be considered valid for the following?
```ts
let id: string | number
```
## --answers--
`false`
### --feedback--
Refer back to the beginning of the lesson.
---
`"28djj38372"`
---
`["a", "b"]`
### --feedback--
Refer back to the beginning of the lesson.
---
`null`
### --feedback--
Refer back to the beginning of the lesson.
## --video-solution--
2
## --text--
What will happen if you call a method that only exists on a string for a union `type string | number` without using type narrowing?
## --answers--
Nothing will happen.
### --feedback--
Refer back to the end of the lesson.
---
It will convert the number to a string.
### --feedback--
Refer back to the end of the lesson.
---
The method will be ignored at runtime.
### --feedback--
Refer back to the end of the lesson.
---
TypeScript will throw an error.
## --video-solution--
4
@@ -0,0 +1,216 @@
---
id: 698f5a3a492176259d1afcf9
title: What are Interfaces and How Do They Work?
challengeType: 19
dashedName: what-are-interfaces-and-how-do-they-work
---
# --description--
In a prior lesson, you learned how to work with type aliases and saw examples on how to name object types.
As you recall, one way to name an object type would be to use the `type` keyword like this:
```ts
type ID = number | string;
type User = {
id: ID;
name: string;
};
```
Another way to name an object type would be to use an `interface` declaration like this:
```ts
type ID = number | string;
interface User {
id: ID;
name: string;
}
```
`type` and `interface` are similar but one key difference is that interfaces use the `extends` keyword to build on other interfaces, while `type` aliases use intersection types (`&`) to combine types.
Let's look at some examples:
```ts
interface User {
id: number | string;
name: string;
}
interface Admin extends User {
role: string;
}
const adminUser: Admin = {
id: 101,
name: "Alice",
role: "superadmin"
};
```
In this example, `User` starts with `id` and `name`. The `Admin` interface extends `User`, meaning it includes `id` and `name` and adds a new property called `role`.
If you wanted to extend a `type`, you would use an intersection type (`&`) like this:
```ts
type User = {
id: number | string;
name: string;
};
type Admin = User & {
role: string;
};
const adminUser: Admin = {
id: 101,
name: "Alice",
role: "superadmin"
};
```
`Admin` combines the `User` type with a new object type that includes `role`. The result is the same shape as the interface example which is an object with `id`, `name`, and `role`.
Both `type` and `interface` can describe the shape of an object. If you're unsure which to use, remember that `interface` is specifically designed for object shapes, while `type` is more flexible and can also represent union types, primitives, and more complex type combinations.
# --questions--
## --text--
Which TypeScript feature allows you to create a new type by combining multiple existing types using `&`?
## --answers--
`interface`
### --feedback--
Refer back to the middle of the lesson where intersection types were discussed.
---
`type`
---
`class`
### --feedback--
Refer back to the middle of the lesson where intersection types were discussed.
---
`enum`
### --feedback--
Refer back to the middle of the lesson where intersection types were discussed.
## --video-solution--
2
## --text--
Which of the following is the correct way to create an `interface`?
## --answers--
```ts
type ID = number | string;
interface User {
id: ID;
name: string;
}
```
---
```ts
type ID = number | string;
interface User == {
id: ID;
name: string;
}
```
### --feedback--
Refer to the beginning of the lesson.
---
```ts
type ID = number | string;
interface > User {
id: ID;
name: string;
}
```
### --feedback--
Refer to the beginning of the lesson.
---
```ts
type ID = number | string;
interface User >> {
id: ID;
name: string;
}
```
### --feedback--
Refer to the beginning of the lesson.
## --video-solution--
1
## --text--
Which of the following is true about type aliases in TypeScript?
## --answers--
They can represent unions and intersections.
---
They can only describe object shapes.
### --feedback--
Refer to the end of the lesson.
---
They must use extends to build on other types.
### --feedback--
Refer to the end of the lesson.
---
They cannot be assigned to variables.
### --feedback--
Refer to the end of the lesson.
## --video-solution--
1
@@ -0,0 +1,187 @@
---
id: 69945fc2cd3160713372860e
title: What are Type Aliases and How Do They Work?
challengeType: 19
dashedName: what-are-type-aliases-and-how-do-they-work
---
# --description--
In the prior lesson, you learned how to work with union types. One common way to work with union types is to use a type alias.
A type alias is a name for a type. Here is an example of providing a name for a union type:
```ts
type ID = number | string;
```
Often times in your projects, you will create type aliases and use them throughout your program like this:
```ts
type ID = number | string;
type User = {
id: ID;
name: string;
};
function getUserId(user: User): ID {
return user.id;
}
```
This example uses two types named `ID` and `User`. `ID` would be a union type for `number` or `string` while `User` is an object type with `id` and `name` properties. The `id` is specified with the `ID` type while the `name` has the `string` type.
The `getUserId` function has one parameter called `user` and its type is `User`. The return type for the `getUserId` function would be `ID`.
Type aliases are a great way to describe the shape of an object. It is common to create object types and use them in arrays like this:
```ts
type User = {
id: number | string;
name: string;
};
const users: User[] = [
{ id: 101, name: "Alice" },
{ id: "A102", name: "Bob" },
{ id: 103, name: "Charlie" }
];
```
In this example, the `users` array must be filled with only `User` objects. If you forget a property in one of the objects, then TypeScript will throw an error like this:
```ts
type User = {
id: number | string;
name: string;
};
const users: User[] = [
// Property 'name' is missing in type '{ id: number; }' but required in type 'User'.
{ id: 101 },
{ id: "A102", name: "Bob" },
{ id: 103, name: "Charlie" }
];
```
You might have noticed the use of PascalCase when it came to naming the type aliases. This is because by convention using PascalCase helps distinguish types from variables. It also helps with readability. When you see `User[]`, you will know it's an array of objects of type `User`.
# --questions--
## --text--
What is a type alias in TypeScript?
## --answers--
A built-in TypeScript keyword used only for arrays.
### --feedback--
Refer back to the beginning of the lesson.
---
A special class that stores multiple values.
### --feedback--
Refer back to the beginning of the lesson.
---
A name given to a type, such as a union or object type.
---
A function that automatically converts one type to another.
### --feedback--
Refer back to the beginning of the lesson.
## --video-solution--
3
## --text--
What is the type of `id` inside of the `User` type here?
```ts
type ID = number | string;
type User = {
id: ID;
name: string;
};
```
## --answers--
`number`
### --feedback--
Refer back to the beginning of the lesson.
---
`string`
### --feedback--
Refer back to the beginning of the lesson.
---
`number | string`
---
`null`
### --feedback--
Refer back to the beginning of the lesson.
## --video-solution--
3
## --text--
Why is PascalCase commonly used when naming type aliases in TypeScript?
## --answers--
Because it is required by TypeScript.
### --feedback--
Refer back to the end of the lesson.
---
To help distinguish types from variables and improve readability.
---
To make the code run faster.
### --feedback--
Refer back to the end of the lesson.
---
To allow the type to be used in arrays.
### --feedback--
Refer back to the end of the lesson.
## --video-solution--
2
@@ -0,0 +1,174 @@
---
id: 699460b8cd3160713372860f
title: What are Type Assertions and How Do They Work?
challengeType: 19
dashedName: what-are-type-assertions-and-how-do-they-work
---
# --description--
Sometimes there will be situations where you will know more about a type than TypeScript does. This is where type assertions come in handy.
Here is an example using the `querySelector()` method to access an element with the `id` of `submit`:
```ts
const submitBtn = document.querySelector("#submit");
```
Right now, TypeScript only knows that this `submitBtn` is some sort of `Element` or possibly `null`. TypeScript will not look at the corresponding HTML to figure it out.
This is good place to use a type assertion to tell TypeScript what type of element this is. Here is an example:
```ts
const submitBtn = document.querySelector("#submit") as HTMLButtonElement;
```
Now, TypeScript will treat this as a `button` element because of that type assertion.
There are limits to using the `as` keyword. For example, you can't do stuff like this without TypeScript throwing an error:
```ts
// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
const age = "awesome" as number;
```
In this example, it doesn't make much sense to try and assert that the string `"awesome"` should be viewed as a number type. TypeScript only allows type assertions between compatible or overlapping types.
One way to remove that error would be to use a double assertion like this:
```ts
const age = "awesome" as unknown as number;
```
Even though this works in TypeScript, it is still not recommended because logically it doesn't make much sense.
Another way to write type assertions is to use angle bracket syntax like this:
```ts
const submitBtn = <HTMLButtonElement>document.querySelector("#submit");
```
You need to be careful with this syntax when using `tsx` files because TypeScript will think you are trying to render a component and throw an error like this:
```ts
// file: index.tsx
// This JSX tag requires 'React' to be in scope, but it could not be found.
// JSX element 'HTMLButtonElement' has no corresponding closing tag.
const submitBtn = <HTMLButtonElement>document.querySelector("#submit");
```
In most situations, you will see the first syntax using the `as` keyword being used over the second option.
# --questions--
## --text--
What is the main purpose of a type assertion in TypeScript?
## --answers--
To convert a value at runtime to a different type.
### --feedback--
Refer back to the beginning of the lesson.
---
To tell TypeScript to treat a value as a more specific type.
---
To prevent values from being `null`.
### --feedback--
Refer back to the beginning of the lesson.
---
To automatically check the DOM for the correct element type.
### --feedback--
Refer back to the beginning of the lesson.
## --video-solution--
2
## --text--
Why does TypeScript throw an error for this code?
```ts
const age = "awesome" as number;
```
## --answers--
TypeScript does not allow type assertions with strings and numbers.
### --feedback--
Refer back to the middle of the lesson where limitations of the `as` keyword were discussed.
---
TypeScript requires using `unknown` for all type assertions.
### --feedback--
Refer back to the middle of the lesson where limitations of the `as` keyword were discussed.
---
String and number do not sufficiently overlap as types.
---
TypeScript requires using `any` for all type assertions.
### --feedback--
Refer back to the middle of the lesson where limitations of the `as` keyword were discussed.
## --video-solution--
3
## --text--
Why should you be careful when using angle bracket syntax for type assertions in `tsx` files?
## --answers--
It is deprecated in TypeScript.
### --feedback--
Refer to the end of the lesson.
---
It only works with primitive types.
### --feedback--
Refer to the end of the lesson.
---
It automatically converts values at runtime.
### --feedback--
Refer to the end of the lesson.
---
TypeScript may interpret it as JSX syntax.
## --video-solution--
4
@@ -11,7 +11,15 @@
},
{
"id": "67d3018062fe6ba92b7d8299",
"title": "How Do the Different Types Work in TypeScript?"
"title": "How Do Primitive Types Work in TypeScript?"
},
{
"id": "698f53374f7ad3a078dafbc0",
"title": "How Do Types Work with Objects and Arrays?"
},
{
"id": "698f515e99f9a25a3462f97f",
"title": "How do Function Types Work?"
}
],
"helpCategory": "JavaScript"
@@ -0,0 +1,30 @@
{
"name": "Understanding Type Composition",
"blockLabel": "lecture",
"isUpcomingChange": true,
"dashedName": "lecture-understanding-type-composition",
"helpCategory": "JavaScript",
"blockLayout": "challenge-list",
"challengeOrder": [
{
"id": "698f5a372871b5dfb9e01698",
"title": "What are Union Types and How Do They Work?"
},
{
"id": "69945fc2cd3160713372860e",
"title": "What are Type Aliases and How Do They Work?"
},
{
"id": "698f5a3a492176259d1afcf9",
"title": "What are Interfaces and How Do They Work?"
},
{
"id": "698f4f9ff24a150af1b1fa06",
"title": "How Do The any, never, unknown and void Types Work?"
},
{
"id": "699460b8cd3160713372860f",
"title": "What are Type Assertions and How Do They Work?"
}
]
}
@@ -87,6 +87,7 @@
"comingSoon": true,
"blocks": [
"lecture-introduction-to-typescript",
"lecture-understanding-type-composition",
"lecture-working-with-generics-and-type-narrowing",
"lecture-working-with-typescript-configuration-files",
"review-typescript",