๐ [Recat] React ๋ฆฌ์กํธ ์ปดํฌ๋ํธ ์คํ์ผ๋ง
![๐ [Recat] React ๋ฆฌ์กํธ ์ปดํฌ๋ํธ ์คํ์ผ๋ง](/assets/img/thumbnail/react-thumbnail.jpg)
[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
- ใํ๊ธ์๋งใ React ์๋ฒฝ ๊ฐ์ด๋ 2025 with React Router & Redux | Udemy
- ์ด๋ฏธ์ง ์ถ์ Freepik | ์ฌ์ธ์ AI ํฌ๋ฆฌ์์ดํฐ๋ธ ํด