레이아웃?
- 프로젝트의 기본적인 화면 구조를 잡는다
- 반복적으로 들어가야하는 헤더, 푸터 등 매 화면마다 제공한다.
- 상황과 필요에 따라 레이아웃이 변경될 수 있도록 대비한다.
Header, Footer 통일된 레이아웃 컴포넌트
방법
- 각 화면 페이지마다 컴포넌트 호출
- 레이아웃
pico css 사용
index.html `<linkrel="stylesheet"href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"/>`
dark 테마
각 페이지마다 공통 컴포넌트 호출하는 경우
Home.tsx
import React from "react";
import Header from "../components/common/Header";
import Footer from "../components/common/Footer";
const Home = () => {
return (
<>
<Header />
<div>home</div>
<Footer />
</>
);
};
export default Home;
Detail.tsx
import React from "react";
import Header from "../components/common/Header";
import Footer from "../components/common/Footer";
import Sidebar from "../components/common/Sidebar";
const Detail = () => {
return (
<>
<Header />
<Sidebar />
<div>디테일</div>
<Footer />
</>
);
};
export default Detail;
공통되는 부분이 많기 때문에, 레이아웃으로 구성하는게 좋다.
레이아웃으로 구성하는 경우
import React from "react";
import Header from "../common/Header";
import Footer from "../common/Footer";
interface LayoutProps {
children: React.ReactNode;
}
const Layout = ({ children }: LayoutProps) => {
return (
<>
<Header />
<main>{children}</main>
<Footer />
</>
);
};
export default Layout;
children을 이용해서 ReactNode 타입 지정해서, 내용물에서 사용한다.
ReactNode 타입 : 리액트로 만든 모든 컴포넌트들이 배치될 수 있다. 집합 형태로 구분된다. 외부 라이브러리나 다른쪽에서 React Element, JSX Element를 사용한 경우 타입오류가 발생하는 경우도 있다.
App.tsx 에서 레이아웃을 호출하면서, props로 children의 Home을 전달한다.
import Layout from "./components/layout/Layout";
import Home from "./pages/Home";
function App() {
return <Layout children={<Home />} />;
}
export default App;
or 일반적인 형태(html)로 사용할 수도 있음
import Layout from "./components/layout/Layout";
import Home from "./pages/Home";
function App() {
return (
<Layout>
<Home />
</Layout>
);
}
export default App;
inedx.tsx, route에서도 레이아웃을 제공할 수 있다.
route 경로마다 레이아웃이 변경되는 경우가 있기 때문에 Outlet 을 이용해 레이아웃 지정하는 경우가 많다.
Global style
프로젝트에 일관된 스타일링을 적용한다.
`user agent stylesheet`로 표시되는 브라우저의 기본 스타일이 차이를 만든다.
브라우저 간의 스타일 차이를 극복하기 위해 사용한다.
에릭 마이어 reset css
역사가 깊고, 아직도 많이 쓰이는 근본 css
모든 element 의 스타일을 리셋, 즉 0 으로 만든다.
normalize.css
reset css 다음으로 오래되고, 많이 쓰인다. 각각 고유의 속성(h1, h2의 차이)를 유지하면서, 기기간의 차이를 줄이는것을 목적으로 한다.
학습하기 좋은 코드
sanitize.css
normalize 발전된 버전이다. 목적은 같고, selecter 부분에서 개선되어진 부분이다.
sanitize 사용
`npm install sanitize.css --save`
index.tsx 하위에 import 해주면 적용된다.
개발자도구에 where 셀렉트를 이용해서 평탄화 작업을 진행한다.
where 셀렉트 : css 중복 작성을 줄이기 위한 helper selector. 해당 태그가 어디에 있던지 해당 속성을 적용하겠다.
styled component : css in js
근본 css 로 해결할 수 없는 문제점을 해결하였다.
- css 태그 명의 하위 속성값의 명확성 부족하다. 추측하기 어려움.
- 전달해야할 상태가 여러개라면 한번에 적용하거나 형태를 적용하기 까다롭다.
- 여러개의 css 파일을 import 했을 경우, 무엇이 우선인지 파악하기 어렵다.
필요한 이유?
- 전역 충돌
- 의존성 관리 어려움
- 불필요한 코드, 오버라이딩 : 여러개의 상태 한번에 정의
- 압축
- 상태 공유 어려움
- 순서와 명시도
- ⭐ 캡슐화
각 언어의 흐름을 자연스럽게 하기위한 목적으로 styled component 사용한다
설치
`npm install styled-components`
import React from "react";
import { styled } from "styled-components";
const Header = () => {
return (
<HeaderStyle>
<h1>book store</h1>
</HeaderStyle>
);
};
const HeaderStyle = styled.header`
background-color: #333;
h1 {
color: white;
}
`;
export default Header;
Tailwind css 와 유사한 사용법!!!
파일을 분리해서 사용할 수 있다. > 추가로 찾아보기!
이때 개발자 도구로 확인하면 난수화 된 클래스 명을 가진 것으로 표시된다.
헤더라는 컴포넌트의 스타일을 캡슐화 하기 위해서, 제한하기 위해서!
이전에 index.tsx 에 적용한 글로벌 스타일을 따로 폴더에 `createGlovalStyle` 메소드로 구성한다!
src > sytle > global.ts
import { createGlobalStyle } from "styled-components";
import "sanitize.css";
export const GlobalStyle = createGlobalStyle``;
index.tsx 에 global 호출해서 적용한다.
styled-component 테마 ThemeProver
- UI / UX의 일관성 유지
- 유지보수가 용이
- 확장성
- 재사용성
- 사용자 정의
theme provider : 프로젝트를 구성하는 컴포넌트가 배치된다. 컴포넌트보다 상위에 위치한다.
theme 1, theme 2로 자유롭게 변경할 수 있다.
테마는 확장성 때문에 자주 수정되는 부분이기 때문에 타입으로 관리하면 좋다.
interface Theme {
name: string;
color: {
primary: string;
background: string;
};
}
export const light: Theme = {
name: 'light',
color: {
primary: 'brown',
background: 'lightgray',
},
};
export const dark: Theme = {
name: 'dark',
color: {
primary: 'coral',
background: 'midnightblue',
},
};
이때 color는 무한정 추가하는 방식은 좀 무리... 배열로 바꿀 수 있다, key 를 제한하는 타입
type ThemeName = 'light' | 'dark';
type ColorKey = 'primary' | 'background' | 'secondary' | 'third';
interface Theme {
name: ThemeName;
color: Record<ColorKey, string>;
/*{
[key in ColorKey]: string;
};*/
}
theme css 에 적용하는 코드
const HeaderStyle = styled.header`
background-color: ${({theme}) => theme.color.background};
h1 {
color: ${({theme}) => theme.color.primary};
}
`;
GloblaStyle 코드에 theme 에 따른 변경 코드 적용하기
import { createGlobalStyle } from 'styled-components';
import 'sanitize.css';
import { ThemeName } from './theme';
interface Props {
themeName: ThemeName;
}
export const GlobalStyle = createGlobalStyle<Props>`
body {
padding:0;
margin:0;
}
h1{
margin:0;
}
*{
color : ${(props) => (props.themeName === 'light' ? 'black' : 'white')}
}
`;
Theme Swicher with Context API
사용자는 토글 UI를 통해 웹사이트의 색상 테마를 바꿀수 있다. 이때 색상 테마는 전역상태로 존재하고, 사용자의 선택값을 로컬스토리지에 저장한다.
이때 테마는 app.tsx에서 변환해주고 있기 때문에 import는 app.tsx 에서 해주기!
import React from 'react';
import { light, ThemeName } from '../../style/theme';
interface Props {
themeName: ThemeName;
setThemeName: (themeName: ThemeName) => void;
}
const ThemeSticher = ({ themeName, setThemeName }: Props) => {
const toggleTheme = () => {
setThemeName(themeName === 'light' ? 'dark' : 'light');
};
return <button onClick={toggleTheme}>{themeName}</button>;
};
export default ThemeSticher;
ThemeSwicher.tsx 에서 ThemeProvider 안에 ThemeSwicher 을 넣어준다. + GlobalStyle 스타일 지정해주기
//theme.ts
export const getTheme = (themeName: ThemeName): Theme => {
switch (themeName) {
case 'light':
return light;
case 'dark':
return dark;
}
};
context api 변경
해당 스위치 토글을 어디에서든 사용할 수 있도록 변경한다.
App에서 전체적으로 사용하려면, App 보다 상위인 index.tsx 에서 제공해주어야 한다. > 추천하는 방법이 아니다. > 이따가 context에 provider 제공해서 app.tsx 에서 최상위 감싸는 컴포넌트가 될 것!
themeContext.tsx
import { createContext, ReactNode } from 'react';
import { ThemeName } from '../style/theme';
interface State {
themeName: ThemeName;
setThemeName: (themeName: ThemeName) => void;
}
export const state = {
themeName: 'light' as ThemeName,
setThemeName: (themeName: ThemeName) => {},
};
export const ThemeContext = createContext<State>(state);
export const BookStoreThemeProvider = ({children}:{children:ReactNode}) =>{
return(
<ThemeContext.Provider value={state}>
{children}
</ThemeContext.Provider>
)
}
themeName 을 'light'로만 작성했을때, 타입이 아닌 스트링으로 인식하기 때문에, `'light' as ThemeName` 으로 작성해야 한다.
import Layout from './components/layout/Layout';
import Home from './pages/Home';
import { GlobalStyle } from './style/global';
import { ThemeProvider } from 'styled-components';
import { dark, getTheme, light, ThemeName } from './style/theme';
import ThemeSwicher from './components/header/ThemeSwicher';
import { useContext, useState } from 'react';
import { BookStoreThemeProvider, ThemeContext } from './context/themeContext';
function App() {
const { themeName, setThemeName } = useContext(ThemeContext);
return (
<BookStoreThemeProvider>
<ThemeProvider theme={getTheme(themeName)}>
<GlobalStyle themeName={themeName} />
<ThemeSwicher themeName={themeName} setThemeName={setThemeName} />
<Layout children={<Home />} />
</ThemeProvider>
</BookStoreThemeProvider>
);
}
export default App;
최상위로 감싸고, context를 useContext 상태값으로 가져와서 전역으로 사용한다.
토글기능 : state
import { createContext, ReactNode, useState } from 'react';
import { getTheme, ThemeName } from '../style/theme';
import { ThemeProvider } from 'styled-components';
import { GlobalStyle } from '../style/global';
interface State {
themeName: ThemeName;
toggleTheme: () => void;
}
export const state = {
themeName: 'light' as ThemeName,
toggleTheme: () => {},
};
export const ThemeContext = createContext<State>(state);
export const BookStoreThemeProvider = ({
children,
}: {
children: ReactNode;
}) => {
const [themeName, setThemeName] = useState<ThemeName>('light');
const toggleTheme = () => {
setThemeName(themeName === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ themeName, toggleTheme }}>
<ThemeProvider theme={getTheme(themeName)}>
<GlobalStyle themeName={themeName} />
{children}
</ThemeProvider>
</ThemeContext.Provider>
);
};
이전의 테마 상태들을, 컨텍스트로 싹다 가져옴
결과적으로! app.tsx 의 BookStorethemeProvider로 감싸서, 해당 컴포넌트는 themeContext 에서 전역적으로 관리하고 있다.
이때 toggleTheme가 파라미터가 필요한데, 코드가 길어지거나 중요하지 않아 추천하지 않는다. themeContext.tsx에서 toggleTheme 타입 생성시 파라미터 값 없앤다.
themeSwicher.tsx 가 컨텍스트를 참조하도록 한다.
import React, { useContext } from 'react';
import { ThemeContext } from '../../context/themeContext';
const ThemeSticher = () => {
const { themeName, toggleTheme } = useContext(ThemeContext);
return <button onClick={() => toggleTheme}>{themeName}</button>;
};
export default ThemeSticher;
새로고침 시 테마 light로 초기화된다. 전역상태 초기화 > localstorage 에 저장해서 테마값 유지할 수 있도록 한다.
//themeContext.tsx
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);
}, []);
정리
themeContext.tsx에서 creageContext를 이용해 ThemeContext를 만들고 전역으로 사용하도록 export 해주었다.
<BookStoreThemeProvider> 하위의 컴포넌트들이 context를 구독해서 꺼내서 사용한다 `useContext` 에서 생성한 객체를 넣어서 사용한다. 커스텀 context api, provider 컴포넌트에 넣어서 `ThemeContext.Provider`,`ThemeProvider`, `GlobalStyle`모아서 사용하였다.
즉 테마 관련 모든 css 는 themeContext.tsx의 `BookStoreThemeProvider` 에서 지정할 수 있다.
☑️ 배운 점
레이아웃, 컨텍스트 api