TIL_2024_01_13

이종현·2024년 1월 13일
0

Today_I_Learned

목록 보기
121/145
post-thumbnail

Today 요약

  1. 리액트 강의
  2. 토이 프로젝트

1. What I Learned?

리액트 강의

컴포넌트 나누기

컴포넌트를 나누는 부분에 있어서 그냥 완전히 단순하게만 생각해왔었다. 하지만 오늘 강의를 들으면서 약간 새로운 시각으로 컴포넌트를 나누는 걸 느끼고 나서 앞으로 코드 구현할 때 조금 더 컴포넌트를 분리할 수 있는 게 없을지 고민하는 과정을 반드시 거쳐야겠다는 생각을 했다.

const App = () => (
  <div className="ProductPage">
    <div className="Page">
      <header>
        <Title>메뉴목록</Title>
      </header>
      <main>
        <ul>
          <li>
            <ProductItem product={fakeProduct} />
          </li>
        </ul>
      </main>
      <footer>
        <Navbar />
      </footer>
    </div>
  </div>
)

위에 있는 App 컴포넌트를 보면 내가 기존에 항상 작업했던 결과물이 저렇게 생겼었다. 하지만 위에 있는 App 컴포넌트를 아래와 같이 Page 컴포넌트로 다시 한 번 추상화를 할 수 있다. 코드 자체만 보면 그렇게 어렵지 않은 코드지만 저렇게 생각 자체를 할 수 있다는 게 중요한 거라 생각한다.

const App = () => (
  <div className="ProductPage">
    <Page header={<Title>메뉴목록</Title>} footer={<Navbar />}>
      <ul>
        <li>
          <ProductItem product={fakeProduct} />
        </li>
      </ul>
    </Page>
  </div>
)
const Page = ({ header, children, footer }) => (
  <div className="Page">
    <header>{header}</header>
    <main>{children}</main>
    <footer>{footer}</footer>
  </div>
)

기능 구현을 빠르게 하는 것도 중요하지만 코드를 작성할 때 고민이 너무 없으면 안된다는 걸 이걸 통해 느끼는 것 같다. 내 코드에 그동안 너무 고민이 없었다.

import 정리하기

사용하지 않는 import 정리하기 → option + shift + o

import Navbar from './components/Navbar'
import ProductItem from './components/ProductItem'
import Title from './components/Title'
import Page from './components/Page'
import Product from './pages/Product'

const App = () => <Product />

export default App

위에 처럼 사용하지 않는 import가 많을 때는 option + shift + o를 누르면 한 번에 정리가 가능하다!! 정말 유용!

2. What I did?

토이 프로젝트

Hook의 사용

const goToThePage = () => {
  if (text === '로그인') {
    useUrlCheckAndRoute('/')
  } else {
    useUrlCheckAndRoute('/register')
  }
}

훅을 사용할 때는 컴포넌트의 최상위에 있어야 한다. 위의 코드는 useUrlCheckAndRoute라는 훅이 실행되지 않는다. 컴포넌트의 최상위에 없고 함수에서 실행되기 때문이다. 그래서 실행하려면 아래와 같이 고쳐야 한다.

const urlCheckAndRoute = useUrlCheckAndRoute // 최상위 수준에서 훅 호출

const goToThePage = () => {
  if (text === '로그인') {
    urlCheckAndRoute('/') // 이미 훅에서 반환된 함수 사용
  } else {
    urlCheckAndRoute('/register')
  }
}

url에 경로에 따라 원하는 컴포넌트 보여주기

현재 쇼핑몰 토이프로젝트에서 네비게이션 컴포넌트를 로그인, 회원가입 페이지에서는 보여주지 않고 다른 페이지에서는 보여주고 싶었다. 추후에 다른 쇼핑몰을 진행할 때는 Next로 진행할 것이기 때문에 지금 생각해본 부분을 완전히 똑같이 적용하기는 힘들겠지만, 그래도 고민을 해본 만큼 그때 가서 활용하려고 할 때 조금 더 수월할 거라고 생각한다.

url 경로가 바뀌는 상황은 현재 토이프로젝트에서 여러가지 상황이 있다. 네비게이션의 홈 버튼이랑 카트 버튼, 로그인 버튼을 누르면 바뀌게 되고 로그인 페이지에서는 회원가입 페이지로 이동하거나 메인페이지로 이동하는 버튼 회원가입 페이지에서는 로그인 페이지로 이동하는 버튼이 있다 이때 마다 url을 체크해서 로그인, 회원가입 페이지에서는 네비게이션 컴포넌트를 보여주지 않고 나머지 url에서는 보여줘야 한다.

처음에는 단순하게 state로 관리하려고 했으나, prop으로 내려줘야 하는 상황인데다가 App 컴포넌트에서 Outlet으로 라우팅을 하고 있으니 Context API를 사용하거나 아니면 아래와 같은 방법을 사용해야 할 것이다.

const App = () => {
  const [state, setState] = useState('some state')

  return <Outlet context={{ state, setState }} />
}

결국에는 props 드릴링이 발생하기 때문에 그냥 이참에 recoil을 사용해볼 겸, url path를 recoil로 선언해서 그에 따라 원하는 경로를 보여줄 수 있도록 구현했다.

import { atom } from 'recoil'

export const urlState = atom<string>({
  key: 'urlState',
  default: ''
})
const App = () => {
  const pathname = window.location.pathname

  const setUrlPath = useSetRecoilState(urlState)
  const path = useRecoilValue(urlState)

  useEffect(() => {
    setUrlPath(pathname)
  }, [pathname])

  return (
    <section className="flex flex-col w-full min-w-690 max-w-1024 h-full min-h-500 m-auto">
      {!(path === '/login' || path === '/register') && <Nav />}
      <Outlet />
    </section>
  )
}
import { useNavigate } from 'react-router-dom'
import { useRecoilValue, useSetRecoilState } from 'recoil'
import { urlState } from '../../recoil/url/atom'

type ButtonProps = {
  type?: 'submit' | 'reset' | 'button' | undefined
  className?: string
  text: string
}

const AuthButton = ({
  type = 'submit',
  className = 'w-full p-10 mt-4 rounded-sm bg-blue-500 text-white',
  text
}: ButtonProps) => {
  const navigate = useNavigate()
  const setUrlPath = useSetRecoilState(urlState)
  const path = useRecoilValue(urlState)

  const goToThePage = () => {
    if (path === '/register') {
      setUrlPath('/login')
      navigate('/login', { replace: true })
    } else {
      if (text === '로그인') {
        setUrlPath('/')
        navigate('/', { replace: true })
      } else {
        setUrlPath('/register')
        navigate('/register', { replace: true })
      }
    }
  }

  return (
    <button type={type} className={className} onClick={goToThePage}>
      {text}
    </button>
  )
}

export default AuthButton

중복코드 제거하기(feat. 단일 책임 원칙)

이 즈음 되면서 중복을 제거할 만한 부분이 없을까를 찾아보다가 setUrlPath와 navigate 코드가 중복되는 부분이 많은 걸 체크했다. 하지만 두 코드를 하나의 함수로 추출한다고 했을 때 단일 책임 원칙을 준수하고 있는지에 대한 부분을 명확하게 설명하기가 힘들었다.

내가 생각해 본 것은 두 가지다. 첫째는 setUrlPath는 recoil에 path를 저장해서 관리하는 로직이고 navigate는path를 이동하는 로직인데 두 개가 서로 다른 책임을 가지고 있는 거 아닐까라는 생각이 들었다. 하지만 두 번째 들었던 생각은 setUrlPath나 navigate모두 path와 관련된 로직이기 때문에 path를 관리하는 두 개의 로직은 하나의 함수로 관리해도 path를 관리하는 단일한 책임을 가지고 있는게 아닐까라는 생각이었다.

처음 생각은 단순히 단일 책임을 따지려고 한 게 아니라 코드의 중복이 발생하고 가독성을 조금이나마 더 좋게 하려는 목적이 더욱 강했다. 하지만 이렇게 내가 맞지 않더라도 계속 생각해보면서 구현하는 게 맞다고 생각한다.

나중에 지인들 중에 잘하는 분들한테 이 부분에 대해서 물어봐야겠다.

profile
데이터리터러시를 중요하게 생각하는 프론트엔드 개발자

0개의 댓글