본문 바로가기

[IT/Programming]/HTML related

React Router 에서 CSS 충돌을 막고 좀 더 개발 친화적으로 CSS 를 다룰 수 있게 해주는 CSS module 을 배워봅시다. (Learning module.css)

728x90
반응형
# React Router 에서 CSS 충돌을 막고 좀 더 개발 친화적으로 CSS 를 다룰 수 있게 해주는 CSS module 을 배워봅시다. (Learning module.css) ## PH
  • 2024-09-12 : First posting.
## TOC ## React Router ### Install (npm install react-router-dom) ``` npm install react-router-dom ```/ 과 같은 명령어로 우선 package 를 깔자. ### 사용방법 ``` import { BrowserRouter, Route, Routes } from 'react-router-dom'; import ReactDOM from 'react-dom/client'; import './root.css'; import CommonsPage from './pages/CommonsPage.js'; import HomePage from './pages/HomePage.js'; import ItemsPage from './pages/ItemsPage.js'; import RegisPage from './pages/RegisPage.js'; import LogInPage from './pages/LogInPage.js'; import SignUpPage from './pages/SignUpPage.js'; import NotFoundPage from './pages/NotFoundPage.js'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <BrowserRouter> <Routes> <Route path="/" element={<CommonsPage/>}> <Route index element={<HomePage/>}/> <Route path="items" element={<ItemsPage/>}/> <Route path="registration" element={<RegisPage/>}/> <Route path="login" element={<LogInPage/>}/> <Route path="signup" element={<SignUpPage/>}/> </Route> <Route path="*" element={<NotFoundPage/>}/> </Routes> </BrowserRouter> ); ```/ 와 같이 page 분기, routing 을 할 수 있다. Single Page Application (SPA) 인 셈. 그런데 한 페이지에서 여러 components 들을 다루다 보니 css 충돌 문제가 심각했다. css file 의 loading 순서도 중요했고, page 마다 main {} section {} 의 css 가 달라져야 했는데, 각 페이지 별로 (혹은 component 별로) css file import 를 다르게 해줘도 SPA 이다 보니 css 파일이 어느 페이지를 들어가든 이미 다 load 된 상태가 되어버려서 뒤죽박죽이 된 듯 했다. ## module.css (CSS module 을 사용해보자.) 속도 측면에서도 CSS in js 보다 빠르다고 하고 , 계층적 구조와 semantic 하게 css 를 써야 좋을거 같아서 module.css 를 사용해 보기로 했다. ```[.scrollable.lang-css] /* ItemsPage.module.css */ .main { max-width: 1920px; padding: 20px 0; display: flex; flex-direction: column; justify-content: space-evenly; gap: 20px; background-color: white; } .section { max-width: 1200px; width: 100%; padding: 0; margin: 0 auto; } .items_head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; margin-bottom: var(--text-xl); } .items_head h2 { font-size: var(--text-xl); font-weight: 700; } ul.items { list-style-type: none; display: flex; justify-content: space-between; flex-wrap: wrap; margin: 0; padding: 0; } ul.items.normal { display: grid; grid-template: repeat(2, auto) / repeat(5, 1fr); gap: 10px; } ul.items li { display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start; gap: 8px; margin-bottom: 30px; & .name { font-size: var(--text-md); font-weight: 500; line-height: var(--text-2xl); } & .price { font-size: var(--text-lg); font-weight: 700; line-height: var(--text-2xl); } & .favorite_count { font-size: var(--text-xs); font-weight: 500; line-height: var(--text-2lg); & .heart { font-size: 1.3em; } } } ul.items.best li { width: 24%; } ul.items.normal li { width: 100%; } ul.items li img { width: 100%; aspect-ratio: 1 / 1; border-radius: 16px; } .input_wrapper { position: relative; height: 43px; width: 325px; } .input_wrapper img { position: absolute; cursor: pointer; left: 20px; top: 14px; bottom: 14px; width: 17px; z-index: 10; } .input_wrapper input { position: absolute; inset: 0; border-radius: 12px; padding: 9px 20px 9px 45px; background-color: var(--secondary-color-gray100); color: var(--secondary-color-gray400); z-index: 1; } .items_head_query { display: flex; align-items: center; justify-content: flex-end; gap: 12px; } .post_product { padding: 12px 23px; font-size: var(--text-lg); font-weight: 600; color: var(--secondary-color-gray100); background-color: var(--primary-color-100); border-radius: 8px; display: inline-block; } .select_order_by { padding: 12px 20px; background-color: white; font-size: var(--text-lg); font-weight: 400; color: var(--secondary-color-gray800); } .pagenation { display: flex; align-items: center; gap: 12.5px; margin: 0 auto 45px; justify-content: center; } .pagenation div { display: inline-flex; align-items: center; justify-content: center; width: var(--text-4xl); height: var(--text-4xl); padding: 12.5px; background-color: white; color: var(--secondary-color-gray500); font-size: var(--text-lg); font-weight: 600; border-radius: 9999px; border: 2px solid var(--secondary-color-gray200); } .pagenation div:hover { background-color: var(--primary-color-300); color: white; } .pagenation div.disabled { background-color: rgb(216, 216, 216); color: var(--secondary-color-gray200); } .pagenation div.selected { background-color: var(--primary-color-100); color: white; } @media (max-width:1200px) { .section { flex-direction: column; margin: 0 auto; padding: 0 24px; } .main { padding: 15px; } .input_wrapper { width: 242px; } ul.items.normal { grid-template: repeat(2, auto) / repeat(3, 1fr); } ul.items.best>li { width: 49%; } } @media (max-width:744px) { .section { margin: 0 auto; padding: 0 16px; } .main { padding: 10px; } .head_row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; margin: 6px 0; width: 100%; gap: 10px; } .head_row h2 { flex-grow: 1; } .head_row .input_wrapper { flex-grow: 1; width: auto; min-width: 90px; } .input_wrapper { flex-grow: 1; } ul.items.normal { grid-template: repeat(2, auto) / repeat(2, 1fr); } ul.items.best>li { width: 100%; } } ```/ 와 같이 일반 css 작성하듯 css 를 작성하고 ItemsPage.js 에서 다음과 같이 className 을 설정하자. ```[.scrollable.lang-jsx] import { useEffect, useState, useCallback } from "react"; import styles from './ItemsPage.module.css'; // 이렇게 import 하자. import useAsync from "../hooks/useAsync.js"; import BestItemsList from "../BestItemsList.js"; import ItemsList from "../ItemsList.js"; import PageNum from "../PageNum.js"; function ItemsPage() { // ... codes ... return ( <main className={styles.main}> <BestItemsList bestItems={bestItems}/> <ItemsList items={items} orderBy={orderBy} setOrderBy={setOrderBy} keyword={keyword} setKeyword={setKeyword} onSearch={handleSearch}/> <PageNum pageNum={pageNum} setPageNum={setPageNum} pageNumMax={pageNumMax}/> </main> ); } export default ItemsPage; ```/ ### 작동되는 원리 정확한건 아니지만 내가 써보면서 느낀 작동원리를 설명하자면, .class-selector .sub-class-selector tagName 과 같은 css 가 module.css 에 작성되었다고 치면, compile 단계에서 이런 class-selector, sub-class-selector 의 이름들을 FileName_class-selector__hashxxx, FileName_sub-class-selector__hashxxx 로 변환한 채로 css file 이 upload (client 입장에선 download) 된다. 어차피 bundling 된 상태로 올라갈거라 다른 react component 의 같은 이름의 class selector 에 걸리지 말라고 prefix 로 FileName 을, 뒷쪽엔 postfix 로 hashxxx random 문자열이 추가되는거 같다. 그리고 jsx 에서 className={styles[".class-selector"]} 를 불러오면 그냥 class-selector 가 들어가는게 아니라 FileName_class-selector__hashxxx 형태로 올라가는듯 하다. (class-selector 를 javascript 에서 변수로 불러 올 수 있게 항상 class_selector 처럼 underbar 같은걸로 대체해야 하는 줄만 알았는데, 아니었다. 그냥 styles.class-selector 로 못 쓸 뿐, styles[".class-selector"] 로 충분히 접근 가능했다. 이거 때문에 엄청 삽질을 했는데 ㅠㅜ 다른 분들은 저같은 삽질 하지 마시고 시간 아끼시길...) 그래서 @media query 도 쓸 수 있고, nested CSS 도 사용 가능하고, tagName0 .class0 ul li 같은 selector 도 정상 동작한다. 헷갈리는 부분들은 댓글로 알려주시길... 댓글에서 코드 블록은 ``` 시작 ```/ 마지막으로 감싸면 prettyprint 되어서 나옵니다. (티스토리도 댓글 수에는 제한을 둬놔서 긴 코드는 못쓸거에요. 최대한 축약해서 올리시길...) ## RRA
  1. 카카오웹툰은 CSS를 어떻게 작성하고 있을까?, 2022-02-10, by 서현우(bono)
  2. 내가 하면 더 잘 만들 것 같아서 만들어 본 세상 귀여운 on-demand Atomic CSS Library. -Part.1, 2022-05-20, by teo.yu
  3. 리액트에서 모듈 CSS를 쓸 때 이모저모, 2024-09-12, by 코드잇 풀스택 2기 주강사 이창신
  4. React로 웹사이트 만들기 (Router/라우터) - 코드잇:
    https://recoeve.net/user/kipid/mode/multireco?cat=[IT/Programming]--국비 지원 코딩/공부#https://www.codeit.kr/topics/building-a-website-with-react

    https://www.codeit.kr/topics/building-a-website-with-react
728x90
반응형