본문 바로가기

IT

Effective Typescript - 17 - 변경 과련된 오류를 방지하기 위한 readonly 속성 활용하기

아래 함수는 배열을 인자로 받아서 배열 내 모든 숫자를 합한 숫자를 반환하는 함수이다.

 

function sum_array(array: number[]): number {
    let sum = 0;
    while(true){
        const el:number | undefined = array.pop();
        if(el === undefined) break;
        sum += el;
    }

    return sum;
}

const array = [1, 2, 3, 4, 5];
console.log(sum_array(array)); // 15
console.log(array); // [] -> 배열이 빈 상태로 된다.

숫자를 모두 더했지만 sum_array 함수에서 인자로 넘긴 배열을 빈 배열로 만들어 버렸다. 인자로 넘긴 배열이 변경되는 것을 의도하지 않은 상황이라면 이와 같은 상황은 예상치 못한 결과를 초래할 수 있다.

 

위와 같이 문제가 될 수 있는 상황을 타입 스크립트 타입 체커를 통해 방지할 수 있다.

바로 'readonly' 접근제한자 속성을 활용하는 것이다.

function sum_array(array: readonly number[]): number {
    let sum = 0;
    while(true){
        const el:number | undefined = array.pop(); // 파라메터에 readonly 속성을 부여하면 pop() 함수 호출시 에러가 발생함..
        if(el === undefined) break;
        sum += el;
    }

    return sum;    
}

타입스크립트의 타입은 '범위'의 개념이라고 이해한다면, 'number[] 타입'은 'readonly number[] 타입'의 슈퍼셋이다.

아래의 예제를 보면 이해할 수 있다.

const a: number[] = [1, 2, 3];
const b: readonly number[] = [4, 5, 6];
const c: readonly number[] = a // 정상
const d: number[] = b // 비정상

 

한편, 객체에 사용될 수 있는 Readonly 제네릭도 활용할 수 있다.

const o: Readonly<Outer> = { inner : { x: 1} };
o.inner = {x: 3}; // 비정상
o.inner.x = 1 // 정상, Readonly 제네릭은 객체 얕게 동작한다.

마지막으로, readonly 접근 제한자 속성은 인덱스 시그니처에도 적용할 수 있다.

type TestType = {
    readonly [key: string]: string;
}

let test_obj: TestType = {
    a: 'a',
    b: 'b'
}

//test_obj.a = 3; // 오류.

//const와는 다르게 재 할당이 가능하다.
test_obj = {...test_obj, c: 'c'}; // 정상
test_obj = {...test_obj, d: 'e'}; // 정상

 

[요약]

* 함수의 형태를 명세할 때, 파라메터가 수정되지 않는 상황이라면, readonly 접근 제한자 속성을 부여하는 것이 일반적으로 유리하다.

* const와 readonly의 차이점을 이해해야 한다.

  * const : 재 할당을 방지한다. (런타임 동작)

  * readonly: 오브젝트 내 변조를 막는다. (타입 스크립트 타입 체커에서 걸러줌)

* readonly는 swallow 하게 동작한다는 점을 잊지말자.