리액트는 간단함과 유연함을 강점으로 최근 몇 년 동안 가장 인기 있는 프런트엔드 라이브러리 중 하나가 되었습니다. 그러나 어플리케이션이 더 복잡해질수록 상태를 관리하고 비동기 입력을 처리하며 확장 가능한 아키텍처를 유지하는 것이 어려워질 수 있습니다. 이 기사에서는 이러한 어려움을 극복하는 데 도움이 되는 다섯 가지 고급 리액트 패턴을 살펴보겠습니다:
- Backend for Frontend Pattern (BFF)
- Hooks Pattern
- Higher-Order Component Pattern
- Observer Pattern
- Compound Pattern
1. Backend for Frontend Pattern (BFF)
Backend for Frontend (BFF) 패턴은 React 애플리케이션을 개발할 때 별도의 백엔드를 사용하여 모든 API 쿼리와 데이터 처리를 관리할 수 있게 해 줍니다. 이를 통해 프런트엔드 코드를 단순하고 클린하게 유지해서 애플리케이션의 성능을 향상할 수 있습니다.
이러한 패턴을 구현하는 것은 Next.js와 같은 프레임워크를 사용할 때 더욱 간단해집니다. Next.js는 통합된 API 라우트 시스템을 갖추고 있어 프론트엔드 애플리케이션에 빠르게 API 서비스를 구축할 수 있습니다. 하지만 BFF 패턴을 사용하여 애플리케이션에 구현하는 것이 필수적이지는 않습니다.
BFF 패턴을 리액트 애플리케이션에 적용하는 경우는 언제일까요? 대형이고 복잡한 프론트엔드 애플리케이션이며 다수의 API 호출, 데이터 처리 및 집계 역할을 가지는 대시보드와 같은 경우가 예시입니다. 무거운 처리 로직을 프론트엔드에서 분리함으로써 더 확장 가능하고 유지 보수가 가능한 아키텍처를 만들 수 있습니다.
BFF 패턴을 사용하면 코드 중복을 피하고 유사한 데이터 및 API 쿼리를 공유하는 여러 프론트엔드 애플리케이션에서 코드 재사용을 추구하는 데도 도움이 될 수 있습니다.
아래는 BFF 패턴을 사용하지 않을 때의 예시 코드입니다:
import { useState } from 'react';
function MyComponent() {
const [data, setData] = useState([]);
const processData = (rawData) => {
// super long and complex data processing here
};
const loadData = () => {
const rawData = [
{ id: 1, title: 'Smartphone', category: 'electronics' },
{ id: 2, title: 'Laptop', category: 'electronics' },
{ id: 3, title: 'Chair', category: 'furniture' },
{ id: 4, title: 'Table', category: 'furniture' },
];
const processedData = processData(rawData);
setData(processedData);
};
loadData();
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</div>
);
}
아래는 BFF 패턴을 사용할 때의 예시 코드입니다:
import { useEffect, useState } from 'react';
import axios from 'axios';
const API_URL = 'https://my-bff-service.com';
function MyComponent({ data }) {
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</div>
);
}
function MyBFFComponent() {
const [data, setData] = useState([]);
useEffect(() => {
axios.get(`${API_URL}/my-data`).then((response) => {
setData(response.data);
});
}, []);
return <MyComponent data={data} />;
}
이 예시에서는 BFF 서비스로 요청을 보내고, 데이터를 미리 처리해주기 때문에 프론트엔드에서 불필요한 작업을 줄여 클라이언트 측 부담을 줄일 수 있습니다. 그러나 이 패턴을 사용하는 것이 항상 유리한 것은 아닙니다. API 요청의 지연 시간을 고려해야 합니다. 예를 들어 너무 많은 네트워크 요청이 있거나 대용량 데이터의 복잡한 변환 등이 해당 등이 있어 프론트엔드를 심각하게 느리게 만드는 경우에만 데이터 처리를 API로 이관함으로서 이점이 있습니다.
2. Hooks Pattern
리액트의 hooks 패턴은 함수형 컴포넌트에서 리액트의 상태와 다른 기능을 재사용할 수 있게 해주는 훌륭한 기능입니다. hooks는 리액트의 상태 및 기타 기능을 함수형 컴포넌트에서 사용할 수 있게 해줍니다.
useState 훅은 이전 예제에서 사용한 가장 일반적인 훅 중 하나입니다. 하지만 useEffect 훅과 같은 다른 많은 훅들도 사용할 수 있습니다. useEffect 훅은 컴포넌트 내에서 사이드 이펙트를 수행할 수 있게 해 주며, useContext 훅은 애플리케이션 전체에서 전역 데이터에 접근할 수 있게 해 줍니다.
아래는 API에서 데이터를 가져오는 커스텀 훅을 구성하는 예시 코드입니다:
import { useState, useEffect } from 'react';
import axios from 'axios';
function useFetch(url) {
const [data, setData] = useState([]);
useEffect(() => {
axios.get(url).then((response) => {
setData(response.data);
});
}, [url]);
return data;
}
function MyComponent() {
const data = useFetch('https://my-api.com/my-data');
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</div>
);
}
이 예시에서는 "useFetch"라는 커스텀 훅을 만들어 URL을 매개변수로 받고 API에서 데이터를 가져옵니다. 이 커스텀 훅은 "MyComponent" 컴포넌트에서 사용되어 데이터를 가져오고 표시합니다. 커스텀 훅을 생성함으로써 여러 컴포넌트에서 상태를 재사용할 수 있으며, 더 모듈화 되고 유지 관리가 용이한 아키텍처를 만들 수 있습니다.
참고: 리액트 훅은 일반적인 함수로 선언되고 보통과 같이 생겼지만, 리액트의 상태와 라이프사이클에 "훅"되는 특별한 함수입니다. 그래서 항상 "use"로 시작하는 것이 특징입니다.
3. Higher-Order Component Pattern
Higher-Order Component (HOC) 디자인 패턴은 코드를 재사용하고 결합하는 데 도움이 되는 강력한 도구입니다. HOC는 컴포넌트를 인풋으로 받고 새로운 기능을 갖춘 새로운 컴포넌트를 반환하는 함수입니다.
HOC의 인기 있는 응용 프로그램 사용 사례 중 하나는 인증을 수행하는 것입니다. 예를 들어, 인증을 확인하고 지정된 컴포넌트를 렌더링 하거나 로그인 페이지로 리디렉션 하는 함수형 컴포넌트를 작성할 수 있습니다. 이 컴포넌트는 HOC로 래핑 되어 다른 컴포넌트에 인증 기능을 추가할 수 있습니다.
이 패턴은 여러 컴포넌트에서 필요한 공통 기능을 추상화하여 코드를 더 모듈화 하고 유지 관리하기 쉽도록 도와줍니다. 또한 HOC는 여러 컴포넌트에서 재사용할 수 있으므로 코드 중복을 피할 수 있습니다.
결국, HOC는 기능과 재사용성을 향상하는 데 효과적인 도구로서 인증과 같은 교차 문제를 통합하는 데 특히 효과적입니다.
아래는 HOC를 사용한 예시 코드입니다:
import React from 'react';
function withAuth(WrappedComponent) {
return function WithAuth(props) {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
// 사용자가 로그인되어 있는지 확인
const isLoggedIn = true; // 실제 인증 로직으로 대체해야 합니다.
setIsLoggedIn(isLoggedIn);
}, []);
if (!isLoggedIn) {
return <p>이 컨텐츠에 접근하려면 로그인해야 합니다.</p>;
}
return <WrappedComponent {...props} />;
};
}
function MyComponent() {
return <p>인증이 필요한 보호된 컨텐츠입니다.</p>;
}
export default withAuth(MyComponent);
이 예시에서는 "withAuth"라는 HOC를 작성하여 컴포넌트를 인풋으로 받고 인증 로직을 포함한 새로운 컴포넌트를 반환합니다. 이 HOC는 "MyComponent" 컴포넌트에서 콘텐츠를 보호하고 인증을 요구합니다.
4. Observer Pattern
옵저버 패턴은 하나의 객체와 여러 구독자(옵저버)들 간의 일대다 관계를 구성하여 한 객체의 변경 사항을 자동으로 모든 구독자에게 전파하는 디자인 패턴입니다. 이 접근 방식은 상태와 데이터 흐름을 제어하는 데 유용합니다.
리액트 앱에서 옵저버 패턴을 구현하기 위해 내장된 useContext 훅을 사용할 수 있습니다. 이를 활용하면 명시적으로 props를 전달하지 않아도 컴포넌트 트리를 통해 데이터를 전달할 수 있습니다.
아래는 애플리케이션에서 옵저버를 구성하는 예시 코드입니다:
import { createContext, useContext, useState, useEffect } from 'react';
const MyContext = createContext([]);
function MyProvider(props) {
const [data, setData] = useState([]);
useEffect(() => {
// API에서 데이터 가져오기
const newData = [...]; // 실제 데이터로 대체
setData(newData);
}, []);
return <MyContext.Provider value={data}>{props.children}</MyContext.Provider>;
}
function MyObserver() {
const data = useContext(MyContext);
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</div>
);
}
function App() {
return (
<MyProvider>
<MyObserver />
</MyProvider>
);
}
이 예시에서는 "MyProvider" 프로바이더 컴포넌트를 생성하여 API에서 데이터를 가져오고 "MyContext"를 사용하여 자식 컴포넌트에 데이터를 제공합니다. 프로바이더에서 제공하는 데이터는 "MyObserver" 컴포넌트에서 사용되어 보여집니다. 이러한 방식으로 상태와 데이터 흐름을 제어하는데 옵저버 패턴을 활용할 수 있습니다.
5. Compound Pattern
Compound 패턴은 여러 패턴을 결합하여 보다 복잡한 아키텍처를 형성하는 디자인 패턴입니다. BFF 패턴, hooks 패턴, HOC 패턴, 옵저버 패턴 등을 하나의 솔루션으로 통합하여 확장 가능하고 견고한 프론트엔드 아키텍처를 구축할 수 있습니다.
아래는 이러한 패턴들을 하나로 묶어서 애플리케이션에 적용한 예시 코드입니다:
import { createContext, useContext, useState, useEffect } from 'react';
import axios from 'axios';
const API_URL = 'https://my-bff-service.com';
const MyContext = createContext([]);
function withAuth(WrappedComponent) {
return function WithAuth(props) {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
// 사용자가 로그인되어 있는지 확인
const isLoggedIn = true; // 실제 인증 로직으로 대체해야 합니다.
setIsLoggedIn(isLoggedIn);
}, []);
if (!isLoggedIn) {
return <div>이 컨텐츠에 접근하려면 로그인해야 합니다.</div>;
}
return <WrappedComponent {...props} />;
};
}
function useFetch(url) {
const [data, setData] = useState([]);
useEffect(() => {
axios.get(url).then((response) => {
setData(response.data);
});
}, [url]);
return data;
}
function MyComponent() {
const data = useFetch(`${API_URL}/my-data`);
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</div>
);
}
function MyObserver() {
const data = useContext(MyContext);
return (
<div>
{data.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</div>
);
}
function App() {
return (
<MyProvider>
<MyComponent />
<MyObserver />
</MyProvider>
);
}
이 예시에서는 "MyProvider" 컴포넌트로 데이터를 제공하고, "MyComponent"와 "MyObserver" 컴포넌트에서 이 데이터를 사용합니다. 또한 "withAuth" HOC를 사용하여 "MyComponent" 컴포넌트의 콘텐츠를 보호합니다. 이렇게 다양한 패턴을 결합함으로써 복잡한 인터페이스를 단순화시키고 코드베이스를 쉽게 확장하고 중복 없이 새로운 기능을 추가할 수 있습니다.
마무리하면, 이 기사에서 살펴본 다섯 가지 고급 리액트 패턴을 활용하여 더 복잡하고 견고한 애플리케이션을 만들 수 있습니다. 이러한 패턴을 적용하여 리액트 스킬을 한 단계 더 발전시키고 효율적이고 깨끗하며 안정적인 애플리케이션을 만들 수 있습니다.
'개발관련 > 리액트' 카테고리의 다른 글
React는 rendering 중에 계산하는 것이 더 빠르다. (0) | 2023.08.20 |
---|---|
react에서 Windowing을 구현해보자 (0) | 2023.08.10 |
리액트 컴포넌트 무한 스크롤 구현 (0) | 2023.05.11 |
CRA test 할때 axios import outside 에러 관련 (0) | 2023.05.01 |
React 프로젝트에서 GZIP 압축 사용하기 (0) | 2023.05.01 |