타입스크립트 복습 - 2

Stulta Amiko·2022년 7월 18일
0
post-thumbnail

배열의 디스트럭처링 / 제네릭

디스트럭처링 자체는 저번에 복습 1때 다뤘었던 내용이다
객체와 같은 자료형을 디스트럭처링 하는것을 다뤘었다면 이번에는 배열을 디스트럭처링 하는 방법이다.

let array: number[] = [1,2,3,4,5]
let [first,second,third,...other] = array
console.log(first,second,third,other)

이렇게 생긴 코드가 있다. array는 숫자 배열이다
두번째줄이 디스트럭처링을 하는 코드인데 보면 순서대로
first second ... 하다가 마지막에 ... other 하는모습을 볼 수 있다.
그러면 1,2,3까지는 각각 first second third에 할당되는데 ...other은 레스트 연산자이기 때문에 4와 5가 한번에 other에 할당되는 모습을 볼 수 있다.
그리고 세번째 줄에서 출력하게 되면
1,2,3,[4,5] 이런식으로 출력되게 될것이다.


제네릭 자체는 다른언어에서도 쓰긴한다. 대표적으로 자바같은 언어에서 사용하고 타입스크립트에서도 제네릭을 많이 사용하는 편이다.

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

위와같은 코드가 있다고 할때 예를들어 지금같은경우는 제네릭 T를 이용해서 배열을 받고있지만 만약에 제네릭을 사용하지 않고 number를 사용한다고 하자
하지만 입력되는 배열이 number 타입이 아닐수도 있고 boolean이나 string일 수도있는 것이다. 그에 맞게 알아서 바꿔주는게 제네릭인것 같다.

배열의 메서드 filter / map / reduce

filter

let num : number[] = range(1,11)

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

console.log(odds,evens)

먼저 필터함수부터 살펴보면 num 이라는 array가 있고

odds 와 evens 배열이 있다. 이는 num배열을 가공해서 만들어졌다.
먼저 odds 부터 보면 num 배열에 filter 메서드를 걸어서 가공하는 모습이다.
value를 2로 나눴을때 0이 아닌 수들을 필터링 해서 넣는것이다. 보면 이제 filter의 파라미터에는 함수가 들어가고 value 의 값대로 순환을 하면서 이제 참인것들만 배열에 남기는 것임을 알 수 있다.
evens는 odds 와 반대로 작동하므로 짝수만 남긴다
따라서 위 코드를 출력하게 되면 odds에는 홀수만 남게되고 evens에는 짝수만 남게 되어 출력될것이다.


map

map 메서드는 배열을 순회하며 함수를 실행시키고 저장한 배열을 반환한다.

let num : number[] = range(1,11)

let arr: number[] = num.map((val: number)=> val*val)

console.log(arr)

쉽게 이해하기 위해서 작성된 예제를 보면서 분해하면서 이해를 해보자

filter와 마찬가지로 num 이라는 1부터 10까지의 배열을 생성해준다.

arr에서는 map 메서드를 사용하는데 사용방법은 위에서 했던것과 비슷하지만 결과는 다르다.
filter는 반환값이 true인것만 남기는 반면에 map메서드는 함수의 실행결과를 각 인덱스에 저장하는 형태로 작동을 한다.

위 코드를 실행하게 되면 1부터 10까지 제곱한 수가 arr에 저장된 코드가 출력될것이다.


reduce

메서드는 배열의 각 요소에 대해 주어진 reduce 함수를 실행하고, 하나의 결과값을 반환합니다.

let num : number[] = range(1,101)
let add : number = num.reduce((result: number,value: number)=> result+value,0)

console.log(add)

위와 같은 코드를 보면서 이해를 해보자 이번엔 num 배열이 1부터 100까지 담겨있는 배열이다. result와 value가 존재하는데 여기서 result는 기존의 값이고 value는 그다음 인덱스의 값을 의미한다. 예를들어 위 코드는 1부터 100까지 돌면서 더한값을 result로 반환하고 최종적으로 result가 출력된다.
인덱스가 0부터 시작하니깐 인덱스가 0일때의 값은 1이다. result의 초기값은 0이고 첫 순회때는 1이 result에 저장될것이다. 1+0 = 1 이니 저장이되고 그다음에는 1+2 = 3이 result에 저장될것이다 이런식으로 차례로 계산하게 되면 최종적으로 5050을 반환하게 될것이다.
이런식으로 reduce 메서드는 배열을 하나로 만들어주는 결과를 생성한다.

iterator / generator

iterator 혹은 반복기라고 불리는것은 자주 사용되는 연산이다.
반복기를 구현한 코드를 보자

const createRangeIterable = (from: number,to: number) =>{
    let currentValue = from
    return{
        next(){
            const value = currentValue < to ? currentValue++ : undefined
            const done = value == undefined
            return {value,done}
        }
    }
}

rangeIterable이라는 클래스를 만들어서 반복기를 사용할 수 있다.
반복기를 제공하는 역할을 하는 함수를 반복기 제공자라고 한다.
next 메서드를 리턴하기 때문에 위 코드는 반복기를 제공하는 역할을 한다.

const iterator = createRangeIterable(1,4)
while(true){
    const {value,done} = iterator.next()
    if(done) break
    console.log(value)
}

createRangeIterable을 실행하기 위해 iterator 라는 변수를 만들었다.
range와 유사하게 createRangeIterable에 파라미터로 범위가 들어간다. iterator의 next 메서드를 호출하게되면 value와 done을 반환하게 된다.
그리고 done이 true가 되면 반복문이 멈추게 된다.
creatRanceIterable에서 currentvalue는 to가 될때까지 값을 늘리게 되고 목표한 값까지 반복이 끝나면 value가 undefined가 되게되고 따라서 done도 true가 되게 된다.

iterable한 createRangeIterable로 구현한 iterator를 for of에 넣게 되면 오류가 발생한다.

오류를 보면 [Symbol.iterator]를 가지고 있어야 한다고 나온다.
createRangeIterable을 함수에서 클래스로 바꿔준다음에 [Symbol.iterator]을 넣어준다.

class createRangeIterable{
    constructor(public from: number,public to: number) {}
    [Symbol.iterator](){
        const that = this
        let currentValue = that.from
        return{
            next(){
                const value = currentValue < that.to ? currentValue++ : undefined
                const done = value == undefined
                return{value,done}
            }
        }
    }

}

const iterator = new createRangeIterable(1,4)

for(let value of iterator){
    console.log(value)
}

위와같은 방식으로 짜게된다.
이제 함수가 아니기 때문에 constructor 즉 생성자를 따로 넣어줘야한다.
그리고 for of는 iterable 한 객체만 들어갈 수 있기 때문에 클래스 내부에 iterator을 넣어준 모습을 볼 수 있다.


제네릭이 들어가는 iterable도 있다

class StringIterable implements Iterable<string>{
    constructor(private strings: string[] = [] ,private currentIndex: number = 0) { }
    [Symbol.iterator]() : Iterator<string> {
        const that = this
        let currentIndex = that.currentIndex, length = that.strings.length

        const iterator: Iterator<string> = {
            next(): {value: string,done: boolean} {
                const value = currentIndex < length ? that.strings[currentIndex++] : undefined
                const done = value == undefined

                return {value,done}
            }
        }
        return iterator
    }
}

for(let value of new StringIterable(['hello','world','!']))
    console.log(value)

Iterable을 implements 하는것이 보인다. 자신을 구현하는 클래스가 Symbol.iterator를 제공하는것을 명시하는 것이다.
생성자에서 파라미터로 배열을 받는것이 보인다 후에 인덱스를 받는데 디폴트값으로 0이 설정되어있다. 후에는 iterator가 나오는데 보면 Iterator<>로 선언된게 있다 이는 타입을 명확히하는것을 뜻한다.
내부는 위에서 만들었던 숫자 배열 iterator랑 유사하다
나머지 부분은 따로 해석하지 않겠다.


generator

function *키워드로 yield를 호출할 수 있고
yield는 return 과 유사하다

function* gen(){
    console.log("generator start")
    let value = 1
    while(value < 4)
        yield value++
    console.log('generator end')
}

for(let value of gen()){
    console.log(value)
}

예전에 작성한 게시글에서 발생기의 예제 코드이다 보게 되면 특이하게 function 에 * 이붙는 것을 알 수 있다. 그리고 값을 yield로 반환하는데
gen에 있는 값을 for of로 추출하는 모습을 볼 수 있다. 여기서 좀 특이한 점이 이제
for..of의 경우iterable한 경우에만 사용할 수 있는데 generator의 MDN의 설명을 보면 다음과 같다.

Generator 객체는 generator function 으로부터 반환된 값이며 이터러블 프로토콜과 이터레이터 프로토콜을 준수합니다.

라고 써있으므로 function *로 선언된 gen() generator 객체는 iterable 하기때문에 for of에도 적용할 수 있는 것이다.

그래서 위 코드를 실행하게 되면
start 1 2 3 end 이런식으로 끝나게 되는데 단일스레드로 작동하는게 아닌 다중스레드로 동작하는것 처럼 보이게 해준다.

위에서 구현한 stringIterator의 경우 generator를 사용하면 더 간결하게 작성할 수 있다.

class StringIterable<T> implements Iterable<T>{
    constructor(private values: T[] = [] ,private currentIndex: number = 0) { }
    [Symbol.iterator] = function *(){
        while(this.currentIndex<this.values.length){
            yield this.values[this.currentIndex++]
        }
    }
}

for(let value of new StringIterable([1,2,3,4,5]))
    console.log(value)

for(let value of new StringIterable(['hello','world','!']))
    console.log(value)

정확히는 string만 iterator 하는게 아니라 모든 배열을 iterator하게 해주는 클래스이다. 위에서 복잡하게 value와 done을 관리하던것과는 다르게 단순하게 생성기를 이용해서 값을 반환하는것을 볼 수 있다.

0개의 댓글