tanstackquery, Zustand, Throttling & Debouncing

정소현·2024년 9월 6일
0

React

목록 보기
11/12
post-custom-banner

tanstackquery, Zustand, Throttling & Debouncing

01. Query Cancellation

다운로드 UI가 있을 때 또는 UX 저해시키는 불필요한 네트워크 요청을 제거하기 위해 사용
대용량 fetching을 중간에 취소하거나 사용하지 않는 컴포넌트에서 fetching이 진행 중이면 자동으로 취소시켜 불필요한 네트워크 비용을 줄일 수 있다.

사용방법

export const getTodos = async (queryFnContext) => {
  const { queryKey, pageParam, signal, meta } = queryFnContext;
	// queryKey: 배열형태의 쿼리키
	// pageParam: useInfiniteQuery 사용 시 getNextPageParam 실행 시 적용
	// signal: AbortSignal 을 의미 (네트워크 요청을 중간에 중단시킬 수 있는 장치)
	// meta: query에 대한 정보를 추가적으로 메모를 남길 수 있는 string 필드

  const response = await axios.get("http://localhost:5000/todos", { signal });
  return response.data;
};

useQuery({
  queryKey: ["todos"],
  queryFn: getTodos,
})
// example: <div onClick={(event) => {}}

02. Optimistic Updates

: 서버 요청이 정상적으로 잘 될거란 가정하에 UI 변경을 먼저하고, 서버 요청 하는 방식. 혹시라도 서버 요청이 실패하는 경우, UI 를 원상복구(revert / roll back)

03. Prefetching

: 페이지 이동 전에 이동할 페이지의 쿼리를 백그라운드에서 미리 호출 (prefetching) 캐시 데이터가 있는 상태로 해당 페이지로 이동 시 로딩없이 바로 UI를 볼 수 있다.

04. Paginated / Lagged Queries

: 다른 페이지 클릭 시 매번 Loading UI를 보여주기보다는 기존 UI를 유지하다가 서버로부터 새로운 데이터를 받아왔을 때 바꾸는 방식을 적용할 수 있습니다.

useQuery의 옵션 중 keepPreviousData를 true 로 바꾸면 이전 캐시데이터를 기반으로 isLoading 여부를 판단하게 함

05. Infinite Queries

: Data Fetching 이 일어날 때 마다 기존 리스트 데이터에 Fetched Data 를 추가하고자 할 때 유용하게 사용할 수 있는 hook
더보기 UI 또는 무한스크롤 UI 에 사용하기에 적합

const fetchProjects = async ({ pageParam = 0 }) => {
    const res = await fetch('/api/projects?cursor=' + pageParam)
    return res.json()
  }

  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery({
    queryKey: ['projects'],
    queryFn: fetchProjects,
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  })

Observer (관찰자) (무한스크롤)

Intersection Observer 요소의 가시성 관찰
threshold 0.3 머리의 0.3만큼만 닿으면 새로 데이터를 불러옴
threshold 1 해당 네모박스 맨 끝까지 영역이 닿아야 다음 데이터를 불러오겠다.


🌟Zustand

: 상태관리 본연의 기능에 집중하여 복잡성을 줄이고 보다 간단하고 직관적인 상태관리 기능을 제공

yarn add zustand

zustand와 Redux의 장점과 단점

  • Zustand
    - 장점 : 간단하고 빠르며, 설정이 매우 쉬움
    • 단점 : 상태가 커지면 관리가 어려울수 있음
  • Redux ToolKit
    - 장점 : 구조화된 방법을 통해 대규모 애플리케이션에서도 관리가 용이
    • 단점 : 설정이 복잡하고, 학습 곡선이 가파름
// bearStore.js
import { create } from "zustand";
const useBearsStore = create(function (set) {
  return {
    bears: 0,
    increase: () => {
      set((state) => {
        return {
          bears: state.bears + 1,
        };
      });
    },
    init: () => {
      set({
        bears: 0,
      });
    },
  };
});

export default useBearsStore;
// app.jsx
import React from "react";
import useBearsStore from "./zustand/bearsStore";

const App = () => {
  // const bears = useBearsStore((state) => {
  //   return state.bears;
  // });

  // const increase = useBearsStore((state) => {
  //   return state.increase;
  // });
  const { bears, increase, init } = useBearsStore((state) => state);
  return (
    <div>
      <h2>Zustand</h2>
      <h4>{bears}</h4>
      <button onClick={increase}>+1</button>
      <button onClick={init}>초기화</button>
    </div>
  );
};

export default App;

불변성을 유지하며 데이터를 넣는 immer

: Immer는 JavaScript에서 상태를 쉽게 변경할 수 있게 해주는 라이브러리
원본 데이터를 변경하지 않고도 마치 직접 수정하는 것처럼 코드를 작성할 수 있으며, Immer가 자동으로 불변성을 유지한 새 상태를 만들어줌

yarn add immer
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";

const useTodosStore = create(
  immer((set) => ({
    todos: [
      {
        id: 1,
        title: "Learn Zustand",
        tasks: [{ id: 1, task: "Read documentation", done: false }],
      },
    ],
    addTask: (todoId, newTask) =>
      set((state) => {
        const todo = state.todos.find((todo) => todo.id === todoId);
        if (todo) {
          todo.tasks.push(newTask); // 불변성 유지: immer가 자동으로 처리
        }
        // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
      }),
    toggleTask: (todoId, taskId) =>
      set((state) => {
        const todo = state.todos.find((todo) => todo.id === todoId);
        if (todo) {
          const task = todo.tasks.find((task) => task.id === taskId);
          if (task) {
            task.done = !task.done; // 불변성 유지: immer가 자동으로 처리
          }
        }
        // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
      }),
  }))
);

export default useTodosStore;

유용한 패턴

  • 선택적 상태 구독
  • 미들웨어 사용(persist, devtools)
    persist 사용으로 새로고침 하더라도 데이터 남아있도록 유지가능
    localStorage => session Storage 변경 가능
import { produce } from "immer";
import { create } from "zustand";
import { persist } from "zustand/middleware";

const useTodosStore = create(
  persist(
    (set) => ({
      todos: [
        {
          id: 1,
          title: "Learn Zustand",
          tasks: [{ id: 1, task: "Read documentation", done: false }],
        },
      ],
      addTask: (todoId, newTask) =>
        set(
          produce((state) => {
            const todo = state.todos.find((todo) => todo.id === todoId);
            if (todo) {
              todo.tasks.push(newTask); // 불변성 깨짐: 직접 수정
            }
            // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
          })
        ),
      toggleTask: (todoId, taskId) =>
        set(
          produce((state) => {
            const todo = state.todos.find((todo) => todo.id === todoId);
            if (todo) {
              const task = todo.tasks.find((task) => task.id === taskId);
              if (task) {
                task.done = !task.done; // 불변성 깨짐: 직접 수정
              }
            }
            // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
          })
        ),
    }),
    {
      name: "todos-storage", // 저장소 이름을 설정해요!
      // getStorage: () => sessionStorage, // localStorage가 아닌 곳에 저정하고 싶다면!
    }
  )
);

export default useTodosStore;

🌼 Throttling & Debouncing

☘️ Throttling

: 짧은 시간 간격으로 연속해서 ㅂ라생한 이벤트들을 일정시간 단위로 그룹화하여 처음 또는 마지막 이벤트 핸들러만 호출되도록 하는 것 주로 무한 스크롤에서 사용

- Leading Edge

ex) 사용자가 스크롤을 시작할 때 처음에만 API호출이 이루어지고, 일정 시간 동안 추가 호출이 무시

- Trailing Edge

ex) 사용자가 ㅇ비력 필드에 타이핑을 멈춘 후 일정 시간이 지나야 서버에 검색 요청이 이루어짐

- Leading & Trailing Edge

ex) 여러번 클릭할 때 처음 클릭시 바로 API호출, 주어진 시간의 마지막 이벤트에도 API 호출이 이루어짐

☘️ Debouncing

: 짧은 시간 간격으로 연속해서 이벤트가 발생하면 이벤트 핸들러를 호출하지 않다가 마지막 이벤트로부터 일정시간이 경과한 후에 한 번만 호출하도록 하는 것
주로 입력값 실시간 검색, 화면 resize 이벤트에서 사용

메모리 누수 (Memory Leak)

: 필요하지 않은 메모리를 계속 점유하고 있는 현상


post-custom-banner

0개의 댓글