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

Fix Typo in More on Functions.md #213

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

Conversation

Seohyun-Roh
Copy link

Destructing -> Destructuring

@github-actions
Copy link
Contributor

github-actions bot commented Mar 8, 2023

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.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 8, 2023

Translation of More on Functions.md

title: More on Functions
layout: docs
permalink: /ko/docs/handbook/2/functions.html

oneline: "Let's see how functions work in TypeScript."

Whether it's a local function, a function loaded from another module, or a method of any class, a function is a fundamental component of any application.
A function is a value. And like other values, there are many ways to describe how functions can be called in TypeScript.
Let's see how to write types that describe functions.

Function type expressions

The simplest way to describe a function is to Function type expressions Is.
This type is grammatically similar to the arrow function.

function greeter(fn: (a: string) => void) {
  fn("Hello, World");
}

function printToConsole(s: string) {
  console.log(s);
}

greeter(printToConsole);

(a: string) => void The syntax is "string type" aas a parameter and has no return value".
Like a function declaration, if the parameter is not typed, implicitly anywill be.

If the parameter name is essential Keep in mind. Function Type (string) => voidThe "any With type stringA function with a parameter named

Of course, you can use type aliases to name types in functions.

type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
  // ...
}

Call Signature

In JavaScript, functions can not only be called, but also have properties.
However, the syntax of function type expressions does not allow you to define properties.
If we try to describe something that is callable and has properties, the object type Call Signature can be expressed using

type DescribableFunction = {
  description: string;
  (someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
  console.log(fn.description + " returned " + fn(6));
}

This syntax is different from function type expressions. Between the parameter type and the type of the return value =>Not :must be used.

Configuration Signatures

JavaScript functions are newIt can also be called through operators.
TypeScript is mainly used to create new objects. constructor Considered to be.
You are in front of the call signature. new By attaching keywords, Configuration Signatures.

type SomeObject = any;
// ---cut---
type SomeConstructor = {
  new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
  return new ctor("hello");
}

JavaScript Date Some objects, such as objects, are newcan be called with or without .
You can combine call signatures and configuration signatures in the same type at will.

interface CallOrConstruct {
  new (s: string): Date;
  (n?: number): number;
}

Generic functions

It is common to write a function in a form where the input value is related to the type of the output value, or that the type of the two inputs is related to each other.
Let's consider for a moment a function that returns the first element of an array.

function firstElement(arr: any[]) {
  return arr[0];
}

The function does its job, but unfortunately the return type any Is.
It would be better if the function returned the type of the array element.

In TypeScript, Generic Grammars are used to express the correlation between two values.
We select the function signature from _Type Parameters_You can make that expression by declaring.

function firstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}

Type Parameters Typeby declaring it to this function, and using it where we need it, we created a link between the function's input (array) and output (return value).
Now when we call this function, we get a clearer type.

declare function firstElement<Type>(arr: Type[]): Type | undefined;
// ---cut---
// s는 "string" 타입
const s = firstElement(["a", "b", "c"]);
// n은 "number" 타입
const n = firstElement([1, 2, 3]);
// u는 "undefined" 타입
const u = firstElement([]);

Inference

In this example, we have TypePlease note that it is not specified.
where the type is It has been deduced That is, it was automatically selected by TypeScript.

We can also use multiple type parameters.
For example, mapThe standalone version of will look like this.

// prettier-ignore
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
  return arr.map(func);
}

// 매개변수 'n'의 타입은 'string' 입니다.
// 'parsed'는 number[] 타입을 하고 있습니다.
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

In this example, TypeScript is Input Type and (given as input string from array) OutputChange the type to the return value of the function expression (number) to see what you can deduce.

Type constraints

We are all We have written generic functions that operate on types.
Sometimes, we want to relate two values, but only want them to work on a subset of certain values.
In these cases, we have _Type constraints_to limit the types that the type parameter can accept.

Let's write a function that returns the longer of the two values.
For this task, the number is length Requires a property.
extendsUse the clause to change the type parameter to the type restriction I can.

// @errors: 2345 2322
function longest<Type extends { length: number }>(a: Type, b: Type) {
  if (a.length >= b.length) {
    return a;
  } else {
    return b;
  }
}

// longerArray 의 타입은 'number[]' 입니다'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString 의 타입은 'alice' | 'bob' 입니다.
const longerString = longest("alice", "bob");
// 에러! Number에는 'length' 프로퍼티가 없습니다.
const notOK = longest(10, 100);

In this example, there are a few interesting things to note.
We have TypeScript longestThe return type of inference I allowed it to be.
Return type inference also works with generic functions.

We Typeto { length: number }Since we were limited to, we aand b About the parameters .length I was able to access the property.
Without type restrictions, these values could be of other types that did not have a length property, so they would not have access to those properties.

longerArrayand longerStringThe type of was inferred based on the arguments.
Remember that generics are when you associate two or more values with the same type!

In the end, as we want longest(10,100)silver numberType .length You can see that the call was rejected because it didn't have the property.

Working with restricted values

The following are common mistakes to make when using generic type constraints:

// @errors: 2322
function minimumLength<Type extends { length: number }>(
  obj: Type,
  minimum: number
): Type {
  if (obj.length >= minimum) {
    return obj;
  } else {
    return { length: minimum };
  }
}

This function appears to have no problems. Typesilver { lenght: number }, and the function is Typeor a value that satisfies that constraint.
The problem is that this function satisfies the constraints How Input, not an object How The point is that it returns an object.
If this is valid, you can write the following code that will definitely not work.

declare function minimumLength<Type extends { length: number }>(
  obj: Type,
  minimum: number
): Type;
// ---cut---
// 'arr' gets value { length: 6 }
const arr = minimumLength([1, 2, 3], 6);
// 여기서 배열은 'slice' 메서드를 가지고 있지만
// 반환된 객체는 그렇지 않기에 에러가 발생합니다!
console.log(arr.slice(0));

Specifying Type Arguments

TypeScript usually infers the intended type from generic calls, but this is not always the case.
For example, suppose you have written a function that combines two arrays.

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2);
}

In general, it would be wrong to call that function with an unpaired array.

// @errors: 2322
declare function combine<Type>(arr1: Type[], arr2: Type[]): Type[];
// ---cut---
const arr = combine([1, 2, 3], ["hello"]);

If you intended something like this, you can manually Typemust be specified.

declare function combine<Type>(arr1: Type[], arr2: Type[]): Type[];
// ---cut---
const arr = combine<string | number>([1, 2, 3], ["hello"]);

Guidelines for writing good generic functions

Writing generic functions can be fun, and using type parameters can be easy.
Using too many type parameters or constraints where they aren't essential can make poor reasoning and frustrate your function callers.

Pressing Type Parameters

Here's how to write two functions that look similar.

function firstElement1<Type>(arr: Type[]) {
  return arr[0];
}

function firstElement2<Type extends any[]>(arr: Type) {
  return arr[0];
}

// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);

At first glance, it may look the same, firstElement1This is a better way to write this function.
The inferred return type of this function is Type is, firstElement2The inferred return type of is more likely to be at the time of the call, rather than "waiting" for TypeScript to interpret the type during the call. arr[0] Because expressions are interpreted using type constraints. anywill be.

rule: If possible, use the type parameter itself rather than constraining the type parameter.

Using fewer type parameters

Here is another pair of similar functions:

function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
  return arr.filter(func);
}

function filter2<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] {
  return arr.filter(func);
}

We are Do not associate two values Type Parameters Funchas been created.
This is not a good idea, because callers who want type arguments have to provide additional type arguments for no reason.
Funconly makes the function harder to read and understand, does nothing!

rule: Always use the minimum type parameter if possible

The type parameter must appear twice.

Sometimes we overlook the fact that functions may not need generics.

function greet<Str extends string>(s: Str) {
  console.log("Hello, " + s);
}

greet("world");

We could have easily written a simple version.

function greet(s: string) {
  console.log("Hello, " + s);
}

The type parameter is _Associating multiple value types_Please remember to use it for the purpose of doing so.
If a type parameter is used only once in a function signature, it is not associated with anything.

rule: If the type parameter only comes from one place, think again about whether you really need it.

Optional Parameters

Functions used in JavaScript often take a variable number of arguments.
For example, numberof toFixed The method optionally takes a number of digits.

function f(n: number) {
  console.log(n.toFixed()); // 0 arguments
  console.log(n.toFixed(3)); // 1 argument
}

In TypeScript, we can select the parameters ?By marking with _Optional_Can be made with:

function f(x?: number) {
  // ...
}
f(); // OK
f(10); // OK

If the type of the parameter is number, but not specified in JavaScript, is undefinedBecause it becomes, x The parameters are substantially number | undefined It will be a type.

You can use the parameters _Default_We can also provide it.

function f(x = 10) {
  // ...
}

now fWithin the body of all undefined Argument 10Because it is replaced by xThe type of is numberwill be.
When the parameter is optional, the caller undefined, you can mimic the "missing" argument.

declare function f(x?: number): void;
// cut
// All OK
f();
f(10);
f(undefined);

Optional parameters in callback functions

Once you know about optional parameters and function type expressions, it's easy to make the following mistakes when writing a function that invokes a callback:

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
}

People index?When you write to use a XmlReader as an optional parameter, what you usually intend is to want both calls to be valid.

// @errors: 2532
declare function myForEach(
  arr: any[],
  callback: (arg: any, index?: number) => void
): void;
// ---cut---
myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));

indeed What this means is that callbackcan be called with one argument Is.
In other words, the previous function definition is equivalent to saying that the implementation might look like this:

// @errors: 2532
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    // 오늘은 index를 제공하고 싶지 않아
    callback(arr[i]);
  }
}

Eventually, TypeScript forces these semantics and throws an error that wouldn't actually happen.

// @errors: 2532
declare function myForEach(
  arr: any[],
  callback: (arg: any, index?: number) => void
): void;
// ---cut---
myForEach([1, 2, 3], (a, i) => {
  console.log(i.toFixed());
});

In JavaScript, if it is called by passing more arguments than specified as a parameter, the remaining arguments are simply ignored.
TypeScript behaves the same way.
A function with fewer parameters (with the same type) can replace a function with more parameters.

When creating a function type for a callback, without the corresponding arguments Call Unless there is an intention, never Do not use optional parameters.

Function overload

Some JavaScript functions can be called with a variety of argument counts, types.
For example, you can Date, take one timestamp (one argument), and take the month/day/year format (three arguments).

In TypeScript, we have functions that can be called in different ways. _Overlord Signature_can be described by writing.
To do this, you can write down a few function signatures (usually two or more) and then create the function body.

// @errors: 2575
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);

In this example, we have created two overloads. One receives one argument, and the other receives three arguments.
These two signatures were written at the beginning. Overlord Signature It is called.

Then, we wrote a function implementation with compatible signatures.
The function is Implement It has a signature, but it cannot be called directly.
Although we have created two optional parameters after the essential parameters, we cannot call this function with only two parameters!

Overlord Signatures and Implementation Signatures

This is a common source of confusion.
People often write code like below, and sometimes they don't understand why there is an error.

// @errors: 2554
function fn(x: string): void;
function fn() {
  // ...
}
// 0개의 인자로 호출하기를 예상했음
fn();

Again, the signature used to create the function body is "invisible" from the outside.

_Implement_is not visible from the outside.
When you write an overloaded function, you _More than one_must be written above the function implementation.

In addition, the implementation signature is an overloaded signature and Must be compatible The.
For example, the following functions have errors because their implementation signatures do not correctly match the overloads.

// @errors: 2394
function fn(x: boolean): void;
// 인수 타입이 옳지 않습니다.
function fn(x: string): void;
function fn(x: boolean) {}
// @errors: 2394
function fn(x: string): string;
// 반환 타입이 옳지 않습니다.
function fn(x: number): boolean;
function fn(x: string | number) {
  return "oops";
}

Writing good overloads

As with generics, there are some guidelines to follow when writing function overloads.
Following these rules will make your functions easy to call, understand, and implement.

Consider a function that returns the length of a string or array.

function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
  return x.length;
}

This function is fine. We can call this function via string or array.
However, since TypeScript only interprets a function through a single overload, we can use this function as a string or It cannot be called through a value that can be an array.

// @errors: 2769
declare function len(s: string): number;
declare function len(arr: any[]): number;
// ---cut---
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);

Since both overloads have the same number of arguments and the same return type, we can write the following in the form of an unoverloaded function:

function len(x: any[] | string) {
  return x.length;
}

Much better!
Callers can call this function with either value, and additionally eliminate the need to find the exact implementation signature.

If possible, use union types instead of overloads

Within a function this Declaring

TypeScript is a function within thisis inferred through code flow analysis as shown in the example below.

const user = {
  id: 123,

  admin: false,
  becomeAdmin: function () {
    this.admin = true;
  },
};

TypeScript is a function user.becomeAdminThis external object userEquivalent to thisI understand that you have. Usually this may be enough, but you thisYou'll often need more control over what objects represent. The JavaScript specification states that thisBecause it says that you cannot have a parameter named , TypeScript uses that grammar space in the function body thisPermits it to be used to define the type of .

interface User {
  id: number;
  admin: boolean;
}
declare const getDB: () => DB;
// ---cut---
interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

This pattern is commonly used in callback-style APIs that control when other objects call the function. To achieve this effect, you need to use the arrow function, not function You must use keywords.

// @errors: 7041 7017
interface User {
  id: number;
  isAdmin: boolean;
}
declare const getDB: () => DB;
// ---cut---
interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const db = getDB();
const admins = db.filterUsers(() => this.admin);

Other types you need to know

When working with function types, there are a few more additional types that often appear.
Like all types, you can use them anywhere, but they are particularly relevant to the context of functions.

void

voidmeans the return value of a function that does not return a value.
To the function returnIf there is no statement, or when it does not explicitly return a value, it is the type that is deduced.

// 추론된 반환 타입은 void 입니다.
function noop() {
  return;
}

In JavaScript, functions that return nothing implicitly undefined Returns a value.
But in TypeScript voidand undefinedis not considered the same.
We'll cover them in detail at the end of this chapter.

voidThe undefinedis not the same as.

object

Special type objectis the raw value (string, number, bigint, boolean, symbol, null, undefined) for any value.
This is Empty Object Type { }, global type ObjectIt's also different.
Perhaps you ObjectYou won't be able to use it.

objectThe ObjectIt's not. All the time objectUse it!

In JavaScript, a function value is an object. It has properties, and in the prototype chain Object.prototypeThere is, instanceof ObjectIn the meantime, Object.keys, etc.
For this reason, in TypeScript, function types are objectis considered.

unknown

unknown The type is _All Values_Indicates .
any It is similar to a type, unknown It is safer to assign something to a type because it is not valid.

// @errors: 2571
function f1(a: any) {
  a.b(); // OK
}
function f2(a: unknown) {
  a.b();
}

This means that any Since you can express a function that accepts any value without using the value of the type in the function body, it is useful for describing function types.

Conversely, you can express a function that returns a value of type unknown.

declare const someRandomString: string;
// ---cut---
function safeParse(s: string): unknown {
  return JSON.parse(s);
}

// 'obj'를 사용할 때 조심해야 합니다!
const obj = safeParse(someRandomString);

never

Some functions are Never does not return a value

function fail(msg: string): never {
  throw new Error(msg);
}

never Type means a value that can never be observed.
In the return type, it means that the function raises an exception or terminates program execution.

neveralso appears when TypeScript determines that there is nothing left in the union.

function fn(x: string | number) {
  if (typeof x === "string") {
    // do something
  } else if (typeof x === "number") {
    // do something else
  } else {
    x; // 'never' 타입이 됨!
  }
}

Function

Global Type Functionsilver bind, call, apply It is used to describe other properties in JavaScript function values.
In addition, this includes FunctionThe value of the type has a value that can always be called, and such a call is anyReturns.

function doSomething(f: Function) {
  return f(1, 2, 3);
}

This means that Untyped function calls Unsafe, any It is generally best to avoid returning types.

If you need to allow an arbitrary function, but don't intend to call it, () => void The type is generally safer.

The rest of the parameters and arguments

배경지식 읽기:
나머지 매개변수(Rest Parameter)
전개 구문(Spread Syntax)

Rest Parameters

We can use optional parameters and overloads to accept a variety of definite arguments, but we Indeterminate A function that accepts arguments of a number _Remaining parameters_can be defined using

The remaining parameters appear after all other parameters. ... syntax.

function multiply(n: number, ...m: number[]) {
  return m.map((x) => n * x);
}
// 'a' gets value [10, 20, 30, 40]
const a = multiply(10, 1, 2, 3, 4);

In TypeScript, type notation for these parameters is implicitly anyNot any[], and the type expression is Array<T> or T[] Or it should be expressed as a tuple type (which we will learn later).

Rest Argument

On the contrary, we use the unfolding syntax to change the number of arguments supplied in the array to _offer_I can.
For example, of push A method can accept any number of arguments.

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);

In general, TypeScript does not consider arrays immutable.
This can result in the following surprising behavior:

// @errors: 2556
// 추론된 타입은 0개 이상의 숫자를 가지는 배열인 number[]
// 명시적으로 2개의 숫자를 가지는 배열로 간주되지 않습니다
const args = [8, 5];
const angle = Math.atan2(...args);

The best solution for this situation depends on your code, but in general const Context is the simplest solution.

// 길이가 2인 튜플로 추론됨
const args = [8, 5] as const;
// OK
const angle = Math.atan2(...args);

Using the remaining arguments is important when targeting an older runtime, downlevelIterationYou may need to.

Parameter Destructuring

배경지식 읽기:
구조분해 할당

You can use parameter decomposition to conveniently unpack the object supplied as an argument into one or more local variables in the function body.
In JavaScript, it looks like the form below.

function sum({ a, b, c }) {
  console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });

The type notation for the object is placed after the decomposition statement.

function sum({ a, b, c }: { a: number; b: number; c: number }) {
  console.log(a + b + c);
}

It may seem a bit wordy, but you can use any named type here as well.

// 이전 예제와 동일
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
  console.log(a + b + c);
}

Assignmentability of functions

void Return Type

hydrous void Return types can cause some uncommon but predictable behavior.

void Contextual typing into a return type returns nothing to the function Avoid Not forced IsAnother way to explain this is, void A contextual function type with a return type (type vf = () => void) is implemented, _No value_or can be returned, but is ignored.

Therefore, the type to be described later () => voidare all valid.

type voidFunc = () => void;

const f1: voidFunc = () => {
  return true;
};

const f2: voidFunc = () => true;

const f3: voidFunc = function () {
  return true;
};

And when the return value of these functions is assigned to another variable, they still voidwill keep the type.

type voidFunc = () => void;

const f1: voidFunc = () => {
  return true;
};

const f2: voidFunc = () => true;

const f3: voidFunc = function () {
  return true;
};
// ---cut---
const v1 = f1();

const v2 = f2();

const v3 = f3();

Because this behavior exists, Array.prototype.pushreturns number, Array.prototype.forEach Method void Even if you expect a function with a return type, the following code might be valid:

const src = [1, 2, 3];
const dst = [0];

src.forEach((el) => dst.push(el));

There is one other case to keep in mind. If the literal function definition is void If it has a return value, the function should not return anything. Not.

function f2(): void {
  // @ts-expect-error
  return true;
}

const f3 = function (): void {
  // @ts-expect-error
  return true;
};

voidFor more information, refer to the following document items.

Generated by 🚫 dangerJS against 93d4496

Copy link
Contributor

@yeonjuan yeonjuan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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.

None yet

2 participants