Skip to content

Instantly share code, notes, and snippets.

@superLipbalm
Last active March 21, 2024 10:54
Show Gist options
  • Save superLipbalm/dea43695b66897d1b4d246b402aab320 to your computer and use it in GitHub Desktop.
Save superLipbalm/dea43695b66897d1b4d246b402aab320 to your computer and use it in GitHub Desktop.
리액트로 프록시 디자인 패턴 사용하기

리액트로 프록시 디자인 패턴 사용하기

리액트 환경에서 프록시 디자인 패턴을 사용하는 방법을 살펴보세요.

thumbnail

원문: https://blog.bitsrc.io/proxy-design-pattern-with-react-c0b465980fbf

개발자라면 **'디자인 패턴'**이라는 용어를 들어본 적이 있을 것입니다. 이는 단순히 지나쳐도 되는 단어가 아니라 코딩 숙달의 영역을 여는 열쇠입니다. 이처럼 다양하고 폭넓은 패턴은 소프트웨어 개발에서 코드 정리, 유지보수성 및 확장성을 향상하는 유용한 도구로 사용됩니다.

다른 디자인 패턴 중에서도 1995년 구조적 디자인 패턴으로 소개된 프록시 디자인 패턴은 이후 다양한 시나리오에서 광범위하게 적용되고 있습니다.

이 글에서는 프록시 디자인 패턴과 사용법을 살펴보고, 향후 리액트 애플리케이션에 이러한 패턴을 적용하여 구조와 기능을 향상하는 방법에 대해 알아보겠습니다.

프록시 디자인 패턴 이해

image

Source: https://refactoring.guru/design-patterns/proxy

프록시 디자인 패턴을 사용하면 다른 객체의 대체물이나 플레이스 홀더를 제공할 수 있습니다.

이렇게 하면 프록시는 원본 객체에 대한 접근 권한을 확보하여 요청이 원본 객체에 도달하기 전이나 후에 작업을 실행할 수 있습니다.

객체에 대한 접근을 제어하려는 이유는 무엇인가요?

예를 들어, 특정 함수가 지속적으로 작동하여 시스템 및 네트워크 리소스를 많이 소비하는 대규모 객체를 생성하는 상황이 발생할 수 있습니다. 주기적으로 또는 특정 조건에서 실행해야 할 수도 있지만 계속 실행하면 코드의 전반적인 성능이 저하될 수 있습니다.

이러한 경우 프록시 디자인 패턴이 유용합니다. 여기에는 원래 서비스 객체의 인터페이스를 미러링하는 또 다른 프록시 클래스를 구축하는 작업이 포함됩니다.

프록시 디자인 패턴을 사용하면 어떤 이점이 있나요?

프록시 객체를 구현하면 클래스의 메인 로직을 실행하기 전이나 후에 다른 로직을 실행할 수 있습니다.

예를 들어, 앞서 언급한 예시 시나리오에서 프록시 객체를 설정하여 메인 객체가 처리하는 데이터를 캐시 할 수 있습니다. 이러한 사전 캐싱은 비용이 많이 드는 계산의 반복을 최소화하여 성능과 리소스 효율성을 개선합니다.

데이터 캐싱뿐만 아니라 프록시 디자인 패턴은 다음과 같은 여러 가지 방면에서 유용할 수 있습니다.

  • 보안 강화: 인증 및 권한 확인과 같은 보안 조치를 시행하여 권한이 있는 사용자만 민감한 데이터나 작업에 접근 할 수 있도록 할 수 있습니다.
  • 리소스 소비 감소: 지연 로딩, 캐싱, 프록시를 통한 제어된 접근은 애플리케이션의 리소스 소비를 줄일 수 있습니다.
  • 네트워크 최적화: 분산 시스템에서 프록시 디자인 패턴은 원격 객체의 플레이스홀더 역할을 함으로써 네트워크 통신을 최적화할 수 있습니다. 이는 네트워크 지연의 영향을 최소화하는 데 도움이 됩니다.
  • 동시성 제어 프록시는 공유 리소스에 대한 동시 접근을 제어하고, 경합 조건을 방지하며, 데이터 일관성을 보장하는 메커니즘을 구현할 수 있습니다.

자바스크립트에서 프록시 객체란 무엇인가요?

자바스크립트의 프록시 객체는 다른 프로그래밍 언어의 프록시 핵심 개념을 기반으로 구축되었지만, 자바스크립트의 동적 특성에 맞춘 고유한 기능 세트를 함께 제공합니다.

다른 언어의 기존 프록시 객체와 달리 자바스크립트의 프록시 객체는 객체와 상호작용하고 조작할 수 있는 매우 유연하고 동적인 방법을 제공합니다.

자바스크립트에서 프록시 객체는 다음과 같이 정의할 수 있습니다.

const proxy = new Proxy(target, handler);

target은 프록시가 감싸는 객체를 말하며, handler는 대상 객체에서 수행되는 작업을 가로채고 수정하도록 설계된 함수인 하나 이상의 "트랩"을 포함하는 객체입니다.

사용 예시

const targetObject = {
  message: "Hello, Proxy!",
};

const handler = {
  get: function (target, prop) {
    console.log(`Accessing property: ${prop}`);
    return target[prop];
  },
};

const proxy = new Proxy(targetObject, handler);
console.log(proxy.message);

// Accessing property: message 
// Hello, Proxy!

이 예제에서는 get 트랩을 사용하여 JS 프록시 객체의 기본 사용법을 보여드렸습니다. 자세한 내용은 여기에서 확인할 수 있습니다.

JS 프록시 객체에 대한 기본적인 지식이 있으면 다음 섹션을 이해하는 데 도움이 됩니다.

어떻게 리액트에서 프록시 디자인 패턴을 사용하나요?

비트 스코프에서 코드를 직접 살펴볼 수 있습니다.

이제 자바스크립트의 프록시 디자인 패턴과 프록시 객체에 대해 명확하게 이해하셨을 것입니다. 이제 리액트에서 프록시를 사용해 직접 경험해 볼 차례입니다.

리액트에는 앱의 성능과 전반적인 응답성을 최적화하는 기능이 내장되어 있습니다. 하지만 프록시 디자인 패턴과 이러한 기능을 결합하면 객체 상호작용과 데이터 접근을 잘 제어하여 앱의 성능을 더욱 향상할 수 있습니다.

보안 강화

프록시 디자인 패턴은 객체 상호작용과 민감한 데이터에 대한 접근에 대한 제어 계층을 제공하여 보안을 강화하는 데 리액트 애플리케이션에서 유용할 수 있습니다. 다음은 이 개념에 대한 간단한 데모입니다.

import { useEffect, useState } from 'react';

const fetchUserData = () => {
 // 네트워크 요청을 여기에 위치시킵니다.
 console.log('Data fetching...');

 return {
  username: 'john_doe',
  password: 'user_password',
 };
};

const useSecureUserData = () => {
 const [userData, setUserData] = useState(null);

 const isUserAdmin = () => {
  // 여기에서 인증 로직을 구현하세요.
  // 간단하게 하기 위해 로그인한 사용자가 관리자라고 가정합니다.
  return true;
 };

 useEffect(() => {
  const userProxy = new Proxy(fetchUserData(), {
   get(target, prop) {
    // 사용자가 사용자 비밀번호에 접근할 수 있는 권한이 있는지 확인합니다.
    if (prop === 'password' && !isUserAdmin()) {
     console.warn('Unauthorized access to password!');
     return null; // null을 반환하거나 허가되지 않은 접근을 처리합니다.
    }

    return target[prop];
   },
  });

  setUserData(userProxy);
 }, []);

 return userData;
};

export const UserProfile = () => {
 const userData = useSecureUserData();

 if (!userData) {
  return <div>Loading...</div>;
 }

 return (
  <div>
   <p>Username: {userData.username}</p>
   <p>Password: {userData.password}</p>
  </div>
 );
};

이 간단한 예시에서, useSecureUserData 커스텀 훅은 사용자 데이터를 가져와서 프록시 객체를 생성하여 사용자 데이터의 유효성을 검사한 후 반환합니다. 여기서 fetchUserData는 실제 객체를 반환하고 userProxy는 프록시 객체입니다. 마지막으로 UserProfile 컴포넌트는 프록시 객체를 호출하는 클라이언트로 작동합니다.

이 예시에서는 접근 관리를 위해 프록시 객체를 사용하는 방법을 설명했지만, 고차 컴포넌트(HOC), 컨텍스트 API 또는 라우터 가드를 사용하는 것이 리액트에서 일반적이며 때로는 더 일반적인 접근 방식이라는 점에 유의해야 합니다. 무엇을 사용할지는 애플리케이션의 특정 요구 사항과 원하는 접근 제어 수준에 따라 달라집니다.

효율적인 데이터 관리

애플리케이션에 리소스를 소모하고 성능을 저하하는 네트워크 요청이 많은 경우, 가져온 데이터를 캐싱하는 것이 실행할 수 있는 해결책이 될 수 있습니다. 리액트에서 데이터를 캐싱하기 위한 많은 솔루션이 있으며, 프록시 객체를 사용하는 것도 그중 하나입니다.

다음 예제에서는 프록시 객체를 만들어 네트워크 응답을 캐시 하고 추가 네트워크 요청을 하지 않고 데이터를 재사용하겠습니다.

import React from 'react';

const fetchUserData = () => {
 // 네트워크 요청을 여기에 위치시킵니다.
 console.log('Data fetching...');

 return [
  {
   id: '0001',
   name: 'John Doe',
   email: 'john@mail.com',
  },
 ];
};

const cachedUserData = new Proxy([], {
 get: function (target: any) {
  if (!target.data) {
   // 캐시되지 않은 경우 데이터 가져옵니다.
   target.data = fetchUserData();
  } else {
   console.log('Returning cached data...');
  }

  return target.data;
 },
});

export function UserList() {
 const [state, setState] = React.useState(
  [] as { id: string; name: string; email: string }[]
 );

 return (
  <div>
   {state.map((user) => (
    <div key={user.id}>
     <p>Name: {user.name}</p>
     <p>Email: {user.email}</p>
    </div>
   ))}
   <button onClick={() => setState(cachedUserData.data)}>Click me</button>
  </div>
 );
}

여기서 데이터가 이미 객체에 있는 경우 API 호출이 전송되지 않습니다. 그렇지 않으면 데이터를 가져와서 프록시 객체를 사용하여 캐시 합니다. 여기에서 "Click me" 버튼을 클릭하고 콘솔을 확인하면 캐싱이 어떻게 작동하는지 더 잘 이해할 수 있습니다.

이와 같은 간단한 시나리오의 경우 데이터 상태를 효과적으로 관리하고 모든 렌더링에서 네트워크 요청을 피하면 애플리케이션의 성능 문제를 완화할 수 있습니다. 이와 같은 시나리오뿐만 아니라 프록시 데이터 객체는 애플리케이션에서 데이터를 캐싱하는 데 여러 가지 방법으로 유용할 수 있습니다.

코드를 통해 살펴보기

const createExpensiveFunction = () => {
 // 이 함수를 실제로 비용이 큰 함수로 대체하세요.
 const expensiveFunction = (arg) => {
  console.log(`Calculating expensive value for ${arg}`);

  return arg * 2;
 };

 const cache = new Map();

 const handler = {
  apply(target, thisArg, argumentsList) {
   const arg = argumentsList[0];

   if (!cache.has(arg)) {
    cache.set(arg, expensiveFunction(arg));
   } else {
    console.log('Returning cached data...');
   }

   return cache.get(arg);
  },
 };

 return new Proxy(expensiveFunction, handler);
};

const memorizedFunction = createExpensiveFunction();

export const MemorizeExpensiveFunction = () => {
 const items = [1, 2, 1, 1, 5];

 return (
  <div>
   {items.map((item, index) => (
    <div key={`${item}-${index}`}>
     <p>Item: {item}</p>
     <p>Expensive Value: {memorizedFunction(item)}</p>
    </div>
   ))}
  </div>
 );
};

이 예시는 비용이 많이 드는 함수 결과를 캐싱하는 간단한 방법을 보여줍니다. createExpensiveFunctionProxy 객체를 반환합니다. 이 함수는 입력 인수를 확인하고 동일한 입력으로 함수가 다시 호출되면 캐시된 결과를 검색합니다. 그렇지 않으면 비싼 함수를 실행하고 결과를 캐시에 저장합니다.

기타 일반적인 사용 사례

앞서 언급한 사례뿐만 아니라 프록시 디자인 패턴을 사용하면 개발에 도움이 될 수 있는 다양한 사용 사례가 있습니다.

  • 추상화 및 코드 재사용성: 프록시는 복잡한 데이터 접근 패턴이나 API 상호 작용을 추상화하여 코드를 단순화하고, 코드 중복을 줄이며, 코드의 재사용성을 향상할 수 있습니다.
  • 데이터 유효성 검사 및 새니타이즈(Sanitize): 프록시는 컴포넌트의 로직에 도달하기 전에 사용자 입력의 유효성을 검사하는 데 사용할 수 있습니다. 이는 또한 데이터를 새니타이즈 하여 XSS 공격으로부터 보호하고 전반적인 애플리케이션 보안을 강화하는 데 도움이 될 수 있습니다.
  • 성능 모니터링 및 최적화: 프록시는 API 호출 시간이나 데이터 접근 패턴과 같은 성능 지표를 모니터링하는 데 사용할 수 있어 개발자가 성능 병목 현상을 정확히 파악하고 해결할 수 있습니다.

앞서 언급한 사용 사례와 관련된 더 많은 코딩 예제를 보려면 이 비트 스코프를 살펴보세요. 자유롭게 시도하고, 커스터마이징하고, 실험해 보면서 개념을 더 깊이 이해해 보세요.

결론

프록시 디자인 패턴은 자바스크립트의 프록시 객체와 결합하여 리액트 애플리케이션을 향상하기 위한 강력한 툴킷을 제공합니다. 성능 최적화 및 민감한 데이터 보안부터 효율적인 데이터 관리까지 프록시는 다양한 솔루션을 제공합니다.

실제 사용 사례를 살펴보았지만, 이상적인 솔루션은 특정 애플리케이션 요구 사항에 따라 다르다는 점을 기억하세요. 리액트는 개발자에게 유연성을 부여하여 최상의 접근 방식을 선택할 수 있게 해 줍니다.

프록시 디자인 패턴을 살펴볼 때 코드 간소화, 보안 개선, 탄력적인 리액트 개발 환경을 위한 최적화된 성능 등 프록시의 힘을 기억하세요.

구현된 코드를 살펴보고 싶다면 이 비트 스코프를 살펴보세요.

읽어주셔서 감사합니다.

더 알아보기

🚀 한국어로 된 프런트엔드 아티클을 빠르게 받아보고 싶다면 Korean FE Article(https://kofearticle.substack.com/)을 구독해주세요!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment