๐Ÿ“˜ [Recat] React ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ๋ง

๐Ÿ“˜ [Recat] React ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ๋ง

[Recat] React ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ๋ง

Vanilla CSS๋ž€?

Vanilla CSS๋Š” ๊ธฐ๋ณธ์ ์ธ CSS ํŒŒ์ผ์„ ํ™œ์šฉํ•˜๋Š” ์ „ํ†ต์ ์ธ ๋ฐฉ์‹์œผ๋กœ, ํŠน๋ณ„ํ•œ CSS-in-JS ๋„๊ตฌ ์—†์ด ์ˆœ์ˆ˜ CSS ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜๊ณ  .jsx ์ปดํฌ๋„ŒํŠธ์—์„œ importํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.


index.css ๋ฐฉ์‹

  • ์ „์—ญ ์Šคํƒ€์ผ ํŒŒ์ผ(index.css ๋“ฑ)์„ ๋งŒ๋“ค์–ด ํ”„๋กœ์ ํŠธ์˜ ๊ณตํ†ต ์Šคํƒ€์ผ์„ ์ •์˜ํ•˜๊ณ  ์‚ฌ์šฉ
  • Vite, Webpack ๋“ฑ์˜ ๋นŒ๋“œ ๋„๊ตฌ๋Š” import './index.css'์™€ ๊ฐ™์€ ํ˜•ํƒœ๋ฅผ ๋งŒ๋‚˜๋ฉด, ํ•ด๋‹น CSS๋ฅผ HTML์— ์ž๋™ ์‚ฝ์ž…ํ•ด์คŒ

๐Ÿ“Œ ์‚ฌ์šฉ ์˜ˆ์‹œ:

`/* index.css */ body {   margin: 0;   font-family: 'Arial'; }  .header {   background-color: #f0f0f0; }`
`// App.jsx import './index.css';  function App() {   return <h1 className="header">Hello CSS</h1>; }`
  • ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์— ์ „์—ญ ์ ์šฉ๋˜๋ฏ€๋กœ ์Šคํƒ€์ผ ์ถฉ๋Œ ์œ„ํ—˜ ์žˆ์Œ
  • ๋น ๋ฅด๊ณ  ๊ฐ„๋‹จํ•จ

Vanilla CSS์˜ ์žฅ์ ๊ณผ ํ•œ๊ณ„

โœ… ์žฅ์ :

  • CSS๋ฅผ ์ž˜ ์•„๋Š” ๋””์ž์ด๋„ˆ์™€์˜ ์—ญํ•  ๋ถ„๋‹ด์ด ์‰ฌ์›€
  • ๋ณ„๋„ ๋ฌธ๋ฒ• ์—†์ด๋„ ๋ฆฌ์•กํŠธ ์•ฑ์— ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ์—ฌ๋Ÿฌ CSS ํŒŒ์ผ๋กœ ๋ชจ๋“ˆํ™” ๊ฐ€๋Šฅ
  • ํŠน์ • CSS ๊ทœ์น™๋งŒ ์ •์˜๋œ ํŒŒ์ผ์„ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ์—์„œ importํ•˜๋Š” ์œ ์—ฐํ•จ

โš ๏ธ ๋‹จ์ :

  • CSS๊ฐ€ ์ปดํฌ๋„ŒํŠธ ๋ฒ”์œ„์— ์Šค์ฝ”ํ”„ ๋˜์ง€ ์•Š์Œ
  • CSS ์ด๋ฆ„ ์ถฉ๋Œ ๋ฐ ์Šคํƒ€์ผ ์˜ค์—ผ ์œ„ํ—˜
    • ์˜ˆ: .header๋ผ๋Š” ํด๋ž˜์Šค๊ฐ€ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ค‘๋ณต ์‚ฌ์šฉ๋  ๊ฒฝ์šฐ
  • ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ์Šคํƒ€์ผ ๊ฐ„์„ญ ๊ฐ€๋Šฅ์„ฑ ์กด์žฌ

Vanilla CSS์˜ ์Šค์ฝ”ํ•‘ ๋ฌธ์ œ

Vanilla CSS๋Š” ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„๋กœ ์Šคํƒ€์ผ์ด ์Šค์ฝ”ํ•‘(๋ฒ”์œ„ ์ง€์ •)๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฆ‰ ํŠน์ • CSS ํŒŒ์ผ์„ ํŠน์ • ์ปดํฌ๋„ŒํŠธ์—์„œ importํ–ˆ๋”๋ผ๋„, ๊ทธ ๊ทœ์น™์ด ์•ฑ ์ „์ฒด์— ์ ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ธ๋ผ์ธ ์Šคํƒ€์ผ๋ง

  • JSX ์š”์†Œ์— ์ง์ ‘ ์Šคํƒ€์ผ ์ง€์ •
  • ์Šคํƒ€์ผ์ด ํ•ด๋‹น ์š”์†Œ์—๋งŒ ์ ์šฉ๋จ โ†’ ์•ˆ์ „ํ•œ ์Šค์ฝ”ํ”„
  • ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๊ฐ์ฒด๋กœ ์Šคํƒ€์ผ ์ง€์ •ํ•ด์•ผ ํ•จ
<p style="color: red;">์ž˜๋ชป๋œ ๋ฐฉ์‹</p>  // HTML์—์„œ๋Š” ๊ฐ€๋Šฅํ•˜์ง€๋งŒ React์—์„œ๋Š” ์—๋Ÿฌ ๋ฐœ์ƒ
<p style=>
  ์ธ๋ผ์ธ ์Šคํƒ€์ผ ์ ์šฉ
</p>
  • style=: JSX์—์„œ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉ
  • text-align โ†’ textAlign: CSS ์†์„ฑ๋ช…์€ ์นด๋ฉœ์ผ€์ด์Šค๋กœ ํ‘œ๊ธฐ

์กฐ๊ฑด๋ถ€ ์ธ๋ผ์ธ ์Šคํƒ€์ผ

const isValid = true;
<p style=>
  ์œ ํšจ์„ฑ์— ๋”ฐ๋ฅธ ์ƒ‰ ๋ณ€๊ฒฝ
</p>

์กฐ๊ฑด๋ถ€ className ์ ์šฉ (CSS ํด๋ž˜์Šค ํ™œ์šฉ)

<label className={`label ${emailNotValid ? 'invalid' : ''}`}>
  Email
</label>
  • JSX์—์„œ๋Š” ์กฐ๊ฑด์— ๋”ฐ๋ผ ํด๋ž˜์Šค๋ฅผ ๋ถ™์ผ ๋•Œ, ์‚ผํ•ญ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ •์ .
  • ๋ฐฑํ‹ฑ(``)๊ณผ ${}๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด๋กœ ํด๋ž˜์Šค ๋ณ‘ํ•ฉ.

CSS Modules ๋ฐฉ์‹ ์„ค๋ช…

  • ๊ธฐ์กด CSS ๋ฐฉ์‹(vanilla CSS)์€ ์Šคํƒ€์ผ์˜ ์ „์—ญ ์ ์šฉ ๋ฌธ์ œ๊ฐ€ ์žˆ์Œ.
  • ํŠน์ • CSS ํด๋ž˜์Šค๊ฐ€ ์˜๋„์น˜ ์•Š๊ฒŒ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—๋„ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์Œ. ์ด์™€ ๊ฐ™์€ ๋ฌธ์ œ๋ฅผ CSS Module ๋ฐฉ์‹์œผ๋กœ ํ•ด๊ฒฐ
Header.css โžœ Header.module.css
// Header.jsx
import classes from './Header.module.css';

<p className={classes.paragraph}>ํ…์ŠคํŠธ</p>
  • ๋นŒ๋“œ ํˆด์ด classes.paragraph๋ฅผ ๊ณ ์œ ํ•œ ์ด๋ฆ„์œผ๋กœ ๋ณ€ํ™˜ (paragraph_abc123) ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์—์„œ ํ™•์ธ๊ฐ€๋Šฅ
  • ์ด ํด๋ž˜์Šค๋Š” ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ๋งŒ ์œ ํšจํ•จ โ†’ ์Šคํƒ€์ผ ์Šค์ฝ”ํ•‘ ๊ฐ€๋Šฅ
<p className={isHighlighted ? classes.highlight : ''}>์กฐ๊ฑด๋ถ€ ์Šคํƒ€์ผ</p>
  • ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ๋งŒ CSS ์ ์šฉ ๊ฐ€๋Šฅ
  • CSS ์ฝ”๋“œ๊ฐ€ ์—ฌ์ „ํžˆ JS/JSX์™€ ๋ถ„๋ฆฌ๋จ
  • CSS ํŒŒ์ผ์ด ์ปดํฌ๋„ŒํŠธ๋งˆ๋‹ค ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ์Œ โ†’ ๊ด€๋ฆฌ ๋ฒˆ๊ฑฐ๋กœ์›€

styled-components ๋ฐฉ์‹ ์„ค๋ช…

CSS-in-JS ๋ฐฉ์‹์œผ๋กœ, JavaScript ์ฝ”๋“œ ๋‚ด๋ถ€์—์„œ ์Šคํƒ€์ผ์„ ์ง์ ‘ ์ž‘์„ฑ

//์„ค์น˜
npm install styled-components
// AuthInputs.jsx
import styled from 'styled-components';

const ControlContainer = styled.div`
  margin: 1rem;
  padding: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
`;

function AuthInputs() {
  return (
    <ControlContainer>
      <label>์ด๋ฉ”์ผ</label>
      <input type="email" />
    </ControlContainer>
  );
}
  • styled.div`: ํƒœ๊ทธ๋“œ ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด(tagged template literals)์„ ์‚ฌ์šฉํ•ด ์Šคํƒ€์ผ ์ •์˜
  • ControlContainer: styled-components๊ฐ€ ์ƒ์„ฑํ•œ ํŠน๋ณ„ํ•œ div ์ปดํฌ๋„ŒํŠธ
  • ๋‚ด๋ถ€์ ์œผ๋กœ ๊ณ ์œ ํ•œ ํด๋ž˜์Šค๋ช…์ด ์ƒ์„ฑ๋˜์–ด ์ ์šฉ๋จ
  • ์Šคํƒ€์ผ๊ณผ ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ๊ฐ€ ํ†ตํ•ฉ๋จ
  • JS ๋ณ€์ˆ˜๋‚˜ ์กฐ๊ฑด์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
  • ๋™์  ์Šคํƒ€์ผ ์ ์šฉ ์šฉ์ด
    const Button = styled.button`
    background: ${props => props.primary ? 'blue' : 'gray'};
    %% ๋™์  ์Šคํƒ€์ผ ์ ์šฉ %%
    `;
    
  • CSS ์ฝ”๋“œ๊ฐ€ JS ๋‚ด๋ถ€์— ์žˆ์–ด ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์Œ
  • styled-components ํŒจํ‚ค์ง€์— ์˜์กดํ•ด์•ผ ํ•จ

styled-components ์กฐ๊ฑด๋ถ€ ์Šคํƒ€์ผ๋ง ์ ์šฉ

// ๋ ˆ์ด๋ธ”์— ์กฐ๊ฑด๋ถ€ ์ƒ‰์ƒ ์ ์šฉ
const Label = styled.label`
  color: ${props => props.$invalid ? '#ca3e51' : '#464646'};
`;

// ์ธํ’‹์— ์กฐ๊ฑด๋ถ€ ์ƒ‰์ƒ + ๋ฐฐ๊ฒฝ ์ ์šฉ
const Input = styled.input`
  background-color: ${props => props.$invalid ? '#fddddd' : '#f8f8f8'};
  color: ${props => props.$invalid ? '#ca3e51' : '#2c292b'};
  border-color: ${props => props.$invalid ? '#ca3e51' : '#ccc'};
`;
  • styled-components๋งŒ์œผ๋กœ ์™„์ „ํ•œ UI ์ œ์–ด ๊ฐ€๋Šฅ
  • className ๋Œ€์‹  props๋กœ ๋™์  ์ œ์–ด โ†’ ๋” React์Šค๋Ÿฌ์šด ๋ฐฉ์‹

Styled Components: ๊ฐ€์ƒ ์„ ํƒ์ž, ์ค‘์ฒฉ ๊ทœ์น™ & ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ

import styled from 'styled-components';

const StyledHeader = styled.header`
  text-align: center;
  margin: 2rem auto;

  & img {
    width: 5rem;
    height: 5rem;
  }

  & h1 {
    font-size: 2rem;
    color: #333;
  }

  & p {
    color: #666;
  }

  @media (min-width: 768px) {
    margin-bottom: 4rem;

    & h1 {
      font-size: 3rem;
    }
  }
`;
  • & img โ†’ Header ๋‚ด๋ถ€์˜ ์ด๋ฏธ์ง€์— ์ ์šฉ
  • & h1, & p โ†’ ๋‚ด๋ถ€ ํ…์ŠคํŠธ์— ์ ์šฉ
  • @media (min-width: 768px) ๋‚ด์— & h1 ๋“ฑ์„ ์ค‘์ฒฉํ•ด์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

Styled Components ๋ถ„๋ฆฌ

์Šคํƒ€์ผ๋“œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ์žฌ์‚ฌ์šฉ์„ฑ ์žˆ๊ฒŒ ๋งŒ๋“ค๊ธฐ

// Button.jsx
import styled from 'styled-components';

const Button = styled.button`
  background-color: #6200ee;
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  cursor: pointer;

  &:hover {
    background-color: #3700b3;
  }
`;
export default Button;
// AuthInputs.jsx
import Button from './Button';
import Input from './Input';

const AuthInputs = () => {
  return (
    <>
      <Input label="Email" type="email" invalid={false} />
      <Input label="Password" type="password" invalid={true} />
      <Button>Submit</Button>
    </>
  );
};
  • ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ๋ฅผ importํ•˜์—ฌ Wrapper ์ปดํฌ๋„ŒํŠธ ์ ์šฉ
  • ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์€ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌํ•˜๋ฉด ์œ ์ง€๋ณด์ˆ˜์„ฑ๊ณผ ์žฌ์‚ฌ์šฉ์„ฑ์ด ํ–ฅ์ƒ๋จ
  • ์Šคํƒ€์ผ์ด ์ปดํฌ๋„ŒํŠธ์™€ ํ•จ๊ป˜ ์กด์žฌํ•˜๋ฏ€๋กœ ๋ฒ”์œ„ ์ถฉ๋Œ์ด ์—†์Œ
  • CSS๋ฅผ ์•„๋Š” ๊ฒƒ์ด ์ „์ œ ์กฐ๊ฑด์ž„
  • Wrapper ์ปดํฌ๋„ŒํŠธ์˜ ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ ์ด๋Š” ๋ฆฌ์•กํŠธ์˜ ์ฒ ํ•™๊ณผ ๋ถ€ํ•ฉํ•จ

๐Ÿ“‘ Reference



Pagination


ยฉ 2025. All rights reserved.

Powered by Hydejack v9.2.1