05-1 배열 이해하기

자바스크립트와 타입스크립트에서 배열은 다른 언어와 다르게 객체이다.

 

  • new Array 배열 선언하기

new Arrar 키워드를 통해 배열을 만들 수 있고, 배열에 담긴 값을 아이템(item) 또는 원소(element) 라고 한다.

let arr: number[] = new Array

arr.push(1)
arr.push(2)
arr.push(3)

console.log(arr) // [1, 2, 3]

 

  • [] 단축 구문 배열 선언하기

[] 단축 구문을 사용해서 배열을 만들 수 있다.

// [] 단축 구문
const numArr: number[] = [1, 2, 3]
const strArr: string[] = ['hello', 'world']

console.log(num) // [1, 2, 3]
console.log(str) // ['hello', 'world']

 

문자열과 배열 간 변환

타입스크립트에서는 문자 타입이 없고, 문자열의 내용 또한 변경할 수 없다.

따라서 문자열을 변경하기 위해서는 문자열을 배열로 변환해야 한다.

 

let str: string = 'hello'
let strArr: string[] = str.split('')

console.log(strArr.join('-')) // h-e-l-l-o

 

인덱스 연산자

인덱스 연산자, [인덱스]는 배열의 특정 위치에 있는 값을 알 수 있다.

let numbers: number[] = [1, 2, 3]

for(let i = 0; i < numbers.length; i++) {
    console.log(numbers[i])
}

// 1 2 3

 

배열의 비구조화 할당

객체뿐만 아니라 배열에도 비구조화 할당을 적용할 수 있다.

비구조화 할당이란, 배열이나 객체 속성을 해체하여 그 값을 변수에 담을 수 있게 하는 표현식이다.

let arr: number[] = [1, 2, 3, 4, 5]
let [first, second, third, ...rest] = arr

console.log(first, second, third, rest) // 1 2 3 [ 4, 5 ]

 

for ...in 문

배열의 인덱스 값을 순회한다.

let names: string[] = ['다연', '하영', '희재']

for(let index in names) {
    console.log(`[${index}]: ${names[index]}`)
}

// [0]: 다연 [1]: 하영 [2]: 희재

 

for ...of 문

배열의 아이템 값을 대상으로 순회한다. (인덱스값을 알 수 없다.)

let names: string[] = ['다연', '하영', '희재']

for(let index of names) {
    console.log(index)
}

// 다연 하영 희재

 

제네릭 방식 타입

배열을 다루는 함수를 작성할 때는 고정된 타입을 함수를 만들기보다는 T[] 형태(제네릭 타입)로 배열의 아이템 타입을 한꺼번에 표현하는 것이 편리하다.

타입스크립트는 타입 변수가 생략된 제네릭 함수를 만나면 타입 추론을 통해서 생략된 타입을 찾아낸다.

 

제네릭 사용 이유
  1. 한 가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성할 수 있다.
  2. 재사용성이 높은 함수와 클래스를 생성할 수 있다.
  3. any 타입을 사용하는 대신에 사용하기 때문에 오류를 쉽게 검출할 수 있다.

 

function sort<T>(item: T[]): void {
    console.log(item.sort())
}
 
const nums: number[] = [3, 2, 5, 4, 1];
const chars: string[] = ['d', 'a', 'e', 'c', 'b'];
 
sort<number>(nums) // [ 1, 2, 3, 4, 5 ]
sort<string>(chars) // [ 'a', 'b', 'c', 'd', 'e' ]

 

전개 연산자

배열에서도 전개 연산자를 사용할 수 있다.

const arr1: number[] = [1, 2, 3]
const arr2: string[] = ['a', 'b', 'c']
const arr3: (number | string)[] = [...arr1, ...arr2]

console.log(arr3) // [ 1, 2, 3, 'a', 'b', 'c' ]

 

range 함수 구현

range 함수는 재귀 함수 스타일로 동작하고, 배열을 생성해 준다.

const range = (from: number, to: number): number[] => {
    return from < to ? [from, ...range(from + 1, to)] : []
}

let numbers: number[] = range(1, 5)

console.log(numbers) // [ 1, 2, 3, 4 ]

 


05-2 선언형 프로그래밍과 배열

명령형 프로그래밍

ex) for 문

  • 입력 데이터 얻기
  • 입력 데이터 가공해 출력 데이터 생성
  • 출력 데이터 출력

 

선언형 프로그래밍

ex) for 문을 사용하지 않고 모든 데이터를 배열에 담는 형식

  • 문제를 푸는 데 필요한 모든 데이터 배열에 저장
  • 입력 데이터 배열을 가공해 출력 데이터 배열 생성
  • 출력 데이터 배열에 담긴 아이템 출력

 

1부터 100까지 더하기 

  • 명령형 프로그래밍
let sum = 0

for(let i = 1; i <= 100; i++) {
    sum = sum + i
}

console.log(sum) // 5050

 

  • 선언형 프로그래밍
const range = (from: number, to: number): number[] => {
    return from < to ? [from, ...range(from + 1, to)] : []
}

const numbers: number[] = range(1, 101)

console.log(numbers.reduce((acc, cur) => acc + cur)) // 5050

 


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

filter 메서드

깊은 복사의 형태로 동작한다. (= 원본 배열을 변경하지 않는다.)

const arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

const odd: number[] = arr.filter((item, index) => item % 2 !== 0)
const even: number[] = arr.filter((item, index) => item % 2 === 0)

console.log(odd) // [ 1, 3, 5, 7, 9 ]
console.log(even) // [ 2, 4, 6, 8, 10 ]

 

map 메서드

깊은 복사의 형태로 동작한다. (= 원본 배열을 변경하지 않는다.)

const arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

const double = arr.map((item, index) => item * 2)

console.log(double) // [2,  4,  6,  8, 10, 12, 14, 16, 18, 20]

 

reduce 메서드

const arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

const total = arr.reduce((acc, cur) => acc + cur)

console.log(total) // 55

 


05-4 순수 함수와 배열

함수형 프로그래밍에서 함수는 '순수 함수'라는 조건을 만족해야 한다.

그러나 타입스크립트의 배열에는 순수 함수 조건에 부합하지 않는 메서드들이 많다.

 

순수 함수란?

'순수 함수'는 부수 효과(side-effect)가 없는 함수를 의미한다. (부수 효과가 있으면 '불순 함수' 라고 한다.)

 

  • 함수 몸통에 입출력에 관련된 코드가 없어야 한다.
  • 함수 몸통에서 매개변숫값을 변경시키지 않는다. (즉, 매개변수는 const나 readonly 형태로만 사용한다.)
  • 함수는 몸통에서 만들어진 결과를 즉시 반환한다.
  • 함수 내부에 전역 변수나 정적 변수를 사용하지 않는다.
  • 함수가 예외를 발생시키지 않는다.
  • 함수가 콜백 함수로 구현되었거나 함수 몸통에 콜백 함수를 사용하는 코드가 없다.
  • 함수 몸통에 Promise와 같은 비동기 방식으로 작동하는 코드가 없다.

 

  • 순수 함수
function pure(a: number, b: number): number {
    return a + b
}

console.log(pure(1, 2)) // 3

 

  • 불순 함수

매개변수 arr이 변경된다.

function impure(arr: number[]): void {
    console.log(arr) // []
    
    arr.push(100)
    
    console.log(arr) // [100]
}

impure([])

 

타입 수정자 readonly

타입스크립트는 순수 함수 구현을 쉽게 하도록 readonly 키워드를 제공한다.

또는 인터페이스, 클래스, 함수의 매개변수는 let, const 키워드 없이 선언하기 때문에 심벌에 const와 같은 효과를 주기 위해서 readonly 타입 수정자가 필요하다.

function impure(arr: readonly number[]): void {
    // Property 'push' does not exist on type 'readonly number[]'.
    arr.push(100)
}

 

불변과 가변

  • 불변 변수

const 또는 readonly 키워드로 선언된 변수이다.

초기값을 항상 유지한다.

 

  • 가변 변수

var 또는 let 키워드로 선언된 변수이다.

언제든 변수의 값을 변경할 수 있다.

 

깊은 복사와 얕은 복사

순수 함수를 구현할 때는 매개변수가 불변성을 유지해야 한다. 매개변수를 가공하려고 할 때 깊은 복사를 실행해 매개변숫값이 변경되지 않게 해야 한다.

그러나 새로운 변수에 기존 변수를 할당하는 방식으로 복사를 시도하면 객체와 배열은 얕은 복사 방식으로 동작하기 때문에 주의해야 한다.

 

  • 깊은 복사

원본 변수의 값이 변경되지 않는다.

let original = 1
let copy = original

copy = copy + 2

console.log(original) // 1
console.log(copy) // 3

 

  • 얕은 복사

원본 배열의 값이 변경된다.

let original = [1, 2, 3]
let copy = original

cop.push(4)

console.log(original) // [1, 2, 3, 4]
console.log(copy) // [1, 2, 3, 4]

 

전개 연산자와 깊은 복사

전개 연산자를 사용해 객체와 배열을 깊은 복사할 수 있다.

let originArr = [1, 2, 3]
let copyArr = [...originArr]

copyArr.push(4)

console.log(originArr) // [ 1, 2, 3 ]
console.log(copyArr) // [ 1, 2, 3, 4 ]

 

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

sort 메서드는 원본 배열의 내용을 변경한다. 

따라서 원본 배열을 유지한채 sort 메서드를 사용하고 싶다면 전개 연산자의 깊은 복사 기능을 먼저 사용하면 된다.

 

  • sort 얕은 복사

원본 배열의 값이 변경된다.

let arr: number[] = [3, 5, 2, 1, 4]
let sortArr: number[] = arr.sort()

console.log(arr) // [ 1, 2, 3, 4, 5 ]
console.log(sortArr) // [ 1, 2, 3, 4, 5 ]

 

  • sort 깊은 복사

원본 배열의 값이 변경되지 않는다.

let arr: number[] = [3, 5, 2, 1, 4]
let sortArr: number[] = [...arr].sort()

console.log(arr) // [ 3, 5, 2, 1, 4 ]
console.log(sortArr) // [ 1, 2, 3, 4, 5 ]

 

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

보통 배열에서 특정 아이템을 삭제할 때 splice 메서드를 많이 사용하는데 splice 메서드는 원본 배열을 변경하므로 순수 함수에서는 사용할 수 없다.

filter 메서드는 깊은 복사 형태로 동작하기 때문에 원본 배열을 유지한 채 특정 아이템을 삭제할 수 있다.

interface Data {
  id: number
  title: string
}

const data: Data[] = [
  { id: 1, title: '제목1' },
  { id: 2, title: '제목2' },
  { id: 3, title: '제목3' }
]

let filterData = data.filter((item, index) => item.id !== 1)

console.log(data) // [ { id: 1, title: '제목1' }, { id: 2, title: '제목2' }, { id: 3, title: '제목3' } ]
console.log(filterData) // [ { id: 2, title: '제목2' }, { id: 3, title: '제목3' } ]

 

가변 인수 함수와 순수 함수

가변 인수(variadic arguments)는 함수를 호출할 때 전달하는 인수의 개수를 제한하지 않는다.

 

  1. 가변 인수로 호출할 때 타입에 상관없이 동작하게 하기 위해서 제네릭 타입으로 구현한다.
  2. 전개 연산자를 사용해 가변 인수를 표현한다.
  3. 가변 인수로 전달하는 값이 배열이기 때문에 타입을 배열의 배열로 선언하고, 매개변수는 변경되면 안되기 때문에 readonly로 선언한다.

 

const mergeArr = <T>(...arr: readonly T[][]): T[] => {
  let result: T[] = []

  for(let i = 0; i < arr.length; i++) {
    const array: T[] = arr[i]
    result = [...result, ...array]
  }

  return result
}

console.log(mergeArr([1, 2, 3])) // [1, 2, 3]
console.log(mergeArr(['hello', 'world'])) // [ 'hello', 'world' ]
console.log(mergeArr([{name: '희재', age: 27}])) // [ { name: '희재', age: 27 } ]

 

순수 함수를 고려하면 사실상 자바스크립트 배열이 제공하는 많은 메서드를 사용할 수 없지만,
전개 연산자 등의 메커니즘을 사용하면 순수 함수 형태로 간단하게 표현될 수 있음을 알아야 한다.

 


05-5 튜플 이해하기

튜플이란, 길이와 타입이 고정된 배열이다. 배열에 담긴 데이터를 만들 때 순서를 무시하고 만들게 되는 오류를 방지할 수 있다.

또한 튜플은 물리적으로 배열이기 때문에 배열처럼 인덱스 연산자나 비구조화 할당문을 적용할 수 있다.

// 타입 별칭으로 튜플의 의미를 정확하게 한다.
type tupleType = [number, string]

const tuple1: tupleType = [1, 'title1']
const tuple2: tupleType = ['title2', 2] // Type 'string' is not assignable to type 'number'.

console.log(tuple1[0], tuple1[1]) // 1, title1 (인덱스 연산자 사용)

// 비구조화 할당
let [id, title] = tuple1 
console.log(id, title) // 1, title1

 

그러나 튜플의 의미와 맞지 않게 push 메서드를 사용해 배열의 길이를 늘릴 수 있어 주의해야 한다. 

type tupleType = [number, string]

const tuple: tupleType = [1, 'title']

tuple.push('content')
tuple.push(true) // Argument of type 'boolean' is not assignable to parameter of type 'string | number'.

console.log(tuple) // [ 1, 'title', 'content' ]

 

 

 

참고자료

Do it! 타입스크립트 프로그래밍

+ Recent posts