자바스크립트에서 함수는 function 키워드와 화살표 ⇒ 기호로 만드는 두 가지 방법이 있다. 타입스크립트 함수는 이를 바탕으로 타입 기능을 추가한 것이다. 이번 장은 함수를 효과적으로 구현하는 방법과 클래스의 메서드를 구현하는 방법을 알아보자.
4-1 함수 선언문
자바스크립트에서 함수는 function 키워드로 만드는 함수와 ⇒ 기호로 만드는 화살표 함수 두 가지 방법이 있다. 다음은 function 키워드로 만드는 함수 구조이다.
function 함수이름(매개변수1, 매개변수2[,...]){
body
}
타입스크립트 함수 선언문은 자바스크립트 함수 선언문에서 매개변수와 함수 반환 값에 타입 주석을 붙이는 형태로 구성된다.
function 함수이름(매개변수:타입1, 매개변수:타입2[,...]):반환값 타입{
body
}
위 코드를 토대로 예를 들면
function add(a: number, b: number): number{
return a + b;
}
매개변수와 반환값의 타입 주석 생략
변수 때와 마찬가지로 함수 선언문에서도 매개변수와 반환값에 대한 타입 주석을 생략할 수 있다. 다만, 함수의 매개변수 타입과 반환 타입을 생략하는 것은 바람직하지 않는다. 타입이 생략되어 있으면 함수의 구현 의도를 알기 어렵고 잘못 사용할 수 있기 때문이다.
void 타입
값을 반환하지 않는 함수는 반환 타입이 void이다. void 타입은 함수 반환 타입으로만 사용할 수 있다.
function printMe(name: string, age: number): void {
console.log(`name: ${name}, age: ${age}`)
}
함수 시그니처
변수에 타입이 있듯이 함수 또한 타입이 있다. 이러한 함수의 타입을 함수 시그니처라고 한다. 함수의 시그니처는 다음과 같은 형태로 표현한다.
(매개변수1 타입, 매개변수2 타입[,...]) => 반환값 타입
let printMe: (string, number) => void = function(name: string, age: number): void{}
printMe 함수는 string과 number 타입의 매개변수가 두 개 있고 반환 타입이 void이다. 따라서 함수 시그니처는 (string, number) ⇒ void 이다.
만약, 매개변수가 없으면 단순히 ()로 표현한다. 즉, () ⇒ void는 매개변수와 반환값이 없는 함수 시그니처이다.
type 키워드로 타입 별칭 만들기
타입스크립트는 type이라는 키워드를 제공한다. type 키워드는 기존에 존재하는 타입을 단순히 이름만 바꿔서 사용할 수 있게 해준다. 이러한 기능을 타입 별칭(type alias)라고 한다.
type 새로운 타입 = 기존 타입
type stringNumberFunc = (string, number) => void;
let f: stringNumberFunc = function(a: string, b:number):void {}
let g: stringNumberFunc = function(c: string, d:number):void {}
- (string, number) ⇒ void 함수 시그니처를 stringNumberFunc이라는 이름으로 타입 별칭을 만든다.
- 변수 f와 g에 타입 주석을 더 수월하게 붙일 수 있다.
함수 시그니처를 명시하면 매개변수의 개수나 타입, 변환 타입이 다른 함수를 선언하는 잘못을 미연에 방지할 수 있다.
undefined 관련 주의 사항
undefined 타입은 타입스크립트의 타입 계층도에서 모든 타입 중 최하위 타입이다. undefined 타입을 고려하지 않으면
interface INameable {
name: string
}
function getName(o: INameable) {return o.name}
let n = getName(undefined) // error
console.log(n);
- o.name 부분이 undefined.name이 되어 “cannot read property name of undefined”라는 오류가 발생한다.
- 방지하려면 매개변수 값이 undefined인지 판별하는 코드를 작성해야 한다.
interface INameable {
name: string
}
// function getName(o: INameable) {
function getName(o: INameable | null | undefined) {
return o != undefined ? o.name : 'unknown name'
}
let n = getName(undefined)
console.log(n) // unknown name
console.log(getName({ name: 'Jack' })) // Jack
- getName 함수의 몸통은 매개변수 o의 값이 undefined일 때를 고려한 것
interface IAgeable {
age?: number
}
//function getAge(o: IAgeable) {
function getAge(o: IAgeable | null | undefined) {
return o != undefined && o.age ? o.age : 0
}
console.log(getAge(undefined)) //0
console.log(getAge(null)) //0
console.log(getAge({ age: 32 })) // 32
선택적 매개변수
함수의 매개변수에도 다음처럼 이름 뒤에 물음표를 붙일 수 있으며, 이를 선택적 매개변수라고 한다.
function fn(arg1: string, arg?: number): void {}
선택적 매개변수는 함수 호출을 모두 가능하게 하고 싶을때 사용한다.
function fn(arg?: number) {
console.log(`arg: ${arg}`)
}
fn(1) // 1
fn() // undefined
// type OptionalArgFunc = (string, number?) => void
type OptionalArgFunc = (arg?: number) => void
let h: OptionalArgFunc = fn
4-2 함수 표현식
함수는 객체다
자바스크립트는 함수형 언어 “스킴”과 프로토타입 기반 객체지향 언어 “셀프”를 모델로 만들어졌다. 따라서 자바스크립트는 객체지향 언어와 함수형 언어의 특징이 모두 있다.
let add = new Function('a', 'b', 'return a + b;')
let result = add(1, 2)
console.log(result) // 3
add 함수는 다음과 같은 형태로도 구현할 수 있다.
//let add2 = function(a, b) {
let add2 = function (a: number, b: number) {
return a + b
}
console.log(add2(1, 2)) //3
- 이처럼 함수 선언문에서 함수 이름을 제외한 코드를 함수 표현식이라고 한다.
- 함수 표현식은 함수형 언어의 핵심 기능이다.
일등함수
프로그래밍 언어가 일등 함수 기능을 제공하면 함수형 프로그래밍 언어라고 한다. 일등 함수란 함수와 변구를 구분 하지 않는다는 의미이다.
//let f = function(a, b) { return a + b; }
// f = function(a, b) { return a - b; }
let f = function (a: number, b: number) {
return a + b
}
f = function (a: number, b: number) {
return a - b
}
- f가 변수인지 함수인지 구분할 수 없으므로 이것이 변수와 함수를 차별하지 않는다는 의미이다.
표현식
프로그래밍 언어에서 표현식이라는 용어는 리터럴, 연산자, 변수, 함수 호출 등이 복합적으로 구성된 코드 형태를 의미한다.
계산법
컴파일러는 표현식을 만나면 계산법을 적용해 어떤 값을 만든다. 계산법에는 조급한 계산법과 느긋한 계산법이 있다.
컴파일러가 1 + 2라는 표현식을 만나면 조급한 계산법을 적용해 3이라는 값을 만든다. 반면에 컴파일러가 function(a,b) {return a + b} 라는 함수 표현식을 만나면, 심벌 a와 b가 어떤 값인지 알 수 없어서 느긋한 계산법을 적용해 계산을 보류한다.
함수 호출 연산자
어떤 변수가 함수 표현식을 담고 있다면, 변수 이름 뒤에 함수 호출 연산자()를 붙여서 호출할 수 있다. 여기서 함수 호출이란, 함수 표현식의 몸통 부분을 실행한다는 뜻이다. 만약, 함수가 매개변수를 요구한다면 함수 호출 연산자 () 안에 필요한 매개변수를 명시할 수 있다.
let functionExpression = function(a,b){return a + b}
let value = functionExpression(1,2) // 3
익명함수
함수 표현식은 대부분 언어에서 언급되는 익명 함수의 다른 표현이다. 단순히 이름이 없는 함수로만 이해하면, 이런 형태의 코드가 어떻게 동작하는지 그 원리를 쉽게 알 수 없다.
let value1 =
(function(a, b) { return a + b})
(1, 2) // 3
- 컴파일러는 2행의 익명 함수 부분에 게으른 계산법을 적용해 그 상태로 놔두지만, 곧바로 3행의 함수 호출 연산자를 만나므로 2행의 함수 몸통에 조급한 계산법을 적용해 최종적으로 3이라는 값을 만들어 낸다.
const 키워드와 함수 표현식
함수 표현식을 담는 변수는 let 보다는 const 키워드로 선언하는 것이 바람직하다. let 키워드는 변수값이 변할 수 있으므로 언젠가 다른 내용으로 바뀔 수 있다.
반면에 const 키워드로 선언하면 함수 내용이 이후에 절대로 바뀔 수 없다.
4-3 화살표 함수와 표현식 문
ESNext 자바스크립트와 타입스크립트는 function 키워드가 아닌 ⇒ 기호로 만드는 화살표 함수를 제공한다.
const 함수이름 = (매개변수1: 타입1, 매개변수2: 타입2[,...]): 반환 타입 => 함수 몸통
화살표 함수는 function 때와는 다르게 중괄호를 사용할 수도 있고 생략할 수도 있다. 중괄호 사용 여부에 따라 타입스크립트 문법이 동작하는 방식이 실행문 방식과 표현식 문 방식으로 달라진다.
실행문과 표현식 문
자바스크립트는 흥미롭게도 ES5는 실행문 지향 언어이지만, ESNext와 타입스크립트는 실행문과 표현식 문을 동시에 지원한다. 이러한 언어를 다중 패러다임 언어라고 한다.
프로그래밍 언어에서 실행문은 CPU에서 실행되는 코드를 의미한다. 실행문이 실행된 결과를 알려면 반드시 return 키워드를 사용해야 한다. 반면에 표현식 문에 CPU에서 실행된 결과를 굳이 return 키워드를 사용하지 않아도 알려준다.
복합 실행문
프로그래밍 언어에서 if와 같은 구문은 다음처럼 조건을 만족하면 단순히 한 줄의 실행문만을 실행하는 형태로 설계한다.
if(조건식)
실행문
이런 설계가 가능한 이유는 복합 실행문이라는 또 다른 형태를 함께 제공하기 때문이다. 대부분 언어에서는 복합 실행문은 중괄호 {}를 사용해 다음처럼 이용한다.
if(조건식){
실행문1
실행문2
}
복합 실행문은 컴파일러로 하여금 여러 실행문을 한 개처럼 인식하게 한다. 따라서 앞의 형태로 작성된 if문은 여전히 한 줄의 실행문으로 인식한다.
함수 몸통과 복합 실행문
function f() {
let x = 1, y = 2
let result = x + y + 10
}
function 키워드로 만드는 함수는 반드시 몸통을 중괄호 {}로 감싸야 하는데, 여기서 중괄호는 앞서 설명한 복합 실행문을 의미한다.
4-4 일등 함수 살펴보기
콜백 함수
일등 함수(first-class function)는 프로그래밍 언어가 제공하는 기능이다. 일등 함수가 기능을 제공하는 언어에서 함수는 함수 표현식이라는 일종의 값이다. 따라서 변수에 담을 수 있다. 이 말은 함수 표현식을 매개변수로 받을 수 있다는 것을 의미한다. 이처럼 매개변수 형태로 동작하는 함수를 콜백 함수(callback function)라고 한다.
const f = (callbacj:() => void): void => callback()
export const init = (callback: () => void): void => {
console.log('default initialization finished.')
callback()
console.log('all initialization finished.')
}
- init 함수는 중간에 매개변수로 받은 callback에 담긴 함수 표현식을 실행한다.
import { init } from './init'
init(() => console.log('custom initialization finished.'))
// 결과
default initialization finished.
custom initialization finished.
all initialization finished.
- 앞서 구현한 init 함수에 자신이 실행하려는 내용을 익명 함수로 전달한다.
중첩 함수
함수형 언어에서 함수는 변수에 담긴 함수 표현식이므로 함수 안에 또 다른 함수를 중첩해서 구현할 수 있다.
const calc = (value: number, cb: (arg: number) => void): void => {
let add = (a: number, b: number) => a + b
function multiply(a: number, b: number) {
return a * b
}
let result = multiply(add(1, 2), value)
cb(result)
}
calc(30, (result: number) => console.log(`result is ${result}`)) // result is 90
고차 함수와 클로저, 그리고 부분 함수
고차 함수는 또 다른 함수를 반환하는 함수를 말한다. 고차 함수 기능이 없다면 함수형 프로그래밍이 불가능할 정도로 매우 중요한 기능이다.
일반적인 형태는
const add1 = (a: number, b: number): number => a + b // 보통 함수
const add2 = (a: number): (number) => number => (b: number): number => a + b //고차 함수
- add1은 일반적인 함수
- add2는 고차 함수로 선언되었다. add 함수는 앞의 add2 함수를 이름만 바꾼 것이다.
- 고차 함수가 흥미로운 것은 2행에 있는 함수 호출 부분이다.
//const add = (a: number): ((number) => number) => (b: number): number => a + b
const add =
(a: number): ((arg: number) => number) =>
(b: number): number =>
a + b
const result = add(1)(2)
console.log(result) // 3
좀 더 이해하기 쉬운 형태로 구현해보자. 다음 코드는 Number 타입의 매개변수를 받아 number 타입의 값을 반환하는 함수 시그니처를 NumberToNumberFunc 타입으로 정의한다.
type NumberToNumberFunc = (number) => number
이제 NumberToNumberFunc 타입의 함수를 반환하는 add와 같은 함수를 만들 수 있다.
export const add = (a: number): NumberToNumberFunc => {
// NumberToNumberFunc 타입의 함수 반환
}
add의 반환값을 중첩 함수로 구현할 수 있다.
export const add = (a: number): NumberToNumberFunc => {
const _add: NumberToNumberFunc = (a: number): number => {
// Number 타입의 함수 반환
}
return _add
}
- add 함수가 반환하는 _add는 NumberToNumberFunc 타입이다. 고차 함수는 이처럼 중첩 함수를 반환할 수 있다.
최종적으로 _add 몸통을 구현하면 add라는 이름의 고차 함수가 완성된다.
export type NumberToNumberFunc = (arg: number) => number
export const add = (a: number): NumberToNumberFunc => {
const _add: NumberToNumberFunc = (b: number): number => {
return a + b
}
return _add
}
- a는 add 함수의 매개변수이고 b는 _add 함수의 매개변수이다. 즉, _add 함수의 관점에서만 보면 a는 외부에 선언된 변수이다. 함수형 프로그래밍 언어에서는 4행과 같은 형태를 클로저라고 한다.
- 고차 함수는 이 클로저 기능이 반드시 필요하다.
지금까지 구현한 고차 함수 add를 사용해보자. 구현한 add는 NumberToNumberFunc 타입의 값을 반환하므로
import {NumberToNumberFunc, add} from "./add";
let fn:NumberToNumberFunc = add(1);
- 변수 fn에 담긴 값은 NumberToNumberFunc 타입의 함수 표현식이므로 fn 뒤에 함수 호출 연산자를 붙일 수 있다.
import {NumberToNumberFunc, add} from './add'
let fn: NumberToNumberFunc = add(1)
let result = fn(2)
console.log(result) // 3
console.log(add(1)(2)) // 3
- 변수 fn은 add(1)을 저장하는 임시 변수의 역할만 한다.
- fn과 같은 임시 변수를 사용하지 않는다면 7행과 같은 구문이 된다.
- 2차 고차 함수인 add는 add(1)(2)처럼 함수 호출 연산자를 두 개 사용해야만 함수가 아닌 값을 얻을 수 있다.
- 만약 3차 고차 함수로 구현되었다면 add(1)(2)(3) 처럼 호출 연산자가 3개가 필요하다.
4-5 함수 구현 기법
매개변수 기본값 지정하기
선택적 매개변수는 항상 그 값이 undefined로 고정된다. 만일, 함수 호출 시 인수를 전달하지 않더라도 매개변수에 어떤 값을 설정하고 싶다면 매개변수의 기본값을 기정할 수 있다. 이를 디폴트 매개변수라고 한다.
(매개변수 타입 = 매개변수 기본값)
export type Person = { name: string; age: number }
export const makePerson = (name: string, age: number = 10): Person => {
const person = { name: name, age: age }
return person
}
console.log(makePerson('Jack')) // { name: 'Jack', age: 10 }
console.log(makePerson('Jane', 33)) // {name: 'Jane', age: 33 }
- makePerson 함수는 호출 때 매개변수 age에 해당하는 값을 전달받지 못하면 기본으로 10이 설정된다.
객체 생성 시 값 부분을 생략할 수 있는 타입스크립트 구문
타입스크립트는 다음처럼 매개변수의 이름과 똑같은 이름의 속성을 가진 객체를 만들 수 있다. 이때 속성값 부분을 생략할 수 있는 단축 구문을 제공한다.
const makePerson = (name: string, age: number) => {
const person = {name, age} // {name: name, age: age} 의 단축 표현
}
export type Person = { name: string; age: number }
export const makePerson = (name: string, age: number = 10): Person => {
const person = { name, age }
return person
}
console.log(makePerson('Jack')) // { name: 'Jack', age: 10 }
console.log(makePerson('Jane', 33)) // {name: 'Jane', age: 33 }
- 객체 관련 단축 구문이 적용되어 코드가 간결해졌다.
객체를 반환하는 화살표 함수 만들기
export type Person = { name: string; age: number }
export const makePerson = (name: string, age: number = 10): Person => ({ name, age })
console.log(makePerson('Jack')) // { name: 'Jack', age: 10 }
console.log(makePerson('Jane', 33)) // {name: 'Jane', age: 33 }
매개변수에 비구조화 할당문 사용하기
export type Person = { name: string; age: number }
const printPerson = ({ name, age }: Person): void => console.log(`name: ${name}, age: ${age}`)
printPerson({ name: 'Jack', age: 10 }) // name: Jack, age: 10
- 함수의 매개변수도 변수의 일종이므로 비구조화 할당문을 적용할 수 있다.
색인 키와 값으로 객체 만들기
ESNext 자바스크립트에서는 다음과 같은 코드를 작성할 수 있다.
const makeObject = (key, value) => ({[key]: value})
- 이 코드는 객체의 속성 이름을 변수로 만들려고 할 때 사용한다.
- [key] 부분이 “name”이면 {name: value}형태, “firstName”이면 {firstName:value}형태의 객체를 생성한다.
const makeObject = (key, value) => ({[key]: value})
console.log(makeObject("name", "Jack")) // {name: "Jack"}
console.log(makeObject("firstName", "Jane")) // {firstName: "Jane"}
타입스크립트에서는 {[key]: value} 형태의 타입을 “색인 가능 타입” 이라고 하며, 다음과 같은 형태로 타입을 명시한다.
type KeyType = {
[key: string]: string
}
export type KeyValueType = {
[key: string]: string
}
export const makeObject = (key: string, value: string): KeyValueType => ({ [key]: value })
console.log(makeObject('name', 'Jack')) // { name: 'Jack' }
console.log(makeObject('firstName', 'Jane')) // {firstName: 'Jane' }
4-6 클래스 메서드
function 함수와 this 키워드
객체지향 언어에서 인스턴스는 this 키워드를 사용할 수 있다. 타입스크립트에서는 function 키워드로 만든 함수에 this 키워드를 사용할 수 있다. 반면에 화살표 함수에는 this 키워드를 사용할 수 없다.
메서드란?
타입스크립트에서 메서드는 function으로 만든 함수 표현식을 담고 있는 속성이다.
export class A {
value: number = 1
//method: () => void = function(): void {
method: () => void = () => {
console.log(`value: ${this.value}`)
}
}
//test
import { A } from './A'
let a: A = new A()
a.method() // value: 1
- A.ts의 2행에서 value 속성을 1로 설정했으므로 4행의 this.value가 1이 되어 value: 1이라는 문자열이 출력된다.
클래스 메서드 구문
앞에서 작성한 클래스 A는 구현하기도 번거롭고 가독성도 떨어진다. 타입스크립트는 클래스 속성 중 함수 표현식을 담는 속성은 function 키워드를 생략할 수 있게 하는 단축 구문을 제공한다.
export class B {
constructor(public value: number = 1) {}
method(): void {
console.log(`value: ${this.value}`)
}
}
- A와 B는 똑같이 동작하지만 B 코드가 더 간결하다.
// test
import { B } from './B'
let b: B = new B(2)
b.method() // value: 2
정적 메서드
클래스의 속성은 static 수정자(modifier)를 속성 앞에 붙여서 정적으로 만들 수 있었다. 메서드 도한 속성이므로 이름 앞에 static을 붙여 정적 메서드를 만들 수 있다.
export class C {
static whoAreYou(): string {
return `I'm class C`
}
}
export class D {
static whoAreYou(): string {
return `I'm class D`
}
}
console.log(C.whoAreYou()) // I'm class C
console.log(D.whoAreYou()) // I'm class D
메서드 체인
제이쿼리(jQuery)와 같은 라이브러리는 다음처럼 객체의 메서드를 이어서 계속 호출하는 방식의 코드를 작성할 수 있다. 이러한 방식을 메서드 체인 (method chain)이라고 한다.
$("#p1").css("color","red").slideUp(2000).slideDown(2000);
타입스크립트로 메서드 체인을 구현하려면 메서드가 항상 this를 반환하게 한다.
export class Calculator {
constructor(public value: number = 0) {}
add(value: number) {
this.value += value
return this
}
multiply(value: number) {
this.value *= value
return this
}
}
// test
import { Calculator } from './method-chain'
let calc = new Calculator()
let result = calc
.add(1)
.add(2)
.multiply(3)
.multiply(4).value
console.log(result) // (0 + 1 + 2) * 3 * 4 = 36
'Books > Do It 타입스크립트 프로그래밍' 카테고리의 다른 글
6장 반복기와 생성기 (0) | 2022.12.12 |
---|---|
5장 배열과 튜플 (0) | 2022.12.11 |
3장 객체와 타입 (0) | 2022.11.29 |
2장 타입스크립트 프로젝트 생성과 관리 (0) | 2022.11.28 |
1장 타입스크립트와 개발환경 만들기 (0) | 2022.07.07 |