항해99 Week_04 WIL

그루트·2021년 10월 10일
0

전역상태관리

전역상태관리란 말하는바 그대로 전역에서 상태를 관리한다는 의미입니다.
리액트에서는 상위에서 하위로 상태값을 전달하여 상태를 관리하게 되어있습니다.

그러나 위와 같이 컴포넌트가 분리되어 점점 더 많아지는 컴포넌트에서 상태를 관리하게 위해서는 결국 props를 통해 사용하는 하위까지 값을 전달해야 합니다.

실제로 하위 컴포넌트에서 사용을 위해 불필요한 props를 전달받아야 하는 상황이 발생하고 props drilling이라는 문제가 발생합니다.

Prop Drilling ?
Prop Drilling 은 props를 오로지 하위 컴포넌트로 전달하는 용도로만 쓰이는 컴포넌트들을 거치면서 React Component 트리의 한 부분에서 다른 부분으로 데이터를 전달하는 과정입니다.
React에서 Prop Drilling과 해결 방법

Prop Drilling과 같은 문제로 인해 상태관리를 보다 편하고 불필요한 props 전달을 막을 필요가 생겼고 전역상태관리가 필요해졌습니다.

ConText API

전역에서 상태관리를 하는 방법에는 리액트 Hook에서 제공하는 Context API 라는것을 제공합니다.

자세한 사항은 리액트 공식 사이트(리액트 Context)에서 확인 가능합니다.

Redux

리덕스는 현재 가장 많은 리액트 사용자가 사용하는 전역상태관리 입니다.

위에는 리덕스 공식 홈페이지에서 제공하는 메인 화면 입니다.

Redux는 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너입니다.
Redux는 여러분이 일관적으로 동작하고, 서로 다른 환경(서버, 클라이언트, 네이티브)에서 작동하고, 테스트하기 쉬운 앱을 작성하도록 도와줍니다. 여기에 더해서 시간여행형 디버거와 결합된 실시간 >코드 수정과 같은 훌륭한 개발자 경험을 제공합니다.


Context API란?

Context API는 react에서 정식적으로 지원하고 있는 전역상태관리 API 입니다.
일반적인 React 애플리케이션에서 데이터는 위에서 아래로 (즉, 부모로부터 자식에게) props를 통해 전달되지만, 애플리케이션 안의 여러 컴포넌트들에 전해줘야 하는 props의 경우 (예를 들면 선호 로케일, UI 테마) 이 과정이 번거로울 수 있습니다. context를 이용하면, 트리 단계마다 명시적으로 props를 넘겨주지 않아도 많은 컴포넌트가 이러한 값을 공유하도록 할 수 있습니다.
리액트 Context 에서 확인 할 수 있습니다.

Context 구성요소

  • Context를 간단히 설명하자면 크게 Provider와 Consumer로 나뉩니다.
    • Provider는 전역 상태를 정의하고, 전역 상태를 update하는 로직이 있습니다.
    • Consumer로 전역 상태를 사용할 수 있습니다.
    • useContext 훅을 통해 상태를 관리할 수 있다.

Context API 사용하기

우선 간단하게 todo 항목을 입력하는 부분과 렌더링 하는 부분을 추가하겠습니다.

context 생성하기

context에 todo 리스트를 추가하는 todos와 todos의 상태를 바꿔줄 함수인 setTodos를 추가하겠습니다.

//TodoContext.js
import { createContext, useState, useContext } from "react";

const TodoContext = createContext(); // 1

export const TodoProvider = ({ children }) => {
  const [todos, setTodos] = useState([]);

  return (
    <TodoContext.Provider value={{ todos, setTodos }}> // 2
      {children}
    </TodoContext.Provider>
  );
};

export const TodoConsumer = TodoContext.Consumer; // 3
export const useTodoContext = () => useContext(TodoContext); // 4
  1. createContext를 통해 Context를 사용할 항목을 정의한다.
  2. Provider에서 사용할 todos와, 목록을 업데이트 해주는 setTodos 함수를 넘겨준다.
  3. Consumer을 통해 상태를 관리해가 위해 추가해준다.
  4. Consumer와 동일한 상태를 관리하게 위한 훅을 설정해준다.
    3,4. 두가지 모두 2번에서 지정해준 Provider를 가져올수 있습니다.

Provier/Consumer

Provider는 context를 구독(Consumer)하는 컴포넌트들에게 context의 변화를 알린다.

  • Provider 하위(내부)에 있는 컴포넌트 중에 context를 구독(Consumer)하는 부분이 Provider의 value가 바뀔 때마다 다시 렌더링된다.
  • Provider 하위에 있다고 value가 업데이트 될 때마다 모든 컴포넌트가 다시 렌더링된다는 소리는 아니다. context를 구독이 포함되어 있는 컴포넌트도 다시 렌더링 되는 것은 아니다. 오로지 context.Consumer 부분만 rerender 된다.
  • 당연히, Provider 컴포넌트 외부에 있는 컴포넌트에는 변화를 감지하지 못한다.

Provider로 감싸기

Todo를 감지할 컴포넌트를 provider로 감싸야 한다. 모든 페이지에서 로그인 여부를 알아야 하므로 최상단에 넣었다.

// App.js
import { TodoProvider } from "./TodoContext";
import Todo from "./Todo";

function App() {
  return (
    <TodoProvider>
      <Todo />
    </TodoProvider>
  );
}

export default App;

Consumer, useContext로 변화 감지하기

TodoConsumer 내부에는 함수로 작성한다. 인자에 provider value에 할당했던 값이 전달 된다.
useContext를 통해 보다 쉽게 값을 사용할 수 있습니다.

import { useState } from "react";
import { TodoConsumer, useTodoContext } from "./TodoContext";

export default function Todo() {
  const [input, setInput] = useState("");
  const { todos, setTodos } = useTodoContext(); // 1

  const handleInput = (e) =>setInput(e.target.value);
  
  const handleSubmit = (e) => {
    e.preventDefault();
    setInput("");
    setTodos((prev) => [...prev, input]); // 2
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" onChange={handleInput} />
      </form>

      <section>
        <article>
          useContext를 사용한 예제
          {todos.map((todo) => (
            <div>{todo}</div>
          ))}
        </article> // 2

        <TodoConsumer>
          {({ todos }) => (
            <article>
              Consumer를 사용한 예제
              {todos.map((todo) => (
                <div>{todo}</div>
              ))}
            </article>
          )}
        </TodoConsumer> // 3
      </section>
    </div>
  );
}
  1. TodoContext.js에서 선언한 useContext값을 구조 분해 할당으로 가져와서 사용할 수 있습니다.
  2. 가져온 context는 보기와 같이 기존의 state를 사용하듯이 사용할 수 있습니다.
  3. ConSumer의 경우 내부에 인자값으로 todos, setTodos를 사용할 수 있습니다.

context API를 간단하게 사용해봤습니다.
위 예제는 간단하게 구현한 것으로 useState를 통해 상태를 관리했지만 보다 복잡한 상태의 경우 useReduser을 통해 관리할 수 있습니다.


리덕스(Redux)란?

이전에도 한번 얘기했듯이 전역상태를 관리하는 여러방법들중 하나입니다.
리덕스 공식 문서에서는 다음과 같이 리덕스를 정의하고 있습니다.

리덕스에 들어가기 앞서 Flux패턴이라는것을 먼저 짚고 넘어가겠습니다.

MVC & Flux Pattern

우선 Flux패턴에 들어가기 전 MVC패턴에 대해서 알아보겠습니다.

MVC 패턴

관심사의 분리(separation of concern, SoC)에서부터 나온 패턴입니다.

관심사 분리로 코드의 단순화 및 유지보수의 더 높은 수준의 자유가 생긴다.
관심사가 잘 분리될 때 독립적인 개발과 업그레이드 외에도 모듈 재사용을 위한 더 높은 정도의 자유가 있다.
모듈이 인터페이스 뒤에서 이러한 관심사의 세세한 부분을 숨기기 때문에 자유도가 높아짐으로써 다른 부분의 세세한 사항을 모르더라도, 또 해당 부분들에 상응하는 변경을 취하지 않더라도 하나의 관>심사의 코드 부분을 개선하거나 수정할 수 있게 된다.

Model

  • 데이터라고 생각하면 된다. json 또는 데이터 모델 정의.
  • 일반적으로 데이터를 처리하는 로직과 함께!

View

  • 화면, html, 레이아웃 이라고 생각하면 된다.

Controller

  • 이벤트 핸들러와 이벤트를 처리하는 로직이 있는 곳이라고 생각하면 쉽다.
  • 데이터랑 뷰 이어주는 역할.
  • 요청이(이벤트 발생) 오면 모델에 적절한 로직을 실행하도록 한다.

MVC 에서 Flux 로

Flux 패턴의 경우 기존 MVC패턴의 문제점에서 나온 패턴이라고 합니다.

기존 MVC의 문제점

  • 프로젝트 규모가 커질수록 빠르게 복잡해진다.
  • feature 추가될 때마다 모델과 뷰를 연결하는 복잡성이 증가한다.
  • 데이터 간의 의존성과 연쇄적인 갱신은 뒤얽힌 데이터 흐름을 만들고 예측할 수 없는 결과로 이끌게 된다.
  • 새로온 개발자가 합류하면, 너무 복잡해서 코드만 보고 해석조차 불가능. (유지비용 증가)
  • 복잡성이 증가할 수록 예측 불가능해지고 안정성이 떨어진다. (어디서 버그가 터져나올지 테스트도 어렵다..)

이 프로젝트는 파생되는 데이터를 올바르게 다루기 위해 시작되었다. 예를 들면 현재 뷰에서 읽지 않은 메시지가 강조되어 있으면서도 읽지 않은 메시지 수를 상단 바에 표시하고 싶었다. 이런 부분은 MVC에서 다루기 어려운데 메시지를 읽기 위한 단일 스레드에서 메시지 스레드 모델을 갱신해야하고 동시에 읽지 않은 메시지 수 모델을 갱신 해야하기 때문이다.

Flux 패턴이란?

  • 단방향 데이터 흐름(unidirectional data flow)이 핵심.
  • 사용자에 의해 Action이 발행되면, 해당 Action에 영향이 있는 모든 View가 갱신(rerender)된다. 어디에서 어떤 일이 일어날지 알 수 있다(=예상가능하다. 복잡하지 않다). 흐름은 단방향으로 진행되어 하나임
  • Flux는 세 가지 부분으로 구성 되어 있다.
    1. Dispatcher
    2. Stores
    3. Views(리액트 컴포넌트)

Dispatcher

  • dispatcher를 통해 action을 발행한다.
  • 모든 데이터는 중앙 허브인 dispatcher를 통해 흐른다.

Store

  • 어플리케이션의 데이터와 비지니스 로직을 가지고 있는 store

Views

  • action이 발행되면 이 action에 영향이 있는 모든 view가 갱신됨

리덕스(Redux)

그렇다면 왜 리덕스를 사용하기 전에 Flux 패턴에 대해 알아봤을까요?

Redux는 Flux 패턴을 근본으로한 라이브러리이다.
React 뿐 아니라 다른 UI 라이브러리에서도 사용할 수 있다.

  • Store: Application의 전체 state는 store라고 불리는 곳에서 관리된다.

    • store는 redux의 상태값(state)를 갖는 객체이다.
  • Action: action은 state 변화를 일으킬 수 있는 행동정보, 현상등이라고 생각하면 된다.

  • Dispatcher: action이 일어나면 Dispatcher를 통해서 store의 state가 업뎃된다.

  • View: state가 변경되면 view에서 감지하고, 화면에 반영(render) 된다.

  • 위의 그림과 같이 view단에서 action이 일어날 수도 있다(당연). view에서 action이 일어나면 -> 다시 dispatcher에 의해 store에 저장되고 -> state가 변경되면 -> 필요한 view에서 감지를 알아차린다.

<리덕스 구조>

Redux의 세 가지 원칙

전체 상태값이 하나의 객체로 표현됨

  • 간단히 말하면 하나의 React앱에 store가 하나라는 뜻임

상태값(state)는 읽기 전용이다.

  • 원래 컴포넌트에서의 state 관리 생각하시면 됩니다.
  • (state 값 직접 바꾸지 않고, setState 함수 사용해서 변경 했잖아요~

상태값(state)은 순수 함수에 의해서만 변경되어야 한다.

  • 상태값(state)을 변경시키려면, 상태값을 변경하는 함수가 필요합니다.
  • 이 함수를 reducer라고 하고, 이 함수가 순수함수(pure function) 이어야 한다는 말.

순수함수(Pure function)란?
항상 같은 input은 항상 같은 output을 반환하는 함수.


한주를 보내며 느낀건 정말 공부가 많이 필요하다는거다..
이번 개인 과제는 정말 힘들었다. 머 지금 까지 과제들 모두가 힘들었지만 이번엔 정말 힘들었다. 너무 모르다보니 그저 따라가기에만 바뻐 중요한 개념들을 무시하고 넘어갔던거 같다.
이렇게 주말을 정리하면서 많이 느낀다.
그리고 그것을 꼭 해야된다는걸 많이 느낀다.
물론 TIL은 계속 쓰고있지만 앞으론 이게 왜 생겼고 왜 사용하는지 어떻게 사용하는지에 대해 따로 공부하는 시간을 따로 가질까한다.
한주가 너무 힘들어서 난 안되나 생각많이 했다.
이겨내자 분명히 익숙해지고 익숙해지면 잘할수있을꺼라고 믿는다.😊

profile
i'm groot

0개의 댓글