[S3U4] React 상태관리

👽·2024년 4월 30일
0
post-thumbnail

CH1. 상태관리

📌 전역 상태 관리

🔸 상태(State) : 동적으로 변하는/표현될 데이터. 이 상태를 다룰 때, Side Effect를 잘 고려해야 함.

🔸 Side Effect : 함수(또는 컴포넌트)의 입력 외에도 함수의 결과에 영향을 미치는 요인 (ex. 네트워크 요청, API 호출)

🔸 앱을 개발할 때, 이런 side effect는 불가피하게 생기기 마련이기 때문에 최대한 줄이기 위해 컴포넌트 단위로 개발하는 것이 좋음. 이러한 점을 반영하여 상태의 위치를 잘 선택해야 함.

🔸 React의 주요 개발원칙인 '컴포넌트 우선 개발 방식'에서 API 요청(ex. fetch) 없이도 컴포넌트는 잘 작동이 됨. 이렇게 어떤 데이터가 들어오는지 상관하지 않고 (fake 데이터여도) 컴포넌트가 표현되는 것을 presentation 컴포넌트라고 함.

로컬 상태

🔸 특정 컴포넌트 안에서만 관리되는 상태.
🔸 보통 컴포넌트 내에서만 영향을 끼치며, 다른 컴포넌트와 데이터를 공유하지 않는 폼(form) 데이터는 대부분 로컬 상태임.
🔸 ex) input box, select box 등과 같이 입력값을 받는 경우

전역 상태

🔸 프로덕트 전체 혹은 여러 가지 컴포넌트가 동시에 관리하는 상태.

🔸 다른 컴포넌트와 상태를 공유하고 영향을 끼침.

🔸 ex) 데이터 로딩 여부(로딩 중), 다크 모드 기능, 다국어 설정 기능, 히스토리 기능, 포토샵의 Undo/Redo 등

전역 상태에서의 데이터 무결성

🔸 서로 다른 컴포넌트가 사용하는 상태의 종류가 다르면, 출처(source)가 달라도 상관없음.

🔸 하지만 서로 다른 컴포넌트가 동일한 상태를 다룰 때는, 출처는 한 곳이어야 함. 만약 사본이 있을 경우, 두 데이터는 서로 동기화(sync)하는 과정이 필요. 따라서 한 곳(전역 공간)에서만 상태를 저장하고 접근해야 함.

🔸 데이터 무결성 : 데이터의 정확성을 보장하기 위해 데이터의 변경이나 수정 시 제한을 두어 안정성을 저해하는 요소를 막고 데이터 상태들을 항상 옮게 유지하는 것

🔸 동일한 데이터는 항상 같은 곳에서 데이터를 가지고 오는 것을 Single source of truth(신뢰할 수 있는 단일 출처) 원칙이라고 하며, 무결성을 위한 방법론임.

📌 Props Drilling

🔸 상위 컴포넌트의 state를 props를 통해 전달하고자 하는 컴포넌트로 전달하기 위해 그 사이는 props를 전달하는 용도로만 쓰이는 컴포넌트들을 거치면서 데이터를 전달하는 현상

문제점

🔸 Props의 전달 횟수가 5회 이내로 많지 않다면 Props Drilling 은 큰 문제가 되지 않지만, 규모가 커지고 구조가 복잡해지면서 Props의 전달 과정이 늘어난다면 아래와 같은 문제가 발생.

  • 코드의 가독성이 매우 나쁨
  • 코드의 유지보수 힘듦.
  • state 변경 시 Props 전달 과정에서 불필요하게 관여된 컴포넌트들 또한 리렌더링이 발생. 따라서 웹성능에 악영향.

해결 방안

🔸 상태관리 라이브러리를 사용하는 방법 : 전역으로 관리하는 저장소에서 직접 state를 꺼내쓸 수 있기 때문에 Props Drilling을 방지하기에 매우 효과적.

🔸 물론 상태 관리 툴이 없어도 충분히 규모 있는 애플리케이션을 만들 수 있음.

상태 관리를 위한 각종 툴

🔸 React Context, Redux, Mobx 등

CH2. Redux

🔸 React는 상태와 속성(props)을 이용한 컴포넌트 단위 개발 아키텍처임.
🔸 React의 데이터 흐름에 따르면 컴포넌트3, 컴포넌트6에서만 사용되는 상태는 최상위 컴포넌트에 위치시키는 것이 적절, 하지만 이러한 상태 배치는 다음과 같은 비효율적인 면이 있음.

  • 해당 상태를 직접 사용하지 않는 최상위 컴포넌트, 컴포넌트1, 컴포넌트2도 상태 데이터를 가짐.
  • 상태 끌어올리기, Props 내려주기를 여러 번 거쳐야 함.
  • 애플리케이션이 복잡해질수록 데이터 흐름도 복잡해짐.
  • 컴포넌트 구조가 바뀐다면, 지금의 데이터 흐름을 완전히 바꿔야 할 수도 있음

🔸 Redux는 전역 상태를 관리할 수 있는 저장소인 Store를 제공함으로써 이러한 문제들을 해결해줌.

📌 Redux 기초

Redux의 구조

🔸 Redux는 다음과 같은 순서로 상태를 관리함.

  • 상태 변경 이벤트 발생 → 변경될 상태에 대한 정보가 담긴 Action 객체 생성.

  • Action 객체는 Dispatch 함수의 인자로 전달.

  • Dispatch 함수는 Action 객체를 Reducer 함수로 전달.

  • Reducer 함수는 Action 객체의 값을 확인하고, 그 값에 따라 전역 상태 저장소 Store의 상태를 변경.

  • 상태가 변경되면, React는 화면을 다시 렌더링.

🔸 Action → Dispatch → Reducer → Store 순서로 데이터가 단방향으로 흐름.

📌 Redux 사용하기

  1. Store 생성 후 Reducer 함수 할당
  2. Reducer 함수 생성
  3. Action 객체 작성 및 생성
  4. Dispatch 함수로 Action 객체 전달
  5. Redux Hooks 사용

1. Store 생성 후 Reducer 함수 할당하기

🔸 store : Redux 앱의 state가 저장되어 있는 공간. 상태가 관리되는 오직 하나뿐인 저장소 역할.

// 📍 1.  Redux에서 createStore를 불러옴.
import { legacy_createStore as createStore } from 'redux'

// 📍 2. store 생성
const store = createStore(rootReducer);

🔸 전역 상태 저장소 store를 사용하기 위해서는 <App> 컴포넌트를 <Provider>로 감싸준 후 props로 변수 store를 전달

<Provider store={store}>
	<App />
</Provider>

Reducer 함수 생성하기

🔸 reducer(State, Action) : Dispatch에게서 전달받은 Action 객체의 type 값에 따라서 state를 변경시키는 함수.

  • State : 변경될 state
  • Action : action 객체

🔸 외부 요인으로 인해 기대한 값이 아닌 엉뚱한 값으로 상태가 변경되는 일이 없는 Reducer는 순수함수여야 함.

const count = 1

// Reducer를 생성할 때에는 초기 상태를 인자로 요구
const counterReducer = (state = count, action) => {

  // Action 객체의 type 값에 따라 분기하는 switch 조건문
  switch (action.type) {

    //action === 'INCREASE'일 경우
    case 'INCREASE':
			return state + 1

    // action === 'DECREASE'일 경우
    case 'DECREASE':
			return state - 1

    // action === 'SET_NUMBER'일 경우
    case 'SET_NUMBER':
			return action.payload

    // 해당 되는 경우가 없을 땐 기존 상태를 그대로 리턴
    default:
      return state;
	}
}
// Reducer가 리턴하는 값이 새로운 상태가 됨

❗Reducer함수 첫번째 인자에는 기존 state가 들어오며, default value를 설정해줘야 함. 그렇지 않을 경우 undefined가 할당되기 때문에 그로 인한 오류가 발생. 추가 설명

🔸 여러 개의 Reducer를 사용하는 경우, Redux의 combineReducers 메서드를 사용해서 하나의 Reducer로 합쳐줄 수 있음.

import { combineReducers } from 'redux';

const rootReducer = combineReducers({
  counterReducer,
  anyReducer,
  ...
});

Action

🔸 어떤 액션을 취할 것인지 정의해 놓은 객체.

🔸 Action 객체 작성시, 아래와 같은 규칙이 있음.

  • type 은 필수로 지정해줘야 함 (해당 Action 객체가 어떤 동작을 하는지 명시해 주는 역할)
  • 대문자와 Snake Case로 작성.
  • 구체적인 값을 전달할 시, payload 작성.
    • payload가 필요 없는 경우 : { type: 'INCREASE' }
    • payload가 필요한 경우 : { type: 'SET_NUMBER', payload: 5 }

🔸 보통 Action을 직접 작성하기보다는 Action 객체를 생성하는 함수를 만들어 사용하는 경우가 많으며, 이러한 함수를 액션 생성자(Action Creator) 라고 함.

// 📍 payload가 필요 없는 경우
const increase = () => {
  return {
    type: 'INCREASE'
  }
}

// 📍 payload가 필요한 경우
const setNumber = (num) => {
  return {
    type: 'SET_NUMBER',
    payload: num
  }
}

Dispatch

🔸 ReducerAction을 전달해 주는 함수

🔸 전달인자로 Action 객체가 전달됨.

// 📍 Action 객체를 직접 작성하는 경우
dispatch( { type: 'INCREASE' } );
dispatch( { type: 'SET_NUMBER', payload: 5 } );

// 📍 액션 생성자(Action Creator)를 사용하는 경우
dispatch( increase() );
dispatch( setNumber(5) );

🔸 Action 객체를 전달받은 Dispatch 함수는 Reducer를 호출함.

Redux Hooks

🔸 Redux Hooks는 React-Redux에서 Redux를 사용할 때 활용할 수 있는 Hooks 메서드를 제공함.

🔸 useDispatch() : Action 객체를 Reducer로 전달해 주는 Dispatch 함수를 반환하는 메서드

import { useDispatch } from 'react-redux'

const dispatch = useDispatch()
dispatch( increase() )
console.log(counter) // 2

dispatch( setNumber(5) )
console.log(counter) // 5

🔸 useSelector(callBack) : 컴포넌트와 state를 연결하여 Redux의 state에 접근할 수 있게 해주는 메서드

  • callBack 함수 : Store에 저장된 모든 state가 담김. 그대로 return을 하게 되면 Store에 저장된 모든 state를 사용할 수 있음.
import { useSelector } from 'react-redux'

const counter = useSelector(state => state)

Redux의 세 가지 원칙

🔸 Single source of truth : 동일한 데이터는 항상 같은 곳에서 가지고 와야 함. Redux에는 데이터를 저장하는 Store라는 단 하나뿐인 공간이 있음과 연결이 되는 원칙.

🔸 State is read-only : React에서 상태갱신함수로만 상태를 변경할 수 있었던 것처럼, Redux의 상태도 직접 변경할 수 없음을 의미. Action 객체가 있어야만 상태를 변경할 수 있음과 연결되는 원칙.

🔸 Changes are made with pure functions : 변경은 순수함수로만 가능하다는 뜻. 상태가 엉뚱한 값으로 변경되는 일이 없도록 순수함수로 작성되어야 하는 Reducer와 연결되는 원칙.

참고 자료

🔸 React-Redux

추가 공부

📌 Flux

등장 배경

🔸 기존 애플리케이션들은 보편적으로 MVC (Model View Controller) 패턴을 사용하여 구축됨.

🔸 MVC : Model에 데이터를 저장하고 Controller를 통해 Model의 데이터를 관리(CRUD)가 이루어짐. Model의 데이터가 변경되면 View로 전달되어 사용자에게 보여지고, 사용자가 View를 통해 데이터를 입력하면 View가 Model을 업데이트할 수 있는 양방향 데이터 흐름을 가짐.

🔸 그러나 애플리케이션 규모가 커지면서 View가 다양한 상호작용을 위해 여러 개의 Model을 동시에 업데이터하고 Model 역시 여러 개의 View에 데이터를 전달하는 상황이 발생.

🔸 즉, 복잡한 데이터 흐름과 상호 의존성이 증가하게 되어, 이러한 복잡성을 관리하기 위해 단방향 데이터 흐름을 가지는 Flux 패턴이 나옴.

Flux 패턴

🔸 Flux : 사용자 입력을 기반으로 Action을 만들고, Action을 Dispatcher에 전달하여 Store(Model)의 데이터를 변경한 뒤 View에 반영하는 단방향의 흐름으로 애플리케이션을 만드는 아키텍처.

Action

🔸 Action : 데이터를 변경하는 행위. Dispatcher에게 전달되는 객체.

🔸 Action Creator : 새로 발생한 Action의 Type과 새로운 데이터(Payload)를 묶어 Dispatcher에게 전달.

{
  type: 'SET_PROFILE',
  data: {
    'name': 'Park',
    'age': 48
  }
}

Dispatcher

🔸 Dispatcher : 모든 데이터의 흐름을 관리하는 중앙 허브. Store들이 등록해놓은 Action 타입마다 콜백 함수들이 존재.

🔸 Action을 감지하면 Store들이 각 타입에 맞는 Store의 콜백 함수를 실행.

🔸 Store를 조작하는 것은 오직 Dispatcher를 통해서만 가능.

🔸 Store들 사이에 의존성이 있는 상황에서도 순서에 맞게 콜백 함수를 순차적으로 처리할 수 있도록 관리.

Store (Model)

🔸 Store : 상태 저장소. 상태와 상태를 변경할 수 있는 메서드를 가짐.

🔸 어떤 타입의 Action이 발생했는지에 따라 그에 맞는 데이터 변경을 수행하는 콜백 함수를 Dispatcher에 등록.

🔸 Dispatcher에서 콜백 함수를 실행하여 상태가 변경되면 View에게 데이터가 변경되었음을 알림.

View

🔸 View : React 컴포넌트와 비슷.

🔸 Store에서 View에게 상태가 변경되었음을 알려주면, 최상위 View(Controller View)는 Store에서 데이터를 가져와 자식 View에게 내려 보내고 새로운 데이터를 받은 View는 화면을 리렌더링함.

🔸 또한 사용자가 View에 어떠한 조작을 하면 그에 해당하는 Action을 생성.

참고 자료

🔸 Flux

profile
코린이👽

0개의 댓글