1. useMemo
리렌더링 사이에 연산 값(value)을 캐시할 수 있는 리액트 훅으로, 메모화된(memoized) 값을 계산하는 함수를 호출한다.
import { useMemo } from "react";
const cachedValue = useMemo(calculateValue, dependencies)
1) 사용 예시
리액트 공식 문서에서 설명하고 있는 사용 예시는 다음과 같다.
- Skipping expensive recalculations (비용이 많이 드는 재연산 생략)
- Skipping re-rendering of components (컴포넌트의 리렌더링 생략)
- Memoizing a dependency of another Hook (다른 훅의 의존성 메모화)
- Memoizing a function (함수 메모화)
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
// todos와 tab을 인수로 받는 filterTodos라는 함수의 값을 캐싱
// todos 또는 tab, 즉 의존성 배열의 값이 변경되었을 때만 재연산하고 변경되지 않으면 캐싱한 값을 재사용하여 불필요한 연산을 줄임
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
return (
<div className={theme}>
{ */ 따라서 재연산이 이루어지지 않으면 불필요한 컴포넌트의 리렌더링도 방지할 수 있음 /* }
<List items={visibleTodos} />
</div>
);
}
2) 무한 스크롤 시 사용
- `declare` 키워드는 주로 외부 모듈이나 라이브러리의 타입 선언을 지정하는데 사용된다.
- 따라서 `declare` 키워드는 내부 모듈이나 파일에서 사용하지 않아야 한다.
- 나는 현재 내부 컴포넌트들에서만 Todo와 TodoProps의 타입을 사용하고 있기 때문에 declare로 선언해주면 안되는 것이다.
"use client";
import Link from "next/link";
import { useMemo } from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
import { getPosts } from "@/api/community/post";
import { useInView } from "react-intersection-observer";
import { usePathname } from "next/navigation";
import Image from "next/image";
import { getFirstImage, getImgUrl, removeHtmlTags } from "@/libs/util";
import Loading from "@/app/loading";
import CategoryTag from "../ui/CategoryTag";
import { PostType, ToTalDataType } from "@/types/types";
import { PATHNAME_OHJIWAN, PATHNAME_PRODUCT, PATHNAME_RECIPE, PATHNAME_RESTAURANT } from "@/enums/community";
type QueryKeyMap = {
[key: string]: string[];
};
const GetPosts = () => {
// .. 중간 생략
const {
data: posts,
isLoading,
isError,
hasNextPage,
fetchNextPage,
isFetchingNextPage
} = useInfiniteQuery<ToTalDataType>({
queryKey: queryKey,
queryFn: ({ pageParam }) => getPosts(pathname, pageParam),
getNextPageParam: (lastPage) => {
// 전체 페이지 개수보다 작을 때 다음 페이지로
if (lastPage.page < lastPage.total_pages) {
return lastPage.page + 1;
}
},
cacheTime: 300000,
});
// useMemo: posts가 변하지 않으면 캐싱해둔 이전 값을 재사용
const accumulatePosts = useMemo(() => {
return posts?.pages
.map((page) => {
return page.posts;
})
.flat();
// flat() : 모든 하위 배열 요소를 지정한 깊이까지 재귀적으로 이어붙인 새로운 배열 생성
}, [posts]);
const { ref } = useInView({
threshold: 1,
onChange: (InView) => {
if (!InView || !hasNextPage || isFetchingNextPage) return;
fetchNextPage();
}
})
if (isLoading) return <Loading />;
if (isError) {
console.error("데이터를 불러오는 중에 오류가 발생했습니다:", isError);
return "데이터를 불러오는 중에 오류가 발생했습니다.";
}
return (
<section className="flex flex-col mt-10 mb-20 w-[725px]">
{
<div className="flex flex-col mb-5 justify-center">
<h2 className="text-xl flex mb-8">{getCategoryName(pathname)} 글</h2>
{accumulatePosts?.map((post: PostType) => (
<div
key={post.post_uid}
className="border-t last:border-b flex flex-col justify-between px-4 py-4"
>
{ */ ... 중간 생략 /* }
))}
</div>
}
<div ref={ref} className="w-full h-3" />
</section>
);
};
export default GetPosts;
2. useCallback
리렌더링 사이에 함수 정의를 캐시할 수 있는 리액트 훅으로, 함수를 메모화한다.
import { useMemo } from "react";
const cachedFn = useCallback(fn, dependencies)
1) 사용 예시
리액트 공식 문서에서 설명하고 있는 사용 예시는 다음과 같다.
- Skipping re-rendering of components (컴포넌트 리렌더링 생략)
- Updating state from a memoized callback (메모화 한 콜백함수로 상태 업데이트)
- Preventing an Effect from firing too often (이펙트가 너무 자주 실행되는 것을 방지)
- Optimizing a custom Hook (커스텀 훅 최적화)
Preventing an Effect from firing too often의 예시
// useCallback을 활용한 예시
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ Only changes when createOptions changes
// ...
// useCallback을 사용하지 않아 비효율적인 예시
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
function createOptions() {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🔴 Problem: This dependency changes on every render
// ...
'✍️ What I Learned > TIL' 카테고리의 다른 글
[TIL] React 챗봇 만들기2 - redux-toolkit으로 챗봇 on/off여부 전역적인 상태관리 (0) | 2023.08.08 |
---|---|
[TIL] React 챗봇 만들기1 - OpenAI api 세팅 및 사용 방법 (ChatGPT) (0) | 2023.08.07 |
[TIL] 온프레미스(On-premise) vs. 클라우드(Cloud), AWS 소개, AWS의 주요 서비스 (0) | 2023.08.02 |
[TIL] Redux toolkit - createAction, createReducer, configureStore, createSlice (0) | 2023.08.01 |
[TIL] 중첩 라우팅(Nested Routes) (feat. React-Router-DOM v6) (0) | 2023.07.31 |