React 밖 상태를 useSyncExternalStore로 구독하기

class ToastManager extends EventTarget {
  get toasts() {
    return this._toasts // 새로 만들지 않는다 — 같은 참조면 안 바뀐 것
  }
  set toasts(value) {
    this._toasts = [...value] // 새 참조 = "바뀌었다"는 신호
    this.dispatchEvent(new Event('changed'))
  }
  // add·remove … 는 전부 이 setter 를 거친다 — 변경과 발행이 한 곳이라 발행 누락이 없다
}
const subscribe = (cb) => {
  toastManager.addEventListener('changed', cb)
  return () => toastManager.removeEventListener('changed', cb)
}

// useEffect + useState 로 손으로 복사하던 동기화를 React 가 맡는다.
// changed 마다 getSnapshot 을 불러 참조를 비교하고, 다를 때만 리렌더한다.
const toasts = useSyncExternalStore(subscribe, () => toastManager.toasts)

어디서든 add 를 부르면 setter 가 새 배열을 만들고 changed 를 발행한다. React 는 getSnapshot 으로 다시 읽어 이전 값과 Object.is 로 비교해 다르면 리렌더, 같으면 건너뛴다. getter 는 내부 배열을 그대로 돌려주므로 안 바뀌면 같은 참조다. getSnapshot 이 매번 새 배열을 만들면 항상 달라 무한 루프가 된다.


#563