๋ง์ผ์ ๋ฑ๋ก๋์ด ์๋ ์ํ์ ๊ตฌ๋งคํ๊ณ ์ ํ๋ค๋ฉด ์ํ์ ์ธ๋ถ์ฌํญ์ ํ์ธํ ๋ค, ์ฅ๋ฐ๊ตฌ๋์ ๋ฃ์ด, ์ํ์ ๊ตฌ๋งคํ ์ ์์ต๋๋ค.
- ํ๋ก์ ํธ ๊ธฐ๊ฐ : 2022.11.24. ~ 2022.12.15.
- ๋ฐฐํฌ URL : ๐ ์คํ๋ง์ผ
๋ฐ๋๋ผ ์๋ฐ์คํฌ๋ฆฝํธ๋ก UI ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ณ ๋ณ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด SPA๋ก ๊ตฌํํ ํ๋ก์ ํธ์ ๋๋ค.
- React๋ฅผ ์ฌ์ฉํ ๋๋ DOM์ ์ง์ ์กฐ์ํ ์ผ์ด ๋๋ฌผ์๋๋ฐ ์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ DOM ๊ตฌ์กฐ ๋ฐ ๊ด๋ จ ํจ์๋ฅผ ์ดํดํ ์ ์์์ต๋๋ค.
- ํ์ด์ง ๋ฆฌ๋ก๋ ์์ด ๋ผ์ฐํ ๋ณ๊ฒฝ, root ํ๊ทธ๋ง ์๋ ๊ตฌ์กฐ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ ๋ฐฉ๋ฒ ๋ฑ SPA๋ฅผ ์ง์ ๊ตฌํํ๋ฉด์ SPA๊ฐ ๋์ํ๋ ๋ฐฉ์์ ๋ก์ฐ ๋ ๋ฒจ์์ ์ดํดํ ์ ์์์ต๋๋ค.
state๋ฅผ ๊ธฐ์ค์ผ๋ก DOM์ด ๋ ๋๋ง ๋ ์ ์๋๋ก state-setState-render ๊ท์น์ ์ง์ผ๊ฐ๋ฉฐ ์ปดํฌ๋ํธ๋ฅผ ์ค๊ณํ์์ต๋๋ค. URL ๋ผ์ฐํ ์ฒ๋ฆฌ๋ location.pathname์ ์ด์ฉํด URL ๋ณ๋ก ๋ถ๊ธฐ๋ฅผ ํ์ฐ๊ณ , History API๋ฅผ ์ด์ฉํด URL๋ง ์ ๋ฐ์ดํธํ๋ฉด์ ์น ๋ธ๋ผ์ฐ์ ์ ๊ธฐ๋ณธ์ ์ธ ํ์ด์ง ์ด๋ ์ฒ๋ฆฌ๋ฅผ ๋ฐฉ์งํ์์ต๋๋ค.
URL์ ๋ณ๊ฒฝ์ ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์งํ๊ณ ํ์ด์ง๋ฅผ ์๋ก ๊ทธ๋ฆฌ๋๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ปค์คํ ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ฅ๋ฐ๊ตฌ๋์ ๋ด์ ์ํ ๋ฐ์ดํฐ๋ฅผ localStorage์ ์ ์ฅํ์ฌ ํด๋ผ์ด์ธํธ ๋ด์์ ๊ธฐ๋ก๋๊ณ ๋์๋๊ฒ ํ์์ต๋๋ค.
- ๋ชฉ๋ก์์ ์ํ์ ํด๋ฆญํ๋ฉด ์ํ ์์ธ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
- ์ํ์๋ ์ํ ํ๋งค์, ์ํ๋ช , ๊ฐ๊ฒฉ์ด ๋ณด์ฌ์ง๋๋ค.
- ์ํ ์ข์์ ๊ธฐ๋ฅ์ด ํด๋ผ์ด์ธํธ๋ด์์ ๊ธฐ๋ก๋๊ณ ๋์ํฉ๋๋ค.
- productId์ ํด๋นํ๋ ์ํ์ ๋ถ๋ฌ์ค๊ณ , ํด๋น ์ํ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ค๋๋ค.
- + ๋ฒํผ๊ณผ - ๋ฒํผ์ ์ฌ์ฉํด์ผ๋ง ์๋ ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ์๋์ ๋ณ๊ฒฝํ ๋ ํ์ฌ ์ํ์ ์ฌ๊ณ ์๋๋ํ ๋ณํฉ๋๋ค.
- ์ ํ๋ ์ต์ ์ ๋ง์ถฐ์ ๊ฐ๊ฒฉ์ ๊ณ์ฐํ๊ณ , ์ด ๊ฐ๊ฒฉ์ด ๋ํ๋ฉ๋๋ค.
- ์ด๋ฏธ ์ ํ๋ ์ํ์ ๋ค์ ์ ํํ์์ ๋, ์ํ์ ์ถ๊ฐ๋์ง ์์ต๋๋ค.
- ๋ฐ๋ก ๊ตฌ๋งค ๋ฒํผ์ ๋๋ ์ ์, ๊ฒฐ์ ํ์ด์ง๋ก ๋์ด๊ฐ๋๋ค.
- ์ฅ๋ฐ๊ตฌ๋ ๋ฒํผ์ ๋๋ ์ ์, ์ฅ๋ฐ๊ตฌ๋ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
- ์ํ ์์ธ ํ์ด์ง์์ ์ฅ๋ฐ๊ตฌ๋ ์ถ๊ฐ ๊ธฐ๋ฅ์ด ํด๋ฆฌ์ด์ธํธ๋ด์์ ๊ธฐ๋ก๋๊ณ ๋์ํฉ๋๋ค.
- ์ ํ๋ ์ ๋ณด๋ง ์ด ์ํ๊ธ์ก๊ณผ ํ ์ธ์ด ์ ์ฉ๋์ด ์ด ๊ฒฐ์ ํ ๊ฐ๊ฒฉ์ด ๋ํ๋ฉ๋๋ค.
- ์ ํ๋ ์ ๋ณด๋ง ์ด ์ํ๊ธ์ก๊ณผ ํ ์ธ์ด ์ ์ฉ๋์ด ์ด ๊ฒฐ์ ํ ๊ฐ๊ฒฉ์ด ๋ํ๋ฉ๋๋ค.
- ์ํ์
x
๋ฒํผ์ ํด๋ฆญํ ์ ์ํ์ด ์ญ์ ๋ฉ๋๋ค.
- ์ฅ๋ฐ๊ตฌ๋ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ฅ๋ฐ๊ตฌ๋ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
- ํ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ฅ๋ฐ๊ตฌ๋ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
๐public
โโ๐favicon.ico
โโ๐index.html
๐src
โโ๐components
โ โโ๐Cart
โ โ โโ๐index.js
โ โ โโ๐Order.js
โ โ โโ๐OrderList.js
โ โ โโ๐Payment.js
โ โโ๐Common
โ โ โโ๐Anchor.js
โ โ โโ๐Header.js
โ โโ๐ProductDetail
โ โ โโ๐Card.js
โ โ โโ๐index.js
โ โ โโ๐Info.js
โ โ โโ๐InfoImage.js
โ โโ๐ProductList
โ โโ๐Content.js
โ โโ๐Contents.js
โ โโ๐index.js
โ โโ๐Price.js
โ โโ๐Product.js
โ โโ๐Thumbnail.js
โโ๐pages
โ โโ๐CartPage.js
โ โโ๐ErrorPage.js
โ โโ๐ProductDetailPage.js
โ โโ๐ProductListPage.js
โโ๐utils
โ โโ๐api.js
โ โโ๐handleLocalStorage.js
โ โโ๐router.js
โ โโ๐toKRCurrency.js
โโ๐App.js
๐index.html
๐index.js
๐input.css
๐package-lock.json
๐package.json
๐README.md
๐tailwind.config.js
import ProductListPage from "./pages/ProductListPage.js";
import ProductDetailPage from "./pages/ProductDetailPage.js";
import CartPage from "./pages/CartPage.js";
import ErrorPage from "./pages/ErrorPage.js";
import { init } from "./utils/router.js";
export default function App({ $target }) {
this.route = () => {
const { pathname } = location;
$target.innerHTML = "";
if (pathname === "/") {
new ProductListPage({ $target }).render();
} else if (pathname.indexOf("/products/") === 0) {
const [, , productId] = pathname.split("/");
new ProductDetailPage({
$target,
productId,
}).render();
} else if (pathname === "/cart") {
new CartPage({ $target }).render();
} else {
new ErrorPage({ $target }).render();
}
};
init(this.route);
this.route();
window.addEventListener("popstate", this.route);
}
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
type=module๋ก ./index.js๋ฅผ ์คํฌ๋ฆฝํธํ๊ณ . import๋ฅผ ์ฌ์ฉํ์ ๋ ๊ฒฝ๋ก์ ํ์ฅ์๋ฅผ ์ ํํ๊ฒ ์ ์ง ์์์ ์๊ธด ์ค๋ฅ
index.html์ ์๋๊ฒฝ๋ก์ธ ./์ ์ง์ ํ๊ธฐ ๋๋ฌธ์ด์๋ค.
<script src="/index.js" type="module"></script>
DOM tree์์ ์ํ๋ node๋ค์ ์ถ๊ฐํ ๋ createElement์ appendChild ๋ฑ์ ๋น์ฉ์ด ๋ง์ด ๋๋ ์์ ์ด๊ธฐ์ ๋์์ด ํ์ํ๋ค. innerHTML์ XXS(ํฌ๋ก์ค ์ฌ์ดํธ ์คํฌ๋ฆฝํ ) ๊ณต๊ฒฉ์ ์ทจ์ฝํ๊ณ , style ํ๋ ์์ํฌ๋ก tailwind๋ฅผ ์ฌ์ฉํ๊ณ ์๊ธฐ์ ํ์ฑ์ด ๋๋ฆฌ๋ค.
- insertAdjacentHTML
์๋กญ๊ฒ ์ฝ์ ๋ ์์๋ง์ ํ์ฑํ์ฌ innerHTML๋ณด๋ค ํจ์จ์ ์ด๊ณ ๋น ๋ฅด์ง๋ง ๋ง์ฐฌ๊ฐ์ง๋ก XSS์ ์ทจ์ฝํ๋ค. ํ์ด์ง ๋ด์์ ๋์ ์ผ๋ก ๋ฐ๋๋ ์์๊ฐ ๊ฑฐ์ ์๋ค๊ณ ํ๋จํ์ฌ ์ฌ์ฉํ์ง ์์.
- DOMPurify ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฌธ์์ด์ ํ๋ฒ ํํฐ๋งํด์ฃผ๋ Sanitize ๊ธฐ๋ฅ์ผ๋ก XSS ๊ณต๊ฒฉ์ ์ํ์ด ๋ ๋งํ ์์๋ฅผ ์ ๊ฑฐํด์ค๋ค.
์ด ํ๋ก์ ํธ๋ ์์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก SPA๋ฅผ ๊ตฌํํด๋ณด๋ฉฐ ์น ์ปดํฌ๋ํธ์ SPA๋ฅผ ์ดํดํ๊ณ ์ ํ๋ ๋ชฉ์ ์ ๊ฐ์ง๊ณ ์๊ธฐ์ ์ฌ์ฉํ์ง ์์.
- ๋ ์์ฑ Factory ํจ์
๋ฆฌ์กํธ์ React.createElement(component, props, ...children)์์ ์ฐฉ์ํ์์. ํ์ง๋ง ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ๊ณ ๋ คํ์ฌ ํจ์๋ฅผ ์์ฑํ๊ฒ ๋๋ค๋ฉด ๋น์ฉ์ด ๋ง์ด ๋๋ ์์ ์ด ๋ ๊ฒ์ด๋ผ ์์ ๋๊ธฐ์ ์ถํ ๋ฆฌํฉํ ๋ง ๊ณผ์ ์์ ๊ณ ๋ คํด๋ณผ ๊ฒ.
ํ๋ก์ ํธ์ ๊ท๋ชจ๊ฐ ํฌ์ง ์๊ณ , ์๋น์ค ์ ๊ณต์ด ์๋, SPA ๊ตฌํ์ด ๋ชฉ์ ์ด๊ธฐ์ ํ์ฑ ์๋์ ๋ณด์์ ๊ณ ๋ คํ์ง ์๋๋ค.
style ํ๋ ์์ํฌ๋ก tailwind๋ฅผ ์ฌ์ฉํ๊ณ ์๊ธฐ์ innerHTML์ ์ฌ์ฉํ๋ฉด ๋์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ธ๋ฉํด์ผํ๋ ์ํฉ์์ ์ ์ง๋ณด์๊ฐ ์ด๋ ค์์ง๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์๋ค.
์ปดํฌ๋ํธ ๋จ์๋ฅผ ๋์ฑ ์๊ฒ ์ชผ๊ฐ์ด ์ตํ์ ์ปดํฌ๋ํธ์์ innerHTML๋ฅผ ์ฌ์ฉํ๊ณ , appendChild๋ก ์์๋ฅผ ์์ ์ปดํฌ๋ํธ์ ์ ๋ฌํ๋ค. ์ด๊ธฐ ์ค๊ณ ๋จ๊ณ์์ ์ฌ์ฌ์ฉ์ฑ์ ๊ณ ๋ คํ์ง ๋ชปํ๊ณ ๋ฐ๋๋ผ JS์ ์ฑ๋ฅ์ ๋ณต์ก๋๊ฐ ์์นํ์ฌ ์ํ ๋ฏน ํจํด์ ๊ณ ๋ คํ์ง ์๋๋ค.
์ด๊ธฐ ๋จ๊ณ์์ ์ฌ์ฌ์ฉ์ฑ์ ๊ณ ๋ คํ์ฌ ๊ณตํต ์ปดํฌ๋ํธ๋ฅผ ๋ถ๋ฆฌํ์ง ์์ ํ์ด์ง ๋ณ๋ก ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ์ ์ํด์ผ ํ๋ ๋ฌธ์ ๊ฐ ์๊ฒผ๋ค.
ํ ์ผ์๋์ layer๊ธฐ๋ฅ์ผ๋ก class๋ฅผ ๋ฌถ๊ณ ๊ณตํต ์์๋ฅผ shared ์ปดํฌ๋ํธ๋ก ์ ์ํ ํ ๋์ ์ผ๋ก ์์๋ฅผ ๋ฐํํ์๋ค๋ฉด ๊ฐ๋ ์ฑ๊ณผ ์์ฐ์ฑ์ด ํจ์ฌ ์ข์์ก์ ๊ฒ์ผ๋ก ์์ ๋๋ค. ์ด ๋ฌธ์ ๋ ๋ฆฌํฉํ ๋ง ์ ๊ณ ๋ คํ ์์ ์ด๋ค.
history API๋ฅผ ์ด์ฉํ์ฌ URL๋ง ์ ๋ฐ์ดํธํ๋ฉด์ ์น ๋ธ๋ผ์ฐ์ ์ ๊ธฐ๋ณธ์ ์ธ ํ์ด์ง ์ด๋ ์ฒ๋ฆฌ๋ฅผ ๋ฐฉ์งํ๋ ค ํ์์ผ๋, pushState๋ฅผ ํตํด URL์ด ๋ณ๊ฒฝ๋ ๊ฒ์ ๊ฐ์งํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํด์ผ ํ๋ ๋ฌธ์ ๊ฐ ์๊ฒผ๋ค.
์ปค์คํ ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ์ฌ route๊ฐ ๋ณ๊ฒฝ๋๋ค๋ฉด ์ฝ๋ฐฑ ํจ์๋ฅผ ํธ์ถํ๋๋ก ์ด๋ฒคํธ๋ฅผ ๋ฐ์ธ๋ฉํ์ฌ ํด๊ฒฐํ์๋ค.
const ROUTE_CHANGE_EVENT = "ROUTE_CHANGE";
export const init = (onRouteChange) => {
window.addEventListener(ROUTE_CHANGE_EVENT, () => {
onRouteChange();
});
};
export const routeChange = (url, params) => {
history.pushState(null, null, url);
window.dispatchEvent(new CustomEvent(ROUTE_CHANGE_EVENT, params));
};
innerHTML๋ก ์์ฑํ ๋ ธ๋๋ querySeletor๋ก ์ ํํ์ง ๋ชปํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์๋ค.
์ด๋ฒคํธ ์์์ ์ฌ์ฉํ์ฌ ์์ ์์์์ ํ์ ์์์ ์ด๋ฒคํธ๋ค์ ์ ์ดํ์ฌ ํด๊ฒฐํ์๋ค.
์๋ก๊ณ ์นจํ๋ฉด ๋ธ๋ผ์ฐ์ ๋ html css js ํ์ผ์ ์ฒ์๋ถํฐ ๋ค์ ์ฝ๊ธฐ ๋๋ฌธ์ ์ฐํ ์ํ์ ๋ํ state ๋ฐ์ดํฐ๊ฐ ๋ฆฌ์ ๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์๋ค.
localStorage์ ์ฐํ ์ํ์ ๋ํ state ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ์ฌ ํด๊ฒฐํ๊ณ , setState ํจ์๋ฅผ ์ฌ์ฉํ๋ฉฐ ๋์ ์ผ๋ก ์ํ ๊ฐ์ ๊ฐฑ์ ํด์ฃผ์๋ค.
setState ํจ์๊ฐ ์คํ๋์ด ์ปดํฌ๋ํธ๋ฅผ ์ฌ๋ ๋๋งํ ์ ํ์ ์ปดํฌ๋ํธ๊ฐ ๊ทธ ์์ ์์น๋ก ์ด๋ํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์๋ค.
insetAdjacentHTML() ๋ฉ์๋์ position ์ธ์๋ฅผ ์ฌ์ฉํ์ฌ ์์๊ฐ ์ ์ง๋๋๋ก ์กฐ์ ํ์๋ค.
์ฃผ๋ฌธ ์๋์ ์ํ๋ฅผ ๊ฐ๋ณ ์ปดํฌ๋ํธ์ ์ ๋ฌํด์ฃผ์ด์ผ ํ๋ ๋ฌธ์ ๊ฐ ์๊ฒผ๋ค.
๊ณต์ ํ ์ํ ๊ฐ์ ํจ์ ๋ฐ ์์ญ์ ์ค์ฝํ์ ์ ์ญ ๋ณ์๋ก ์ค์ ํ ํ MutationObserver๋ก ํ๊ฒํ DOM ๋ณ๊ฒฝ์ ๊ฐ์ํ์ฌ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ ์ํ๊ฐ ๋ณ๊ฒฝ๋์์ ๋ ์ฌ๋ ๋๋งํ๋ฉฐ ์ํ๋ฅผ ๊ณต์ ๋ฐ๋ ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐํ์๋ค.
// ์ต์ ๋ฒ ์ธ์คํด์ค ์์ฑ
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (!!mutation) {
console.log(Info.state.orderQuantity);
}
});
});
// ์ต์
์ค์
const config = {
attributes: true,
childList: true,
characterData: true,
};
// ์คํ
observer.observe($target, config);
}
SPA๋ฅผ ๊ตฌํํ๋ฉด์ ํ๋์ ํ์ผ์์ ์ฐ๊ฒฐํ๊ณ ์๋ ๋ฌด์ํ ๋ง์ ํ์ผ๋ค์ด์๊ฒผ๋ค. ์ฌ๋ฌ๋ฒ์ ๋คํธ์ํฌ ํต์ ์ ๊ฐ์์์ผ ์ฑ๋ฅ ๊ฐ์ ๊ณผ ์ต์ ํ๊ฐ ํ์ํ์ฌ ๋ชจ๋ ๋ฒ๋ค๋ฌ๋ฅผ ์ฌ์ฉํ๋ค.
"vite": "^4.1.0"