코드스테이츠/코드스테이츠 @ 개발 복습

[코드 스테이츠] 63일차, "9주차 복습 (2) - Redux"

Je-chan 2021. 9. 19. 23:36


[ 오늘의 TODO ]

  1. 코드 스테이츠) 목~금 내용 복습
    // Redux
  2. 패스트 캠퍼스) 인강 3개 이상 듣기 // optional
  3. 스터디 그룹) 프로그래머스 문제 풀기
  4. 생활) 물 1L 이상 마시기
  5. 생활) 1시간 이상 걷기

[ 오늘의 복습 ]

1. 상태 관리

  React 뿐만 아니라 프론트엔드 개발 전체에 걸쳐서 상태 관리는 매우 중요하다. 여기서 상태란 State, 즉 변하는 데이터다. 특히 UI, 프론트엔드 개발에서는 동적으로 표현되는 데이터라 한다. 

 

1) 상태관리의 중요성

 

  예를 들어 Youtube를 생각해보자. 댓글을 다는 것도 동적으로 표현되는 데이터고, 좋아요를 누르는 것과 플레이 리스트에 영상을 담는 건 전부 동적으로 변하며 표현되는 데이터다. 이를 더 깊게 파고 들어가 본다면 상태에 따라서 각 컴포넌트가 어떤 영향을 주고 받는지를 확인할 수 있을 것이다. Youtube는 동영상에 좋아요 버튼을 누른다면, [좋아요를 누른 동영상] 이라는 카테고리에 영상이 담긴다. 이때, State 는 동영상을 재생하는 컴포넌트에서 변화된 것이지만, 이 변화에 따라서 [좋아요를 누른 동영상]을 담은 컴포넌트에 영향을 주게 된다. 이런 UI덕에 우리는 좋아요를 누른 동영상들을 쉽게 다시 볼 수 있다. 이처럼 프론트엔드에서 상태 관리는 매우 중요한 이슈다.

 

2) Side Effect

 

  상태를 다룰 때 side effect 는 주요 고려 대상이다. side effect 란 한 함수의 결과가 자기 뿐만 아니라 다른 함수의 결과에도 영향을 주는 요인을 말한다. 예시로는 API 호출 등이 있다. 

 

  React 를 대표하는 특징 중 하나는 컴포넌트 단위 개발이다. 컴포넌트에 어떤 데이터가 들어오든 컴포넌트는 자기 자신을 표현하는 것에만 집중하게 된다. 쉽게 풀어 설명하면 컴포넌트 안의 데이터는 바뀔지라도 컴포넌트라는 UI 조각은 고정되는 것이다. 좋아요를 많이 눌렀다고 해서 [좋아요를 누른 동영상]의 전체적인 UI가 흔들리지 않는 것과 같다. 이런 걸 presentatioan 컴포넌트라고 한다. 하지만 앱을 만들면 API 호출 등 side effect 는 불가피하다. 예를 들면 Youtube 에서 동영상을 불러와야 하는데 스트리밍이 되지 않아 로딩 중 상태창이 나타나는 것이다. 이런 경우는 데이터가 전송이 되었느냐 안 되었느냐에 따라 UI 가 달라지는 것이라 볼 수 있다.

 

3) 로컬 상태, 전역 상태

 

  상태를 구분하는 절대적 기준과 법칙은 존재하지 않다. 편의상 특정 컴포넌트 안에서만 관리되는 상태를 로컬, 프로덕트 전체나 여러 컴포넌트에서 관리되는 상태를 전역이라고 하자

 

  로컬 상태는 컴포넌트 내에서만 영향을 끼친다. 해당 컴포넌트 안에서만 조작하면 되므로 관리하기 매우 쉽다. Youtube 댓글창을 생각해보자. 댓글창은 Youtube 동영상을 재생하는 컴포넌트에서만 관리되고 다른 컴포넌트에서는 관리하지 않는다.(확실치는 않은데 만약 아니라면 그렇다고 가정해보자) 전역 상태는 여러 컴포넌트에서 공유하고 영향을 주는 상태다. 좋아요를 누르는 경우, 좋아요를 눌렀느냐에 따라 [좋아요를 누른 동영상] 을 담은 컴포넌트에 영향을 준다. 

  

  4) 전역 상태의 무결성

  

  나는 처음 자바 스크립트를 배울 때부터 var 를 웬만하면 사용하지 말라고 가르침을 받았다. var 는 전역 변수, 함수 스코프를 따르므로 우리가 의도하지 않은 결과를 낼 수 있기 때문이다. 그와 마찬가지로 전역 상태는 우리가 의도하지 않은 결과를 만들어낼 수 있다. 예를 들면, 좋아요를 누르더라도 제대로 플레이 리스트에 담기지 않다거나 싫어요를 눌렀는데 [좋아요를 누른 동영상]에 추가되는 경우다. 

 

  서로 다른 컴포넌트가 사용하는 상태의 종류가 다르면 그 상태가 반드시 전역 상태일 필요가 없다. 하지만 우리가 배운 것에 따라 서로 다른 컴포넌트가 동일한 상태를 관리하고 그에 따라 영향을 받는다면 그 컴포넌트들의 공통 조상 컴포넌트에 상태를 관리하게 된다. 이는 상태의 출처를 한 곳으로 잡는 것이다. 이런 작업을 해주는 이유는 전역 상태의 무결성을 위함이다. 데이터에 무결성이란 말을 붙이는 건 데이터의 정확성을 보장하기 위해서 데이터 변경이나 수정 시 제한을 두는 것을 의미한다. 제한을 걸어두면 데이터 안정성을 확보하고 Single Source of Truth (신뢰할 수 있는 단일 출처 원칙) 에 위배되지 않도록 관리할 수 있다. 만약에 무결성이 보장하지 않는다면 우리가 의도하지 않은 일들이 일어날 수 있다. 전역 상태도 마찬가지다. 

 

  전역으로 상태를 관리하는 대표적인 예시는 브라우저 다크모드다. 모든 페이지, 코든 컴포넌트에 다크모드를 적용해야 하기에 이런 테마 설정은 전역으로 관리할 수 있어야 한다. 언어 설정도 마찬가지고, 포토샵의 undo, redo 기능도 마찬가지로 전역 상태로 관리한다. undo, redo 기능은 화면에 표시되는 모든 내용을 전부 상태 객체로 만들어 저장해서 특정 상태를 바탕으로 컴포넌트를 표현할 수 있게 한다. 그렇기에 전역 상태로 관리해야 하며 만약 이 상태를 무결성으로 보장하지 않는다면 바로 이전으로 되돌아가기가 랜덤의 순번으로 돌아거나 이제껏 만든 모든 프로젝트가 사라질 수 있다.

 

5) 상태 관리를 위한 각종 툴

  상태를 관리하는 대표적인 툴은 React Context, Redux, MobX 가 있다. 이중에서 우리는 Redux 를 다룰 예정이다. 상태 관리 툴이 반드시 필요하진 않다. 없어도 충분히 규모가 있는 애플리케이션을 만들 수 있다. 리덕스 개발자도 "You might not need Redux" 라고 말한다. 하지만 props drilling 문제를 해결해주기도 하고, 사용하면 각종 편의를 받을 수 있다.

 

 

 

2. Redux

  Redux 는 자바스크립트 안에서 예측 가능한 상태 관리를 해주는 Container 다. 자바스크립트 상태 관리이기에 React 뿐만 아니라 다른 곳에서도 Redux 를 사용할 수 있다. 

 

  리덕스의 장점은 4개로 볼 수 있다. 1) 상태를 예측 가능하게 만들어 준다. reducer 가 순수함수기 때문에 다음 함수가 어떻게 될지 예측이 가능하다. 2) 유지보수에 용이하다. 3) 디버깅에 유리하다. 4) 테스트를 붙이기 쉽다

1) Redux 의 3대 원칙

  1. Single Source of Truth
    항상 같은 곳에서 데이터를 가지고 온다. 리덕스에서 하나의 데이터 저장소는 store 다.
  2. State is read-only
    액션이라는 객체를 통해서만 state를 변경할 수 있다.

  3. Changes are made with pure functions
    pure functions는 Reducer 와 관련된 내용이다 

 

2) Store, Action, Dispatch, Reducer 사이의 관계

 

https://ko.redux.js.org/tutorials/essentials/part-5-async-logic/

 

  store 는 상태를 관리하는 단 하나의 공간, Single Source 다. 컴포넌트와 별개로 store 가 존재한다. 컴포넌트에서 state 정보를 필요로할때, store 안에 있는 state 를 가져온다.

 

  필요한 경우에는 내용을 추가하거나 변형할 때일 것이다. action 은 자바 스크립트 객체인데 type 을 비롯해 다양한 데이터를 담는다. 그리고 dispatch는 컴포넌트 내에서 action을 실행한다. dispatch의 인자로 action을 import 한 변수가 들어가게 되면 해당 action 의 타입이 reducer 로 넘어간다. reducer 는 각 action type 마다 어떻게 함수 기능을 달리한다. 그리고 reducer 의 내용으로 store 의 내용이 새롭게 업데이트 된다. 이런 과정을 거치는 이유는 데이터가 한 방향으로 흘러야하기 때문이다

 

 

ACTION EXAMPLE

 

// /actions/index.js 파일

// action types
export const ADD_TO_CART = "ADD_TO_CART";


// actions creator functions
export const addToCart = (itemId) => {
  return {
    type: ADD_TO_CART,
    payload: {
      quantity: 1,
      itemId,
    },
  };
};

 

DISPATCH

 

import { addToCart} from "../actions/index";

const handleClick = (item) => {
  dispatch(addToCart(item.id));
};

 

REDUCER

 

import { REMOVE_FROM_CART, ADD_TO_CART } from "../actions/index";

const itemReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TO_CART:
      return Object.assign({}, state, {
        cartItems: [...state.cartItems, action.payload],
      });
      break;
    case REMOVE_FROM_CART:
      return Object.assign({}, state, {
        cartItems: [...state.cartItems].filter(
          (el) => el.itemId !== action.payload.itemId
        ),
      });
      break;
    default:
     return state;
}