본문 바로가기
Book Review/Effective Typescript

Day4_EffectiveTypescript 스터디

by 예 강 2023. 6. 16.

2022년 11월 29일 글입니다.

Item16. number 인덱스 시그니처 보다는 Array, 튜플, ArrayLike 를 사용하기

요약 1. 배열은 객체이므로 키는 숫자가 아니라 문자열이다. 인덱스 시그니처로 사용된 number 타입은 버그를 잡기위한 순수 타입스크립트 코드다.
요약 2: 인덱스 시그니처에 number을 쓰기보다는 Array나 튜플, ArrayLike 타입을 이용하는게 좋다.

  • 자바스크립트는 혼란스러운 언어다. 그 예로
  • 자바스크립트에서는 객체의 키로 문자열만 사용할 수 있다. 만약 숫자를 사용하면 자바스크립트 런타임이 문자열로 변환한다.
  • 하지만 배열은 객체인데 숫자인덱스를 사용하는게 당연하다. 하지만 object.keys(x)를 하면 문자열로 출력된다.

=> 타입스크립트는 이러한 혼란을 바로잡기 위해 숫자 키를 허용하고 문자열 키와 다른것으로 인식한다.

이러한 키값의 타입의 혼돈으로 인해 저자는 인덱스 시그니처 x[i]의 방법보다느 다른 방법을 추천하고 있다.
예를들어

이런식으로 배열에서 키값을 읽어 들여서 그 key를 for문을 돌려서 할당하는 방법을 추천하고 있다.
왜냐? number인지 str인지 신경 안써도 되게!
string이 number에 할당 될 수 없어서 이상하게 보이지만 배열을 순회하는 코드 스타일에 대한 실용적인 허용이니 받아들이라 ^-^고 책에 쓰여있다.

  • 인덱스를 순회하는 여러 방법
  1. fot of 구문 쓰기
  1. forEach 구문 쓰기
  1. for문쓰기
  • 타입이 불확실 할때, for-in은 다른 루프에 비해 몇배나 느리다.
  • 실제 런타임에서는 타입스크립트의 실용적인 허용이 사라져서 런타임에 사용되는 키는 string 타입이다.

어떤 길이를 가지는 배열과 비슷한 형태의 튜플을 사용하고 싶다면 타입스크립트의 ArrayLike 타입을 사용하자.

ArrayLike란 ? 유사배열이란 뜻으로 배열의 인자인 length 변수를 가지는 배열이다.

Item17. 변경 관련된 오류 방지를 위해 readonly 사용하기

요약 1. 설계에서 함수가 매개변수를 수정하지 않는다면 readonly로 선언하는게 좋다.
요약 2. readonly를 사용하면 변경하면서 발생하는 오류를 방지할 수 있고, 변경이 발생하는 코드도 쉽게 찾을 수 있다.
요약 3. readonly는 얕게 동작한다.
요약 4. const와 readonly의 차이를 알자

  • 자바스크립트에서는 명시적으로 언급하지 않는 한, 함수가 매개변수를 변경하지 않는다고 가정한다. 그러나 이러한 암묵적인 방법은 타입체크에 문제를 일으킬 수 있기 때문에, 명시적인 방법을 사용하는 것이 모두에게 좋다. 그래서 변경 관련된 오류 방지를 위해서 명시적으로 readonly를 쓰자고 한다.
  • readonly number[]는 number[]와 구분되는 타입으로 여겨지고 몇가지 차이가 있다.
    1. 배열의 요소를 읽을 수 있지만 쓸 수는 없다.
    1. length를 읽을 수는 있지만 바꿀수는 없다.
    2. 배열을 변경하는 pop을 비롯한 변경 메서드들을 호출할 수 없다.
  • 이렇게 동작하면 number[]의 기능이 더 많으므로 readonly number[]은 서브타입이 된다.
  • currPara의 주소를 참조하는 push를 하면서 다음에 currPara를 0으로 만드는 오류덩어리 문장이다. 현재는 Readonly로 선언된 currPara를 변경하려고 해서 접근 불가능 하다는 오류가 뜨고 있다.
  • 참조 값을 수정해주어도 여전히 readonly 인 currParasms paragraphs.push에 할당할 수 없다고 나온다. 여기서 처리하는 방법을 배워보자

옳게된 방법
1. currPara의 복사본을 전달한다

paragraphs.push([...currPara])
  1. paragraphs를 readonly string[]의 배열로 변경
    const paragraphs : (readonly string[])[] =[]
  1. readonly 속성을 제거하기 위해 단언문을 쓴다.
paragraphs.push(currPara as string[])

const와 Readonly의 차이점

const
1. 변수 참조를 위한 것
2. 변수에 다른 값을 할당 / 대입할 수 없음

readonly
1. 속성을 위한 것
2. 속성은 앨리어싱을 통해 변경될 수 있음

  • 1을 위한 예제
const foo = 123; // 변수 참조
var bar: {
    readonly bar: number; // 속성의 경우
}
  • 2를 위한 예제

위를 보면 foo가 iMutateFoo에서 readonly를 제거함으로써 앨리어싱된다.
(앨리어싱이란? js에서 앨리어싱이란 새롭게 별칭지어진다 라는 뜻으로 해석되는 것 같다.)

즉 매개변수로 전달된 foo를 새롭게 별칭(재정의?)했기 때문에 readonly는 지워진다.

기본적으로 readonly는 내가 속성을 변경하지 못함은 보장하지만 객체를 다른 사람에게 넘길 경우에는 이게 보장되지 않고 다른사람이 객체의 속성을 변경할 수 있다.

readonly는 얕게 동작한다.

  • 만약 readonly 배열이 있다면 그 객체 자체는 readonly가 아니다.
  • readonly로 선언된 배열 자체에 변경되는 push 같은 메소드는 허용되지 않지만 그 내부의 객체 자체를 건드는 setFullYear은 값 변경이 가능하다.
  • 이는 readonly가 얕게(shallow) 동작하기 때문
    깊게(deep) 동작하는 readonly는 제네릭을 쓰면 되지만 이는 까다롭기 때문에 ts-essentials의 DeepReadonly 제네릭 라이브러리를 쓰면 된다고 한다.