JS Signal

발생

프론트 작업에 이제는 거의 필수가 된 React는 상태 변화 시 컴포넌트를 리렌더링하여 최신화 시킨다. 이러한 컴포넌트 단위 리렌더는 이제는 모두가 알정도로 불필요한 연산을 불러오기 쉽다. 이러한 불필요한 동작을 막기위해 memoization 등 여러 방법을 동원하지만 이는 React의 단순한 선언적 UI 철학에서 점점 멀어지게 만드는 요소가 되었다.

사람들은 복잡한 리렌더링 방지 코드를 작성하다. 다른 방법을 당연하게도 찾기 시작했고 세상에는 그런 이들이 찾던 불필요한 연산 제어를 신경쓰지 않고 상태 관리가 가능한 Signal 이라는 개념을 누군가 만들어두었다.

이 글에서는 Signal을 React 환경에서 설명한다.

Signal ?

Signal 은 값과 그 값이 변할 때 반응하는 시스템을 표현하는 객체이다.

Signal은 상태를 추적하고 그 값이 바뀌면 자동으로 관련 코드가 재실행되도록 하는 매커니즘이다.

React의 컴포넌트 단위 리렌더링과 달리 Signal은 정확히 변경된 필드만 최신화 할 수 있다.

e.g.,

const count = signal(0);

function Counter() {
  return <div>{count.value}</div>;
}

위 예시 코드를 보면 signal이 UI 렌더 함수 외부에 존재하는 걸 확인 할 수 있다. 이는 Signal의 또 다른 특징 중 하나이다.

만약 count의 value가 변경된다면 Counter 전체를 호출하지 않고 정확히 count의 vlaue를 가지고 있는 div의 텍스트 노드만 교체된다.

구조

Signal은 Observer Pattern을 기반으로 만들어진 패턴이다.

class Signal<T> {
  private value: T;
  private listeners = new Set<() => void>();

  constructor(initial: T) {
    this.value = initial;
  }

  get() { return this.value; }
  set(v: T) {
    this.value = v;
    this.listeners.forEach(l => l()); // 모든 구독자에게 알림
  }

  subscribe(fn: () => void) {
    this.listeners.add(fn);
    return () => this.listeners.delete(fn);
  }
}

위 코드는 간략화한 옵저버 패턴 형식의 Signal 구조이다. 해당 구조에서 참조 그래프와 반응형 로직을 구현한다면 실제 Signal의 구조가 된다.

즉 Signal은 Observer 패턴을 기반으로한 Dependency Graph 기반 Reactive Programming 패턴이라고 할 수 있다.

여러 패턴이 모여 만들어진 Signal은 이러한 구독자들에게 상태의 변화를 전파하는 과정을 통해 리액트 렌더 사이클에 포함되지 않고도 상태 전달이 가능한 것이다.

의문

Signal에 대해 알아보며 느끼게 된 의문이 몇가지 존재한다.

  1. 기존 React 상태 환경을 수정하면 Signal과 같이 실제 변경이 진행된 필드만 수정 할 수 있을거 같은데 왜 React 팀은 그러지 않았을까?
  2. 기존 Flux 패턴의 Zustand, Redux, Recoil 등 과는 어떤 차이점이 존재하는걸까?
  3. Signal은 React 환경에서 실제로 어떻게 동작할까?

결론

오늘은 글이 길어지는 관계로 이만하겠다. 나는 의문을 해소하였고 추후에 글로 작성 할 예정이다.
만약 이 글을 보는 사람이 생긴다면 직접 의문을 해소해보는걸 추천한다.

Day-49


tech