본문 바로가기
Archive

React에서 순수 Redux의 사용과 개념+원리

by livemehere 2022. 1. 26.

Redux는 왜 사용하는가?

일반적인 Vanilla Javascript에서는 크게 Redux의 필요성을 못느꼈을 수 있을것입니다.

하지만 리액트를 어느정도 사용해본 사람이라면 컴포넌트간에 state를 props를 통해서 전달하는 것이 익숙할 겁니다.

하지만 App이라는 root 컴포넌트에서 자식컴포넌트들이 10개, 20개만 되어도, state를 한번공유하는것이 번거로워집니다.

리액트는 중요한 state나 로직은 최상위 컴포넌트에서 가지고있고, 하위컴포넌트들은 간단히 props를 받아 display하는것으로 가볍게 동작하는것이 이상적이니깐요

 

 이때 리덕스를 사용하는겁니다. 리액트에선 흔히 depth라고 하는, 컴포넌트의 자식의 자식의 자식.... 에게 도달하기위한 깊이가 생기는데, 그러기위해서 수많은 컴포넌트를 거쳐 props를 전달합니다. 

그림과 같이 리덕스가없다면 화살표가 중구난방이고, 리덕스를 사용한다면 하나의 store에 저장된 state를 공유하게되는 형태가 됩니다.

그렇다고, 모든 state를 redux로 관리하는것은 좋지않습니다.

 

Redux의 원리

 

리덕스의 원리입니다.

보통 리액트에서는 useState를 사용하여 state를 만들고, 리액트의 Virtual DOM이 state의 변화를 감지하여 View를 자동으로 업데이트합니다.

 

리덕스도 비슷한 원리로 동작하지만, state를 변경하는 과정에서의 차이가 있습니다.

 

Store라는 것은 우리의 state가 저장되는 곳입니다. 여기서 데이터를 가져다쓸수있고,데이터를 subscribe하여 변화를 감지하죠

 

그리고 그 store을 변경하기 위한 정의가 되어있는곳이 Reducer입니다. Reducer에서는 조건에따라서, 값을 변경할 로직을 가지고있습니다.

 

Reducer를 원하는 조건으로 호출하는 것이 Action이 됩니다.

 

그리고 이 Action을 호출하는 곳은 사용자겠죠? 그러니 View에서 dispatch를 하여 ActionReducer에게 전달하도록 합니다.

 

 이런 것들을 구조화 해놓았기때문에, 어떤 컴포넌트이던지 dispatch를 할수있다면 ,state를 변경할수있고

subscribe을 한다면 state를 사용하고, 변경사항이 바로적용되도록 할수있습니다.

 

리덕스와 리액트에서 사용하기위한 라이브러리 설치

yarn add redux react-redux
npm install redux react-redux

 

우선 저는 처음에 redux를 접했을때, 이해는됬으나, 그럼 여러개의 state를 어떻게 관리하지? 라는궁금증이있어서 한번에 두개의 별도의 state를 관리하는 store를 만들어보겠습니다.

 

index.js

import { createStore } from "redux";
import rootReducer from "./rootReducer";
import { Provider } from "react-redux";

const store = createStore(rootReducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

app.js에서는 store를 만들고, store에 저장할 state를 넣어줍니다(reducer)

그리고 최상위 컴포넌트를 감싸도록 Provider 태그에 store를 담아서 감싸줍니다. 

이제 App이라는 컴포넌트를 포함한 모든 하위컴포넌트들은 store에 접근이 가능하게된겁니다.

 

rootReducer.js

import { combineReducers } from "redux";
import todoReducer from "./todoReducer";
import counterReducer from "./counterReducer";

const rootReducer = combineReducers({
  todoReducer,
  counterReducer,
});

export default rootReducer;

저는 reducer를 두개 사용할 것이기 때문에 rootReducer를 만들어 여러개의 reducer를 합쳐주었습니다.

만약 내가 todoReducer만 사용할거다! 라고한다면, App.js에서 createStore(todoReducer) 로 변경해주면 됩니다.

reducer를 합쳐주는 함수는 combineReducers({}) 입니다.

인자로 사용하고자하는 모든 reducer를 넣어주면 됩니다.

reducer는 쉽게 비교해서 생각하자면 useState()와 같은 역할입니다. 일반적으로 useState를 사용해서 변수를 생성했을때, 첫번째로 그변수를 read하고,subscribe하는 변수를 제공해주고, 두번째로는 그 state를 변경할수있는 함수를 제공해줍니다. reducer도 동일하게, subscribe을 제공하여 state를 read하고, 상태를 감시할수있게하고, dispatch를 통해서 state를 변경할 수 있게해줍니다.

counterReducer.js

export const INCREASE = "INCREASE";
export const DECREASE = "DECREASE";

export const increaseCount = () => ({ type: INCREASE });
export const decreaseCount = () => ({ type: DECREASE });

const initalState = 0;

const counterReducer = (state = initalState, action) => {
  switch (action.type) {
    case INCREASE:
      return state + 1;
    case DECREASE:
      return state - 1;
    default:
      return state;
  }
};

export default counterReducer;

Reducer는 일반적으로 크게 3가지로 구성할 수 있습니다. (취향에따라 정말간소화 하고자한다면 counterReducer함수 하나만 만들어도 됩니다) 하지만 위코드와 같은 구조를 가졌을때 사용하기도 좋고, 유지보수하고, 사용자의 실수를 최소화 시킬 수 있습니다.

 

가장중요한 최초의 state 값은 0 으로 초기화해주었습니다.

그리고 counterReducer함수는 인자를 두개받도록합니다(store에서 이런함수를 받아서 처리하기때문에, 인자두개를 가진 함수형태로 만들어줍니다)

 

state에는 초기 state값을 대입해줍니다. 두번째 인자로는 action파라미터를 넣습니다.

유저가 사용할때 action이라는 객체에 type을 담아서 reducer로 전달하기때문에, action.type에 따라서 state의 상태를 변형해주는 switch문도 작성해줍니다.

 

그리고 중요한것은 이 reduer함수는 state를 항상반환한다는 것인데, 이말은 즉 기존의 state가 아닌, 새로운 state를 반환한다는 말입니다.

redux에서는 항상 이전 state의 값과 변경된 state값을 비교하기때문에, 이런식으로 새로운 state를 반환하는 역할을 하는 것이 reducer입니다.

 

위의 export된 4개의 코드라인은 앞서말한 안정적인 구조를위한 추가적인 것입니다.

사용자가 dispatch할때 단순히 dispatch({type:"INCREASE"})를 해도되지만, 이렇게된다면 오류발생시 찾기가 힘들어집니다.

string은 컴파일단계에서 오류를 잡지않습니다. 만약에 오타가 나서 ({type:"JNCREASE"}) 이런식으로 한다면, 에러가 발생하기전까진 오류를 찾지못하고, 발생하면 해당 컴포넌트를 찾아 이동해서 수정해야합니다.

 

하지만 위코드처럼, reducer파일에 export로 action부분을 정의해두고 사용자는 그것만 사용한다면, 에러가발생해도 reducer로만 오면되기때문에 더 이점을 가져옵니다.

 

todoReducer.js

export const ADD = "ADD";
export const REMOVE = "REMOVE";

export const AaddTodo = (text) => ({ type: ADD, text: text });
export const AremoveTodo = (id) => ({ type: REMOVE, id: id });

const initalState = [{ id: Date.now(), text: "go work" }];

const todoReducer = (state = initalState, action) => {
  switch (action.type) {
    case ADD:
      return [...state, { id: Date.now(), text: action.text }];
    case REMOVE:
      return state.filter((todo) => todo.id !== action.id);
    default:
      return state;
  }
};

export default todoReducer;

잘만들어놓은 redux 사용하기

app.js

import { useDispatch, useSelector } from "react-redux";
import Todo from "./components/todo";
import { decreaseCount, increaseCount } from "./counterReducer";
import { AaddTodo } from "./todoReducer";

function App() {
  const { todoReducer, counterReducer } = useSelector((state) => state);
  const dispatch = useDispatch();

  const addTodo = (e) => {
    const text = e.target[0].value;
    e.preventDefault();
    dispatch(AaddTodo(text));
    e.target[0].value = "";
  };

  const handleIncrease = () => {
    dispatch(increaseCount());
  };

  const handleDecrease = () => {
    dispatch(decreaseCount());
  };

  return (
    <div className="App">
      <h1>React Todo with Redux</h1>
      <form onSubmit={addTodo}>
        <input type="text" />
        <button>ADD</button>
      </form>
      <ul>
        {todoReducer.map((todo) => (
          <Todo key={todo.id} {...todo} />
        ))}
      </ul>
      <h1>Counter</h1>
      <span>{counterReducer}</span>
      <button onClick={handleIncrease}>+</button>
      <button onClick={handleDecrease}>-</button>
    </div>
  );
}

export default App;

보기와같이 hook을 사용하면 손쉽게 useSelector와, useDispatch()를 이용하여 Provider로 전달한 store를 꺼내쓸수있고, 자동으로 subscribe까지하고, dispatch를 간단히 reducer로 전달할 수 있습니다.

 

Github

https://github.com/livemehere/react-redux-study

 

GitHub - livemehere/react-redux-study: react에서 redux 사용하기

react에서 redux 사용하기. Contribute to livemehere/react-redux-study development by creating an account on GitHub.

github.com

 

단점

단점이 느껴지지 않으신가요?

useState를 이용해서 props지옥을 막고자 redux를 썻건만, 코드량이 이렇게 많다는 것이 사용을 망설이게 할겁니다.

다음 포스팅에선 redux의 코드를 줄여주는 redux Toolkit에 대해서 알아보겠습니다. 

반응형