하루살이 개발일지

모듈 패턴(Module Pattern)에 대하여 본문

웹개발/React

모듈 패턴(Module Pattern)에 대하여

harusari 2023. 9. 13. 23:52

모듈 패턴이란?


JavaScript에서 모듈 패턴은 특정 코드 조각들을 모듈로서 분리하여 재사용 가능하게 만드는 패턴이다.

 

 

모듈이 뭔데?

모듈이란 프로그래밍에서 독립적인 기능 단위를 의미 일반적으로 함수, 변수, 클래스 등을 그룹화하여 하나의 파일(또는 스크립트)에 넣은 것 의미

 

모듈 패턴의 이점

  1. 네임스페이스 관리: 전역 네임스페이스의 오염을 방지하며, 이름 충돌의 위험을 줄임
  2. 재사용성: 코드를 모듈화하면, 특정 기능을 캡슐화하고 이를 다른 프로젝트에서도 재사용할 수 있음
  3. 유지보수성: 모듈별로 코드를 분리하면, 버그를 찾고 수정하는 것이 쉬우며, 기능 추가나 수정이 간편
  4. 테스트 용이성: 모듈은 독립적으로 테스트할 수 있어, 단위 테스트가 더욱 쉬워짐

모범 사례

  • 단일 책임 원칙: 각 모듈은 하나의 책임만 가져야 한다
  • 종속성 관리: 모듈은 필요한 종속성만 가져와야 하며, 이는 코드의 효율성과 유지 보수성을 향상시킨다

 

 

JavaScript에서 모듈 패턴의 사용 예시


1. 즉시 실행 함수 표현식 (IIFE: Immediately Invoked Function Expression)

IIFE(즉시 실행 함수 표현식)는 이름에서 알 수 있듯이 정의되자마자 즉시 실행되는 함수를 의미한다. 이 구조는 함수를 선언하고, 이어서 그 함수를 즉시 호출하는 방식으로 작동한다. JavaScript에서는 이를 사용하여 변수와 함수를 지역 범위 내에 유지하며, 전역 네임스페이스의 오염을 방지할 수 있다.

 

IIFE 선언:

(function() {
  // ...
})();

이 부분은 IIFE를 선언하고 즉시 실행한다. IIFE의 본문 내부에서 선언된 모든 변수와 함수는 IIFE의 지역 범위 내에서만 유효하므로 전역 네임스페이스를 오염시키지 않는다.

 

IIFE의 사용 예시:

(function() {
  var privateVariable = "private";
  
  function privateFunction() {
    console.log(privateVariable);
  }

  window.myModule = {
    publicFunction: function() {
      privateFunction();
    }
  };
})();

myModule.publicFunction(); // "private"

이 예제에서는 IIFE를 사용해 모듈을 생성하고 있다.

이를 통해 얻는 이점은

 

코드 캡슐화

var privateVariable = "private";

function privateFunction() {
  console.log(privateVariable);
}

privateVariable 변수와 privateFunction 함수 내부에서 캡슐화가 일어난다. IIFE 내부에서 정의되므로 외부에서 직접 접근할 수 없기 때문이다. 이를 통해 모듈의 내부 구현을 숨기고 공개 인터페이스만을 통해 접근할 수 있다.

 

네임스페이스 관리

window.myModule = {
  publicFunction: function() {
    privateFunction();
  }
};

window.myModule 객체를 통해 이름 공간 관리가 이루어진다. myModule 객체는 모듈의 공개 인터페이스를 나타내며, 모듈 외부에서 이 객체를 통해 모듈의 기능에 접근할 수 있다. 모듈 내부의 세부 구현은 외부로부터 숨겨진다.

 

변수 이름의 재사용

IIFE 내부에서는 어떤 변수 이름이든 자유롭게 사용할 수 있다. IIFE 내부는 별도의 스코프를 가지기 때문이다. 즉 privateVariable 변수 이름은 다른 곳에서도 사용할 수 있으며 IIFE 내부의 변수와는 다른 변수가 된다.

 

2. Revealing Module Pattern

이 패턴은 IIFE의 변형으로, 모듈 내부의 private 멤버와 public 멤버를 명확하게 구분한다. private 멤버는 모듈 외부에서 접근할 수 없으며, public 멤버는 모듈 외부에서도 접근할 수 있다. 모듈이 제공하는 공개 API를 정의하여 다른 모듈이 사용할 수 있게 한다 (public 멤버)

var myModule = (function() {
  var privateVariable = "private";
  
  function privateFunction() {
    console.log(privateVariable);
  }

  function publicFunction() {
    privateFunction();
  }

  return {
    publicFunction: publicFunction
  };
})();

myModule.publicFunction(); // "private"

IIFE 패턴과의 공통점은 둘 다 즉시 호출되는 함수를 사용해 변수와 함수를 캡슐화하는 것이다.

그러나 차이점은,

  1. IIFE는 그냥 즉시 실행되는 함수일 뿐이며 특별한 방식으로 멤버를 공개하지 않는다. 주로 변수와 함수의 스코프를 제한하는 데 사용 그러나 Revealing Module Pattern은 IIFE를 확장해 모듈 내의 private 멤버와 public 멤버를 구분하고 특정 멤버만을 외부에 공개
  2. IIFE 내부의 모든 변수, 함수는 기본적으로 private이지만, Revealing Module Pattern은 반환 객체 내 명시적으로 public 멤버를 나열함으로써 어떤 멤버가 공개되는지 명확히 나타낼 수 있다.

 

3. ES6 모듈

ES6부터 JavaScript는 언어 수준에서 모듈을 지원한다. import와 export 키워드를 사용하여 모듈을 가져오거나 내보낼 수 있다. 현대 환경에서는 이것이 가장 일반적이다.

// module1.js
export function greet() {
  console.log("Hello!");
}

// main.js
import { greet } from './module1';
greet(); // "Hello!"

 

 

React에서의 Module Pattern


React에서 모듈 패턴을 사용하면 컴포넌트, 유틸리티 함수, 상태 관리 로직 등을 재사용 가능하고 유지 관리 가능한 코드 단위로 분리할 수 있다.

 

1. 컴포넌트 모듈화

React 컴포넌트 자체가 모듈로서의 역할을 할 수 있다.

// components/Button.js
import React from 'react';

function Button({ label, onClick }) {
  return <button onClick={onClick}>{label}</button>;
}

export default Button;

 

2. 유틸리티 함수 모듈화

유틸리티 함수를 별도의 모듈로 분리하여 여러 컴포넌트에서 재사용할 수 있다.

// utils/formatDate.js
export function formatDate(date) {
  return new Date(date).toLocaleDateString();
}

 

3. 컨텍스트 API와 모듈 패턴

React의 Context API를 사용하여 전역 상태 관리 로직을 모듈로 분리할 수 있다.

// context/UserContext.js
import React, { createContext, useState } from 'react';

export const UserContext = createContext();

export function UserProvider({ children }) {
  const [user, setUser] = useState(null);

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}
// App.js
import React from 'react';
import { UserProvider } from './context/UserContext';
import UserProfile from './components/UserProfile';

function App() {
  return (
    <UserProvider>
      <UserProfile />
    </UserProvider>
  );
}

export default App;

 

4. 모듈화된 스타일

스타일도 모듈화할 수 있으며, CSS Modules 또는 styled-components와 같은 라이브러리를 사용하여 스타일을 모듈화할 수 있다.

/* styles/Button.module.css */
.button {
  background-color: blue;
  color: white;
  border: none;
  padding: 10px;
}
// components/Button.js
import React from 'react';
import styles from '../styles/Button.module.css';

function Button({ label, onClick }) {
  return <button className={styles.button} onClick={onClick}>{label}</button>;
}

export default Button;

 

 

모듈 패턴을 사용하는 기술들


모듈 패턴이라는 개념 자체는, 코드를 여러 개의 독립적이고 재사용 가능한 부분으로 분리하는 더 넓은 개념이며, 이러한 원칙을 기반으로 한 다양한 기술과 방법론이 있다.

이는 모듈 패턴의 여러 이점(코드 재사용성, 유지보수성 향상 등)을 실현할 수 있다.

 

1. 코드 스플리팅과 레이지 로딩

대규모 애플리케이션에서 모듈 패턴을 적용하면 코드 스플리팅과 레이지 로딩을 이용해 애플리케이션의 로딩 시간을 줄일 수 있다. React에서는 React.lazy()와 Suspense를 사용하여 이를 구현할 수 있다.

import React, { Suspense } from 'react';

const LazyLoadedComponent = React.lazy(() => import('./LazyLoadedComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyLoadedComponent />
    </Suspense>
  );
}

export default App;

코드 스플리팅 (Code Splitting)

애플리케이션의 코드베이스를 여러 개의 번들로 나누는 과정 이 방법으로 초기 로딩 시간을 줄이고 불필요한 코드 다운로드 방지 가능 사용자가 특정 기능이나 라우트에 접근할 때만 관련 코드가 로딩

레이지 로딩 (Lazy Loading)

특정 리소스를 실제로 필요로 할 때까지 로딩을 지연시키는 기술 이 경우, LazyLoadedComponent는 사용자가 이 컴포넌트에 접근할 때까지 로딩이 지연 React.lazy() 메서드는 동적 import()를 사용하여 컴포넌트를 레이지 로드

Suspense

Suspense는 React가 컴포넌트의 렌더링을 지연시킬 수 있게 하며, 지연시키는 동안 로딩 인디케이터 같은 것을 보여줄 수 있음 이는 레이지 로딩된 컴포넌트가 아직 로딩되지 않았을 때 보여줄 대체 컴포넌트를 정의할 수 있게 해줌

 

 

2. 트리 쉐이킹 (Tree Shaking)

트리 쉐이킹은 사용되지 않는 코드를 최종 번들(bundle)에서 제거하는 최적화 과정을 의미한다. 주로 모듈 번들러(module bundler)들, 예를 들면 Webpack이나 Rollup에서 지원하는 기능이다.

// utilities.js
export function function1() { /* ... */ }
export function function2() { /* ... */ }
export function function3() { /* ... */ }

// main.js
import { function1 } from './utilities.js';

function1();
  • utilities.js: 3개의 함수를 export
  • main.js: utilities.js에서 function1만을 import하여 사용

트리 쉐이킹이 적용된다면, 최종적으로 번들링된 결과에서 function2와 function3는 제외. 왜냐하면 main.js에서는 function1만 사용되었기 때문

트리 쉐이킹은 코드를 독립적이고 재사용 가능하게 분리하는 모듈 패턴의 원칙을 따르며 번들링 과정에서 불필요한 부분을 제거해 효율적인 결과물을 생성한다. 따라서 트리 쉐이킹도 모듈 패턴을 사용하는 원칙 중 하나라고 볼 수 있다.

 

 

3. 컴포넌트 디자인 및 패턴

React 컴포넌트를 설계할 때에도 모듈 패턴을 적용할 수 있다. 예를 들어, Higher-Order Components (HOC) 또는 컴포넌트 컴포지션과 같은 패턴을 사용하여 컴포넌트를 모듈화하고 재사용할 수 있다.

//PreventScroll.tsx

const withPreventScroll = (
  Component: ComponentType<{
    preventScroll?: boolean;
  }>,
) => {
  return (props: { preventScroll?: boolean }) => {
    const { innerWidth, innerHeight } = useWindowSize();

    const windowWidth = innerWidth as number | undefined;
    const windowHeight = innerHeight as number | undefined;

    useDidMount(() => {
      document.body.style.overflow = 'hidden';
    });

    useWillUnmount(() => {
      document.body.style.overflow = 'scroll';
    });

    if (props.preventScroll) {
      return (
        <ConfettiWrapper>
          <Confetti
            gravity={0.6}
            recycle={false}
            width={windowWidth}
            height={windowHeight}
          />
          <Component {...props} />;
        </ConfettiWrapper>
      );
    }
    return <Component {...props} />;
  };
};

export default withPreventScroll;
//LoginModal.tsx

export const LoginModal: FC<LoginModalProps> = withPreventScroll(({}) => {
  const { form } = useLoginModalForm();
  const setIsVerificationFailed = useSetRecoilState($isVerificationFailed);

  useEffect(() => {
    return () => {
      setIsVerificationFailed(null);
    };
  }, []);

  return (
    <RecoilProvider>
      <FormProvider {...form}>
        <LoginLayout>
          <LoginModalContainer />
        </LoginLayout>
      </FormProvider>
    </RecoilProvider>
  );
});

PreventScroll.tsx

  • withPreventScroll이라는 HOC를 정의하는 파일
  • 다른 컴포넌트를 인자로 받아와, 그 컴포넌트에 몇 가지 추가적인 기능(스크롤 방지 및 선택적으로 컨페티 표시) 부여

LoginModal.tsx

  • withPreventScroll이라는 HOC로 래핑