// _app.tsx
import { ThemeProvider } from "styled-components"
<ThemeProvider theme={lightTheme}>
<component />
</ThemeProvider>
가장 원초적인 _app.tsx를 뜯어 보면서 가장 의문이 들었던 ThemeProvider
를 구글링해 보았다.
검색을 해보니, Styled-component에서 context API 기반으로 이루어진 전역적인 스타일 관리법인 듯했다.
ThemeProvider
로 감싸진 자식 컴포넌트들은 ThemeProvider
로 전달받은 theme
을 props
로 전달받아서 사용이 가능하다고 한다.
여기서 context API 라는 것이 궁금해서 또 구글링을 해보았다.
context
는 리액트 컴포넌트 간에 어떠한 값을 공유할 수 있게 해 주는 기능이다. 주로 context
는 전역적(global)으로 필요한 값을 다룰 때 사용하는데, 꼭 전역적일 필요는 없다.
리액트 컴포넌트에서 props가 아닌 또 다른 방식(전역적(global))으로 컴포넌트 간에 값을 전달하는 방법
리액트에서는 일반적으로 컴포넌트에게 데이터를 전달해주어야 할 때 props를 통해 전달한다. 그런데 깊숙히 위치한 컴포넌트에 전달해야 할 경우에는, 여러 컴포넌트를 거쳐 연달아서 props를 설정해 주어야 하기 때문에 매우 불편하다.
컴포넌트를 한두 개 정도 거쳐서 props를 전달하는 거라면 괜찮지만, 여러 개 거쳐서 전달하게 된다면 너무 불편할 것이다.
이러한 문제들은 context
를 사용하면 깔끔하게 해결할 수 있다.
context
는 createContext
라는 함수를 불러와서 만들 수 있다.
import { createContext } from 'react'
const MyContext = createContext()
context
객체 안에는 Provider라는 컴포넌트가 들어있다. 그리고 그 컴포넌트 간에 공유하고자 하는 값을 value
라는 props로 설정하면 자식 컴포넌트들에서 해당 값에 바로 접근할 수 있다.
import { createContext } from 'react'
const MyContext = createContext()
function App() {
return (
<MyContext.Provider value="Hello world">
<Component />
</MyContext>
)
}
이렇게 하면, 원하는 자식 컴포넌트에서 useContext
라는 Hook을 사용하여 context에 넣은 값에 바로 접근할 수 있다. 해당 Hook의 인자에는 createContext
로 만든 MyContext를 넣는다.
import { createContext, useContext } from 'react'
function Msg() {
const value = useContext(MyContext)
return <div>{value}</div>
}
ThemeProvider 의 작동 방식은 Context API를 기반으로 이루어져 있다. ThemeProvider로 감싸진 자식 컴포넌트들은, ThemeProvider로 전달받은 Theme을 props로 전달받아서 사용이 가능하다.
import { ThemeProvider } from 'styled-components'
import Theme from '../theme.js'
const App = () => {
return (
<ThemeProvider theme={ThemeProvider}>
<Navbar />
<Search />
</ThemeProvider>
)
}
ThemeProvider 하위에 있는 자식 컴포넌트들은 전달받은 theme의 값들을 받아서 사용하고 있다. 전달할 theme은 내가 지정한 theme의 값들을 전달해 주면 된다.
이때 theme.js 파일은 전역적으로 스타일을 관리하는 스타일 관련 코드이며, 하위 컴포넌트에게 전달할 style 요소들을 지정해주는 작업을 거쳐야 한다.
// theme.js
const fontSizes = {
small: calcRem(14),
base: calcRem(16),
lg: calcRem(18),
xl: calcRem(20),
xxl: calcRem(22),
xxxl: calcRem(24),
titleSize: calcRem(50),
};
const paddings = {
small: calcRem(8),
base: calcRem(10),
lg: calcRem(12),
xl: calcRem(14),
xxl: calcRem(16),
xxxl: calcRem(18),
};
const NavBar = styled.div`
padding: ${({theme}) => theme.paddings.xl};
font-size: ${({theme}) => theme.fontSizes.base};
`
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
Component
, pageProps
를 props로 받는다.Component
: 요청한 페이지. get / 요청을 보냈다면, Component에는 /pages/index.js 파일이 props로 내려오게 된다.pageProps
는 페이지 getInitialProps를 통해 내려받은 props들을 말한다.다중 레이아웃은 getLayout
이라는 기능을 추가해주기만 하면 된다.
import type { ReactElement, ReactNode } from 'react';
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
// 각 페이지별 적용된 레이아웃 입력 및 반환 타입 지정
/* NextPageWithLayout으로 page의 타입(ReactElement)을 지정하면,
getLayout 속성 함수를 사용할 수 있게 된다.
(? 부분은 사용해도 되고, 안 해도 되고) */
type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactElement) => ReactNode;
}
// 각 페이지를 컴포넌트 단위로 받을 커스텀 타입
/* 기존 AppProps 타입에 layout을 추가한 것*/
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
// 페이지 단위에서 정의한 레이아웃이 있다면, 해당 레이아웃을 적용한다.
const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(<Component {...pageProps} />)
}
감사한 출처
1. https://kyounghwan01.github.io/blog/React/next/basic/#app-tsx
2. https://velog.io/@velopert/react-context-tutorial
3. https://velog.io/@hoi/Styled-components-ThemeProvider%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%8A%A4%ED%83%80%EC%9D%BC-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95