From a7f210d21f13a4088796812f698bae86e667a8fa Mon Sep 17 00:00:00 2001 From: bumkeyy Date: Sun, 26 Dec 2021 18:30:15 +0900 Subject: [PATCH 1/4] add initial narrowing.md --- .../documentation/ko/handbook-v2/Narrowing.md | 769 ++++++++++++++++++ 1 file changed, 769 insertions(+) create mode 100644 docs/documentation/ko/handbook-v2/Narrowing.md diff --git a/docs/documentation/ko/handbook-v2/Narrowing.md b/docs/documentation/ko/handbook-v2/Narrowing.md new file mode 100644 index 00000000..51a063d5 --- /dev/null +++ b/docs/documentation/ko/handbook-v2/Narrowing.md @@ -0,0 +1,769 @@ +--- +title: Narrowing +layout: docs +permalink: /docs/handbook/2/narrowing.html +oneline: "Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects." +--- + +Imagine we have a function called `padLeft`. + +```ts twoslash +function padLeft(padding: number | string, input: string): string { + throw new Error("Not implemented yet!"); +} +``` + +If `padding` is a `number`, it will treat that as the number of spaces we want to prepend to `input`. +If `padding` is a `string`, it should just prepend `padding` to `input`. +Let's try to implement the logic for when `padLeft` is passed a `number` for `padding`. + +```ts twoslash +// @errors: 2345 +function padLeft(padding: number | string, input: string) { + return " ".repeat(padding) + input; +} +``` + +Uh-oh, we're getting an error on `padding`. +TypeScript is warning us that adding a `number` to a `number | string` might not give us what we want, and it's right. +In other words, we haven't explicitly checked if `padding` is a `number` first, nor are we handling the case where it's a `string`, so let's do exactly that. + +```ts twoslash +function padLeft(padding: number | string, input: string) { + if (typeof padding === "number") { + return " ".repeat(padding) + input; + } + return padding + input; +} +``` + +If this mostly looks like uninteresting JavaScript code, that's sort of the point. +Apart from the annotations we put in place, this TypeScript code looks like JavaScript. +The idea is that TypeScript's type system aims to make it as easy as possible to write typical JavaScript code without bending over backwards to get type safety. + +While it might not look like much, there's actually a lot going under the covers here. +Much like how TypeScript analyzes runtime values using static types, it overlays type analysis on JavaScript's runtime control flow constructs like `if/else`, conditional ternaries, loops, truthiness checks, etc., which can all affect those types. + +Within our `if` check, TypeScript sees `typeof padding === "number"` and understands that as a special form of code called a _type guard_. +TypeScript follows possible paths of execution that our programs can take to analyze the most specific possible type of a value at a given position. +It looks at these special checks (called _type guards_) and assignments, and the process of refining types to more specific types than declared is called _narrowing_. +In many editors we can observe these types as they change, and we'll even do so in our examples. + +```ts twoslash +function padLeft(padding: number | string, input: string) { + if (typeof padding === "number") { + return " ".repeat(padding) + input; + // ^? + } + return padding + input; + // ^? +} +``` + +There are a couple of different constructs TypeScript understands for narrowing. + +## `typeof` type guards + +As we've seen, JavaScript supports a `typeof` operator which can give very basic information about the type of values we have at runtime. +TypeScript expects this to return a certain set of strings: + +- `"string"` +- `"number"` +- `"bigint"` +- `"boolean"` +- `"symbol"` +- `"undefined"` +- `"object"` +- `"function"` + +Like we saw with `padLeft`, this operator comes up pretty often in a number of JavaScript libraries, and TypeScript can understand it to narrow types in different branches. + +In TypeScript, checking against the value returned by `typeof` is a type guard. +Because TypeScript encodes how `typeof` operates on different values, it knows about some of its quirks in JavaScript. +For example, notice that in the list above, `typeof` doesn't return the string `null`. +Check out the following example: + +```ts twoslash +// @errors: 2531 +function printAll(strs: string | string[] | null) { + if (typeof strs === "object") { + for (const s of strs) { + console.log(s); + } + } else if (typeof strs === "string") { + console.log(strs); + } else { + // do nothing + } +} +``` + +In the `printAll` function, we try to check if `strs` is an object to see if it's an array type (now might be a good time to reinforce that arrays are object types in JavaScript). +But it turns out that in JavaScript, `typeof null` is actually `"object"`! +This is one of those unfortunate accidents of history. + +Users with enough experience might not be surprised, but not everyone has run into this in JavaScript; luckily, TypeScript lets us know that `strs` was only narrowed down to `string[] | null` instead of just `string[]`. + +This might be a good segue into what we'll call "truthiness" checking. + +# Truthiness narrowing + +Truthiness might not be a word you'll find in the dictionary, but it's very much something you'll hear about in JavaScript. + +In JavaScript, we can use any expression in conditionals, `&&`s, `||`s, `if` statements, Boolean negations (`!`), and more. +As an example, `if` statements don't expect their condition to always have the type `boolean`. + +```ts twoslash +function getUsersOnlineMessage(numUsersOnline: number) { + if (numUsersOnline) { + return `There are ${numUsersOnline} online now!`; + } + return "Nobody's here. :("; +} +``` + +In JavaScript, constructs like `if` first "coerce" their conditions to `boolean`s to make sense of them, and then choose their branches depending on whether the result is `true` or `false`. +Values like + +- `0` +- `NaN` +- `""` (the empty string) +- `0n` (the `bigint` version of zero) +- `null` +- `undefined` + +all coerce to `false`, and other values get coerced `true`. +You can always coerce values to `boolean`s by running them through the `Boolean` function, or by using the shorter double-Boolean negation. (The latter has the advantage that TypeScript infers a narrow literal boolean type `true`, while inferring the first as type `boolean`.) + +```ts twoslash +// both of these result in 'true' +Boolean("hello"); // type: boolean, value: true +!!"world"; // type: true, value: true +``` + +It's fairly popular to leverage this behavior, especially for guarding against values like `null` or `undefined`. +As an example, let's try using it for our `printAll` function. + +```ts twoslash +function printAll(strs: string | string[] | null) { + if (strs && typeof strs === "object") { + for (const s of strs) { + console.log(s); + } + } else if (typeof strs === "string") { + console.log(strs); + } +} +``` + +You'll notice that we've gotten rid of the error above by checking if `strs` is truthy. +This at least prevents us from dreaded errors when we run our code like: + +```txt +TypeError: null is not iterable +``` + +Keep in mind though that truthiness checking on primitives can often be error prone. +As an example, consider a different attempt at writing `printAll` + +```ts twoslash {class: "do-not-do-this"} +function printAll(strs: string | string[] | null) { + // !!!!!!!!!!!!!!!! + // DON'T DO THIS! + // KEEP READING + // !!!!!!!!!!!!!!!! + if (strs) { + if (typeof strs === "object") { + for (const s of strs) { + console.log(s); + } + } else if (typeof strs === "string") { + console.log(strs); + } + } +} +``` + +We wrapped the entire body of the function in a truthy check, but this has a subtle downside: we may no longer be handling the empty string case correctly. + +TypeScript doesn't hurt us here at all, but this is behavior worth noting if you're less familiar with JavaScript. +TypeScript can often help you catch bugs early on, but if you choose to do _nothing_ with a value, there's only so much that it can do without being overly prescriptive. +If you want, you can make sure you handle situations like these with a linter. + +One last word on narrowing by truthiness is that Boolean negations with `!` filter out from negated branches. + +```ts twoslash +function multiplyAll( + values: number[] | undefined, + factor: number +): number[] | undefined { + if (!values) { + return values; + } else { + return values.map((x) => x * factor); + } +} +``` + +## Equality narrowing + +TypeScript also uses `switch` statements and equality checks like `===`, `!==`, `==`, and `!=` to narrow types. +For example: + +```ts twoslash +function example(x: string | number, y: string | boolean) { + if (x === y) { + // We can now call any 'string' method on 'x' or 'y'. + x.toUpperCase(); + // ^? + y.toLowerCase(); + // ^? + } else { + console.log(x); + // ^? + console.log(y); + // ^? + } +} +``` + +When we checked that `x` and `y` are both equal in the above example, TypeScript knew their types also had to be equal. +Since `string` is the only common type that both `x` and `y` could take on, TypeScript knows that `x` and `y` must be a `string` in the first branch. + +Checking against specific literal values (as opposed to variables) works also. +In our section about truthiness narrowing, we wrote a `printAll` function which was error-prone because it accidentally didn't handle empty strings properly. +Instead we could have done a specific check to block out `null`s, and TypeScript still correctly removes `null` from the type of `strs`. + +```ts twoslash +function printAll(strs: string | string[] | null) { + if (strs !== null) { + if (typeof strs === "object") { + for (const s of strs) { + // ^? + console.log(s); + } + } else if (typeof strs === "string") { + console.log(strs); + // ^? + } + } +} +``` + +JavaScript's looser equality checks with `==` and `!=` also get narrowed correctly. +If you're unfamiliar, checking whether something `== null` actually not only checks whether it is specifically the value `null` - it also checks whether it's potentially `undefined`. +The same applies to `== undefined`: it checks whether a value is either `null` or `undefined`. + +```ts twoslash +interface Container { + value: number | null | undefined; +} + +function multiplyValue(container: Container, factor: number) { + // Remove both 'null' and 'undefined' from the type. + if (container.value != null) { + console.log(container.value); + // ^? + + // Now we can safely multiply 'container.value'. + container.value *= factor; + } +} +``` + +## The `in` operator narrowing + +JavaScript has an operator for determining if an object has a property with a name: the `in` operator. +TypeScript takes this into account as a way to narrow down potential types. + +For example, with the code: `"value" in x`. where `"value"` is a string literal and `x` is a union type. +The "true" branch narrows `x`'s types which have either an optional or required property `value`, and the "false" branch narrows to types which have an optional or missing property `value`. + +```ts twoslash +type Fish = { swim: () => void }; +type Bird = { fly: () => void }; + +function move(animal: Fish | Bird) { + if ("swim" in animal) { + return animal.swim(); + } + + return animal.fly(); +} +``` + +To reiterate optional properties will exist in both sides for narrowing, for example a human could both swim and fly (with the right equipment) and thus should show up in both sides of the `in` check: + + +```ts twoslash +type Fish = { swim: () => void }; +type Bird = { fly: () => void }; +type Human = { swim?: () => void; fly?: () => void }; + +function move(animal: Fish | Bird | Human) { + if ("swim" in animal) { + animal; +// ^? + } else { + animal; +// ^? + } +} +``` + +## `instanceof` narrowing + +JavaScript has an operator for checking whether or not a value is an "instance" of another value. +More specifically, in JavaScript `x instanceof Foo` checks whether the _prototype chain_ of `x` contains `Foo.prototype`. +While we won't dive deep here, and you'll see more of this when we get into classes, they can still be useful for most values that can be constructed with `new`. +As you might have guessed, `instanceof` is also a type guard, and TypeScript narrows in branches guarded by `instanceof`s. + +```ts twoslash +function logValue(x: Date | string) { + if (x instanceof Date) { + console.log(x.toUTCString()); + // ^? + } else { + console.log(x.toUpperCase()); + // ^? + } +} +``` + +## Assignments + +As we mentioned earlier, when we assign to any variable, TypeScript looks at the right side of the assignment and narrows the left side appropriately. + +```ts twoslash +let x = Math.random() < 0.5 ? 10 : "hello world!"; +// ^? +x = 1; + +console.log(x); +// ^? +x = "goodbye!"; + +console.log(x); +// ^? +``` + +Notice that each of these assignments is valid. +Even though the observed type of `x` changed to `number` after our first assignment, we were still able to assign a `string` to `x`. +This is because the _declared type_ of `x` - the type that `x` started with - is `string | number`, and assignability is always checked against the declared type. + +If we'd assigned a `boolean` to `x`, we'd have seen an error since that wasn't part of the declared type. + +```ts twoslash +// @errors: 2322 +let x = Math.random() < 0.5 ? 10 : "hello world!"; +// ^? +x = 1; + +console.log(x); +// ^? +x = true; + +console.log(x); +// ^? +``` + +## Control flow analysis + +Up until this point, we've gone through some basic examples of how TypeScript narrows within specific branches. +But there's a bit more going on than just walking up from every variable and looking for type guards in `if`s, `while`s, conditionals, etc. +For example + +```ts twoslash +function padLeft(padding: number | string, input: string) { + if (typeof padding === "number") { + return " ".repeat(padding) + input; + } + return padding + input; +} +``` + +`padLeft` returns from within its first `if` block. +TypeScript was able to analyze this code and see that the rest of the body (`return padding + input;`) is _unreachable_ in the case where `padding` is a `number`. +As a result, it was able to remove `number` from the type of `padding` (narrowing from `string | number` to `string`) for the rest of the function. + +This analysis of code based on reachability is called _control flow analysis_, and TypeScript uses this flow analysis to narrow types as it encounters type guards and assignments. +When a variable is analyzed, control flow can split off and re-merge over and over again, and that variable can be observed to have a different type at each point. + +```ts twoslash +function example() { + let x: string | number | boolean; + + x = Math.random() < 0.5; + + console.log(x); + // ^? + + if (Math.random() < 0.5) { + x = "hello"; + console.log(x); + // ^? + } else { + x = 100; + console.log(x); + // ^? + } + + return x; + // ^? +} +``` + +## Using type predicates + +We've worked with existing JavaScript constructs to handle narrowing so far, however sometimes you want more direct control over how types change throughout your code. + +To define a user-defined type guard, we simply need to define a function whose return type is a _type predicate_: + +```ts twoslash +type Fish = { swim: () => void }; +type Bird = { fly: () => void }; +declare function getSmallPet(): Fish | Bird; +// ---cut--- +function isFish(pet: Fish | Bird): pet is Fish { + return (pet as Fish).swim !== undefined; +} +``` + +`pet is Fish` is our type predicate in this example. +A predicate takes the form `parameterName is Type`, where `parameterName` must be the name of a parameter from the current function signature. + +Any time `isFish` is called with some variable, TypeScript will _narrow_ that variable to that specific type if the original type is compatible. + +```ts twoslash +type Fish = { swim: () => void }; +type Bird = { fly: () => void }; +declare function getSmallPet(): Fish | Bird; +function isFish(pet: Fish | Bird): pet is Fish { + return (pet as Fish).swim !== undefined; +} +// ---cut--- +// Both calls to 'swim' and 'fly' are now okay. +let pet = getSmallPet(); + +if (isFish(pet)) { + pet.swim(); +} else { + pet.fly(); +} +``` + +Notice that TypeScript not only knows that `pet` is a `Fish` in the `if` branch; +it also knows that in the `else` branch, you _don't_ have a `Fish`, so you must have a `Bird`. + +You may use the type guard `isFish` to filter an array of `Fish | Bird` and obtain an array of `Fish`: + +```ts twoslash +type Fish = { swim: () => void; name: string }; +type Bird = { fly: () => void; name: string }; +declare function getSmallPet(): Fish | Bird; +function isFish(pet: Fish | Bird): pet is Fish { + return (pet as Fish).swim !== undefined; +} +// ---cut--- +const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()]; +const underWater1: Fish[] = zoo.filter(isFish); +// or, equivalently +const underWater2: Fish[] = zoo.filter(isFish) as Fish[]; + +// The predicate may need repeating for more complex examples +const underWater3: Fish[] = zoo.filter((pet): pet is Fish => { + if (pet.name === "sharkey") return false; + return isFish(pet); +}); +``` + +In addition, classes can [use `this is Type`](/docs/handbook/2/classes.html#this-based-type-guards) to narrow their type. + +# Discriminated unions + +Most of the examples we've looked at so far have focused around narrowing single variables with simple types like `string`, `boolean`, and `number`. +While this is common, most of the time in JavaScript we'll be dealing with slightly more complex structures. + +For some motivation, let's imagine we're trying to encode shapes like circles and squares. +Circles keep track of their radiuses and squares keep track of their side lengths. +We'll use a field called `kind` to tell which shape we're dealing with. +Here's a first attempt at defining `Shape`. + +```ts twoslash +interface Shape { + kind: "circle" | "square"; + radius?: number; + sideLength?: number; +} +``` + +Notice we're using a union of string literal types: `"circle"` and `"square"` to tell us whether we should treat the shape as a circle or square respectively. +By using `"circle" | "square"` instead of `string`, we can avoid misspelling issues. + +```ts twoslash +// @errors: 2367 +interface Shape { + kind: "circle" | "square"; + radius?: number; + sideLength?: number; +} + +// ---cut--- +function handleShape(shape: Shape) { + // oops! + if (shape.kind === "rect") { + // ... + } +} +``` + +We can write a `getArea` function that applies the right logic based on if it's dealing with a circle or square. +We'll first try dealing with circles. + +```ts twoslash +// @errors: 2532 +interface Shape { + kind: "circle" | "square"; + radius?: number; + sideLength?: number; +} + +// ---cut--- +function getArea(shape: Shape) { + return Math.PI * shape.radius ** 2; +} +``` + + + +Under [`strictNullChecks`](/tsconfig#strictNullChecks) that gives us an error - which is appropriate since `radius` might not be defined. +But what if we perform the appropriate checks on the `kind` property? + +```ts twoslash +// @errors: 2532 +interface Shape { + kind: "circle" | "square"; + radius?: number; + sideLength?: number; +} + +// ---cut--- +function getArea(shape: Shape) { + if (shape.kind === "circle") { + return Math.PI * shape.radius ** 2; + } +} +``` + +Hmm, TypeScript still doesn't know what to do here. +We've hit a point where we know more about our values than the type checker does. +We could try to use a non-null assertion (a `!` after `shape.radius`) to say that `radius` is definitely present. + +```ts twoslash +interface Shape { + kind: "circle" | "square"; + radius?: number; + sideLength?: number; +} + +// ---cut--- +function getArea(shape: Shape) { + if (shape.kind === "circle") { + return Math.PI * shape.radius! ** 2; + } +} +``` + +But this doesn't feel ideal. +We had to shout a bit at the type-checker with those non-null assertions (`!`) to convince it that `shape.radius` was defined, but those assertions are error-prone if we start to move code around. +Additionally, outside of [`strictNullChecks`](/tsconfig#strictNullChecks) we're able to accidentally access any of those fields anyway (since optional properties are just assumed to always be present when reading them). +We can definitely do better. + +The problem with this encoding of `Shape` is that the type-checker doesn't have any way to know whether or not `radius` or `sideLength` are present based on the `kind` property. +We need to communicate what _we_ know to the type checker. +With that in mind, let's take another swing at defining `Shape`. + +```ts twoslash +interface Circle { + kind: "circle"; + radius: number; +} + +interface Square { + kind: "square"; + sideLength: number; +} + +type Shape = Circle | Square; +``` + +Here, we've properly separated `Shape` out into two types with different values for the `kind` property, but `radius` and `sideLength` are declared as required properties in their respective types. + +Let's see what happens here when we try to access the `radius` of a `Shape`. + +```ts twoslash +// @errors: 2339 +interface Circle { + kind: "circle"; + radius: number; +} + +interface Square { + kind: "square"; + sideLength: number; +} + +type Shape = Circle | Square; + +// ---cut--- +function getArea(shape: Shape) { + return Math.PI * shape.radius ** 2; +} +``` + +Like with our first definition of `Shape`, this is still an error. +When `radius` was optional, we got an error (only in [`strictNullChecks`](/tsconfig#strictNullChecks)) because TypeScript couldn't tell whether the property was present. +Now that `Shape` is a union, TypeScript is telling us that `shape` might be a `Square`, and `Square`s don't have `radius` defined on them! +Both interpretations are correct, but only does our new encoding of `Shape` still cause an error outside of [`strictNullChecks`](/tsconfig#strictNullChecks). + +But what if we tried checking the `kind` property again? + +```ts twoslash +interface Circle { + kind: "circle"; + radius: number; +} + +interface Square { + kind: "square"; + sideLength: number; +} + +type Shape = Circle | Square; + +// ---cut--- +function getArea(shape: Shape) { + if (shape.kind === "circle") { + return Math.PI * shape.radius ** 2; + // ^? + } +} +``` + +That got rid of the error! +When every type in a union contains a common property with literal types, TypeScript considers that to be a _discriminated union_, and can narrow out the members of the union. + +In this case, `kind` was that common property (which is what's considered a _discriminant_ property of `Shape`). +Checking whether the `kind` property was `"circle"` got rid of every type in `Shape` that didn't have a `kind` property with the type `"circle"`. +That narrowed `shape` down to the type `Circle`. + +The same checking works with `switch` statements as well. +Now we can try to write our complete `getArea` without any pesky `!` non-null assertions. + +```ts twoslash +interface Circle { + kind: "circle"; + radius: number; +} + +interface Square { + kind: "square"; + sideLength: number; +} + +type Shape = Circle | Square; + +// ---cut--- +function getArea(shape: Shape) { + switch (shape.kind) { + case "circle": + return Math.PI * shape.radius ** 2; + // ^? + case "square": + return shape.sideLength ** 2; + // ^? + } +} +``` + +The important thing here was the encoding of `Shape`. +Communicating the right information to TypeScript - that `Circle` and `Square` were really two separate types with specific `kind` fields - was crucial. +Doing that let us write type-safe TypeScript code that looks no different than the JavaScript we would've written otherwise. +From there, the type system was able to do the "right" thing and figure out the types in each branch of our `switch` statement. + +> As an aside, try playing around with the above example and remove some of the return keywords. +> You'll see that type-checking can help avoid bugs when accidentally falling through different clauses in a `switch` statement. + +Discriminated unions are useful for more than just talking about circles and squares. +They're good for representing any sort of messaging scheme in JavaScript, like when sending messages over the network (client/server communication), or encoding mutations in a state management framework. + +# The `never` type + +When narrowing, you can reduce the options of a union to a point where you have removed all possibilities and have nothing left. +In those cases, TypeScript will use a `never` type to represent a state which shouldn't exist. + +# Exhaustiveness checking + +The `never` type is assignable to every type; however, no type is assignable to `never` (except `never` itself). This means you can use narrowing and rely on `never` turning up to do exhaustive checking in a switch statement. + +For example, adding a `default` to our `getArea` function which tries to assign the shape to `never` will raise when every possible case has not been handled. + +```ts twoslash +interface Circle { + kind: "circle"; + radius: number; +} + +interface Square { + kind: "square"; + sideLength: number; +} +// ---cut--- +type Shape = Circle | Square; + +function getArea(shape: Shape) { + switch (shape.kind) { + case "circle": + return Math.PI * shape.radius ** 2; + case "square": + return shape.sideLength ** 2; + default: + const _exhaustiveCheck: never = shape; + return _exhaustiveCheck; + } +} +``` + +Adding a new member to the `Shape` union, will cause a TypeScript error: + +```ts twoslash +// @errors: 2322 +interface Circle { + kind: "circle"; + radius: number; +} + +interface Square { + kind: "square"; + sideLength: number; +} +// ---cut--- +interface Triangle { + kind: "triangle"; + sideLength: number; +} + +type Shape = Circle | Square | Triangle; + +function getArea(shape: Shape) { + switch (shape.kind) { + case "circle": + return Math.PI * shape.radius ** 2; + case "square": + return shape.sideLength ** 2; + default: + const _exhaustiveCheck: never = shape; + return _exhaustiveCheck; + } +} +``` From b6c3a6d5a5e18491f7382c0aaa706aae3c79e355 Mon Sep 17 00:00:00 2001 From: bumkeyy Date: Mon, 27 Dec 2021 00:07:22 +0900 Subject: [PATCH 2/4] translate ko --- .../documentation/ko/handbook-v2/Narrowing.md | 292 +++++++++--------- 1 file changed, 146 insertions(+), 146 deletions(-) diff --git a/docs/documentation/ko/handbook-v2/Narrowing.md b/docs/documentation/ko/handbook-v2/Narrowing.md index 51a063d5..030bb04d 100644 --- a/docs/documentation/ko/handbook-v2/Narrowing.md +++ b/docs/documentation/ko/handbook-v2/Narrowing.md @@ -2,10 +2,10 @@ title: Narrowing layout: docs permalink: /docs/handbook/2/narrowing.html -oneline: "Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects." +oneline: "프로젝트에서 TypeScript가 어떻게 많은 양의 타입 문법을 제거하기 위해 JavaScript 지식을 사용하는지 이해합니다." --- -Imagine we have a function called `padLeft`. +`padLeft`라는 함수가 있다고 가정합니다. ```ts twoslash function padLeft(padding: number | string, input: string): string { @@ -13,9 +13,9 @@ function padLeft(padding: number | string, input: string): string { } ``` -If `padding` is a `number`, it will treat that as the number of spaces we want to prepend to `input`. -If `padding` is a `string`, it should just prepend `padding` to `input`. -Let's try to implement the logic for when `padLeft` is passed a `number` for `padding`. +`padding`의 타입이 `number`일 땐, `input` 앞에 `padding`만큼 여백을 추가합니다. +`padding`의 타입이 `string`일 땐, `input` 앞에 `padding`을 바로 붙여야 합니다. +이런 `padLeft`가 `padding`이 `number`일 때 어떻게 동작하는지 확인해 봅시다. ```ts twoslash // @errors: 2345 @@ -24,9 +24,9 @@ function padLeft(padding: number | string, input: string) { } ``` -Uh-oh, we're getting an error on `padding`. -TypeScript is warning us that adding a `number` to a `number | string` might not give us what we want, and it's right. -In other words, we haven't explicitly checked if `padding` is a `number` first, nor are we handling the case where it's a `string`, so let's do exactly that. +오 이런, `padding`에서 오류가 발생했습니다. +TypeScript는 `number | string`에 `number`를 할당할 때 우리의 의도와 다르다고 해석하여 경고를 하고, 이건 올바른 결과입니다. +다시 말하면, 우린 `padding`이 `number`인지, `string`일 땐 어떻게 처리할 지 명시적으로 확인하지 않았습니다. 그럼 정확하게 수정해봅시다. ```ts twoslash function padLeft(padding: number | string, input: string) { @@ -37,17 +37,17 @@ function padLeft(padding: number | string, input: string) { } ``` -If this mostly looks like uninteresting JavaScript code, that's sort of the point. -Apart from the annotations we put in place, this TypeScript code looks like JavaScript. -The idea is that TypeScript's type system aims to make it as easy as possible to write typical JavaScript code without bending over backwards to get type safety. +이 JavaScript 코드가 재미없어 보인다면, 정확하게 보고 있습니다. +우리가 표시한 주석을 제외하곤, TypeScript 코드는 JavaScript와 다를 바 없어 보입니다. +TypeScript의 타입 시스템은 일반적인 JavaScript 코드를 최대한 쉽게 작성해서 타입의 안정성을 얻는 것이 목표입니다. -While it might not look like much, there's actually a lot going under the covers here. -Much like how TypeScript analyzes runtime values using static types, it overlays type analysis on JavaScript's runtime control flow constructs like `if/else`, conditional ternaries, loops, truthiness checks, etc., which can all affect those types. +별거 아닌 것처럼 보여도, 사실 여기 안에선 많은 일들이 벌어지고 있습니다. +TypeScript가 정적 타입을 사용해서 런타임 값을 분석하는 것과 마찬가지로, 타입에 영향을 줄 수 있는 `if/else`, 조건부 삼진법, 루프, 진실성 검사 등과 같은 JavaScript 런타임 제어 흐름 구조에 타입 분석을 겹처서 진행합니다. -Within our `if` check, TypeScript sees `typeof padding === "number"` and understands that as a special form of code called a _type guard_. -TypeScript follows possible paths of execution that our programs can take to analyze the most specific possible type of a value at a given position. -It looks at these special checks (called _type guards_) and assignments, and the process of refining types to more specific types than declared is called _narrowing_. -In many editors we can observe these types as they change, and we'll even do so in our examples. +`if` 검사에서, TypeScript는 `typeof padding === "number"`를 확인하고 _type guard_ 라고 하는 특수한 형태의 코드로 인식합니다. +TypeScript는 현재 위치에서 가능한 값의 가장 구체적인 타입을 분석하기 위해 취할 수 있는 실행 경로를 따르게 됩니다. +이러 특수한 검사(_type guards_ 라고 불리는)와 함께 할당 부분과 선언된 타입을 더 세분화하는 과정을 _narrowing_ 이라고 합니다. +많은 에디터에서 이러 타입들이 변화되는 것을 확인할 수 있으며, 다음 예에서도 볼 수 있습니다. ```ts twoslash function padLeft(padding: number | string, input: string) { @@ -60,12 +60,12 @@ function padLeft(padding: number | string, input: string) { } ``` -There are a couple of different constructs TypeScript understands for narrowing. +TypeScript narrowing을 이해하기 위해선 알아야 할 몇 가지 다른 구조가 있습니다. ## `typeof` type guards -As we've seen, JavaScript supports a `typeof` operator which can give very basic information about the type of values we have at runtime. -TypeScript expects this to return a certain set of strings: +지금까지 살펴 본 것처럼, JavaScript는 런타임시에 우리가 값의 타입에 대한 기본적인 정보를 알 수 있는 `typeof` 연산자를 제공합니다. +TypeScript는 다음과 같은 특정한 문자열 집합을 반환할 것으로 예상합니다. - `"string"` - `"number"` @@ -76,12 +76,12 @@ TypeScript expects this to return a certain set of strings: - `"object"` - `"function"` -Like we saw with `padLeft`, this operator comes up pretty often in a number of JavaScript libraries, and TypeScript can understand it to narrow types in different branches. +`padLeft`에서 본 것처럼 이 연산자는 많은 JavaScript 라이브러리에서 등장하며, TypeScript는 각 분기들에서 타입을 좁혀서 이해할 수 있습니다. -In TypeScript, checking against the value returned by `typeof` is a type guard. -Because TypeScript encodes how `typeof` operates on different values, it knows about some of its quirks in JavaScript. -For example, notice that in the list above, `typeof` doesn't return the string `null`. -Check out the following example: +TypeScript에서는 `typeof`에 의해서 반환되는 값을 확인하는 행위를 type guard라고 합니다. +`typeof`가 값에 동작하는 방식을 TypeScript가 인코딩하기 때문에 JavaScript의 여러 특징에 대해서도 잘 알고 있습니다. +예를 들어 위의 목록에서 `typeof`는 문자열 `null`을 반환하지 않습니다. +다음 예를 확인하세요. ```ts twoslash // @errors: 2531 @@ -98,20 +98,20 @@ function printAll(strs: string | string[] | null) { } ``` -In the `printAll` function, we try to check if `strs` is an object to see if it's an array type (now might be a good time to reinforce that arrays are object types in JavaScript). -But it turns out that in JavaScript, `typeof null` is actually `"object"`! -This is one of those unfortunate accidents of history. +`printAll` 함수에서 `str`이 객체인지 확인하여 배열 타입인지(JavaScript에서는 배열이 객체 타입으로 강제할 수 있습니다.) +그러나 JavaScript에서 `typeof null`은 실제로 `"object"` 입니다! +이건 역사적으로 아주 불행한 사건입니다. -Users with enough experience might not be surprised, but not everyone has run into this in JavaScript; luckily, TypeScript lets us know that `strs` was only narrowed down to `string[] | null` instead of just `string[]`. +경험이 충분히 있는 사용자들은 놀라지 않겠지만, JavaScript에서 모든 사람들이 이 문제를 마주친 것은 아닙니다. 다행히 TypeScript는 `strs`가 `string[]`이 아닌 `string[] | null`로 좁혀졌음을 알려줍니다. -This might be a good segue into what we'll call "truthiness" checking. +이건 소위 "truthiness" 확인이라고 부르는 좋은 예시가 될 수 있습니다. # Truthiness narrowing -Truthiness might not be a word you'll find in the dictionary, but it's very much something you'll hear about in JavaScript. +Truthiness는 사전에서 찾을 수 있는 단어는 아니지만, JavaScript에서는 많이 들을 수 있습니다. -In JavaScript, we can use any expression in conditionals, `&&`s, `||`s, `if` statements, Boolean negations (`!`), and more. -As an example, `if` statements don't expect their condition to always have the type `boolean`. +JavaScript에서는 조건문, `%%`, `||`, `if` 구문, Boolean 부정(`!`)등에서 어떤 표현식이든 사용할 수 있습니다. +예를 들어, `if` 구문은 조건이 항상 `boolean`이라고 예상하지 않습니다. ```ts twoslash function getUsersOnlineMessage(numUsersOnline: number) { @@ -122,27 +122,27 @@ function getUsersOnlineMessage(numUsersOnline: number) { } ``` -In JavaScript, constructs like `if` first "coerce" their conditions to `boolean`s to make sense of them, and then choose their branches depending on whether the result is `true` or `false`. -Values like +JavaScript에서 `if`와 같은 구조는 처음에 조건 결과를 `boolean`과 같게 "강제"한 다음에 결과가 `true`인지 `false`인지에 따라 분기를 선택합니다. +다음과 같은 값들은 - `0` - `NaN` -- `""` (the empty string) -- `0n` (the `bigint` version of zero) +- `""` (빈 문자열) +- `0n` (0의 `bigint` 버전) - `null` - `undefined` -all coerce to `false`, and other values get coerced `true`. -You can always coerce values to `boolean`s by running them through the `Boolean` function, or by using the shorter double-Boolean negation. (The latter has the advantage that TypeScript infers a narrow literal boolean type `true`, while inferring the first as type `boolean`.) +모두 `false`로, 그 외의 값은 전부 `true`로 강제합니다. +항상 `Boolean` 함수를 사용하거나 더 짧은 이중-불린 부정(!!)을 `boolean` 으로 값을 강제할 수 있습니다. ( 전자는 `boolean` 타입으로 추론하고, 후자는 TypeScript가 더 좁은 리터럴 불린 값인 `true`로 추론합니다.) ```ts twoslash -// both of these result in 'true' +// 두 결과 모두 `true` 입니다. Boolean("hello"); // type: boolean, value: true !!"world"; // type: true, value: true ``` -It's fairly popular to leverage this behavior, especially for guarding against values like `null` or `undefined`. -As an example, let's try using it for our `printAll` function. +대부분 이러한 방식을 통해 `null` 또는 `undefined`와 같은 값에서 보호합니다. +예를 들어, `printAll` 함수에 적용해 보겠습니다. ```ts twoslash function printAll(strs: string | string[] | null) { @@ -156,15 +156,15 @@ function printAll(strs: string | string[] | null) { } ``` -You'll notice that we've gotten rid of the error above by checking if `strs` is truthy. -This at least prevents us from dreaded errors when we run our code like: +`strs`가 참 값인지 확인해서 위 오류를 제거한 것을 알 수 있습니다. +이렇게 하면 적어도 다음과 같은 코드가 실행될 때 발생할 수 있는 오류를 방지할 수 있습니다. ```txt TypeError: null is not iterable ``` -Keep in mind though that truthiness checking on primitives can often be error prone. -As an example, consider a different attempt at writing `printAll` +원시 값에 대한 truthiness 검사는 종종 오류가 발생하기 쉽다는 점을 명심하세요. +예를 들어 `printAll`을 작성할 때 다른 방식을 생각해볼 수 있습니다. ```ts twoslash {class: "do-not-do-this"} function printAll(strs: string | string[] | null) { @@ -184,13 +184,13 @@ function printAll(strs: string | string[] | null) { } ``` -We wrapped the entire body of the function in a truthy check, but this has a subtle downside: we may no longer be handling the empty string case correctly. +함수 전체에 참 값 검사로 감쌌지만, 미묘한 차이가 있습니다. 빈 문자열을 더 이상 올바르게 처리할 수 없습니다. -TypeScript doesn't hurt us here at all, but this is behavior worth noting if you're less familiar with JavaScript. -TypeScript can often help you catch bugs early on, but if you choose to do _nothing_ with a value, there's only so much that it can do without being overly prescriptive. -If you want, you can make sure you handle situations like these with a linter. +TypeScript는 여기서 전혀 문제가 되지 않지만, JavaScript가 익숙하지 않다면 주의해야 합니다. +TypeScript는 버그를 초기에 잡을 수 있도록 도울 수 있지만 값을 이용해서 _아무 것도_ 하지 않기로 결정한다면, 지나치게 규정되지 않은 상태에서 버그가 수행할 수 있는 작업만 할 수 있습니다. +linter를 사용하면 이러한 상황을 처리할 수 있습니다. -One last word on narrowing by truthiness is that Boolean negations with `!` filter out from negated branches. +마지막으로 truthiness를 좁힐 수 있는 방식은 부울 부정 `!`이 부정 분기에서 걸러 낼 수 있습니다. ```ts twoslash function multiplyAll( @@ -207,13 +207,13 @@ function multiplyAll( ## Equality narrowing -TypeScript also uses `switch` statements and equality checks like `===`, `!==`, `==`, and `!=` to narrow types. -For example: +TypeScript는 `switch` 구문이나 `===`, `!==`, `==`, `!=`과 같은 같음 비교를 통해서 타입을 좁힐 수 있습니다. +예를 들면 ```ts twoslash function example(x: string | number, y: string | boolean) { if (x === y) { - // We can now call any 'string' method on 'x' or 'y'. + // 이제 'x'와 'y'에 대해서 아무 'string' 메서드를 호출 할 수 있습니다. x.toUpperCase(); // ^? y.toLowerCase(); @@ -227,12 +227,12 @@ function example(x: string | number, y: string | boolean) { } ``` -When we checked that `x` and `y` are both equal in the above example, TypeScript knew their types also had to be equal. -Since `string` is the only common type that both `x` and `y` could take on, TypeScript knows that `x` and `y` must be a `string` in the first branch. +위의 예제에서 `x`와 `y`가 같다는 것을 확인했을 때, TypeScript는 둘의 타입이 같아야 한다는 것을 알 수 있습니다. +`x`와 `y`가 가질 수 있는 공통 타입이 `string`이 유일하기 때문에, TypeScript는 `x`와 `y`가 첫 번째 분기에서 `string`이여야 한다는 것을 알 수 있습니다. -Checking against specific literal values (as opposed to variables) works also. -In our section about truthiness narrowing, we wrote a `printAll` function which was error-prone because it accidentally didn't handle empty strings properly. -Instead we could have done a specific check to block out `null`s, and TypeScript still correctly removes `null` from the type of `strs`. +변수가 아닌 특정 리터럴 값에 대해서도 검사가 가능합니다. +truthiness narrowing 섹션에서, `printAll` 함수는 실수로 빈 문자열을 제대로 처리하지 못해 오류가 발생하기 쉬웠습니다. +하지만 `null`을 차단하기 위해 특정한 검사를 할 수 있고, TypeScript는 올바르게 `strs`에서 `null` 타입을 제거할 수 있습니다. ```ts twoslash function printAll(strs: string | string[] | null) { @@ -250,9 +250,9 @@ function printAll(strs: string | string[] | null) { } ``` -JavaScript's looser equality checks with `==` and `!=` also get narrowed correctly. -If you're unfamiliar, checking whether something `== null` actually not only checks whether it is specifically the value `null` - it also checks whether it's potentially `undefined`. -The same applies to `== undefined`: it checks whether a value is either `null` or `undefined`. +JavaScript의 느슨한 동등 검사인 `==`와 `!=` 또한 올바르게 타입을 좁힐 수 있습니다. +하지만 익숙하지 않다면, 어떤 `== null`이 실제로 `null` 값인지 검사하는 것 뿐만 아니라 잠재적으로 `undefined` 인지 검사하게 됩니다. +`== undefined` 또한 마찬가지 입니다. `null`이거나 `undefined` 인지 검사합니다. ```ts twoslash interface Container { @@ -260,12 +260,12 @@ interface Container { } function multiplyValue(container: Container, factor: number) { - // Remove both 'null' and 'undefined' from the type. + // 타입에서 'null'과 'undefined' 제거 if (container.value != null) { console.log(container.value); // ^? - // Now we can safely multiply 'container.value'. + // 이제 'container.value' 를 안전하게 곱할 수 있습니다. container.value *= factor; } } @@ -273,11 +273,11 @@ function multiplyValue(container: Container, factor: number) { ## The `in` operator narrowing -JavaScript has an operator for determining if an object has a property with a name: the `in` operator. -TypeScript takes this into account as a way to narrow down potential types. +JavaScript는 이름을 통해 속성이 객체 안에 있는지 확인하는 `in` 연산자가 있습니다. +TypeScript는 이 연산자를 사용해서 잠재적인 타입을 좁힐 수 있습니다. -For example, with the code: `"value" in x`. where `"value"` is a string literal and `x` is a union type. -The "true" branch narrows `x`'s types which have either an optional or required property `value`, and the "false" branch narrows to types which have an optional or missing property `value`. +예를 들어, 코드에서 `"value in x`가 있을 때 `"value"`는 문자열 리터럴이며 `x`는 유니언 타입입니다. +"true" 분기는 `x`가 `value`를 옵셔널이거나 필수적인 프로퍼티로 있다고 타입을 좁히고, "false" 분기는 `value`가 옵셔널이거나 프로퍼티로 없다고 타입을 좁히게 됩니다. ```ts twoslash type Fish = { swim: () => void }; @@ -292,7 +292,7 @@ function move(animal: Fish | Bird) { } ``` -To reiterate optional properties will exist in both sides for narrowing, for example a human could both swim and fly (with the right equipment) and thus should show up in both sides of the `in` check: +타입이 좁혀질 때 양쪽 모두 옵셔널 프로퍼티가 존재하며, 예를 들어 사람이 수영과 비행을 (올바른 장비와 함께) 할 수 있다면 `in` 검사시에 모두 나타나야 합니다. ```ts twoslash @@ -313,10 +313,10 @@ function move(animal: Fish | Bird | Human) { ## `instanceof` narrowing -JavaScript has an operator for checking whether or not a value is an "instance" of another value. -More specifically, in JavaScript `x instanceof Foo` checks whether the _prototype chain_ of `x` contains `Foo.prototype`. -While we won't dive deep here, and you'll see more of this when we get into classes, they can still be useful for most values that can be constructed with `new`. -As you might have guessed, `instanceof` is also a type guard, and TypeScript narrows in branches guarded by `instanceof`s. +JavaScript는 하나의 값이 다른 값의 "인스턴스" 여부를 확인하는 연산자가 있습니다. +보다 구체적으로 JavaScript에서 `x instanceof Foo`는 `x`의 _prototype chain_ 에 `Foo.prototype`을 포함하고 있는지 검사합니다. +여기보단 classes에서 더 자세히 확인할 수 있고, `new`를 이용해서 생성하는 대부분의 값에 대해선 여전히 유용합니다. +짐작하셨겠지만, `instanceof`는 또한 type guard이며, TypeScript는 `instanceof`를 통해 분기를 좁힐 수 있습니다. ```ts twoslash function logValue(x: Date | string) { @@ -332,7 +332,7 @@ function logValue(x: Date | string) { ## Assignments -As we mentioned earlier, when we assign to any variable, TypeScript looks at the right side of the assignment and narrows the left side appropriately. +앞서 언급했듯이 어떤 변수를 할당할 때, TypeScript는 할당의 오른쪽을 보고 왼쪽의 타입을 적절하게 좁힙니다. ```ts twoslash let x = Math.random() < 0.5 ? 10 : "hello world!"; @@ -347,11 +347,11 @@ console.log(x); // ^? ``` -Notice that each of these assignments is valid. -Even though the observed type of `x` changed to `number` after our first assignment, we were still able to assign a `string` to `x`. -This is because the _declared type_ of `x` - the type that `x` started with - is `string | number`, and assignability is always checked against the declared type. +이러한 할당들은 전부 유효합니다. +관찰하고 있던 `x`의 타입이 첫 번째 할당에서 `number`로 변경되어도, `x`에 `string`을 할당할 수 있습니다. +처음에 `x`의 _선언된 타입_ 이 `string | number`이고, 할당은 선언된 타입에 의해서 항상 검사되기 때문입니다. -If we'd assigned a `boolean` to `x`, we'd have seen an error since that wasn't part of the declared type. +만약 우리가 `x`에 `boolean`을 할당했다면, 선언된 타입의 일부가 아니기 때문에 오류를 확인할 수 있습니다. ```ts twoslash // @errors: 2322 @@ -369,9 +369,9 @@ console.log(x); ## Control flow analysis -Up until this point, we've gone through some basic examples of how TypeScript narrows within specific branches. -But there's a bit more going on than just walking up from every variable and looking for type guards in `if`s, `while`s, conditionals, etc. -For example +지금까지 TypeScript가 특정 분기내에서 어떻게 타입이 좁아지는지에 대해 몇 가지 기본적인 예를 살펴봤습니다. +하지만 그냥 모든 변수에서 `if`, `while`, 조건문 등에서 타입 가드를 찾는 것보다 더 많은 일들이 일어나고 있습니다. +예를 들면 ```ts twoslash function padLeft(padding: number | string, input: string) { @@ -382,12 +382,12 @@ function padLeft(padding: number | string, input: string) { } ``` -`padLeft` returns from within its first `if` block. -TypeScript was able to analyze this code and see that the rest of the body (`return padding + input;`) is _unreachable_ in the case where `padding` is a `number`. -As a result, it was able to remove `number` from the type of `padding` (narrowing from `string | number` to `string`) for the rest of the function. +`padLeft`는 첫 번째 `if` 블락에서 반환을 하고 있습니다. +TypeScript는 이 코드를 분석해서 `padding`이 `number`인 경우 본문의 나머지 부분인(`return padding + input;`)에 _도달할 수 없음_ 을 알 수 있습니다. +결과적으로 함수의 나머지 부분에서 `padding`의 타입을(`string | number`에서 `string`으로 좁히면서) `number`를 제거할 수 있습니다. -This analysis of code based on reachability is called _control flow analysis_, and TypeScript uses this flow analysis to narrow types as it encounters type guards and assignments. -When a variable is analyzed, control flow can split off and re-merge over and over again, and that variable can be observed to have a different type at each point. +도달가능성에 기반해서 코드를 분석하는 것을 _control flow analysis_ 라고 하며, TypeScript는 이 흐름 분석을 통해서 type guard나 할당을 마주쳤을 때 타입을 좁게 만듭니다. +변수를 분석할 때, 제어 흐름은 분할했다가 다시 병합할 수 있으며, 각 지점에서 변수들은 다른 타입을 가질 수 있습니다. ```ts twoslash function example() { @@ -415,9 +415,9 @@ function example() { ## Using type predicates -We've worked with existing JavaScript constructs to handle narrowing so far, however sometimes you want more direct control over how types change throughout your code. +지금까지 기존의 JavaScript 구조체들과 함께 타입을 좁혀왔지만, 때로는 코드에서 타입이 어떻게 변하는지보다 직접 통제가 필요할 수 있습니다. -To define a user-defined type guard, we simply need to define a function whose return type is a _type predicate_: +사용자-정의된 type guard를 정의하려면, 반환 타입이 _type predicate_ 인 함수를 정의하기만 하면 됩니다. ```ts twoslash type Fish = { swim: () => void }; @@ -429,10 +429,10 @@ function isFish(pet: Fish | Bird): pet is Fish { } ``` -`pet is Fish` is our type predicate in this example. -A predicate takes the form `parameterName is Type`, where `parameterName` must be the name of a parameter from the current function signature. +이 예제에서 `pet is Fish`는 type predicate 입니다. +predicate는 `parameterName is Type` 형태를 취하고 있으며, `parameterName`은 현재 함수 시그니처의 인자 이름이여야 합니다. -Any time `isFish` is called with some variable, TypeScript will _narrow_ that variable to that specific type if the original type is compatible. +어떤 변수와 함께 `isFish`가 호출되면, TypeScript는 원래 타입과 호환이 될 때 변수를 특정 타입으로 _좁힙니다._ ```ts twoslash type Fish = { swim: () => void }; @@ -442,7 +442,7 @@ function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; } // ---cut--- -// Both calls to 'swim' and 'fly' are now okay. +// 'swim'과 'fly에 대한 호출은 이제 괜찮습니다. let pet = getSmallPet(); if (isFish(pet)) { @@ -452,10 +452,10 @@ if (isFish(pet)) { } ``` -Notice that TypeScript not only knows that `pet` is a `Fish` in the `if` branch; -it also knows that in the `else` branch, you _don't_ have a `Fish`, so you must have a `Bird`. +TypeScript는 `if`분기에서 `pet`이 `Fish`라는 걸 알고 있을 뿐만 아니라, +`else` 분기에서는 `Fish`가 _없으니_ 반드시 `Bird`인 것도 알고 있습니다. -You may use the type guard `isFish` to filter an array of `Fish | Bird` and obtain an array of `Fish`: +Type guard인 `isFish`를 사용해서 `Fish | Bird` 배열을 필터링하고 `Fish` 배열을 얻을 수 있습니다. ```ts twoslash type Fish = { swim: () => void; name: string }; @@ -470,24 +470,24 @@ const underWater1: Fish[] = zoo.filter(isFish); // or, equivalently const underWater2: Fish[] = zoo.filter(isFish) as Fish[]; -// The predicate may need repeating for more complex examples +// predicate는 더 복잡한 예제에서 반복해서 사용해야 할 수 있습니다. const underWater3: Fish[] = zoo.filter((pet): pet is Fish => { if (pet.name === "sharkey") return false; return isFish(pet); }); ``` -In addition, classes can [use `this is Type`](/docs/handbook/2/classes.html#this-based-type-guards) to narrow their type. +추가적으로 classes는 타입을 줄이기 위해 [`this is Type` 사용할 수](/docs/handbook/2/classes.html#this-based-type-guards) 있습니다. # Discriminated unions -Most of the examples we've looked at so far have focused around narrowing single variables with simple types like `string`, `boolean`, and `number`. -While this is common, most of the time in JavaScript we'll be dealing with slightly more complex structures. +지금까지 살펴본 예제들은 `string`, `boolean` 그리고 `number`와 같이 단순한 타입으로 단일 변수를 좁히는데 집중했습니다. +위 경우가 흔하지만, JavaScript에서 대부분 복잡한 구조를 다뤄보도록 하겠습니다. -For some motivation, let's imagine we're trying to encode shapes like circles and squares. -Circles keep track of their radiuses and squares keep track of their side lengths. -We'll use a field called `kind` to tell which shape we're dealing with. -Here's a first attempt at defining `Shape`. +동기 부여를 위해, 원과 정사각형 모양을 암호화한다고 상상해보겠습니다. +원은 지름을 측정하고 정사각형은 측면 길이를 측정합니다. +여기서 모양을 다루기 위해 `kind`라는 필드를 사용하겠습니다. +`Shape`을 정의하는 첫 번째 예제입니다. ```ts twoslash interface Shape { @@ -497,8 +497,8 @@ interface Shape { } ``` -Notice we're using a union of string literal types: `"circle"` and `"square"` to tell us whether we should treat the shape as a circle or square respectively. -By using `"circle" | "square"` instead of `string`, we can avoid misspelling issues. +`"circle"`과 `"square"`이라는 문자열 리터럴 타입의 union을 사용해서 도형을 원으로 처리할지 사각형으로 처리할지 알려줍니다. +`string` 대신에 `"circle" | "square"`을 사용해서 오타 문제를 방지할 수 있습니다. ```ts twoslash // @errors: 2367 @@ -517,8 +517,8 @@ function handleShape(shape: Shape) { } ``` -We can write a `getArea` function that applies the right logic based on if it's dealing with a circle or square. -We'll first try dealing with circles. +원 혹은 정사각형을 다룰지에 따라 올바르게 동작하는 `getArea` 함수를 작성할 수 있습니다. +먼저 원에 대해서 작성하겠습니다. ```ts twoslash // @errors: 2532 @@ -536,8 +536,8 @@ function getArea(shape: Shape) { -Under [`strictNullChecks`](/tsconfig#strictNullChecks) that gives us an error - which is appropriate since `radius` might not be defined. -But what if we perform the appropriate checks on the `kind` property? +[`strictNullChecks`](/tsconfig#strictNullChecks)에서 오류를 발생시킵니다. `radius`가 정의되지 않았기 때문 적절한 오류입니다. +하지만 `kind` 프로퍼티를 이용해서 적절하게 검사하면 어떻게 될까요? ```ts twoslash // @errors: 2532 @@ -555,9 +555,9 @@ function getArea(shape: Shape) { } ``` -Hmm, TypeScript still doesn't know what to do here. -We've hit a point where we know more about our values than the type checker does. -We could try to use a non-null assertion (a `!` after `shape.radius`) to say that `radius` is definitely present. +흠 TypeScript는 여전히 무엇을 해야할지 모릅니다. +드디어 타입 검사기보다 우리가 더 많이 아는 부분에 도착했습니다. +non-null assertion(`shape.radius` 뒤에 `!`)를 통해서 `radius`가 확실하게 존재하는 것을 알려줄 수 있습니다. ```ts twoslash interface Shape { @@ -574,14 +574,14 @@ function getArea(shape: Shape) { } ``` -But this doesn't feel ideal. -We had to shout a bit at the type-checker with those non-null assertions (`!`) to convince it that `shape.radius` was defined, but those assertions are error-prone if we start to move code around. -Additionally, outside of [`strictNullChecks`](/tsconfig#strictNullChecks) we're able to accidentally access any of those fields anyway (since optional properties are just assumed to always be present when reading them). -We can definitely do better. +하지만 이상적으로 느껴지지 않습니다. +non-null assertions (`!`)을 이용해서 타입 검사기에 `shape.radius`가 정의된걸 알려야 하고, 이는 코드가 변경되면 오류가 발생하기 쉽습니다. +또한 [`strictNullChecks`](/tsconfig#strictNullChecks)를 제외하면 이러한 필드는 실수로 접근할 수 있습니다(옵셔널 프로퍼티는 단지 읽을 때 항상 있는 것으로 간주됩니다). +우린 분명히 더 잘 할 수 있습니다. -The problem with this encoding of `Shape` is that the type-checker doesn't have any way to know whether or not `radius` or `sideLength` are present based on the `kind` property. -We need to communicate what _we_ know to the type checker. -With that in mind, let's take another swing at defining `Shape`. +`Shape` 인코딩의 문제는 타입 검사기가 `kind` 프로퍼티를 가지고 `radius`가 있는지 `sideLength`가 있는지 알 수 없기 때문입니다. +타입 검사기에 이러한 점을 알려줄 수 있는 방법이 필요합니다. +이 점을 생각해서, 또다른 `Shape`를 정의하겠습니다. ```ts twoslash interface Circle { @@ -597,9 +597,9 @@ interface Square { type Shape = Circle | Square; ``` -Here, we've properly separated `Shape` out into two types with different values for the `kind` property, but `radius` and `sideLength` are declared as required properties in their respective types. +여기서는 `Shape`를 `kind` 프로퍼티에 따라서 적절하게 두 가지 타입으로 분리했지만, `radius`와 `sideLength`는 각각의 타입에 필수 프로퍼티로 선언되었습니다. -Let's see what happens here when we try to access the `radius` of a `Shape`. +`Shape`의 `radius`에 접근할 때 어떤 결과가 발생하는지 살펴보세요. ```ts twoslash // @errors: 2339 @@ -621,12 +621,12 @@ function getArea(shape: Shape) { } ``` -Like with our first definition of `Shape`, this is still an error. -When `radius` was optional, we got an error (only in [`strictNullChecks`](/tsconfig#strictNullChecks)) because TypeScript couldn't tell whether the property was present. -Now that `Shape` is a union, TypeScript is telling us that `shape` might be a `Square`, and `Square`s don't have `radius` defined on them! -Both interpretations are correct, but only does our new encoding of `Shape` still cause an error outside of [`strictNullChecks`](/tsconfig#strictNullChecks). +`Shape`에 대한 첫 번째 정의와 마찬가지로 오류가 발생하고 있습니다. +`radius`가 옵셔널일 때 오류가 발생하는데([`strictNullChecks`](/tsconfig#strictNullChecks) 일 때만), TypeScript는 프로퍼티가 있는지 알 수 없기 때문입니다. +이제 `Shape`은 union이고 TypeScript는 `shape`은 `Square`일 수 있으며 `Square`에는 `radius`가 정의되지 않았다고 알려줍니다. +두 해석 모두 올바르지만, 새 `Shape`은 여전히 [`strictNullChecks`](/tsconfig#strictNullChecks) 외부에서 오류를 발생시킵니다. -But what if we tried checking the `kind` property again? +하지만 `kind` 프로퍼티를 이용해서 다시 한번 확인하면 어떻게 될까요? ```ts twoslash interface Circle { @@ -650,15 +650,15 @@ function getArea(shape: Shape) { } ``` -That got rid of the error! -When every type in a union contains a common property with literal types, TypeScript considers that to be a _discriminated union_, and can narrow out the members of the union. +오류는 이제 사라졌습니다! +union의 모든 타입이 문자 그대로의 타입을 가진 공통 프로퍼티를 포함하면, TypeScript는 이를 _구별가능한 union_ 으로 간주해서 union의 요소를 좁힐 수 있습니다. -In this case, `kind` was that common property (which is what's considered a _discriminant_ property of `Shape`). -Checking whether the `kind` property was `"circle"` got rid of every type in `Shape` that didn't have a `kind` property with the type `"circle"`. -That narrowed `shape` down to the type `Circle`. +이 경우, `kind`가 공통 프로퍼티(`Shape`의 구별 가능한 프로퍼티로 고려되는)입니다. +`kind` 프로퍼티가 `"circle`인지 검사하는 동안, `kind` 프로퍼티가 `"circle"`이 아닌 프로퍼티는 전부 제거되었습니다. +이렇게 `shape`은 `Circle`로 좁혀졌습니다. -The same checking works with `switch` statements as well. -Now we can try to write our complete `getArea` without any pesky `!` non-null assertions. +`switch` 구문도 똑같이 검사가 동작합니다. +성가신 `!` non-null assertions 없이도 완전한 `getArea`를 작성할 수 있습니다. ```ts twoslash interface Circle { @@ -686,27 +686,27 @@ function getArea(shape: Shape) { } ``` -The important thing here was the encoding of `Shape`. -Communicating the right information to TypeScript - that `Circle` and `Square` were really two separate types with specific `kind` fields - was crucial. -Doing that let us write type-safe TypeScript code that looks no different than the JavaScript we would've written otherwise. -From there, the type system was able to do the "right" thing and figure out the types in each branch of our `switch` statement. +여기서 가장 중요한 점은 `Shape`의 인코딩입니다. +`Circle`과 `Square`가 `kind` 필드로 두 개의 타입으로 구분할 수 있다는 올바른 정보를 TypeScript에게 알려주는 것은 정말 중요합니다. +이렇게 하면 지금껏 작성한 JavaScript와 별 차이 없는 안전한 타입의 TypeScript 코드를 작성할 수 있습니다. +거기서부터 타입 시스템은 "올바르게" 동작할 수 있고, `switch` 구문에서 각 분기마다 정확한 타입을 확인할 수 있습니다. -> As an aside, try playing around with the above example and remove some of the return keywords. -> You'll see that type-checking can help avoid bugs when accidentally falling through different clauses in a `switch` statement. +> 그 외에 위의 예제에서 많은 시도를 해보고, 반환 키워드를 제거 해보세요. +> 타입 검사는 `switch`문의 다른 절에서 떨어질 때 버그를 방지하는데 도움이 될 수 있습니다. -Discriminated unions are useful for more than just talking about circles and squares. -They're good for representing any sort of messaging scheme in JavaScript, like when sending messages over the network (client/server communication), or encoding mutations in a state management framework. +원과 정사각형 이상으로 구분할 수 있는 unions는 유용합니다. +네트워크를 통해 메세지를 보내거나(클라이언트/서버 통신) 상태 관리 프레임워크에서 mutation을 코딩할 때와 같은 모든 종류의 메세징 체계를 JavaScript에서 나타내는 데 유용합니다. # The `never` type -When narrowing, you can reduce the options of a union to a point where you have removed all possibilities and have nothing left. -In those cases, TypeScript will use a `never` type to represent a state which shouldn't exist. +타입이 좁혀질 때, 모든 가능성을 제거하고 남는 것이 없는 상황이까지 줄일 수 있습니다. +이 경우 TypeScript는 존재하지 않는 상태를 나타내기 위해 `never` 타입을 사용합니다. # Exhaustiveness checking -The `never` type is assignable to every type; however, no type is assignable to `never` (except `never` itself). This means you can use narrowing and rely on `never` turning up to do exhaustive checking in a switch statement. +`never` 타입은 모든 타입에 할당할 수 있지만 `never` 자체르 ㄹ제외하고 `never`에 할당할 수 있는 타입은 없습니다. 즉 범위를 좁히거나 switch 구문에서 철저하게 확인할 때 `never`를 사용할 수 있습니다. -For example, adding a `default` to our `getArea` function which tries to assign the shape to `never` will raise when every possible case has not been handled. +예를 들어, 도형을 `never`에 할당하려는 `getArea` 함수에 `default`를 추가하면 모든 사례가 처리되지 않을 때 동작합니다. ```ts twoslash interface Circle { @@ -734,7 +734,7 @@ function getArea(shape: Shape) { } ``` -Adding a new member to the `Shape` union, will cause a TypeScript error: +`Shape` union에 새 멤버를 추가하면 TypeScript 오류가 발생합니다. ```ts twoslash // @errors: 2322 From 118a4ef9a341e5ec3560f97286315a44363c9576 Mon Sep 17 00:00:00 2001 From: bumkeyy Date: Mon, 27 Dec 2021 00:14:42 +0900 Subject: [PATCH 3/4] fix lint --- docs/documentation/ko/handbook-v2/Narrowing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/ko/handbook-v2/Narrowing.md b/docs/documentation/ko/handbook-v2/Narrowing.md index 030bb04d..b7258c54 100644 --- a/docs/documentation/ko/handbook-v2/Narrowing.md +++ b/docs/documentation/ko/handbook-v2/Narrowing.md @@ -1,7 +1,7 @@ --- title: Narrowing layout: docs -permalink: /docs/handbook/2/narrowing.html +permalink: /ko/docs/handbook/2/narrowing.html oneline: "프로젝트에서 TypeScript가 어떻게 많은 양의 타입 문법을 제거하기 위해 JavaScript 지식을 사용하는지 이해합니다." --- From b7ada760f83e20ba83b70be649ff9196ef546447 Mon Sep 17 00:00:00 2001 From: Kibeom Kwon Date: Sun, 27 Feb 2022 10:46:23 +0900 Subject: [PATCH 4/4] Update docs/documentation/ko/handbook-v2/Narrowing.md Co-authored-by: Seohee Park --- docs/documentation/ko/handbook-v2/Narrowing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/ko/handbook-v2/Narrowing.md b/docs/documentation/ko/handbook-v2/Narrowing.md index b7258c54..6f63d0f3 100644 --- a/docs/documentation/ko/handbook-v2/Narrowing.md +++ b/docs/documentation/ko/handbook-v2/Narrowing.md @@ -704,7 +704,7 @@ function getArea(shape: Shape) { # Exhaustiveness checking -`never` 타입은 모든 타입에 할당할 수 있지만 `never` 자체르 ㄹ제외하고 `never`에 할당할 수 있는 타입은 없습니다. 즉 범위를 좁히거나 switch 구문에서 철저하게 확인할 때 `never`를 사용할 수 있습니다. +`never` 타입은 모든 타입에 할당할 수 있지만 `never` 자체를 제외하고 `never`에 할당할 수 있는 타입은 없습니다. 즉 범위를 좁히거나 switch 구문에서 철저하게 확인할 때 `never`를 사용할 수 있습니다. 예를 들어, 도형을 `never`에 할당하려는 `getArea` 함수에 `default`를 추가하면 모든 사례가 처리되지 않을 때 동작합니다.