210916-210927 CodeStates 44-46일차

공윤배·2021년 10월 3일

210916-210927 CodeStates 44-46일차

추석연휴 일주일을 제외한 3일동안 상태관리를 도와주는 상태관리 라이브러리 Redux의 개념에 대해 공부하고 React의 useState를 이용해 상태관리를 하는 웹애플리케이션을 작성하고 다시 Redux로 리팩토링해보는 실습을 진행했다.

프론트엔드 개발에서의 상태관리

상태(State)란 변하는 데이터, 다시말해 프론트엔드 개발에서 UI에 동적으로 표현될 데이터이다.
React에서 State는 컴포넌트내에서 관리된다.
얼마전 React의 단방향 데이터흐름에 대해 공부했었다.

왼쪽의 페이지를 구성하기 위해 컴포넌트별로 나누고 아래의 그림과 같이 트윗목록을 저장하는 상태값 tweetList는 Tweets컴포넌트와 NewTweet컴포넌트 모두 접근할 수 있어야 하기 때문에 두개의 컴포넌트의 공통 부모컴포넌트인 App컴포넌트에서 tweetList를 관리해야한다.

페이지의 구조가 단순하기 때문에 React컴포넌트의 수도 많지 않고 복잡한 구조를 이루고 있지 않기 때문에 코드를 봐도 그렇게 헷갈리지는 않을 것이다.
하지만 만약에 밑의 그림처럼 React컴포넌트의 수가 많아지고 트리구조가 복잡해질 경우 상태관리가 복잡해진다.

만약 C컴포넌트가 NewTweet컴포넌트이고, J컴포넌트가 Tweets컴포넌트라면 React의 단방향 데이터흐름원칙때문에, App컴포넌트에서 상태관리를 해야하고 상태갱신함수와 State값을 props로 전달해야한다.(props drilling)

이러한 상태관리의 복잡성을 줄이기 위해 상태관리 라이브러리 Redux를 사용할 수 있다.

상태관리 라이브러리 Redux

Redux는 React에서 가장 많이 사용되는 대표적인 상태관리 라이브러리이다.
다만 React만을 위한 라이브러리는 아니다.
위의 사례처럼 컴포넌트수가 많아지고, 트리구조가 복잡해질수록 상태관리가 어려워지는 문제가 있다.
Redux를 사용하면 상태관리를 컴포넌트 내부에서 처리하지않고, 컴포넌트의 밖에서 처리할 수 있게된다.
Redux를 이용하여 상태관리를 컴포넌트 외부에서 처리하게되면(state를 변경하는 로직을 컴포넌트 밖에서 처리하게 되면) 개발자는 단순하게 표현에 집중한 React컴포넌트를 작성할 수 있게된다.

Redux의 용어정리

  • Action(액션)
    Action은 애플리케이션에서 발생한 일을 설명하는 이벤트로 생각할 수 있다.
    State를 변경할 때 참조하는 객체이다.
    반드시 type필드를 가지고 있어야한다.

  • Action Creator(액션생성함수)
    Action객체를 만들어주는 함수이다.

  • Reducer(리듀서)
    Reducer는 액션객체와 현재 State를 인자로 받아 필요하다면 새로운 State를 리턴하는 함수이다.
    수신된 Action(이벤트) 유형에 따라 이벤트를 처리하는 이벤트리스너라고 생각할 수 있다.
    Reducer는 기존의 State와 Action객체를 인자로받아 리턴할 State를 계산할 때 인자외의 값에는 의존하지 않는 순수함수로 작성되어야 한다.
    기존의 상태를 건드리지 않고 새로운 State객체를 만들어 반환해야한다.

  • Store(스토어)
    Store는 애플리케이션에서 사용하는 모든 State들이 저장되어있는 저장소이다.
    하나의 애플리케이션은 유일한 Store를 갖는다.

connect를 이용하여 컴포넌트와 스토어를 연결해주는 대신 react-redux의 useDispatch와 useSelector를 사용하는 방법을 배웠다.

  • useDispatch
    Action객체를 Reducer에 전달하는 역할을 한다.

  • useSelector
    컴포넌트가 Store에 저장된 State를 조회할 수 있게 해준다.

Redux의 세가지원칙

  1. Single source of truth
    유일한 저장소(store)가 존재하며, 애플리케이션의 변할 수 있는 모든 상태 값들은 이 저장소에 저장되어 있다.
    유일한 저장소를 이용하여 상태관리를 하기 때문에 상태관리가 간단해졌다.
  2. State is read-only
    State는 읽기전용이며, State를 변경하는 유일한 방법은 dispatch함수를 이용하여 Reducer에 Action객체를 전달한는 것이다.
  3. Changes are made with pure function
    현재의 State와 어떠한 Action객체를 전달받아 새로운 State를 반환하는 Reducer함수는 순수함수로 작성되어야 한다.
    이전의 State를 변경시켜 반환하는것이 아닌 새로운 State객체를 만들어 반환해야한다.

간단한 실습

복습 겸 redux를 이용하여 todo를 작성하고 삭제할수 있는 간단한 페이지를 만들어보았다.

// App.js

import './App.css';
import {useDispatch,useSelector} from 'react-redux';
import {useRef} from 'react';

// Action Creator 액션생성함수
const addTodo=(text)=>{
    // Action객체를 반환한다.
    return {
        type:"ADD",
        payload:text
    }
}
const deleteTodo=(text)=>{
    return {
        type:"DEL",
        payload:text
    }
}

// 사용자의 입력을 받는 Input컴포넌트
const Input=()=>{
  //사용자가 input태그에 입력한 값에 접근하기 위해 useRef를 사용
  const todoRef=useRef(null);
  const dispatch=useDispatch();

  const addHandler=()=>{
      //dispatch를 이용하여 addTodo가 반환하는 Action객체를 Reducer로 전달한다.
      dispatch(addTodo(todoRef.current.value));
      todoRef.current.value='';
  }

  return (
      <div>
          <input type='text' ref={todoRef}/>
          <button onClick={addHandler}>todo등록</button>
      </div>
  )
}

// 사용자가 입력한 todo들을 보여주는 컴포넌트
const Todolist=()=>{
  const dispatch=useDispatch();
  // useSelector를 이용하여 Store에 저장된 todo값에 접근할 수 있다.
  const todoList=useSelector(state=>state.todo);

  function deleteHandler(todo){
      dispatch(deleteTodo(todo));
  }

  return (
      <div>
          {todoList.length===0?
          '등록된 todo가 없습니다.':
          todoList.map((el,index)=>{
              return (
                  <div key={index}>
                      <span> {el} </span>
                      <button onClick={()=>{deleteHandler(el)}}>todo삭제</button>
                  </div>
              )
          })
          }
      </div>
  )
}

function App() {
  return (
    <div className="container">
      <Input></Input>
      <Todolist></Todolist>
    </div>
  );
}

export default App;
//index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from 'react-redux';
import {createStore} from 'redux';

// Reducer에 전달할 State의 초기값
const initialState={todo:[]};

// Reducer함수
// 전달받은 Action객체의 type을 기준으로 State를 변경한다.
const todoReducer=(state=initialState,action)=>{
    switch(action.type){
        case "ADD":
            return {todo:[...state.todo,action.payload]};
        case "DEL":
            return {todo:state.todo.filter(el=>el!==action.payload)};
        default:
            return state; 
    }
}

// createStore함수를 이용하여 Store객체를 생성
// 인자로 Reducer를 전달한다.
const store=createStore(todoReducer);

// Provider를 이용하여 페이지의 코드를 작성한 App컴포넌트를 감싸준다.
// 생성된 Store를 애플리케이션의 유일한 저장소로 연결시켜준다.
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

reportWebVitals();

밑의 그림은 Redux공식문서에 첨부된 사진이다.
위의 코드와 아래의 그림을 보며 Redux가 어떻게 작동하는지 대략적으로 알 수 있었다.

최초에 Reducer를 이용해 Store가 생성된다.
State의 초기값은 Reducer의 State인자 default값으로 넣어준다.
작성된 페이지의 UI의 이벤트가 발생하고, 이벤트핸들러안의 dispatch함수가 실행된다.(addHandler,deleteHandler)
dispatch함수는 인자로 전달된 Action객체(Action Creator로 만들어진)를 Reducer에 전달하고 Reducer는 기존의 State와 전달받은 Action객체를 이용하여 새로운 State를 반환한다.
State가 반환되고 페이지의 리렌더링이 진행된다.

0개의 댓글