코린이에서 코른이로 ( 상태 관리 )

양선우·2023년 2월 23일
0

상태관리가 필요한 이유?

상태관리가 필요한 이유를 이해하기 위해선 우선 상태가 무엇인지 알아야한다

상태란?

상태(state)는 React에서 컴포넌트 내에 관리되는 변수, 즉 변하는 데이터들이다.

React API setState()로 선언되는 그것 맞다.

props drilling

컴포넌트들은 서로 상태를 공유해야한다. 컴포넌트들은 props 형태로 상태를 공유한다.

자식 컴포넌트간에는 상태 공유가 불가능하고, 부모 컴포넌트를 통해서만 상태를 공유할 수 있다. 이때 문제는, 컴포넌트 계층이 많아지면 props 하나를 전달하는데 거쳐야하는 컴포넌트가 많아지고, 중간단계의 컴포넌트들은 사용하지도 않는 props를 들고있어야하는 문제가 생긴다.

이러한 문제를 props drilling 이라고 부르는데, 프로젝트가 커질수록 state 관리가 어려워진다.

상태 관리를 라이브러리의 종류는

  • React Context API
  • Redux
  • Recoil

정도가 있다 볼 수 있다.
간략적으로 소개를 해보겠다.

React Context API

react에서도 자체적으로 전역 상태관리 API를 제공해주긴한다..

그냥 react 라이브러리에서 import 해서 사용하면 된다.

사용법

//context 선언
const context = React.createContext(defaultValue)

//Provider 선언
const App = () => {
	return (
		<Context.Provider value={context}>
			...
		</Context.Provider>
	)
}

//context 구독

const state = useContext()

문제점

React Context API를 사용하면 리액트 앱 최상단에 Provider를 배치하는데, 이는 context를 구독하는 컴포넌트들에게 context의 변화를 알리는 역할을 한다.

context를 구독하는 컴포넌트들은 이 Provider의 value prop이 바뀔때마다 다시 렌더링된다. context 값 변경 감지는 Object.is와 동일한 알고리즘을 사용한다고 한다.

이때 문제는, context가 변경될때마다 useContext()를 사용한(컨텍스트를 구독한) 모든 컴포넌트가 리렌더링된다는 것인데,
*이는 심각한 성능상의 비효율성을 낳는다….

이를 막기 위해 value에 들어갈 객체를 통째로 관리한다거나 하면 문제가 해결된다고 하긴하는데, 그래도 여전히 이 프로젝트에 사용할 모든 전역 상태를 이걸로 관리하기엔 쉽지 않을 것 같다. 어차피 이것보다 더 좋고 편하게 상태를 관리해주는 라이브러리가 많기 때문에 이를 해결하는 방법은 깊게 알아볼 필요는 없을 듯 하다.

Redux(Reducer + flux)

자바스크립트 앱을 위한 예측 가능한 상태 컨테이너.

Redux는 위의 인용구의 말대로, 자바스크립트를 위한 상태관리 라이브러리다. 리액트 생태계가 아니더라도 vanila js 환경에서도 사용할 수 있다.

방금 위에서 Context를 사용할때 useReducer라는 훅이랑 같이 사용한다고 했는데, reducer의 개념에 flux 구조를 끼얹어 탄생한것이 redux이다.

구조

flux패턴을 사용하여, action이 발생하면 dispatcher에 의해 store에 변경된 사항이 저장되고, 그 상태에 의해 view가 변경되는, 데이터가 한 방향으로 흐르는 구조를 갖는다.

사용법

좀 더 직관적으로 Todo 앱에 비유해서 하나씩 설명해보자면

action은 상태를 변경시키는 이벤트를 가리킨다. 예를 들어 버튼을 눌러서 Todo를 추가하고 삭제하는 등 … 이 버튼을 눌렀을때 상태를 어떻게 변경시킬지(삭제를 하는지, 추가할 때 숫자를 더한다던지 ..)를 이 action에 명시한다.

//1. action
const ADD_TODO = 'ADD_TODO'

const addTodo = (text) => {
	return {
		type: ADD_TODO,
		text
	}
}

action이 발생하면, 이 객체는 dispatch의 인자로 넘어간다.

dispatch함수는 reducer함수를 호출한다.

//2. dispatch
store.dispatch({ type: ADD_TODO, payload: {'감자 사기'} });

//3. reducer
const reducer(state, action){
	switch(action.type){
		case ADD_TODO: 
			return {...state, action.payload};
		case REMOVE_TODO:
			return .......
		....
		default:
			return state;
	}
}

reducer는 store를 직접적으로 업데이트 하는 역할을 한다. state는 이 reducer를 통해서만 업데이트 될 수 있다. redux는 기본적으로 store를 하나만 두는 것을 권장 구조로 한다. state가 업데이트 되면 이를 추적하는 view도 자동으로 업데이트 된다. view는 쉽게 말하면 화면 UI라고 생각하면 된다.

//4. store
import { createStore } from 'redux';
import rootReducer from '../reducers/index';

const store = createStore(rootReducer);

redux의 기본 구조는 이렇고, 대체로는 redux toolkit을 설치해서 같이 사용하고 redux saga를 이용해 비동기 처리를 한다.

위의 코드들을 보면 프로젝트 구조가 커짐에 따라 보일러플레이트가 아주 아주 방대해질 것 같다. 이를 해결하기 위해 redux toolkit에서 추가적인 기능을 제공하는데, createSlice를 사용해서 action type을 자동으로 만들어준다던지 하는 방향으로 개발을 좀 더 쉽게 만들어 준다.

느낌

React 프로젝트에서 무조건 Redux를 써야하는 것은 아니다. Redux 공식 문서에서도 ‘사람들이 좋다고 말해서 그냥 써보는것’은 지양한다 라고 말한다. 프로젝트에서 Redux 방식의 상태관리가 적절하다면 사용해라 라고 말한다.
하지만 가장 많이 쓰는 만큼 문제점에 대한 해결방법을 찾기 더 수월 할 것이다.

Recoil

React를 위한 상태관리 라이브러리

redux와는 다르게 react 전용 상태관리 라이브러리다.

구조

Recoil에서 제일 핵심되는 개념은 atom 이다. atom은 recoil에서 상태(state)를 나타내며, 컴포넌트들이 구독할수 있는 단위이다. atom의 값을 읽는 컴포넌트들은 암묵적으로 atom을 구독한다. atom에 변화가 있으면 구독하는 모든 컴포넌트가 재렌더링된다.

action → dispatch → reducer → view 의 흐름을 가지는 redux와 달리

atom → selector → view 의 data-flow를 가진다.

사용법

atom을 생성할때 useRecoilState라는 훅을 사용하는데, 이는 react의 useState와 비슷하지만 상태가 컴포넌트간에 공유될 수 있다는 차이점이 있다.

// atom 선언
const todoState = atom({
	key: 'todoState', //식별자이므로 unique해야함
	default: ''
})

// state 사용법
const [todo, setTodo] = useRecoilState(todoState);

selector는 파생된 상태(derived state)를 나타낸다. 여기서 파생된 상태란 상태를 변화를 의미한다. 쉽게 말하면 동기/비동기적으로 상태를 변환하는 용도로 사용한다고 생각하면 된다.

// selector 선언
const charTodoState = selector({
	key: 'TodoState', //식별자이므로 unique해야함
	get: ({get}) => {
		const text = get(todoState); // => get 내에서 다른 atom과 selector에 접근 가능
		
		return `TODO: ${text}`;
	}
})

text: hi
state <- 'TODO: hi'

// getter 사용
const todo = useRecoilValue(charTodoState)

recoil의 문법은 이게 끝이다.

redux에 비해 비교도 안되게 가볍고 쉽고 직관적이다. redux thunk, redux saga 등 redux는 비동기 처리를 위해 서드파티 라이브러리를 사용해야하는 것과 달리 recoil은 비동기 기반으로 작성되어 동시성 모드도 지원한다고 한다.

동시성모드(Concurrent Mode)란?
데이터 흐름이 여러개 있을 때 렌더링 동작의 우선순위를 정하여 적절한 시점에 렌더링을 해주는 기능

느낌

일단 redux보다 진입장벽이 낮고, 이해하고 사용하기 쉬울 것같은 느낌이 든다.

작고 가벼운 프로젝트에서는 recoil만 써도 충분할 것같은 느낌

혹은 글로벌한 내용들은 전역에서 redux를 사용하여 관리하고, 그 외 데이터들은 관심사의 분리로서 atom에서 관리하는 방식을 사용하기도 한다.

장점을 요약하자면 가볍고, 배우기 쉬우며, React 전용 라이브러리인만큼 내부접근성이 용이하며 React 문법과 유사하다는 것이다.

profile
코딩이 하고 싶은 사람

0개의 댓글