Section4 / Unit5 : TypeScript(타입, 함수)
TypeScript
JavaScript의 상위 집합(Superset) 언어
→ JavaScript에 정적타입 검사와 클래스 기반 객체 지향 프로그래밍 등의 기능을 추가
→ JavaScript가 발전하면서 생긴 단점을 보완하기 위해 등장
등장 배경
JavaScript는 초반, 브라우저에서만 동작하는 스크립팅 언어였다.
JS로 웹 애플리케이션의 상호작용이 증가하면서 코드의 양이 폭발적으로 늘어나게 되면서, 한계가 부각되었다.
JS 장점 : 동적 타입이 결정되어 유연하고, 다양한 라이브러리와 프레임워크 사용 가능
JS 단점 : 타입의 명시성이 부족
→ ex) let add = ( x, y ) -> { return x + y;} => add(5, "7")의 값으로 "57"이 나온다
→ 숫자 타입의 인수 쪽을 강제적으로 타입 변환해 문자열을 만든다.
이런 문제점을 보완하기 위해 TypeScript 언어가 등장!
TypeScript 장점
- 정적타입 검사 기능 제공
- 코드의 가독성
- 유지 보수성 ↑
- 런타임 에러 최소화
- 코드 작성 시간을 단축
TypeScript 타입
- Boolean(불리언)
- 가장 기본적은 데이터 타입
- 참(true), 거짓(false) 값
let isShow: boolean = true;
let isDone: boolean = false;
- Number(숫자)
- 정수와 실수의 구분 없음
- 이 외에도 추가로 bigint를 지원
let number1: number = 5;
let number2: number = 0.7;
- String(문자열)
- 큰따옴표(")나 작은따옴표(')를 사용하여 문자열 데이터를 표현
- 백틱(`)을 사용한 문자열인 템플릿 리터럴을 사용하면
여러 줄에 걸쳐 문자열을 작성 가능
let firstName: string = "coding";
let lastName: string = 'kim';
let longString: string = `Kimcoding is a developer.
He is 20 years old.`
- Array(배열)
- 두 가지 방법으로 배열 타입 선언
- []
- 제네릭 배열
- 기본적으로 하나의 타입만 작성하게 되어 있음
- 타입을 혼용해서 작성하는 것은 불가능
- 두 가지 방법으로 배열 타입 선언
//첫 번째 방법
let items: string[] = ["apple", "banana", "grape"];
//두 번째 방법
let numberList: Array<number> = [4, 7, 100];
- Tuple(튜플)
- 요소의 타입과 개수가 고정된 배열을 표현 가능
- 모든 요소가 전부 같을 필요는 없음
→ 배열의 인덱스마다 타입이 정해져 있기 때문에 정확한 인덱스에 접근할 필요가 있음
let user: [string, number, boolean] = ["kimcoding", 20, true];
- Object(객체)
- 원시 타입이 아닌 타입
- TypeScript에서 object 타입은 모든 객체를 수용하는 타입으로,
객체의 프로퍼티 타입들이 any로 지정되기 때문에 어떠한 프로퍼티라도 추가할 수 있음
→ 하지만 타입 안정성을 보장하지 않기 때문에 추천 X - 객체의 프로퍼티 타입들을 각기 명시해 주는 것이 좋음
- key-value에 구체적인 타입까지도 지정 가능
let obj: object = {};
let user: {name: string, age: number} = {
name: "kimcoding",
age: 20
}
- Any
- 타입 검사를 하지 않고자 할 때 any 타입을 사용
ex) 클라이언트에서 유저로부터 받은 데이터 및 서드파티 라이브러리에서 들어오는 값인 경우
개발자가 알지 못하는 타입일 수 있음 - 변수에 값을 재할당하는 경우 타입을 명시한 변수와 달리 타입에 구애받지 않고 재할당 가능
- 엄격한 타입 검사를 진행하지 않기 때문에, 실제 할당된 값이 가지지 않는 메서드 및 프로퍼티로 접근해도 에러나지 않음
- 실제 할당된 값이 가지지 않는 메서드 및 프로퍼티이기 때문에 반환되는 값은 undefined
- any 타입은 타입의 일부만 알고, 전체는 알지 못할 때 유용
→ 여러 타입이 섞인 배열을 받고자 할 때 유용함
- 타입 검사를 하지 않고자 할 때 any 타입을 사용
let maybe: any = 4;
let obj: object = {};
//에러가 납니다.
obj = "hello";
let maybe: any = 4;
//정상적으로 동작합니다.
maybe = true;
let maybe: any = 4;
//undefined로 출력됩니다.
console.log(maybe.length);
let list: any[] = [1, true, "free"];
//any로 다루고 있기 때문에 index 1번째 요소가 boolean 타입이지만 number 타입으로 재할당할 수 있습니다.
list[1] = 100;
TypeScript의 함수
- 기명 함수(named function)
- 화살표 함수(arrow function)
//named function
function add(x: number, y: number):number {
return x + y;
}
//arrow function
let add = (x: number, y: number): number => {
return x + y;
}
- TypeScript에서 함수를 표현할 때는 매개변수의 타입과 리턴값의 타입을 명시해야 함
- 반환되는 타입은 타입추론을 이용하여 생략할 수도 있음
- 타입 추론 : 리턴값을 작성하지 않아도 TypeScript 컴파일이 스스로 판단해서 타입을 넣어줌
//named function
function add(x: number, y: number) {
return x + y;
}
//arrow function
let add = (x: number, y: number) => {
return x + y;
}
- 함수에 리턴값이 없다면, void를 사용하여 작성
let printAnswer = (): void => {
console.log("YES");
}
- 매개변수의 개수에 맞춰 전달인자를 전달해야함
let greeting = (firstName: string, lastName: string): string => {
return `hello, ${firstName} ${lastName}`;
}
//에러가 납니다.
greeting('coding');
//정상적으로 작동합니다.
greeting('coding', 'kim');
//너무 많은 매개변수를 보내 에러가 납니다.
greeting('coding', 'kim', 'hacker');
- 만약 개발자가 전달인자를 전달하지 않거나, undefined를 전달했을 때 할당될 매개변수의 값을 정해놓을 수도 있음
→ JavaScript에서의 default parameter와 같은 동작
let greeting = (firstName: string, lastName: string ="kim"): string => {
return `hello, ${firstName} ${lastName}`;
}
//정상적으로 작동합니다.
greeting('coding');
//정상적으로 작동합니다.
greeting('coding', undefined);
//너무 많은 매개변수를 보내 에러가 납니다.
greeting('coding', 'kim', 'hacker');
- 선택적 매개변수를 원한다면 매개변수의 이름 끝에 물음표(?)를 붙임으로써 해결
let greeting = (firstName: string, lastName?: string): string => {
return `hello, ${firstName} ${lastName}`;
}
//정상적으로 작동합니다.
greeting('coding');
//전달인자를 하나만 전달했기 때문에, 뒤의 매개변수는 undefined로 반환
//정상적으로 작동합니다.
greeting('coding', 'kim');
//너무 많은 매개변수를 보내 에러가 납니다.
greeting('coding', 'kim', 'hacker');
유니온(Union) 타입
둘 이상의 타입을 합쳐서 만들어진 새로운 타입
- | 연산자를 이용하며, 자바스크립트의 || (OR) 연산자와 같이 "A이거나 B이다"라는 의미의 타입
ex) number | string 은 숫자 또는 문자열 타입을 의미
function printValue(value: number|string): void {
if (typeof value === "number") {
console.log(`The value is a number: ${value}`);
} else {
console.log(`The value is a string: ${value}`);
}
}
printValue(10); // The value is a number: 10
printValue("hello"); // The value is a string: hello
유니온(Union) 타입의 장점
- 타입을 추론할 수 있기 때문에, 타입에 관련된 API를 쉽게 자동완성으로 얻어낼 수 있음
→ any 타입은 자동완성 기능 사용하기 어려움 - 코드의 가독성 ↑
유니온(Union) 타입의 유의점
- 유니온에 있는 모든 타입에 공통인 멤버들에만 접근할 수 있음
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
function askSomeone(someone: Developer | Person) {
console.log(someone.name);
}
위에 예시에서는 askSomeone 함수 내부에서는 Developer와 Person이 갖고 있는 공통 프로퍼티인 name에만 접근 가능
나머지 프로퍼티에도 접근하고 싶다면? → 타입 가드 사용!
타입 가드(Type Guard)
TypeScript에서 타입을 보호하기 위해 사용되는 기능 중 하나
특정 코드 블록에서 타입의 범위를 제한해 해당 코드 블록 안에서 타입 안정성을 보장
function askSomeone(someone: Developer | Person) {
// in 연산자 : 타입스크립트에서 객체의 속성이 존재하는지를 체크하는 연산자
// in 연산자는 객체의 속성 이름과 함께 사용하여 해당 속성이 객체 내에 존재하는지 여부를 검사
if ('skill' in someone) {
console.log(someone.skill);
}
if ('age' in someone) {
console.log(someone.age);
}
}
//in연산자 : 객체의 프로퍼티 이름과 함께 사용되며, 해당 프로퍼티가 객체 내에 존재하는지 여부를 검사
인터섹션(Intersection) 타입
둘 이상의 타입을 결합하여 새로운 타입을 만드는 방법
&연산자를 사용하여 표현
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
type User = Developer & Person;
User 변수는 Developer, Person 각각에 정의된 속성 모두를 받게 됨
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
function askSomeone(someone: Developer & Person) {
console.log(someone.age);
console.log(someone.name);
console.log(someone.skill);
}
위의 코드는 인터섹션 타입을 사용하여 Developer와 Person을 하나의 타입으로 묶었습니다.
따라서 askSomeone 함수 내에선 정의된 프로퍼티에 전부 접근할 수 있습니다.
그러나 인터섹션 타입은 타입 가드는 필요 없는 반면 Developer와 Person이라는 새로운 교집합을 만들어 내는 것이기 때문에,
전달인자를 전달할 때 모든 프로퍼티를 전부 보내줘야만 합니다.
반대로 유니온 타입은 타입 가드를 해줘야 하지만 전달인자를 전달할 때 선택지가 생기게 됩니다.
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
function askSomeone(someone: Developer | Person) {
//이런 식으로 프로퍼티에 접근할 수 있습니다.
if ('skill' in someone) {
console.log(someone.skill);
}
if ('age' in someone) {
console.log(someone.age);
}
}
//유니온 타입은 전달인자를 전달할 때 선택지가 생깁니다.
askSomeone({name: '김코딩', skill: '웹 개발'});
askSomeone({name: '김코딩', age: 20});
function askSomeone2(someone: Developer & Person) {
//타입 가드를 사용하지 않아도 모든 프로퍼티에 접근할 수 있습니다.
console.log(someone.age);
console.log(someone.name);
console.log(someone.skill);
}
//그러나 인터섹션 타입으로 결합하게 된다면 전달인자를 전달할 때 선택지가 없습니다.
askSomeone2({name: '김코딩', skill: '웹 개발', age:20});