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: 코딩애플