Typescript와 React - #1 타입스크립트 문법
타입스크립트 자료 정리 : https://codingmoondoll.tistory.com/entry/츄라이-츄라이-리액트-타입스크립트부제-나만-고통-받을-수는-없지
https://typescript-kr.github.io/pages/tutorials/ts-for-the-new-programmer.html
https://velog.io/@velopert/create-typescript-react-component
https://react.vlpt.us/using-typescript/
https://velog.io/@swimme/React-Typescript-시작하기
타입스크립트란?
자바스크립트 + 알파(확장). 자바스크립트의 모든 기능에 타입이라는 새로운 개념이 추가된다.
기존의 자바스크립트 코드에서는 자료형을 지정하지 않고 유연하고 자유로운 조작이 가능했었다. 그러나 프로젝트의 규모가 커지면 이 자유로움이라는 부분은 장점보다는 단점이 될 가능성이 크다.
타입스크립트의 특징으로는
- 컴파일 필요
- 타입 지정 필요
- 엄격한 검사
가 있다.
타입스크립트 컴파일
타입스크립트 파일을 html이 인식하지 못하므로, js로 변환해줄 필요가 있다.
타입 지정
타입 스크립트의 가장 큰 특징으로, 자료형이 존재하지 않는 자바스크립트에 자료형을 부여하여 준다.
엄격한 검사
console.log("" == ture) // false
console.log(0 == ture) // false
console.log("" == 0) // true????
console.log(1 < 3 < 2) // ture?????
이것이 자바스크립트다! 절망편
단순히 위의 사례 말고도, 에러가 나도 제대로 잡지 않는 등 자바스크립트의 허술한 부분에 감탄한 사람들이 적지 않을 것이다. 타입스크립트에서는 어디에서 어떤 부분이 에러가 났는지 확실하게 잡아주게 된다.
타입스크립트 환경 세팅하기
https://www.kenrhee.com/blog/getting-started-with-typescript-with-react
mkdir typeScriptTest
cd typeScriptTest
npm install -g typescript
tsc --init
https://www.kenrhee.com/blog/getting-started-with-typescript-with-react
mkdir typeScriptTest
cd typeScriptTest
npm install -g typescript
tsc --init
tsconfig.json 파일에는 타입스크립트가 컴파일 될 때 필요한 옵션들을 지정할 수 있다.
옵션들에 대한 자세한 설명은 다음 링크 참조.
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist"
}
}
target - 컴파일된 코드가 어떤 환경에서 실행될 지 정의한다. 화살표 함수를 사용하는데 target이 es5로 되어 있다면 일반 함수로 자동 변환된다.
module - 컴파일된 코드가 어떤 모듈 시스템을 사용할 지 정의한다. es2015라면 아무런 변화가 없지만 commonjs 라면 export default Temp
를 할 때 exports.default = Temp
로 변환될 것이다.
strict : 모든 타입 체킹 옵션을 활성화 한다.
esModuleInterop : commonjs 모듈 형태로 이루어진 파일을 es2015 모듈 형태로 불러올 수 있게 해준다.
outDir : ts 파일에서 js 파일로 컴파일된 파일들이 저장될 경로를 지정할 수 있다.
"strict" : true만 켜줘도 입문자들은 타입스크립트의 대부분의 기능을 사용할 수 있다.
타입스크립트 컴파일
// src/test.ts
let test : string = "1";
test = "42";
// src/test.ts
let test : string = "1";
test = "42";
다음과 같은 타입스크립트는 실행하기 위해서 자바스크립트로 컴파일해서 실행해야만 한다.
타입스크립트를 컴파일할 때는 tsc {파일명}
형식을 이용한다.
tsc src/test.ts을 실행하면 src/test.js 파일이 생성된다.
//src/test.js
var test = "1";
test = "42";
tsc {파일명} -w
옵션을 넣어주면 터미널을 종료하기 전까지 실시간으로 파일의 변경을 감지해서 컴파일한다.
지금까지 글로벌로 설치한 타입스크립트 CLI로 코드를 컴파일 했는데, 프로젝트에서는 typescript를 설치해서 컴파일하게 된다.
기초 문법
변수(상수)의 타입 지정
let num : number;
num = 1;
num = "1"; // Type 'string' is not assignable to type 'number'
let str : string;
str = "123";
str = 123; // Type 'string' is not assignable to type 'number'
let numOrStr : string | number;
numOrStr = 1;
numOrStr = "1";
numOrStr = true; // Type 'boolean' is not assignable to type 'string | number'.
let list: number[];
list = [1, 2, 3];
list = [1, 2, "3"]; // Type 'number' is not assignable to type 'string'.
let anotherList: Array<number> = [1, 2, 3];
let tuple : [string, number];
tuple = ["hello", 1];
tuple = [1, "hello"]; // Type 'string' is not assignable to type 'number'.
let object : {name : string};
object = {name : "123"};
object = {name : 123}; // Type 'string' is not assignable to type 'number'.
type CusType = string | number;
let cum : CusType;
let num : number;
num = 1;
num = "1"; // Type 'string' is not assignable to type 'number'
let str : string;
str = "123";
str = 123; // Type 'string' is not assignable to type 'number'
let numOrStr : string | number;
numOrStr = 1;
numOrStr = "1";
numOrStr = true; // Type 'boolean' is not assignable to type 'string | number'.
let list: number[];
list = [1, 2, 3];
list = [1, 2, "3"]; // Type 'number' is not assignable to type 'string'.
let anotherList: Array<number> = [1, 2, 3];
let tuple : [string, number];
tuple = ["hello", 1];
tuple = [1, "hello"]; // Type 'string' is not assignable to type 'number'.
let object : {name : string};
object = {name : "123"};
object = {name : 123}; // Type 'string' is not assignable to type 'number'.
type CusType = string | number;
let cum : CusType;
타입스크립트에서 모든 변수나 상수에 타입을 지정해줄 수 있다. 이 때 타입에 맞지 않는 값을 집어넣을 경우 IDE 상에서도, 컴파일 시에도 어떤 부분이 잘못되었는지를 상세한 에러를 뿜어준다.
중요할 것 같은 타입이나 문법에 대해 추가로 기술하자면
배열 타입 지정
배열의 타입을 지정하고 싶다면 타입[]
형태로 지정한다. 제네릭 배열 타입을 이용해서 Array<타입>
형태도 가능하다. 해당 배열 안의 모든 요소는 명시한 타입이어야만 한다.
튜플 타입 지정
배열의 요소의 타입, 개수를 정확하게 설정하고 싶다면 [타입, 타입...]
형태로 지정할 수 있다.
객체 타입 지정
객체의 키에 해당하는 값에 타입을 객체 : { 키 : 타입 }
형태로 지정한다.
여러 타입 중 하나
|
를 사용하면 복수의 타입을 설정할 수 있다. 설정한 타입 중 하나도 속하지 않는 값일 경우 에러가 발생한다.
함수의 타입 지정
const sum = (x : number, y : number) : number => {
return x + y;
}
const wrongSum = (x : number, y : number) : number => {
return "x + y"; // Type 'string' is not assignable to type 'number'.
}
const useless = () : void => {
console.log("나는 쓰레기야...");
}
console.log(sum(1, 2));
console.log(sum(1, "2")); // Argument of type 'string' is not assignable to parameter of type 'number'.
함수의 인자 하나하나 마다, 리턴하는 값까지 타입을 지정해줄 수 있다.
리턴을 하지 않는 함수의 경우 리턴 부분의 타입을 void로 설정해주자. 안 해줘도 에러는 나지 않는다.
인터페이스
클래스나 객체를 위한 타입을 지정할 때 사용하는 문법.
클래스 인터페이스
클래스(생성자 함수)에 대해 헷갈리다면 다음 링크로.
https://ordinary-code.tistory.com/22
https://webclub.tistory.com/136
클래스가 특정 조건을 준수하게끔 하고 싶을 때, interface
를 설정해서 갖추어야 할 요구사항을 설정할 수 있다.
클래스를 선언할 때 implements
키워드를 사용하여 특정 interface
의 요구사항을 충족하도록 명시 할 수 있다. interface
의 요구사항과 다르다면 에러를 발생한다.
// Human이라는 인터페이스를 생성한다. 이 안에는 이 인터페이스가 갖추어야 할 조건이 들어간다.
interface Human {
weight : number,
age : number,
getBmi() : number
}
class Minsu implements Human { // Minsu라는 클래스는 Human의 조건을 충족하겠다는 의미.
weight : number;
age : number;
nickname : string;
constructor(weight : number, age : number, nickname : string) {
this.weight = weight;
this.age = age;
this.nickname = nickname;
}
getBmi() {
return this.weight / this.age;
}
}
const minsu: Minsu = new Minsu(67, 15, "Min");
console.log(minsu);
console.log(minsu.getBmi());
Human
이라는 인터페이스의 조건은 number
타입의 weight
와 age
, number
를 리턴하는 getBmi
가 있어야 하는 것이다.
Minsu
클래스를 선언하며 implements
로 Human
클래스의 조건을 충족할 것임을 명시한다. interface
와 동일한 형태의 멤버 변수를 설정하고 생성자 쪽에서 인자로 들어온 값들을 설정해준다.
Minsu
클래스에 멤버 변수의 타입과 맞는 인자를 전달해서 Minsu1이라는 인스턴스를 생성한다.
accessor
이 때 constructor
의 파라미터 쪽에 public
또는 private
accessor
를 사용하면 직접 하나하나 설정해주지 않아도 된다.
// Human이라는 인터페이스를 생성한다. 이 안에는 이 인터페이스가 갖추어야 할 조건이 들어간다.
interface Human {
weight : number,
age : number,
getBmi() : number
}
class Minsu implements Human { // Minsu라는 클래스는 Human의 조건을 충족하겠다는 의미.
constructor(public weight : number, public age : number, public nickname : string) {
this.weight = weight;
this.age = age;
this.nickname = nickname;
}
getBmi() {
return this.weight / this.age;
}
}
class Minji implements Human {
constructor(public weight : number, private age : number, private nickname : string) {
this.weight = weight;
this.age = age; // Class 'Minji' incorrectly implements interface 'Human'.
this.nickname = nickname;
}
getBmi() {
return this.weight / this.age;
}
}
const minsu: Minsu = new Minsu(67, 15, "Min");
const minji: Minji = new Minji(44, 11, "Ji");
console.log(minsu.nickname);
console.log(minji.nickname); // Property 'nickname' is private and only accessible within class 'Minji'.
public
accessor은 특정 값이 클래스의 코드 밖에서도 조회 가능함을 의미한다.
private
accessor는 반대로 클래스 밖에서 조회할 수 없음을 의미한다. 만약 private
로 설정한 값이 interface
에서 요구한 조건이었다면 에러로 인식한다. interface
의 조건이 되는 멤버는 public
으로 돌리자.
객체 인터페이스
interface Person {
name: string;
age?: number; // 물음표가 들어갔다는 것은, 설정을 해도 되고 안해도 되는 값이라는 것을 의미한다.
}
interface Developer {
name: string;
age?: number;
skills: string[];
}
const person: Person = {
name: 'sham',
age: 20
};
const expert: Developer = {
name: 'sham',
skills: ['javascript', 'react'],
useless : "yeah!" // Type '{ name: string; skills: string[]; useless: string; }' is not assignable to type 'Developer'.
};
객체의 타입을 interface
로 지정한 후 객체가 해당 인터페이스의 형태에서 벗어나게 되면 에러가 발생한다.
인터페이스에 없는 키가 들어간 경우에도 에러로 인식한다.
다만 키 뒤에 ? 가 붙으면 해당 키는 있어도 되고 없어도 되는 값으로 인식한다.
interface 상속(extends)
interface Person {
name: string;
age?: number; // 물음표가 들어갔다는 것은, 설정을 해도 되고 안해도 되는 값이라는 것을 의미한다.
}
interface Developer extends Person{
skills: string[];
}
const person: Person = {
name: 'sham',
age: 20
};
const expert: Developer = {
name: 'sham',
skills: ['javascript', 'react'],
};
Type Alias
type
키워드를 이용하면 나만의 커스텀 타입을 만들 수 있는데, 이를 이용해 특정 객체, 배열을 위한 타입을 만들 수 있다.
type Person = {
name: string;
age?: number;
};
type Developer = Person & { // & 는 Intersection 으로서 두개 이상의 타입들을 합쳐준다.
skills: string[];
};
const person: Person = {
name: 'sham'
};
const expert: Developer = {
name: 'sham',
skills: ['javascript', 'react']
};
type People = Person[]; // Person[] 를 이제 앞으로 People 이라는 타입으로 사용 할 수 있다.
const people: People = [person, expert];
type Color = 'red' | 'orange' | 'yellow';
const color: Color = 'red';
const colors: Color[] = ['red', 'orange'];
type Password = 1 | 4 | 6 | 7;
const tryPass : Password[] = [1, 4, 5, 7]; // Type '5' is not assignable to type 'Password'.
Intersection(&)
&를 이용하면 두 개 이상의 타입들을 합쳐 줄 수 있다.
People이라는 타입은 Person이라는 타입을 요소로 하는 배열을 의미한다.
people의 타입을 People로 선언하고 그 요소로 person과 expert를 넣어준다. expert는 Person 타입을 충족하기에 정상 작동된다.
자유로운 타입 지정
자료형만 타입으로 지정할 수 있는 것이 아니다.
문자열, 숫자 또한 타입으로 지정해줄 수도 있다!
타입과 인터페이스의 차이
https://rinae.dev/posts/practical-advanced-typescript-summary#switch-문에서-자동으로-타입-추론하기
https://rinae.dev/posts/practical-advanced-typescript-summary#switch-문에서-자동으로-타입-추론하기
두 가지 모두 무언가 구조를 정의할 때 사용할 수 있다. 서로 다른 타입과 결합도 가능하다. (interface - extends / type - &).
두 형태의 타입 정의 방식을 교차해서 사용할 수도 있다. type이 interface나 다른 type과 함께 결합될 수도 있고, 클래스에 extends
, implements
를 쓸 때 interface뿐 아니라 type도 가져올 수 있다. 하지만 유니언 타입은 extends
, implements
에 사용될 수 없다.
type은 같은 파일 안에서 두 번 선언 될 수 없지만, interface는 중복 선언될 경우 타입 결합과 동일하게 동작한다. 이 원리를 활용하여 라이브러리의 타입을 확장하는데도 사용할 수 있다.
까마득한 먼 미래에 라이브러리 작성하게 된다면 공개되는 타입 형태를 interface로 내보내어 사용자들이 필요할 경우 확장하기 쉽게 만들 수 있을 것이다.
제네릭(Generics)
제네릭(Generics)은 타입스크립트에서 함수, 클래스, interface
, type
을 사용하게 될 때 여러 종류의 타입에 대하여 호환을 맞춰야 하는 상황에서 사용하는 문법이다.
type Arr = {
[key : string] : string;
}
type Num = {
[key : string] : number;
}
function merge(a: any, b: any): any {
return {
...a,
...b
};
}
const merged = merge({ name: "1" }, { index: 1 });
console.log(merged);
merge 함수는 인자로 들어온 객체를 서로 합쳐주는 함수이다. 그러나 인자로 어떤 타입이 들어올지 모르는 상황이다. any
라는 타입을 쓸 수도 있겠지만 결국 인자로 들어온 객체나 합쳐서 나가는 객체에 무엇이 들었는지 알 수 없는 상황, 타입추론을 하는 의미가 없어진다.
이때 제네릭을 사용하면 알 수 없는 타입이 인자로 들어와도 타입을 그대로 보장한 채 사용해줄 수 있다.
function merge<A, B>(a: A, b: B): A & B {
return {
...a,
...b
};
}
const merged = merge({ foo: 1 }, { bar: 1 });
꺽쇠 안에 타입의 이름을 넣음으로써 그 어떤 타입이 들어와도 타입을 보장해줄 수 있다.
인자로 들어온 a는 A라는 타입으로 인식하고 b는 B라는 타입으로 인식하고 A와 B를 더한 타입을 리턴한다. 어떤 타입인지 함수에서 알 수 없지만 타입을 지켜내는데 성공한 것이다.
function same<T>(param: T) {
return param;
}
console.log(same("hello!"));
함수의 인자, 리턴 값으로 다양한 타입이 들어오게 될 때 제네릭을 사용하면 타입 지원을 지켜낼 수 있다.
interface에서 제네릭 사용
interface Items<T> {
list: T[];
}
type TypeItems<T> = {
list: T[];
}
const stringItem: Items<string> = {
list: ["1, 2, 3"]
};
const numberItem: Items<number> = {
list: [1, 2, 3],
};
const strnumItem: Items<number | string> = {
list: [1, 2, "3"]
};
인터페이스에서도 제너릭을 지정해주어서 해당 인터페이스를 실제로 사용하게 될 때 타입을 지정하게끔 할 수도 있다. type의 사용법과 거의 같기에 같이 묶어서 넘어가겠다.
클래스에서 제네릭 사용
class Queue<T> {
list: T[] = [];
length() {
return this.list.length;
}
enqueue(item: T) {
this.list.push(item);
}
dequeue() {
return this.list.shift();
}
}
const queue = new Queue<number | string>();
queue.enqueue(0);
queue.enqueue("1");
queue.enqueue(2);
queue.enqueue("3");
queue.enqueue(4);
console.log(queue.dequeue());
console.log(queue.dequeue());
console.log(queue.dequeue());
console.log(queue.dequeue());
console.log(queue.dequeue());
클래스에 사용하게 되는 제네릭은/ 클래스 자체의 타입이 아닌/ 클래스 내부에서 사용되게 될 타입이/ 클래스를 선언하면서 결정된다는 것을 말한다.
Author And Source
이 문제에 관하여(Typescript와 React - #1 타입스크립트 문법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@sham/Typescript와-React-1저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)