옵저버 패턴에 대해 알아보자

다 되면 알려주세요 옵저버 패턴 by ZeroCho, 근데 이제 Tanstack Query를 살짝 곁들인

2024-10-23 · 5분 · 조회 1

@ZeroChoTV님의 다 되면 알려주세요 옵저버 패턴 를 보고 정리한 내용입니다.

옵저버 패턴이란?

옵저버 패턴은 객체의 상태가 변화할 때 그 상태를 구독하고 있는 다른 객체들에게 알림을 보내는 패턴이다.

class A {
  check(b) {
    if (b.finished()) {
      return b.getItem();
    }
    return false;
  }
}
 
class B {
  item = null;
  finished() {
    return !!this.item;
  }
  getItem() {
    return this.item;
  }
}
 
const b = new B();
const a = new A();
while (!a.check(b)) {
  // A는 계속해서 B가 만들어졌는지 확인해야함
  console.log('NOT YET');
}

위와 같이 객체 A가 객체 B의 상태를 계속해서 확인해야 하는 경우가 있다.
옵저버 패턴을 사용하면 '다 되면 알려주세요' 라는 요청을 할 수 있게 한다.

옵저버 패턴 적용하기

class A {
  constructor(id) {
    this.id = id;
  }
  itemDone(item) {
    console.log(item);
  }
}
 
class B {
  item = null;
  // 고객 리스트
  customers = [];
 
  // 고객 등록 받기 -> subscribe
  getOrderFrom(a) {
    this.customers.push(a);
  }
  // 고객 취소 받기 -> unsubscribe
  cancelOrderFrom(a) {
    this.customers = this.customers.filter((v) => v !== a);
  }
  finish(item) {
    this.customers.forEach((c) => c.itemDone(item));
  }
  getItem() {
    return this.item;
  }
}
 
const b = new B();
const a = new A();
b.getOrderFrom(a);
 
// ...
// B가 일을 끝냈을 때
b.finish();
  • while문으로 계속해서 확인하지 않고, B가 일을 끝냈을 때 A에게 알려주는 방식으로 변경할 수 있다.

결론

옵저버 패턴은

  • 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용하며 발행/구독 모델로 알려져있다.
  • 기본적으로 다음과 같은 주요 요소를 가진다.
    1. 발행자(Subject): 상태를 유지하고 변화할 때 옵저버(구독자)들에게 알림을 보내는 객체.
    2. 구독자(Observer): 주제의 상태를 구독하고, 상태가 변화할 때 알림을 받는 객체.
    3. 알림(Notification): 주제의 상태가 변화하면 옵저버들에게 알림을 보내는 메커니즘.

추가: Tanstack Query

FrontEnd 개발에서 자주 사용하는 Tanstack Query도 옵저버 패턴을 사용하고 있다.

Tanstack Query에서는 QueryObserver가 옵저버 패턴의 발행자(Subject) 로서 동작한다.
상태 변화 시 QueryObserver는 등록된 옵저버들에게 알림을 보내는 역할을 한다.

useBaseQuery

Tanstack Query의 Data Fetching Hook이다.

// src/react/query/core/useBaseQuery.ts
export function useBaseQuery<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData
// ...
  const query = useQueryInstance({
    queryKey,
    queryFn,
    config,
    notifyManager,
    queryHash,
    defaultOptions,
    state,
    defaultQueryObserverOptions,
  })
  // ...
  return query
}
  • useBaseQuery에서 useQueryInstance 함수를 호출하는데, 이 함수는 데이터 상태(로딩, 성공, 실패 등)를 관리하는 QueryObserver 인스턴스를 생성한다.

QueryObserver

subscribe, unsubscribe, notify 메서드를 가지고 있는 Observer 역할이다.

// src/react/query/core/QueryObserver.ts
export class QueryObserver<TResult, TError> {
  // ...
  constructor({
    queryKey,
    queryHash,
    observerProps,
    defaultOptions,
    notifyManager,
    state,
    defaultQueryObserverOptions,
  }: QueryObserverOptions<TResult, TError>) {
    // ...
    this.listeners = new Set()
    this.listeners.add(this)
  }
  // ...
  subscribe(listener: QueryObserverListener<TResult, TError>) {
    this.listeners.add(listener)
  }
  unsubscribe(listener: QueryObserverListener<TResult, TError>) {
    this.listeners.delete(listener)
  }
  // ...
  notify() {
    this.listeners.forEach(listener => listener.onQueryUpdate(this.state))
  }
}

Notify 흐름

  1. QueryObserver는 서버에서 데이터를 가져오는 동안 상태를 loading으로 설정하고, 구독자들에게 이 상태를 알림(notify).
  2. 서버에서 응답이 도착하면 상태가 success 또는 error로 변경되고, 다시 구독자들에게 알림(notify).
  3. 구독자(컴포넌트 또는 훅)는 이 상태 변화에 따라 UI를 업데이트하거나 다른 로직을 실행.
Tanstack Query 사용 예시
function MyComponent() {
  const { data, error, isLoading } = useQuery('myKey', fetchMyData);
 
  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error occurred!</p>;
 
  return <div>{data}</div>;
}

Ref.

Home
Posts
Notes