Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translate 1 file to ko - Narrowing.md #131

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

bumkeyy
Copy link
Contributor

@bumkeyy bumkeyy commented Dec 26, 2021

b6c3a6d

번역 확인 부탁드려요~

@github-actions
Copy link
Contributor

Thanks for the PR!

This section of the codebase is owned by @bumkeyy, @yeonjuan, @guyeol, and @dvlprsh - if they write a comment saying "LGTM" then it will be merged.

@bumkeyy bumkeyy changed the title Narrowing ko bumkeyy Translate 1 file to ko - Narrowing.md Dec 26, 2021
@github-actions
Copy link
Contributor

github-actions bot commented Dec 26, 2021

Translation of Narrowing.md

title: Narrowing
layout: docs
permalink: /ko/docs/handbook/2/narrowing.html

oneline: "I understand how TypeScript uses JavaScript knowledge to remove large amounts of type grammar from a project."

padLeftAssume that you have a function called .

function padLeft(padding: number | string, input: string): string {
  throw new Error("Not implemented yet!");
}

paddingA type of numberWhen it happens, input before paddingAdd as many margins.
paddingA type of stringWhen it happens, input before paddingMust be attached immediately.
Oh dear padLeftprice paddingTwo numberLet's see how it works.

// @errors: 2345
function padLeft(padding: number | string, input: string) {
  return " ".repeat(padding) + input;
}

Oh my god paddingThere was an error in .
TypeScript number | stringat numberWhen assigning , we interpret it as different from our intentions and warn you, and that's the right result.
In other words, we paddingTwo numbercognition stringI haven't explicitly confirmed how to handle it. So let's fix it correctly.

function padLeft(padding: number | string, input: string) {
  if (typeof padding === "number") {
    return " ".repeat(padding) + input;
  }
  return padding + input;
}

If this JavaScript code doesn't seem funny, you're looking at it correctly.
Except for the comments we display, typeScript code looks no different from JavaScript.
TypeScript's type system aims to make it as easy as possible to write common JavaScript code to achieve type stability.

It rather than seems like a big deal, but there's actually a lot going on here.
Just as TypeScript uses static types to analyze runtime values, it can affect types. if/elseLayer type analysis on JavaScript runtime control flow structures, such as conditional strikeouts, loops, veracity checks, and so on.

if In the scan, TypeScript typeof padding === "number"Check type guard It is recognized as a special form of code called .
TypeScript follows the path of action that can be taken to analyze the most specific types of possible values in the current location.
This special inspection (type guards the process of further subdividing the allocation portion and declared type. narrowing It is called .
You can see these types changing in many editors, and you can also see them in the following examples.

function padLeft(padding: number | string, input: string) {
  if (typeof padding === "number") {
    return " ".repeat(padding) + input;
    //                ^?
  }
  return padding + input;
  //     ^?
}

To understand TypeScript narrowing, there are a few different structures that you need to know.

typeof type guards

As we've seen, JavaScript provides basic information about the type of value we have at runtime. typeof Provides operators.
TypeScript expects to return a specific set of strings, such as:

  • "string"
  • "number"
  • "bigint"
  • "boolean"
  • "symbol"
  • "undefined"
  • "object"
  • "function"

padLeftAs you can see, this operator appears in many JavaScript libraries, and TypeScript can be understood by narrowing down the types in each branch.

In TypeScript, typeofThe act of determining the value returned by is called type guard.
typeofBecause TypeScript encodes how it behaves on values, we are also familiar with the many characteristics of JavaScript.
For example, in the list above, typeofstring nullDoes not return .
Check out these examples:

// @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
  }
}

printAll In a function strThis object is checked to see if it is an array type (in JavaScript, the array can be forced into an object type.)
However, in JavaScript, typeof nullis actually "object" Is!
This is a very unfortunate event historically.

I wouldn't be surprised if there's enough experience, but not everyone in JavaScript has encountered this problem. Fortunately, TypeScript strsprice string[]non- string[] | nullit has been narrowed down to .

This could be a good example of what we call so-called "truthiness" verification.

Truthiness narrowing

Truthiness is not a word found in dictionaries, but it can be heard a lot in JavaScript.

In JavaScript, the conditional statement, %%, ||, if Syntax, Boolean Negativity (!) and any expression can be used.
For example if The syntax is always booleanI don't expect that.

function getUsersOnlineMessage(numUsersOnline: number) {
  if (numUsersOnline) {
    return `There are ${numUsersOnline} online now!`;
  }
  return "Nobody's here. :(";
}

In JavaScript ifThe same structure initially results in the condition boolean"Forced" as follows, and then the result is truecognition falseSelect a branch based on whether it is.
The following values:

  • 0
  • NaN
  • "" (empty string)
  • 0n (0 bigint Version)
  • null
  • undefined

all falseall other values. trueForce to .
All the time Boolean Use functions or shorter double-called negatives (!!) boolean you can force a value with . (The former boolean the latter is a value called a literal, where TypeScript is narrower. trueinferring to .)

// 두 결과 모두 `true` 입니다.
Boolean("hello"); // type: boolean, value: true
!!"world"; // type: true,    value: true

Most of the time, this is how null or undefinedProtects from values such as .
For example printAll Let's apply it to a function.

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);
  }
}

strsYou can see that you removed the above error by verifying that is a true value.
This at least avoids errors that can occur when the following code runs:

TypeError: null is not iterable

Keep in mind that truthiness checks for raw values are often error-prone.
For example printAllYou can think of a different approach when you create .

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);
    }
  }
}

The entire function is wrapped in a true value check, but there are subtle differences. Empty strings can no longer be processed correctly.

TypeScript is not a problem at all here, but be careful if you are not familiar with JavaScript.
TypeScript can help you catch bugs early, but you can use values to Nothing If you decide not to, you can only do what a bug can do without being overly prescribed.
Linter allows you to handle this situation.

Finally, the way to narrow down truthiness is to deny Booleans !You can filter out this negative branch.

function multiplyAll(
  values: number[] | undefined,
  factor: number
): number[] | undefined {
  if (!values) {
    return values;
  } else {
    return values.map((x) => x * factor);
  }
}

Equality narrowing

TypeScript switch Phrases or ===, !==, ==, !=You can narrow the type by comparing equality, such as .
Like what

function example(x: string | number, y: string | boolean) {
  if (x === y) {
    // 이제 'x'와 'y'에 대해서 아무 'string' 메서드를 호출 할 수 있습니다.
    x.toUpperCase();
    // ^?
    y.toLowerCase();
    // ^?
  } else {
    console.log(x);
    //          ^?
    console.log(y);
    //          ^?
  }
}

In the example above xand yWhen you confirm that the type script is the same, you can see that the two types must be the same.
xand yThe common type that can have stringBecause it is unique, TypeScript xand yin the first quarter. stringyou can see that it must be.

You can also check specific literal values, not variables.
In the truthiness narrowing section, printAll The function was prone to errors because it accidentally failed to handle empty strings properly.
but nullA specific check can be performed to block the TypeScript. strsIn null You can remove the type.

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);
      //          ^?
    }
  }
}

A loose equivalence check of JavaScript ==and != You can also narrow the type correctly.
But if you're not familiar with it, == nullThis is actually null not only checking for values, but also potentially undefined You'll have a cognitive check.
== undefined The same is true. nullOr undefined Check for cognition.

interface Container {
  value: number | null | undefined;
}

function multiplyValue(container: Container, factor: number) {
  // 타입에서 'null'과 'undefined' 제거
  if (container.value != null) {
    console.log(container.value);
    //                    ^?

    // 이제 'container.value' 를 안전하게 곱할 수 있습니다.
    container.value *= factor;
  }
}

The in operator narrowing

JavaScript uses a name to verify that the property is inside an object. in There is an operator.
TypeScript can use this operator to narrow down potential types.

For example, in code, "value in xwhen there is "value"is a string literal. xis a Union type.
The "true" branch xprice valuethe type that is optional or essential, and the "false" branch valueis not an optional or property, and the type is narrowed.

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    return animal.swim();
  }

  return animal.fly();
}

When the type is narrowed, both have optional properties, for example, if a person can swim and fly (with the right equipment), in It should all appear at the time of inspection.

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 that checks whether one value is an "instance" of another.
More specifically, in JavaScript x instanceof Foo xof prototype chain at Foo.prototypeCheck to see if it contains .
You can find out more in classes than here. newThis is still useful for most of the values you generate using .
As you might have guessed, instanceofis also a type guard, and TypeScript instanceofto narrow the branch.

function logValue(x: Date | string) {
  if (x instanceof Date) {
    console.log(x.toUTCString());
    //          ^?
  } else {
    console.log(x.toUpperCase());
    //          ^?
  }
}

Assignments

As mentioned earlier, when assigning a variable, TypeScript looks to the right of the assignment and narrows the type on the left accordingly.

let x = Math.random() < 0.5 ? 10 : "hello world!";
//  ^?
x = 1;

console.log(x);
//          ^?
x = "goodbye!";

console.log(x);
//          ^?

All of these allocations are valid.
I was observing xThe type of numbereven if it changes to , xat stringYou can assign .
initially xof Declared type Two string | numberthe allocation is always checked by the declared type.

If we do, xat booleanIf you have assigned , you can check for errors because they are not part of the declared type.

// @errors: 2322
let x = Math.random() < 0.5 ? 10 : "hello world!";
//  ^?
x = 1;

console.log(x);
//          ^?
x = true;

console.log(x);
//          ^?

Control flow analysis

So far, we've seen some basic examples of how TypeScript narrows down within a particular branch.
but just from all the variables if, whileThere is more going on than finding a type guard in a conditional statement.
Like what

function padLeft(padding: number | string, input: string) {
  if (typeof padding === "number") {
    return " ".repeat(padding) + input;
  }
  return padding + input;
}

padLeftis the first if I'm making a return from Blac.
TypeScript analyzes this code and paddingTwo numberthe rest of the body .return padding + input;) on Unre reachable You can tell.
As a result, the rest of the function paddingA type of (string | numberIn stringwhile narrowing it down to) numberyou can remove .

Analysis of code based on reachability. control flow analysis TypeScript uses this flow analysis to narrow down the type when encountered with a type guard or assignment.
When analyzing variables, the control flow can be split and re-merged, and at each point the variables can have different types.

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

So far, we've narrowed down the type with traditional JavaScript structures, but sometimes you need more direct control over how the type changes in your code.

To define a user-defined type guard, the return type type predicate All you have to do is define a function.

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;
}

In this example pet is Fishis type predict.
Predict is parameterName is Type I take form, parameterNamemust be the factor name of the current function signature.

with any variable isFishWhen called, TypeScript will Narrow.

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---
// 'swim'과 'fly에 대한 호출은 이제 괜찮습니다.
let pet = getSmallPet();

if (isFish(pet)) {
  pet.swim();
} else {
  pet.fly();
}

TypeScript ifIn branch petTwo FishNot only do you know that,
else In a branch, Fishprice There's no. without fail BirdI know that it is.

Type guard isFishusing Fish | Bird Filter the array and Fish You can get an array.

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[];

// predicate는 더 복잡한 예제에서 반복해서 사용해야 할 수 있습니다.
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
  if (pet.name === "sharkey") return false;
  return isFish(pet);
});

In addition, classes to reduce the type this is Type available You.

Discriminated unions

The examples we've seen so far string, boolean and numberWe focused on narrowing a single variable to a simple type, such as .
Although it is common to do so, javaScript will cover mostly complex structures.

For motivation, imagine encrypting circles and square shapes.
The circle measures the diameter, and the square measures the lateral length.
To deal with the shape here kindI'll use the field.
ShapeThe first example that defines .

interface Shape {
  kind: "circle" | "square";
  radius?: number;
  sideLength?: number;
}

"circle"and "square"Use a string literal type of union to tell you whether to circle a shape or a rectangle.
string Instead of "circle" | "square"to avoid typos.

// @errors: 2367
interface Shape {
  kind: "circle" | "square";
  radius?: number;
  sideLength?: number;
}

// ---cut---
function handleShape(shape: Shape) {
  // oops!
  if (shape.kind === "rect") {
    // ...
  }
}

Depending on whether you want to handle a circle or square, getArea You can write functions.
First, I'll write about the circle.

// @errors: 2532
interface Shape {
  kind: "circle" | "square";
  radius?: number;
  sideLength?: number;
}

// ---cut---
function getArea(shape: Shape) {
  return Math.PI * shape.radius ** 2;
}

strictNullChecksraises an error. radiusIs an appropriate error because is not defined.
but kind What happens if I use properties to properly inspect them?

// @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.
We've finally got to a part where we know more than type checkers.
non-null assertion(shape.radius behind !) through radiuscan tell you what exists.

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 it doesn't feel ideal.
non-null assertions (!) to the type checker. shape.radiusyou need to know what is defined, which is prone to errors if the code changes.
In addition strictNullChecksExcept for these fields, these fields can be accessed by mistake (optional properties are always considered to be there when they are just read).
We can definitely do better.

Shape The problem with encoding is that the type checker kind Have a property radiusIf there's one. sideLengthI don't know if there's any.
The type checker needs a way to tell you this.
Thinking about this, another ShapeLet's define .

interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

Here, Shape kind Depending on the property, we separated them into two types. radiusand sideLengthhas been declared a required property for each type.

Shapeof radiusSee what happens when you approach .

// @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;
}

ShapeAs with the first definition for , an error is occurring.
radiuserrors occur when (strictNullChecks Only when it is), TypeScript does not know if there are properties.
now Shapeis union and TypeScript shapesilver Squareit can be. Squarein radiusis not defined.
Both analyses are correct, but Shapestill strictNullChecks Causes an external error.

but kind What happens if I use a property to check it again?

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;
    //               ^?
  }
}

The error is now gone!
If all types of unions contain common properties with literal types, TypeScript Distinguishable union you can narrow down elements of the union by considering .

In this case, kindcommon properties (Shape(considered a distinguishable property of ).
kind Properties "circleDuring cognitive examination, kind Properties "circle"All non-properties have been removed.
like this shapesilver Circlenarrowed down to .

switch The syntax works the same.
cumbersome ! Complete without non-null assertions getAreaYou can create .

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 most important thing here is ShapeThe encoding of .
Circleand Squareprice kind It's really important to tell TypeScript the correct information that you can separate them into two types of fields.
This allows you to write a safe type of TypeScript code that doesn't make much difference from the JavaScript you've written so far.
From there, the type system can work "correctly". switch You can see the exact type for each branch in the syntax.

In addition to that, try a lot of trying in the example above, and try removing the return keyword.
Type check switchIt can help prevent bugs when falling out of other sections of the door.

Unions, which can be separated by circles and squares, are useful.
This is useful for JavaScript to represent all kinds of messaging schemes, such as sending messages over a network (client/server communication) or coding mutations in a health management framework.

The never type

When the type is narrowed down, all possibilities can be eliminated and the situation where there is nothing left can be reduced.
In this case, TypeScript will indicate a state that does not exist. never Use a type.

Exhaustiveness checking

never Types can be assigned to any type. never Except for itself neverThere is no type that can be assigned to . That is, when narrowing or thoroughly checking the switch syntax. neveryou can use .

For example, a shape neverI want to assign it to getArea To a function defaultWhen you add , it behaves when not all cases are processed.

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;
  }
}

Shape When you add a new member to the union, you get a TypeScript error.

// @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;
  }
}

Generated by 🚫 dangerJS against b7ada76

@yeonjuan
Copy link
Contributor

yeonjuan commented Feb 5, 2022

LGTM

@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2022

There was an issue merging, maybe try again yeonjuan. Details

@bumkeyy
Copy link
Contributor Author

bumkeyy commented Feb 13, 2022

LGTM

@github-actions
Copy link
Contributor

There was an issue merging, maybe try again bumkeyy. Details

Co-authored-by: Seohee Park <dvlprsh103@gmail.com>
@bumkeyy
Copy link
Contributor Author

bumkeyy commented Mar 5, 2022

LGTM

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2022

There was an issue merging, maybe try again bumkeyy. Details

@bumkeyy
Copy link
Contributor Author

bumkeyy commented Sep 27, 2022

LGTM

@github-actions
Copy link
Contributor

There was an issue merging, maybe try again bumkeyy. Details

@bumkeyy
Copy link
Contributor Author

bumkeyy commented Nov 22, 2022

LGTM

@github-actions
Copy link
Contributor

There was an issue merging, maybe try again bumkeyy. Details

@bumkeyy
Copy link
Contributor Author

bumkeyy commented Dec 19, 2022

LGTM

@github-actions
Copy link
Contributor

There was an issue merging, maybe try again bumkeyy. Details

@bumkeyy
Copy link
Contributor Author

bumkeyy commented Dec 19, 2022

@orta

If you have time, please check this merge issue. #131 (comment)
Thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants