2차 공부/TIL

24.08.27 react의 useSyncExternalStore 훅 알아보기

공대탈출 2024. 8. 27. 21:41

useSyncExternalStore Hook은 외부 Store을 구독할 수 있는 React Hook입니다.

컴포넌트의 최상위 레벨에서 해당 훅을 호출하여 외부 데이터 저장소에서 값을 읽습니다.

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

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

subscribe함수는 해당 store을 구독하고, 구독을 취소하는 함수를 반환하여야 합니다.

getSnapshot함수는 store에서 데이터의 스냅샷을 읽어야 합니다.

리덕스에서 자동으로 해주는 subscription을 수동으로 해줘야 한다고 생각하였습니다.

 

주의사항

  • 여기서 getSnapshot이 반환하는 store 스냅샷은 불변성을 지켜야 합니다. 기본 스토어에 변경 가능 데이터가 변경된 경우 변경 불가능한 새 스냅샷을 반환합니다. 그렇지 않으면 캐시된 마지막 스냅샷을 반환합니다.
  • 리렌더링하는 동안 다른 subscribe함수가 전달되면 React는 새로 전달된 subscribe함수를 사용하여 저장소를 다시 구독합니다. 컴포넌트 외부에서 subscribe를 선언하면 이를 방지할 수 있습니다.

 

import { useSyncExternalStore } from "react";

let nextId = 0;
let todos = [{ id: nextId++, text: "Todo #1" }];
let listeners = [];

const todosStore = {
    addTodo() {
        todos = [...todos, { id: nextId++, text: "Todo #" + nextId }];
        emitChange();
    },
    subscribe(listener) {
        listeners = [...listeners, listener];
        console.log("listeners :>> ", listeners);
        return () => {
            listeners = listeners.filter((l) => l !== listener);
        };
    },
    getSnapshot() {
        return todos;
    },
};

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

export const useTodoStore = () => {
    const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
    return { todos, todosStore };
};
import { useTodoStore } from "../hooks/useTodoStroe";

const TodoList = () => {
    const { todos, todosStore } = useTodoStore();
    return (
        <>
            <button onClick={() => todosStore.addTodo()}>Add todo</button>
            <hr />
            <ul>
                {todos.map((todo) => (
                    <li key={todo.id}>{todo.text}</li>
                ))}
            </ul>
        </>
    );
};

export default TodoList;

순서대로 어떻게 코드가 동작하는지 살펴보자.

1. 첫 렌더링 > useTodoStore 실행

2. useSyncExternalStore으로 todos를 받아오고, todos와 todosStore을 반환하여 변수에 저장함

3. todosStore.subscribe가 실행되어 해당 컴포넌트가 listeners배열에 추가됨

4. todosStore.getSnapshot가 실행되어 todos에 할당됨

5. todosStore.addTodo()를 버튼에 달아주고, todos를 li태그 요소로 만들어줌

6. 버튼클릭 > addTodo()실행

7. store의 todos에 새로운 객체추가하여 저장

8. emitChange실행 > listeners배열의 listener함수가 모두 실행됨

const listener = () => {
	triggerTargetComponentRerender()
}

listener의 실제 코드는 보지 못했지만, 각 listener함수는 연결된 해당 컴포넌트의 리렌더링을 유발시키는 함수가 내장되어있어, 각 listener가 실행되면 타겟컴포넌트가 리렌더링됨

 

9. 구독이 되어있기때문에 변경된 snapshot을 컴포넌트로 보내줌

10. 컴포넌트에 변경된 state가 화면에 뿌려지도록 리렌더링됨

 

useSyncExternalStore훅 문서를 읽으며 느낀점은 redux같은 store지원 라이브러리가 많은것을 해준다는 것이다.

또한 아직 완벽하게 이해한것같지 않아 내일 세션을 들으며 틀린부분이있거나 새롭게 알게되는 점이 있다면 재정리를 해봐야할것같다.