본문 바로가기

공부/react

useSyncExternalStore 훅 알아보기

반응형

 

useSyncExternalStore 📖

`useSyncExternalStore`는 외부 상태 store를 React 컴포넌트와 동기화해 concurrent reading(동시 읽기)를 지원할 수 있도록 하는 React Hook입니다.

 

쉽게 설명하면

react가 아닌 외부 저장소 (ex. 바닐라 자바스크립트) 값을 읽어드려 외부 저장소 값의 변화를 추적하고, 동시성 상태 변화에 대응할 수 있습니다.

 

기본 사용법

useSyncExternalStore은 세 가지 인자를 받아서 사용합니다.

import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';

function TodosApp() {
  const todos = useSyncExternalStore(
    todosStore.subscribe, 
    todosStore.getSnapshot, 
    todosStore.getServerSnapshot
  );
  // ...
}

 

`subscribe`: 하나의 `callback`인수를 받아 외부 저장소의 변화를 구독하는 함수입니다. 상태가 변할 때마다 인자로 받아온 `callback`을 호출합니다. 

 

`getSnapshot`: 현재 상태의 스냅샷을 반환하는 함수입니다. 컴포넌트가 상태를 필요로 할 때 호출됩니다.

 

`getServerSnapshot (옵션)`: 서버 사이드 렌더링 환경에서 사용되는 스냅샷을 반환하는 함수입니다.

 

타입

useSyncExternalStore의 `Type`을 확인해 봤습니다.

export function useSyncExternalStore<Snapshot>(
  subscribe: (onStoreChange: () => void) => () => void,
  getSnapshot: () => Snapshot,
  getServerSnapshot?: () => Snapshot,
): Snapshot;

 

subscribe는 `onStoreChange`함수를 인자로 받습니다. `onStoreChange`함수가 실행되면 변경된 상태를 렌더링 시켜주는 함수입니다.

 

쉽게 말하면

이 함수를 외부 저장소에 등록하고, 외부 저장소의 상태가 변화했을 때 `onStoreChange`함수를 실행시켜 주면 리액트에서 렌더링을 시켜준다는 말입니다.

 

사용 방법 📖

외부 저장소 만들기

간단하게 작성한 외부 저장소입니다.

function externalStore(initialState) {
  let state = initialState;
  const callbacks = new Set();

  function subscribe(callback) {
    callbacks.add(callback);
    return () => callbacks.delete(callback);
  }

  function getState() {
    return state;
  }

  function setState(update) {
    state = typeof update === 'function' ? update(state) : update;
    callbacks.forEach((callback) => callback());
  }

  return {
    getState,
    setState,
    subscribe,
  };
}

 

externalStore는 `initialState` 값을 받아와 state 변수에 저장합니다.

function externalStore(initialState) {
  let state = initialState;
  ...
}

 

subscribe는 외부에서 `callback`함수를 받아와 callbacks변수에 저장하고 `callback`을 지우는 함수를 반환합니다.(구독 취소)

function externalStore(initialState) {
  let state = initialState;
  const callbacks = new Set();

  function subscribe(callback) {
    callbacks.add(callback);
    return () => callbacks.delete(callback);
  }
}

 

getState함수는 현재 상태를 반환하는 함수입니다.

function getState() {
  return state;
}

 

setState함수는 매개변수로 함수 또는 값을 받아와 현재 상태를 변경하는 함수입니다.

function setState(update) {
  state = typeof update === 'function' ? update(state) : update;
  callbacks.forEach((callback) => callback()); // 변화를 알림
}

 

useStore

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

 

useStore에 인자로 위에 생성한 외부 저장소 값이 들어가고, `useSyncExternalStore`에는 외부 저장소의 `subscribe``getState`를 넣어줍니다. 이렇게 간단한 전역 상태 라이브러리를 만들게 되었습니다.

 

todoList 만들기

위에서 만든 전역 상태 라이브러리를 이용해 간단한 todoList를 만들어보겠습니다.

const todoStore = externalStore([]);

export const useTodos = () => {
  const [todos, setTodos] = useStore(todoStore)

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

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

  return {
    todos,
    addTodo,
    deleteTodo
  }
}

 

`useTodos` hook에 todo 전역 상태와, todo 상태를 변경시켜 주는 `addTodo``deleteTodo`를 만들었습니다.

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

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

 

위에서 만든 externalStore를 사용해 `todoStore`를 생성하고, `useStore`hook에 넣어줘 useSyncExternalStore와 연결시켜 주었습니다.

const todoStore = externalStore([]);

export const useTodos = () => {
  const [todos, setTodos] = useStore(todoStore)
  ...
}

 

todoList UI 만들기

간단한 UI를 만들어 다른 컴포넌트에서 하나의 전역 상태를 관리할 수 있게 만들었습니다.

import { useState } from "react";
import { useTodos } from "./useTodoStore";

export const Todos = () => {
  const { todos } = useTodos();

  return (
    <div>
      {todos.map(todo => (
        <div key={todo.id}>{todo.title}</div>
      ))}
      <AddTodo />
    </div>
  );
};

export const AddTodo = () => {
  const { addTodo } = useTodos();
  const [value, setValue] = useState('');

  const onChange = (e) => {
    setValue(e.target.value);
  };

  const onAddTodo = () => {
    addTodo({
      id: Math.random(),
      title: value,
    });
  };

  return (
    <div>
      <input value={value} onChange={onChange} />
      <button onClick={onAddTodo}>추가</button>
    </div>
  );
};

 


출처 🏷️

https://ko.react.dev/reference/react/useSyncExternalStore#subscribing-to-a-browser-api

https://ted-projects.com/react-use-sync-external-store

https://doiler.tistory.com/85

반응형