til

useSyncExternalStore사용하기

fpzmfks 2024. 8. 28. 21:38

어제 오늘은 useSyncExternalStore을 사용해보고 그에 대한 챌린지반 강의를 들었다.

 

useSyncExternalStore란 useState나 contextAPI처럼 상태를 관리하는 기능으로, 그 중에서도 외부 API를 구독하는 데에 특화되어 있는 hook이다. 또다른 이 훅의 특징은  React Concurrent Feature라는 '동시성'을 활용한다는 것이다. 

 

언급한 외부 API 구독과 동시성은 이 hook에서 서로 긴밀하게 연결되어 있다. 

 

우선 외부 API를 구독하여 데이터를 받아오면 딜레이가 발생하기 쉽다. 이러한 딜레이를 방지하기 위해 쓰로틀링(실행 횟수를 줄임)과 디바운스(가장 마지막 요청이 실행됨)를 사용하여 의도적 지연을 일으켰지만 이러한 방식은 결국 일정 시간 기다려야 한다는 것은 마찬가지라는 단점이 있었다. 

 

하지만 '동시성'을 활용하면 이러한 단점을 일부 상쇄할 수 있게 되는데, 간단하게 말하자면 실행 순서를 앞당겨올 수 있게 되어 딜레이를 온전히 기다리지 않고도 다른 기능이 작동될 수 있게 된 것이다. 즉 '동시 작업'이 가능하다.

 

이러한 동시 작업은 병렬 작업과는 다른데, 병렬 작업은 2명이 2가지 일을 하는 거라면 동시 작업은 1명이 2가지 일을 하고 있는 것이다. 이러한 동시 작업의 장점은 1명이 2가지 일을 할 수 있다는 것뿐만 아니라 일을 하는 도중 1가지 일을 더 이상 할 필요가 없어지면 작업을 중단할 수도 있다는 것이다.(중단뿐만 아니라 data를 출력 중이면 출력중인 data의 update 또한 가능하다)

 

아래는 튜터님이 제시해주신 간단한 useSyncExternalStore 함수이다. 

const externalStore = (initialState) => {
  //초기값 설정
  let state = initialState;
  //callback=자동으로 할당되는 handleStoreChange함수가 들어가고 Set()에 의해 똑같은 함수가 들어가면 하나면 저장됨
  const callbacks = new Set();

  const subscribe = (callback) => {
    // store가 구독되면 자동으로 할당되는 handleStoreChange함수가 callbacks에 저장됨
    callbacks.add(callback);
    // store가 구독 해제되면 실행되는 함수
    return () => callbacks.delete(callback);
  };

  // 상태값 반환
  const getState = () => state;

  // 상태값 업데이트
  const setState = (newState) => {
    // 상태값 업데이트
    state = typeof newState === "function" ? newState(state) : newState;
    //리렌더링 되며 상태값 업데이트 반영
    callbacks.forEach((callback) => callback());
  };

  return {
    getState,
    setState,
    subscribe,
  };
};
const useStore = (store) => {
  const state = useSyncExternalStore(
    store.subscribe,
    store.getState,
    store.getState
  );
  return [state, store.setState];
};

 

이러한 useSyncExternalStore를 활용하여 useState처럼 사용할 수 있고, 커스텀 훅 또한 만들 수 있다. 

const todoStore = externalStore([]);

function useTodos() {
  const [todos, setTodos] = useStore(todoStore);

  function addTodo(todo) {
    setTodos((prev) => [...prev, todo]);
  }

  function deleteTodo(todoId) {
    setTodos((prev) => prev.filter((todo) => todo.id !== todoId));
  }

  return {
    todos,
    addTodo,
    deleteTodo,
  };
}

 

이러한 일련의 과정을 보니 useSyncExternalStore는 외부 api를 구독할 수 있고, 그러면서 동시성이라는 편의성을 가질 수 있다는 것을 제외하면 useState와 아주 유사하다는 것을 알 수 있었다.