기존 emotion-diary 프로젝트를 적용해보려 했지만 TypeScript가 익숙하지 않고 완성된 프로젝트에 적용하기엔 힘들다고 판단하여 새로운 프로젝트를 만들며 처음부터 적용해보려 한다.
목차
0. TypeScript란
1. TypeScript 적용
2. TypeScript 사용하기
TypeScript란
// 예시 (코딩앙마 Typescript #1 영상 중)
// 기존 javascript 실행시
function add(num1, num2){
console.log(num1 + num2);
}
add(); // NaN
add(1); // NaN
add(1,2); // 3
add(3,4,5); // 7
add("hello", "world"); // "helloworld"
// typescript 적용
function add(num1: number, num2: number){
console.log(num1 + num2);
}
add(); // err
add(1); // err
add(1,2); // 3
add(3,4,5); // err
add("hello", "world"); // err
JavaScript 특성상 매우 자유롭기 때문에 함수를 만들거나 연산할 때 예상되는 타입이외에 타입을 사용한다해도 정상적으로 실행되는 경우가 많다. 이렇게 되면 다른 개발자가 함수를 사용할 때 예상한 실행결과와 다르게 나타날 경우 그 개발자는 직접 코드를 살펴보며 오류를 찾아야한다. 이는 JavaScript가 연산을 할 때 타입을 크게 따지지 않는 특성과 동적언어이기 때문에 런타임 즉 실행시간에 타입이 결정되고 오류가 발견되기 때문에 오류를 미연에 방지하기가 힘들다.
이와 달리 TypeScript는 정적언어이기 때문에 컴파일 단계에서 타입이 결정되기 때문에 맞지않은 문법이 존재한다면 오류를 확인하고 방지할 수 있다. 따라서 TypeScript를 사용하여 코드를 기술하여 가독성과 개발속도를 향상 시키고 이를 JavaScript로 변환하여 브라우저에서 실행하기 때문에 문제없이 실행이 가능하다.
TypeScript 적용
새로운 프로젝트를 시작할 때 적용한다면 CRA(create-react-app)을 통해 프로젝트를 생성할 때 --template를 typescript로 설정하여 실행해주면 함께 생성된다.
$ npx create-react-app app-name --template typescript
만약 기존의 프로젝트에 적용한다면 npm을 통해 typescript를 설치하면 된다.
$ npm install typescript @types/node @types/react @types/react-dom @types/jest
파일 확장자는 jsx형태라면 tsx로 변경해주고 js형태라면 ts로 변경해준다.
이후 react-router-dom이 필요하다면 react-router-dom과 typescript의 react-router-dom을 함께 설치하여 사용한다.
$ npm install react-router-dom
$ npm i @types/react-router-dom
TypeScript 사용하기
// 기본 타입
let car:string = 'kia';
let age:number = '26';
let isAdult:boolean = true;
let a:number[] = [1,2,3];
let b:Array<number> = [1,2,3];
let week:string[] = ['mon', 'tue', 'wed'];
// 튜플
let b:[string, number];
b = ['a', 1];
b = [2, 'b'; // err
// void: 아무것도 반환하지 않는 함수에 지정
function showLog():void{
console.log();
}
// never: err만 반환하는 함수나 영원히 끝나지 않는 함수에 지정
function showError():never{
throw new Error();
}
function infLoop():never{
while(true){
}
}
// enum: 비슷한 값들끼리 묶어둔 형태
// 자동으로 0부터 1씩 증가하는 값 부여
// 값을 지정해주면 이후 값은 지정한 값에 1씩 증가한 값 부여
enum Os {
Window, // 0
Ios, // 1
Android // 2
}
console.log(Os[1]); // Ios
console.log(Os['Android']); // 2
// 문자열로 값을 지정한 경우 단방향 맵핑만 됨
enum Os{
Window = 'win',
Ios = 'ios',
Android = 'android'
}
let myOs:Os;
myOs = Os.Window;
// null, undefined
let a:null = null
let b:undefined = undefined
// 객체의 속성값의 타입을 정의해야 할 때 interface 사용
// 속성값이 선택적일 땐(Optional) 속성명 뒤에 ? 입력
// readonly를 속성명 앞에 지정하게 되면 첫 할당이후 읽기만 가능
interface User{
name: string;
age: number;
gender?: string;
readonly birthYear: number;
}
let user:User = {
name = 'Jhon';
age = 30;
birthYear = 1993;
}
user.age = 10;
user.birthYear = 2012; // err
// 학년별 학점을 입력하는거와 같이 순차적인 인덱스에 값을 할당해야할 경우 문자열 인덱스 서명 추가
// 값이 고정된 선택지로 되어 있다면 type를 통해 선택지 지정 후 사용
type Score = 'A' | 'B' | 'C' | 'D' | 'F';
interface Student{
1?: Score;
2?: Score;
3?: Score;
4?: Score;
}
// 위와 같은 결과
interface Student{
[grade:number]: Score;
}
// interface를 통한 함수 만들기
interface Add {
(num1:number, num2:number):number;
}
const add:Add = function (x, y){
return x+y;
}
// interface로 클래스 정의 - implement
interface Car{
color: string;
wheels: number;
start(): void;
}
class Kia implement Car{
color;
wheels = 4;
constructor(c: string){
this.color = c;
}
start(){
console.log('go...');
}
}
const k = new Kia('blue');
k.start(); // 'go...'
// interface 확장 - extends
// 확장시 상속받은 속성을 모두 정의해야함
interface Benz extends Car{
door: number;
stop(): void;
}
const benz:Benz = {
door = 4;
stop(){
console.log('stop');
}
color;
wheels = 4;
constructor(c: string){
this.color = c;
}
start(){
console.log('go...');
}
}
// 확장은 여러개 동시에 가능
interface ToyCar extends Car, Toy{
property1 : number;
...
}
// 함수
// 두 함수는 같은 결과를 나타냄
function hello(name? : string){
return `Hello, ${name || "world"}`;
}
function hello(name = "world"){
return `Hello, ${name}`;
}
// 선택적 매개변수는 매개변수 가장 앞으로 올 수 없음
function hello(name: string, age?: number): string{
if (age !== undefined) {
return `Hello, ${name}, You are ${age}.`;
} else {
return `Hello, ${name}`;
}
}
console.log(hello("Tom", 30));
// 만약 앞으로 옮기고 싶다면 undefined를 명시적으로 매개변수로 전달해야함
function hello(age: number | undefined, name: string): string{
if (age !== undefined) {
return `Hello, ${name}, You are ${age}.`;
} else {
return `Hello, ${name}`;
}
}
console.log(hello(30, "Tom"));
console.log(hello(undefined, "Tom"));
// 나머지 매개변수 작성법
function add(...nums) {
return nums.reduce((result, num) => result + num, 0);
}
add(1,2,3); // 6
add(1,2,3,4,5,6,7,8,9,10); // 55
// this에 대한 정의는 매개변수 가장 앞에 지정
// 매개변수에 this에 대한 정의가 있지만 실제 함수 사용시 제외
interface User{
name: string;
}
const Tom: User = {name: 'Tom'}
function showName(this:User, age:number, gender:'m'|'f'){
console.log(this.name, age, gender)
}
const a = showName.bind(Tom);
a(30, 'm');
// 함수 오버로딩
// javascript 경우 동일한 매개변수라도 다른 타입이 가능함
// 매개변수 타입에 따라 결과값의 타입이 달라지는 경우 함수 오버로딩을 해주어야함
interface User{
name: string;
age: number;
}
function join(name: string, age: number): User;
function join(name: string, age: string): string;
function join(name: string, age: number | string): User | string{
if(typeof age === "number"){
return{
name,
age,
};
} else {
return "나이는 숫자로 입력해주세요";
}
}
const sam: User = join("Sam", 30);
const jane: User = join("Jane", "30");
// Literal Types 리터럴 타입
const userName1 = "Bob"; // userName1의 타입은 "Bob"임, 이를 문자열 리터럴 타입이라 함
// type로 enum과 비슷하게 만들 수 있음
type Job = "police" | "developer" | "teacher";
interface User{
name: string;
job: Job;
};
const user: User= {
name: "Bob",
job: "studnet" // err
job: "developer"
}
// Union Types "|"
interface Car{
name: "car";
color: string;
start(): void;
}
interface Mobile{
name: "mobile";
color: string;
call(): void;
}
// 동일한 속성 타입을 통해 구별가능한 것을 식별가능한 유니온 타입이라고 함
function getGift(gift: Car | Mobile){
console.log(gift.color);
if (gift.name === "car"){
gift.start();
} else {
gift.call();
}
}
// Intersection Types "&"
// 교차타입으로 묶어줬다면 두 타입의 속성값을 모두 명시해주어야함
interface Car{
name: string;
start(): void;
}
interface Toy{
name: string;
color: string;
price: number;
}
const toyCar: Toy & Car = {
name: "타요",
start(){},
color: "red",
price: 1000,
}
// Class 선언
// javascript와 다르게 typescript는 접근제한자가 존재
// public(default) : 자식 클래스, 클래스 인스턴스 모두 접근 가능
// protected : 자식 클래스에서 접근 가능
// private : 해당 클래스 내부에서만 접근 가능, 변수명 앞에 #붙이는 것과 동일
// static으로 선언된 속성은 클래스명으로 접근해야함(전역 사용 가능)
class Car{
protected name: string = "car";
#color: string; // private color: string;
static wheels = 4;
constructor(color: string){
this.color = color;
}
start(){
console.log("start");
console.log(Car.wheels);
}
}
class Bmw extends Car{
constructor(color: string) {
super(color); // color가 private이기 때문에 err
}
showName(){
console.log(super.name); // protected이기 때문에 자식 클래스에서 사용가능
}
}
const z4 = new Bmw("red");
console.log(z4.name); // protected이기 때문에 err
console.log(Car.wheels);
// 추상클래스
// 추상클래스는 new로 할당 불가능, 오직 상속을 통해서만 사용가능(extends)
// 추상메소드는 반드시 상속받은 클래스에서 구현해주어야함
abstract class Car{
color: string;
constructor(color: string){
this.color = color;
}
start(){
console.log("start");
}
abstract doSomething():void;
}
const car = new Car("blue"); // err
class Bmw extends Car{
constructor(color:string){
super(color);
}
doSomething(){
alert(3);
}
}
// Generic
// 클래스, 함수, 인터페이스를 다양한 타입으로 재사용 가능
// 타입파라미터 사용, 주로 T로 사용
// 전달되는 매개변수에 따라 타입추론이 되어서 사용할 때 타입정의 생략가능
function getSize<T>(arr: T[]):number{
return arr.length;
}
const arr1 = [1,2,3];
getSize<number>(arr1); // 3
const arr2 = ['a', 'b', 'c'];
getSize<string>(arr2); // 3
const arr3 = [true, false, false];
getSize<boolean>(arr3); // 3
const arr4 = [{}, {name: "tom"}, {}];
getSize<object>(arr4); // 3
// 프로퍼티에 어떤 데이터가 들어올지 모를때
interface Mobile<T>{
name: string;
price: number;
option: T;
}
const m1: Mobile<{color: string; coupon: boolean;}> = {
name: "s21",
price: 1000,
option: {
color: "yellow",
coupon: false,
},
};
const m2: Mobile<string> = {
name: "s20",
price: 900,
option: "good",
};
// 모든 매개변수에 특정 프로퍼티가 존재한다는 걸 장담할 수 없을때
// T타입에서 name 속성을 string으로 확장한 형태로 지정
// name이 string이 아니거나 없다면 오류 발생
interface User{
name: string;
age: number;
}
interface Car{
name: string;
color: string;
}
interface Book{
price: number;
}
const user: User = {name: "a", age: 10};
const car: Car = {name: "bmw", color: "blue"};
const book: Book = {price: 1000};
function showName<T extends {name: string}>(data: T): string{
return data.name;
}
showName(user);
showName(car);
showName(book); // err name속성이 없기때문
// 유틸리티 타입
// keyof, 속성명을 유니온타입으로 가져옴
interface User{
id: number;
name: string;
age: number;
gender: "m" | "f";
}
type UserKey = keyof User; // 'id' | 'name' | 'age' | 'gender' 와 같음
// Partial<T>, 해당 인터페이스 부분만 구현가능, 단 새로운 속성은 정의하지 못함
let admin: Partical<User> = {
id: 1,
name: "Tom",
};
// Required<T>, Partial과 반대로 속성 모두 구현해야함
let admin: Required<User> = {
id: 1,
name: "Tom",
age: 30,
gender: 'm',
};
// Readonly<T>, 첫 할당 이후 읽기만 가능
let admin: Readonly<User> = {
id: 1,
name: "Tom",
};
admin.id = 3; // err
// Record<K, T>, K값인 키값에 T값의 타입값을 맵핑
type Grade = '1'|'2'|'3'|'4';
type Score = 'A'|'B'|'C'|'D'|'F';
const score: Record<Grade, Score> = { // Record<'1'|'2'|'3'|'4', 'A'|'B'|'C'|'D'|'F'>
1: "A",
2: "C",
3: "B",
4: "D",
}
interface User{
id: number;
name: string;
age: number;
}
function isValid(user: User{
const result: Record<keyof User, boolean> = {
id: user.id > 0,
name: user.name !== "",
age: user.age > 0,
};
return result;
}
// Pick<T, K>, T타입에서 K프로퍼티만 불러와 사용
interface User{
id: number;
name: string;
age: number;
gender: "m" | "f";
}
const admin: Pick<User, "id" | "name"> = {
id: 0,
name: "Tom",
};
// Omit<T, K>, Pick과 반대로 K프로퍼티를 제외한 프로퍼티 사용
const admin: Omit<User, "age" | "gender"> = {
id: 0,
name: "Tom",
};
// Exclude<T1, T2>, T1타입에서 T2타입과 겹치는 타입을 제거
type T1 = string | number | boolean;
type T2 = Exclude<T1, number | string>; // boolean
// NanNullable<Type>, 타입중에서 null과 undefined 타입을 제거
type T1 = string | null | undefined | void;
type T2 = NanNullable<T1> // string | void
'DEV > WebProgramming' 카테고리의 다른 글
[React.js] Viewport와 동적으로 meta 태그 수정하기 (0) | 2023.01.31 |
---|---|
[React] Table 사용기 (0) | 2022.12.27 |
[React] Index.html 파일 수정하기 (0) | 2022.07.14 |
[React] 프로젝트 빌드 및 배포 (0) | 2022.07.12 |
[React] Local Storage (0) | 2022.07.08 |