๐Ÿ”ฎ ์†Œ๋งˆ๋ฒ• ํ”„๋กœ์ ํŠธ -1 (container)

#. Project Map


์ œ์ž‘๋…ธํŠธ ํ•œ๋ˆˆ์—๋ณด๊ธฐ[์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ]

0. Intro

๊ธฐ๋ณธ๊ธฐ๋ฅผ ์–ผ์ถ”(๋˜ ์–ผ์ถ”์•ผ?) ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋– ์˜ค๋ฅด๋Š” ์•„์ด๋””์–ด๋“ค์„ ๋ฉ”๋ชจ์žฅ์— ์ ์–ด๋’€๋‹ค.

์•„์ด๋””์–ด๋“ค์€ ์‹ค์ œ ์‚ฌ์šฉ์ž๋“ค์ด ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋–จ๊นŒํ•˜๋Š” ๋ณตํ•ฉ์ ์ธ ์„œ๋น„์Šค๋ถ€ํ„ฐ

์‚ฌ์†Œํ•œ ์ธํ„ฐ๋ ‰์…˜ ๊ธฐ๋Šฅ๊นŒ์ง€ ๋ญ”๊ฐ€ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š”, ๋งŒ๋“ค๊ณ  ์‹ถ์€ ๋ชจ๋“  ๊ฒƒ๋“ค์„ ์ฐจ๊ณก์ฐจ๊ณก ์Œ“์•„๋‘์—ˆ๋‹ค.

๊ทผ๋ฐ ์ฒ˜์Œ๋ถ€ํ„ฐ ๊ฑฐ๋Œ€ํ•œ ์‹œ์Šคํ…œ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ๋ณด๋‹ค๋Š” ์‚ฌ์†Œํ•œ ๊ธฐ๋Šฅ๋“ค์„ ๋งŒ๋“ค์–ด๋ณด๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜์ž๋Š” ์ƒ๊ฐ์„ ํ–ˆ๋‹ค.

  1. ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์˜ ๋ชฉ์ ์€ ๊ณต๋ถ€์ด๋‹ค.
  1. ๊ฐœ๋ฐœ์€ ํ˜‘์—…ํ•˜๋Š” ๊ฒƒ์ด์ง€๋งŒ ๊ฐœ์ธํ”„๋กœ์ ํŠธ๋Š” ์ž์‹ ์ด ํ•  ์ˆ˜ ์žˆ๊ณ , ํ•˜๊ณ ์‹ถ์€ ๋ถ„์•ผ์— ์ง‘์ค‘ํ•˜๋Š” ๊ฒƒ์ด ๋‚ซ๊ฒ ๋‹ค๋ผ๋Š” ํŒ๋‹จ์„ ํ–ˆ๋‹ค.

๊ฐœ๋ฐœ์— ๋งค๋ ฅ์„ ๋Š๋ผ๊ฒŒ๋œ ๊ณ„๊ธฐ๋ฅผ ๋งˆ๋ฒ•์œผ๋กœ ํ‘œํ˜„ํ–ˆ๋“ฏ์ด (๋ธ”๋กœ๊ทธ๋ฅผ ์‹œ์ž‘ํ•˜๋ฉฐ)

ํ”„๋กœ์ ํŠธ๋ช…์„ ์†Œ๋งˆ๋ฒ• ํ”„๋กœ์ ํŠธ๋กœ ์ง€์—ˆ๋‹ค. (a.k.a ํ† ์ดํ”„๋กœ์ ํŠธ)

์ด๋ฒˆํ”„๋กœ์ ํŠธ๋Š” ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ๋“ค๊ณผ ๋กœ์ง, ํ˜น์€ ๋””์ž์ธ(ux,ui)์— ํฌ์ปค์Šค๋ฅผ ๋งž์ถ”๊ณ  ์•ž์œผ๋กœ ๋‚ด๊ฐ€ ๊ตฌ์ƒํ•œ ๊ฒƒ์„ ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•ด๋ณด๊ณ ์žํ•œ๋‹ค.

๋ฌผ๋ก  ๊ณต๋ถ€์˜ ๋ชฉ์ ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ตœ๋Œ€ํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ ๋Š๋‚€ ๊ฒƒ์ด๋‚˜ ๋ฐฐ์šด ๊ฒƒ๋“ค์„ ๋ธ”๋กœ๊ทธ์— ๊ธฐ๋กํ•  ๊ฒƒ์ด๋‹ค.

1.์‚ฌ์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ํ”„๋ ˆ์ž„์›Œํฌ

ํฐ ํ‹€์€ nextjs ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ†ตํ•ด ๋ผ์šฐํŒ…์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์ธํŽ˜์ด์ง€์—์„œ ๊ฐ๊ฐ์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋„๋ก ๊ตฌ์ƒํ•˜์˜€๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ฐ ํŽ˜์ด์ง€์˜ view๋Š” react ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

์ƒํƒœ๊ด€๋ฆฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฆฌ์•กํŠธ hooks๋ฅผ ํ†ตํ•ด์„œ ํ•˜๋˜, ๊ฐ๊ฐ์˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์—ฌ๋Ÿฌ๊ฐœ ์ผœ๋‘๊ฑฐ๋‚˜ ์ž ์‹œ background์— ๋‘˜ ๋•Œ์ฒ˜๋Ÿผ ์ „์ฒด์ ์ธ ์ƒํƒœ๊ด€๋ฆฌ๋Š” redux๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

๋ฆฌ๋•์Šค๋˜ํ•œ hooks๋ฅผ ์‚ฌ์šฉํ•˜๋ ค ํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋””์ž์ธ์€ props์ „๋‹ฌ์ด๋‚˜ ์žฌ์‚ฌ์šฉ์„ฑ, ๊ด€๋ฆฌ๊ฐ€ ์ข‹์€ styled-component๋ฅผ ์„ ํƒํ–ˆ๋‹ค.

2. ๋ ˆ์ด์•„์›ƒ ๊ตฌ์ƒ

layout

๋ฉ”์ธํŽ˜์ด์ง€

2-1. ๋””์ž์ธ ์ปจ์…‰1(๋‰ด๋ชจํ”ผ์ฆ˜)

์ „์ฒด์ ์ธ ๋””์ž์ธ ์ปจ์…‰์€ neumorphism์„ ์“ฐ๊ธฐ๋กœ ํ–ˆ๋‹ค.

๋ชจ๋˜ํ•˜๊ณ  ๋ฏธ๋‹ˆ๋ฉ€๋ฆฌ์ฆ˜ํ‹ฑํ•œ ๋Š๋‚Œ๋„ ๋Œ๋ ค์„œ ์„ ํƒํ–ˆ๋‹ค.

๋ญ”๊ฐ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  ์‹ถ๊ฒŒ ํ•˜๋Š” ux ์ ์ธ ์š”์†Œ๋„ ์ข‹์•˜๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

์‚ฌ์‹ค ux์ ์œผ๋กœ ํฐ ๊ณ ๋ฏผ์„ ํ•˜๊ณ  ์ •ํ•˜์ง„ ์•Š์•˜๋‹ค. (๊ทธ์ € ์˜ˆ์œ ๊ฒƒ ๊ฐ™์•„์„œ..)

2-2. ๋””์ž์ธ ์ปจ์…‰2(๋งฅ๋ถ)

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ ๋‚ด์— ์—ฌ๋Ÿฌ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜(๊ธฐ๋Šฅ๋“ค)์„ ๋„ฃ๋Š” ๊ฒƒ์„ ๊ตฌ์ƒํ•˜์˜€๊ณ 

๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋™์‹œ์— ์“ธ ์ˆ˜ ์žˆ๋Š” ๋ฉ€ํ‹ฐํƒœ์Šคํ‚น๋„ ๊ตฌ์ƒํ•ด๋ณด์•˜๋Š”๋ฐ

๊ทธ๋ ‡๊ฒŒ ๋– ์˜ฌ๋ฆฐ ๋””์ž์ธ ์ปจ์…‰์ด ๋งฅ๋ถ ๋ฐ์Šคํฌํƒ‘ ํ˜•ํƒœ์˜€๋‹ค.

๋ฉ”์ธํŽ˜์ด์ง€์— ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์•„์ด์ฝ˜ ํ˜•์‹์œผ๋กœ ๋„ฃ์–ด๋‘๊ณ  ํด๋ฆญํ•˜์—ฌ ์‹คํ–‰ํ•˜๋ฉฐ(๋ผ์šฐํŒ…),

์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ข…๋ฃŒํ•˜์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ ๋‹ค์‹œ ๋Œ์•„์™€๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚จ์•„์žˆ๋Š” ๊ฒƒ์„ ์ƒ๊ฐํ–ˆ๋‹ค.(๋ฆฌ๋•์Šค)

๊ทธ๋ ‡๊ฒŒ ํ•˜๋‹จ์— Docker๋ฅผ ๋‘์—ˆ๋‹ค.

2-3. ๋””ํ…Œ์ผ

ํ”„๋กœ์ ํŠธ ๊ณต์œ  ๋ ˆ์ด์•„์›ƒ (next์˜ App ์ปดํฌ๋„ŒํŠธ!)๋ฅผ ํ†ตํ•ด

ํ™”๋ฉด ์ƒ๋‹จ ์ค‘์•™์—๋Š” full screen ๋ชจ๋“œ ๋ฒ„ํŠผ๊ณผ ์ œ์ž‘๋…ธํŠธ๋งํฌ ๋ฒ„ํŠผ์„ ๋‘์—ˆ๊ณ 

ํ™”๋ฉด ์ƒ๋‹จ ์šฐ์ธก์—๋Š” ๊นƒํ—ˆ๋ธŒ ๋งํฌ๋ฅผ ๋‘์—ˆ๋‹ค.

3. ์ฝ”๋“œ์Šคํ”Œ๋ฆฌํŒ…, ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ

3-1. ์ „์ฒด์ ์ธ ๊ตฌ์กฐ

constructor

3-2. pages

pages

next ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๋ผ์šฐํŒ… ์‹œ์Šคํ…œ

3-3. components

components

์ปดํฌ๋„ŒํŠธ๋“ค์€ ๊ฐ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋”ฐ๋กœ ํด๋”๋ฅผ ๋‘์—ˆ๊ณ , ์ „์ฒด์ ์ธ ๋ ˆ์ด์•„์›ƒ ํ˜น์€ ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋ถ€๋ถ„์€ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ์— ๋‘์—ˆ๋‹ค.

3-4. public

public

ํฐํŠธ, ์ด๋ฏธ์ง€, ํŒŒ๋น„์ฝ˜ ๋“ฑ ํ”„๋กœ์ ํŠธ์— ์“ฐ์ผ static file๋“ค์„ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

3-5. reducer

reducers

๋ฆฌ๋“€์„œ๋„ ๊ฐ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜๋ณ„๋กœ ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ… ํ•ด์ฃผ์—ˆ๋‹ค.

4. ๋ผ์šฐํŒ… ์‹œ์Šคํ…œ

4-1. pages ๋””๋ ‰ํ† ๋ฆฌ

next ํ”„๋ ˆ์ž„์›Œํฌ๋Š” pages ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ํ†ตํ•ด ๋ผ์šฐํŒ…์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿฅ _error.js๋กœ Error์ปดํฌ๋„ŒํŠธ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜๊ธฐ

4-2. _app.js

App ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์Œ ๋ฐฉ์‹์œผ๋กœ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•ด๋ณด์•˜๋‹ค.

import React from 'react'
import { GlobalStyle } from '../reset.css.js'
import { Provider } from 'react-redux'
import withRedux from 'next-redux-wrapper'
import { applyMiddleware, compose, createStore } from 'redux'
import reducer from '../reducers'
import Background from '../components/Background.js'

const _app = ({ Component, store }) => {
  return (
    <>
      {/* โญ ๊ธ€๋กœ๋ฒŒ์Šคํƒ€์ผ*/}
      <GlobalStyle />
      <Provider store={store}>
        {/* โญ ๊ณตํ†ต ๋ ˆ์ด์•„์›ƒ*/}
        <Background>
          <Component />
        </Background>
      </Provider>
    </>
  )
}

const configureStore = (initialState, options) => {
  const middlewares = []
  const enhancer =
    process.env.NODE_ENV === 'production'
      ? compose(applyMiddleware(...middlewares))
      : compose(
          applyMiddleware(...middlewares),
          !options.isServer &&
            typeof window.__REDUX_DEVTOOLS_EXTENSION__ !== 'undefined'
            ? window.__REDUX_DEVTOOLS_EXTENSION__()
            : f => f
        )
  const store = createStore(reducer, initialState, enhancer)
  return store
}

export default withRedux(configureStore)(_app)

4-3. โญ <GlobalStyle />

styled component์˜ createGloadStyle์„ ์ด์šฉํ•ด ์ „์ฒด์ ์ธ css๋ถ€๋ถ„์„ reset.css.js ํŒŒ์ผ์— ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

// reset.css.js
import { createGlobalStyle } from 'styled-components'

export const GlobalStyle = createGlobalStyle`

// ...

`

๋ชจ๋“  css ์†์„ฑ๊ณผ ๊ฐ’์„ resetํ•ด์ฃผ๊ณ , ์‚ฌ์šฉํ•  ๋กœ์ปฌ ํฐํŠธ ๋“ฑ์„ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

4-4. โญ Background.js

Component (next์—์„œ๋Š” pages๋ฅผ ์˜๋ฏธ)๋“ค์„ Background ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ์‹ธ์ฃผ์—ˆ๋‹ค. Background ์ปดํฌ๋„ŒํŠธ๋Š” ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ์ ์šฉ๋  ๋ ˆ์ด์•„์›ƒ ์š”์†Œ (Header, Footer ๋“ฑ)๋“ค์„ ๋‹ค์‹œ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค.

// Background.js
import React from 'react'
import styled from 'styled-components'
import Header from './Header'
import Footer from './Footer'

const BackgroundContainer = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
  background: #f5f6f7;
`

const FixedGithub = styled.div`
  position: fixed;
  z-index: 10;
  right: 0;
  top: 0;
  margin: 6px;

  & > img {
    filter: invert(48%) sepia(13%) saturate(3207%) hue-rotate(130deg) brightness(
        95%
      ) contrast(60%);
    cursor: pointer;
  }
`

const Background = ({ children }) => {
  return (
    <>
      <BackgroundContainer>
        <a href="https://github.com/taenykim/" target="_blank">
          <FixedGithub>
            <img src="./github.png" width="28" height="28" />
          </FixedGithub>
        </a>
        <Header></Header>
        {children}
        <Footer></Footer>
      </BackgroundContainer>
    </>
  )
}

export default Background

4-5. _document.js

Document ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์Œ ๋ฐฉ์‹์œผ๋กœ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ํ•ด๋ณด์•˜๋‹ค.

import React from 'react'
import { ServerStyleSheet } from 'styled-components'
import Document, { Html, Head, Main, NextScript } from 'next/document'

class _document extends Document {
  static getInitialProps({ renderPage }) {
    const sheet = new ServerStyleSheet()
    const page = renderPage(App => props =>
      sheet.collectStyles(<App {...props} />)
    )
    const styleTags = sheet.getStyleElement()
    return { ...page, styleTags }
  }
  render() {
    return (
      <Html>
        <Head>{this.props.styleTags}</Head>
        <body style={{ fontFamily: 'escore3' }}>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default _document

styled-components๊ฐ€ ๋ Œ๋”๋ง ๋˜๊ณ  ์ ์šฉ๋˜๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด getInitialProps๋ฅผ ํ†ตํ•˜์—ฌ ์Šคํƒ€์ผ์„ ์ ์šฉ์‹œํ‚จ ํ›„ ๋ Œ๋”๋ง์„ ํ•˜๋„๋ก ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

4-6. _index.js

๋ฉ”์ธํŽ˜์ด์ง€

import React from 'react'
import styled from 'styled-components'
import AppIcon from '../components/AppIcon'
import AppName from '../components/AppName'

const IndexContainer = styled.div`
  display: flex;
  width: 90vw;
  height: 90vh;
  margin: 10px 10px 10px 10px;
`

const AppContainer = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 5px 20px 10px 20px;
`

const index = () => {
  return (
    <>
      <IndexContainer>
        <AppContainer>
          <AppIcon name="calculator" />
          <AppName name="calculator" />
        </AppContainer>
        <AppContainer>
          <AppIcon name="graph" />
          <AppName name="graph" />
        </AppContainer>
      </IndexContainer>
    </>
  )
}

export default index

๋ฉ”์ธํŽ˜์ด์ง€๋Š” AppIcon๊ณผ AppName ์ปดํฌ๋„ŒํŠธ๋ฅผ importํ•ด์„œ ๊ตฌ์„ฑํ•˜์˜€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ๊ฐ์˜ props๋กœ ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„์„ ๋„˜๊ฒจ์ค˜์„œ ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ง์ ‘ ๊ฐ’์ด ๋ฐ”๋€Œ๊ฒŒ๋” ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ์— ์ดˆ์ ์„ ๋งž์ถฐ๋ณด์•˜๋‹ค.

4-7. application.js

// calculator page
import React from 'react'
import Layout from '../components/calculator/Layout'

const calculator = () => {
  return (
    <>
      <Layout />
    </>
  )
}

export default calculator
// graph page
import React from 'react'
import Layout from '../components/graph/Layout'

const graph = () => {
  return (
    <>
      <Layout />
    </>
  )
}

export default graph

๊ฐ๊ฐ์˜ ํŽ˜์ด์ง€์— ์ง์ ‘ ๊ธฐ๋Šฅ๋“ค์„ ๋„ฃ๋Š” ๊ฒƒ๋ณด๋‹ค ํ•ด๋‹น ๊ธฐ๋Šฅ๋“ค์ด ๋‹ด๊ธด components ๋ฅผ ํ•œ๋ฒˆ์— ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ฒƒ์ด ๊ด€๋ฆฌ๊ฐ€ ์‰ฌ์šธ ๊ฒƒ ๊ฐ™์•„ components ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์˜ ํ•ด๋‹น application ์ด๋ฆ„์˜ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ  layout ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‘์—ˆ๊ณ  ๊ทธ๊ฒƒ์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌ์„ฑํ•˜์˜€๋‹ค.

5. ์ปดํฌ๋„ŒํŠธ

5-1. Header (FullScreen Mode)

in Background Component

import React, { useEffect, useState } from 'react'
import styled from 'styled-components'

const HeaderContainer = styled.div`
  display: flex;
  position: fixed;
  top: 0;
  margin-top: 10px;
  z-index: 5;
  font-size: 12px;
  font-family: escore6;
  color: #666;
`

const FullscreenText = styled.p`
  cursor: pointer;
`

const NoteText = styled.p`
  cursor: pointer;
`

const Header = () => {
  const [full, setFull] = useState(false)

  useEffect(() => {
    document.addEventListener('fullscreenchange', () => {
      setFull(!full)
    })
  })

  const openFullScreen = () => {
    if (!document.fullscreenElement) {
      document.documentElement.requestFullscreen()
    } else {
      document.exitFullscreen()
    }
  }

  return (
    <HeaderContainer>
      <FullscreenText onClick={openFullScreen}>
        {full ? 'exit full screen' : 'full screen'}
      </FullscreenText>
      &nbsp;&nbsp;|&nbsp;&nbsp;<NoteText>note</NoteText>
    </HeaderContainer>
  )
}

export default Header
  1. position : fixed๋กœ ์œ„์น˜๊ณ ์ •.
  2. ์ดˆ๊ธฐ full state ๊ฐ’์„ false๋กœ ๋‘๊ณ (hooks) document ๊ฐ์ฒด์— fullscreenchange ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๊ณ  ์ด๋ฒคํŠธ ๋ฐœ์ƒ์‹œ full state๊ฐ’์„ toggle ํ•˜๋„๋ก ํ•˜์˜€๋‹ค.
  3. ๊ทธ๋ฆฌ๊ณ  pํƒœ๊ทธ์— click ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ๊ณ  ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ˜„์žฌ ์Šคํฌ๋ฆฐ๋ชจ๋“œ๋ฅผ ์ธ์‹ํ•ด์„œ ์Šคํฌ๋ฆฐ๋ชจ๋“œ๋ฅผ toggleํ•˜๋„๋ก ํ•˜์˜€๋‹ค.
  4. ๋งˆ์ง€๋ง‰์œผ๋กœ ์‚ผํ•ญ์—ฐ์‚ฐ์ž๋ฅผ ํ†ตํ•ด ํ˜„์žฌ ์Šคํฌ๋ฆฐ๋ชจ๋“œ๊ฐ€ pํƒœ๊ทธ์— ์ ํžˆ๋„๋ก ํ•˜์˜€๋‹ค.

in Background Component

import React from 'react'
import styled from 'styled-components'
import { useSelector } from 'react-redux'
import DockerIcon from './DockerIcon'

const FooterContainer = styled.div`
  display: flex;
  align-items: center;
  position: fixed;
  bottom: 0;
  width: 100%;
  height: 60px;
  background: rgba(0, 0, 0, 0.85);
  border-radius: 8px 8px 0px 0px;
`

const Docker = styled.div`
  display: flex;
  align-items: center;
  padding: 0px 20px 0px 20px;
`

const Footer = () => {
  const { docker } = useSelector(state => state.wrapper)
  return (
    <FooterContainer>
      <Docker>
        {docker.map((item, i) => {
          // โญ
          return <DockerIcon key={i} item={item} />
        })}
      </Docker>
    </FooterContainer>
  )
}

export default Footer
  1. MacBook ํ•˜๋‹จ์˜ ๊ทธ Docker๊ฐ€ ๋งž๋‹คโ€ฆ
  2. useSelector ๋ฅผ ํ†ตํ•ด docker array๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  3. ๊ทธ๋ฆฌ๊ณ  ํ˜„์žฌ docker array ์•ˆ์— ์žˆ๋Š” ์š”์†Œ๋“ค์„ mapํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด DockerIcon ์ปดํฌ๋„ŒํŠธ์— ์š”์†Œ์˜ ๊ฐ’์„ props๋กœ ์ „๋‹ฌํ•˜๊ณ  ๋ทฐ๋ฅผ ๊ตฌ์„ฑํ•˜๋„๋ก ํ–ˆ๋‹ค.
  4. docker์˜ ์š”์†Œ ๊ฐ’์€ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„์ด string ํ˜•ํƒœ๋กœ ์ €์žฅ๋˜์–ด์žˆ๋‹ค.
{
  "docker": ["calculator"]
}

5-3. โญ DockerIcon

in Footer Component

import React from 'react'
import styled from 'styled-components'
import Link from 'next/link'

const AppIconContainer = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  width: 40px;
  height: 40px;
  box-sizing: border-box;
  border-radius: 15%;
  background: #f5f6f7;
  color: ${props => {
    if (props.name === 'calculator') return 'red'
    else if (props.name === 'graph') return 'blue'
    else return 'black'
  }};
  margin-right: 10px;

  & > div {
    font-family: escore9;
    font-size: 20px;
    text-shadow: 3px 3px #ccc;
  }
`

const DockerIcon = ({ item }) => {
  const url = `/${item}`
  return (
    <Link href={url}>
      <a style={{ textDecoration: 'none' }}>
        <AppIconContainer name={item}>
          <div>{item[0].toUpperCase()}</div>
        </AppIconContainer>
      </a>
    </Link>
  )
}

export default DockerIcon
  1. DockerIcon์€ ํ˜„์žฌ docker array ์•ˆ์˜ string์„ props๋กœ ๋ฐ›๋Š”๋‹ค.
  2. ๊ทธ props string์€ ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ด๋ฆ„์ด๋ฉฐ url๋กœ๋„ ์‚ฌ์šฉํ•œ๋‹ค.
  3. ๐ŸŽ ๊ทธ๋ฆฌ๊ณ  styled components ์˜ props๋กœ๋„ ๋„˜๊ฒจ์ค˜์„œ ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„์— ๋”ฐ๋ผ ์•„์ด์ฝ˜ ์ƒ‰์ƒ์„ ๋ณ€๊ฒฝํ•˜๊ฒŒ๋” ํ•ด์ฃผ์—ˆ๋‹ค.

6. ๋ฆฌ๋•์Šค(์ƒํƒœ๊ด€๋ฆฌ)

6-1. ContentsMenubar ์ปดํฌ๋„ŒํŠธ

in Application > layout Component

contentsmenubar

์ด ๋ถ€๋ถ„..!

import React from 'react'
import styled from 'styled-components'
import Link from 'next/link'
import { useDispatch } from 'react-redux'
import {
  STORE_CALCULATOR_DATA,
  STORE_RESET_CALCULATOR,
} from '../reducers/calculator'
import { STORE_GRAPH_DATA, STORE_RESET_GRAPH } from '../reducers/graph'
import { DOCKER_STORE, DOCKER_DELETE } from '../reducers/wrapper'

const ContentsMenubarContainer = styled.div`
  display: flex;
  align-items: center;
  width: 100%;
  height: 60px;
  top: 0;
  & img {
    width: 17px;
    height: 17px;
  }
`

const ImageContainer = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  height: 30px;
  width: 30px;
  box-shadow: -4px -2px 4px 0px #ffffff, 4px 2px 6px 0px #ddd;
  border-radius: 2px;
  padding: 2px;
  &:hover {
    cursor: pointer;
  }
  &:active {
    box-shadow: 2px 2px 2px 0px #dfe4ea inset, -2px -2px 2px 0px white inset;
  }
  & > img {
    filter: invert(48%) sepia(13%) saturate(3207%) hue-rotate(130deg) brightness(
        95%
      ) contrast(80%);
  }
`

// โญ props ?!
const ContentsMenubar = ({ data, name }) => {
  const dispatch = useDispatch()

  // โญ redux action dispatch ?!
  const storeHandler = () => {
    switch (name) {
      case 'calculator': {
        dispatch({
          type: STORE_CALCULATOR_DATA,
          data: data,
        })
        dispatch({
          type: DOCKER_STORE,
          data: name,
        })
        return
      }
      case 'graph': {
        dispatch({
          type: STORE_GRAPH_DATA,
          data: data,
        })
        dispatch({
          type: DOCKER_STORE,
          data: name,
        })
        return
      }
      default: {
        dispatch({
          type: DOCKER_STORE,
          data: name,
        })
        return
      }
    }
  }

  const storeReset = () => {
    switch (name) {
      case 'calculator': {
        dispatch({
          type: STORE_RESET_CALCULATOR,
        })
        dispatch({
          type: DOCKER_DELETE,
          data: name,
        })
        return
      }
      case 'graph': {
        dispatch({
          type: STORE_RESET_GRAPH,
        })
        dispatch({
          type: DOCKER_DELETE,
          data: name,
        })
        return
      }
      default: {
        dispatch({
          type: DOCKER_DELETE,
          data: name,
        })
        return
      }
    }
  }

  return (
    <ContentsMenubarContainer>
      <Link href="/">
        <a style={{ margin: '2px 2px 2px 10px' }}>
          <ImageContainer onClick={storeReset}>
            <img src="cancel.png"></img>
          </ImageContainer>
        </a>
      </Link>
      <Link href="/">
        <a style={{ margin: '2px 2px 2px 10px' }}>
          <ImageContainer onClick={storeHandler}>
            <img src="bottom_arrow.png"></img>
          </ImageContainer>
        </a>
      </Link>
    </ContentsMenubarContainer>
  )
}

export default ContentsMenubar

6-2. โญ ContentsMenubar ์ปดํฌ๋„ŒํŠธ์˜ props

// calculator > layout.js
<ContentsMenubar
  data={{ result, tempResult, pressedOperator, isFirstNumberTyping }}
  name="calculator"
></ContentsMenubar>

data props

๋ฆฌ๋•์Šค ์Šคํ† ์–ด์— ์ €์žฅํ•˜๊ณ ํ”ˆ ๋ฐ์ดํ„ฐ๋“ค์„ ๊ฐ์ฒดํ˜•ํƒœ๋กœ ๋„˜๊ฒจ์คŒ

name props

ํ˜„์žฌ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ด๋ฆ„

6-3. wrapper ๋ฆฌ๋“€์„œ (Docker)

export const initialState = {
  docker: [],
}

export const DOCKER_STORE = 'DOCKER_STORE'
export const DOCKER_DELETE = 'DOCKER_DELETE'

export default (state = initialState, action) => {
  switch (action.type) {
    case DOCKER_STORE: {
      // docker์— ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์žˆ์œผ๋ฉด return
      if (state.docker.indexOf(action.data) >= 0)
        return {
          ...state,
        }
      //docker์— ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์—†์œผ๋ฉด push
      return {
        ...state,
        docker: [...state.docker, action.data],
      }
    }
    // filter๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ delete
    case DOCKER_DELETE:
      return {
        ...state,
        docker: state.docker.filter((v, i) => action.data !== v),
      }
    default: {
      return {
        ...state,
      }
    }
  }
}

6-4. application ๋ฆฌ๋“€์„œ (calculator)

export const initialState = {
  result: '0',
  tempResult: '',
  pressedOperator: '',
  isFirstNumberTyping: true,
}

export const STORE_CALCULATOR_DATA = 'STORE_CALCULATOR_DATA'
export const STORE_RESET_CALCULATOR = 'STORE_RESET_CALCULATOR'

export default (state = initialState, action) => {
  switch (action.type) {
    // contentsMenubar ์—์„œ ์ €์žฅ์„ ํ–ˆ์œผ๋ฉด ๋ฐ์ดํ„ฐ ์ €์žฅ
    case STORE_CALCULATOR_DATA: {
      return {
        result: action.data.result,
        tempResult: action.data.tempResult,
        pressedOperator: action.data.pressedOperator,
        isFirstNumberTyping: action.data.isFirstNumberTyping,
      }
    }
    // contentsMenubar์—์„œ ๋‹ซ๊ธฐ๋ฅผ ํ–ˆ์œผ๋ฉด ์ดˆ๊ธฐ๋ฐ์ดํ„ฐ๋กœ ์ดˆ๊ธฐํ™”
    case STORE_RESET_CALCULATOR: {
      return {
        result: '0',
        tempResult: '',
        pressedOperator: '',
        isFirstNumberTyping: false,
      }
    }

    default: {
      return {
        ...state,
      }
    }
  }
}

7. ๊ฐœ์ธ์ ์ธ ํ”ผ๋“œ๋ฐฑ

7-1. SSR

์„œ๋ฒ„์‚ฌ์ด๋“œ๋ Œ๋”๋ง์ด ์•„์ง ์•ˆ๋˜์—ˆ๋‹ค.

7-2. ๋ฐ˜์‘ํ˜•

๋ฐ˜์‘ํ˜• ์•„์ง ์•ˆ๋˜์—ˆ๋‹ค.


Written by@taenyKim
๋ฐฐ์šฐ๋ฉฐ ์„ฑ์žฅํ•˜๊ณ  ๊ธฐ๋กํ•˜๊ธฐ #FE #UI #๊ฐœ๋ฐœ #life

GitHubFacebook