React Native

리덕스의 흐름 파악하기

리액트바오 2023. 5. 25. 22:47

회사에서 새로 만들고 있는 서비스는 리덕스를 사용한다.  지금까지 UI만 개발 했다면, 다음주 정도 부턴 실제 데이터를 가져오게 될텐데 그때 본격적으로 사용해야하기에 미리 집에서 한번 공부를 해보는것이다.

리덕스를 처음 접해보는것은 아니다. 이전에 코드스테이츠에서 학습한 경험이 있고 팀 프로젝트에서도 리덕스로 상태관리를 했다. 

 

하지만 파일을 나누거나 코드를 분류하는 방식이 제 각각이기 때문에 다시 한번 공부해보며 적응해야할 필요가 있다. 

 

리덕스란 무엇일까?

Redux(리덕스)란 자바스트립트 상태관리 라이브러리이다. 

 

상태관리의 문제

예를들어 온라인 쇼핑몰 사이트가 있을때 그 사이트에는 여러가지 상태가 있을것이다. 장바구니에 물건을 담거나 뺄때  물건의 가격 상태가 변하며 장바구니에 담긴 물건 수가 증감 하는 등 데이터가 변한다.

이때, 상태가 변하는 함수가 적힌 상위 컴포넌트에서 자식 컴포넌트로 상태를 전달해줘야하는데, 컴포넌트가 많을시 위에서 부터 차례대로

전달

전달

전달

전달

전달

전달

전달

해서 상태를 변경해야할 수도 있다는것이다. 

 

다이렉트로 전달되면 좋겠지만, 컴포넌트의 관계에 따라 여러번에 걸쳐서 props를 전달해야할 수 도 있다. 아주 복잡하고 어디서 내려온 것인지 찾아가기도 시간이 많이 걸린다!! 으휴,, (경험담)

 

상태관리에는 리덕스 말고도 여러가지가 있다.

-  Redux

-  MobX

-  Zustand

-  Recoil

등등

 

리덕스를 사용하는 이유

이글은 여러 상태 관리중에 '왜 하필이면 리덕스를 사용해야하는지'에 대한 글이 아니다. 일단 난 리덕스를 써야하는 운명? 이고  그에 따라 상태관리툴인 '리덕스를 사용해햐하는 이유'를 정리하는 것이다.

1. 상태를 한곳에서 관리할 수 있다.

리덕스는 스토어(store)를 사용해서 상태를 관리하는데, 이는 상태가 예측 가능하고 일관성을 유지한다. 또 여러 컴포넌트에서 동일한 상태를 공유하고 사용할 수 있도록 해준다.

2. 상태를 예측 할 수 있으며 유지 보수에 좋다.

리덕스는 상태 변화를 예측 가능한 방식으로 관리한다. 모든 상태 변화는 순수 함수인 리듀서(reducer)를 통해 이루어지기 때문에 상태 변화를 추적하거나 디버깅하기에 좋다. 또 상태의 변화를 기록하거나 되돌릴 수 있는 기능도 제공한다.

3. 컴포넌트 간 데이터 흐름을 단순화 시켜준다.

리액트에서 컴포넌트는 상태를 가질 수 있으며 이 상태는 부모 컴포넌트로부터 전달되는 props로 관리된다. 그러나 컴포넌트 트리가 깊어지고 컴포넌트 간의 관계가 복잡해질수록 데이터 흐름을 관리하기 어려워진다. 위에서 언급했듯 

전달

전달

전달

전달

전달

해줘야할 수 도 있는것이다. 리덕스는 컴포넌트 간의 데이터 흐름을 단순화시켜준다.

4. 비동기 작업 관리

애플리케이션에서 비동기 작업은 흔한 요구 사항이다. 리덕스는 미들웨어(middleware)를 통해 비동기 작업을 처리하고, 비동기 작업 상태를 효과적으로 관리할 수 있도록 해준다. 예를 들어, 네트워크 요청, 데이터 가져오기  등의 비동기 작업을 리덕스 미들웨어를 사용하여 처리할 수 있다.

 

리덕스의 핵심요소

1. 액션(Action)

상태 변화를 일으키는 정보를 가지고 있는 객체다. 액션은 반드시 type이라는 필수 속성을 가져야 한다. 예를 들어 type: ADD_TODO와 같이 액션의 종류를 나타내는 문자열 값을 가질 수 있다. 다른 추가적인 데이터를 담을 수도 있다.

2.리듀서(Reducer)

액션에 따라 상태를 어떻게 업데이트할 지 정의하는 순수함수다 리듀서는 이전 상태와 액션을 인자로 받아서 새로은 상태를 반환한다. 이전 상태는 변경되지 않고 새로운 상태 객체를 반환하는 것이 원칙이다.

3. 상태(State)

애플리케이션의 전역 상태를 나타내는 객체다. 리덕스에서는 하나의 상태트리를 가지고 있으며 애플리케이션의 모든 컴포넌트에서 접근 가능하다. 상태는 읽기 전용이며, 리듀서에 의해서만 변경된다. 

4.스토어(Store)

애플리케이션의 상태와 리듀서를 포함하는 객체이다. 스토어는 리덕스의 핵심이며, 애플리케이션의 상태를 단일 소스로 관리한다. 스토어에는 상태를 조회하기 위한 메서드와 상태를 업데이트하기 위한 메서드가 제공된다. 

5. 디스패치(Dispatch)

디스패치는 액션을 스토어에 전달하여 상태를 업데이트하는 메서드이다. 디스패치는 액션 객체를 받아서 해당 액션을 처리할 리듀서를 호출한다. 리듀서는 액션의 type에 따라 상태를 변경하고 새로운 상태를 반환한다.

 

즉, 리덕스는 위의 핵심 요소들을 조합하여 상태 관리를 수행한다. 액션은 리듀서에 의해 처리되어 상태가 처리되어 상태가 업데이트되고, 업데이트된 상태는 스토어에 저장된다. 컴포넌트는 스토어에서 상태를 조회하고, 디스패치를 통해 액션을 전달하여 상태를 업데이트할 수 있다. 이를 통해 상태 변화를 예측 가능하고 일관성 있게 관리할 수 있다는 것이다. 

 

예시 코드

1. 액션 타입 정의하기 (actionTypes.js)

// 액션 타입 정의
export const ADD_TODO = 'ADD_TODO';
export const DELETE_TODO = 'DELETE_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';

2.액선 생성자 함수 만들기 (actions.js)

import { ADD_TODO, DELETE_TODO, TOGGLE_TODO } from './actionTypes';

// 할 일 추가 액션 생성자 함수
export const addTodo = (text) => ({
  type: ADD_TODO,
  payload: {
    text,
  },
});

// 할 일 삭제 액션 생성자 함수
export const deleteTodo = (id) => ({
  type: DELETE_TODO,
  payload: {
    id,
  },
});

// 할 일 완료 여부 토글 액션 생성자 함수
export const toggleTodo = (id) => ({
  type: TOGGLE_TODO,
  payload: {
    id,
  },
});

3. 리듀서 작성하기 (reducer.js)

import { ADD_TODO, DELETE_TODO, TOGGLE_TODO } from './actionTypes';

const initialState = {
  todos: [],
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(),
            text: action.payload.text,
            completed: false,
          },
        ],
      };
    case DELETE_TODO:
      return {
        ...state,
        todos: state.todos.filter((todo) => todo.id !== action.payload.id),
      };
    case TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map((todo) => {
          if (todo.id === action.payload.id) {
            return {
              ...todo,
              completed: !todo.completed,
            };
          }
          return todo;
        }),
      };
    default:
      return state;
  }
};

export default reducer;

4. 스토어 생성하기 (store.js)

import { createStore } from 'redux';
import reducer from './reducer';

const store = createStore(reducer);

export default store;

5. 컴포넌트에서 스토어 사용하기 (App.js)

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, deleteTodo, toggleTodo } from './actions';

const App = () => {
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  const handleAddTodo = (text) => {
    dispatch(addTodo(text));
  };

  const handleDeleteTodo = (id) => {
    dispatch(deleteTodo(id));
  };

  const handleToggleTodo = (id) => {
    dispatch(toggleTodo(id));
  };

  return (
    <div>
      <h1>Todo List</h1>
      <form onSubmit={(e) => {
        e.preventDefault();
        const text = e.target.todo.value;
        handleAddTodo(text);
        e.target.todo.value = '';
      }}>
        <input type="text" name="todo" />
        <button type="submit">Add</button>
      </form>
      <ul>
        {todos.map((todo) => (
          <li
            key={todo.id}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
            onClick={() => handleToggleTodo(todo.id)}
          >
            {todo.text}
            <button onClick={() => handleDeleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;

예시 코드는 할일을 추가하고 삭제할 수 있으며 완료 여부를 체크할 수 있다. 리덕스를 사용하여 상태 관리를 하기 위해 'useSelector'dhk 'useDispatch' 훅을 사용한다. 

- useSelector로 상태를 조회한다.

- useDispatch를 통해 액션을 디스패치 한다.

 

느낀점

리덕스 없었음 큰일날뽄 ..?

현재 회사에서 진행중인 프로젝트에서 아주 유용하게 사용해봐야겠다. 

하나씩 알아가는 즐거움을 느끼는 요즘이다.