[TypeScript] chap.2

 

 

 

추가 Narrowing

1. undefined & null 처리 narrowing

- && 연산자

1 && null && 3   // null이 남음
undefined && '안녕' && 100  // undefined 남음

&& 값은 and값 말고 true/false 대신 자료형을 넣게되면 falsy 값을 찾아주는데 falsy값이란 false와 비슷한 null, undefined, NaN 등의 값을 의미한다.

 

function printAll(strs: string | undefined) {
  if (strs && typeof strs === "string") {  
    console.log(s);
  } 
}

따라서, 해당 함수에서 strs 값이 undefined값이 나온다면 console.log()가 실행되지 않는다.

 

 

2. 여러가지 object자료 narrowing

- in 연산자

type Fish = { swim: string };
type Bird = { fly: string };
function 함수(animal: Fish | Bird) {
  if ("swim" in animal) { // fly도 in을 쓸 수 있다.
    return animal.swim
  }
  return animal.fly
}

해당 object의 유니크한 속성을 가지고 있다면 그 속성으로 narrowing을 할 수 있다. (해당 object의 키값 in 자료형)의 형태로 쓰면되고 서로 배타적인 속성이 있어야한다.

 

 

- instanceof

let 날짜 = new Date();
if (날짜 instanceof Date){
  console.log('참이에요')
}

어떤 클래스로부터 new 키워드로 생성된 object는 instanceof를 통해 부모 클래스가 누군지 검사할 수 있는데 그것으로 narrowing을 할 수 있다.

 

 

- literal type

type Car = {
  wheel : '4개',
  color : string
}
type Bike = {
  wheel : '2개',
  color : string
}

function 함수(x : Car | Bike){
  if (x.wheel === '4개'){
    console.log('the car is ' + x.color)
  } else {
    console.log('the bike is ' + x.color)
  }
}

typeof 연산자를 사용하면 같은 object 타입이기때문에 구분할 수 없고, in은 해당 타입에 배타적인 속성이 없기때문에 마찬가지로 구분할 수 없다.

 

이럴때 literal type을 사용한다. Car 타입의 wheel은 무조건 4개, Bike 타입의 wheel은 무조건 2개가 나온다. 이것을 가지고 narrowing이 가능하다.

 

 

 

Never

함수에서 사용되는 return 타입으로 never 타입이 있다.

 

1. 절대 return을 하지 않아야한다.

2. 함수 실행이 끝나지 않아야된다. (endpoint가 없어야 한다)

 

function 함수(){
  console.log(123)
} // never 붙일 수 없음 => return undefined

///////////////////////

function 함수() :never{
  while ( true ) { // 무한반복
    console.log(123)
  }
} // never 가능

function 함수() :never{
  throw new Error('에러메세지') // 에러 강제 발생
}// never 가능

 

 

- 파라미터가 never 타입이 되는 경우

function 함수(parameter: string) {
  if ( typeof parameter === "string"){
    parameter + 1;
  } else {
    parameter;
  }
}

함수에서 타입을 string으로 정해 놓았기 때문에 무조건 string 타입이 들어오기 때문에 if문으로 type 체크를 하여 string을 제외한 타입이 들어오는 경우는 일어날 수 없으므로 never 타입이 출력된다. never 타입은 일어날 수 없는 경우, 일어나면 안되는 경우를 보여줄 때 나타난다.

 

 

 

public & private

class 안에서 public이나 private 등의 접근제한자를 통해 데이터 사용 제한을 조정할 수 있다.

 

* public

class User {
  public name: string;

  constructor(){
    this.name = 'kim';
  }
}

let 유저1 = new User();
유저1.name = 'park';  //가능

public은 어디서든 마음대로 사용하고 수정이 가능하다.

 

 

* private

class User {
  public name :string;
  private familyName :string;  

  constructor(){
    this.name = 'kim';
    let hello = this.familyName + '안뇽'; //가능
  }
  
  changeSecret(){
    this.familyName = 'park';
  }

}

let 유저1 = new User();
유저1.name = 'park';  //가능
유저1.familyName = 456; //에러남

private은 오로지 class{} 안에서만 수정이 가능하다. 따라서 private 제한자가 붙은 필드를 수정한다면 class함수 안에서 수정하는 메서드를 만들어 사용하면 된다. (ex. changeSecret())

 

 

- 추가 기능

class Person { 
  name;
  constructor ( name :string ){  
    this.name = name;
  } 
}
let 사람1 = new Person('john')

////////////////////////////////////////

class Person { 
  constructor ( public name :string ){  
  
  } 
}
let 사람1 = new Person('john')

public이나 private같은 접근 제한자를 constructor에 쓰면 필드나 constructor안에 this.속성 등을 생략할 수 있다.

 

 

 

protected & static

* protected

class User {
  protected x = 10;
}

class NewUser extends User {
  doThis(){
    this.x = 20;
  }
}

class 간에 extends로 상속을 받았을 때, 상속 받은 class끼리 공유할 수 있는 속성이라면 protected를, 해당 class에서만 쓸 수 있는 속성을 만들고싶다면 private을 쓴다.

 

 

* static

class에 정의되는 필드나 메서드는 class로부터 생성되는 object(instance)에 부여되지만 static 키워드를 사용하면 class에 직접 변수나 함수를 부여할 수 있다.

class User {
  static x = 10;
  y = 20;
}

let john = new User();
john.x //불가능
User.x //가능

 

ex)

class User { 
  static skill = 'js'; 
  intro = User.skill + '전문가입니다'
}

var 철수 = new User();
console.log(철수); // js전문가입니다.

User.skill = 'python';
var 민수 = new User();
console.log(민수); // python전문가입니다.

class 안에 간단한 메모나, 기본 설정값을 입력하거나, class로부터 생성되는 object가 사용할 필요 없는 변수들을 만들때 사용한다.

 

 

 

import & export & namespace

* import & export

// (a.ts)
export var 이름 = 'kim';
export var 나이 = 30;

// (b.ts)
import {이름, 나이} from './a' // ts는 확장자를 안붙임
console.log(이름)

 

 

- type도 가능하다

// (a.ts)
export type Name = string | boolean;
export type Age = (a :number) => number;

// (b.ts)
import {Name, Age} from './a'
let 이름 :Name = 'kim';
let 함수 :Age = (a) => { return a + 10 }

 

 

* namespace

import와 export가 없을 때에 여러개의 파일을 첨부(<script src="...">)하는 방식으로 사용 했는데 변수명이 겹치는 문제로 인해서 namespace를 사용했다.

// (a.ts)
namespace MyNamespace {
  export interface PersonInterface { age : number };
  export type NameType = number | string;
}

// (b.ts)
/// <reference path="./a.ts" /> // import 대신 namespace에선 이게 필요하다
let 이름 :MyNamespace.NameType = '민수';
let 나이 :MyNamespace.PersonInterface = { age : 10 };

예전엔 namespace 키워드 대신 module 키워드를 사용했는데 namespace로 변경되었다.

 

 

 

 

 

Generic

타입스크립트는 unknown, any, union 타입이 들어왔을 때, 일련의 과정을 거칠 때 타입을 알아서 변경해주지 않기때문에 문제가 생긴다. 해결책은 1) narrowing을 하거나 2) 애초에 타입을 파라미터로 함수에 미리 입력하는 방법이 있다. 2)의 경우를 generic이라고 한다.

 

 

* generic

function 함수<T>(x: T[]) :T {
  return x[0];
}

let a = 함수<number>([4,2])
let b = 함수<string>(['kim', 'park'])

함수를 실행할 때, generic(<>)을 통해 실행하는 함수의 타입을 동적으로 지정할 수 있다. <T>와 같은 generic 타입은 여러개 선언도 가능하다.

 

function 함수<T>(x: T) {
  return x - 1 // 에러
}

let a = 함수<number>(100)

함수를 generic으로 설정 했을 때, -1 연산을 하게되면 에러가 난다. 이유는 generic <T>에 들어가는 타입이 number 말고도 string 등 -1 연산이 불가능한 타입을 넣을 수 있기때문에 1) narrowing을 하거나 2) 타입을 미리 제한 해야한다.

 

 

* constraints

2)에 해당하는 generic 타입을 제한하는 것이다.

function 함수<T extends number>(x: T) {
  return x - 1
}

let a = 함수<number>(100) //잘됩니다

extends를 사용하면 generic 타입을 받아오는 부분을 입력한 형식으로 제한할 수 있다. (class나 interface에서 쓰는 extends 와는 살짝 다르다) 다른 extends는 상속, 복사의 역할을 하지만 여기서의 extends는 체크하는 역할을 한다.

 

또한 커스텀 타입도 extends 가능하다.

interface lengthCheck {
  length : number
}
function 함수<T extends lengthCheck>(x: T) {
  return x.length
}

let a = 함수<string>('hello')  //가능
let a = 함수<number>(1234) //에러남

length의 타입은 number이고 string의 length는 해당 문자열의 갯수를, array에서의 length는 배열의 갯수를 가져오기 때문에 사용할 수 있지만 number는 length를 사용할 수 없다.

 

 

 

react typescript
npx create-react-app 프로젝트명 --template typescript

 

* .tsx

- ts랑 똑같은데 jsx문법을 지원한다. 함수, 컴포넌트, state, props 타입체크를 해야한다.

 

1. 일반 변수, 함수 타입지정

- 일반 타입스크립트처러 함.

 

 

2. JSX 타입지정

// 타입 지정
let 박스 :JSX.Element = <div></div>
let 버튼 :JSX.Element = <button></button>

// 더 정확히 타입 지정
let 박스 :JSX.IntrinsicElements['div'] = React.createElement('div'); // 함수
let 버튼 :JSX.IntrinsicElements['button'] = <button></button>; // JSX

HTML태그는 JSX.Element라는 타입을 쓰면되고 정확히 지정하려면 JSX.IntrinsicElements라는 타입을 사용하면된다.

 

 

3. function component 타입지정

type AppProps = {
  name: string;
}; 

function App (props: AppProps) :JSX.Element { // 생략해도 알아서 지정
  return (
    <div>{message}</div>
  )
}

props 파라미터가 어떻게 생겼는지 타입지정 하면되고 return은 JSX.Element 이지만 자동으로 지정된다.

 

 

4. state 문법 사용시 타입지정

const [user, setUser] = useState<string | null>('kim');

state 만들 땐 자동으로 타입이 할당되지만 나중에 타입이 변하는 경우가 생긴다면 generic을 통해 타입을 집어넣으면 된다.

 

 

5. type assertion 문법 사용시

let code: any = 123; 
let employeeCode = <number> code; //안됩니다

리액트에서는 컴포넌트와 혼동될 수 있으므로 <>는 안되고 as만 사용할 수 있지만 웬만하면 안쓰는게 좋다.

 

 

 

Redux

1. state와 reducer 만들어서 쓰는 방법 (전통 방식)

index.tsx에 설정하는 부분:

import { Provider } from 'react-redux';
import { createStore } from 'redux';

interface Counter {
  count : number
}

const 초기값 :Counter  = { count: 0 }; // 변수 타입 지정

function reducer(state = 초기값, action :any) { // 값 변경하는 부분 타입 지정
  if (action.type === '증가') {
    return { count : state.count + 1 }
  } else if (action.type === '감소'){
    return { count : state.count - 1 }
  } else {
    return initialState
  }
}

const store = createStore(reducer);

// store의 타입 미리 export 해두기 
export type RootState = ReturnType<typeof store.getState>

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
)

state에는 기본적으로 타입을 지정한 변수를 받아오기 때문에 따로 타입 설정이 필요 없다. 변수(state 초기값)나 함수(reducer) 등에 타입을 지정하고 action은 값 변경하는 부분의 타입을 지정하면 된다.

 

App.tsx에 받아오는 부분:

import React from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { Dispatch } from 'redux'
import {RootState} from './index'

function App() {
  const 꺼내온거 = useSelector( (state :RootState) => state );
  const dispatch :Dispatch = useDispatch();

  return (
    <div className="App">
      { 꺼내온거.count }
      <button onClick={()=>{dispatch({type : '증가'})}}>버튼</button>
      <Profile name="kim"></Profile>
    </div>
  );
}

1) react-redux의 useSelector를 받아오는 부분의 타입을 지정한다. 받아오는 타입을 매번 손수 지정하기 귀찮으니 index.tsx에 있는 export해서 가져올 수 있다. (export type RootState = ReturnType<typeof store.getState> 부분)

2) useDispatch에서 데이터를 보내는데 dispatch의 타입으로 redux에서 import한 타입인 Dispatch를 사용해야한다. 안쓰면 에러남.

 

 

2. redux toolkit 쓰는 방법 (요즘 방식)

npm install @reduxjs/toolkit

신식 redux를 사용하려면 해당 툴킷을 설치하면 된다.

 

index.tsx에 설정하는 부분:

import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';

const 초기값 = { count: 0, user : 'kim' };

const counterSlice = createSlice({ // state + reducer 하는 부분
  name: 'counter',
  initialState : 초기값,
  reducers: {
    increment (state){
      state.count += 1
    },
    decrement (state){
      state.count -= 1
    },
    incrementByAmount (state, action :PayloadAction<number>){
      state.count += action.payload
    }
  }
})

let store = configureStore({ // slice로 만든 부분 저장
  reducer: {
    counter1 : counterSlice.reducer
  }
})

//state 타입을 export 해두는건데 나중에 쓸 데가 있음
export type RootState = ReturnType<typeof store.getState>

//수정방법 만든거 export
export let {increment, decrement, incrementByAmount} = counterSlice.actions

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
)

1) state 초기값 타입지정 하기

2) reducer 안의 action 파라미터 타입지정하기

 - action 타입은 PayloadAction이라는 Type을 import 해와서 해당하는 자료의 타입을 generic해서 타입지정 할수 있다.

 

App.tsx에 받아오는 부분:

import { useDispatch, useSelector } from 'react-redux'
import {RootState, increment} from './index'

function App() {

  const 꺼내온거 = useSelector( (state :RootState) => state);
  const dispatch = useDispatch();

  return (
    <div className="App">
      {꺼내온거.counter1.count}
      <button onClick={()=>{dispatch(increment())}}>버튼</button>
    </div>
  );
}

1) useSelector() 안에 파라미터에 타입지정 해서 가져오기

 - state의 타입을 따로 지정하거나 index.tsx에 해당 state의 타입으로 미리 만든 RootState라는 타입을 export해놓고 import해서 타입으로 사용한다.

2) useDispatch()에 타입 지정해서 보내기

 - 공식 문서에는 index.tsx에서 (export type AppDispatch = typeof store.dispatch)을 설정하고 App.tsx에서 (useDispatch<AppDispatch>()) 하는 방식을 권장하지만 (export let {increment, decrement, incrementByAmount} = counterSlice.actions) 부분에서 가져와서 사용해도 된다.

 

 

 

Tuple

array에 붙일 수 있는 타입으로 자료의 위치까지 정확히 지정할 수 있다.

let 멍멍이 :[string, boolean];
멍멍이 = ['dog', true]

 

 

- Tuple: rest parameter

function 함수(...x :[string, number] ){
  console.log(x)
}
함수('kim', 123)  //가능
함수('kim', 123, 456)  //에러
함수('kim', 'park')  //에러

x에 들어오는 파라미터는 array 형태로 담겨오기 때문에 array처럼 타입을 지정한다. 튜플을 이용하면 rest parameter를 엄격하게 사용할 수 있다. 일반 파라미터를 2개 넣고 설정해도 되는데 자료형의 차이가 있다.(array이거나 다른 타입이거나)

 

 

- Tuple option

type Num = [number, number?, number?];
let 변수1: Num = [10];
let 변수2: Num = [10, 20];
let 변수3: Num = [10, 20, 10];

해당의 형태로 자료를 만들 수 있는데 '?' 옵션은 항상 마지막에만 붙일 수 있고 앞의 자료가 같은 '?' 옵션이 붙어있어야 여러개가 사용 가능하다.

 

 

- Tuple: spread 연산자

let arr = [1,2,3]
let arr2 :[number, number, ...number[]] = [4,5, ...arr]

let arr3 :[number, number, ...number[]] = [4,5,6,7,8,9,10]

tuple 타입에 rest parameter 처럼 사용한다. 

 

 

 

declare & ambient module

타입스크립트에서 외부 자바스크립트 파일을 이용할 때 에러가 발생한다.

// (data.js)
var a = 10;
var b = {name :'kim'};

// (index.ts)
console.log(a + 1);

// (index.html)
<script src="data.js"></script>
<script src="index.js"></script>  //index.ts에서 변환된 js 파일

해당 결과는 제대로 잘 나오지만 index.ts 파일에서 a가 정의가 되어있지 않다고 에러가 난다.

 

 

* declare

declare를 사용하면 이미 정의된 변수나 함수를 재정의(override) 할 수 있고 타입도 지정할 수 있다.

- 자바스크립트로만 작성된 외부 라이브러리를 쓸 때 유용하다.

// (data.js)
var a = 10;
var b = {name :'kim'};

// (index.ts)
declare let a :number;
console.log(a + 1);

data.js에 a라는 변수가 있으니 index.ts에서는 에러를 내지말라고 하는 뜻. declare가 붙은 코드는 js로 변환되지 않는다.

 

 

* Ambient Module 

전역으로 쓸 수 있는 파일을 ambient module이라고 한다.

// (data.ts)
type Age = number;
let 나이 :Age = 20;

// (index.ts)
console.log(나이 + 1) // 가능
let 철수 :Age = 30; // 가능

타입스크립트에서 let name 이라는 이름의 변수생성이 안되는 이유를 여기서 찾을 수 있습니다. 어떤 기본 ts 파일이 let name 이미 쓰고있음.

 

 

- import & export

// (data.ts)
export {};
type Age = number;
let 나이 :Age = 20;

// (index.ts)
console.log(나이 + 1) // 불가능
let 철수 :Age = 30; // 불가능

import나 export 키워드가 적어도 하나라도 있다면 그 파일은 로컬 모듈이 된다. 거기에 있는 모든 변수는 export를 해야 다른 파일에서 사용 가능하다.

 

 

* declare global

import나 export가 있는 로컬 모듈에서 전역 변수를 만들고 싶다면 ?

declare global {
  type Dog = string;
}

로컬 파일에서 이런 코드를 적는다면 Dog라는 타입을 전역으로 이용 가능하다. 일종의 namespace라고 보면 된다.

 

 

 

 

 

d.ts

파일명.d.ts 형식의 파일은 타입만 따로 import 하기위한 타입저장용 파일이거나 프로젝트에서 사용하는 타입을 정리해놓은 레퍼런스용 파일이다. (d => definition이며 자바스크립트로 컴파일 되지 않는다.)

 

1. 타입만 따로 정의하기

export type Age = number;
export type multiply = (x :number ,y :number) => number
export interface Person { name : string }

정의 해둔 타입은 export해서 써야한다. d.ts파일은 ts파일이 아니기때문에 global ambient module이 되지 않는다. 쓰려는 ts파일에서 import해서 써야한다.

 

 

2. 레퍼런스로 사용하기

// (tsconfig.json)
{
    "compilerOptions": {
        "target": "es5",
        "module": "es6",
        "declaration": true, // 이 부분
    }
}

declaration 옵션을 true로 바꿔준다. 그럼 ts파일마다 d.ts파일이 생성되고 타입 정의만 정리되어서 담긴다.

 

// (index.ts)
let 이름 :string = 'kim';
let 나이 = 20;
interface Person { name : string } 
let 사람 :Person = { name : 'park' }


// (index.d.ts)
declare let 이름: string;
declare let 나이: number;
interface Person {
    name: string;
}
declare let 사람: Person;

 

 

- d.ts 파일 글로벌 모듈 만들기

{
    "compilerOptions": {
        ...
        "typeRoots": ["./types"] // d.ts 파일 글로벌 모듈화 하기
        ...
    }
}

프로젝트 내에 types/common 이라는 폴더를 만들고 해당 경로에 d.ts 파일을 넣으면 된다. 하지만 이 경우엔 d.ts 자동생성 기능을 끄고 기존 ts파일명과 겹치지 않도록 작성하는게 좋다. 그러므로 웬만하면 중복을 피하기 위해서 import export를 사용하는게 좋다.

 

 

- 유명한 라이브러리 d.ts

jquery나 bootstrap과 같은 잘 알려진 라이브러리는 d.ts 파일을 제공한다. Definitely Typed 이라는 사이트에 주로 쓰는 라이브러리가 모여있어서 찾아쓰거나 타입스크립트 공식 홈페이지 Type Search 에는 npm으로 라이브러리 설치 시 타입스크립트로 정의된 버전이 node_modules/@types 의 경로에 설치되거나 d.ts파일(타입 부분)만 따로 설치할 수 있다. (typeRoots 옵션이 있을경우 제거하거나 node_modules/@types 폴더를 추가해야 한다.)

 

 

 

implements

class 타입을 확인할 때 interface 문법을 사용하는데 implements를 통해 사용한다.

interface CarType {
  model : string,
  price : number
}

class Car implements CarType {
  model : string;
  price : number = 1000;
  constructor(a :string){
    this.model = a
  }
}
let 붕붕이 = new Car('morning');

interface는 object의 타입지정할 때도 쓰지만 class의 타입을 확인할 때도 사용한다. Car 클래스가 model과 price 속성을 다 가지고 있는지 확인 할때 implements를 사용한다.

 

interface CarType {
  model : string,
  tax : (price :number) => number;
}

class Car implements CarType {
  model;   ///any 타입됨
  tax (a){   ///a 파라미터는 any 타입됨 
    return a * 0.1
  }
}

implements는 class의 타입을 체크하는 용도이지 할당하지는 않는다.

 

 

 

object index signatures

object 자료에 타입을 미리 만들 때

1. object 자료에 어떤 속성들이 들어올 수 있는지 아직 모르는 경우

2. 타입지정할 속성이 너무 많은 경우

index signatures를 사용한다.

 

 

* index signatures

interface StringOnly {
  [key: string]: string
}

let obj :StringOnly = {
  name : 'kim',
  age : '20',
  location : 'seoul'
}

[모든 string으로 들어오는 key값에 할당되는 value는 string이다]라는 말이 된다.

 

따라서,

interface StringOnly {
  age : number,   // 에러
  [key: string]: string,
}

interface StringOnly {
  age : string,   // 가능  
  [key: string]: string,
}

위의 코드를 가능하게 하려면

 

interface StringOnly {
  age : number,   ///가능
  [key: string]: string | number,
}

이런 형식으로 작성해야 한다.

 

 

- array

interface StringOnly {
  [key: number]: boolean,
}

let obj :StringOnly = {
  0 : 'kim'
  1 : '20',
  2 : 'seoul'
}

[] 안의 key 값의 타입을 number로 지정할 수도 있다. array처럼 쓰고싶은 object가 있으면 이렇게 타입지정도 가능하다.

 

 

 

* Recursive Index Signature

interface MyType {
  'font-size' : {
    'font-size' : {
      'font-size' : number
    }
  }
}

let obj :MyType = {
  'font-size' : {
    'font-size' : {
      'font-size' : 14
    }
  }
}

object 안의 object 형태로 되어 있는 object의 타입은

 

interface MyType {
  'font-size': MyType | number
}


let obj :MyType = {
  'font-size' : {
    'font-size' : {
      'font-size' : 14
    }
  }
}

이렇게 사용할 수도 있다.

 

 

 

keyof & in

- keyof

keyof 연산자는 object 타입이 가지고 있는 모든 key 값을 union type으로 합쳐서 보내준다. object의 key를 뽑아서 새로운 타입을 만들고 싶을 때 사용한다.

interface Person {
  age: number;
  name: string;
}
type PersonKeys = keyof Person;   //"age" | "name" 타입됩니다
let a :PersonKeys = 'age'; //가능
let b :PersonKeys = 'ageeee'; //불가능

/////////////////////////////////////////////////////////

interface Person {
  [key :string]: number;
}
type PersonKeys = keyof Person;   //string | number 타입됩니다
let a :PersonKeys = 'age'; //가능
let b :PersonKeys = 'ageeee'; //가능

 

 

- Mapped Types

type Car = {
  color: boolean,
  model : boolean,
  price : boolean | number,
};

type TypeChanger <MyType> = {
  [key in keyof MyType]: string; // [ 자유작명 in keyof 타입파라미터 ] : 원하는 타입
};

type 새로운타입 = TypeChanger<Car>;

let obj :새로운타입 = {
  color: 'red',
  model : 'kia',
  price : '300',
}

object 안에 있는 속성들을 다른 타입으로 한번에 전부 변환되어진 타입을 만들 수 있다.

 

 

 

조건문 타입 & infer
type Age<T> = T extends string ? string : unknown;
let age : Age<string> //age는 string 타입
let age2 : Age<number> //age는 unknown 타입

삼항 연산자를 통해 type을 지정해줄 수 있다.

 

ex)

type FirstItem<T> = T extends any[] ? T[0] : any 

let age1 :FirstItem<string[]>;
let age2 :FirstItem<number>;

 

 

- infer

조건문을 사용할 때 infer 키워드를 사용하면 지금 입력한 타입을 변수로 만들 수 있다.

type Person<T> = T extends infer R ? R : unknown; 
type 새타입 = Person<string> // 새타입은 string 타입입니다

1) infer는 조건문 안에서만 사용가능

2) infer 우측을 자유롭게 작명하면 타입을 T에서 유추해서 R이라는 변수에 집어넣는다

3) R을 조건식 안에서 맘대로 사용 가능하다.

 - 타입파라미터에서 타입을 추출해서 쓰고싶을 때 사용하는 키워드이다.

 

ex)

// array 안에 있던 타입이 어떤 타입인지 뽑아서 변수로 만들 수 있다
type 타입추출<T> = T extends (infer R)[] ? R : unknown; 
type NewType = 타입추출< boolean[] > // NewType 은 boolean 타입입니다 

// 함수의 return 타입이 어떤 타입인지 뽑아서 변수로 만들 수 있다
type 타입추출<T> = T extends ( ()=> infer R ) ? R : unknown; 
type NewType = 타입추출< () => number > // NewType은 number 타입입니다

타입을 추출하는 식이라고 생각하면 된다. 이 작업을 대체하는 ReturnType<>이런 예약 타입이 있는데 여기에 함수타입을 대입하면 return 타입만 알아서 뽑아준다.

 

type Age<T> = T extends [string, ...any] ? T[0] : unknown;
let age1 :Age<[string, number]>;
let age2 :Age<[boolean, number]>;

type 타입뽑기<T> = T extends (x :infer R) => any ? R : any;
type a = 타입뽑기<(x :number) => void> //이러면 number가 이 자리에 남음
type b = 타입뽑기<(x :string) => void> //이러면 string이 이 자리에 남음

 

 

 

'TypeScript' 카테고리의 다른 글

[TypeScript] chap.1  (0) 2021.12.15

[TypeScript] chap.1

 

 

 

설치 및 세팅

1. nodejs 설치

2. 타입스크립트 설치

npm install -g typescript
tsc -w // 자바스크립트로 저장시 자동 컴파일

 

 

* React에서 Typescript 사용

- 이미 있는 프로젝트

npm install --save typescript @types/node @types/react @types/react-dom @types/jest

 

- 새로운 프로젝트

npx create-react-app my-app --template typescript

 

 

* Vue에서 Typescript 사용

vue add typescript
<script lang="ts">
  
</script>

lang 옵션을 키고 사용할 수 있다.

 

 

* tsconfig.json

- 타입스크립트 ts 파일들을 .js 파일로 변환할 때 어떻게 변환할 것인지 세부설정하는 파일

{
 "compilerOptions": {

  "target": "es5", // 'es3', 'es5', 'es2015', 'es2016', 'es2017','es2018', 'esnext' 가능
  "module": "commonjs", //무슨 import 문법 쓸건지 'commonjs', 'amd', 'es2015', 'esnext'
  "allowJs": true, // js 파일들 ts에서 import해서 쓸 수 있는지 
  "checkJs": true, // 일반 js 파일에서도 에러체크 여부 
  "jsx": "preserve", // tsx 파일을 jsx로 어떻게 컴파일할 것인지 'preserve', 'react-native', 'react'
  "declaration": true, //컴파일시 .d.ts 파일도 자동으로 함께생성 (현재쓰는 모든 타입이 정의된 파일)
  "outFile": "./", //모든 ts파일을 js파일 하나로 컴파일해줌 (module이 none, amd, system일 때만 가능)
  "outDir": "./", //js파일 아웃풋 경로바꾸기
  "rootDir": "./", //루트경로 바꾸기 (js 파일 아웃풋 경로에 영향줌)
  "removeComments": true, //컴파일시 주석제거 

  "strict": true, //strict 관련, noimplicit 어쩌구 관련 모드 전부 켜기
  "noImplicitAny": true, //any타입 금지 여부
  "strictNullChecks": true, //null, undefined 타입에 이상한 짓 할시 에러내기 
  "strictFunctionTypes": true, //함수파라미터 타입체크 강하게 
  "strictPropertyInitialization": true, //class constructor 작성시 타입체크 강하게
  "noImplicitThis": true, //this 키워드가 any 타입일 경우 에러내기
  "alwaysStrict": true, //자바스크립트 "use strict" 모드 켜기

  "noUnusedLocals": true, //쓰지않는 지역변수 있으면 에러내기
  "noUnusedParameters": true, //쓰지않는 파라미터 있으면 에러내기
  "noImplicitReturns": true, //함수에서 return 빼먹으면 에러내기 
  "noFallthroughCasesInSwitch": true, //switch문 이상하면 에러내기 
 }
}

 

 

 

기본 타입
let 이름 :string = 'kim'; // string
let 나이 :number = 20; // number
let 결혼했니 :boolean = false; // boolean

let 회원들 :string[] = ['kim', 'park'] // array
let 내정보 : { age : number } = { age : 20 } // object

// **기본적으로 변수 생성 시 타입이 알아서 지정되기 때문에 일일이 할 필요는 없다.

 

 

 

 

union 타입
let 이름: string | number = 'kim';
let 나이: (string | number) = 100; // 같음

///////////////////////////////////////////

var 어레이: number[] = [1,'2',3]
var 오브젝트: {data : number} = { data : '123' }

var 어레이: (number | string)[] = [1,'2',3]
var 오브젝트: {data : (number | string) } = { data : '123' }

 

 

 

any, unknown 타입

* any

아무 자료나 대입할 수 있는 타입. (타입스크립트의 기능을 해제하는 타입)

let 이름: any = 'kim';
이름 = 123;
이름 = undefined;
이름 = [];

 

 

* unknown

아무 자료나 대입할 수 있는 타입. (타입은 uknown으로 지정되어 다른 타입과는 에러발생. any는 아님)

let 이름: unknown;

let 변수1: string = 이름;
let 변수2: boolean = 이름;
let 변수3: number = 이름;

///////////////////////////

let 이름: unknown;
이름[0];
이름 - 1;
이름.data;

 

타입스크립트는 언어가 엄격하기 때문에 변경하려는 변수의 타입이 확실해야만 연산을 수행해준다. -1, +1 같은 경우는 number 타입일 때만, .name 등은 object 타입 일때만 사용이 가능하다.

 

 

 

함수 & void

1. 함수로 들어오는 자료 (파라미터)

2. 함수에서 나가는 자료 (return)

function 내함수(x :number) :number { 
  return x * 2 
}

 

 

- void

function 내함수(x :number) :void { 
  return x * 2 //여기서 에러남 
}

함수에서 return을 방지할 때 사용한다.

 

 

- 파라미터가 옵션인 경우

function 내함수(x? :number) { // == x : number | undefined

}
내함수(); //가능
내함수(2); //가능

x? :number은 x :number | undefined 와 같은 의미이다.

 

 

 

 

 

Narrowing & Assertion

타입을 확정할 때 쓰는 문법.

 

* Type Narrowing

function 내함수(x :number | string){
  if (typeof x === 'number') {
    return x + 1
  } 
  else if (typeof x === 'string') {
    return x + 1
  }
  else { // 함수안에서 if쓸 때 else가 없으면 에러날 수도 있으니 쓰자
    return 0
  }
}

 

 

* Type Assertion

function 내함수(x :number | string){ 
    return (x as number) + 1 
}
console.log( 내함수(123) )

해당하는 변수를 number로 지정한다. (내가 꼭 ! number 타입이 들어올 것이라는 확신이 있을 때 사용한다.)

 

특징:

1. union 타입 중에서 하나의 정확한 타입으로 지정할 때 사용 (형 변환처럼 사용하면 에러 남)

2. 타입실드를 임시로 해제한다고 생각하면 된다. (타입을 바꿔주는건 아니다)

 

Assertion은

1. 타입에러가왜 나는지 모르겠는 상황에서 에러 해결용으로 사용

2. 어떤 타입이 들어갈지 확실히 아는데 컴파일 상에서 에러가 날 때 사용.

 

**웬만하면 Narrowing 쓰는게 더 나음.

 

 

 

type & readonly

* type

타입의 정의가 너무 길거나 복잡하게 나열된 타입을 변수에 담아 사용할 수 있다.

type Animal = string | number | undefined;
let 동물 :Animal;
type 사람 = {
  name : string,
  age : number,
}

let teacher :사람 = { name : 'john', age : 20 }

 

 

- object의 속성이 선택사항일 때

type Square = {
  color? : string,
  width : number,
}

let 네모2 :Square = { 
  width : 100 
}

함수의 파라미터와 마찬가지로 object에서도 ?를 통해 string | undefined 형태로 선택적으로 사용할 수 있다.

 

 

- type 키워드 여러개 합치기

type Name = string;
type Age = number;
type NewOne = Name | Age;

or 연산자를 이용해서 union 타입으로 만들 수 있다.

 

type PositionX = { x: number };
type PositionY = { y: number };
type XandY = PositionX & PositionY
let 좌표 :XandY = { x : 1, y : 2 }

and(&) 기호를 쓰면 object 안의 두개의 속성을 합칠 수 있다. (extend)

 

type Name = string;
type Name = number; // 불가

type 키워드는 재정의가 불가능하다.

 

 

* readonly

object 속성을 const처럼 재 할당할 시에 값이 변경하는걸 미리 감지하고 차단한다

type testType = {
  readonly name : string,
}

let 이름 :testType = {
  name : '홍길동'
}

이름.name = '고길동' //readonly라서 에러남

컴파일 시에 에러만 출력되는것이고, 자바스크립트로 변환하면 문제없이 작동한다.

 

 

 

Literal type

특정 글자나 숫자만 가질 수 있게 그것으로 타입 제한을 두는 타입

let john :'대머리';
let kim :'솔로';

let 방향: 'left' | 'right';
방향 = 'left';

function 함수(a : 'hello') : 1 | 0 | -1 {
  return 1 
}

 

- as const

1. 타입을 object의 value로 바꿔준다. (타입을 'kim'으로 바꿔준다)

2. object 안에 있는 모든 속성을 readonly로 바꾼다. (변경하면 에러)

var 자료 = {
  name : 'kim'
}
function 내함수(a : 'kim') { // 'kim' 타입

}
내함수(자료.name) // string 타입 => 에러

function의 파라미터 변수 a는 'kim'이라는 리터럴 타입이고, 자료.name으로 가져오는 'kim'은 string 타입이다.

 

var 자료 = {
  name : 'kim'
} as const;

function 내함수(a : 'kim') {

}
내함수(자료.name)

해결책:

1. object 만들 때 object의 타입을 지정한다.

2. assertion을 쓴다 (ex. as 'kim')

3. as const를 object 자료형에 붙인다.

 

 

 

function & method의 type alias

* function

type NumOut = (x : number, y : number ) => number ;

let ABC :NumOut = function(x,y){
  return x + y
} // 함수 표현식

함수에 type 지정하기 (함수 선언문 형태로는 type을 만들 수 없다. 사용할 때도 표현식으로 사용)

 

 

* method

type 회원정보타입 = {
    name: string,
    plusOne: (a: number) => number,
    changeName: () => void
};

let 회원정보 :회원정보타입 = {
    name: 'kim',
    plusOne(a: number): number{
        return a + 1;
    },
    changeName: () => {
        console.log('안녕');
    }
};
회원정보.plusOne(1);
회원정보.changeName();

 

 

 

HTML

- 세팅

{
    "compilerOptions": {
        "target": "ES5",
        "module": "commonjs",
        "strictNullChecks": true // true로 설정 (false는 타입스크립트 쓰는 의미가..)
    }
}

null 체크 옵션인데 html 조작할 때 셀렉터로 찾으면 null이 발생하는 일이 많다.

 

 

- HTML 변경

  • narrowing
  • instanceof
  • assertion
  • optional chaining
  • strict false
let 제목 = document.querySelector('#title');
if(제목 != null){
    제목.innerHTML = '반가우요';
}
if(제목 instanceof Element){
    제목.innerHTML = '반가우요';
}
제목.innerHTML = '반가우요'; //as Element => assertion
if(제목?.innerHTML != undefined){
    제목.innerHTML = '반가우요';
}

 

 

- instanceof & optional chaining

let 링크 = document.querySelector('.link');

// instanceof
if(링크 instanceof HTMLAnchorElement){
    링크.href = 'https://kakao.com'
}

// optional chaining
let 버튼 = document.querySelector('#button');
버튼?.addEventListener('click', function(){
    
})

ex:

<a> =  HTMLAnchorElement

<img> = HTMLImageElement

<h4> = HTMLHeadingElement

 

 

 

 

 

class

- 필드값 타입지정

class Person {
  data = 0;
}

let john = new Person();
let kim = new Person();

console.log(john.data);
console.log(kim.data);

 

 

- constructor 타입지정

class Person {
  name;
  age;
  constructor ( a :string ){
    this.name = a;
    this.age = 20;
  }
}

constructor에도 default parameter나 rest parameter를 사용 할 수 있다.

 

 

- method 타입지정

class Person {
  
  add(숫자){ // Person의 prototype에 저장 됌
    console.log(숫자 + 1)
  }
}

add라는 함수를 prototype으로 사용할 수 있다.

 

 

 

interface

object 자료형의 타입을 편리하게 저장할 수 있다.

interface Square { 
  color :string, 
  width :number, 
} 

let 네모 :Square = { color : 'red', width : 100 }

 

 

- extends

interface Student {
  name :string,
}
interface Teacher {
  name :string,
  age :number,
}

///////////////////////////////////////

interface Student {
  name :string,
}
interface Teacher extends Student {
  age :number
}

let 학생: Student = {name: 'kim'}
let 선생: Teacher = {name: 'kim', age: 20 }

interface의 extends를 사용하면 extends 되는 object 안에 있는 것들을 복사해서 넣어준다.

 

 

* interface와 type의 차이점

- interface

interface Animal { 
  name :string 
} 
interface Cat extends Animal { 
  legs :number 
}

extends하기

 

interface Student {
  name :string,
}
interface Teacher {
  age :number
}

let 변수 :Student & Teacher = { name : 'kim', age : 90 }

intersection(&) 기호를 통한 extends와 유사하게 사용하기

 

// 에러 안남 (타입 명 중복 허용)
interface Animal { 
  name :string 
} 
interface Animal { 
  legs :number 
}

////////////////////////////

// 에러 남
interface Animal { 
  name :string 
} 
interface Dog extends Animal { 
  name :number 
}

타입이름 중복 선언시 extends한 것이랑 동일하게 동작하며 interface 안에 개별적 속성을 한꺼번에 가질 수 있다. type 선언을 자주하는 외부 라이브러리 이용시 type 선언을 덮어쓰기(override) 하기 편하다.

 

 

- type

type Animal = { 
  name :string 
} 
type Cat = Animal & { legs: number }

extends 대신에 intersection(&) 기호를 통해서 object 두개를 합칠 수 있다. (하지만 interface도 이렇게 사용할 수 있다.)

 

// 에러 남
type Animal = { 
  name :string 
} 
type Animal = { 
  legs :number 
}

///////////////////

type Animal = {name: string}
type Cat = {name: number} & Animal

let 야옹이 :Cat = {name: 'kim'}

타입이름 중복 선언시 에러난다. 타입을 intersaction(&) 했을 때, 타입 안에 개별적 속성이 겹치는 경우, 사용할 때 에러가 난다.

 

 

결론: object형태의 자료형은 interface로 만들고 다른형태(string, number, boolean ... 등 primitive 타입)의 자료형은 type 키워드로 만들어 사용하자.

 

 

 

'TypeScript' 카테고리의 다른 글

[TypeScript] chap.2  (0) 2021.12.17

+ Recent posts