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 = [1, 2, 3]
let strings = ['Hello', 'World']
console.log(numbers, strings)
(2) 자바스크립트에서 배열은 객체다
- 자바스크립트에서 배열은 다른 언어와 다르게 객체임. Array클래스의 인스턴스이기 때문
- Array클래스에서 제공하는 여러 메서드 중 Array.isArray는 매개변수로 전달받은 심벌이 배열인지 객체인지 알려줌
(3) 배열의 타입
- 타입스크립트에서 배열의 타입은 ‘아이템 타입[]’임.
let numArray: number[] = [1, 2, 3]
let strArray: string[] = ['Hello', 'World']
type IPerson = {name: string, age?: number}
let personArray: IPerson[] = [{name: 'Jack'}, {name: 'Jane', age: 32}]
(4) 문자열과 배열 간 변환
- 어떤 프로그래밍 언어는 문자열(string)을 문자(character)들의 배열(array)로 간주하지만 타입스크립트에선 문자 타입이 없고 문자열의 내용 또한 변경할 수 없다.
- 문자열을 가공하려면 문자열을 배열로 전환해야함 (String의 split 클래스 이용)
split(구분자: string): string[]
export const split = (str: string, delim: string = ''): string[] => str.split(delim)
split 함수는 매개변수로 전달받은 문자열과 구분자를 이용해 String 클래스의 split 메서드를 호출함으로써 string[] 타입의 배열로 만들어줌
import {split} from './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[] = [1, 2, 3, 4, 5]
for(let index = 0; index < 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 {arrayLength, isEmpty} from './arrayLength'
let numArray: number[] = [1, 2, 3]
let strArray: string[] = ['Hello', 'World']
type IPerson = {name: string, age?: 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: number, to: number): number[] =>
from < to ? [from, ...range(from + 1, to)] : []
import {range} from './range'
let numbers: number[] = range(1, 9 + 1)
console.log(numbers)
5-3) 배열의 map, reduce, filter 메서드
(1) filter 메서드
- 배열의 타입이 T[]일 때 배열의 filter 메서드는 다음과 같은 형태로 설계되었음
Filter(callback: (value: T, index?: number): boolean ): T[]
Filter method의 사용 예
import {range} from '../../ch05-1/src/range'
const array: number[] = range(1, 10+1)
let odds: number[] = array.filter((value) => value % 2 != 0)
let evens: number[] = array.filter((value) => value % 2 == 0)
console.log(odds, evens)
- filter 메서드는 두번째 매개변수에 index라는 선택 속성을 제공
index값을 사용해 배열을 반(half)으로 나눈 예
import {range} from '../../ch05-1/src/range'
const array: number[] = range(1, 10+1)
const half = array.length / 2
let belowHalf: number[] = array.filter((v, index) => index < half)
let overHalf: number[] = array.filter((v, index) => index >= half)
console.log(belowHalf, overHalf)
(2) map 메서드
- 배열의 타입이 T[] 일 때 배열의 map 메서드는 다음과 같은 형태로 설계되며 filter와 달리 map 메서드는 입력 타입과 다른 타입의 배열을 만들 수 있음
Map(callback: (value: T, index?: number): Q): Q[]
Map을 사용해 Number[] 타입 배열을 string[] 타입배열로 가공한 예
import {range} from '../../ch05-1/src/range'
let names: string[] = range(1, 5 + 1)
.map((val, index) => `[${index}]: ${val}`)
console.log(names)
(3) reduce 메서드
- reduce함수는 다음과 같이 설계됨
Reduce(callback: (result: T, value: T), initialValue: T): T
1부터 100까지 더하는 로직을 reduce메소드를 사용해 구현한 예
import {range} from './range'
let reduceSum: number = range(1, 100 + 1)
.reduce((result: number, value: number) => result + value, 0)
console.log(reduceSum)
5-4) 순수 함수와 배열
- 함수형 프로그래밍에서 함수는 ‘순수 함수(pure function)’라는 조건을 만족해야 함. 그러나 타입스크립트의 Array 클래스에는 순수 함수 조건에 부합하지 않는 메서드가 많음
- 순수 함수란 부수 효과(side-effect)가 없는 함수를 말하는데, 부수 효과란 함수가 가진 고유 목적 이외에 다른 효과가 나타나는 것을 말하며 부작용이라고도 함
- 이러한 부수 효과가 있는 함수는 불순 함수(impure function)이라고 함
- 순수 함수과 되려면 다음과 같은 조건을 만족해야 함
※ 순수 함수의 조건
• 함수 몸통에 입출력 관련 코드가 없어야한다.
• 함수 몸통에서 매개변숫값을 변경시키지 않는다.(즉, 매개변수는 const나 readonly 형태로만 사용한다.)
• 함수는 몸통에서 만들어진 결과를 즉시 반환한다.
• 함수 내부에 전역 변수나 정적 변수를 사용하지 않는다.
• 함수가 예외를 발생시키지 않는다.
• 함수가 콜백 함수로 구현되었거나 함수 몸통에 콜백 함수를 사용하는 코드가 없다.
• 함수 몸통에 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) 불변과 가변
- const나 readonly를 명시하고 있으면 변숫값은 초깃값을 항상 유지하며 이를 ‘불변(immutable) 변수’라고 함
- 반면 let이나 readonly를 명시하지 않는 변수는 값 변경이 가능하므로 ‘가변(mutable) 변수’라고 함
(3) 깊은 복사와 얕은 복사
- 프로그래밍 언어에서 어떤 변숫값을 다른 변숫값으로 설정하는 것을 복사(copy)라고 표현하며 복사에는 ‘깊은 복사(deep-copy)’, ‘얕은 복사(shallow-copy)’ 두 종류가 있음
- 순수 함수 구현시에는 매개변수가 불변성을 유지해야하므로, 깊은 복사를 실행해 매개변숫값이 변경되지 않도록 해야함
- 깊은 복사는 대상 변숫값이 바뀔 때 원본 변숫값은 그대로인 형태로 동작
※ 깊은 복사의 예
let original = 1
let copied = original
copied += 2
console.log(original, copied)
결괏값: 1 3
- 객체와 배열은 얕은 복사 방식으로 동작함
※ 얕은 복사의 예
const originalArray = [5, 3, 9, 7]
const shallowCopiedArray = originalArray
shallowCopiedArray[0] = 0
console.log(originalArray, shallowCopiedArray)
결괏값: [ 0, 3, 9, 7 ] [ 0, 3, 9, 7 ]
(4) 전개 연산자와 깊은 복사
- 전개 연산자를 사용해 배열을 복사하면 깊은 복사를 할 수 있음
※ 전개 연산자를 사용해 깊은 복사를 한 예
const oArray = [1, 2, 3, 4]
const deepCopiedArray = [...oArray]
deepCopiedArray[0] = 0
console.log(oArray, deepCopiedArray)
결괏값: [ 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 {pureSort} from './pureSort'
let beforeSort = [ 6, 2, 9 ,0]
const afterSort = pureSort(beforeSort)
console.log(beforeSort, afterSort)
결괏값: [ 6, 2, 9, 0 ] [ 0, 2, 6, 9 ]
(6) 배열의 filter 메서드와 순수한 삭제
- 배열에서 특정 아이템을 삭제할 때는 slice 메서드를 사용하지만, 원본 배열의 내용을 변경하므로 순수 함수에서는 사용 불가함
- 대신 filter 메서드를 사용할 수 있음. 배열이 제공하는 filter와 map 메서드는 sort와 달리 깊은 복사 형태로 동작하므로 filter 사용하면 원본 배열의 내영을 훼손하지 않으면서 조건에 맞지 않는 아이템을 삭제 가능함
※ filter 사용해 특정 아이템을 삭제하는 함수 예
export const pureDelete = <T>(array: readonly T[], cb: (val: T, index?: number) =>
boolean): T[] => array.filter((val, index) => cb(val, index) == false)
※ 배열과 객체가 섞인 원본을 훼손하지 않으면서 배열만 제거한 배열(objectOnly)를 만든 예
import {pureDelete} from './pureDelete'
const mixedArray: object[] = [
[], {name:'Jack'}, {name: 'Jane', age: 32}, ['description']
]
const objectOnly: object[] = pureDelete(mixedArray, (val) => Array.isArray(val))
console.log(mixedArray, objectOnly)
(7) 가변 인수 함수와 순수 함수
- 함수를 호출할 때 전달 인수의 개수를 제한하지 않는 것을 가변 인수(variadic arguments)라고 하며 매개변수 앞의 …은 잔여나 전개 연산자가 아니라 가변 인수를 표현하는 구문임
5-5) 튜플 이해하기
- 파이썬 등에는 튜플이 존재하지만, 자바스크립트에서는 튜플이 없으며 단순히 배열의 한 종류로 취급됨.
let tuple: any[] = [true, 'the result is ok']
- 그러나 any[] 형태는 타입 기능을 무력하므로 다르게 선언할 수 있음
const array: number[] = [1, 2, 3 ,4]
const tuple: [boolean, string] = [true, 'the result is ok']
(1) 튜플에 타입 별칭 사용하기
- 일반적으로 튜플 사용시에는 타입 별칭(alias)로 튜플의 의미를 명확하게 함
'TypeScript' 카테고리의 다른 글
Do it! 타입스크립트 프로그래밍 7장 (0) | 2020.05.07 |
---|---|
Do it! 타입스크립트 프로그래밍 6장 (0) | 2020.05.06 |
Do it! 타입스크립트 프로그래밍 4장 (0) | 2020.04.28 |
Do it! 타입스크립트 프로그래밍 3장 (0) | 2020.04.27 |
Do it! 타입스크립트 프로그래밍 2장 (0) | 2020.04.25 |