1. Context란?
전역 데이터(Global data)
- Language처럼 프로젝트 전체적으로 사용하는 데이터
- 전역 데이터를 다룰 때 props와 state만 사용하면 Prop Drilling 문제가 발생한다.
Prop Drilling
- 드릴로 땅을 파듯이 상위 컴포넌트에서 하위 컴포넌트로 반복해서 Prop을 내려주는 상황
React Conext
- React Context는 React 애플리케이션 전체에서 전역적으로 사용될 데이터를 관리하기 위한 기능입니다.
- Context를 사용하면, 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전달하지 않아도 하위 컴포넌트에서 해당 데이터에 접근할 수 있습니다.
<Context.Provider />
컴포넌트로 범위를 정해 줄 수 있습니다.
2. Context로 데이터 내려주기
import { createContext } from "react";
const LocaleContext = createContext();
export default LocaleContext;
import LocaleContext from "../contexts/LocaleContext";
...
function App() {
...
return (
<LocaleContext.Provider value="ko">
...
</LocaleContext.Provider>
);
}
import LocaleContext from "../contexts/LocaleContext";
function ReviewListItem({ item, onDelete, onEdit }) {
const locale = useContext(LocaleContext);
...
return (
...
<p>현재 언어: {locale}</p>
...
);
}
useContext()
는 React Hook 중 하나로, React Context에서 값을 읽어오는 데 사용됩니다. Context 객체와 함께 사용되며, 해당 Context 객체가 제공하는 값을 리턴합니다.
createContext()
함수는 아규먼트로 Context가 제공할 기본 값을 받습니다.
<LocaleContext.Provider>
안의 컴포넌트에서는 어디서나 LocaleContext가 제공하는 값을 쓸 수 있습니다.
- 공유할 데이터를 Provider의 value prop으로 내려줄 수 있습니다.
- Provider는 태그로 감싸지만 Fragment처럼 아무 것도 렌더링하지 않습니다.
3. Context 값에 State 사용하기
LocaleSelect.js
컴포넌트를 만듭니다.
function LocaleSelect({ value, onChange }) {
const handleChange = (e) => onChange(e.target.value);
return (
<select value={value} onChange={handleChange}>
<option value="ko">한국어</option>
<option value="en">English</option>
</select>
);
}
export default LocaleSelect;
- App 컴포넌트에 locale state를 만듭니다.
function App() {
const [locale, setLocale] = useState("ko");
...
return (
...
<LocaleSelect value={locale} onChange={setLocale} />
...
);
}
4. Context 코드 분리하기
Locale Context에서 상태를 관리하는 것의 이점
- State를 컴포넌트 외부에서 관리하여, 상태 변경에 따른 리렌더링이 불필요한 컴포넌트에서 발생하지 않습니다. 이는 성능 향상에 도움이 됩니다.
- 상태를 컴포넌트 외부에서 관리하면, 다른 컴포넌트에서도 해당 상태에 접근할 수 있으므로, 코드의 중복을 줄일 수 있습니다.
- 언어 설정과 관련된 상태는 애플리케이션 전역에서 사용되므로, 이를 하나의 중앙화된 장소에서 관리할 수 있어서 유지보수가 용이합니다.
Locale Context에서 state 관리하기
- locale state를
LocaleContext.js
로 옮겨줍니다.
import { createContext, useState } from "react";
const LocaleContext = createContext();
export function LocaleProvider(defaultValue = "ko", { children }) {
const [locale, setLocale] = useState(defaultValue);
return (
<LocaleContext.Provider value={{ locale, setLocale }}>
{children}
</LocaleContext.Provider>
);
}
export default LocaleContext;
- locale 값을 전달하고 가져오기 위한 커스텀 hook을 만듭니다.
...
export function useLocale() {
const context = useContext(LocaleContext);
if (!context) {
throw new Error("반드시 LocaleProvider 안에서 사용해야 합니다");
}
const { locale } = context;
return locale;
}
export function useSetLocale() {
const context = useContext(LocaleContext);
if (!context) {
throw new Error("반드시 LocaleProvider 안에서 사용해야 합니다");
}
const { setLocale } = context;
return setLocale;
}
...
- 컴포넌트에서 관리하던 상태들을 커스텀 훅으로 전부 바꿔줍니다.
5. 다국어 기능 완성하기
useTranslate.js
커스텀 훅을 만듭니다.
import { useLocale } from '../contexts/LocaleContext';
const dict = {
ko: {
'confirm button': '확인',
'cancel button': '취소',
'edit button': '수정',
'delete button': '삭제',
},
en: {
'confirm button': 'OK',
'cancel button': 'Cancel',
'edit button': 'Edit',
'delete button': 'Delete',
},
};
function useTranslate() {
const locale = useLocale();
const translate = (key) => dict[locale][key] || '';
return translate;
}
export default useTranslate;
- 컴포넌트에서 커스텀 훅을 사용합니다.
import useTranslate from "../hooks/useTranslate";
function component () {
...
const t = useTranslate();
...
return (
...
<>{t("edit button")}</>
<>{t("delete button")}</>
<>{t("cancel button")}</>
<>{t("confirm button")}</>
);
}
6. 상태 관리의 짧은 역사
초기 버전의 React
- 초기 버전의 React에서는 상태 관리를 위해 컴포넌트 내부에 state를 정의하고, 이를 업데이트하는 방식으로 처리했습니다.
- 그러나 React 애플리케이션의 규모가 커질수록 컴포넌트 간의 데이터 흐름이 복잡해지고, 상태 관리가 어려워졌습니다. 이에 따라 React 생태계에서는 여러 가지 상태 관리 라이브러리가 등장하게 되었습니다.
- 2013년, Flux 아키텍처가 처음으로 Facebook에서 발표되었습니다.
Flux
- Flux는 단방향 데이터 흐름을 따르는 아키텍처 패턴으로, 상태를 변경하는 모든 액션(action)이 중앙 집중적으로 관리되는 스토어(store)에서 처리됩니다.
- 이는 데이터가 어디에서 변경되었는지 추적하기 쉽고, 예측 가능한 데이터 흐름을 제공하여 React 애플리케이션의 복잡성을 줄입니다.
Dispatcher
애플리케이션 내에서 발생한 모든 액션(action)을 처리합니다. Dispatcher는 단 하나만 존재하며, 모든 액션을 처리하기 위한 등록된 콜백 함수를 갖고 있습니다.
Store
애플리케이션의 상태를 저장하고 관리합니다. Store는 Dispatcher로부터 전달받은 액션을 처리하여 상태를 업데이트합니다.
Action
애플리케이션에서 발생한 이벤트를 나타냅니다. 액션은 일반적으로 액션 타입과 데이터를 포함합니다.
View
상태를 표시하는 사용자 인터페이스입니다. View는 상태를 변경할 수 없으며, 상태 변경을 위해 액션을 디스패치합니다.
- 2015년
Dan Abramov
는 Flux를 단순화하고 개선한 Redux 라이브러리를 만들었습니다.
Redux
- Redux는 React 애플리케이션에서 상태(state)를 관리하기 위한 JavaScript 라이브러리로, Flux 아키텍처를 기반으로 하며, 단방향 데이터 흐름을 따릅니다.
- Redux는 간단한 디자인과 순수한 함수를 사용하여 Flux 구현의 복잡성을 줄이고 불필요한 코드들을 단순화하였습니다.
- Redux는 다음과 같은 핵심 개념으로 이루어져 있습니다.
Store
애플리케이션의 상태를 저장하고 관리하는 객체입니다. Redux에서는 하나의 Store만 존재하며, 모든 상태가 Store에 저장됩니다.
Action
상태를 변경하는 객체입니다. 액션은 일반적으로 타입(type)과 데이터(payload)를 포함합니다.
Reducer
액션을 받아서 상태를 업데이트하는 함수입니다. Reducer는 이전 상태와 액션을 입력받아 새로운 상태를 반환합니다.
Dispatch
액션을 Store에 전달하는 함수입니다. Dispatch를 호출하면 액션이 Reducer에 전달되어 상태가 업데이트됩니다.
- Redux는 Flux와 비슷한 아키텍처를 사용하지만, 상태 변경을 위해 불변성(immutability)을 강조하고, 컴포넌트와 스토어 간의 연결을 위해 컨테이너(component)를 도입하는 등의 차이가 있습니다.
React Context API
- React Context API는 기존의 있던 내장 Context 기능을 업데이트하여 2018년 React 16.3에서 새롭게 도입되었습니다.
- useContext Hook을 사용하여 값에 대한 참조를 가져올 수 있게 되었습니다.
- 여러 개의 Context 객체를 중첩해서 사용할 수 있게 되었습니다.
- React Context API는 Redux와 함께 React 상태 관리에 많이 사용되었습니다.
- 하지만 Redux는
Store
에 모든 상태를 저장하기 때문에 데이터 하나를 추가할 때마다 무의미한 Redux 코드를 작성해야 했고, 비동기를 처리를 하려면 코드가 복잡해지고 가독성이 떨어지는 문제가 있었습니다.
- 그래서 데이터의 출처로 Client-state와 Server-state를 분리하려는 시도가 생겼습니다.
React Query
- 2020년에 Tanner Linsley가 React Table의 개발 경험에 영감을 받아 React Query 라이브러리를 개발했습니다.
- React 애플리케이션에서 서버에서 가져온 데이터를 관리하는 라이브러리입니다.
- Client-state와 Server-state를 분리하지는 않지만, 클라이언트 측에서 데이터 캐싱 및 자동 업데이트를 처리하므로 클라이언트 상태 관리에 유용한 라이브러리입니다.
- React Query는 다음과 같은 특징을 가지고 있습니다.
Data caching
React Query는 데이터를 캐싱하여, 동일한 요청이 반복될 때 서버에서 데이터를 다시 가져오지 않고 캐시된 데이터를 사용합니다. 이를 통해 네트워크 대역폭을 절약하고, 더 빠른 데이터 요청을 처리할 수 있습니다.
Automatic data updates
React Query는 캐싱된 데이터를 주기적으로 업데이트하여, 최신 데이터를 유지합니다. 이를 통해 UI를 업데이트하는데 필요한 데이터가 항상 최신 상태인 것을 보장할 수 있습니다.
Error handling
React Query는 네트워크 오류나 서버 오류 등의 오류를 처리하고, 오류 발생 시 UI를 업데이트합니다. 이를 통해 사용자가 오류를 인지하고, 적절한 대처를 할 수 있습니다.
Ease of use
React Query는 간단한 구현과 사용성을 가지고 있으며, 다른 상태 관리 라이브러리와 함께 사용할 수 있습니다.
SWR
- SWR은 2020년에 Zeit(지트) 팀에서 개발한 React Hooks 기반의 React 라이브러리입니다.
- Zeit 팀은 Next.js와 같은 프레임워크와 서비스를 제공하고 있는 회사로, SWR은 Zeit 팀에서 개발한 Next.js의 핵심 라이브러리 중 하나입니다.
- SWR은 데이터 요청과 관리를 효율적으로 처리하기 위해 개발되었습니다.
- SWR은 Next.js와 함께 사용할 수 있으며, React Native, Vue.js 등 다양한 프레임워크에서도 사용할 수 있습니다.
- SWR은 다음과 같은 특징을 가지고 있습니다.
Data caching
, Automatic data updates
, Error handling
Server-side Rendering
SWR은 서버 사이드 렌더링을 지원합니다. 이를 통해 초기 로딩 속도를 개선하고, SEO에 유리한 페이지를 생성할 수 있습니다.
- 이후에는 MobX, Recoil, Zustand 등의 다양한 상태 관리 라이브러리가 등장하면서, React 애플리케이션의 상태 관리 방식이 다양화되었습니다.
Recoil
Recoil
은 2020년 페이스북이 개발한 React 상태 관리 라이브러리입니다.
Recoil
은 상태를 작은 조각으로 분할하고, 상태 간의 의존성을 처리하는 방식으로 상태 관리를 간편하게 처리할 수 있도록 도와줍니다.
Recoil
은 다음과 같은 특징을 가지고 있습니다.
Atomic state management
Recoil은 상태를 작은 조각으로 분할하여 원자적으로 관리합니다. 이를 통해 상태 관리를 간단하게 처리할 수 있습니다.
Asynchronous data handling
Recoil은 비동기 데이터를 처리하기 위한 기능을 제공합니다. 이를 통해 비동기 데이터를 간편하게 처리할 수 있습니다.
State selectors
Recoil은 상태 선택자를 제공하여, 상태를 읽고 변경하는 방법을 편리하게 처리할 수 있습니다.
TypeScript support
Recoil은 TypeScript를 지원하며, 타입 안정성을 보장합니다.
Feedback
- 상태 관리 section은 더 보충해서 작성하자.
- 상태 관리 라이브러리를 더 자세하게 찾아보자.
- Language 외에 전역 데이터로 관리해야할 데이터는 무엇이 있을까?
Reference