TypeScript 기초

김민성·2022년 5월 23일
0
post-thumbnail

타입스크립트의 기초를 다지자

이전 회사에서 긴급하게 타입스크립트를 도입하는 바람에 기초지식이 부족한 상태로 프로젝트를 진행한 적이 있다.
현재 쉬는 기간동안 현업에서 모자랐던 부분들의 기초를 다지는 시간을 가지고 있는데, 타입스크립트 기초 인강을 들은겸 정리 해두고자 한다.

역시 기초가 중요해 최고야 짜릿해

타입스크립트는 뭐고 왜 쓸까?

이후부터는 아래와 같이 표기하겠음
Javascript => JS
Typescript => TS

정의

JS의 슈퍼셋 언어
간단하게는 (타입) + 자바스크립트 = 타입스크립트
원래 사용하던 자바스크립트에 정적타입을 선언하여 오류를 줄인 언어이다

TS로 작성한 코드는 JS코드로 변환(컴파일)되어 브라우저가 이해하게 된다.

TS의 장점, 쓰는이유

1. 안정성이 높다

JS는 런타임시 타입을 정하는 동적언어이다.
하지만 TS는 정적 언어로 컴파일 시 타입을 정한다.
위에서 말했듯이 TS-> JS로 컴파일 하는 과정이 필요한데 이때 오류를 파악할 수 있어 안정성이 높다.

짧은 견해로는 프로젝트가 커지면서 예기치못한 side-effect들이 발생하는데 타입설정을 통해 막을 수 있을것 같다. 작은 프로젝트보다는 큰 프로젝트에서 빛을 발할것 같다.

2. 코드 자동완성

타입을 설정하니 예측되는 타입에 대한 가이드 및 메소드가 자동완성되어 생산성이 향상된다.

3. JS는 타입에 대한 제약이 없다

가장 간단한 예시로는 아래의 경우 JS 는 아래와 같은 결과값을 반환한다.

let test = [1,2,3,4] + false
console.log(test) 
// 결과값 : '1,2,3,4false'

하지만 타입스크립트의 경우 아래와 같이 친절한 오류를 띄워준다.

4. Visual Studio Code 랑 찰떡

TS와 Visual Studio Code는 모두 마이크로소프트에서 만들었다.
호환이 잘되고 다양한 기능들이 extention없이도 가능하니 더할나위 없다.

TS 타입

결국 TS는 타입설정이 메인포인트인데 어떤 타입이 있는지, 어떻게 타입설정을 하는지 살펴보자

1. 타입 설정방법

타입설정방법은 2가지가 있다

  1. TS가 추론하게 둔다
  2. 개발자가 명시한다

아래의 예시를 보자

1. let a = "hello". // 이렇게해도 자동으로 string인줄 인식함
2. let b : boolean = false // 변수가 어떤 타입인지 명시해줌

let c : number[] = [1,2,3]

c.push("1") /// 에러 -> 타입에 맞지않는 변수가 들어감

2. 타입의 종류

  • Boolean
  • Number
  • String
  • Object
  • Array
  • Tuple
  • Enum
  • Any
  • Void
  • Null
  • Undefined
  • Never

뭐가 엄청많다. 천천히 살펴보자.

3. 타입별 설정

간단한 타입들은 아래와 같이 예시로만 알아봐도 될것 같고, 생소한 단어인 enum은 아래에서 더욱 깊게 알아보자.


let character : string = "문자열" // string
let rate : number = 1  //number
let items : number[] = [1,2,3] // array
let items2 : Array<number> = [3,4,5] // array
let address : [string, number] = ["gangnam", 123] // tuple = 배열의 길이가 고정되고 , index마다 타입 지정
let player : {
  name: string,
  age?: number // object , ? 는 옵션 
}

let bool : boolean = false // boolean

enum이란 특정값들의 집합을 의미한다. 무슨말인지 모르겠다.
아래의 예시와 활용을 통해서 알아보자.

- 숫자형 이넘

예를들어 신발의 이넘이 있을때 위에서 부터 순서대로 index는 0 부터 시작하고 따로 인덱스를 부여할경우 특정 인덱스로 부터 자동 증가한다.

enum Shoes {
  Nike, // 0
  Adidas, // 1
  
var myShoes = Shoes.Nike

console.log(myShoes) // 0

enum Direction {
Up =1 ,
Down,
Left,
Right
}

var up = Direction.Up // 1
var down = Direction.Down //2
  

그럼 숫자형 이넘은 어떻게 활용할 수 있을까? 만약 response를 다루어야 하는경우
"아니오"라는 답변이 No, no, n, N 등 사람에 따라 다양한 답변을 받을 수 있다.
그에대해 일일히 다 대응하는것보다 이넘으로 특정하는 경우 효율적으로 선택지에 대응할 수 있다.

enum Response {
No = 0,
Yes =1,
}

function respond(recipient: string, message: Response): void {
}

respond("hello", Response.Yes) //1 

- 문자형 이넘

문자형 이넘은 숫자형과는 달리 모든 이넘값에 초기화 값이 있어야 한다.

enum Shoes {
Nike = "나이키",
Adidas = "아디다스",
}

var myShoes = Shoes.Nike

console.log(myShoes) //"나이키"

- 문자형 이넘의 활용

숫자형 이넘과 동일하게 이넘에서 지정한 값만 넘김으로서 범위를 좁혀 오류를 줄일 수 있다.
선택지, 리스트 등에 활용하면 좋을것 같다.

enum Answer {
    Yes = 'Y',
    No = "N"
}


function askQuestion(answer: Answer ) {
    if(answer === Answer.Yes){
        console.log('정답입니다.')
    }
    if(answer === Answer.No){
        console.log('오답임')
    }
    }

    askQuestion(Answer.Yes)

- 리버스 매핑

리버스 매핑이랑 이넘의 키, 값을 서로의 값을 이용해 가져오는 방법이다.

enum Enum {
  A
}
let a = Enum.A; // 0 , 키로 값을 획득 하기 
let keyName = Enum[a]; // A,  값으로 키를 획득 하기

함수의 타입설정

함수에서는 인자, 반환값 2가지에 타입을 부여해야한다.
타입을 부여한 인수는 필수값이 되며 ? 키워드를 통해 옵션으로 둘 수 있다.

function sum(a: number, b: number): number {
  return a + b;
}
sum(10, 20); // 30
sum(10, 20, 30); // error, 많은 인자를 넘김
sum(10); // error, 인자를 적게 넘김


function sum(a: number, b?: number): number { // 물음표를 이용해서 필수-> 옵션 변경가능
  return a + b;
}
sum(10, 20); // 30
sum(10, 20, 30); // error, 많은 인자를 넘김
sum(10); // 10 

함수 내 ES6의 rest 문법은 아래와 같이 활용할 수 있다.

function sum(a: number, ...nums: number[]): number {
  const totalOfNums = 0;
  for (let key in nums) {
    totalOfNums += nums[key];
  }
  return a + totalOfNums;
}

함수 내 this를 사용할때 타입을 명시하여 오류를 방지할 수도 있다.

interface Vue {
  el: string;
  count: number;
  init(this: Vue): () => {};
}

let vm: Vue = {
  el: '#app',
  count: 10,
  init: function(this: Vue) {
    return () => {
      return this.count;
    }
  }
}

let getCount = vm.init();
let count = getCount();
console.log(count); // 10

위의 예시에서 모르던 개념이 나왔다 interface. 현업을 하면서 interface와 type이 뭐가다른지 혼돈이 있었는데 이참에 알게되어 기쁘다.

interface

interface는 동일한 규칙으로 사용할수있게 타입을 미리 설정해두고 적용하는것이다.
파라미터, 배열, 객체,객체의 속성 등에 사용할 수 있고 아래의 예시를 통해 자세히 알아보자.

interface User { 
	age: number;
	name: string;
}

//변수에 인터페이스 활용
const seho: User = {
	age: 33,
	name: "세호"
}

//함수의 인터페이스 활용
function getUser(user: User) {
 //// 유저 땡겨오는 함수...
}

const me = {
name: "민성",
age: 12
}

getUser(me)

//함수의 스펙(구조)에 인터페이스를 활용
interface SumFunction {
 (a:number, b:number) : number
}

var sum : SumFunction;
sum = function (a:number, b:number): number {
return a+ b
}

user라는 인터페이스를 두고 동일한 속성이 필요한곳에 편리하게 모듈처럼 사용할 수 있게 되었다!

인터페이스 속성

인터페이스를 읽기전용으로 하고 데이터 변경이 불가능하게 만들고 싶은경우 readonly, ReadonlyArray 속성이 있다.


//readonly 속성
interface CraftBeer {
readonly brand: string
}

let myBeer: CraftBeer = {
  brand: 'Belgian Monk'
};
myBeer.brand = 'Korean Carpenter'; // error!

//ReaconlyArray<T> 속성
let arr: ReadonlyArray<number> = [1,2,3];
arr.splice(0,1); // error
arr.push(4); // error
arr[0] = 100; // error

참고 : 인터페이스를 사용할때 인터페이스의 속성개수 != 객체의 속성 갯수여도 상관없다
즉, 인터페이스에 정의된 타입에 부합하면 갯수가 더 많아도 상관없다.
쉽게말해 인터페이스 내의 조건 1개 = 객체 속성 10개 여도 1개만 만족시키면 오류는 안난다.

인터페이스의 확장(상속)

인터페이스와 타입의 차이점은 여기에 있다. 타입과는 달리 인터페이스는 상속(extneds)을 통해 다른인터페이스와의 결합으로 확장할 수 있다.

interface Person {
 name: string;
 age: number;
}

interface Developer extends Person {
	language: string;
}

var min : Developer = { 
	name: "민성",
	age: 20,
	language: "한국어"
}

유니온 타입 - 연산자를 이용한 타입정의

유니온 타입은 | 연산자를 통해 여러개의 타입을 설정할 수 있는 것이다.
여러개의 특정 타입으로 타입의 범위를 좁혀 타입가드를 할 수 있다.
아래의 예시를 보자.

function logMessage(value: string | number){
    if(typeof value === 'number'){
        value.toLocaleString() // 자동으로 넘버관련 메소드 추첨
    }
    if(typeof value === 'string'){
        value.toString() // 자동으로 스트링 관련 메소드 추천
    }
    throw new TypeError("value must be string or number")
}

logMessage('hello')
logMessage(100)

어떤 타입이냐에따라 타입에 따른 메소드도 자동 추천 해준다!

인터섹션 타입(&)

유니언타입과 비슷하게 & 연산자를 통해 여러가지의 타입을 사용할 수 있다.

interface Developer {
    name:string;
    skill:string; 
}

interface Person {
    name:string;
    age:number;
}

// 유니언타입
// 유니언 타입은 공통된 특성만 제공한다.
// 인터페이스안의 모든 타입을 제공해주면 안전하지 않다고 생각한대...
// 추가적인 타입가드 처리가 필요함
function askSomeone(someone: Developer | Person) {
someone.name // someone.name만 제공한다
}


//인터섹션 타입 
//&로 연결시 모든 속성 사용가능
//타입가드가 필요없음
function askSomeone(someone: Developer & Person) {
someone.name
someone.age
someone.skill
}

사용하는 방법은 유니언 타입과 다를게 없다. 그럼 뭐가 다를 까?
위의 예시를 보면 유니언타입은 공통된 특성에 대한 메소드를 추천해주고, 인터섹션타입은 연결된 모든 속성을 사용하게 해준다.

아래의 사진을 보면 좀더 쉽게 이해가 될것이다.

제네릭 타입

위의 타입설정들을 보면서 의문이 들었을수도있다. 타입을 고정해두면 다른경우는 어떡하지?
제네릭 타입을 써라!
제네릭타은 타입을 함수의 변수처럼 사용해 유연하게 사용할 수 있다. 어떤 타입을 사용할지 호출할때 넘겨주어 유연하게 사용할 수 있다.

function getText<T>(value:T):T {
  return value;
}

//어떤 타입을 넘길지 지정해준다.
getText<string>('hi'); // 'hi'
getText<number>(10); // 10
getText<boolean>(true); // true 

인터페이스에서의 제네릭 활용

일일히 인터페이스를 지정해주면 추후 확장성이 낮기때문에 제네릭을 활용해 확장성을 높힐 수 있다.

interface Dropdown<T> {
value: T;
selected: boolean;
}

// string을 전달해줌으로서 value를 string으로 사용할 수 있음
const object: Dropdown<string> {
value: 'abc',
selected: boolean
}

//만약 number를 넘겨주면 아래와 같이 사용할수도 있다.
const object: Dropdown<number> {
value: 123,
selected: boolean
}

제네릭의 타입제한

제네릭을 사용했을시 아직 어떤 타입이 들어올지 모르기 때문에 메소드 사용이 제한이 생길 수 있다.
아래와 같이 타입을 알려주거나 interface를 활용해서 타입을 제한하여 해결할 수 있다.

  1. 타입 알려주기
function logTextLength<T>(text: T[]):T[] {
text.length; // X 아직 어떤타입이 들어올지 모르기 때문에 length 사용이 안된다.
							//그래서 타입에 대한 힌트를 주기위해 위에 배열을 추가해준다.
	return text;
}

logTextLength<"string">(["hi",'hello','sexy']) // string배열이 들어와야함
  1. interface로 정의된 타입 이용하기
interface LengthType {
length: number;
}

//받을때 기본으로 타입을 제한하여 사용할 수 있다.
function logTextLength<T extends LengthType>(text: T[]):T[] {
text.length; //
	return text;
}

API 응답

비동기 처리시 promise를 반환하는경우 아래와같이 제네릭을 활용하여 처리할 수 있다.

function fetchItems(): Promise<string[]> {
		let items:string[] = ['a','b','c']
		return new Promise(fucntion (resolve) {
		resolve(items) // promise를 반환하게됨
	}
}

타입 추론

타입스크립트는 타입을 지정하지 않는경우 기본적으로 그에맞는 타입을 추론한다.
아래의 예시를 보자.

let a = 3


// 기본값을 number으로 줬을때 기본 type은 number이 된다.
function getB(b = 10){
		var c ='hi' //string
    return b + c // 
}

getB('hi') // number가 들어가야한다는 오류 발생

타입 단언

타입스크립트가 타입추론을 못할때 개발자가 as 키워드로 타입을 선언할 수 있다.

var a;

a = 20 
a = 'a'

var b = a as string

타입단언은 DOM API 조작시 많이 활용할 수 있다.
아래의 예시를 살펴보자. 타입스크립트는 아래의 div가 divelement | null 이라고 추론한다.

var div = document.querySelector('div') 
if(div){  //div가 null이 아닌지 확인하기 위해서 함
div.innerText
}

그래서 개발자의 의도대로 div 일때 어떤 기능을 수행하고자 한다면 위와같이 if문을 활용해 null이아닌지 확인해야한다.

하지만 TS의 추론에 의존하는것보다 개발자는 div라는것을 명확히 알기때문에 as키워드를 통해 타입을 단언해주고 div라는것을 확인시켜 줄 수 있다.

var div = document.querySelector('div') as HTMLDivElement;

div.innerText

그리하여 TS도 div로 인식하였고, 비효율적인 if문 또한 줄일 수 있다.

참고 강의 : https://www.inflearn.com/course/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%85%EB%AC%B8/dashboard

profile
다양한 경험의 개발자를 꿈꾼다

0개의 댓글