현재의 이미지 업로드 방식의 비효율적인 부분은
1. 이미지 찌꺼기가 남는다
2. 이미지 미리보기가 느리다.
const startPromise = async () => {
console.time("=== 개별 Promise 각각 ===");
const result1 = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공");
}, 2000);
});
const result2 = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공");
}, 3000);
});
const result3 = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공");
}, 1000);
});
console.timeEnd("=== 개별 Promise 각각 ===");
};
그냥 Promise로 함수를 만들어서 실행하게 되면, result1 → result2 → result3이 순차적으로 실ㄹ행되게 되고, 약 6초의 시간이 소요된다.
const startPromiseAll = async () => {
// await Promise.all([promise, promise, promise])
console.time("=== 한방 Promise.all ===");
const result = await Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공");
}, 2000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공");
}, 3000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공");
}, 1000);
}),
]);
console.log(result);
console.timeEnd("=== 한방 Promise.all ===");
};
Promise.all()은 그 안에 포함되어 있는 함수들을 동시에 실행하게 되어서 3초의 시간이 소요된다.
Promise.all은 Promise보다 시간을 단축하지만, 결과값은 같도록 해주는 기능이다.
처음부터 다운로드 받아오지 않고, 스크롤을 내릴 때 이미지를 다운로드 받아오게 하는 것
→ 데이터의 낭비를 막을 수 있다.
다른 페이지에서 받아봐야 할 사진들을 미리 preload한 후, 글로벌한 변수에 담아서 사용하는 것.
// index.tsx
import { useRouter } from "next/router";
import { useEffect } from "react";
import { preloadImage } from "../../src/commons/libraries/preloadimage";
const PRELOAD_IMAGES = [
"https://upload.wikimedia.org/wikipedia/commons/9/96/%22Den_kjekke_gutt%22_-_6._Internasjonale_Akademiske_Vinterleker_%281939%29_%2840200856483%29.jpg",
];
export default function ImagePreloadPage() {
const router = useRouter();
useEffect(() => {
preloadImage(PRELOAD_IMAGES)
}, []);
const onClickMove = () => {
void router.push("/32-06-image-preload-moved");
};
return (
<>
<button onClick={onClickMove}>페이지 이동하기</button>
</>
);
}
// preloadimage.ts
const PRELOADED_IMAGES: HTMLImageElement[] = [];
export const preloadImage = (images: string[]) => {
images.forEach((el) => {
const img = new Image();
img.src = el;
img.onload = () => PRELOADED_IMAGES.push(img);
});
};
프리로드 함수는 따로 공통으로 분리해서 여기저기서 사용할 수 있다.
마우스 오버 하면 미리 fetch를 하는것
import { useQuery, gql, useApolloClient } from "@apollo/client";
import { useRouter } from "next/router";
import { MouseEvent } from "react";
import {
IQuery,
IQueryFetchBoardsArgs,
} from "../../src/commons/types/generated/types";
const FETCH_BOARDS = gql`
query fetchBoards($page: Int) {
fetchBoards(page: $page) {
_id
writer
title
contents
}
}
`;
const FETCH_BOARD = gql`
query fetchBoard($boardId: ID!) {
fetchBoard(boardId: $boardId) {
_id
writer
title
contents
}
}
`;
export default function StaticRoutedPage() {
const router = useRouter();
const client = useApolloClient();
const { data, refetch } = useQuery<
Pick<IQuery, "fetchBoards">,
IQueryFetchBoardsArgs
>(FETCH_BOARDS);
console.log(data?.fetchBoards);
const prefetchBoard = (boardId: string) => async () => {
await client.query({
query: FETCH_BOARD,
variables: { boardId },
});
};
const onClickMove = (boardId: string) => () => {
void router.push(`/32-08-data-prefetch-moved/${boardId}`);
};
return (
<>
{data?.fetchBoards.map((el) => (
<div key={el._id}>
<span style={{ margin: "10px" }}>{el.writer}</span>
{/* 마우스를 올렸을 때 data를 받아오도록 */}
<span
style={{ margin: "10px" }}
onMouseOver={prefetchBoard(el._id)}
onClick={onClickMove(el._id)}
>
{el.title}
</span>
</div>
))}
</>
);
}
💡 이미지 Webp 확장자
구글에서 만든 이미지 포맷으로, 기존 이미지의 용량을 줄일 수 있는 장점이 있지만, 화질이 조금 떨어질 수 있는 단점이 있다.
구글에서 jpg to webp 이런식으로 검색하면 변경할 수 있다.
💡 Google PageSpeed Insights
실제 배포를 진행하고 나서, 내가 배포한 페이지의 개선할 점을 찾을 때 유용한 사이트
💡 추천 라이브러리
react-dropzone
react-avatar-editor
react-beautiful-dnd(drop and drop)