0%

assets

webpack 을 기본적인 사용 방법과 assets 를 다루는 방법을 살펴 본다.

먼저 webpack 을 사용하기 위해서 webpack 을 다운 받아야 한다.

1
yarn add webpack webpack-cli -D

webpack 은 자바스크립트 파일을 하나로 번들링 하여 여러 종속성을 관리할 수 있도록 해준다. 이렇게 다운 받으면 webpack 명령어를 사용하면 되는데, 이때에는 글로벌로 설치해야 한다. 글로벌로 설치하고 싶지 않다면 package.json 에 scripts 을 추가하면 되는데 뒤에 내용에서 다룬다.

다음으로 기본적인 webpack 의 설정 파일을 작성한다

webpack.config.js

1
2
3
4
5
6
7
8
9
const path = require("path");

module.exports = {
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
};

webpack 을 실행하면 webpack 은 webpack.config.js 파일을 찾아서 설정을 확인하고 webpack 을 실행한다.

  • entry : 앱의 진입점이 되는 js 파일
  • output : 번들링 한 후 번들링 된 파일의 이름, 패스 지정

위에 설정 대로하려면 dits 폴더를 만들고 /src/index.js 파일을 만든다. 그리고 dist 폴더안에 index.html 파일을 생성한 후 다음과 같이 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>

dist 폴더 내에서 번들링 결과로 main.js 파일이 생길 것이므로 main.js 추가해 주었다.

그리고 webpack 을 실행하는 명령을 package.json 에 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "test",
"version": "1.0.0",
"private": true,
"license": "MIT",
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
},
"dependencies": {
"lodash": "^4.17.21"
}
}

추가적으로 종속성 관리를 확인해 볼 수 있도록 lodash 를 설치하고 yarn add lodash src 폴더와 index.js 파일을 추가한다.

1
2
3
4
5
6
7
8
9
10
11
import _ from "lodash";

function component() {
const element = document.createElement("div");

element.innerHTML = _.join(["Heelo", "webpack"], " ");

return element;
}

document.body.appendChild(component());

이제 yarn build 를 터미널에서 실행하면 dist 폴더에서 번들링된 결과물을 확인할 수 있다.

Assets 가져오기

다음으로 assets 을 가져오는 방법을 알아본다. webpack 의 빌트인 모듈을 사용하면 이미지와 폰트는 설정 만으로 import 할수 있다. 하지만 css 의 경우 추가적인 설치와 설정이 필요하다.

css 를 사용하기 위해서는 style-loader, css-loader 가 필요하다. 추가적으로 scss 를 사용하기 위해서는 sasssass-loader 가 필요하다.

1
yarn add style-loader css-loader sass sass-loader

이미지와 폰트는 빌트인 모듈을 사용가능하다. webpack.config.js 에 다음 내용을 추가한다.

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
const path = require("path");

module.exports = {
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: "asset/resource",
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
},
],
},
};

이제 간단한 scss 파일과 index.js 에 파일을 추가하도록 한다.

utils.scss :

1
2
3
4
5
@mixin squre($size) {
$contentSize: 2rem * $size;
width: $contentSize;
height: $contentSize;
}

style.scss:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@import './utils.scss';

@font-face {
font-family: 'MyFont';
src: url('./Dongle-Regular.ttf') format('truetype');
font-weight: 400;
}

@font-face {
font-family: 'MyFont';
src: url('./Dongle-Light.ttf') format('truetype');
font-weight: 300;
}

@font-face {
font-family: 'MyFont';
src: url('./Dongle-Bold.ttf') format('truetype');
font-weight: 700;
}

.box {
color: white;
@include squre(5);
background: url('./profile.png');
background-size: contain;
img {
@include squre(1);
}
}

html {
font-family: 'MyFont';
font-weight: 400
}

.light {
font-size: 4rem;
font-weight: 300;
}
.regular {
font-size: 4rem;
font-weight: 400;
}
.bold {
font-size: 4rem;
font-weight: 700;
}

font 를 사용하기 위해 구글 폰트에서 Dongle 채를 다운 받아왔다.

그리고 적당한 이미지 파일을 추가하도록 한다. 여기서는 profile.png 파일을 추가했다.

이제 index.js 에서 리소스를 사용하는 코드를 추가한다.

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
32
33
import _ from "lodash";
import "./style.scss";
import Icon from "./profile.png";

function component() {
const element = document.createElement("div");

element.innerHTML = _.join(["Heelo", "webpack"], " ");
element.classList.add("box");

const myIcon = new Image();
myIcon.src = Icon;

element.appendChild(myIcon);

return element;
}

function divBox(text, className) {
const el = document.createElement("div");

el.innerHTML = text;
el.classList.add(className);

return el;
}

document.body.appendChild(component());
document.body.appendChild(divBox("안녕하세요 웹펙 테스트 중입니다.", "light"));
document.body.appendChild(
divBox("안녕하세요 웹펙 테스트 중입니다.", "regular")
);
document.body.appendChild(divBox("안녕하세요 웹펙 테스트 중입니다.", "bold"));

이제 다시 yarn build 를 수행하면 dist 폴더에 index.html 에서 결과물을 확인할 수 있다.

Code splitting

리액트와 같은 SPA 앱 을 만들때는 한번에 모든 페이지에 대한 리소스를 다운로드 받아야 하기 때문에 번들링 사이즈가 커지게 된다. 이렇게 처음 진입할때 모든 리소스를 다운 받으면 다운 받기 까지 시간도 많이 걸리고, 특히 네트워크 상황이 좋지 않은 곳에서는 좋지 않은 사용자 경험을 줄수 도 있다.

code splitting 은 번들링 된 부분의 코드를 분활하여 필요할때 분활한 리소스를 다운 받도록 하는 기술이다. React 에서 Code splitting 을 하기 위해서 어떻게 해야 하는지 살펴 본다.

Import

먼저 import() 를 사용한다. import 문은 thenable 을 채택하기 때문에 import 가 완료됬을때 then 을 통해 특정한 작업을 하도록 할 수 있다. 예를들어 특정 컴포넌트를 불러 오는 작업이 끝났을때 특정 작업을 하다록 다음과 같이 작성할 수 있다.

1
2
3
import('./components/SomeComponent').then(() => {
console.log('특정 컴포넌트 불러오기 완료')
})

React.lazy

React.lazy 를 사용하면 필요할때 컴포넌트를 불러오는 작업을 할 수 있다. lazy 는 콜백을 받는 함수인데 콜백 안에서 import() 를 실행하면 된다.

1
React.lazy(() => import('./components/SomeComponent'));

이렇게 하면 SomeComponent 에 관한 리소스는 따로 분활하고 컴포넌트가 필요한 시점에 리소스를 다운받게 된다.

어디에서 진행해야 할까?

그렇다면 Code splitting 은 어느 단위, 어디에서 진행해야 할까? 어떤 답이 있는것은 아니지만, 사용자에 행동에 비추어 봤을대 페이지 단위로 코드를 분활하는것이 좋다. 사용자들은 특정 페이지에서 다시 리소스를 다운받아서 기다리는것은 오래 걸린다고 생각하겠지만, 어떤 페이지를 넘어갈때에는 상대적으로 그럴 수 있다고 생각하기 때문이다. 페이지 컴포넌트를 불러오는 시점에서 Code splitting 을 해주도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { Suspense } from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";

const LongPage = React.lazy(() => import("./components/LongPage"));
const ShortPage = React.lazy(() => import("./components/ShortPage"));

function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<BrowserRouter>
<Routes>
<Route path="/" element={<ShortPage />} />
<Route path="/long" element={<LongPage />} />
</Routes>
</BrowserRouter>
</Suspense>
);
}

export default App;

보통 페이지 컴포넌트를 불러오는 시점은 React-router 을 사용하는 시점이다. 다음과 같이 작성하면 페이지에 들어갈때, 리소스를 따로 다운 받는 것을 확인할 수 있다.

브라우저 개발자 도구를 통해 확인한 모습 :

처음 / 페스로 접근했을때 src_components_ShortPage_js.chunk.js 를 다운 받고, /long 패스로 접근했을때 src_components_LongPage_js.chunk.js를 다운 받았다.

Suspense

코드에서 Suspense 를 사용하는것을 볼 수 있다. 이는 리소스를 다운 받을때 데체 컨텐츠로 fallback 에 넘겨준 컴포넌트를 사용하겠다는 것이다. 코드를 분활했기 때문에 리소스를 다운로드를 받을 수 밖에 없고, 이때는 필연적으로 빈 컨텐츠를 볼것이기 때문에 대체 컨텐츠를 명시한 것이다.

여담으로 useTransition 을 사용하면 오래 걸리는 작업을 뒤로 미룰수 도 있다. 만약 특정 컨텐츠 안에서 분활된 리소스를 다운받는 작업을 트리거 하는 함수 가있다면 이 함수를 useTransition 에 리턴값에 두번째 값으로 얻어지는 함수로 감싸서 나중에 처리하도록 할 수 있다. 코드 분할 예제는 아니지만 useTransition 을 사용한 예제는 다음과 같다.

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
import React, { useCallback, useState, useTransition } from "react";
import { Link } from "react-router-dom";

function LongPage() {
const [value, setValue] = useState("");
const [isPending, startTransition] = useTransition();

const handleChange = (e) => {
startTransition(() => {
setValue(e.target.value);
});
};

return (
<>
<Link to={"/"}>short</Link>
<input type="text" value={value} onChange={handleChange} />
{isPending ? (
<div>Loading...</div>
) : (
Array(10000)
.fill(0)
.map(() => <div>{value}</div>)
)}
</>
);
}

export default LongPage;

메서드

HTTP 메서드에 메서드에 종류와 주요 특징, 사용예시를 정리한다;

주요 메서드

메서드 동작
GET 리소스 조회
POST 요청 데이터 처리, 주로 등록에 사용
PUT 리소스 대체, 해당 리소스가 없으면 생성
PATCH 리소스 부분 변경
DELETE 리소스 삭제

기타 메서드

메서드 동작
HEAD GET 과 동일하지만, 메시지 부분을 제외한, 헤더와 상태 정보만
OPTIONS 대상 리소스에 대한 통신 가능 메서드 정보
CONNECT 대상 자원으로 식별되는 서버에 대한 터널을 설정
TRACE 대상 리소스에 대한 경로를 추적

Methods

메서드에 대한 상세 설명

1. GET

  • 리소스 조회
  • 서버에 저달하고 싶은 데이터 query 를 통해서 전달 (http://expamle.com?name=koo&age=25)
  • 메시지 바디를 사용해서 데이터를 전달할 수 있지만, 지원하지 않는 곳이 많아서 권장하지 않음. (최신 스펙에서는 막지 않음)

2. POST

body 에 데이터를 넣어서 서버에게 특정 프로세스를 처리하게 하는 메서드, 보통은 신규 리소스를 등록하는 일을 한다.

주요 쓰임

  1. 새 리소스 생성
  2. 요청 데이터 처리(프로세스)
    • 배달 시작 같은 프로세스의 시작 같은 의미에서도
    • post 결과로 새로운 리소스가 생성되지 않을 수 도 있음.
    • 컨트롤 URI (POST /orders/(orderId)/start-delevery) 실무에서는 리소스 가지고만 URI 를 설계하기는 불가하기 때문에 컨트롤 url 를 만들기도 함.
  3. 다른 메서드로 처리하기 애매한 경우
    • JSON 으로 조회 데이터를 넘겨야 하는데, GET 메서드를 사용하기 어려운 경우
    • POST 에 바디 넣어서 보냄

하지만, 조회 데이터는 되도록이면 GET 을 쓰는게 낳음. 캐싱 하는등의 작업을 할 수 있기 때문에

3. PUT

리소스를 대체

  • 리소스가 있으면 완전히 대체
  • 리소스가 없으면 생성
  • 쉽게 이야기해서 덮어버림.

중요! 클라이언트가 리소스 전체를 알아서 식별함

post 는 리소스를 전체를 몰라도 생성하거나 프로스를 처리할 수 있음.

  • ex) post /members

put 은 리소스 전체를 식별해야 함.

  • ex ) put /members/100

4. PATCH

리소스 부분 변경

POST 도 똑같은 작업을 할 수 있다. 만약 PATCH 가 지원 안되는 서버에 경우 POST 를 사용해서 처리하면 된다.

5. DELETE

리소스를 제거한다.


HTTP 메서드 속성

  • 안전(Safe Methods)
  • 멱등(Idempotent Methods)
  • 캐시 가능(Cacheable Methods)

안전(Safe Methods)

호출해도 리소스를 변경하지 않는 메서드를 안전한 메서드라고 한다.

Ex) → GET, HEAD

멱등(Idempotent)

여러번 호출해도 항상 결과가 같은 메서드를 멱등하다고 한다.

  • GET: 여러번 조호하든 결과가 같다.
  • PUT: 결과를 대체하기 때문에 최종 결과는 같다. 덮어씌기 때문에
  • DELETE: 결과를 삭제한다. 여려번 해도 결과는 같다.
  • POST: 멱등이 아니다! 두번 호출하면 같은 결과가 두면 발생함.

활용

  • 자동 복구 매커니즘
  • 서버가 TIMEOUT 등으로 정상 응답을 못주었을때, 클라이언트가 같은 요청을 다시 해도 되는가? 에 판단 근거가 됨.

Q ) 만약 재요청 중간에 다른 곳에서 리소스를 변경해 버리면?

  • 멱등은 외부 요인으로 중간에 리소스가 변경되는 것 까지는 고려하지 않는다.

캐시 가능 (Cacheable)

  • 응답 결과 리소스를 캐시해서 사용해도 되는가?
  • GET, HEAD, POST, PATCH 캐시 가능
  • 실제로는 GET, HEAD 정도만 캐시로 사용 (키가 같아야 하는데)
    • POST, PATCH 는 본문 내용까지 캐시 키로 고려해야 하는데 구현이 쉽지 않음.

Factory pattern

생성 패턴중 하나로서 객체의 생성을 추상화 한다. 팩토리 패턴을 사용하여 객체 생성 로직을 클라이언트에 노출하지 않고 생성할 수 있고, 공통 인터페이스를 사용하여 새로 생성된 객체를 참조할 수 있다.

구현

공통 Interface 및 구현하는 class 생성

interface :

1
2
3
interface Animal {
speak(): void;
}

Cat :

1
2
3
4
5
class Cat implements Animal {
speak(): void {
console.log("야옹");
}
}

Dog :

1
2
3
4
5
class Dog implements Animal {
speak(): void {
console.log("명");
}
}

TODO :
class UML 그려 넣기

Factory class 생성

객체에 대한 정보를 기반으로 인스턴스를 리턴하는 클래스를 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type instanceType = "Cat" | "Dog";

class AnimalFactory {
static createAnimal(type: instanceType) {
switch (type) {
case "Cat":
return new Cat();
case "Dog":
return new Dog();
default:
return null;
}
}
}

URI 와 웹 브라우저 요청 흐름

URI는 로케이터 이름 또는 둘다 추가로 분류될 수 있다

url은 로케이션만 위치를 지정

urn 은 이름만, 이름을 지정 이름만으로 실제 리소스를 찾을 수 있는 방법이 보편화 되지 않음

url

  • 프로토콜 (스키마) http(80) https(443)
  • 유저 인포 유알엘에 사용자 정보를 포함해서 인증 - 거의 사용 x
  • 호스트명
  • 포트번호
  • 패스 - 계층적 구조
  • 쿼리 파라미터 - 키 밸류에 형태로
  • 프래그먼트 html내부 북마크 # 서버로 전송하는 정보 아님

웹브라우저 요청 흐름

  • dns 조회 - ip
  • 포트 - 프로토콜 별로 생략가능한것 있음
  • http 요청 메시지 생성
  • 메시지 전송 1. 웹브라우저가 http메시지 생성 2. socket 라이브러리를 통해 전달 TCP / IP 연결 3. 데이터 전달 4. 패킷 생성, HTTP메시지 포함

http 응답 메시지 서버에서 생성 받아서 렌더링 등등의 작업

http기본

원래는 html 을 보낼 용도로 설례된 프로토콜 이였으니 지금은 모든것을 http 를 통해서 전달함

서버간의 데이터도 http를 사용 특수한 경우에만 직접 tcp 연결

http1.1 가장 많이 사용되는 버전

특징

  • 클라이언트 서버 구조
  • 무상태 스태이스리트 , 비연결성
  • http 메시지
  • 단순함 , 확장 가능

클라이언트 서버 구조

요청 응답 구조, 클라이언트는 요청을 보내고 응답을 대기함, 서버가 요청에 대한 결과를 만들어서 응답

무상태 프로토콜(stateless)

서버가 클라이언트에 이전상태를 저장하지 않음
-> 상태를 저장하지 않기 때문에 서버를 바뀌어도 장애 없이 교체 가능 (확장성이 높아진다.)

한계

상태를 유지해야 하는 경우도 있다. (예를들면 로그인 같은 겨웅)
무상태 이기 때문에 한번한번 요청에 너무 많은 정보를 포함시켜 요청하기도 한다.

비연결성

http 는 연결을 유지하는 모델이 아니여서 한번 요청과 응답 후에는 연결을 끊어버리는 특징이 있다.

한계

매번 새로 연결을 맺는 과정을 해야 함. (3 way handshake). 자바스크립트 css 이미지등 많은 자원을 계속 다운로드 해야 함. 요즘은 http 지속연결 persistent connection 으로 어느정도 연결을 유지함.

인터넷 네트워크

HTTP 학습을 위한 사전 기본 학습

IP

인터넷 프로토콜

  • 지정한 IP 주소에 데이터 전달
  • 패킷 이라는 단위로 데이터 전달

IP 패킷 정보

출발지 IP, 목적지 IP, 기타 …

요청할때랑 응답할때는 다른 노드로 연결될 수 도 있다.

IP 프로토콜의 한계

비연결성

  • 패킷을 받을 대상이 없거나 서비스 불능 상태여도 패킷 전송, 보낸 사람은 모름..

비 신뢰성

  • 중간에 패킷이 사라지면?
  • 패킷이 순서대로 안오면?

프로그램 구분

  • 같은 IP 를 사용하는 서버에서 통신하는 애플리케이션이 둘 이상이면?

⇒ 이런 문제를 해결하는 것이 TCP UDP 이다

TCP UDP

인터넷 프로토콜 스택의 4계층

  • 애플리케이션 계층 - HTTP, FTP
  • 전송 계층 - TCP, UDP
  • 인터넷 계층 - IP
  • 네트워크 인터페이스 계층

TCP/IP 패킷 정보

출발지 PORT, 목적지 PORT, 전송제어, 순서, 검증정보

TCP 특징

전송 제어 프로토콜(Transmission Control Protocol)

  • 연결 지향 - TCP 3 way handshake ( syn → syn + ack → ack) - 가상연결 (논리적으로만, 물리적 연결은 모름)
  • 데이터 전달 보증 ( 데이터를 전달하면 서버에서 받았다는 응답을 보내줌)
  • 순서 보증 - 클라이언트에서 보낸 순서대로 서버가 받지 못하면 잘못된 순서부터 다시보내라고 서버에서 응답함
  • 신뢰할 수 있는 프로토콜

UDP 특징

  • 기능이 거의 없음.
  • IP 랑 거의 똑같음
  • PORT 가 추가됨! → 하나의 IP 에서 여러 application이 다른 PORT 를 배정 받음.
  • 체크섬 (메세지 검증 데이터) 정도 추가됨.
  • 장점 : 데이터 전달 및 순서가 보장되지 않지만, 단순하고 빠름

PORT

하나의 IP 에서 둘 이상을 연결하라면 → PORT 를 사용해서 분리함.

DNS

IP 는 외우기 어렵고 변경 될 수 있다 Domain Name System → 전화번호부 같은 서버를 제공

도메인 명으로 DNS 서버에에 요청 IP 주소 응답 받음 실제로 IP 주소로 요청

typescript

  • Conditional Types
  • Mapped Types
  • Utility Types

Type Alias 와 Interface 뭘 써야 할까?

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
32
33
34
type PositionType = {
x: number;
y: number;
}
interface PositionInterface {
x: number;
y: number;
}

// object
const obj1: PositionType = {
x: 1,
y: 1,
}
const obj2: PostionInterface {
x: number;
y: number;
}

//class
class Pos1 implements PositionType {
x: number;
y: number;
}
class Pos2 implements PositionIneterface {
x: number;
y: number;
}

// Extends
interface ZPositionInterface extends PositionInterface {
z: number;
}
type ZpoistionType = PostionType & {z: number}

차이점은 Interface 만 결합이 가능하다. 정의가 2개 되어 있으면 두개가 합쳐짐.

1
2
3
4
// 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 };
}

PickType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Video = {
id: string;
title: sstring;
url: string;
data: string;
}
// video 에 모든 정보를 반환함 - 무거움
function getVideo(id: string): Video {
return {
id,
title: 'video',
url: 'dfdf',
data: 'byte-data'
}
// meta 정보만 반환
function getVideoMetaData(id: string): Pick<Video, 'id' | 'title'> {
return {
id,
title: 'title'
}
}

OmitType

원하는 것만 빼는것 가능

1
type VideoMetaData = Omit<Video, 'url' | 'data'>

Record

Basic Type

JavaScript 는 다이나믹 타입으로서 실행되는 도중에 타입이 바뀐다.

TypeScript 는 static 타입으로서 실행되기 전에 타입을 정한다.

제공되는 타입

기본 자료형

  • Boolean
  • Number
  • String
  • Null
  • Undefined
  • Symbol
  • Array: object

프로그래밍을 도울 몇가지 타입

  • Any, Void, Never, Unknown
  • Enum
  • Tuple: object

Primitive Types

오브젝트나 레퍼런스 형태가 아닌 실제 값을 저장하는 자료형

  • boolean
  • number
  • string
  • symbol
  • null
  • undefined

literal 값으로 Primitive 타입의 서브 타입을 나타낼 수 있다.

또는 래퍼 객체로 만들 수 있다.

1
2
3
new Boolean(false) // typeof : object
new String('world') // typeof: object
new Number(42) // typeof: object

but, 이렇게 만든건 객체임 primitive type 이 아니다.

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 nullobject이다.

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)

any 는 계속해서 개체를 통해 전파된다.

결국, 타입 안정성을 잃는 대가로 이어짐.

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
32
function returnAny(message: any): any {
console.log(message);
}

const any1 = returnAny("리턴은 아무거나");

any1.toString();

// 어떻게 전파 되는가?
let losselyTyped: any = {};

const d = losselyTyped.a.b.c.d;

// 동적인 경우는? any를 피하기 위해서
function leakingAny(obj: any) {
const a = obj.num;
// b 도 any 가 됨
const b = a + 1;
return b;
}

// c 도 any 가 됨
const c = leakingAny({ num: 0 });

function leakingAny1(obj: any) {
const a: number = obj.num;
// b 도 number 이다.
const b = a + 1;
return b;
}

// but 함수 안에서 사용하기 전에 처리를 하거나 type guard 를 사용하는 것이 좋음.

unknown

any 가 전파되는 등의 문제를 해결하기 위해서 unknown을 사용한다.

type guard 를 연계해서 사용할 수 있다.
스코프 안 (type guard) 안에서는 특정 자료형임을 확정 지을 수 있다.

1
2
3
4
5
6
7
8
9
declare const maybe: unknown;

if (maybe === true) {
const aBoolean: boolean = maybe;
}

if (typeof maybe === "string") {
const aString: string = maybe;
}

any 보다 type-Safe 한 타입이다.
any와 같이 아무거나 할당할 수 있지만, 컴파일러가 타입을 추론할 수 있게끔 타입의 유형을 좁히거나 타입을 확정해 주지 않으면 다른 곳에 할당 할 수 없고, 사용할 수 없다.

never

보통은 return 에 사용된다.
never를 리턴하게 되면 아무것도 리턴 하지 않는다는 의미가 된다.
따라서 함수가 끝나버리면 안되고 에러를 던지는 등의 동작을 하게 된다.

  • never는 모든 타입의 서브타입이여서 모든 타입에 할당할 수 있다.
  • 하지만 never에는 어떤 것도 할당할 수 없다.
  • any 조차도 never에게 할당할 수 없다.
  • 잘못된 타입을 넣는 실수를 막고자 할 때 사용하기도 한다.
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
function error(message: string): never {
throw new Error(message);
}

// 이때도 never라고 추론하게 된다.
function fail() {
return error("faild");
}

// 무한 반복에 예시에서 never를 사용할 수 있다.
function infinitiLoop(): never {
while (true) {}
}

let a: string = "hello";

// 잘못된 타입을 넣는 실수를 막을 수 있다.
// a 는 스코프 안에서 never가 된다.
if (typeof a !== "string") {
a;
}

declare const b: string | number;

// b는 number 가 된다.
// 이런식으로 type guard를 사용할 수 있다.
if (typeof b !== "string") {
b;
}

void

void로 지정된 변수에는 undefined도 할당할 수 없다.
void를 리턴하는 함수의 경우 리턴받은 void 변수를 가지고 어떤것도 하지 않겠다는 의미이다.

명시적으로 void 로 선언해서 리턴 타입을 가지고 아무것도 하지 않겠다는 의미임.

TypeScript

TypeScript란 무엇인가?

자바 스크립트에 타입을 명시. 확장 한 것

코드를 실행 시키기 전에 에러를 잡아줌.

자바스크립트가 실행되는 어떤 환경에서도 동작함.

Typed superset of JavaScript

컴파일해서 plain JavaScript 로 변환함.
but, 정통적인 컴파일과는 다름. 타이밍상 컴파일이라 많은 사람들이 Transpile 이라는 용어를 사용함.

TypeScript 설치 및 설정

  • node.js 설치

  • 크롬 설치

  • 타입 스크립트 컴파일러 설치

    1
    2
    npm i typescript -g 
    tsc source.ts

    node_module/.bin/tsc 에 tsc 있음.

  • 초기화
    tsc --init 실행하면 어떻게 컴파일 할것인지에 대한 설정 파일 tsconfig.json 생성.
    tscconfig.json 이 있는 폴더에서 tsc 실행하면 해당 폴더에 있는 모든 ts 파일이 설정에 맞게 컴파일 됨.

  • 와치 모드로 시작
    새롭게 파일이 생성되거나 저장될때 마다 tsc 를 실행해 주지 않고 와치 모드로 시작되서 변경 감지 후 자동으로 변환 시작하게 함.
    tsc -w

프로젝트에만 설치하기

글로벌로 설치한 것 지우기
npm uninstall typescript -g

npm init -y

npm i typescript -D 개발 라이브러리로 설치

글로벌이 아닐때에는 tsc 경로를 명시해줘야함. node_modules/.bin/tsc
npx tsc로 똑같이 사용 가능

모든 명령어 앞에다 npx 붙여서 사용.

보통은 package.json 에 script를 수정함.
"build": "tsc" npm run build 로 실행 가능.

First Type Annotation

ts 는 처임 초기화 된 변수를 토대로 type를 할당함. 만약 초기화 하지 않았다면 any 가 됨.

어떤 type인지 명시하는 것을 type annotation 이라고 하고 아래와 같이 사용한다.

1
2
let a: number;
a = 39;