
π― λ μ΄μμ ꡬμ±μ μ‘κ³
sanitize.cssμstyled-componentsλ₯Ό μ¬μ©ν΄μ ν λ§λ₯Ό μ μ©μν΅λλ€.
λ μ΄μμμ μ½ν μΈ λ₯Ό νλ©΄μ΄λ νμ΄μ§μ λ°°μΉνλ ꡬ쑰λ λ°©μμ λ§ν©λλ€.
π Home Page
βββ <Header/> β μλ¨ ν€λ μμ (κ³΅ν΅ μ»΄ν¬λνΈ)
βββ <Home/> β λ©μΈ μ½ν
μΈ μμ (ν νλ©΄ μ μ©)
βββ <Footer/> β νλ¨ νΈν° μμ (κ³΅ν΅ μ»΄ν¬λνΈ)
π Detail Page
βββ <Header/> β μλ¨ ν€λ μμ (κ³΅ν΅ μ»΄ν¬λνΈ)
βββ μ½ν
μΈ μμ
β βββ <Sidebar/> β μΌμͺ½ μ¬μ΄λλ° (μΆκ° λ©λ΄ λ±)
β βββ μμΈ μ»΄ν¬λνΈ β μ€λ₯Έμͺ½ μμΈ μ½ν
μΈ (νμ΄μ§ λ΄μ©)
βββ <Footer/> β νλ¨ νΈν° μμ (κ³΅ν΅ μ»΄ν¬λνΈ)
π€ μ λ μ΄μμμ ꡬμ±ν΄μΌ ν κΉ?
- νλ©΄ ꡬ쑰λ₯Ό μ€κ³ν μ μμ΅λλ€.
- Header, Footer κ°μ κ³΅ν΅ μμλ₯Ό ν λ²μ κ΄λ¦¬ν μ μμ΅λλ€.
- λ€μν μν©μ λ§κ² μ μ°νκ² νλ©΄μ λ°κΏ μ μμ΅λλ€.
μΆμ²: MDN
import Footer from '../common/Footer';
import Header from '../common/Header';
interface LayoutProps {
children: React.ReactNode; // μμ μμλ₯Ό λ°μμ¬ μ μλλ‘ νμ
μ€μ
}
export default function Layout({ children }: LayoutProps) {
return (
<>
<Header />
<main>{children}</main>
<Footer />
</>
);
}
React.ReactNode : Reactμμ λ λλ§ κ°λ₯ν λͺ¨λ νμ
μ μΌμ»«μ΅λλ€. (ex. λ¬Έμμ΄, μ«μ, JSX, μ»΄ν¬λνΈ, λ°°μ΄ λ±)React.ReactNode > ReactElement > JSX.Elementexport default function App() {
return (
<Layout children={<Home />} />
);
}
export default function App() {
return (
<Layout>
<Home /> // children
</Layout>
);
}
λ μ΄μμ μ»΄ν¬λνΈλ μ΄λ€ νμ΄μ§λ <Layout> μμ λ£κΈ°λ§ νλ©΄ Headerμ Footerλ μλμΌλ‘ ν¬ν¨λ©λλ€.
Global Styleμ΄λ νλ‘μ νΈ μ 체μ μ μ©λλ μ μ μ€νμΌμ λ§ν©λλ€.
π€ μ Global Styleμ μμ±ν κΉ?
user agent stylesheetλ‘ νμλλ λΈλΌμ°μ λ§λ€ λ€λ₯Έ κΈ°λ³Έ μ€νμΌ μ°¨μ΄λ₯Ό μμ κΈ° μν΄ μ¬μ©ν©λλ€.
Reset CSS
λͺ¨λ μμμ κΈ°λ³Έ μ€νμΌμ μμ κ³ μμ ν μ΄κΈ°ν ν©λλ€.
normalize.css
λΈλΌμ°μ κ° μ€νμΌ μ°¨μ΄λ₯Ό μμ κ³ , μλ―Έ μλ κΈ°λ³Έ μ€νμΌμ μ μ§νλ©° μ΄κΈ°ν ν©λλ€.
β¨ sanitize.css
μ¬μ©μμ μ κ·Όμ±μ ν΄μΉμ§ μμΌλ©΄μλ κΈ°λ³Έ λΈλΌμ°μ μ€νμΌ μ°¨μ΄λ₯Ό μ 리ν΄μ£ΌκΈ° λλ¬Έμ, λ§μ΄ μ¬μ©λκ³ μμ΅λλ€.
styled-componentsλ CSS-in-JS λ°©μμ λΌμ΄λΈλ¬λ¦¬λ‘ JavaScript νμΌ μμμ μ€νμΌμ μ μνκ³ , μ΄λ₯Ό React μ»΄ν¬λνΈμ²λΌ μ¬μ©ν μ μκ² ν΄μ£Όλ λꡬμ
λλ€.
<npm install styled-components
π€ μ CSS-in-JS λ°©μμΌλ‘ μμ±ν κΉ?
κΈ°μ‘΄ CSS νμΌ μμ±μλ λͺ κ°μ§ λ¨μ πμ΄ μμ΅λλ€.
- μ μ μ€νμΌ μΆ©λ
- μ»΄ν¬λνΈ κ°μ μ€νμΌ μ¬μ¬μ©μ μ΄λ €μ
- ν΄λμ€λͺ μ€λ³΅, μ€λ²λΌμ΄λ© 볡μ‘ν¨
- CSS μμ λ¬Έμ λ‘ μ°μ μμ κΌ¬μ λ°μ
π‘ μ΄λ¬ν λ¬Έμ μ μ ν΄κ²° νκΈ° μν΄ CSS-in-JS λ°©μμ΄ λ±μ₯νμ΅λλ€. λνμ μΌλ‘
styled-componentsκ°μ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νλ©΄ λ€μκ³Ό κ°μ μ₯μ πμ΄ μ겨λ©λλ€.
- μ€νμΌμ μ»΄ν¬λνΈννμ¬ μ½λ κ΅¬μ‘°κ° λͺ νν΄μ§
- μ¬μ¬μ©μ± λκ³ μ μ§λ³΄μ μ©μ΄
- λμ μ€νμΌλ§μ΄ μ¬μ
sanitize.cssμ styled-componentsλ₯Ό μ¬μ©ν΄μ μ€νμΌμ μ μ©νμμ΅λλ€.
// style/global.ts
import 'sanitize.css';
import { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
body {
padding: 0;
margin: 0;
}
h1 {
margin: 0;
}
`;
// App.tsx
import { GlobalStyle } from './styles/GlobalStyle';
function App() {
return (
<>
<GlobalStyle />
<Layout>
<Home />
</Layout>
</>
);
}
GlobalStyleμ λ³΄ν΅ App.tsx μ΅μλ¨μμ ν λ²λ§ μ μ©ν©λλ€.
ν λ§λ μΉμ¬μ΄νΈμ μ¬μ©λλ μμ, ν°νΈ, κ°κ²© λ±μ μκ°μ μμλ€μ ν κ³³μμ κ΄λ¦¬νλ λ°©μμ λλ€.
π€ μ ν λ§λ₯Ό μ¬μ©ν κΉ?
- UX/UI μΌκ΄μ± μ μ§
- μ μ§λ³΄μμ κΈ°λ₯ νμ₯μ μ 리
- μ¬μ¬μ©μ±κ³Ό κ°λ μ± μ¦κ°
- μ¬μ©μ λ§μΆ€ μ€μ κ°λ₯
type ThemeName = 'light' | 'dark';
type ColorKey = 'primary' | 'background' | 'secondary' | 'third';
interface Theme {
name: ThemeName;
color: Record<ColorKey, string>;
}
export const light: Theme = {
name: 'light',
color: {
primary: 'brown',
background: 'lightgrey',
secondary: 'blue',
third: 'green',
},
};
export const dark: Theme = {
name: 'dark',
color: {
primary: 'coral',
background: 'midnightblue',
secondary: 'darkblue',
third: 'darkgreen',
},
};
Record<ColorKey, string>λ‘ μΈν΄ colorλ λ€μκ³Ό κ°μ ꡬ쑰λ₯Ό κ°μ΅λλ€. color: {
primary: string;
background: string;
secondary: string;
third: string;
}
β οΈ
colorκ°μ²΄λ λ°λμ 4κ°μ ν€λ₯Ό λͺ¨λ κ°μ§κ³ μμ΄μΌ ν©λλ€.κ°μ νλ μ΄μ λ μ»΄ν¬λνΈλ₯Ό λ§λ€ λ ν λ§ μμμ μ¬μ©νλ λΆλΆμμλ μμΈ μμ΄ μΌκ΄λ μ€νμΌμ μ€ μ μκΈ° λλ¬Έμ λλ€.
const SomeComponent = styled.div` color: ${({ theme }) => theme.color.primary}; background: ${({ theme }) => theme.color.background}; `;
Context APIλ Reactμ μ μ μν κ΄λ¦¬ λκ΅¬λ‘ μ»΄ν¬λνΈλ₯Ό μ¬λ¬ λ¨κ³ λ΄λ €κ°μ§ μκ³ λ λ°μ΄ν°λ₯Ό 곡μ ν μ μκ² ν΄μ€λλ€.
Context APIλ₯Ό νμ©ν΄ ν λ§ μ€μμ²λ₯Ό λ§λλλ€.
μ¬μ©μλ ν κΈ UIλ₯Ό ν΅ν΄ λΌμ΄νΈ/λ€ν¬ ν λ§λ₯Ό μ νν μ μλλ‘ μ€μ ν©λλ€.
ν λ§ μνλ Contextλ‘ κ΄λ¦¬νμ¬ μ 체 μ»΄ν¬λνΈμμ μ κ·Ό κ°λ₯νλλ‘ ν©λλ€.
μ¬μ©μκ° μ€μ ν ν λ§λ λ‘컬 μ€ν 리μ§μ μ μ₯νμ¬ μλ‘κ³ μΉ¨ν΄λ μ μ§λκ² ν©λλ€.
ThemeContext.tsximport { createContext, ReactNode, useEffect, useState } from 'react';
import { getTheme, ThemeName } from '../style/theme';
import { ThemeProvider } from 'styled-components';
import { GlobalStyle } from '../style/global';
const DEFAULT_THEME_NAME = 'light';
const THEME_LOCALSTORAGE_KEY = 'book_store_theme';
interface State {
themeName: ThemeName;
toggleTheme: () => void;
}
export const state = {
themeName: DEFAULT_THEME_NAME as ThemeName,
toggleTheme: () => {},
};
export const ThemeContext = createContext<State>(state);
export const BookStoreThemeProvider = ({
children,
}: {
children: ReactNode;
}) => {
const [themeName, setThemeName] = useState<ThemeName>(DEFAULT_THEME_NAME);
const toggleTheme = () => {
setThemeName(themeName === 'light' ? 'dark' : 'light');
localStorage.setItem(
THEME_LOCALSTORAGE_KEY,
themeName === 'light' ? 'dark' : 'light'
);
};
useEffect(() => {
const savedThemeName = localStorage.getItem(
THEME_LOCALSTORAGE_KEY
) as ThemeName;
setThemeName(savedThemeName || DEFAULT_THEME_NAME);
}, []);
return (
<ThemeContext.Provider value={{ themeName, toggleTheme }}>
<ThemeProvider theme={getTheme(themeName)}>
<GlobalStyle themeName={themeName} />
{children}
</ThemeProvider>
</ThemeContext.Provider>
);
};
BookStoreThemeProviderλ μ΄ ν
λ§ μνλ₯Ό μ μμΌλ‘ 곡κΈνλ μ»΄ν¬λνΈμ
λλ€.
νμ¬ ν
λ§ μνλ useStateλ‘ κ΄λ¦¬ν©λλ€.
λ²νΌ ν΄λ¦ μμ toggleThemeμ νΈμΆν΄ ν
λ§λ₯Ό λ°μ μν€κ³ , ν
λ§λ localStorageμ μ μ₯νμ¬ μλ‘κ³ μΉ¨ν΄λ μ μ§λκ² ν©λλ€.
useEffectλ μ²μμ λ λλ§λ λ([]), λ‘컬 μ€ν 리μ§μμ ν
λ§ κ°μ λΆλ¬μ΅λλ€. κ°μ΄ μμΌλ©΄, κΈ°λ³Έκ°μΌλ‘ lightλ₯Ό μ¬μ©ν©λλ€.
Contextμ styled-componentsμ ThemeProviderλ₯Ό ν¨κ» κ°μΈμ£Όμ΄ returnν΄μ€λλ€.
ThemeContext.Provider : React λ΄λΆμμ ν
λ§ μ΄λ¦(themeName)κ³Ό ν
λ§ λ³κ²½ ν¨μ(toggleTheme)λ₯Ό μ»΄ν¬λνΈ μ΄λμλ μ¬μ©ν μ μκ² ν΄μ€λλ€.
ThemeProvider : μ€νμΌμ μ¬μ©λλ ν
λ§ μμλ€μ μ μ©μμΌμ€λλ€.
ThemeSwitcher.tsximport { useContext } from 'react';
import { ThemeContext } from '../../context/ThemeContext';
export default function ThemeSwitcher() {
const { themeName, toggleTheme } = useContext(ThemeContext);
return <button onClick={toggleTheme}>{themeName}</button>;
}
useContextλ‘ themeNameκ³Ό toggleThemeλ₯Ό λ°μμ λ²νΌμ λ λλ§ν©λλ€.App.tsximport Layout from './components/layout/Layout';
import Home from './pages/Home';
import ThemeSwitcher from './components/header/ThemeSwitcher';
import { BookStoreThemeProvider } from './context/ThemeContext';
function App() {
return (
<BookStoreThemeProvider>
<ThemeSwitcher />
<Layout>
<Home />
</Layout>
</BookStoreThemeProvider>
);
}
export default App;
BookStoreThemeProvider μ»΄ν¬λνΈλ μ± μ·¨μμμμ μ¬μ©λλ©° μ 체 ν
λ§ μνλ₯Ό κ΄λ¦¬ν©λλ€.λΈλΌμ°μ μ€νμΌμ μ΄κΈ°ννκ³ , styled-componentsμ ν
λ§ μμ€ν
π / πμ μ΄μ©ν΄μ λ§λ€μ΄λ³΄λ κ² μ½μ§ μμμ§λ§, ν
λ§ μνλ₯Ό μ μμμ κ΄λ¦¬νλ μ°μ΅μ λ λ§μ΄ ν΄μΌν κ² κ°λ€.