React 상태관리 라이브러리 비교분석-!(Redux, Recoil 편)

Jiwon Joung·2022년 11월 4일
12

New Techs : FRONTEND

목록 보기
1/1

상태관리가 필요한 이유?

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

상태란?

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

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

props drilling

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

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

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

💡 이래서 전역적으로 상태를 관리하는 상태관리 라이브러리가 필요하다 !

상태관리 라이브러리는 어떤걸 써야할까?

프론트엔드의 필연적 문제이자 우리에게 어려움을 주는것은 .. 아무래도 이쪽 시장(?)이 변화가 빠르고 자율성이 높다는것이겠다. 상태관리 라이브러리는 종류가 여러가지 있고, 현재에도 간간히 만들어지고 있는다.

각각이 설계적 특성이 다르고, 새로 나오는 기술은 대체로 기존 기술의 고질적 문제들을 해결해서 개발되는 방향이라서 단순히 시장 점유율이 높은 라이브러리를 사용할지, 요즘 뜨는 라이브러리를 사용할지 고민될 때가 있다.

그래서 몇가지 리액트 상태관리 라이브러리를 비교해보려고 한다!

아래는 npm trend 로 몇가지 리액트 라이브러리 다운로드 추세를 비교한 그래프이다.

보면 .. 요즘 recoil, zustand가 뜬다고는 하지만 redux가 압도적인것같긴 하다

몇가지를 살펴보자


🔥 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()

더 자세한건 아래…

Context - React

문제점

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

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

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

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

  • 굳이 알고싶다면?
    1. context를 여러개 만들어서 분할 ⇒
    2. memoization 사용 ⇒ 컴포넌트 수가 많아짐
    3. useReducer hook 사용 ⇒ 안써봤지만 redux랑 개발방식이 비슷하다고 하네요

통상적으로 theme(dark/light mode), lang 같이 값 변화가 자주 일어나지 않는 변수들만 context로 관리하는 것 같다.


🔥 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;
	}
}

reducerstore를 직접적으로 업데이트 하는 역할을 한다. 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을 자동으로 만들어준다던지 하는 방향으로 개발을 좀 더 쉽게 만들어준다.

하지만 .. 여전히 복잡하고 배울게 너무 많다 ㅜ.ㅜ

느낌

일단 내 개인적인 느낌을 적어보자면, Vue는 상태관리 라이브러리가 거의 Vuex로 단일화돼서 이것만 써봤었는데, Vuex에 익숙하던 난 Redux를 처음 접했을때 굉장히 어렵고 복잡하다라는 느낌을 받았다.

React 프로젝트에서 무조건 Redux를 써야하는 것은 아니다. Redux 공식 문서에서도 ‘사람들이 좋다고 말해서 그냥 써보는것’은 지양한다 라고 말한다. 프로젝트에서 Redux 방식의 상태관리가 적절하다면 사용해라 라고 말하더라..


🔥 Recoil

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

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

구조

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

actiondispatchreducerview 의 흐름을 가지는 redux와 달리

atomselectorview 의 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 문법과 유사하다는 것!

단점은 .. 실제 프로젝트에 사용하게 되면 알려드릴게요

오늘의 포스팅은 여기까지..
다음 포스팅은 zustand에 대해 조사해볼게용

1개의 댓글

comment-user-thumbnail
2023년 5월 27일

좋은 글 잘 보고갑니다!😁😁
그런데 Context API는 React라는 라이브러리의 포함된 기능의 일부인데, 이걸 라이브러리라고 할 수 있을까요? (정말 제가 궁금해서 하는 질문입니다!)

답글 달기