- ๋ค๋ฅธ ์ฌ๋ ์ปดํจํฐ์ ํด๋น ์ด๋ฏธ์ง๊ฐ ์์ ๊ฒฝ์ฐ ์ค๋ฅ ๋ฐ์
- ์ ๋ก๋ํ๋ค๊ฐ ์ค๋จ ์์ ์ด๋ฏธ์ง ๋ฐ์ดํฐ๊ฐ ์ง์์ง์ง ์๊ณ ๋จ์
์์ ๊ฐ์ ๋ฌธ์ ์ ์ ์ด๋ฏธ์ง ์ฃผ์๋ฅผ ์๋ฒ์์ ๊ฐ์ ธ์ค๋ ๊ฒ์ด ์๋๋ผ, ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ฉ์ผ๋ก ๋ง๋ ์์ ์ฃผ์๋ฅผ ๋ง๋ค์ด์ ํด๊ฒฐํ ์ ์๋ค. ์ฌ๊ธฐ์ ์์ ์ฃผ์๋ ๋ธ๋ผ์ฐ์ ์์ ์ ์ ๊ฐ๋ฅํ ์์ URL์ด๋ค.
์์ URL์ ๋ง๋๋ ๋ฐฉ๋ฒ์ 2๊ฐ์ง๊ฐ ์๋ค.
- ๊ฐ์ง URL ์์ฑํ๊ธฐ(๋ด ๋ธ๋ผ์ฐ์ ์์๋ง ์ด๋ฏธ์ง์ ์ ๊ทผ ๊ฐ๋ฅ)
- ์ง์ง URL ์์ฑํ๊ธฐ(๋ค๋ฅธ ๋ธ๋ผ์ฐ์ ์์๋ ์ด๋ฏธ์ง์ ์ ๊ทผ ๊ฐ๋ฅ)
export default function ImageUploadPreviewPage() {
const [imageUrl, setImageUrl] = useState("")
const onChangeFile=(event: ChangeEvent<HTMLInputElement>)=> {
const file = event.target.files?.[0]
console.log(file);
// ํ์ผ์ด ์์ผ๋ฉด ํจ์๋ฅผ ์ข
๋ฃ
if (!file) {
alert("ํ์ผ์ด ์์ต๋๋ค.");
return
}
// 1. ์์ URL ์์ฑ -> ๊ฐ์ง URL์์ฑ, ๋ด ๋ธ๋ผ์ฐ์ ์์๋ง ์ ๊ทผ ๊ฐ๋ฅ
const result = URL.createObjectURL(file)
console.log(result)
setImageUrl(result)
}
return (
<>
<div>
<input type="file" onChange={onChangeFile} />
<img src={imageUrl}/>
</div>
</>
);
}
new FileReader()
๊ธฐ๋ฅ์ ํ์ผ ๊ฐ์ฒด๋ฅผ ์ด์ฉํด ๋ด์ฉ์ ์ฝ๊ณ ์ฌ์ฉ์ ์ปดํจํฐ์ ์ ์ฅํ๋ ๊ฒ์ ๊ฐ๋ฅํ๊ฒ ํด์ฃผ๋ ๋ธ๋ผ์ฐ์ ์์ ์ง์ํด์ฃผ๋ ๊ธฐ๋ฅ์ด๋ค. new FileReader()
๋ฅผ ์ฌ์ฉํ๋ฉด new FileReader()์ ์๋ ๊ธฐ๋ฅ๋ค์ ์ด์ฉํ ์ ์๋ค.
// ์๋ ์ฝ๋๋ฅผ ์ ์ธํ๊ณ ์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ๊ณผ ์ฝ๋ ๋์ผ
// 2. ์์ URL์์ฑ -> ์ง์ง URL์์ฑ, ๋ค๋ฅธ ๋ธ๋ผ์ฐ์ ์์๋ ์ ๊ทผ ๊ฐ๋ฅ
const fileReader = new FileReader()
// readAsDataURL(): Data URL์ ์ป์ ์ ์์.
fileReader.readAsDataURL(file);
// ํ์ผ ์ฝ๊ธฐ์ ์ฑ๊ณตํ๋ฉด onload ์คํ.
// onload์์๋ ํ์ผ์ ์ฝ๊ณ ์์ฑ๋ Data URL์ด target.result์ ๋ด๊ธฐ๊ฒ ๋จ.
fileReader.onload = (data) => {
// fileReader์ ๊ฒฐ๊ณผ๊ฐ์ด string์ด ์๋ ์๋ ์์ผ๋ string์ผ๋๋ง ์คํ๋๋๋ก ํจ.
if(typeof data.target?.result === "string"){
console.log(data.target?.result);
setImageUrl(data.target?.result)
}
}
// return ๊ฐ ์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ๊ณผ ๋์ผ
์์ URL์ ๋ฐ์์ค๋ ๋ฐฉ๋ฒ ์ค ๋ ๋ฒ์งธ ๋ฐฉ๋ฒ(์ง์ง URL ๋ฐ์์ค๊ธฐ)์ ์ฌ์ฉํด์ API๋ฅผ ์์ฒญํ๋ค.
์ฌ๊ธฐ์ ์์ URL์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ์ผ์ด๊ธฐ ๋๋ฌธ์ ์ฌ๋ฐ๋ฅธ ํํ์ file
ํ์
์ด ์๋๋ค. ๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค.
// import ๋ถ๋ถ ์๋ต
const CREATE_BOARD = gql`
mutation createBoard($createBoardInput: CreateBoardInput!) {
createBoard(createBoardInput: $createBoardInput) {
_id
}
}
`;
const UPLOAD_FILE = gql`
mutation uploadFile($file: Upload!) {
uploadFile(file: $file) {
url
}
}
`;
export default function ImageUploadPage() {
// imageUrl์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์ํ ์ฃผ์์ด๋ฏ๋ก ํด๋น URL์ ์คํ ๋ฆฌ์ง๋, DB์ ๋ฃ์ด์๋ ์๋จ.
const [imageUrl, setImageUrl] = useState("");
// DB์ ๋ฃ๊ธฐ ์ํ URL์ฃผ์
const [file, setFile] = useState<File>();
const [ uploadFile ] = useMutation<Pick<IMutation, "uploadFile">,IMutationUploadFileArgs>(UPLOAD_FILE);
const [ ๋์ํจ์ ] = useMutation(CREATE_BOARD);
// ๋ฐ์์จ ์ฃผ์๋ก api ์์ฒญ
const onClickSubmit = async () => {
// ์คํ ๋ฆฌ์ง์ ์
๋ก๋
const resultFile = await uploadFile({ variables: { file } });
const url = resultFile.data?.uploadFile.url;
// ๊ฒ์๊ธ์ ์ด๋ฏธ์ง ๋ฑ๋ก
const result = await ๋์ํจ์({
variables: {
createBoardInput: {
writer: "์ฒ ์",
password: "1234",
title: "์๋
ํ์ธ์",
contents: "๋ฐ๊ฐ์ต๋๋ค",
images: [url],
},
},
});
console.log(result);
};
const onChangeFile = async (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
// <input type="file" multiple /> ์์ multiple ์์ฑ์ผ๋ก ์ฌ๋ฌ ๊ฐ ๋๋๊ทธ ๊ฐ๋ฅ
if (!file) return;
console.log(file);
// 2. ์์URL ์์ฑ => (์ง์งURL - ๋ค๋ฅธ ๋ธ๋ผ์ฐ์ ์์๋ ์ ๊ทผ ๊ฐ๋ฅ)
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = (event) => {
if (typeof event.target?.result === "string") {
console.log(event.target?.result);
// ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ฉ
setImageUrl(event.target?.result);
// DB์ ๋ฃ์ด์ฃผ๊ธฐ์ฉ
setFile(file);
};
return (
<>
<input type="file" onChange={onChangeFile} />
<img src={imageUrl} />
{/* <img src={`https://storage.googleapis.com/${imageUrl}`} /> */}
<button onClick={onClickSubmit}>๊ฒ์๊ธ ๋ฑ๋กํ๊ธฐ</button>
</>
);
}
createObjectURL?
createObjectURL๊ณผ fileReader ๋ชจ๋ ์ด๋ฏธ์ง ์ ๋ก๋ ์ ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ํ ์ ์๋ค. createObjectURL์ ์ฌ์ฉํ๋ฉด ์์ค์ฝ๋๊ฐ ์งง์์ ธ์ ์์ฑํ๊ธฐ ์ฝ์ง๋ง, fileReader์ ๋ฌ๋ฆฌ blob ๊ฐ์ฒด๋ก ์ด๋ฏธ์ง๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์ ํต์ ํ ๋ ์ฌ์ฉํ ์ ์๋ค! ๋ํ, fileReader๊ฐ ๋ธ๋ผ์ฐ์ ํธํ์ฑ๋ ๋ ์ข๊ธฐ ๋๋ฌธ์ fileReader๋ฅผ ์ฌ์ฉํ์.
์๋ ์ฝ๋๋ฅผ ์คํ์ํค๋ฉด result1๋ถํฐ 2, 3๊น์ง ์ฐจ๋ก๋ก ์คํ๋๊ณ ์ด 6์ด์ ์๊ฐ์ด ๊ฑธ๋ฆฐ๋ค.
const startPromise = async () => {
// console.time: ์๊ฐ ์ธก์
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.all()
์ ์ฌ์ฉํ๋ฉด Promise.all()์ ํฌํจ๋ ํจ์๋ค์ ๋์์ ์คํํ๊ธฐ ๋๋ฌธ์ ์ฝ 3์ด์ ์๊ฐ์ด ๊ฑธ๋ฆฐ๋ค.
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()
์ ๊ฒฐ๊ณผ๊ฐ์ Promise์ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฐฐ์ด์ '์ฑ๊ณต' 3๊ฐ๊ฐ ๋๋ํ ๋ค์ด์จ๋ค. Promise์ ๊ฐ์ ๊ธฐ๋ฅ์ ํ์ง๋ง, ์๊ฐ์ ํจ์ฌ ๋จ์ถ์์ผ์ค๋ค!
์ฌ๋ฌ ์ด๋ฏธ์ง๋ฅผ ํ๋ฒ์ ์ฌ๋ฆฌ๊ธฐ ์ํด promise.all()
๊ณผ map
์ ํจ๊ป ์ฌ์ฉํ๋ค.
// import ๋ถ๋ถ ์๋ต
// CREATE_BOARD API gql ์ฝ๋ ์๋ต
// UPLOAD_FILE API gql ์ฝ๋ ์๋ต
export default function ImageUploadPage() {
const [imageUrls, setImageUrls] = useState(["", "", ""]);
// ์ด๋ฏธ์ง url 3๊ฐ๊ฐ ๋ค์ด๊ฐ ๋ฐฐ์ด ๋ง๋ค๊ธฐ
const [files, setFiles] = useState<File[]>([]);
const [uploadFile] = useMutation<
Pick<IMutation, "uploadFile">,
IMutationUploadFileArgs
>(UPLOAD_FILE);
const [๋์ํจ์] = useMutation(CREATE_BOARD);
const onClickSubmit = async () => {
// Promise.all ์ฌ์ฉ!
const results = await Promise.all([
uploadFile({ variables: { file: files[0] } }),
uploadFile({ variables: { file: files[1] } }),
uploadFile({ variables: { file: files[2] } }),
]);
console.log(results); // [resultFile0, resultFile1, resultFile2]
// map ์ฌ์ฉ!
const resultUrls = results.map((el) => (el ? el.data?.uploadFile.url : "")); // [dog1.jpg, dog2.jpg, dog3.jpg]
const result = await ๋์ํจ์({
variables: {
createBoardInput: {
writer: "์ฒ ์",
password: "1234",
title: "์๋
ํ์ธ์",
contents: "๋ฐ๊ฐ์ต๋๋ค",
images: resultUrls, // [url0, url1, url2]
},
},
});
console.log(result);
};
const onChangeFile =
(index: number) => async (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]; // <input type="file" multiple /> ์์ multiple ์์ฑ์ผ๋ก ์ฌ๋ฌ ๊ฐ ๋๋๊ทธ ๊ฐ๋ฅ
if (!file) return;
console.log(file);
// 2. ์์URL ์์ฑ => (์ง์งURL - ๋ค๋ฅธ ๋ธ๋ผ์ฐ์ ์์๋ ์ ๊ทผ ๊ฐ๋ฅ)
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = (event) => {
if (typeof event.target?.result === "string") {
console.log(event.target?.result);
const tempUrls = [...imageUrls];
tempUrls[index] = event.target?.result;
setImageUrls(tempUrls);
const tempFiles = [...files];
tempFiles[index] = file;
setFiles(tempFiles);
};
return (
<>
<input type="file" onChange={onChangeFile(0)} />
<input type="file" onChange={onChangeFile(1)} />
<input type="file" onChange={onChangeFile(2)} />
<img src={imageUrls[0]} />
<img src={imageUrls[1]} />
<img src={imageUrls[2]} />
<button onClick={onClickSubmit}>๊ฒ์๊ธ ๋ฑ๋กํ๊ธฐ</button>
</>
);
}
์ฌ๊ธฐ์ ๋ฐ๋ณต๋๋ ๋ถ๋ถ์ ์์ค์ฝ๋๋ฅผ ๋ฐ๋ณต๋ฌธ์ ํตํด ๊ฐ๊ฒฐํ๊ฒ ๋ฐ๊ฟ๋ณด์๋ค.
const onClickSubmit = async () => {
// 1. Promise.all ์ผ์ ๋
// const results = await Promise.all([
// uploadFile({ variables: { file: files[0] } }),
// uploadFile({ variables: { file: files[1] } }),
// uploadFile({ variables: { file: files[2] } }),
// ]);
// console.log(results); // [resultFile0, resultFile1, resultFile2]
// const resultUrls = results.map((el) => (el ? el.data?.uploadFile.url : "")); // [dog1.jpg, dog2.jpg, dog3.jpg]
// 2. Promise.all ์ผ์ ๋ - ๋ฆฌํฉํ ๋ง
// files - [File0, File1, File2]
// files.map(el => uploadFile({ variables: { file: el } })) // [uploadFile({ ...: File0 }), uploadFile({ ...: File1 }), uploadFile({ ...: File2 })]
const results = await Promise.all(
files.map((el) => el && uploadFile({ variables: { file: el } }))
);
console.log(results); // [resultFile0, resultFile1, resultFile2]
const resultUrls = results.map((el) => (el ? el.data?.uploadFile.url : "")); // [dog1.jpg, dog2.jpg, dog3.jpg]
ํ์ด์ง ๋ ๋๋ง ์ ์ค์ํ์ง ์์ ๋ฆฌ์์ค์ ๋ก๋ฉ์ ๋์ค์ผ๋ก ๋ฏธ๋ฃจ๋ ๊ธฐ์ ์ด๋ค.
์๋ฅผ ๋ค์ด ์ด๋ฏธ์ง๊ฐ 10์ฅ์ด ๋๋ ํ์ด์ง๊ฐ ์๋ค๊ณ ํ๋ฉด, ํ๋ฒ์ 10์ฅ ๋ชจ๋ ๋ค ๋ก๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ฉด, ํ์ด์ง์ ๋ก๋ฉ์ด ๊ธธ์ด์ง ๊ฒ์ด๋ค.
ํ์ง๋ง, ๋งจ ์์ ํ๋ฉด์ ๋ณด์ด๋ ์ด๋ฏธ์ง๋ง ๋ก๋ํ ํ์ ์คํฌ๋กค์ ๋ด๋ฆฌ๋ฉด์ ์ด๋ฏธ์ง๊ฐ ๋ณด์ฌ์ ธ์ผ ํ ๋ ์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ๋ค๋ฉด, ๋ฐ์ดํฐ ๋ญ๋น๋ฅผ ๋ง์ ์ ์๋ค!
PreLoad
๋ ํ์ด์ง๋ฅผ ์ฝ์ ๋ ๋ฏธ๋ฆฌ ๋ฆฌ์์ค๋ฅผ ๋ฐ์๋๋ ๊ธฐ์ ์ด๋ค. LazyLoad์์์ ์์์ ๊ฐ์ด ์ด๋ฏธ์ง๊ฐ 10์ฅ์ด ๋๋ ํ์ด์ง๊ฐ ์๋ค๊ณ ํด๋ณด์. PreLoad์ ๊ฒฝ์ฐ ๋ชจ๋ ๋ฐ์ดํฐ๋ค์ ๋ฏธ๋ฆฌ ๋ก๋ํด๋๊ณ ๋๊ธฐํ๋ ๋ฐฉ์์ด๋ผ๊ณ ๋ณด๋ฉด ๋๋ค.
๋ค์์ ๋ค๋ฅธ ํ์ด์ง๋ก ์ด๋ํ๊ธฐ ์ ์ ์ด๋ฏธ์ง๋ฅผ ๋ฏธ๋ฆฌ ๋ฐ์์ค๋ ๊ธฐ๋ฅ์ ๊ตฌํํ ์์ค์ฝ๋๋ค.
import { useRouter } from "next/router";
import { useEffect } from "react";
const qqq = [];
export default function ImagePreloadPage(): JSX.Element {
const router = useRouter();
useEffect(() => {
// ์ด๋ฏธ์ง ํ๊ทธ ์์ฑ
const img = new Image();
// img.src = "์ฉ๋ํฐ๋ฐฐ๋์ด๋ฏธ์ง.jpg";
img.src = "/IMG_9822.PNG";
// img ํ๊ทธ๊ฐ onload ๋์์ ๋
// ์ด๋ฏธ์ง๋ค์ qqq ๋ฐฐ์ด์ ๋ฃ์ด์ค
img.onload = () => {
qqq.push(img);
};
});
const onClickMove = (): void => {
void router.push("/section31/31-09-image-preload-moved");
};
return <button onClick={onClickMove}>ํ์ด์ง ์ด๋ํ๊ธฐ</button>;
}
๊ฒ์๊ธ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๊ณ , ๋ชฉ๋ก ์ค ํ๋์ ๋ง์ฐ์ค๋ฅผ ๊ฐ์ ธ๋ค ๋จ์ ๋ ๋ฏธ๋ฆฌ ๊ฒ์๊ธ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๊ธฐ๋ฅ์ ๊ตฌํํด๋ณด์๋ค.
// import ๋ถ๋ถ ์๋ต
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);
// cache์ ํด๋น ๋ฐ์ดํฐ ์ ์ฅ๋๊ณ , ํ์ด์ง ์ด๋ํ๋ฉด ๋ฐฑ์๋์ ๊ฐ์ง ์๊ณ , cache์ ์๋ ๊ฒ์ ๊ฐ์ง๊ณ ์ด.
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>
))}
</>
);
}
๋ฏธ๋ฆฌ ๊ฒ์๊ธ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ๋์ผ๋ฉด, ๊ฒ์๊ธ์ ํด๋ฆญํด์ ์์ธ ํ์ด์ง๋ก ๋ค์ด๊ฐ์ ๋ ๋ ๋น ๋ฅด๊ฒ ๊ฒ์๊ธ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ฌ ์ ์๊ฒ ๋๋ค!
์ค์ ๋ฐฐํฌ๋ฅผ ์งํํ๊ณ ๋์, ๋ด๊ฐ ๋ฐฐํฌํ ํ์ด์ง์ ๊ฐ์ ํ ์ ์ ์ฐพ์๋ ์ ์ฉํ ์ฌ์ดํธ๋ค.
png, jpeg์ ๊ฐ์ ์ด๋ฏธ์ง ํ์ฅ์๋ค. ์ด๋ฏธ์ง ์๋ฒ์ ๋ถ๋ด์ ์ค์ด๊ณ , ์๋ฒ๋น๋ฅผ ์๋ผ๊ธฐ ์ํด ๊ตฌ๊ธ์์ ๋ง๋ค์๋ค.
Webp๋ GIF, PNG, JPEG ํ์ฅ์ ๋ชจ๋ ๋์ฒด ๊ฐ๋ฅํ ํ์ฅ์์ด๊ณ , ์ด๋ฏธ์ง๋ฅผ ํ์ผ์ ์์ถํ์ ๋ ๊ธฐ์กด PNG, JPEG๋ณด๋ค ์ฝ 30%์ ๋ ์ฉ๋์ ์ค์ผ ์ ์๋ค!
Webp ํ์ฅ์ ๋ณํ ์ฌ์ดํธ
์ฌ์ฉ๋ฐฉ๋ฒ!
Select File ํด๋ฆญ > ์ด๋ค ํ์ผ๋ก ๋ณํํ ์ง ์ ํ(Webp ์ ํ) > convert ํด๋ฆญ
- React-lazy-load: ์คํฌ๋กค์ด ๋ด๋ ค๊ฐ๋ฉด์ ํด๋น ์ด๋ฏธ์ง๊ฐ ๋ค์ด๊ฐ ์๋ ์ปดํฌ๋ํธ๊ฐ ๋ฑ์ฅํ ๋ ์ด๋ฏธ์ง ๋ค์ด๋ก๋
- React-dropzone: ๋๋๊ทธ ์ค ๋๋
- React-avator-editor
- ant-design
- React-beautiful-dnd
์ฐธ๊ณ ! ๊ทธ ์ธ์ ์ ์ฉํ ์ฌ์ดํธ!
wappalyzer(ํฌ๋กฌ ํ์ฅ ํ๋ก๊ทธ๋จ): ํน์ ํ์ด์ง์์ ์ฌ์ฉํ๋ ๊ธฐ์ ์ ์ ์๋ค.
์ฝ๋๋๋ฆฌ: ๊ธฐ์ ๋ณ ์ฐ๋ ๊ธฐ์