Home Effective Typescript 스터디 5주차 (Item 21 ~ 25)
Post
Cancel

Effective Typescript 스터디 5주차 (Item 21 ~ 25)

DesktopView

이 게시물은 SW 마에스트로 연수생 간 진행한 Effective Typescript 스터디의 개인 정리 내용입니다

[ Item 21 ] 타입 넓히기


타입 넓히기

어떻게 완벽한 기술이 있을 수 있을까요, 아니나 다를까 한참을 타입추론을 찬양하다가 드디어 타입추론도 만능이 아니라는 아이템이 나왔습니다.

변수를 초기화할 때 타입을 명시하지 않는다면 보통 타입체커는 타입 넓히기 에 들어갑니다.

타입 넓히기란 ?

Example case 1

1
2
3
4
5
6
7
8
9
interface Vector3 {x:number; y:number; z:number}
function getComponent(vector:Vector3, axis:'x'|'y'|'z') {
	return vector[axis]
}

let x="x";
let vec = {x:10, y:20, z:30};
getComponent(vec,x);
// Error | 'string' 형식 은 'x'|'y'|'z' 에 포함되지 않는다는 에러

case 1 코드는 런타임에서는 에러가 발생하지 않으나, 편집기에선 오류가 검출됩니다.

함수의 두번째 arg, axis 에서는 ‘x’‘y’‘z’ 타입을 기대했지만, x 는 타입 넓히기를 통해 ‘string’ 타입으로 추론되었기 때문입니다.

여기서 x의 타입은 ‘string’ 이 될수도 있었고, any 타입이 될 수도 있었습니다.

이보다 타입 넓히기가 더 많은 선택지를 가지는 예시는 다음과 같습니다.

Example case 2

1
const mixed = ['x',1]

이 케이스는 다음과 같은 후보군을 갖고 있습니다.

  • (’x’1 )[]
  • [’x’,1]
  • [ string , number ]
  • readonly [string, number]
  • (stringnumber)[]
  • readonly (stringnumber)[]
  • [any,any]
  • any

여기서 타입체커는 최대한 작성자의 의도를 추론합니다.

case 2에선 (stringnumber)[] 으로 추론됩니다.

이를 방지하기 위해선 case 2에는 적용되지 못했으나 case 1과 같이 일반적으로 let 대신 const 를 사용하면 타입 후보가 더 좁혀집니다.

1
2
3
const x = 'x' // 타입이 'x' 로 추론됨
let vec = {x:10, y:20, z:30};
getComponent(vec,x); // 정상!

이 외에도 타입 체커에게 추가적인 문맥을 제공하거나, const 단언문을 사용하는 방법도 존재합니다.

결론

타입추론을 믿으며 타입 구문을 작성하지 않았을 때 편집기 에러를 만난다면, 타입 넓히기도 한번 의심해보세요 !

하지만 개인적인 의견으론 추론을 잘 사용하다가 편집기 에러를 만나는 경우에만 타입구문을 사용해주면 되는 것 아닐까? 싶습니다.

[ Item 22 ] 타입 좁히기


타입 좁히기

앞선 타입 넓히기와 다르게 이번 아이템은 타입 좁히기 입니다.

타입 추론 챕터에 있긴 하지만, 좁히기는 타입 추론과는 오히려 상반된 관계가 아닐까 싶습니다. 타입 넓히기에서 밝혔다시피 타입추론 시 타입체커는 보통 타입 넓히기에 들어가고, 실제로 코드에서 타입을 명시하지 않는다면 조금 더 넓은 범위의 타입으로 추론합니다.

당연한 말이지만 타입 좁히기는 넓은 타입범위에서 더 좁은 범위로 타입을 좁히는것을 말합니다.

백문이 불여일견 그 대표적인 예시로는 null 체크가 있습니다.

Example case 1

1
2
3
4
5
6
7
8
const el = document.getElementById('foo'); // 여기서의 타입은 HTMLElement | null
if(el) {
  el // 여기서의 타입은 HTMLElement !
  el.innerHTML = "Party Time".blink()
} else {
  el // 여기서는 null !
  alert("No element #Foo")
}

case 1 과 같이 일반적으로 React Hook이나 DOM 에 접근해서 받아오는 정보같은 경우는 단일타입이 아닌 유니언타입인 경우가 대다수입니다.

하지만 여기서 조건문을 통해 스코프를 만들고 그 안에 들어갈 수 있는 Element 를 강제할 수 있는데 이를 타입 좁히기라 합니다.

이 외에도 예외처리를 활용하는 방법, instanceof 를 사용하는 방법, 속성을 체크하는 방법 element in Type , 태그를 활용하는 방법 { … type : “TYPE” }

그리고 마지막으로 커스텀 함수를 도입하는 “사용자 정의 타입 가드” 방법이 있습니다.

이것도 말로만 해서는 이해하기가 쉽지 않으니 예시 코드를 같이 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
function isInputElement(el:HTMLElement): el is HTMLInputElement {
  return "value" in el
}

function getElementContent(el:HTMLElement) {
  if (isInputElement(el)) {
    el // 여기서의 타입은 HTMLInputElement
    return el.value
  }
  el // 여기서의 타입은 HTMLElement
  return el.textContent
}

이러한 여러가지 방법들을 통해 적절히 타입을 좁힌다면 타입스크립트를 더 효과적으로 사용할 수 있습니다!

결론

항상 타입을 정확하게 명시하고 코드를 작성한다면, 사실 거의 필요가 없는 기능입니다.

하지만 너무 많은 타입명시는 코드를 장황하게 만들고, 너무 많은 공수를 필요로 합니다.

따라서 만약 이미 타입명시를 정확하게 하고 계시다면 문제가 없겠으나.

저와 같이 그렇지 않은 분들은 이러한 좁히기 스킬들을 통한 방법이 더 효율적일지, 아니면 타입을 귀찮지만 만들어서 명시 해 주는것이 더 효율적일지는 각자의 판단에 맡기는게 좋아 보입니다.

최근에 실제로 Typescript 를 사용하면서 느낀 점은, 현재 읽고있는 내용들이 진짜 전혀 쓸모없는 내용들은 아니었다는 생각이 듭니다.

물론 읽지 않은 상태에서 TS를 써보지 않아서 그 Before & After 가 명확하진 않지만 확실히 왜 타입에러가 발생하고, 대략 어떻게 해결해야겠다 라는 느낌이 쉽게 오는 것 같습니다.

[ Item 23 ] 한꺼번에 객체 생성하기


한꺼번에 객체 생성하기

아이템 20에서 “변수의 값은 변경될 수 있으나, 타입스크립트의 타입은 일반적으로 변경되지 않는다.”라는 키워드가 있었습니다. 이번 아이템은 그 키워드에 대한 예시정도로 이해하면 좋을 듯 합니다.

1
2
3
const pt = {};
pt.x = 3 // {} 형식에 x 속성이 없어요!
pt.y = 4 // {} 형식에 y 속성이 없어요!

만약 위와같은 코드를 작성하면 에러를 만나볼 수 있습니다. 이는 pt의 타입이 {} 값을 기준으로 추론되기 때문입니다. 그렇다고 pt 의 타입을 정의한다면 그것도 그것대로 에러를 일으킵니다.

따라서 가장 좋은 방법은 해당 속성들을 미리 담은채로 한꺼번에 객체를 생성 하는 것입니다.

… 네 … 뭐 이게 끝입니다.

결론

객체를 생성할 땐 속성들을 미리 담은채로 생성해주세요!

개인적 의견으로는 프론트에서 들어올만한 정보들은 어느정도 예측 가능한 정보들일텐데 굳이 빈 객체 생성 후 억지로 속성들을 추가 할 이유가 있을까 싶습니다.

[ Item 24 ] 일관성있는 별칭 사용하기


일관성있는 별칭 사용하기 && 내용이자 결론 . . .

당연한 말이겠지만, 별칭의 값을 변경하면 원래 속성의 값도 변경됩니다.

이러한 별칭의 과도한 사용은 제어흐름의 분석에 악영향을 줍니다.

Polygon 예제에서 볼 수 있듯이, 별칭을 사용하는 것이 나쁜것은 절대 아닙니다. 코드의 중복을 줄이기 위해 별칭을 사용하는 것은 분명 가독성 측면에서는 도움이 됩니다.

다만 올바르게 사용하지 못한다면, 오히려 오류를 발생시킬 수 있습니다.

… 네 … 끝입니다…

아직까지는 이 아이템에서 이 이상의 통찰은 얻을 수 없었습니다.
추후 언젠가 더욱 TS에 대한 지식이 깊어지면 다시 한번 봐야할 것 같습니다.

[ Item 25 ] 비동기 코드에는 콜백 대신 Async 함수 사용하기


비동기 코드에는 콜백 대신 Async 함수 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 fetchURL(url1,function(response1) {
  fetchURL(url2,function(response2) {
    fetchURL(url3,function(response3) {
      console.log(1)
    })
    console.log(2)
  })
  console.log(3)
})
console.log(4)
//출력
//4
//3
//2
//1

사실 만나보진 못했지만 악명높다고 하는 콜백지옥 코드입니다. 가독성이 떨어지는것은 물론이고 코드에서의 순서와 실제 출력의 순서마저 달라서 혼란을 가중시킵니다.

이 문제의 해결을 위해 Promise 를 거쳐 현재 Async/Await 키워드가 자리잡게 되었습니다.

이 자리에서 async 의 필요성과 특징을 설명하는것은 불필요할 듯 하고 오늘은 타입 관점에서의 필요성을 설명하고자 합니다.

왜 async/awiat 일까요?

기능적으로는 당연하겠지만 타입스크립트 관점에서 사용해야 할 이유는 다음과 같습니다.

  • 콜백보다는 프로미스가 타입 추론이 쉽습니다.
    • 타입추론이 쉬운 코드는 우리가 의도한대로 작동할 확률이 높다는 의미입니다.
  • 프로미스보다는 async 로 작성된 코드가 더 직관적입니다.
    • 설명할 가치가 있나요? 직관적입니다
  • async 는 프로미스를 반환하도록 강제할 수 있습니다.
    • 프로미스도 당연하게도 프로미스를 반환합니다.
    • 하지만 그 반환값의 타입은 입력된 타입들의 유니온타입이고 때론 프로미스로 래핑된 프로미스가 반환되기도 합니다.
  • async 함수에서 프로미스를 반환하면 항상Promise<T> 가 반환됩니다.
    • async에서는 래핑된 프로미스가 반환되지 않습니다.

결론

만약 비동기처리, 프로미스를 다뤄야 한다면 async 를 사용하자!

단순히 가독성 뿐만 아니라 실제 타입체크에서도 상당한 이점이 있다!

라곤 했지만, 자동변속기 차량 좋은거 많은데 굳이굳이 “손맛” 느끼겠다고 자동변속기 떼고 수동변속기를 내돈주고 달아서 쓸 필요가 있을까요?

편한 async 있으면 당연히 async 써야하지 않을까 싶습니다.

This post is licensed under CC BY 4.0 by the author.

비전공자에게 CS지식이란 무엇일까?

Modern Javascript Deepdive [ Chapter 04 변수 ]