어제의 나보다 성장한 오늘의 나

[React] useSyncExternalStore 사용 방법과 사용 이유 본문

React

[React] useSyncExternalStore 사용 방법과 사용 이유

today_me 2024. 3. 14. 00:46
반응형

 

 

 

리액트 공식 문서에 따르면 useSyncExternalStore는 '외부 스토어를 구독할 수 있는 React 훅' 이라고 나와있습니다.

 

 


 

 

외부 스토어가 뭘까요??



반대로 외부 스토어가 아닌 경우에 대해서 먼저 생각해보면 이해하기가 수월합니다.

보통 우리는 구독할 데이터를 useState나 useContext 등의 리액트 훅을 이용하여 state에 저장 하고 사용합니다. setState로 값을 수정하면 자동으로 리렌더링이 일어나죠. 하지만 이것들은 리액트 내부에서 사용될 때만 가능합니다. 리액트 훅이니까요.

 

반대로 리액트 내부에서 이용하지 않는 경우는 뭐가 있을 까요??

class를 사용하거나 function 으로 만들어 외부 저장소를 만드는 경우를 들 수 있습니다.

 


counter.js 클래스 형태

class Counter {
  constructor() {
    this.count = 0;
  }

  increment() {
    this.count += 1;
  }

  decrement() {
    this.count -= 1;
  }
}

 

 

 

counter.js 함수 형태

function createCounter() {
  let count = 0;

  function increment() {
    count += 1;
  }

  function decrement() {
    count -= 1;
  }

  return { increment, decrement };
}

 

 

이런식으로 만들 수 있습니다.

사용 방식은 인스턴스를 생성해서 메서드를 호출하는 식으로 되겠죠.

 

 

이렇게 '데이터가 외부'에서 관리되고 있을 때 '리액트 내부에서 구독'하려고 하면 어떻게 해야할까요??

이 때 useSyncExternalStore 리액트 훅을 통해 쉽게 구독할 수 있습니다.

 

 

class CounterStore {
  constructor() {
    this.count = 0;
    this.listeners = [];
  }

  increment() {
    this.count++;
    this.emitChange();
  }

  decrement() {
    this.count--;
    this.emitChange();
  }

  subscribe(listener) {
    listeners = [...listeners, listener];
    return () => {
      listeners = listeners.filter(l => l !== listener);
    };
  },

  getSnapshot() {
    return this.count;
  }

  emitChange() {
    for (let listener of listeners) {
    	listener();
 	}
  }
}

// 싱글톤 인스턴스 생성
export const counterStore = new CounterStore();

 

 

import React from 'react';
import { useSyncExternalStore } from 'react';
import { counterStore } from './path/to/CounterStore';

function CounterComponent() {
  // useSyncExternalStore로 카운터의 상태를 구독
  const count = useSyncExternalStore(
    counterStore.subscribe,
    counterStore.getSnapshot
  );

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => counterStore.increment()}>Increment</button>
      <button onClick={() => counterStore.decrement()}>Decrement</button>
    </div>
  );
}

export default CounterComponent;

 

 

리스너들을 관리할 리스트를 추가하고 구독 (subscribe) 과 값 반환(getSnapshot) 메서드를 이용하여 구독을 진행합니다. 변화 알림 (emitChange) 메서드를 만들고 변화를 알려야 하는 곳에 사용합니다. 위에서는 카운터가 증가하거나 감소하였을 때 변화를 리스너들에게 알릴 수 있도록 emitChange를 호출하게 하였습니다. 필요한 곳에 emitChange를 호출하도록 커스텀 하시면 됩니다.

 

 

 

사용 중에 회고를 해보았다.

그냥 진작에 리액트 내부에서 state로 관리하면 되는거 아닌가??
쓰는 게 맞는 선택이었나??

 

 

 

일단 회사의 요구 사항이 다음과 같았다.

 

1. 프론트엔드와 백엔드 모두에서 접근할 수 있는 형태 ( 리액트 , node.js 를 pnpm으로 한 워크 스페이스에서 관리 중 이었음 )

2. 클래스를 사용하여 관련 변수들을 클래스 프로퍼티로 관리하고 다양한 연산 과정을 클래스 메서드로 관리할 수 있도록.

 

이 두 가지 요구 사항을 충족 하기 위해 클래스를 사용하였고 클래스는 리액트의 외부 스토어로 관리 되기 때문에 useSyncExternalStore가 적절한 선택이었다고 스스로 결론을 내렸다.

반응형
Comments