일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- ASP.NET
- 캐나다취준
- JUNCTION2023
- Semantic Versioning
- SemVer
- useState
- 개발자를 위한 글쓰기 가이드
- 알고리즘
- 시스템디자인
- JSBridge
- CSS방법론
- 글또 10기
- CSS
- 코드트리
- 캐나다개발자
- framer
- 개발자 원칙
- React-Router-Dom
- framer-motion
- TS
- Framer motion
- typescript
- react
- Effective Typescript
- 글또
- 테오의 스프린트
- 이펙티브타입스크립트
- 타입스크립트
- VS Code
- 회고
- Today
- Total
큰 꿈은 파편이 크다!!⚡️
이펙티브 타입스크립트 🦅 1장 본문
들어가며
타입스크립트는 지금 회사에서도 사용하고 있지만 굉장히 얕은 지식과 스킬들로 연명하고 있는 느낌을 지속적으로 받았다. 사실 학습해야할 게 많다보니 우선순위에서 밀리기도 하던 중에.. 이펙티브 타입스크립트 스터디에 참여하게 되었다. 확실히 오프라인 스터디를 하면 책임감이 생겨 더 열심히 하는 것 같다 ^^
스터디를 계기로 학습 내용을 기록하고자 한다.
⚠️
- 이펙티브 시리즈는 초급자를 대상으로 하는 책이 아니다. 책의 서문에도 나와있듯이, 초급자/중급자가 전문가로 발전할 수 있을 정도의 난이도를 제공한다.
- 책의 모든 내용을 요약하는 것이 아닌, 읽어보며 정리하고 싶은 내용+스터디 내용+내가 궁금해서 찾아본 내용 등, 책을 기반으로 재구성한 혼합체..에 가깝다.
- 이하 작성의 편의를 위해 타입스크립트 -> TS, 자바스크립트 -> JS로 표기한다 ㅎㅎ
1. TS와 JS의 관계
TS는 JS의 상위 집합superset이다
- JS로 작성된 프로그램에 문법 오류가 없다면 유효한 TS 프로그램이다
- 각각 다른 확장자(.js
, .ts
)를 사용하지만 .js
를 .ts
로 사용해도 동일하다 (마이그레이션의 이점)
- 즉, 모든 JS프로그램은 TS이다
TS는 정적 타입 시스템이다
- TS는 JS 런타임 동작을 모델링하는 타입 시스템을 가지고 있으므로, 런타임에 오류를 발생시킬 코드를 미리 찾는다 (모두 찾는것은 아님)
- ex. state
에 없는 capitol
속성을 사용하는 문제에 대해 오류를 나타낸다
// HIDE
export const foo = true;
const states = [
{name: 'Alabama', capital: 'Montgomery'},
{name: 'Alaska', capital: 'Juneau'},
{name: 'Arizona', capital: 'Phoenix'},
// ...
];
// END
for (const state of states) {
console.log(state.capitol);
// ~~~~~~~ Property 'capitol' does not exist on type
// '{ name: string; capital: string; }'.
// Did you mean 'capital'?
}
타입 체커 영역
- ex. 하지만 states
에서부터 오타를 낸다면 어떨까? 아래와 같이 interface
를 통해 객체 타입을 명시하여 오류를 찾을 수 있다. 이는 '타입 체커를 통과한 TS'영역이며, 우리가 평소 작성하는 TS코드이다.
interface State {
name: string;
capital: string;
}
const states: State[] = [
{name: 'Alabama', capitol: 'Montgomery'},
// ~~~~~~~~~~~~~~~~~~~~~
{name: 'Alaska', capitol: 'Juneau'},
// ~~~~~~~~~~~~~~~~~
{name: 'Arizona', capitol: 'Phoenix'},
// ~~~~~~~~~~~~~~~~~~ Object literal may only specify known
// properties, but 'capitol' does not exist in type
// 'State'. Did you mean to write 'capital'?
// ...
];
for (const state of states) {
console.log(state.capital);
}
- TS는 JS의 상위 집합이다, 라는 말이 어색하게 느껴지는 이유는 타입 체크된 TS영역 때문이다. 프로그램에 오류가 발생하지 않더라도 타입 체커가 오류를 표시한다.
const a = null + 7; // Evaluates to 7 in JS
// ~~~~ Operator '+' cannot be applied to types ...
2. TS 설정 이해하기
- tsc --init
을 통해 tsconfig.json
설정 파일을 생성하여 동료들, 도구들과 공유하자
- 설정을 제대로 사용하기 위해 noImplicitAny
와 strictNullChecks
를 이해하자
- NoImplicitAny
: 변수들이 미리 정의된 타입을 가져야 하는지 여부를 제어
- ex1. 아래의 경우 TS는 a, b를 any
타입으로 추론한다
// tsConfig: {"noImplicitAny":false}
function add(a, b) {
return a + b;
}
- ex2. noImplicitAny
를 설정하면 오류를 나타낸다. 명시적으로 any
타입을 선언하거나 더 분명한 타입을 선언하여 해결할 수 있다
// tsConfig: {"noImplicitAny":true}
function add(a, b) {
// ~ Parameter 'a' implicitly has an 'any' type
// ~ Parameter 'b' implicitly has an 'any' type
return a + b;
}
- TS는 타입 정보를 가질 때 가장 효과적이므로 되도록 noImplcitAny
를 설정하자!
- noImplicitAny
설정을 해제하는 순간은 기존 JS코드들을 TS로 마이그레이션하는 과정에만 사용하자!
strictNullChecks
: null
, undefined
가 모든 타입에서 허용되는지 확인하는 설정
- ex. strictNullChecks
설정을 사용하는 경우, 명시적으로 null
타입이 지정된 변수에만 null
을 할당할 수 있다. undefined
도 마찬가지.
- 사용하려면 noImplicitAny
설정이 선행되어야 한다
// tsConfig: {"noImplicitAny":true,"strictNullChecks":true}
const x: number = null;
// ~ Type 'null' is not assignable to type 'number'
const properX: number | null = null;
- 이 두 가지 모두를 설정하여 엄격한 체크를 원한다면 strict
설정을 고려하자
3. 코드 생성과 타입은 관계없다
TS 컴파일러는 두 가지 역할을 독립적으로 수행한다
1) 최신 TS/JS가 브라우저에서 동작하도록 구버전의 JS로 트랜스파일transpile한다
💡 트랜스파일: translate+compile. 소스코드를 동일한 동작을 하는 다른 형태의 소스코드(다른 버전, 언어 등)로 변환하는 행위
2) 코드의 타입 오류를 체크한다
- 컴파일은 타입 체크와 독립적이므로, 타입 오류가 있어도 컴파일이 가능하다
- 어떤 부분에 오류가 있더라도 다른 부분을 테스트할 수 있게 해주는 장점이 있다
- 오류가 있을 때 컴파일하지 않으려면 tsconfig.json
에 noEmitOnError
를 설정한다
런타임에는 타입 체크가 불가능하다
- 자바스크립트로 컴파일되는 과정에서 모든 인터페이스, 타입, 타입 구문은 제거된다
- ex. shape instanceof Rectangle
의 형식으로 체크할 때, Rectangle
은 컴파일을 거치면 타입이 아닌 '값'이 된다
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
// ~~~~~~~~~ 'Rectangle' only refers to a type,
// but is being used as a value here
return shape.width * shape.height;
// ~~~~~~ Property 'height' does not exist
// on type 'Shape'
} else {
return shape.width * shape.width;
}
}
- 런타임에도 타입 정보를 유지하려면 추가 작업이 필요하다
- ex. 위 코드에서 해당 속성이 존재하는지 체크하는 방식으로 변경한다. 속성 체크('height' in shape
)는 런타임에 일어나지만, shape
의 타입이 Rectangle
임을 타입 체커가 보정하므로 오류가 사라지고 shape.width
에 정상적으로 접근할 수 있다.
function calculateArea(shape: Shape) {
if ('height' in shape) {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width;
}
}
타입 연산은 런타임에 영향을 주지 않는다
- ex. TS에서는 리턴 값에 타입을 줬다고 생각할 수 있지만(asNumber1
) 실제 JS(asNumber2
)에서는 아무것도 하지 않는다. 값을 정제하기 위해서는 런타임의 타입을 체크하고, JS연산을 통해 변화시켜야 한다. (asNumber3
)
function asNumber1(val: number | string): number {
return val as number;
}
//in js
function asNumber2(val) {
return val;
}
//👍
function asNumber3(val: number | string): number {
return typeof(val) === 'string' ? Number(val) : val;
}
- 타입과 런타임 동작이 무관하다는 점을 잊지말자!
- 타입은 JS 변환 시점에 제거되므로 런타임 성능에 영향을 주지 않는다
- 대신 TS 컴파일 오버헤드가 발생할 수 있다
4. 구조적 타이핑(Structural typing)에 익숙해지기
JS는 덕 타이핑 기반이며, TS는 이를 모델링하기 위해 구조적 타이핑을 사용한다
💡 duck typing: 객체가 어떤 타입에 부합하는 변수와 메서드를 가질 경우 객체를 해당 타입에 속하는 것으로 간주하는 방식 (ex. 만약 어떤 새가 오리처럼 걷고 꽥꽥거린다면 그 새를 오리로 간주한다)
- 함수를 작성할 때 매개변수 속성들이 매개변수 타입에 선언된 속성만을 가질것이라 생각하지만 TS 타입시스템에서는 표현할 수 없음
- ex. 매개변수 타입은 Vector2D
이지만 동일한 속성을 가진 NamedVector
를 매개변수로 사용해도 동작함
interface Vector2D {
x: number;
y: number;
}
function calculateLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface NamedVector {
name: string;
x: number;
y: number;
}
const v: NamedVector = { x: 3, y: 4, name: 'Zee' };
calculateLength(v); // OK, result is 5
5. any
타입 지양하기
TS의 타입 시스템은 점진적gradual이고 선택적optional이며, 이 특성들의 핵심은 any타입이다.
- 점진적: 코드에 타입을 조금씩 추가할 수 있다 (JS를 TS로 마이그레이션하는 경우)
- 선택적: 언제든지 타입 체커를 해제할 수 있다
any
타입에는 타입 안전성이 없다
- ex. string
타입은 number
타입이 필요한 곳에서 오류없이 실행될 수 있기때문에 문제를 일으킨다
let age: number;
age = '12';
// ~~~ Type '"12"' is not assignable to type 'number'
age = '12' as any; // OK
age += 1; // OK; at runtime, age is now "121"
any
는 함수 시그니처를 무시한다
- 함수 작성 시 호출하는 쪽은 약속된 타입의 입력을, 함수는 약속된 타입의 출력을 반환하지만 any
타입을 사용하면 이 약속을 어길 수 있다
any
타입은 언어 서비스가 적용되지 않는다
- any
타입인 심벌은 자동완성, 도움말, 이름 변경 등의 언어 서비스를 사용할 수 없다
- 이는 생산성을 떨어뜨린다
any
타입은 버그와 타입 설계를 감춘다
- 매개 변수를 잘못 넣어도 타입 체커를 통과해서 버그를 낳는다
- 타입 설계는 중요하기 때문에, 설계가 명확히 보이도록 타입을 일일이 작성해야 한다
- any로 인해 개발 경험이 나빠지면 타입 시스템의 신뢰도가 떨어진다
1장 마무리
TS를 통해 원활한 협업이 이루어지면 생산성이 향상된다. 개인적으로 TS를 사용하면서 가장 편리했던 점은 자동완성과 함수를 사용할 때 어떤 타입의 변수를 넣는지 알려주는 도움말이었다 ㅎㅎ
책이 확실히 난이도가 있긴 한건지 가장 쉬울 1장을 읽는데도 꽤 시간이 걸렸다..🫠
TS설정파일에는 소홀했는데 noImplicitAny가 생각보다 훨씬 중요한 역할을 하고 있어서 기억에 남는다.
타입과 런타임이 상상 이상으로 무관하다는 점도 인상깊다. TS의 원리를 조금 더 이해한 느낌이다.
이번 장에서 배운.. 면접질문으로 나올수있는것들!
- 왜 TS를 사용하는가
- Transpile vs Compile
'Web FE' 카테고리의 다른 글
이펙티브 타입스크립트 🦅 2장 (2) (0) | 2022.09.28 |
---|---|
이펙티브 타입스크립트 🦅 2장 (1) (0) | 2022.08.28 |
BEM: CSS 네이밍 방법론 (0) | 2022.08.06 |
🔨 웹에서 RTSP 스트리밍 + video autoplay 삽질기 (0) | 2022.07.23 |
🍪 쿠키와 세션 (feat. 웹에서 로그인을 유지하는 원리) (0) | 2022.07.09 |