webpack 은 자바스크립트 파일을 하나로 번들링 하여 여러 종속성을 관리할 수 있도록 해준다. 이렇게 다운 받으면 webpack 명령어를 사용하면 되는데, 이때에는 글로벌로 설치해야 한다. 글로벌로 설치하고 싶지 않다면 package.json 에 scripts 을 추가하면 되는데 뒤에 내용에서 다룬다.
리액트와 같은 SPA 앱 을 만들때는 한번에 모든 페이지에 대한 리소스를 다운로드 받아야 하기 때문에 번들링 사이즈가 커지게 된다. 이렇게 처음 진입할때 모든 리소스를 다운 받으면 다운 받기 까지 시간도 많이 걸리고, 특히 네트워크 상황이 좋지 않은 곳에서는 좋지 않은 사용자 경험을 줄수 도 있다.
code splitting 은 번들링 된 부분의 코드를 분활하여 필요할때 분활한 리소스를 다운 받도록 하는 기술이다. React 에서 Code splitting 을 하기 위해서 어떻게 해야 하는지 살펴 본다.
Import
먼저 import() 를 사용한다. import 문은 thenable 을 채택하기 때문에 import 가 완료됬을때 then 을 통해 특정한 작업을 하도록 할 수 있다. 예를들어 특정 컴포넌트를 불러 오는 작업이 끝났을때 특정 작업을 하다록 다음과 같이 작성할 수 있다.
이렇게 하면 SomeComponent 에 관한 리소스는 따로 분활하고 컴포넌트가 필요한 시점에 리소스를 다운받게 된다.
어디에서 진행해야 할까?
그렇다면 Code splitting 은 어느 단위, 어디에서 진행해야 할까? 어떤 답이 있는것은 아니지만, 사용자에 행동에 비추어 봤을대 페이지 단위로 코드를 분활하는것이 좋다. 사용자들은 특정 페이지에서 다시 리소스를 다운받아서 기다리는것은 오래 걸린다고 생각하겠지만, 어떤 페이지를 넘어갈때에는 상대적으로 그럴 수 있다고 생각하기 때문이다. 페이지 컴포넌트를 불러오는 시점에서 Code splitting 을 해주도록 한다.
보통 페이지 컴포넌트를 불러오는 시점은 React-router 을 사용하는 시점이다. 다음과 같이 작성하면 페이지에 들어갈때, 리소스를 따로 다운 받는 것을 확인할 수 있다.
브라우저 개발자 도구를 통해 확인한 모습 :
처음 / 페스로 접근했을때 src_components_ShortPage_js.chunk.js 를 다운 받고, /long 패스로 접근했을때 src_components_LongPage_js.chunk.js를 다운 받았다.
Suspense
코드에서 Suspense 를 사용하는것을 볼 수 있다. 이는 리소스를 다운 받을때 데체 컨텐츠로 fallback 에 넘겨준 컴포넌트를 사용하겠다는 것이다. 코드를 분활했기 때문에 리소스를 다운로드를 받을 수 밖에 없고, 이때는 필연적으로 빈 컨텐츠를 볼것이기 때문에 대체 컨텐츠를 명시한 것이다.
여담으로 useTransition 을 사용하면 오래 걸리는 작업을 뒤로 미룰수 도 있다. 만약 특정 컨텐츠 안에서 분활된 리소스를 다운받는 작업을 트리거 하는 함수 가있다면 이 함수를 useTransition 에 리턴값에 두번째 값으로 얻어지는 함수로 감싸서 나중에 처리하도록 할 수 있다. 코드 분할 예제는 아니지만 useTransition 을 사용한 예제는 다음과 같다.
// Only interrace can be merged. interface PostionInterface { z: number; }
type 은 computed properties 를 사용 가능하다.
1 2 3 4 5
type Person = { name: string; age: number; } type Name = Person['name'] // string
개념적인 차이
Interface 란 계약서이다. 규격 사항으로 정의하고 어떤 규격 사항대로 정의하도록 사용하고 싶다면 interface 를 사용한다.
type 은 데이터의 모습을 정의한다. 단순의 데이터의 모습을 정의한다면 type 을 사용하는것이 좋다.
Utility types
type 을 변형 시키는 것이 typescript 에서는 가능하다.
IndexType
1 2 3 4 5 6
const obj = { name: 'koo', }
obj.name // koo obj[name] // koo
Animal type :
1 2 3 4 5 6 7 8 9
type Animal = { name: string; age: number; gender: 'male' | 'female' } type Name = Animal['name'] // string type Gender = Animal['gender'] // 'male' | 'female'
type Keys = keyof Animal //'name' | 'age' | 'gender'
Pertson type :
1 2 3 4 5 6 7 8
type Person = { name: string; gender: Animal['gender'] } const person: Person = { name: 'koo', gender: 'male' }
MappedType
1 2 3 4 5 6 7 8
type Video = { title string; author: string; } type VideoOptional = { title?: string; author?: string; }
맵 타입으로 이런 작업을 할 수 있다.
1 2 3 4 5 6 7 8
// type 정의 안에서 in 사용 가능 type Optional<T> = { [P in keyof T]?: T[P] } type VideoOptional = Optional<Video>; const videoOp: VideoOptional = { title: 'hi' }
readonly:
1 2 3
type ReadOnly<T> = { readonly [P in keyof T]: T[P]; }
Proxy :
1 2 3 4 5 6 7
type Proxy<T> = { get(): T; set(value: T): void; } type Proxify<T> = { [P in keyof T]: Proxy<T[P>; }
Conditional type
type 에도 조건을 줄 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
type Check<T> = T extends string? boolean : number; type Type = Check<string> // boolean
type TypeName<T> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends undefined ? 'undefined' : T extends Function ? 'function' : 'object'
ReadOnly
1 2 3 4 5 6 7 8
type ToDo = { title: string; description: string; } // 위에서 작성했지만 사실 왠만한 utility type 은 정의되어 있다. function display(todo Readonly<ToDo>) { ... }
PartialType
1 2 3 4 5 6 7 8 9
type Todo = { title: string; description: string; label: string; priority: 'high' | 'low' } function updateTodo(todo: Todo, fieldsToUpdate: Partial<ToDo>) : ToDo { return {...todo, ...fieldsToUpdate }; }
TypeScript primitives type은 모두 소문자 이다. 따라서 TypeScirpt 에서 type을 명시할때 래퍼 객체를 사용하면 안된다.
boolean
래퍼 객체로 타입을 정하 지 않는다.
1 2 3 4 5
let isDone: boolean = false;
isDone = true;
console.log(typeof isDone); // boolean
number
10진수 , 16진수, 8진수, 2진수, 모두 가능.
NaN 가능.
1_000_000과 같은 표현 가능.
1 2 3 4 5 6 7 8 9 10 11
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let NotANumber: number = NaN;
let underscoreNum: number = 1_000_000;
string
1 2 3 4 5 6 7 8
const fullName: string = "JayoonKoo"; const age: number = 30;
const sentence: string = `Hello, My name is ${fullName}. I'll be ${age + 1} years old next month.`;
console.log(sentence);
symbol
new Symbol로 사용할 수 없다. Symbol 함수를 사용해서 만들어야 한다.
기본 설정으로 tsc를 만들었다면 오류가 날 수 있다. tsconfig.json으로 이동한 후에 lib를 열어서 ES2015, DOM을 추가해 준다.
Symbol 은 프리미티브 타입의 값을 담아서 사용한다. 고유하고 수정 불가능한 값을 만들어 주는데 주로 접근을 제어하는 용도로 사용하는 경우가 많다.
1 2 3 4 5 6 7 8 9
console.log(Symbol("foo") === Symbol("foo"));
const sym = Symbol();
const obj = { [sym]: "value", };
console.log(obj[sym]);
undefined, null
undefined 로 선언한 변수는 undefined, void만, null 로 선언한 변수는 null 만 받을 수 있다. 따라서 변수 자체로는 할 수 있는게 별로 없다.
null 과 undefined 는 다른 모든 타입의 서브 타입이다. --strictNullCheck 설정을 사용하지 않으면 number 에 null 또는 undefined가 할당될 수 있어서 문제가 있다. 따라서 해당 옵션을 항상 켜 놓는것이 좋다. "strict": true 로 해 두면 그 안에 있는 strictNullCheck 도 켜지게 된다.
하지만 number 같은 경우 undefined 나 null 이 될때가 있을 텐데 불편할 수 있다. 그럴때에는 union type을 사용한다. 나중에 타입 가드를 사용하여 제외하는 식으로 사용 된다.
1 2 3 4 5 6 7 8
let union: string | null = null;
let u: null = null;
union = "Mark";
console.log(u); console.log(typeof u);
JS 에서 null
무언가 사용할 준비가 덜 된 상태 null 이라는 타입은 null 이라는 값만 가질 수 있다.
런타임에서는 typeof null은 object이다.
JS 에서 undefined
값을 할당하지 않는 변수를 undefined라고 함. 무언가 아예 준비가 안된 상태. 프로퍼티가 없을 때에도 undefined 이다.
런타임에서 typeof undefined 하면 undefined이다.
object
non-primitive type 으로서 primitive type이 아닌 것을 사용할때 명시한다.
non-primitive type
not (number, string, boolean, bigint, symbol, null, or undefined.)
Array
원래 JS에서는 object이다. 같은 타입에 데이터만 가능하다.
사용 방법 :
Array<타입>
타입[]
1 2 3 4 5 6 7 8 9
// 보통은 이 방법을 사용함. let list: number[] = [1, 2, 3];
let listUnion: (number | string)[] = [1, 2, 3, "4"];
// 만약 데이터의 순서나 상황을 알고 있다면 튜플을 사용할 것
// jsx 에서 충돌 할수도 있음. let list2: Array<number> = [1, 2, 3];
tuple
순서도 맞아야 하고, 타입도 맞아야 하고, 길이도 맞아야 한다.
1 2 3 4 5 6 7 8 9 10
let x: [string, number];
// 순서도 맞아야 되고 타입도 맞아야 되고 길이도 맞아야 한다. x = ["hello", 39];
const person: [string, number] = ["mark", 49];
// 첫번째는 string, 두번째는 number임이 확실해 진다. // 세번째를 추가하면 오류가 나게 된다. const [first, second] = person;
any
귀찮다고 아무렇게나 사용하면 type system을 무너뜨릴 수 있다.
어떤 타입이어도 상관 없는 타입이다.
이걸 최대한 사용하지 않는것이 핵심이다. (타입 체크가 안되기 때문에 )
컴파일 옵션 중에는 any를 써야하는데 쓰지 않으면 오류를 뱉도록 하는 옵션도 있다. (noImplicitAny)