5 - 1 리덕스 기본 개념 이해하기
리덕스와 리덕스 관련 필수 패키지
리액트 제작사인 메타(구 페이스북)는 리액트를 처음 발표할 때 플럭스라고 부르는 앱 설계 규격을 함께 발표했습니다. 플럭스는 앱 수준 상태, 즉 여러 컴포넌트가 공유하는 상태를 리액트 방식으로 구현하는 방법입니다. 이후로 플럭스 설계 구격을 준수하는 오픈소스 라이브러리가 등장했는데, 리덕스는 그중에서 가장 많이 사용되는 패키지 입니다.
리덕스를 사용하려면 redux와 @reduxjs/toolkit(줄여서 RTK) 패키지, 그리고 리액트 프레임워크에서 사용할 react-redux패키지를 설치해야 합니다.
react-redux는 리액트와 함께 동작해야 하므로 다른 프레임워크에서는 사용할 수 없습니다. 이 패키지들은 모두 타입스크립트로 제작되었으므로 타입 라이브러리를 별도로 설치할 필요는 없습니다.
Provider 컴포넌트와 store 속성
리덕스 기능을 사용하려면 리액트 컨텍스트의 Provider 컴포넌트가 최상위로 동작해야 합니다. 따라서 react-redux 패키지는 다음처럼 Provider 컴포넌트를 제공합니다.
import {Provider} from 'react-redux'
그런데 Provider란 이름은 너무 일반적이므로 이 책에서는 다음처럼 ReduxProvider란 별칭으로 사용하겠습니다.
import {Provider as ReduxProvider} from 'react-redux'
ReduxProvider를 src/App.tsx 파일에 적용해 보면 다음처럼 store 속성값이 설정돼있지 않다는 오류가 발생합니다. 이제 이 오류가 발생하는 배경을 알아보겠습니다.
리덕스 저장소와 리듀서, 액션 알아보기
타입스크립트 언어로 리덕스 기능을 사용할 때는 먼저 다음처럼 앱 수준 상태를 표현하는 AppState와 같은 타입을 선언해야 합니다.
export type AppState = {}
만약 시계 앱을 만든다면 AppState는 다음처럼 작성할 수 있습니다.
export type Appstate = {
today: Date
}
리덕스 저장소는 AppState 타입 데이터를 저장하는 공간입니다. 그런데 리덕스 저장소를 생성하려면 리듀서라는 함수를 아라야 합니다. 리덕스에서 리듀서는 현재 상태와 액션이라는 2가지 매개변수로 새로운 상태를 만들어서 반환합니다.
다음은 리듀서 함수의 선언문입니다. 리듀서는 잠시 후에 다시 알아보기로 하고, 여기서는 리덕스 저장소를 생성하는 부분에만 집중하겠습니다.
Reducer 선언문
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S
리듀서 선언문 나오는 액션은 플럭스에서 온 용어로서 type이란 이름의 속성이 있는 평범한 자바스크립트 객체를 의미합니다. redux 패키지는 다음처럼 액션 객체의 타입을 선언하고 있습니다. 이 액션 선언문은 type 속성이 반드시 있어야 한다는 의미 입니다. 액션에 대해서도 잠시 후에 알아보겠습니다.
Action 선언문
export interface Action<T = any> {
type: T
}
스토어 객체 관리 함수
RTX 패키지는 리듀서에서 반환한 새로운 상태를 스토어라는 객체로 정리해 관리하는 configureStore함수를 제공합니다.
import {configureStore} from '@reduxjs/toolkit'
다음은 단순하게 표현한 configureStore 함수의 선언문으로, 이 선언문에 따르면 configureStore는 ConfigureStoreOptions제네릭 타입 매개변수를 1개 입력받는 함수입니다.
configureStore 함수 선언문
export declare function configureStore<S, A, M>(options: ConfigureStoreOptions<S, A, M>): EnhancedStore<S, A, M>;
다음은 ConfigureStoreOptions 타입을 단순하게 표현한 예입ㄴ디ㅏ. 이 타입은 필수 속성인 reducer와 더불어 middleware등 선택 속성 4개로 구성되어있습니다.
ConfigureStoreOptions 타입 정의
export interface ConfigureStoreOptions<S, A, M> {
reducer
middleware?
devTools?
reloadedState?
enhancers?
}

기본 앱 파일 분리하기
참고로 리액트 커뮤니티에서는 리덕스 관련 파일을 stroe라는 이름의 디렉터리에 저장하는 관행이 있습니다.





이런식으로 분리해서 만들 수 있다.
useSelector 훅 사용하기
이제 리덕스 저장소에 어떤 내용이 저장되었는지 알고자 스토어의 상탯값을 반환해 주는 useSelector훅을 살펴보겠습니다.
useSelector 훅 임포트
import {useSelector} from 'react-redux'
다음은 제네릭 함수입니다.
export function useSelector<TState, TSelected>(
selector: (state: TState) => TSelected
): TSelected;
다음 코드는 useSelector 훅으로 AppState타입의 today속성값을 얻는 예입니다.
const today = useSelector<AppState, Date>(state => state.today)


리덕스 액션 알아보기
앞서 구현한 ReduxClock 컴포넌트가 시계로서 동작하려면 리덕스 저장소의 today값을 현재 시각으로 변경해줘야합니다. 이와동시에 ReduxClock 컴포넌트를 다시 렌더링하여 바뀐 today값을 화면에 반영해야 합니다.
시계를 만드는 코드는 리덕스 저장소의 다른멤버 속성들의 값은 건드리지 않고 오직 today 속성값만 변경해 줘야 합니다.
리덕스에서 액션은 저장소의 특저 속성값(여기서는 today)만 변경하고 싶을 때 사용하는 방법입니다.


위 사진에서 리덕스 액션은 반드시 type이란 이름의 속성이 있어야 하므로 redux 패키지는 Action 타입을 제공합니다. 그래서 교집합 타입 구문으로 속성을 추가해줍니다.
이처럼 액션의 type속성이 리듀서에 분기문을 구현할 수 있게 해주는 것이므로, type속성의 타입을 다음처럼 'setToday'로 정할 수 있습니다
리덕스 리듀서 알아보기
앞서 작성한 rootReducer.ts 파일에서 변수 이름만 다음처럼 변경해보겠습니다.
"첫 번째 매개변수에 담긴 과거 상탯값(preState)을 바탕으로 새로운 상탯값(newState)을 반환한다"는 리듀서 함수의 목적이 분명해집니다. 이 코드에 앞서 구현한 액션 부분의 코드를 반영해 보겠습니다.

다음은 앞에서 구현한 SetTodayAction을 반영한 것으ㅗ 액션과 리듀서의 관계를 좀 더 명확하게 표현하고있습니다. 그런데 리덕스에서 리듀서를 구현할 때는 prevState, newState라는 이름 대신 그냥 state를 주로 사용합니다.

이 코드는 액션이 SetTodayAction 타입이 아니면 state값을 변경하지 않고 그대로 반환한다느 ㄴ의미를 보여줍니다. 또한 마지막 줄에 필수라는 주석을 단 이유는 vscode에서 필요없는 코드처럼 회색표시로 해서인데 꼭있어야하는 코드입니다 .왜그런지는 다음에 절에 알아보겠습니다.
useDispatch 훅 사용하기
useDispatch 훅 임포트
import {useDispatch} from 'react-redux'
useDispatch훅을 호출하면 다음처럼 dispatch() 함수를 얻을 수 있습니다.
const dispatch = useDispatch()
dispatch() 함수를 사용하여 다음 코드 형태로 리덕스 저장소에 저장된 AppState 객체의 멤버 전부나 일부를 변경 할 수 있습니다. 다음은 type 속성값이 'setToday' 인 액션을 dispatch() 함수를 통해 리덕스 저장소로 보내는 코드입니다.
dispatch({type: 'setToday', today: new Date()})
dispatch 함수와 리듀서 간의 관계 이해하기
지금까지 리덕스 저장소와 리듀서 그리고 액션과 dispatch() 함수를 알아보았는데, 이들의 관계그림을 표현하면 다음과 같습니다.

이 그림은 리덕스 저장소에 저장된 앱 수준 상태으 일부 속성값을 변경하려면 일단 액션을 만들어야 한다는 것을 의미합니다. 그리고 액션은 반드시 dispatch() 함수로 리덕스 저장소에 전달해야 합니다. 그리고 액션이 리덕스 저장소에 전달될 때 리듀서가 관여합니다.
또한 다음 그림은 리듀서에 전달되는 두 매개변수 state와 action이 어떻게 만들어지는지를 보여줍니다. 리덕스 저장소는 앱 수준 상태를 저장하는 것이 목적이므로 첫 번째 매개변숫값을 만들수 있습니다. 또한 액션은 반드시 dispatch() 함수로 전달 되므로 dispatch(액션) 코드가 실행되면 두 번째 매개변수 action이 리듀서로 전달됩니다.

시계 완성하기
이제 useDispatch 훅을 호출하여 dispatch() 함수를 얻고, dispatch(액션)를 1초에 한 번씩 호출하여 시계를 완성해 보겠습니다.

useReducer 훅 사용하기
이번에는 useReducer 훅을 알아보겠습니다. useReducer 훅은 이름에서 알 수 있듯이 리덕스의 리듀서와 사실상 똑같은 기능을 수행합니다. useReducer 훅은 4장에서 본 다른 훅 함수들처럼 ReduxProvider와 같은 컨텍스트 없이 사용합니다.이 때문에 리덕스의 상태는 앱의 모든 컴포넌트에 접근할 수 있지만, useReducer 훅의 상태는 다른 훅 함수들처럼 useReducer 훅을 호출한 컴포넌트 안에서만 유효하다는 차이가 있습니다.
useReducer 훅 임포트
import React, {useReducer} from 'react'
useReducer 훅을 사용하면 여러번의 useState와 useCallback 훅 호출 코드를 간견하게 구현 할 수 있습니다.
useReducer 훅 사용법
const [상태, dispatch] = useReducer(리듀서, 상태_초깃값)
그런데 리덕스의 리듀서와 useReducer 훅은 초기 상탯값을 설정하는 부분에 차이가 있습니다. 리덕스의 리듀서는 다음처럼 리듀서의 첫 번째 매개변수에 기본값을 설정합니다.
리덕의 리듀서 기본값 설정방법
const initailState: AppState = {
today: new Date()
}
export const rootReducer = (state: AppState = initialState, action: AppActions) => {}
반면에 useReducer 훅은 두 번째 매개변수에 초깃값을 설정합니다.
useReducer 훅 초기 상탯값 설정 방법
useReducer((state: AppState, action: AppActions) => {}, {today: new Date()})
이제 useReducer 훅을 실습해 보겠습니다.

