이번 주는 별도로 요구사항이 존재하지 않았고, 기존 4-5주차에서 vanilla-js로 작성했던 코드들을 react로 옮기는 게 미션이었다.
처음엔 CRA로 빌드를 했다.

하지만 구현해야 하는 요구사항에 비해 다소 무거웠다.
그리고 사실 이미 강의를 들으며 자주 접했던 탓에 create-react-app말고 다른 걸 사용해보고 싶었다. 그러던 중 케니의 추천으로 Vite를 알게 되었고, 이번 기회에 새로운 기술을 배워보고자 Vite로 다시 선택해서 만들어 보려고 한다.
기존 위클리 미션 폴더에서 .github과 README.md 파일 때문에 제거한 뒤 git stash로 복구했다.
그냥 JavaScript만 쓰려다가 SWC로 새로 배워보고 싶어서 선택했다.
환경 변수 사용법이 다르다.
REACT_APP_API_KEY => VITE_API_KEY
가져올 때는 import.meta.env로 가져온다.
진입점이 다르다.
index.js가 아니라 main.jsx다.
index.html의 위치가 다르다.
public/index.html이 아니라 루트 경로에 index.html이 존재한다.
port 번호가 5173다.
나는 3000번이 익숙해서 설정을 바꿔줬다.
// vite.config.js
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
},
});
alias 적용해보자.
/src 경로를 @로 변경해줬다.
타입스크립트면 tsconfig.json도 변경해줘야 하지만,
나는 js이므로 간단하게 설정할 수 있었다.
// vite.config.js
export default defineConfig({
...
,
resolve: {
alias: [{ find: "@", replacement: "/src" }],
},
});
이제 이렇게 "@/~"로 편하게 불러올 수 있다.

.js도 허용하기
utils나 hooks에 들어가는 파일들은 굳이 jsx일 필요가 없을 것 같아서 js도 허용하도록 설정해줬다.
// vite.config.js
export default defineConfig({
...
,
esbuild: {
include: /\.(tsx?|jsx?)$/,
exclude: [],
},
});
폴더 구조는 다음과 같다.
├─ public/assets
├─ src
├─ components/
├─ hooks/*/index.js
├─ pages/
├─ utils/index.js
├─ App.jsx
└─ main.jsx
└─ index.html
hooks 폴더에는 유저 데이터를 가져오는 훅들을 작성하고
utils 폴더 안에 기타 함수들을 작성했다.
/shared 페이지만 구현하면 되므로 생각보다 간단했다.
라우팅은 다음과 같이 구성했다.

/shared 페이지는 다음과 같이 구성했다.
import Header from "@/components/Header";
import FolderContents from "@/components/FolderContents";
import Footer from "@/components/Footer";
const SharedPage = () => {
return (
<>
<Header />
<FolderContents />
<Footer />
</>
);
};
export default SharedPage;
@/components/FolderContents에서는 다음과 같이 코드를 작성했다.
데이터를 받아오는 건 커스텀 훅으로 빼고,
내부에서 searchBar, Card 컴포넌트에 데이터를 prop으로 전달했다.
// @/components/FolderContents/index.jsx
import { useCallback } from "react";
import styles from "./folderContents.module.css";
import SearchBar from "@/components/SearchBar";
import Card from "@/components/Card";
import useUserFolder from "@/hooks/useUserFolder";
const FolderContents = () => {
const [userFolder] = useUserFolder();
const toggleCardAsterisk = useCallback((e) => {
const cardAsterisk = e.target.closest(".cardAsterisk");
if (!cardAsterisk) return;
if (cardAsterisk.getAttribute("src") === "/assets/card-asterisk.svg") {
cardAsterisk.setAttribute("src", "/assets/card-asterisk-check.svg");
} else if (
cardAsterisk.getAttribute("src") === "/assets/card-asterisk-check.svg"
) {
cardAsterisk.setAttribute("src", "/assets/card-asterisk.svg");
}
}, []);
return (
<main className={`${styles.main}`}>
{userFolder && (
<div className={`${styles.hero} ${styles.inner}`}>
<div className={`${styles.codeitAvatar}`}>
<img
src={userFolder?.owner?.profileImageSource}
alt="Owner Avatar"
/>
</div>
<span className={`${styles.atsign}`}>@{userFolder?.owner?.name}</span>
<span className={`${styles.marks}`}>{userFolder?.name}</span>
</div>
)}
<div className={`${styles.contents}`}>
<SearchBar />
<div
onClick={toggleCardAsterisk}
className={`${styles.cardContainer} ${styles.inner}`}
>
{userFolder?.links?.map((link) => (
<Card key={link.id} link={link} />
))}
</div>
</div>
</main>
);
};
export default FolderContents;
CRA를 쓸 때와 달리 Vite를 쓰니까 Card 컴포넌트에서 에러가 발생했었는데, 타입 체크를 요구했다.
CRA에서는 알아서 다 해주던 걸 Vite로 넘어와서 그런지 이런 부분을 세심하게 고려하면서 개발해야 할 것 같다고 느꼈다.
// @/components/Card/index.jsx
...
import PropTypes from "prop-types";
const Card = ({ link }) => {
...
};
Card.propTypes = {
link: PropTypes.object,
};
export default Card;
env에서 가져오는 방식도 다르다. import.meta.env.로 가져와야 한다.
참고로 여기서는는 useEffect에서 바로 async 함수를 반환하지 않고 간접적인 방식으로 함수를 호출했다.
effect hook이 아무것도 반환하지 않거나 메모리 누수를 방지하기 위한 clean-up 함수만을 반환해야 한다고 배웠다. useEffect에서 async를 직접적으로 사용하면 resolved promise를 반환하므로 저렇게 간접적인 방식으로 사용했다.
(물론 맞는 건지 코드 리뷰로 멘토분께 질문드려야겠다..)
// src/hooks/useUserProfile.js
import { useEffect, useState } from "react";
const useUserProfile = () => {
const [userProfile, setUserProfile] = useState({});
useEffect(() => {
const fetchUser = async () => {
try {
const res = await fetch(import.meta.env.VITE_USER_URL);
const result = await res.json();
setUserProfile(result.data);
} catch (e) {
console.dir(e);
}
};
fetchUser();
}, []);
return [userProfile, setUserProfile];
};
export default useUserProfile;
[Lint] ESLint + Prettier 설정하기
Vite, React, TypeScript, ESlint, Prettier 환경 세팅하는 법
처음 vite로 빌드를 하고 나면, .eslintrc.cjs로 되어 있는데, .js로 변경했다.
eslint: ESLint 코어
eslint-config-airbnb: Airbnb의 eslint 스타일 가이드
eslint-plugin-import: ES2015+의 import/export 구문을 지원
eslint-plugin-react: 리액트 지원
eslint-plugin-jsx-a11y: 접근성 지원
eslint-plugin-react-hooks: 리액트 hooks 지원

자동으로 설치되는 것 제외하고 추가적으로 설치해줬다.
Lint는 소스 코드를 분석하여 프로그램 오류, 버그, 스타일 오류 등을 표시해주는 도구를 가리킨다. ESLint가 요즘 많이 쓰이고 있는 린트 중 하나다.
airbnb 스타일 가이드의 코드 규칙을 적용할 수 있는 eslint-config-airbnb 패키지는 ECMAScript 6+와 (자바스크립트란 소리) 리액트를 포함하는 airbnb ESLint 규칙을 제공하고 아래 5개의 패키지들을 필수로 한다.
prettier도 설정해준다.
npm i -D eslint-config-prettier eslint-plugin-prettier
// .prettierrc
{
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"semi": true
}
on__으로 handle__으로 use__로 (커스텀 훅에서는 JSX 지양하고 리액트 훅이나 JS 로직 위주) __Page로 Page Not Found가 떠서 public 폴더 안에 _redicrects 파일을 추가했다.

netlify에서 내가 설정한 환경변수를 왜 못 읽지 했는데,
URL을 string으로 적어서 그랬던 거였다. 코드 작성해뒀던 걸 그대로 복사했는데 string으로 넣으면 안 되는 거였다..
