Redux

2023. 1. 9. 21:13프로그래밍/React

    목차

Redux

자바스크립트 애플리케이션을 위한
상태 관리 라이브러리

 

Redux 데이터 Flow (단방향 데이터 흐름)

  1. React Componenet: 이벤트 발생
  2. Action: 리듀서 함수에게 액션을 발생시키라고 명령함 (dipatch)
  3. Reducer: 스토어 내부 상태 업데이트
  4. Redux Store: 새롭게 업데이트 된 상태를 이용해 다시 렌더링
  5. React Componenet: 이벤트 발생 ...

 

Action

간단한 자바스크립트 객체
type 속성: 수행하는 작업 유형 지정
payload 속성: redux 저장소에 데이터를 보낼 때 사용
{ type: 'LIKE_ARTICLE', articleId: 42 }
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } }
{ type: 'ADD_TODO', text: 'Read the Redux docs.' }

 

Reducer

애플리케이션 상태의 변경 사항을 결정하고
업데이트된 상태를 반환하는 함수
전달된 인수를 이용해 로직을 처리하고 store 내부의 상태를 업데이트함
(previousState, action) => nextState

이전 State, Action 객체를 받은 후, next state를 반환함

 

Reducer 는 순수 함수이기 때문에 함수 내부에서 다음과 같은 작업을 하지 않아야 함

  1. arguments 를 변경하는 것
  2. API 호출, 라우팅 전환 같은 사이드 이펙트를 수행하지 않아야 함
  3. 비순수함수 호출 (Date.now() 또는 Math.random() 등)

 

Redux Store

애플리케이션의 전체 상태 트리를 보유함
내부 상태를 변경하는 유일한 방법은 해당 상태에 대한 Action을 전달하는 것
Redux Store는 클래스가 아니라 메서드를 가지고 있는 객체임

 

Dispatch

Store의 내장 함수 중 하나
리듀서 함수에게 Action을 발생시키라고 명령함
Action을 인자로 전달하여 사용함
dispatch(action)

리덕스 + 타입스크립트 프로젝트 생성

npx create-react-app [프로젝트 이름] --template typescript

 

리덕스 라이브러리 설치

npm install redux --save

Reducer 생성

Root Reducer

import { combineReducers } from 'redux';
import todos from './todos';
import counter from './counter';

const rootReducer = combineReducers({
  todos,
  counter,
});

export default rootReducer;

 

Sub Reducer

enum ActionType {
  ADD_TODO = 'ADD_TODO',
  DELETE_TODO = 'DELETE_TODO',
}

interface Action {
  type: ActionType;
  text: string;
}

const todos = (state = [], action: Action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, action.text];
    case 'DELETE_TODO':
      return;
    default:
      return state;
  }
};

export default todos;

 

Store 생성

const store = createStore(rootReducer);

 

getState()

애플리케이션의 현재 상태 트리를 반환함
스토어의 리듀서가 반환한 마지막 값과 같음

 

subscribe()

change listener 를 추가함
작업이 전달될 때마다 호출되며 상태 트리의 일부가 잠재적으로 변경되었을 수 있음
그 후 getState() 를 호출하여 콜백 내부의 현재 상태 트리를 읽을 수 있음
const render = () =>
  root.render(
    <Provider store={store}>
      <App
        onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
        onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
      />
    </Provider>
  );
render();

store.subscribe(render);

 

Provider

모든 중첩 구성 요소에서 Redux Store 저장소를 사용할 수 있도록 해줌
대부분의 프로젝트에서는 최상위 컴포넌트에서 렌더링함
import { Provider } from 'react-redux';

const store = createStore(rootReducer);

...

const render = () =>
  root.render(
    <Provider store={store}>
      <App
        onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
        onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
      />
    </Provider>
  );
render();

store.subscribe(render);

 

useSelector

Provider 로 둘러싸인 컴포넌트에서 store 에 접근 가능
  const counter = useSelector((state: RootState) => state.counter);

 

useDispatch

store 에 있는 dispatch 함수에 접근하는 hooks
const counter = useSelector((state: RootState) => state.counter);
const todos: string[] = useSelector((state: RootState) => state.todos);
const dispatch = useDispatch();

const addTodo = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  dispatch({ type: 'ADD_TODO', text: todoValue });
  setTodoValue('');
};

Redux Middleware

액션을 전달하고(dispatch) 리듀서에 도달하는 순간 사이에
미리 지정된 작업을 실행할 수 있게 해줌

 

Redux Logging Middleware 생성

const loggerMiddleware = (store: any) => (next: any) => (action: any) => {
  console.log('store', store);
  console.log('action', action);
  next(action);
};

export default loggerMiddleware;

 

applyMiddleware

미들웨어 함수를 applyMiddleware 함수에 인자로 전달함
인자로 여러 개의 미들웨어를 전달할 수 있음
전달된 모든 미들웨어는 순서대로 실행됨
const middleware = applyMiddleware(loggerMiddleware);

const store = createStore(rootReducer, middleware);