티스토리 뷰
공부를 시작하며
타입스크립트에서의 함수 표현을 어떻게 하는지에 대해서 알아봅니다.
타입스크립트 문서를 통해서 공부 했습니다.
레퍼런스 : https://www.typescriptlang.org/docs/handbook/2/functions.html
Documentation - More on Functions
Learn about how Functions work in TypeScript.
www.typescriptlang.org
[ 함수 타입 표현식 (Function Type Expressions) ]
함수를 매개변수를 통해서 넘기게 되면 다음과 같이 타입을 지정할 수 있습니다.
function functionTypeExpressions(fn: (v: string) => void) {
fn("this is function type expressions!!");
}
function sendFunction(v: string) {
console.log(v);
}
functionTypeExpressions(sendFunction);
함수의 타입 표현식은 (v: string)=>void 와 같은 형식으로 void 가 의미하는 것은 return 문과 같이 반환되는 값이 아무것도 없음을 의미합니다. 여기서 (v)=>void 와 같이 매개변수 값에 아무런 타입을 지정해주지 않으면 자동적으로 any가 지정되게 됩니다.
위의 함수 타입 표현식을 다음과 같이 타입 별칭을 이용해서 지정해 줄 수도 있습니다.
type FnType = (v: string) => void;
function functionTypeExpressions(fn: FnType) {
fn("this is function type expressions!!");
}
[ Call Signatures ]
함수에 타입을 지정하여 사용할 수 있습니다. 일반적인 (매개변수:매개변수 타입)=> 반환 타입 이 아닌 : 를 사용해서 지정이 가능합니다.
아래와 같이 콜백 함수와 같이 사용하고 어떤 값이 반환 될 지 유추가 가능합니다.
type PointToRect = {
(reduce: number): void;
};
function pointReduce(x: number, y: number, reduceFunction: PointToRect) {
reduceFunction(x + y);
}
pointReduce(10, 20, (reduceNum) => console.log(reduceNum));
// console.log => 30
[ Construct Signatures ]
new 생성자를 통해 구조를 만들 때에도 시그니처를 통해 타입을 지정해 줄 수 있습니다.
사용 방법은 new (매개변수:타입): 타입 입니다.
다음 예제에서는 사람의 객체를 만드는 것을 형상화하는 이름을 받아 인스턴스를 생성해 주는 예시입니다.
type IHuman = {
name: string;
};
type IHumanConstructor = {
new (name: string): IHuman;
};
class Human implements IHuman {
age: number;
constructor(public name: string) {
this.age = 0;
}
}
function bornHuman(classes: IHumanConstructor, name: string) {
// .... do something
return new classes(name);
}
const newHuman = bornHuman(Human, "asdlkj");
console.log("newHuman: ", newHuman);
// Human { name:'Jone', age:0 }
Javascript like Date Object
또한 자바스크립트에서 Date 와 같은 객체를 consturct signatures 형식으로 타입을 지정해줄 수 있습니다.
interface DateObject {
new (s: string): Date;
}
다음과 같이 제네릭 타입을 사용해서 반환되는 값을 추론할 수 있습니다.
변수를 생성하면 타입스크립트는 변수를 할당하는 과정에서 바로 타입을 추론하여 지정합니다. 변수를 함수에 넘기게 되면 지정된 타입을 제네릭 타입과 같이 사용하여 추론하게 됩니다.
function lastArray<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
const testArr = ["wow", "oh", "test"];
const resultArr = lastArray(testArr);
console.log(resultArr);
// 출력 : test
추론
타입스크립트가 제네릭 타입을 통해서 타입을 추론하는 과정은 다음 예시를 보면 이해할 수 있습니다.
따로 customMap 함수 내에서는 지정된 타입없이 오직 제네릭 타입을 통해서 모든 타입을 추론하는 것을 확인할 수 있습니다.
function customMap<Input, Output>(
array: Input[],
fn: (arg: Input) => Output
): Output[] {
return array.map(fn);
}
const stringNumArr = ["0", "1", "2", "3"];
const stringNumArrParseToNumArr = customMap(stringNumArr, (item) =>
parseInt(item)
);
console.log(stringNumArrParseToNumArr);
// 출력 : [0, 1, 2, 3]
인수 타입 지정
다음과 같이 두 배열을 합쳐주는 함수가 있습니다. 하지만 아래의 함수는 호출과정에서 다음과 같이 지정하면 오류가 납니다. 첫번째 인자로 넘긴 number[] 타입이 제네릭 타입의 기준이 되어 string 을 number 타입에 지정할 수 없다고 표시됩니다.
Before
function combineArr<T>(arrA: T[], arrB: T[]): T[] {
return arrA.concat(arrB);
}
combineArr([1, 2, 3], ["string"]);
// Type 'string' is not assignable to type 'number'.
After
이때엔 다음과 같이 유니언 타입으로 지정해주면 오류를 피할 수 있습니다.
combineArr<number | string>([1, 2, 3], ["string"]);
[ 선택적 매개변수 (Optional Parameters) ]
매개변수를 항상 받고 싶지 않을 때도 있을겁니다. 그럴때 매개변수의 타입 : 의 전에 ? 를 붙이면 선택적으로 받을 수 있습니다.
// ? 를 붙임으로써 선택적으로 매개변수를 받을 수 있습니다.
function func(v?: string) {
// .....
if (v) {
v.toLocaleUpperCase();
} else {
// ...
}
}
func("string value");
func();
기본값을 제공하기
파라미터에 기본값을 제공하면 타입이 자동으로 추론됩니다.
// 기본 파라미터값을 제공해 줄 수도 있습니다.
function func(v = "default") {
// ....
}
func("test");
func();
선택적 매개변수의 undefined 허용
다음과 같이 선택적 매개변수는 undefined 를 인자로 넘겨도 오류를 표시해주지 않습니다.
? 를 통해 선택적 매개변수 타입을 선언하면 타입 | undefined 와 같이 선언되기 때문입니다.
대신 null 값은 조금 다른데요, 다음과 같이 tsconfig.json을 통해서 strictNullChecks 옵션의 여부에 따라 null 값을 허용해주거나 허용 안해줍니다.
declare function func(v?: string): void;
func();
func("default");
func(undefined);
// strictNullChecks : false
func(null);
// strictNullChecks : true
func(null);
// Argument of type 'null' is not assignable to parameter of type 'string | undefined'.
실수하기 쉬운 콜백의 선택적 매개변수
다음과 같이 함수로 각 배열의 아이템과 인덱스를 콜백의 매개변수 값으로 받을 수 있게 작성하였습니다.
function forEach(
array: any[],
callback: (item: any, index?: number) => void
) {
for (let i = 0; i < array.length; i++) {
callback(array[i], i);
}
}
forEach([1, 2, 3], (item) => console.log(item));
forEach([1, 2, 3], (item, index) => console.log(item, index));
하지만 위와 같이 작성하게 되면 다음과 같이 선택적으로 받는 매개변수인 index 값은 undefined도 반환할 수 있다 판단하여 매서드를 사용하거나 해당 index 값을 사용하려 하면 오류가 표시됩니다.
forEach([1, 2, 3], (item, index) => index.toFixed());
// Object is possibly 'undefined'.
위의 forEach 함수의 구현부에서는 다음과 같이 index 매개변수가 없을 수도 있다고 판단하기 때문입니다.
for (let i = 0; i < array.length; i++) {
// index 매개 변수가 인자값으로 없을 수도 있습니다.
callback(array[i]);
}
* 따라서 타입스크립트에서는 콜백함수를 작성하게되면 매개변수에는 선택적 매개변수는 지양하라고 설명합니다.
[ 오버로드 서명 및 구현 서명 ( overload signatures ) ]
다음과 같이 함수의 타입을 다중으로 오버로드로 작성할 수 있습니다.
이때 함수는 내부의 서명은 2개이상 오버로드 된다면 볼 수 없으며 +?? overload 와 같이 표시만 됩니다.
function getLength(v: string): number;
function getLength(v: number[]): number;
function getLength(v: any) {
return v.length;
}
getLength("ddd");
// 다음과 같이 함수 구현에 사용되는 서명은 외부에서 볼 수 없습니다.
// function getLength(v: string): number (+1 overload)
getLength([0, 1, 2, 3]);
또한 구현부 함수에서의 서명은 오버로드에서의 서명과 일치해야 합니다.
다음과 같이 구현부에서의 서명과 오버로드의 서명이 일치하지 않았을때에 두 서명이 일치하지 않는다는 오류가 표시되게 됩니다.
function func(v: string): void;
// This overload signature is not compatible with its implementation signature.
function func(v: boolean): void;
function func(v: number) {}
* 유니언 타입을 쓰자!
이러한 구현하기 힘든 타입의 특성 때문에 타입스크립트 문서에서는 유니언(Union) 타입을 사용하라고 권장하고 있습니다.
function func(v: string | number) {
// ....
}
[ 다른 유형들 ]
void
함수에서 return 문에 아무 값도 반환하지 않았을땐 undefined 가 아닌 void 로 분류됩니다.
function nothing() {
return;
}
// void
function thisIsSameNothingFunction(): void {
return;
}
unknown
any 타입과 unknown 타입이 있습니다. any 는 모든 타입을 다 허용하여 매개변수에 매서드가 있는지 없는지 알 수 없어도 타입 오류가 발생하지 않고 unknown 는 타입 오류가 발생합니다. 따라서 any 보다는 unknown 이 더 안전합니다.
function funcAny(v: any) {
v.draw();
}
function funcUnknown(v: unknown) {
v.draw();
// Object is of type 'unknown'
}
never
함수에서 값을 반환하지 않고 throw 를 통해서 프로그램 실행을 종료할 때 never의 타입을 지정합니다.
function customError(message: string): never {
// ....
throw new Error(message);
}
유니언 타입에서 아무 값도 반환되지 못할 때 never 타입이 나타납니다.
function neverUnions(v: string | number) {
if (typeof v === "string") {
//...
console.log(`${v} is string`);
} else if (typeof v === "number") {
console.log(`${v} is number`);
} else {
//....
v;
// (parameter) v: never
}
}
Function
Function 타입을 사용하면 자바스크립트에 bind, call, apply 등 프로퍼티가 있는 함수 타입을 지정해 줄 수 있습니다.
하지만 반환되는 타입이 무엇인지 알 수 없는 any로 지정이 되기 때문에 ()=> void 와 같이 함수를 명시적으로 타입을 지정해주는 것이 더 좋습니다.
// Before
function functionType(fnc: Function) {
return fnc(1, 2, 3);
}
// function functionType(fnc: Function): any
// After
function voidFunctionType(fnc: (n: number) => void) {
return fnc(1);
}
// function voidFunctionType(fnc: (n: number) => void): void
나머지 매개변수와 인수 ( Rest Parameters and Arguments )
다음과 같이 나머지 매개변수에 대한 타입을 선언할 수 있습니다. 나머지 변수는 배열 타입으로 받게되어 배열 메서드를 사용할 수 있습니다.
function increaseArrayNum(increaseNum: number, ...array: number[]) {
console.log(array);
// [1, 2, 3, 4, 5]
return array.map((v) => increaseNum + v);
}
const result = increaseArrayNum(5, 1, 2, 3, 4, 5);
console.log(result);
// [6, 7, 8, 9, 10]
나머지 인수
자바스크립트에서 ... 스프레드 구문을 사용하면 배열 또는 객체 내부의 값들을 펼쳐서 제공할 수 있습니다.
다음과 같이 메서드에서 두 인자 값을 받는 상황에서 스프레드 구문을 사용하면 튜플 타입을 사용해야한다고 오류가 발생합니다.
const atn2 = [1, 2];
const angle = Math.atan2(...atn2);
// A spread argument must either have a tuple type or be passed to a rest parameter
여러가지 방법이 있겠지만 다음과 같은 방법으로 해결이 가능합니다.
튜플 타입 tuple type
const atn2: [number, number] = [1, 2];
const angle = Math.atan2(...atn2);
상수 단언 as const
일반적으로 const 를 사용해서 단언하는 것이 간단한 해결책입니다.
const atn2 = [1, 2] as const;
const angle = Math.atan2(...atn2);
매개변수의 분해 ( Parameter Destructuring )
매개변수에 객체를 전달할 때 다음과 같이 구조를 분해해서 타입을 지정해줍니다.
function printUser({
name,
age,
country,
}: {
name: string;
age: number;
country: string;
}) {
console.log(
`my name is ${name}. I'm ${age} years old and I live in ${country}.`
);
}
printUser({ name: "Jone", age: 20, country: "South Korea" });
타입 별칭을 통해서 타입을 지정해 줄 수도 있습니다.
type User = {
name: string;
age: number;
country: string;
};
function printUser({ name, age, country }: User) {
console.log(
`my name is ${name}. I'm ${age} years old and I live in ${country}.`
);
}
'Typescript' 카테고리의 다른 글
[ Typescript ] Object 타입스크립트에서의 객체 타입 (0) | 2022.09.09 |
---|---|
[ Typescript ] Narrowing 좁히기 (0) | 2022.09.05 |
[Typescript] 타입 스크립트의 기본 (0) | 2022.09.03 |
[Typescript] 타입스크립트 핸드북 시작하기 (0) | 2022.09.03 |