본문 바로가기

React

App.js 다이어트

개인적으로 app.js는 간결하고 가독성이 좋은 것을 선호하지만

막~~ 개발을 열심히 하다보면 어느 순간...

import { ThemeProvider } from 'styled-components'
import original from 'react95/dist/themes/original'
import Layout from './components/layout/Layout'
import BgIcon from 'components/bgIcon/BgIcon'
import myComputer from 'asset/images/myComputer.png'
import trashIcon from 'asset/images/trash.png'
import networkIcon from 'asset/images/network.png'
import { useAppWindowRender } from 'utils/zustand/useAppWindowRender'
import MyProjectsWindow from 'components/AppWindow/windows/MyProjectsWindow'
import MyInfoWindow from 'components/AppWindow/windows/MyInfoWindow'
import TrashCanWindow from 'components/AppWindow/windows/TrashCanWindow'
import notePadIcon from 'asset/images/notepad.png'
import GuestBookWindow from 'components/AppWindow/windows/GuestBookWindow'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import BlogWindow from 'components/AppWindow/windows/BlogWindow'
import neki from 'asset/images/네키.png'
import { Route, Routes, useNavigate } from 'react-router-dom'
import LoginWindow from 'components/AppWindow/windows/login/LoginWindow'
import useEscapeKey from 'utils/hook/useEsacpeKey'

export default function App() {
  const { windowRenderObj, onClickWindowOpen, closeAllWindow } = useAppWindowRender()
  const queryClient = new QueryClient()
  const navigate = useNavigate()
  useEscapeKey(closeAllWindow)
  return (
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={original}>
        <Layout>
          <Layout.WindowContainer>
            <BgIcon
              title="내 정보"
              imgObj={{ src: myComputer, alt: 'myComputer' }}
              onDoubleClick={() => onClickWindowOpen('myInfoWindow')}
              border="1px solid rgb(0, 128, 128)"
              color="white"
            />
            <BgIcon
              title="내 프로젝트"
              imgObj={{ src: networkIcon, alt: 'networkIcon' }}
              onDoubleClick={() => onClickWindowOpen('myProjectWindow')}
              border="1px solid rgb(0, 128, 128)"
              color="white"
            />
            <BgIcon
              title="방명록"
              imgObj={{ src: notePadIcon, alt: 'trashicon' }}
              onDoubleClick={() => onClickWindowOpen('guestBookWindow')}
              border="1px solid rgb(0, 128, 128)"
              color="white"
            />
            <BgIcon
              title="블로그"
              imgObj={{ src: neki, alt: 'trashicon' }}
              onDoubleClick={() => {
                onClickWindowOpen('blogWindow')
                navigate('/blog')
              }}
              border="1px solid rgb(0, 128, 128)"
              color="white"
            />
            <BgIcon
              title="휴지통"
              imgObj={{ src: trashIcon, alt: 'trashicon' }}
              onDoubleClick={() => onClickWindowOpen('trashCanWindow')}
              border="1px solid rgb(0, 128, 128)"
              color="white"
            />
            {windowRenderObj.myInfoWindow.isOpen && <MyInfoWindow />}
            {windowRenderObj.myProjectWindow.isOpen && <MyProjectsWindow />}
            {windowRenderObj.trashCanWindow.isOpen && <TrashCanWindow />}
            {windowRenderObj.guestBookWindow.isOpen && <GuestBookWindow />}
            {windowRenderObj.loginWindow.isOpen && <LoginWindow/>}
          </Layout.WindowContainer>
          <Layout.TaskBar />
          <Routes>
            <Route path="/blog/*" element={windowRenderObj.blogWindow.isOpen && <BlogWindow />} />
          </Routes>
        </Layout>
      </ThemeProvider>
    </QueryClientProvider>
  )
}

 

이렇게 더러워진다... 무수한 import문을 보면 정말 정신이 아득해진다 (import문만 거진 20줄..ㅋㅋㅋ)

일단 위의 더러운 코드를 세 부분으로 나누어 따로 컴포넌트를 만들어 세 컴포넌트만 import 해주는 방식으로 하려한다

  • 아이콘을 랜더링 해주는 <BgIcon/> 만을 모아둔 컴포넌트, 그리고 border,color 프롭처럼 반복되는 부분은 제거하자
  • 아이콘 더블 클릭 시 랜더링되는 윈도우 창을 모아둔 컴포넌트
  • routes 컴포넌트

이렇게만 나눠도 우리의 App.js는 많이 깔끔해질것 같다

 

BgIcon 컴포넌트

import styles from './Background.module.css'
import { useAppWindowRender } from 'utils/zustand/useAppWindowRender'
import BgIcon from 'components/layout/background/components/BgIcon'
import myComputer from 'asset/images/myComputer.png'
import trashIcon from 'asset/images/trash.png'
import networkIcon from 'asset/images/network.png'
import neki from 'asset/images/네키.png'
import { useNavigate } from 'react-router-dom'

export default function Background() {
  const { onClickWindowOpen } = useAppWindowRender()
  const navigate = useNavigate()
  const bgIconArr = [
    {
      title: '내 정보',
      imgObj: { src: myComputer, alt: 'myComputer' },
      onDoubleClick: () => onClickWindowOpen('myInfoWindow'),
    },
    {
      title: '내 프로젝트',
      imgObj: { src: networkIcon, alt: 'networkIcon' },
      onDoubleClick: () => onClickWindowOpen('myProjectWindow'),
    },
    {
      title: '방명록',
      imgObj: { src: networkIcon, alt: 'networkIcon' },
      onDoubleClick: () => onClickWindowOpen('guestBookWindow'),
    },
    {
      title: '블로그',
      imgObj: { src: neki, alt: 'nekiIcon' },
      onDoubleClick: () => {
        onClickWindowOpen('blogWindow')
        navigate('/blog')
      },
    },
    {
      title: '휴지통',
      imgObj: { src: trashIcon, alt: 'trashicon' },
      onDoubleClick: () => onClickWindowOpen('trashCanWindow'),
    },
  ]
  return (
    <div className={styles.boxContainer}>
      {bgIconArr.map(item => (
        <BgIcon title={item.title} imgObj={item.imgObj} onDoubleClick={item.onDoubleClick} />
      ))}
    </div>
  )
}

 

배열 객체를 만들어 map함수로 랜더링되게 처리하였고 border와 color 프롭은 제거하였다

 

WindowRender 컴포넌트

import { Suspense, lazy } from 'react'
import { useAppWindowRender } from 'utils/zustand/useAppWindowRender'

const GuestBookWindow = lazy(() => import('components/AppWindow/windows/GuestBookWindow'))
const MyInfoWindow = lazy(() => import('components/AppWindow/windows/MyInfoWindow'))
const MyProjectsWindow = lazy(() => import('components/AppWindow/windows/MyProjectsWindow'))
const TrashCanWindow = lazy(() => import('components/AppWindow/windows/TrashCanWindow'))
const LoginWindow = lazy(() => import('components/AppWindow/windows/login/LoginWindow'))

export default function WindowRender() {
  const { windowRenderObj } = useAppWindowRender()

  return (
    <Suspense>
      {windowRenderObj.myInfoWindow.isOpen && <MyInfoWindow />}
      {windowRenderObj.myProjectWindow.isOpen && <MyProjectsWindow />}
      {windowRenderObj.trashCanWindow.isOpen && <TrashCanWindow />}
      {windowRenderObj.guestBookWindow.isOpen && <GuestBookWindow />}
      {windowRenderObj.loginWindow.isOpen && <LoginWindow />}
    </Suspense>
  )
}

 

각 윈도우 컴포넌트들은 특정 조건을 만족했을 때만 랜더링되므로 성능을 위해 동적 임포트를 사용하여 구현하였다

 

 

Routes 컴포넌트

import BlogWindow from 'components/AppWindow/windows/BlogWindow'
import { Route, Routes } from 'react-router-dom'
import { useAppWindowRender } from 'utils/zustand/useAppWindowRender'

export default function AppRouter() {
  const { windowRenderObj } = useAppWindowRender()

  return (
    <Routes>
      <Route path="/blog/*" element={windowRenderObj.blogWindow.isOpen && <BlogWindow />} />
    </Routes>
  )
}

위와 동일하게 라우팅을 담당하는 부분만 따로 뺐다

 

App.js

import { ThemeProvider } from 'styled-components'
import original from 'react95/dist/themes/original'
import Layout from './components/layout/Layout'
import { useAppWindowRender } from 'utils/zustand/useAppWindowRender'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import useEscapeKey from 'utils/hook/useEsacpeKey'
import AppRouter from 'routes/AppRouter'

export default function App() {
  const { closeAllWindow } = useAppWindowRender()
  const queryClient = new QueryClient()
  useEscapeKey(closeAllWindow)
  return (
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={original}>
        <Layout>
          <Layout.Background />
          <Layout.WindowRender />
          <Layout.TaskBar />
          <AppRouter />
        </Layout>
      </ThemeProvider>
    </QueryClientProvider>
  )
}

 

복잡하던 app.js가 상당히 간결해진 것을 확인 할 수 있다