바닐라 자바스크립트로도 리액트처럼 코딩할 수 있다? 반응형 패턴으로 선언적 프로그래밍하기

GY·2023년 9월 18일
0
post-thumbnail

자바스크립트로 반응형 패턴을 구현할 수 있습니다.

반응성 (Reactivity)

반응성: 시스템이 데이터의 변화에 반응하는 방식

리액트와 같은 프레임워크를 사용하면 데이터의 변화에 따라 UI를 업데이트 하는 등의 동작을 관리하는 것이 익숙합니다. 같은 기능을 순수 자바스크립트로 구현한다면 어떻게 이와 유사하게 선언적인 프로그래밍을 할 수 있을까요?
바닐라 자바스크립트로 기능을 구현할 때 한번씩 떠올랐던 궁금증입니다.
무작정 '리액트는 선언적인 프로그래밍이 가능해'라는 것만 알고 있다면, 이를 프레임워크 없이 구현할 수 없다면 프론트엔드 개발을 한다면 말이 안되는 것 같기도 하고요.🧐 그래서 예시 하나를 가져와 바닐라 자바스크립트로 리액트처럼 직접 반응성을 구현해보기로 했습니다.

아래 글은 반응형 패턴을 통해 바닐라 자바스크립트로 반응성을 구현하는 방식에 대해 작성한 원문을 번역한 글입니다.

https://ktseo41.github.io/blog/log/patterns-for-reactivity-with-modern-vanilla-javascript.html

크게 요약하자면 다음과 같은 내용을 포함하고 있으니, 관심 있는 분들은 읽어보시면 좋을 것 같습니다.

✅ 바닐라 자바스크립트로 반응성을 구현하는 패턴

  1️⃣ PubSub 패턴 : publish()로 이벤트 발행 + subscribe()해 이벤트를 구독
    ▪️ CustomEvent, window.dispatchEvent 사용가능

  2️⃣ Observer 패턴: 특정 주체를 구독 > 주체가 notify메서드를 실행하면, 주체를 구독한 모든 것에게 알림
    ▪️ PubSub 패턴과의 차이점: 주체가 옵저버에 대해 알고 있으며 이를 제거할 수 있음

  3️⃣ Proxy를 활용한 반응형 객체 프로퍼티
  4️⃣ 반응형 시스템: 옵저버블, Signals 등
  5️⃣ UI 반응형 렌더링
  7️⃣ 그 외 다양한 방법들


위의 글에서 다루는 패턴 중, Observer 패턴을 사용해 바닐라 자바스크립트로 기능을 구현해보겠습니다.

Observer 패턴 적용해보기

예시로 사이트에 공개되어 있는 프로그래머스 과제 테스트 ‘영화 예매 페이지 만들기’를 가져왔습니다.

📍 이 과제를 풀어보면서 전부 반응형 패턴을 사용해 코드를 작성해보았지만, 이 글에서는 인원 선택에 따라 예약 가능 좌석이 변경되는 기능에 대해서만 다루겠습니다.

📍 세세한 구현사항이 아닌, 반응형 패턴으로 선택한 인원데이터의 변경에 따라 선택가능한 좌석 상태를 어떻게 변경하도록 코드를 작성하는지에 대해 정리했습니다.


Subject 클래스 생성

먼저 최상위 subject 클래스를 선언합니다.

export default class ObserverSubject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }

  notify(status) {
    this.observers.forEach((observer) => observer.update(status));
  }
}
  • addObserver,removeObserver로 해당 Subject를 관찰하는 여러 observer들을 추가/삭제합니다.
  • notify로 등록된 observer들의 update 메서드를 실행합니다.

이 때 observer에는 모두 update 메서드가 선언되어 있어야 합니다.
이 notify가 사실 패턴의 핵심인데, Subject인스턴스가 데이터를 변경하는 함수 내부에 Notify실행문을 넣으면 자연스레 해당 데이터가 변경될 때마다 각 observer의 update함수를 실행해 원하는 액션을 할 수 있고, 이것이 데이터의 변경에 따라 로직을 처리할 수 있는 원리입니다.


Observer 클래스 생성

observer는 이렇게 구성되어 있습니다.
위에서 언급했듯 동일하게 update 메서드를 가지고 있습니다.

export class observer {
	//...
  update(data) {
    //...
  }
}

Subject 클래스를 상속받은 인원 선택 클래스 만들기

인원 데이터 변경에 따라 좌석 상태가 바뀌어야 하므로, 먼저 관찰할 인원데이터를 관리하는 클래스를 만들어주어야 합니다.
따라서 Subject 클래스를 상속받은 numOfPeopleSubject를 만들어줍니다.
NumOfPeopleSubject는 ObserverSubject 클래스를 상속받습니다. 선택한 인원수에 대한 데이터를 갖고 있고, 이것이 변화할 때 이 클래스를 관찰하고 있는 각각의 observer들에게 상속받은 상위 클래스의 notify메서드를 사용해 변경된 데이터와 함께 이 사실을 알려줍니다.

export class NumOfPeopleSubject extends ObserverSubject {
  constructor() {
    super();
    this.numOfPeople = {
      adult: 0,
      youth: 0,
    };

  updateNumSelection(age, count) {
    this.#handleChangeNumOfPeople(age, count);
    this.numOfPeople[age] = count;
    super.notify({ numOfPeople: this.numOfPeople });
  }
}

인원 데이터 변경을 관찰하는 seatObserver 만들기

seatObserver를 만들어보겠습니다.
seatObserver는 update메서드 안에 인자로 받은 인원수 데이터로 선택가능한 좌석 ui를 변경해주는 메서드를 호출합니다.

export class SeatObserver {
  update({ numOfPeople }) {
    this.#handleSeatSelectEnable(numOfPeople);
  }

observer 등록해 관찰시작하기

좌석을 담당하는 seatObserver가 인원을 담당하는 numOfPeopleSubject를 관찰하도록 등록해줍니다.

const numOfPeopleSubject = new NumOfPeopleSubject();
const seatObserver = new SeatObserver();

numOfPeopleSubject.addObserver(seatObserver);

이제 addObserver로 인스턴스 내 observers 프로퍼티에 넣어진 인스턴스의 update메서드가, notify()메서드가 호출될 때마다 실행됩니다.

export default class ObserverSubject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  notify(status) {
    this.observers.forEach((observer) => observer.update(status));
  }
}

인원 데이터 변경하기

이제 인원수 버튼을 클릭해 선택할 때 해당 subject인스턴스의 인원수 데이터를 변경하는 함수를 호출하겠습니다.

export const addSelectNumOfPeopleHandler = () => {
  youthBtnSection.addEventListener("click", (e) => {
	//...
    numOfPeopleSubject.updateNumSelection("youth", parseInt(target.innerText));
  });

  adultBtnSection.addEventListener("click", (e) => {
	//...
    numOfPeopleSubject.updateNumSelection("adult", parseInt(target.innerText));
  });
};

updateNumSelection 함수를 호출하면 인스턴스의 인원수 데이터를 변경한 다음, notify메서드로 seatObserver의 update메서드를 호출합니다.
이 때 SeatObserver 인스턴스는 전달받은 NumOfPeople인자로 변경된 인원수 데이터를 받아 선택가능한 좌석 상탯값을 변경합니다.

export class SeatObserver {
  update({ numOfPeople }) {
    this.#handleSeatSelectEnable(numOfPeople);
  }

그러면 이렇게, 인원선택에 따른 좌석 선택 ui변경을 구현할 수 있게 됩니다


정리

이번 글에서는...

바닐라 자바스크립트로 리액트처럼 선언적인 프로그래밍을 하는 방법에 대해 알아보았습니다. 데이터에 따라 로직을 처리할 수 있도록 하는 반응형 패턴에 대해 알아보고, 이 중 Observer 패턴을 적용해보았습니다.
이런 디자인 패턴을 사용하면 점점 복잡한 구현도 선언적인 프로그래밍이 가능하기 때문에 사이드 이펙트를 최소화하고 깔끔하게 프로그래밍할 수 있습니다.

다음 글에서는...

이렇게 반응형 패턴을 사용해 선언적인 프로그래밍을 하게 되면 여러 데이터의 변경에 따라 구현해야 하는 사항이 복잡해질 때 장점이 더 빛을 발하는데요,
인원 데이터에 따라 티켓가격도 계산해주어야 하고, 초기화 버튼을 누르면 선택한 인원과 좌석, 티켓가격 데이터가 모두 초기화되어야 합니다.
뿐만 아니라 이미 선택한 좌석이 있을 경우 인원을 변경하는 케이스에 대해서도 처리해주어야 합니다.

오늘은 일부분 구현에 대해서 예시로 들어 글을 써보았는데, 이후 여건이 되면 이러한 부분들까지 고려했을 때 반응형 패턴을 통해 어떻게 장점을 가져갈 수 있는지 이어 글을 써보겠습니다.

profile
Why?에서 시작해 How를 찾는 과정을 좋아합니다. 그 고민과 성장의 과정을 꾸준히 기록하고자 합니다.

0개의 댓글