Million Dreams
100만개의 꿈을 꾸는 개발자 지망생
Do it! 타입스크립트 프로그래밍 5장

Ch05 배열과 튜플

5-1) 배열 이해하기

- 자바스크립트에서 배열은 Array 클래스의 인스턴스이며 다음처럼 선언함

let 배열 이름 = new Array(배열 길이)

let array = new Array

array.push(1); array.push(2); array.push(3)

console.log(array)

 

- 배열에 담긴 각각의 값을 아이템(item) 또는 원소(element)라고 하며, 위의 예제 배열에는 3개의 아이템을 담고 있다.

 

(1) [ ] 단축 구문

- 자바스크립트는 [ ] 단축 구문을 제공하며, 이를 이용해 한번에 배열을 만들 수도 있다.

let numbers = [123]

let strings = ['Hello''World']

console.log(numbersstrings)

 

(2) 자바스크립트에서 배열은 객체다

- 자바스크립트에서 배열은 다른 언어와 다르게 객체임. Array클래스의 인스턴스이기 때문

- Array클래스에서 제공하는 여러 메서드 중 Array.isArray는 매개변수로 전달받은 심벌이 배열인지 객체인지 알려줌

 

(3) 배열의 타입

- 타입스크립트에서 배열의 타입은 아이템 타입[]’.

let numArray: number[] = [123]

let strArray: string[] = ['Hello''World']

 

type IPerson = {name: stringage?: number}

let personArray: IPerson[] = [{name: 'Jack'}, {name: 'Jane', age: 32}]

 

 

(4) 문자열과 배열 간 변환

- 어떤 프로그래밍 언어는 문자열(string)을 문자(character)들의 배열(array)로 간주하지만 타입스크립트에선 문자 타입이 없고 문자열의 내용 또한 변경할 수 없다.

- 문자열을 가공하려면 문자열을 배열로 전환해야함 (Stringsplit 클래스 이용)

split(구분자: string): string[]

export const split = (str: stringdelim: string = ''): string[] => str.split(delim)

 

split 함수는 매개변수로 전달받은 문자열과 구분자를 이용해 String 클래스의 split 메서드를 호출함으로써 string[] 타입의 배열로 만들어줌

import {splitfrom './split'

console.log(

    split('hello'),

    split('h_e_l_l_o''_')

)

 

[ 'h', 'e', 'l', 'l', 'o' ] [ 'h', 'e', 'l', 'l', 'o' ]

 

 

- string[] 타입의 배열을 다시 string으로 변환하려면 Array 클래스의 join 메서드를 사용

join(구분지: string): string

 

(5) 인덱스 연산자

- 배열이 담고 있는 아이템 중 특정 위치에 있는 아이템을 얻고자 할 때는 인덱스 연산자(index operator)[인덱스]를 사용

const numbers: number[] = [12345]

for(let index = 0index < numbers.length; index++) {

    const item: number = numbers[index]

    console.log(item)

}

 

(6) 배열의 비구조화 할당

- 객체뿐 아니라 배열에도 비구조화 할당을 적용할 수 있음. 배열의 비구조화 할당문에는 [] 기호 사용

 

(7) for … in

- ESNext 자바스크립트와 타입스크립트는 for 문을 좀 더 쉽게 사용하도록 for…in 문을 제공

for(변수 in 객체) {

}

let names = ['Jack''Jane''Steve']

 

for (let index in names) {

    const name = names[index]

    console.log(`[${index}]: ${name}`)

}

 

[0]: Jack

[1]: Jane

[2]: Steve

 

- 만약 for…in문에 객체를 사용할 때는 객체가 가진 속성을 대상으로 순회함

let jack = {name: 'Jack', age: 32}

for(let property in jack)

console.log(`${property}: ${jack[property]}`)

 

name: Jack

age: 32

 

(8) for…of

- for…in문이 배열의 인덱스 값으로 대상을 순화한다면, for…of는 배열의 아이템값을 대상으로 순회함

for(let name of ['Jack''Jane''Steve'])

    console.log(name)

 

(9) 제네릭 방식 타입

- 배열을 다루는 함수 작성시 number[]처럼 타입이 고정된 함수가 아닌 T[]처럼 배열의 아이템 타입을 한꺼번에 표현하는 것이 편리함

- 타입을 T와 같이 일종의 변수(타입 변수)로 취급하는 것을 제네릭(generics)타입이라고 함

 

Ex) 예를 들어 배열의 길이를 얻는 함수로 arrayLength를 선언한다면 다음과 같이 만들 수 있음

const arrayLength = (array) => array.length

 

이를 다양한 아이템 타입을 가지는 배열에 똑같이 적용하려면 다음과 같이 표현 가능

const arrayLength = (array: T[]): number => array.length

 

그러나 이러면 컴파일러에게 T가 타입 변수라고 알려줘야 함으로 다음과 같이 표현한다.

const arrayLength = <T>(array: T[]): number => array.length

 

배열이 비어있는지 확인함수는 다음과 같이 표현 가능하다

export const isEmpty = <T>(array: T[]): boolean => arrayLength<T>(array== 0

 

Ex) import {arrayLengthisEmptyfrom './arrayLength'

let numArray: number[] = [123]

let strArray: string[] = ['Hello''World']

 

type IPerson = {name: stringage?: number}

let personArray: IPerson[] = [{name: 'Jack'}, {name: 'Jane', age: 32}]

 

console.log(

 

    arrayLength(numArray),

    arrayLength(strArray),

    arrayLength(personArray),

    isEmpty([]),

    isEmpty([1])

)

 

Result: 3 2 2 true false

 

(10)제네릭 함수의 타입 추론

제네릭 형태로 구현된 함수는 원칙적으로 다음과 같이 명시해주어야함

함수 이름<타입 변수>(매개변수)

그러나 타입스크립트는 다음 예시(4)처럼 타입 변수 부분을 생략할 수 있게함.

const identity = <T>(n: T): T => n

console.log(

    identity<boolean>(true),

    identity(true)

)

 

이 경우, 타입스크립트는 타입 추론을 통해 생략된 타입을 찾아냄

(11) 제네릭 함수의 함수 시그니처

- const f = <T>(cb: (arg: T, i?: number) => number): void => {}

다음과 같은 형태로 변수를 삽입해서 변수에 타입을 명시해 해결함

 

(12) 전개 연산자

- 배열에도 전개연산자 적용이 가능ㅎ며, 예시처럼 전개 연산자를 사용해 두 배열과 특정 값을 동시에 결합할 수 있다.

 

(13) range 함수 구현

- 배열에 전개 연산자를 적용하면 ramda 외부 패키지가 제공하는 R.range와 같은 방식으로 range 함수를 구현할 수 있다.

export const range = (from: numberto: number): number[] =>

    from < to ? [from...range(from + 1to)] : []

 

import {rangefrom './range'

let numbers: number[] = range(19 + 1)

console.log(numbers)

 

 

 

 

 

 

 

 

 

 

 

 

5-3) 배열의 map, reduce, filter 메서드

(1) filter 메서드

- 배열의 타입이 T[]일 때 배열의 filter 메서드는 다음과 같은 형태로 설계되었음

Filter(callback: (value: T, index?: number): boolean ): T[]

Filter method의 사용 예

import {rangefrom '../../ch05-1/src/range'

 

const array: number[] = range(110+1)

 

let odds: number[] = array.filter((value) => value % 2 != 0)

let evens: number[] = array.filter((value) => value % 2 == 0)

console.log(oddsevens)

 

 

- filter 메서드는 두번째 매개변수에 index라는 선택 속성을 제공

index값을 사용해 배열을 반(half)으로 나눈 예

import {rangefrom '../../ch05-1/src/range'

 

const array: number[] = range(110+1)

const half = array.length / 2

 

let belowHalf: number[] = array.filter((vindex) => index < half)

let overHalf: number[] = array.filter((vindex) => index >= half)

console.log(belowHalfoverHalf)

 

(2) map 메서드

- 배열의 타입이 T[] 일 때 배열의 map 메서드는 다음과 같은 형태로 설계되며 filter와 달리 map 메서드는 입력 타입과 다른 타입의 배열을 만들 수 있음

Map(callback: (value: T, index?: number): Q): Q[]

 

Map을 사용해 Number[] 타입 배열을 string[] 타입배열로 가공한 예

import {rangefrom '../../ch05-1/src/range'

 

let names: string[] = range(15 + 1)

    .map((valindex) => `[${index}]: ${val}`)

console.log(names)

 

(3) reduce 메서드

- reduce함수는 다음과 같이 설계됨

Reduce(callback: (result: T, value: T), initialValue: T): T

 

1부터 100까지 더하는 로직을 reduce메소드를 사용해 구현한 예

import {rangefrom './range'

 

let reduceSum: number = range(1100 + 1)

    .reduce((result: numbervalue: number) => result + value0)

console.log(reduceSum)

 

 

5-4) 순수 함수와 배열

- 함수형 프로그래밍에서 함수는 순수 함수(pure function)’라는 조건을 만족해야 함. 그러나 타입스크립트의 Array 클래스에는 순수 함수 조건에 부합하지 않는 메서드가 많음

- 순수 함수란 부수 효과(side-effect)가 없는 함수를 말하는데, 부수 효과란 함수가 가진 고유 목적 이외에 다른 효과가 나타나는 것을 말하며 부작용이라고도 함

- 이러한 부수 효과가 있는 함수는 불순 함수(impure function)이라고 함

- 순수 함수과 되려면 다음과 같은 조건을 만족해야 함

순수 함수의 조건

함수 몸통에 입출력 관련 코드가 없어야한다.

함수 몸통에서 매개변숫값을 변경시키지 않는다.(, 매개변수는 constreadonly 형태로만 사용한다.)

함수는 몸통에서 만들어진 결과를 즉시 반환한다.

함수 내부에 전역 변수나 정적 변수를 사용하지 않는다.

함수가 예외를 발생시키지 않는다.

함수가 콜백 함수로 구현되었거나 함수 몸통에 콜백 함수를 사용하는 코드가 없다.

함수 몸통에 Promise와 같은 비동기 방식으로 동작하는 코드가 없다.

 

순수 함수의 예

Function pure(a: number, b:number): number {return a + b}

 

불순 함수의 예

function impure(array: number[]): void {

array.push(1)

array.splice(0, 1)

}

 

(1) 타입 수정자 readonly

- 타입스크립트는 순수 함수 구현을 위해 readonly 키워드를 제공하며 readonly 타입으로 선언된 매개변숫값을 변경하려면 경고창을 띄워 불순함수가 되는 것을 막는다.

 

(2) 불변과 가변

- constreadonly를 명시하고 있으면 변숫값은 초깃값을 항상 유지하며 이를 불변(immutable) 변수라고 함

 

- 반면 let이나 readonly를 명시하지 않는 변수는 값 변경이 가능하므로 가변(mutable) 변수라고 함

 

(3) 깊은 복사와 얕은 복사

- 프로그래밍 언어에서 어떤 변숫값을 다른 변숫값으로 설정하는 것을 복사(copy)라고 표현하며 복사에는 깊은 복사(deep-copy)’, ‘얕은 복사(shallow-copy)’ 두 종류가 있음

- 순수 함수 구현시에는 매개변수가 불변성을 유지해야하므로, 깊은 복사를 실행해 매개변숫값이 변경되지 않도록 해야함

- 깊은 복사는 대상 변숫값이 바뀔 때 원본 변숫값은 그대로인 형태로 동작

깊은 복사의 예

let original = 1

let copied = original

copied += 2

console.log(originalcopied)

 

결괏값: 1 3

 

- 객체와 배열은 얕은 복사 방식으로 동작함

얕은 복사의 예

const originalArray = [5397]

const shallowCopiedArray = originalArray

shallowCopiedArray[0= 0

console.log(originalArrayshallowCopiedArray)

 

결괏값: [ 0, 3, 9, 7 ] [ 0, 3, 9, 7 ]

 

(4) 전개 연산자와 깊은 복사

- 전개 연산자를 사용해 배열을 복사하면 깊은 복사를 할 수 있음

전개 연산자를 사용해 깊은 복사를 한 예

const oArray = [1234]

const deepCopiedArray = [...oArray]

deepCopiedArray[0= 0

console.log(oArraydeepCopiedArray)

 

결괏값: [ 1, 2, 3, 4 ] [ 0, 2, 3, 4 ]

 

(5) 배열의 sort 메서드를 순수 함수로 구현하기

- Array 클래스의 sort메서드는 배열의 아이템을 오름차순(ascend) 또는 내림차순(descend)로 정렬해줌

 

- , sort 메서드는 원본 배열의 내용을 변경하므로 readonly 타입을 적용해 순수함수로써 sort 역할을 하는 메서드를 만들어 봄

export const puerSort = <T>(array: readonly T[]): T[] => {

    let deepCopied = [...array]

    return deepCopied.sort()

}

 

pureSort의 사용 예

import {pureSortfrom './pureSort'

 

let beforeSort = [ 629 ,0]

const afterSort = pureSort(beforeSort)

console.log(beforeSortafterSort)

결괏값: [ 6, 2, 9, 0 ] [ 0, 2, 6, 9 ]

 

(6) 배열의 filter 메서드와 순수한 삭제

- 배열에서 특정 아이템을 삭제할 때는 slice 메서드를 사용하지만, 원본 배열의 내용을 변경하므로 순수 함수에서는 사용 불가함

 

- 대신 filter 메서드를 사용할 수 있음. 배열이 제공하는 filtermap 메서드는 sort와 달리 깊은 복사 형태로 동작하므로 filter 사용하면 원본 배열의 내영을 훼손하지 않으면서 조건에 맞지 않는 아이템을 삭제 가능함

 

filter 사용해 특정 아이템을 삭제하는 함수 예

export const pureDelete = <T>(array: readonly T[], cb: (val: Tindex?: number) => 

boolean): T[] => array.filter((valindex) => cb(valindex== false)

 

배열과 객체가 섞인 원본을 훼손하지 않으면서 배열만 제거한 배열(objectOnly)를 만든 예

import {pureDeletefrom './pureDelete'

 

const mixedArray: object[] = [

    [], {name:'Jack'}, {name: 'Jane', age: 32}, ['description']

]

const objectOnly: object[] = pureDelete(mixedArray(val) => Array.isArray(val))

console.log(mixedArrayobjectOnly)

 

(7) 가변 인수 함수와 순수 함수

- 함수를 호출할 때 전달 인수의 개수를 제한하지 않는 것을 가변 인수(variadic arguments)라고 하며 매개변수 앞의 은 잔여나 전개 연산자가 아니라 가변 인수를 표현하는 구문임

 

5-5) 튜플 이해하기

- 파이썬 등에는 튜플이 존재하지만, 자바스크립트에서는 튜플이 없으며 단순히 배열의 한 종류로 취급됨.

let tuple: any[] = [true'the result is ok']

 

- 그러나 any[] 형태는 타입 기능을 무력하므로 다르게 선언할 수 있음

const array: number[] = [123 ,4]

const tuple: [booleanstring= [true'the result is ok']

 

(1) 튜플에 타입 별칭 사용하기

- 일반적으로 튜플 사용시에는 타입 별칭(alias)로 튜플의 의미를 명확하게 함

 

  Comments,     Trackbacks