[레벨2 - 미션2] 페이먼츠 기억에 남는 피드백

Nine·2022년 5월 17일
1

범용적인 Custom Hook

특정 컴포넌트에 의존적인 Custom Hook을 많이 사용했었어요.

로직 분리를 위한거라면 어쩔 수 없지 않나? 라고 생각했는데,

크루들의 코드를 보고 느꼈습니다.

공통적으로 재사용 가능한 부분만을 발췌하면 Custom Hook을 의미있게 만들 수 있겠구나!

<첫 번째 예시>

  • 카드정보 입력 시의 validator를 체크하고 괜찮다면 값을 갱신합니다.
  • 이 부분은 카드번호, 만료일, 비밀번호 등 다양한 곳에서 사용이 가능해서 의미있는 커스텀훅이라고 생각이 들었어요.
import { useContext, useState } from 'react';
import { CardInfoDispatchContext } from '../context';

function useInputHandler(validator, { type, key, prevData }) {
	const cardInfoDispatch = useContext(CardInfoDispatchContext);
  const [errorMessage, setErrorMessage] = useState('');
	
  const updateInputState = ({ name, value }) => {
    setErrorMessage('');

    try {
      validator(value);
    } catch (err) {
      setErrorMessage(err.message);
      return;
    }

    cardInfoDispatch({
      type,
      [key]: {
        ...prevData,
        [name]: value,
      },
    });
  };
	
  return { errorMessage, setErrorMessage, updateInputState };
}

export default useInputHandler;

<두 번째 예시>

  • 이 예시도 비슷합니다.
  • 상태가 사용할 준비가 되어있는지 ready 상태를 알려줍니다.
import { useEffect, useState } from "react";

const useReady = (state, validator, data) => {
  const [ready, setReady] = useState(false);

  useEffect(() => {
    setReady(!validator(state, data));
  }, [state, ready, validator, data]);

  return [ready];
};

export default useReady;

Storybook Decorator

Storybook은 우리의 앱 구동 시점과 다르게 완전 컴포넌트 독립적이예요.

따라서 Router, Redux등 다양한 외부 설정을 decorator로 설정해주어야합니다.

export const decorators = [
  Story => (
    <PathProvider>
      <CardInfoProvider initialState={initialCardInfoState}>
        <CardListProvider initialState={initialCardListState}>
          <Story />
        </CardListProvider>
      </CardInfoProvider>
    </PathProvider>
  ),
];

setState

자 이제는 정말 안전하게 이전 상태를 사용합시다.

setIsShown(!isShown)

-------vs-------

setIsShown((prev) => !prev)

useMemo, useCallback

연산이 많아 useMemo를 활용은 인정합니다.

다만, 너무 많은 dependencies들이 있죠. 과연 사용하는게 맞을까요?

const isRequiredCompleted = useMemo(
  () =>
    cardCompany.name &&
		cardCompany.hexColor &&
    isCorrectCardNumber &&
    cardDate.month &&
    cardDate.year &&
    cardCode.cvc.length === 3 &&
    isCorrectPwd,
  [cardCompany, cardDate, cardCode, isCorrectCardNumber, isCorrectPwd],
);

👉 차라리 아예 다른 모듈로 분리하고 가져다 쓰는 것이 좋을 수 있겠어요.


useLayoutEffect를 통한 width, height 설정

useLayoutEffect(() => {
	if (targetRef.current) {
    setDimensions({
      width: targetRef.current.offsetWidth,
      height: targetRef.current.offsetHeight,
    });
  }
}, []);

들어오지 않을 인자

<CardBox onClick={handleClickBox} hexColor={hexColor} large={large} data-testid="card">

handleClickBox가 들어오지 않는다면 어떨까요?

handleClickBox 가 안들어올수 있으니 handleClickBox?.() 혹은 위 handleClickBox: () => void 0

이런식으로 작성해봅시다.


컴포넌트 시작이 Fragment?

return (
    <>
  	  ...
    </>
)

Container니까 fragment 여도 괜찮은데, Container pattern은 HoC여야해요.

그러므로 다른 레이아웃이 추가적으로 붙어있으면 안됩니다! 왜 그럴까요?

레이아웃이 다른 page의 요소에 영향받지 않도록 wrapper를 만드는게 좋지 않을까요?



모달 생성 시 쌓임 맥락을 고려하자

어떤 컴포넌트가 먼저 보이게할지 결정할 수 있어요.

쌓임 맥락 - CSS: Cascading Style Sheets | MDN (mozilla.org)

Portals – React (reactjs.org)


Color 네이밍

color를 정의해줄때 어떠한 "명사"는 비추합니다.

직관적으로 알 수 있도록 GRAY_001

컬러 팔레트 (GRAY_001... WHITE_001.. 등) 를 선 정의해두고

사용하는 color만

const usingColor = {
	pageDefault : WHITE_100;
}

형태로 사용하게 되는 것이죠!

  • 하지만 포코는 usingColor에 대해서도 약간 의문을 가지셨어요!

CRA 설정 시 겹치는 eslint

직접 체크해서 eslint 설정 파일로 옮길 필요가 있겠네요!

"eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ],
    "overrides": [
      {
        "files": [
          "**/*.stories.*"
        ],
        "rules": {
          "import/no-anonymous-default-export": "off"
        }
      }
    ]
  },

URL 파일들은 env로...

const LOCAL_BASE_URL = 'http://localhost:4000';

const PRODUCT_BASE_URL = 'https://sisyphe-shopping-cart-server.herokuapp.com';

export const BASE_URL = process.env.NODE_ENV === 'production' ? PRODUCT_BASE_URL : LOCAL_BASE_URL;

컴포넌트는 return 여러 개도 괜찮아요

👇 아래처럼 분리해보는 시도 멋있어요!

if (loading) return <Loading />;
if (error) return <RequestFail />;

  return (
    <div>
			...
		</div>
	)

API 요청 개수 정하기

limit 개수와 페이지 id를 주면됩니다!

useFetch<Item[]>(`${BASE_URL}/itemList?_page=${id}&_limit=12`);

라우팅 꼼꼼히!

라우트가 매치되지 않았을 때 Not Found 페이지가 뜨는건 필수입니다!


조건문 렌더링

  • 로딩 / 실패 / 성공 케이스가 모두 조건문 기반으로 한 곳에 선언적인 코드의 의미가 떨어지는 것 같아요.

  • 삼항 연산자가 가독성을 많이 망가트리고 있는 것 같은데 susepnse, error boundary에 대해서 한번 알아보시는것도 좋겠네요~

  • • 로딩, 실패, 성공 3가지 상태를 각각 SuspenseErrorBoundary, 정상 UI로 나타내고, 선언적으로 작성한다.

React Suspense & ErrorBoundary 직접 만들기 (velog.io)


색상 네이밍 및 팔레트

숫자를 지정하는 방식도 아래 palette처럼 낮은 숫자일 때 색상이 연하면 좋을 것 같습니다 🙁
Customizing Colors - Tailwind CSS


중복된 라우팅 처리

React Router | Outlet

요건 조금 번외격인 TMI긴한데 레이아웃의 형태가 2개 이상인 경우 Outlet을 통해 해결해볼 수 있습니당.


스타일 처리

공통 로직은 FlexColumnCenter처럼 분리해서 확장하는 형태로 사용합시다.

다른 스타일들은 아래처럼 객체? 느낌으로 가져가도 좋을 것 같아요.

const Styled = {
  Wrapper: styled(FlexColumnCenter)`
    height: calc(100vh - 200px);
    gap: 40px;
  `,
  TextBox: styled.div`
    text-align: center;
    font-weight: 500;
    font-size: 20px;
    line-height: 1.4;
  `,
};

재사용성이 없는 컴포넌트는 어디에?

page/ProductList/index.js
page/ProductList/UniqueProductComponent/index.js (해당 페이지에서만 사용하는 컴포넌트가 있다면 이런식으로?)

요렇게 재사용성이 없는 컴포넌트는 페이지 내부에 폴더별로 depth를 늘려나가면서 depth끼리 컴포넌트의 위계를 맞추면 되지 않을까 싶습니다 🤔


스타일을 분리했으면 쉽게 가져와봅시다

import * as Styled from './ErrorContainer.style';
profile
함께 웃어야 행복한 개발자 장호영입니다😃

0개의 댓글