Develop/TypeScript

[Typescript] React에 Typescript 사용하는 법

dawonny 2024. 1. 16. 15:16
728x90
반응형

React 프로젝트 생성

타입스크립트의 셋팅이 완료된 새로운 프로젝트를 생성하는 법이다

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

 

만약 기존 프로젝트에 타입스크립트만 추가하고 싶다면 다음과 같이 터미널에 입력한다

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

 

Typescript로 만든 React 프로젝트

일반 프로젝트와 다른 점은 컴포넌트 파일에 js 가 아닌 tsx 확장자를 사용해야한다는 것이다.

그러면 리액트 프로젝트에서 typescript 문법을 어디에 응용할 수 있을지 알아보자

 

JSX 타입지정

리액트에서는 변수나 자료에 <div> 를 담아서 쓸 수 있다.

리액트에서 <div> 를 사용하면 html 이 아닌 JSX 라는 자료가 된다.

let 박스 :JSX.Element = <div></div>
let 버튼 :JSX.Element = <button></button>

그래서 이런 타입을 지정하고 싶으면 JSX.Element 라는 타입을 쓰자

component 타입 지정

함수형 컴포넌트의 타입지정은 파라미터와 return 부분을 타입지정한다.

여기서 파라미터는 props를 말한다.

type AppProps = {
  name: string;
}; 

function App (props: AppProps) :JSX.Element {
  return (
    <div>{message}</div>
  )
}

 

위 코드에서는 AppProps 라는 객체 형태의 타입을 지정한다. 

name 이라는 string 속성을 포함한다.

그리고 반환 타입으로는 <div>를 반환하기 때문에 JSX.Element 를 반환 타입으로 지정해주었다.

(하지만 컴포넌트는 JSX를 return 하기 때문에 생략해도 자동으로 타입지정이 된다.)

 

JSX 를 props로 하기

만약 props로 JSX를 입력하고 싶다면 다음과 같이 작성한다.

JSX.IntrinsicElements 라는 이름의 타입을 사용하는 방법이다.

type ContainerProps = {
  a: JSX.IntrinsicElements['h4'];
}; 

function Container (props: ContainerProps) {
  return (
    <div>{props.a}</div>
  )
}

a 라는 props에는 <h4>만 넣을 수 있도록 하는 방법이다.

 

 

state 문법에 타입지정

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

 

state 를 만들 때 자동으로 타입이 할당되긴 하지만,

만약 state 타입이 나중에 변할 수 있거나 여러 타입을 지정해놓고 싶다면 < > 안에 타입을 설정해주면 된다.(Generic 문법이용)

 

Type assertion 문법 사용

assertion 을 하고 싶으면 as 나 < > 를 사용하면 된다

하지만 리액트에서는 < > 를 쓰면 컴포넌트로 오해할 수 있으니 as 키워드만 쓰자

let code: any = 123; 
let employeeCode = <number> code; // NO!

 

 

결국 타입스크립트를 쓴다고 리액트 개발방식이 크게 달라지는 것이 아니라

변수 정의 부분의 타입지정을 할 수 있다는 것과 같은 차이점이 있을 뿐이다.

 

전통방식의 redux + typescript

redux 를 쓰는 이유는 다음과 같았다
1. state를 한 곳에서 관리 가능 -> 컴포넌트들이 props 없이 state 다루기 쉬움

2. 수정방법을 reducer 함수로 정의해놔서 state 수정할 때 발생하는 버그를 줄일 수 있음

 

여기에서 말하는 전통방식이라는 건 redux 공식 toolkit 라이브러리 안쓰고 if문 switch문 등을 쓰는 방식을 말한다.

 

npm install redux react-redux

 

redux 설치는 위처럼 한다.

 

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

interface Counter {
  count : number
}

const 초기값 :Counter  = { count: 0 };

function reducer(state = 초기값, action :{type : string}) {
  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')
)

 

위 코드는 index.ts 의 코드이다.

button 을 누르면 state가 +1, -1 되는 간단한 예제이다.

 

redux 를 쓸 때는 state 초기값과 reducer 함수의 타입지정을 잘해두자.

일단 초기값 변수 오른쪽에 타입지정을 잘해주고, reducer 함수는 state, action 파라미터 2개 타입지정을 한다.

state 는 초기값을 넣으면 타입지정이 잘돼서 필요없긴하다.

reducer 함수의 return 타입도 타입지정을 할 수 있다.

 

 

state 를 꺼낼 때를 살펴보자

아래는 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>
  );
}

 

redux 에 있는 state 를 가져오려면 useSelector 훅을 써서 간단하게 가져올 수 있다.

state 를 변경할 때에는 useDispatch 를 사용한다.

 

- 타입지정하는 법

1. useSelector() 안에 파라미터에 하기
index.ts에서 타입을 export 해서 사용할 수도 있다.
index.ts에 있던 export type RootState = ReturnType<typeof store.getState> 코드가 store 타입을 미리 export 해두는 방법이다.

2. useDispatch 타입지정하기
import {Dispatch} from 'redux' 로 타입을 가져와서
const dispatch :Dispatch 처럼 쓰면 된다.

 

 

신식 redux + typescript

요즘은 redux, react-redux 이외에 redux tookkit을 추가로 설치해서 많이들 사용한다고 한다.

이걸 쓰면 더 코드를 깔끔하게 짤 수 있기 때문

npm install @reduxjs/toolkit

 

아래는 index.ts 이다

// index.tsx

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

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

const counterSlice = createSlice({
  name: 'counter',
  initialState : 초기값,
  reducers: {
    increment (state){
      state.count += 1
    },
    decrement (state){
      state.count -= 1
    },
    incrementByAmount (state, action :any){
      state.count += action.payload
    }
  }
})

let store = configureStore({
  reducer: {
    counter1 : counterSlice.reducer
  }
})

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

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

// React 애플리케이션 루트에 Provider 추가
ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

 

위 코드에 대한 설명은 다음과 같다.

createSlice()로 slice 를 만들어준다. slice 는 state와 reducer 를 합쳐놓은 것이라고 생각하면 편하다.

slice  안에는 slice의 이름, state의 초기값, reducer 가 정확하게 들어가 있어야한다.(작명 X)

state 는 원하는대로 만들고, reducer 는 함수 형태로 만든다. 첫 파라미터에는 state, 두번째 파라미터에는 actions 가 자동 부여된다.

 

다 만들었다면 configureStore에 넣고 등록한다.

내가 만든 reducer 를 쓰고 싶으면 reducer 안의 함수명을 export 한다.

 

타입지정하기
1. state 초기값 타입지정
2. reducer 안의 action 파라미터 타입지정
3. 나머지는 타입지정 자동

 

 

action 타입지정 방법

import { createSlice, PayloadAction } from '@reduxjs/toolkit'

(상단 생략)
  incrementByAmount (state, action: PayloadAction<number>){
      state.value += action.payload
  },

action 타입지정은 위처럼 하라고 권장한다고 한다.

payload란 dispatch할 때 보내는 데이터를 의미하는데,

그 자료의 타입을 < > 안에 넣어서 타입지정하는 것이다.

 

지금은 payload의 타입이 number 여서 < > 안에 number 라고 작성해줬다.

 

신식 redux, state 를 꺼낼 때

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>
  );
}

useSelector() 를 사용하면 state 를 쉽게 꺼낼 수 있다.

꺼내고 사용하는 방법은, ()->{} 와 같은 콜백함수를 작성해주는 것이다.

이 함수의 첫 파라미터는 항상 state 가 된다.

useDispatch () 를 쓰면 쉽게 수정요청을 날릴 수 있다.

 

타입지정은 state 와 dispatch에 하면 된다.

1. useSelector() 안의 파라미터에 타입지정
state 를 파악하고 타입을 지정해주거나, 타입을 index.ts 와 같이 reducer 을 만든 곳에서 미리 RootState 라는 타입을 export 해두면 import 해서 쉽게 타입지정이 가능하다.

2. useDispatch() 사용할 때 타입지정
공식문서에서는 index.ts에서 export type AppDispatch = typeof store.dispatch 를 하고 
App.tsx에서 import 해와서 useDispatch<AppDispatch>() 이렇게 타입지정하라고 되어있긴하다.

 

역시 Redux 보면.. Recoil이나 Zustand가 쉽고 편한건 짱인 것 같다.


ref: 코딩애플

728x90
반응형