Proxy와 옵저버 패턴을 이용하여 전역상태 관리하기

hoon·2023년 3월 28일
0

Proxy와 옵저버 패턴이란 ?

옵저버 패턴과 프록시는 서로 다른 패턴이지만, 종종 같이 사용되기도 한다. 프록시는 객체의 동작을 제어하거나 객체에 접근할 때 추가적인 동작을 수행하기 위해 사용되고, 옵저버 패턴은 객체의 상태 변화를 감지하고 상태 변화에 따른 추가적인 동작을 수행하기 위해 사용된다.

Proxy와 옵저버 패턴을 이용하여 모달창과 overlay 상태관리하기

이제 모달창과 overlay를 프록시와 옵저버 패턴을 이용해서 전역상태 관리를 진행해 보자.

먼저, 프록시를 이용해서 state라는 전역 상태 객체를 만든다. 이 객체에는 모달창이나 overlay와 같은 컴포넌트들의 상태를 저장한다.

// store.js

export const state = new Proxy(
  {
    modalVisible: false,
    overlayVisible: false
  },
  {
    // state의 속성값이 변경될 때마다 호출되는 set 메소드
    set(target, key, value) {
      // 속성값 변경
      target[key] = value;
      // 속성값 변경을 감지하는 옵저버들에게 알림
      notifyObservers(key, value);
      // 속성값 변경이 완료된 후 true를 반환
      return true;
    }
  }
);

위 코드에서 state라는 Proxy 객체를 만들었으며, 이 객체에는 modalVisibleoverlayVisible이라는 두 개의 상태를 저장한다.

다음으로 Proxy 객체를 만들 때, set 핸들러를 추가했는데 이 핸들러는 프록시 객체의 속성이 변경될 때마다 호출된다. 이 핸들러에서는 변경된 속성의 이름과 값을 옵저버에게 알리는 notifyObservers 함수를 호출한다.

다음으로, 옵저버 패턴을 구현한다. 옵저버 패턴을 구현하기 위해서는 일반적으로 옵저버를 등록하고 삭제하는 함수와 옵저버에게 알리는 함수가 필요하다.

const observers = new Map();

function addObserver(key, observer) {
  if (!observers.has(key)) {
    observers.set(key, []);
  }
  observers.get(key).push(observer);
}

// export function removeObserver(key, observer) {
//   // 옵저버 리스트가 존재하는지 확인
//   if (observers.has(key)) {
//     const observerList = observers.get(key);
//     // 해당 옵저버가 존재하는 인덱스를 찾아 제거
//     const index = observerList.indexOf(observer);
//     if (index !== -1) {
//       observerList.splice(index, 1);
//     }
//   }
// }

function notifyObservers(key, value) {
  if (observers.has(key)) {
    const observerList = observers.get(key);
    observerList.forEach(observer => observer(value));
  }
}

위 코드에서는 observers라는 맵을 만들어서 옵저버를 저장한다. addObserver 함수는 observers 맵에 옵저버를 등록하는 함수이다. 만약 해당 속성에 등록된 옵저버가 없다면, 새로운 배열을 만들어서 옵저버를 추가한다.

removeObserver 함수는 observers 맵에서 옵저버를 삭제하는 함수이다. notifyObservers 함수는 set 핸들러에서 호출되는 함수로, 해당 속성에 등록된 옵저버에게 변경된 값을 알려준다.

하지만 removeObserver는 React와 같은 라이브러리나 프레임워크에서 사용되는 개념이기 때문에, 바닐라 자바스크립트에서는 필요하지 않기 때문에 삭제 하도록 한다.

결과적으로 store.js 파일에서 프록시와 옵저버 패턴을 이용해서 전역 상태 관리를 구현하고, Overlay.js, LoginModal.js, Header.js 파일에서 해당 상태를 사용하도록 구현할 수 있다.

각 컴포넌트에서 Proxy와 옵저버 패턴 활용하기

  1. app.js에서 상태 변경 감지

우선 app.js에 옵저버 함수가 관찰하는 state의 객체 중에서 modalVisibleoverlayVisible가 true일때는 모달창과 overlay가 보이도록, false일 때는 보이지 않는 로직을 구현한다. app.js에서 구현하는 이유는 모달창과 overlay는 프로젝트 전역에 걸쳐서 사용될 가능성이 크기 때문이다. 이렇게 하면 state가 변경될때마다 그에 맞는 로직이 프로젝트 전반에 걸쳐 실행된다.

// src/components/app.js
...

import { addObserver } from '../../store';

const App = () => {
  const router = Router();

  const renderApp = () => {
 
...

    const observer = value => {
      if (value) {
        loginModal.classList.remove('hidden');
        overlay.classList.remove('hidden');
        document.body.classList.add('no-scroll');
      } else {
        loginModal.classList.add('hidden');
        overlay.classList.add('hidden');
        document.body.classList.remove('no-scroll');
      }
    };

    addObserver('modalVisible', observer);
    addObserver('overlayVisible', observer);
  };

  const onPopState = () => {
    renderApp();
  };

...

  const app = { renderApp };

  return app;
};

export default App;
  1. Header.js에서 상태 변경 해주기

import './Header.scss';

...
import { state } from '../../../store';

const Header = () => {
 ...
 ...

  profileContainer.addEventListener('click', () => {
    moreInfo.classList.toggle('hidden');
  });

  loginTab.addEventListener('click', () => {
    state.modalVisible = true;
    state.overlayVisible = true;
  });

  signupTab.addEventListener('click', () => {
    state.modalVisible = true;
    state.overlayVisible = true;
  });

  return header;
};

export default Header;
  1. LoginModal.js에서 상태 변경해주기
import './LoginModal.scss';
import { state } from '../../../store';

const LoginModal = () => {
  ...
  window.addEventListener('click', event => {
    if (
      event.target === overlay ||
      event.target.closest('.container-login-modal') === null
    ) {
      state.modalVisible = false;
      state.overlayVisible = false;
    }
  });

  return loginModal;
};

export default LoginModal;
  1. Overlay.js에서 상태 변경해주기
import './Overlay.scss';
import { state } from '../../../store';

const Overlay = () => {
  const app = document.getElementById('app');

  const overlay = document.createElement('div');
  overlay.classList.add('overlay', 'hidden');
  app.appendChild(overlay);

  overlay.addEventListener('click', () => {
    state.modalVisible = false;
    state.overlayVisible = false;
  });

  return overlay;
};

export default Overlay;
profile
프론트엔드 학습 과정을 기록하고 있습니다.

0개의 댓글