mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(curriculum): vidoes and transcripts for TS lecture (#59602)
This commit is contained in:
+42
-2
@@ -2,13 +2,53 @@
|
||||
id: 67d1d96532bc095aee051657
|
||||
title: What Is TypeScript, and Why Is It Used in the Industry?
|
||||
challengeType: 11
|
||||
videoId: nVAaxZ34khk
|
||||
videoId: Dce5dZUL2iI
|
||||
dashedName: what-is-typescript-and-why-is-it-used-in-the-industry
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Watch the video lecture and answer the questions below.
|
||||
Watch the video or read the transcript and answer the questions below.
|
||||
|
||||
# --transcript--
|
||||
|
||||
What is TypeScript, and why is it used in the industry?
|
||||
|
||||
Let's learn about TypeScript and why it is used.
|
||||
|
||||
JavaScript is considered a dynamically-typed language. This means that variables can receive any values at run time - declaring a variable as a number does not prevent you from assigning it a string value later, and function parameters can be passed any value.
|
||||
|
||||
The challenge of a dynamically-typed language is that the lack of type safety can introduce errors if you are not careful. TypeScript extends the JavaScript language to include static typing, which helps catch those errors before you even deploy your code. But how does it work?
|
||||
|
||||
Consider this JavaScript function:
|
||||
|
||||
```js
|
||||
const getRandomValue = (array) => {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
```
|
||||
|
||||
Our `getRandomValue` function takes an array and returns a random value from that array. But because JavaScript does not validate types, there is nothing preventing you from calling the function with a number:
|
||||
|
||||
```js
|
||||
console.log(getRandomValue(10));
|
||||
```
|
||||
|
||||
The console output for the current example will return `undefined` because it was expecting an array instead of a number.
|
||||
|
||||
But with TypeScript you can define a type for the array parameter:
|
||||
|
||||
```js
|
||||
const getRandomValue = (array: string[]) => {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
```
|
||||
|
||||
This type definition tells TypeScript that the array argument must be an array of strings. Then when you call `getRandomValue` and pass it a number, you get a different type of error called a compiler error.
|
||||
|
||||
Most JavaScript runtime environments, such as a browser or Node.js, cannot run TypeScript natively. Instead, you first need to compile, or translate, TypeScript code into regular JavaScript. You can do that with the built-in compiler that comes with the TypeScript language. When you run the compiler, TypeScript will evaluate your code and throw an error for any issues where the types don't match - such as passing a number into a function that expects an array.
|
||||
|
||||
You'll learn more about how these types work in the next few lectures. But it's this extra safety that makes TypeScript an appealing language for many developers and organizations.
|
||||
|
||||
# --questions--
|
||||
|
||||
|
||||
+116
-2
@@ -2,13 +2,127 @@
|
||||
id: 67d3018062fe6ba92b7d8299
|
||||
title: How Do the Different Types Work in TypeScript?
|
||||
challengeType: 11
|
||||
videoId: nVAaxZ34khk
|
||||
videoId: UxmvUfPRqfg
|
||||
dashedName: how-do-the-different-types-work-in-typescript
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Watch the lecture video and answer the questions below.
|
||||
Watch the video or read the transcript and answer the questions below.
|
||||
|
||||
# --transcript--
|
||||
|
||||
How do the different types work in TypeScript?
|
||||
|
||||
Let's learn about the different types in TypeScript.
|
||||
|
||||
You've already seen one in the previous lecture: `string[]`, which represents an array of strings. But how does that actually work?
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
let str: string = "Naomi";
|
||||
let num: number = 42;
|
||||
let bool: boolean = true;
|
||||
let nope: null = null;
|
||||
let nada: undefined = undefined;
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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"];
|
||||
```
|
||||
|
||||
Fundamentally, these two syntaxes are the same, and the choice between the two is generally a matter of preference.
|
||||
|
||||
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 };
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
const objOne: Record<string, string> = {};
|
||||
const objTwo: { [key: string]: string } = {};
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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 lecture.
|
||||
|
||||
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;
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
type stringOrNumber = string | number;
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
const stuff: stringOrNumber[] = ["a", 2, "c", 1000];
|
||||
```
|
||||
|
||||
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 lecture, 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.
|
||||
|
||||
# --questions--
|
||||
|
||||
|
||||
+64
-2
@@ -2,13 +2,75 @@
|
||||
id: 67d30191adb999a9909109ef
|
||||
title: What Are Generics, and How Do They Work?
|
||||
challengeType: 11
|
||||
videoId: nVAaxZ34khk
|
||||
videoId: dBUOmjcR3N4
|
||||
dashedName: what-are-generics-and-how-do-they-work
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Watch the lecture video and answer the questions below.
|
||||
Watch the video or read the transcript and answer the questions below.
|
||||
|
||||
# --transcript--
|
||||
|
||||
What are generics, and how do they work?
|
||||
|
||||
Let's learn about generic types in TypeScript.
|
||||
|
||||
You have actually seen a generic type in a previous lecture: `Array<string>`. But you'll usually use generic types in functions. In fact, they can be thought of as a special parameter you provide to a function to control the behavior of the function's type definition.
|
||||
|
||||
Let's go back to our previous example of a function to get a random value from an array:
|
||||
|
||||
```js
|
||||
const getRandomValue = (array: string[]): string => {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
```
|
||||
|
||||
Our function accepts an array of strings and returns a string. But what if we wanted to pass in an array of numbers and return a number?
|
||||
|
||||
You could use a union type:
|
||||
|
||||
```js
|
||||
const getRandomValue = (array: (string|number)[]): (string|number) => {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
const val = getRandomValue([1, 2, 4])
|
||||
```
|
||||
|
||||
But with this approach, even though we have passed in an array of only numbers, TypeScript determines that the `val` variable could be a number OR a string.
|
||||
|
||||
Instead, you can define a generic type for the function:
|
||||
|
||||
```js
|
||||
const getRandomValue = <T>(array: T[]): T => {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
const val = getRandomValue([1, 2, 4])
|
||||
```
|
||||
|
||||
There are a couple of important components in our new example. First, the `<T>` syntax tells TypeScript that you are defining a generic type `T` for the function. `T` is a common name for generic types, but you can actually name it whatever you want (within JavaScript's variable conventions).
|
||||
|
||||
Then, we tell TypeScript that the array parameter is an array of values matching the generic type, and that the returned value is a single element of that same type. This allows TypeScript to properly detect that `val` will be a number, because the array we passed consists solely of numbers.
|
||||
|
||||
But what about functions you don't control? Let's take a look at the `document.querySelector()` method:
|
||||
|
||||
```js
|
||||
const email = document.querySelector("#email");
|
||||
console.log(email.value);
|
||||
```
|
||||
|
||||
Because we're querying with an ID selector, TypeScript doesn't know what kind of element we are querying. It knows it's an `Element` of some sort, but our attempt to access the `value` property will throw an error because the top-level element interface does not have that property.
|
||||
|
||||
Thankfully, we can pass a type argument to the function call:
|
||||
|
||||
```js
|
||||
const email = document.querySelector<HTMLInputElement>("#email");
|
||||
console.log(email.value);
|
||||
```
|
||||
|
||||
Notice how we're using the angle bracket syntax again (`<HTMLInputElement>`), but this time in a function call. This tells TypeScript that the element we expect to find will be an `input` element, and we no longer get an error on the value property because input elements do have that property. Of course, `querySelector` can return `null` if an element is not found, and TypeScript will throw a compiler error here because we do not handle that case.
|
||||
|
||||
But you'll learn about that in the next lecture.
|
||||
|
||||
# --questions--
|
||||
|
||||
|
||||
+162
-2
@@ -2,13 +2,173 @@
|
||||
id: 67d301a5f536d1a9e5df1a8c
|
||||
title: What Is Type Narrowing, and How Does It Work?
|
||||
challengeType: 11
|
||||
videoId: nVAaxZ34khk
|
||||
videoId: hTY1_Mco_WA
|
||||
dashedName: what-is-type-narrowing-and-how-does-it-work
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Watch the lecture video and answer the questions below.
|
||||
Watch the video or read the transcript and answer the questions below.
|
||||
|
||||
# --transcript--
|
||||
|
||||
What is type narrowing, and how does it work?
|
||||
|
||||
Let's learn about type narrowing in TypeScript.
|
||||
|
||||
There are going to be times where you have a value with a broad type and you need to narrow it down to a more specific type. For example, maybe you need to ensure an object matches an interface you defined. Or a string is within a specific list of values. There are quite a few ways to achieve this.
|
||||
|
||||
The first is narrowing by truthiness. Consider our example from the last lecture:
|
||||
|
||||
```js
|
||||
const email = document.querySelector<HTMLInputElement>("#email");
|
||||
console.log(email.value);
|
||||
```
|
||||
|
||||
We get a compiler error trying to access the `value` property of `email`, because `email` might be `null`. However, we can use a conditional statement to confirm `email` is truthy before accessing the property:
|
||||
|
||||
```js
|
||||
const email = document.querySelector<HTMLInputElement>("#email");
|
||||
if (email) {
|
||||
console.log(email.value);
|
||||
}
|
||||
```
|
||||
|
||||
In this updated example, because `null` is not a truthy value, TypeScript is able to infer that `email` MUST be an `input` element within the conditional block. So it no longer throws a compiler error.
|
||||
|
||||
Truthiness checks can also work in the reverse direction:
|
||||
|
||||
```js
|
||||
const email = document.querySelector<HTMLInputElement>("#email");
|
||||
if (!email) {
|
||||
throw new ReferenceError("Could not find email element!")
|
||||
}
|
||||
console.log(email.value);
|
||||
```
|
||||
|
||||
With this approach, we throw an error if `email` is falsy. `null` is a falsy value. Throwing an error ends the logical execution of this code, which means when we reach the `console.log()` call TypeScript knows `email` cannot be `null`.
|
||||
|
||||
Optional chaining is also a form of type narrowing, under the same premise that the property access can't happen if the `email` value is `null`.
|
||||
|
||||
```js
|
||||
const email = document.querySelector<HTMLInputElement>("#email");
|
||||
console.log(email?.value);
|
||||
```
|
||||
|
||||
But what about other types? Well, you can also narrow types using the `typeof` operator. Let's look at an example of a variable we have indicated might be a string OR a number:
|
||||
|
||||
```js
|
||||
const myVal = Math.random() > 0.5 ? 222 : "222";
|
||||
console.log(myVal / 10)
|
||||
```
|
||||
|
||||
In this example, we see a compiler error because we cannot perform arithmetic on a string value. But we can use a conditional to check the `typeof` the `myVal` variable:
|
||||
|
||||
```js
|
||||
const myVal = Math.random() > 0.5 ? 222 : "222";
|
||||
if (typeof myVal === "number") {
|
||||
console.log(myVal / 10);
|
||||
}
|
||||
```
|
||||
|
||||
Because we have used the `typeof` keyword, TypeScript now knows that `myVal` has to be a number, and we can safely perform arithmetic with it.
|
||||
|
||||
But what about more complex object types? If the object in question happens to come from a class, you can actually use the `instanceof` keyword to narrow the type. Going back to our `querySelector()` example:
|
||||
|
||||
```js
|
||||
const email = document.querySelector("#email");
|
||||
```
|
||||
|
||||
Rather than passing a generic type and telling TypeScript what the element is, we can use `instanceof` to narrow the type and write safer code:
|
||||
|
||||
```js
|
||||
const email = document.querySelector("#email");
|
||||
|
||||
if (email instanceof HTMLInputElement) {
|
||||
console.log(email.value);
|
||||
}
|
||||
```
|
||||
|
||||
This approach may seem the same as our previous one, but `instanceof` is a runtime validation - which means, if we somehow got the TypeScript type wrong, our JavaScript code will still confirm that `email` is an `input` element.
|
||||
|
||||
Next, let's look at an example where we fetch a `User` object from an API and try to print the information:
|
||||
|
||||
```js
|
||||
interface User {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
const printAge = (user: User) =>
|
||||
console.log(`${user.name} is ${user.age} years old!`)
|
||||
|
||||
const request = await fetch("url")
|
||||
const myUser = await request.json();
|
||||
printAge(myUser);
|
||||
```
|
||||
|
||||
We'll get a compiler error trying to pass `myUser` into the function because, even though we know the API returns the correct object, TypeScript does not. And the `.json()` method does not take a generic type.
|
||||
|
||||
The "easy" way to resolve this issue would be to cast the type:
|
||||
|
||||
```js
|
||||
interface User {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
const printAge = (user: User) =>
|
||||
console.log(`${user.name} is ${user.age} years old!`)
|
||||
|
||||
const request = await fetch("url")
|
||||
const myUser = await request.json() as User;
|
||||
printAge(myUser);
|
||||
```
|
||||
|
||||
But whenever you cast the type, you're essentially weakening TypeScript's ability to catch potential errors. So instead of casting the type, you can write a type guard:
|
||||
|
||||
```js
|
||||
interface User {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
const isValidUser = (user: unknown): user is User => {
|
||||
return !!user &&
|
||||
typeof user === "object" &&
|
||||
"name" in user &&
|
||||
"age" in user;
|
||||
}
|
||||
```
|
||||
|
||||
The return type here is the key component of this function definition. The `user is User` syntax indicates that our function returns a boolean value, which when `true` means the `user` value satisfies the `User` interface. We then do some basic checks to ensure that the structure of the `user` object matches - note the use of a truthiness (`!!user`) narrowing and a `typeof` narrowing. We must do this because `typeof null` returns `"object"`:
|
||||
|
||||
```js
|
||||
interface User {
|
||||
name: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
const isValidUser = (user: unknown): user is User => {
|
||||
return !!user &&
|
||||
typeof user === "object" &&
|
||||
"name" in user &&
|
||||
"age" in user;
|
||||
}
|
||||
|
||||
const printAge = (user: User) =>
|
||||
console.log(`${user.name} is ${user.age} years old!`)
|
||||
|
||||
const request = await fetch("url")
|
||||
const myUser = await request.json() as User;
|
||||
if (isValidUser(myUser)) {
|
||||
printAge(myUser);
|
||||
}
|
||||
```
|
||||
|
||||
Now, if we combine all of our logic together, we no longer get compiler errors and can successfully build our code.
|
||||
|
||||
Type narrowing is a powerful feature that helps you write safer, less error-prone code - but remember that TypeScript types are not completely rigid, so avoid practices like casting the type of a value without narrowing it.
|
||||
|
||||
# --questions--
|
||||
|
||||
|
||||
+43
-2
@@ -2,13 +2,54 @@
|
||||
id: 67d301cc87b84eaa42bdcdbe
|
||||
title: What Is a tsconfig File, and Why Is It Important to Include in Your TypeScript Projects?
|
||||
challengeType: 11
|
||||
videoId: nVAaxZ34khk
|
||||
videoId: H-n6N7zmNCg
|
||||
dashedName: what-is-a-tsconfig-file-and-why-is-it-important-to-include-in-your-typescript-projects
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Watch the lecture video and answer the questions below.
|
||||
Watch the video or read the transcript and answer the questions below.
|
||||
|
||||
# --transcript--
|
||||
|
||||
What is a tsconfig file, and why is it important to include in your TypeScript projects?
|
||||
|
||||
Let's learn about the tsconfig file!
|
||||
|
||||
TypeScript's compiler settings can be configured to meet your project's needs. That configuration lives in a `tsconfig.json` file in the root directory of your project. In fact, without it, the compiler will not run unless you pass it command flags directly. But what exactly does this file do? Well, let's take a look at an example file:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./prod",
|
||||
"lib": ["ES2023"],
|
||||
"target": "ES2023",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Node",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true
|
||||
},
|
||||
"exclude": ["test/"]
|
||||
}
|
||||
```
|
||||
|
||||
This seems like a lot! So let's break it down. The `compilerOptions` property is going to contain the "meat" of your configuration - this is where you control how the TypeScript compiler behaves. Looking at that nested object…
|
||||
|
||||
The `rootDir` and `outDir` tell TypeScript which directory holds your source files, and which directory should contain the transpiled JavaScript code.
|
||||
|
||||
The `lib` property determines which type definitions the compiler uses, and allows you to include support for specific ES releases, the DOM, and more.
|
||||
|
||||
`module` and `moduleResolution` effectively work in tandem to manage how your package uses modules - either CommonJS or ECMAScript.
|
||||
|
||||
`esModuleInterop` allows for smoother interoperability between CommonJS and ES modules by automatically creating namespace objects for imports, making it easier to use modules from different systems together in your TypeScript projects, and the `skipLibCheck` option skips validating `.d.ts` files that aren't referenced by imports in your code.
|
||||
|
||||
And finally we reach the `strict` mode. One might argue that TypeScript isn't truly helpful without this flag enabled, as it toggles quite a few other checks, such as requiring you to properly handle nullable types, or warn when TypeScript can't infer a type and falls back to any.
|
||||
|
||||
Before we finish, a quick note about the top-level `exclude` property - when you've defined a source directory, you may have TypeScript code outside of that directory which you don't want compiled as part of your production code. For example, your test code. The `exclude` array tells the compiler to ignore these TypeScript files during compilation, but still allows tooling like Intellisense to expose potential issues.
|
||||
|
||||
There are a ton of other compiler options you can explore - over 50! I encourage you to explore the documentation and experiment to find the configuration that works for your project's needs.
|
||||
|
||||
# --questions--
|
||||
|
||||
|
||||
Reference in New Issue
Block a user