[레벨2 - 미션2] 1단계 페이먼츠 피드백

Nine·2022년 5월 17일
0

미션2 레벨2 페이먼츠 1단계 PR

리액트는 useState, useEffect면 끝이지~ 라고 생각했던 지난 날들을 반성하게 되었습니다..

이번 미션에서는 제가 알지 못했던 부분들을 정말 많이 알게 되었어요.

물론 피드백들을 전부 적용하기란 너무 어렵겠지만, 앞으로 시도할 것들이 많아졌다는 것은 또 큰 행운이네요😀😀

자 1단계 페이먼츠 피드백을 한 번 살펴볼까요?


매칭되지 않는 라우팅 챙기기

매칭되지 않는 라우팅을 챙기는 것은 정말 필수입니다.

예를 들면 switch ~ case를 사용한다거나 NotFound 페이지를 제작하는 것이죠!

<BrowserRouter>
  <Routes>
    <Route path={PATH.HOME} element={<Home cards={cards} />} />
    <Route path={PATH.HOME2} element={<Home cards={cards} />} />
    <Route path="/*" element={<NotFound />} /> // 좋아요👍👍
  </Routes>
</BrowserRouter>

const checkRoutes = (route) => {
  switch (route) {
    case "add-card":
      return <CardFormPage targetRef={targetRef} />
    default: // 좋아요👍👍
      return `${route}는 존재하지 않는 경로입니다.`;
  }
};

API 계층 분리하기 (feat. useFetch)

컴포넌트 안에 있는 API를 분리하는 것이 좋아요.
API는 어디서든 쓸 수 있기 때문이죠!
useFetch같은 커스텀훅을 만들어서 공통적으로 사용해보면 어떨까요?

// 오~ 좋아요👍👍
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const { setError } = useContext(ErrorContext);

  useEffect(() => {
    const callApi = async () => {
      try {
        const res = await fetch(url, {
          method: "GET",
          headers: { "Content-type": "application/json" },
        });

        if (!res.ok) {
          throw new Error("서버에서 데이터를 불러오는데 실패했습니다");
        }
        const data = (await res.json()).data;
        setData(data);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };

    callApi();
  }, [url, setError]);

  return { data, loading };
}

Memoization은 언제 필요할까

무지성 useCallback, memo, useMemo 맞을까요?

언제 넣으면 좋을까? 를 고민하고 넣읍시다.


어디서 관리하는것이 좋을까

// 조건부 렌더링
{description && <StyledLabel>{description}</StyledLabel>}

{
  isModalOpened && shouldShowTypeSelection && (
    <CardSelectModal
      closeModal={closeModal}
      onChangeCardType={onChangeCardType}
    />
  );
}

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

// 일단 컴포넌트 렌더링
<CardSelectModal
  isOpened={isModalOpened && shouldShowTypeSelection}
  closeModal={closeModal}
  onChangeCardType={onChangeCardType}
/>

<Modal isOpen={isShown} setIsOpen={setIsShown} dimensions={dimensions}>
  <GridContainer>
    {cardCompanyList.map(({ color, name }, index) => (
      <CardCompany
        hexColor={color}
        name={name}
        key={index}
        handleClick={() => handleClickCompany({ color, name })}
      />
    ))}{" "}
  </GridContainer>
</Modal>

필요할 때 로드하겠다! 이런 느낌이죠.

관련 키워드라면 suspense가 있어요.

React.lazy() import...

사이드 이펙트 주의

e.target.value같은 것은 사이드 이펙트가 크게 날 수 있어요.

👉 리액트 상태가 아니라 브라우저 상태를 받고 있기 때문입니다.

⚠️위험합니다!

const handleCVCChange = (e) => {
  if (isNaN(e.target.value)) {
    e.target.value = e.target.value.slice(0, e.target.value.length - 1);
    return;
  }

  handleCardCVCCheck(e.target.value.length === 3);
};

NaN

Number.isNaN 쓰세요


왜 data-name을 쓰나요?

일단 관리 포인트가 늘어요.
😱 리액트는 custom props가 있어서 쓰면 되는데 굳이 data-name을 쓰나요?

data attribute를 좋아서 쓰라는 것이 아닙니다. → 다시 말해 브라우저에서 쓰라고 만든 것이 아닙니다.

👆 하도 약속을 안 지키니까 에혀..그냥 data-attribute 써라. 요런 느낌!

  • data attribute를 쓰면 일단 DOM에 노출되잖아요. 사용자가 알 필요가 없는 정보예요.

  • 무조건 쓰지 말라는 건 아닌데 무지성으로 쓰지는 맙시다.

근데 코드 까보셨나요?

  • styled-component 쓰니깐 짜식들이 제 코드에 data-set을 넣어버리네요?! 화가 나야죠 😠

이런 걸 궁금해야합니다. (최소 배포할 때에는 없애야죠)


조건부 props 넘기기

// 이렇게 할 필요없어요.
<Header
  onClickPreviousButton={
    pageLocation !== DEFAULT_PAGE ? onClickPreviousButton : null
  }
/>

// 요렇게 넘길 수 있어요. 값식문을 이해해야합니다.
<SomeComponent {...(condition && { onClickPreviousButton })} />

function App() {
  let props = {
    type: "text",
    disabled: true,
  };

  if (condition) {
    props.type = "submit";
    props.prop2 = false;
  }

  return <SomeButton {...props} />
}

예약어와 비슷한 네이밍 주의

Error, render 이런 네이밍이 괜찮을까요ㅠㅠ?

export default function Error() {
  ...
}

const render = (
  <Container>
	 ...
  </Container>
);

Error Boundaries 시도하기

function useAsyncError() {
  const [_, setError] = useState();

  return useCallback(
    (e) => {
      setError(() => {
        throw e;
      });
    },
    [setError]
  );
}

이전 상태 믿지 말기

prevState를 사용합시다.


기본 설정 확인하기

prettier를 단적으로 생각해봅시다.
🤔 왜 80까지만 허용할까 (default를 왜 그렇게 정해놨을까?)

{
  "useTabs": false,
  "tabWidth": 2,
  "printWidth": 100,
  "arrowParens": "avoid",
  "trailingComma": "all",
  "endOfLine": "auto",
  "singleQuote": true
}
  • 예전에는 개발자 모니터 사이즈가 80 정도까지만 커버가 가능했는데 이제는 그 이상도 가능하다고 하네요.
  • 그래서 default를 80이 아니라 그 이상이어야 한다는 의견도 있어요.

UI와 분리하기

😀😀 아래 코드가 복잡하다구요? 하지만 좋아요.

const FormDataProvider = ({ children }) => {
  return (
    <CardTypeProvider>
      <CardNumberProvider>
        <ExpireDateProvider>
          <UserNameProvider>
            <SecurityCodeProvider>
              <CardPasswordProvider>{children}</CardPasswordProvider>
            </SecurityCodeProvider>
          </UserNameProvider>
        </ExpireDateProvider>
      </CardNumberProvider>
    </CardTypeProvider>
  );
};

어떤 스타일이든 유용하게 적용할 수 있을 것 같아요.

😀 headless UI : HTML, JS는 줄게, CSS는 니가 알아서해~ 위의 코드가 이런 느낌을 주네요.

😢 Material UI를 쓴다고 생각해봅시다.

  • 스타일이 너무 강하게 의존되어 있어서 오버라이딩이 안돼요.

children 활용하기

💪 children 활용은 필수죠. 이제 입이 아프네요.

<Header title="보유 카드 목록 💳" />
<DescriptionIconButton iconImage={QuestionMarkIcon} description={SECURITY_CODE_DESCRIPTION} />

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

<Header>
  보유 카드 목록 💳
</Header>
<DescriptionIconButton iconImage={QuestionMarkIcon} description={SECURITY_CODE_DESCRIPTION} />

테마 활용하기

theme 활용 좋아요!

const colors = {
  pageDefault: "#fff",
  // ...
}

Curly Brace 사용 기준 정리

<Header>{'카드추가'}</Header>

<Header> 카드추가 </Header> // 이게 더 좋지 않나요?
{"<"}
  </LinkButton>
  <PageTitle type="header">{isEdit() ? "카드 수정" : "카드 추가"}</PageTitle>

👆 최소한 통일 되어야 겠죠?


휴먼 에러 방지하기

const cardInfoReducer = (state, action) => {
  switch (action.type) {
    case "UPDATE_COMPANY": // 이런거 휴먼에러 나기 너무 쉬워요.
      return {
        ...state,
        cardCompany: action.cardCompany,
      };
  ...
};

단축 평가 활용하기

{
  cardList.length > 0 ? (
    cardList.map((card: CardType) => (
      <CardWrapper key={uuidv4()}>
        <SomeComponent />
      </CardWrapper>
    ))
  ) : (
    <></>
  );
}

// 위보다는 아래로

{
  cardList.length > 0 && (
    cardList.map((card: CardType) => (
      <CardWrapper key={uuidv4()}>
        <SomeComponent />
      </CardWrapper>
    ))
  );
}

오~ 👇 이런 방법도 있네요. return을 쪼개기!

if (cardList.length > 0)
  return (
    <Wrapper>
      {cardList.map((card: CardType) => (
        <CardWrapper key={uuidv4()}>
          <SomeComponent />
        </CardWrapper>
      ))}
      <AddCardContainer />
    </Wrapper>
  );

return (
  <Wrapper>
    <AddCardContainer />
  </Wrapper>
);

✋ 리액트는 null 안해주니깐 삼항 연산자보다는 단축 평가가 좋죠.


토큰 노출 주의

CRA env
env 관리하는 방법을 공부합시다.


초기값 챙기기

const CardPreview = ({ values, size = 'small' }) => {
	...
}

👆 아~ 안 넘기면 일단 small 이구낭


CSS로는 어디까지 할 것인가

const Palette = ({ onClickCardSelector, isModalOpened }) => {
  return (
		// 이 경우는 굳이 리액트 컴포넌트? css로도 충분하겠죠.
    <Container className={isModalOpened ? 'is-active' : ''}>
      <SelectorContainer>
		<!-- {...some code} -->
      </SelectorContainer>
    </Container>
  );
};

어느 경우를 js, css, react 가 좋을까요? 삽질해보면 좋을 것 같아요.


Global Style 활용

export const decorators = [
  (Story) => (
    <>
      <GlobalStyle />
      <Story />
    </>
  ),
  (Story) => {
    window.localStorage.setItem("card-info", "[]");
    return <Story />
  },
];

👆 css를 미리 처리하는 globalStyle을 적용했군요. (storybook에도 적용해줬네요.)


Fragment vs Empty

<React.Fragment>
</React.Fragment>

<!-- vs -->

<>
</>

React.Fragment가 나오고 empty tag가 나왔어요.

  • empty tag는 문법적 설탕을 제공합니다.
  • 하지만, key값을 사용해야한다면 React.Fragment를 사용해야합니다.

Portal

const ModalPortal = ({ children }) => {
  const modalElement = document.getElementById("modal");
  return ReactDOM.createPortal(children, modalElement);
};

Suspense

<Suspense fallback={<Loading />}>

useLayoutEffect

useEffect와의 차이점을 알아봅시다.

useLayoutEffect(() => {
  apiRequest();
}, []);

태태는 useLayoutEffect를 통해 width와 height를 미리 계산해서 가져오더라구요!


profile
함께 웃어야 행복한 개발자 장호영입니다😃

0개의 댓글