Effective Typescript에 대한 스터디 진행 내용을 요약한 것입니다.
TS는 JS의 상위호환(Superset)이다.
그럼에도 TS와 JS에는 명확한 차이점이 존재한다.
TS는 다음과 같이 타입을 명시해줄 수 있다.
function greet(who: string) {
console.log("Hello", who)
}
greet("jn")
위에서 :
과 string
은 JS에는 없는 문법으로, 이처럼 타입을 사용함으로써 JS는 TS의 영역으로 들어가게 된다.
let city = 'new york city'
console.log(city.toUppercase()) // 'toUppercase' 속성이 'string' 형식에 없습니다. 'toUpperCase'를 사용하시겠습니까?
TS는 위의 예시에서 문제점을 찾아낸다.
이는 TS가 자체적으로 city
의 타입을 string
으로 추론한 결과이다.
이와 같은 타입 추론은 TS의 중요한 특징이다.
타입 추론을 통해 TS는 컴파일 전에 많은 오류를 수정할 수 있도록 도와준다.
const status = [
{ name: "Alabama", capital: "Montgomery" },
{ name: "Alaska", capital: "Juneau" },
{ name: "Arizona", capital: "Phoenix" },
//...
]
for (const state of status) {
console.log(state.capitol) // 'capitol' 속성이 '{ name: string, capital: string }'형식에 없습니다. 'capital'을 사용하시겠습니까?
}
위처럼 TS는 추가적인 타입 구문이 없이도 오류를 찾아낸다.
코드의 의도가 무엇인지 파악하고 이에 따른 오류를 잡아낸다는 것이다.
이를 통해 우리는 코드와 의도가 다른 부분을 손쉽게 찾을 수 있다.
하지만 이 또한 언제나 정확하게 의도와 들어맞을 수는 없기 때문에, 타입 체커가 원활하게 동작하기 위해서 TS는 interface를 정의할 것을 권장하고 있다.
interface State {
name: string;
capital: string;
}
const status: State[] = [ // State interface를 구현한 것들로 이루어진 배열이다.
{ name: "Alabama", capital: "Montgomery" },
{ name: "Alaska", capital: "Juneau" },
{ name: "Arizona", capital: "Phoenix" },
//...
]
for (const state of status) {
console.log(state.capitol) // '{ name: string; capitol: string; }' 형식은 'State' 형식에 할당할 수 없습니다. 개체 리터럴은 알려진 속성만 지정할 수 있짐나 'State' 형식에 'capitol'이(가) 없습니다. 'capital'을(를) 쓰려고 했습니까?
}
위처럼 interface를 미리 정의해둠으로써 TS가 작성자의 의도를 정확히 파악할 수 있도록 하였다. 이는 TS가 정확한 오류를 명시할 수 있도록 만들어준다.
다음의 예시를 보자.
const x = 2 + "3" // OK, type is string
const y = "2" + 3 // OK, type is string
위 코드는 일반적인 언어에서는 비정상적인 동작을 할 코드이다. 그러나 TS에서는 자동으로 타입을 추론하기 때문에 위의 코드 같은 경우도 자동으로 형변환이 이루어져 잘 동작한다.
다만 아래 코드처럼 JS에서만 인정되고 TS에서는 반려되는 일도 있다.
const a = null + 7 // Evaluates to 7 in JS
const b = [] + 12 // Evaluates to '12' in JS\
alert("Hello", "Typescript") // alerts "Hello"
위의 세 문장은 JS에서는 자동 형변환을 통해 실행이 되나, TS 내부적으로 타입 오류로 판정된다.
위와 같은 경우가 많지는 않지만, 우리는 JS의 잘못된 자료형끼리의 연산을 방치해둘 수 없기 때문에 TS를 사용하는 것이 권장된다. 물론 사용 여부는 온전히 개발자의 선택이다(책에서 필수적이라고 언급하지 않았다).
any
타입으로 추론한다. 그러나 noImplicitAny
설정이 true
라면, any
를 이용한 타입 추론을 시도하지 않고 바로 에러를 발생시킨다. false
라면 타입에 대한 명시가 되어있지 않은 자료라도 에러를 발생시키지 않고 암묵적으로 any
로 타입을 추론한다.프로젝트를 처음 시작한다면 처음부터
noImplicitAny
를true
로 설정하여 개발자로 하여금 타입을 명시하도록 해야한다. 그러면 TS가 문제를 발견하기 쉬워지고, 코드의 가독성이 좋아지며, 개발자의 생산성이 향상됩니다. JS로 작성된 레거시 코드를 TS로 마이그레이션할 때 유용하다.
null
과 undefined
가 모든 타입에서 허용되는지에 대한 설정이다.const x: number = null
위의 코드는 strickNullChecks
가 true
라면 에러를 발생시키고, false
라면 에러를 발생시키지 않는다.
const x: number | null = null
위 코드처럼 null
을 명시해줌으로써 null
에 대한 타입 추론 오류 방지를 할 수 있다.
const el = document.getElementById('status') // Object is possibly 'null'
if(el) {
el.textContent = 'Ready' // OK, null has been excluded
}
el!.textContent = 'Ready' // OK, we've asserted that el is non-null
혹은 위 코드처럼 if문을 사용하여 el이 무조건 존재한다는 조건을 걸어주거나, !
를 사용하여 이 자료가 무조건 존재할거라고 TS에게 강제성을 더해줄 수 있다.
아마 기본 설정으로 이 설정이 들어갔을 확률이 높은데, 위의 두 설정을 한번에 포괄하는 설정이다.
strict가 true
라면 noImplicitAny
와 strictNullChecks
가 모두 true
인 상황이고, false
라면 그 반대인 것이다.
아주 엄격하거나, 아주 느슨하거나 라고 생각하면 좋을 것 같다.