본문 바로가기

공부/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에는 외부 저장소의 subscribegetState를 넣어줍니다. 이렇게 간단한 전역 상태 라이브러리를 만들게 되었습니다.

 

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 상태를 변경시켜 주는 addTododeleteTodo를 만들었습니다.

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

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

 

위에서 만든 externalStore를 사용해 todoStore를 생성하고, useStorehook에 넣어줘 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

반응형