[레벨2 - 미션3] 1단계 장바구니 피드백

Nine·2022년 5월 23일
0

장바구니 1단계 PR

이번 미션은 redux를 사용하는것이 필수 요구사항이였어요.

😨 redux를 한 번 사용해본 적이 있긴한데 클론코딩하면서 너무 어려웠던 기억이 있어요.

😀 그래서 겁 먹고 시작했는데 알고보니 괜찮았어요! (너무 겁먹지 말아야겠어요 야생학습의 기본 태도죠!)

(아니 우테코를 나가고 싶다는 건 아니고) 뭐든지 할 수 있을 것이다! 라는 마인드를 가지겠습니다ㅎㅎ

😅 또, redux가 리액트만을 위한 것이 아니였다는 것도 알게 되었어요ㅋㅋ (리액트에서만 쓰이는 건줄)

🦸‍♂️ 자 그럼 피드백을 살펴보러갈까요?

공통 피드백

동사는 함수를 떠올리게 합니다.

const initialState = {
  getProductLoading: false,
  getProductSuccess: false,
  getProductFail: '',
};

👆 get- 인데 false, '' 값을 할당받고 있는것이 이상해요!


env 설정하기

// constant로 관리 -> 외부에 노출되겠죠?
const API_URL = 'https://some.herokuapp.com';

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

// env로 관리
// REACT_APP이라는 prefix는 CRA에서 해주는거예요.
export const API_URL = process.env.REACT_APP_API_URL;

CRA에는 시작했다면 이미 package.json에 scripts가 설정되어 있어요.

  • env가 설정이 미리 되어 있는거죠!

.env는 gitignore처리해야해요 (토큰 노출 방지)

// 개발에서는 mocking
env.development.local ⇒ MSW

// 배포에서는 실제 api
env.production.local ⇒ heroku, json-server

계층 나누기

depth는 평탄하게 가는게 좋아요.
Styled Prefix를 붙이는 순간 오버엔지니어링이 될 수 있어요.
각자의 기준을 정합시다. (너무 어렵고 ㅠㅠ)

function Component() {
  return (
    <Styled.CounterContainer>
      <Styled.CounterButton onClick={decrease}>-</Styled.CounterButton>
      <Styled.Count>{count}</Styled.Count>
      <Styled.CounterButton onClick={increase}>+</Styled.CounterButton>
    </Styled.CounterContainer>
  );
}

/**
 * After...
 */

function Header() {
  return (
    <Styled.Counter.Container>
      <Styled.Counter.Button onClick={decrease}>-</Styled.Counter.Button>
      <Styled.Count>{count}</Styled.Count>
      <Styled.Counter.Button onClick={increase}>+</Styled.Counter.Button>
    </Styled.Counter.Container>
  );
}

styled-component (확장 vs props)

extends냐 props냐 기준을 정해볼까요?

물어볼 때에는 어떤 케이스에서는 이게 좀 괜찮더라, 이런 사고를 가져서 생각해봅시다.

💪 정답은 없어요.

✨ 포코 개인적으로는 props를 선호하신대요.

  • 눈에 더 잘보인다!
  • extends는 기본 StyledComponent이 수정되면 걷잡을 수 없다!
  • props를 쓰면 코드가 쓰레기가 되지만 옆구리 터질 일은 별로 없다!


// 공통적으로 쓰이는 button의 css
const StyledButton = styled.button``;

// ---- 1. 확장 활용 ----
const StyledHeaderButton = styled(StyledButton)``;

// ---- 2. props 활용 ----
const StyledButton = styled.button`
  // 공통적으로 쓰이는 button의 css

  ${({ header }) =>
    header &&
    css`
      // header에서 쓰일 button을 위한 css
    `}
`;

한글 상수 시도

토스 진유림님의 클린코드 강의를 봤는데도 사용하시더라구요!

import { requestGetProductList } from 'api';
import { 비동기_요청 } from 'constants/';
import { 상품리스트_불러오기_액션 } from './types';

근데 포코는 영어로 써도 알아듣기 쉬운걸 굳이 한글 상수로 쓰지는 말자고 했어요ㅋㅋㅋ

💭 예를 들면 레벨2, 크루처럼 우테코 크루 내부에서만 아는 문구들을 한글 상수로 하는 거죠~


테마인가 config인가,,,

너무 복잡한 theme을 사용하지 말아요. 팔레트를 생각합시다.

👇 단지 색상 설정일 뿐인데 config 수준이죠?

const theme = {
  color: {
    primary: "#2ac1bc",
    white: "white",
    black: "black",
    item: {
      hover: { backgroundColor: "#f2efef", textColor: "#666" }, // config 같은데요..? 이런건 컴포넌트로 보냅시다.
    },
    itemDetails: {
      shoppingCartButtonColor: "#73675c",
      priceColor: "#333",
    },
  },
};

👇 반면에 아래는 정석이라고 볼 수 있어요.

const COLORS = {
  BLACK: '#000000',
  GRAY_005: '#555555',
  GRAY_004: '#AAAAAA',
  GRAY_003: '#DDDDDD',
  GRAY_001: '#F6F6F6',
  GRAY_002: '#F3F3F3',
  WHITE: '#FFFFFF',
  BLUE_001: '#0066FF',
};

👇 시도는 좋으나 그냥 팔레트 하나로 색상 코드를 가져오며 사용해도 좋을 것 같아요..

// config에 가까워요.
const colors = {
  primary: '#2AC1BC',
  font: '#333333',
  divisionLine: '#AAAAAA',
  brown: '#73675C',
};

// 아이디어는 좋은데 굳이 해야하나요?
const usingColor = {
  headerFont: colors.WHITE_001,
  defaultFont: colors.BLACK_001,
  headerBackground: colors.MINT_001,
  shadow: colors.GRAY_001,
  shoppingCartIcon: colors.BLACK_001,
  selectedShoppingCartIcon: colors.MINT_001,
  loadingSpinner: colors.MINT_001,
};

이미지 핸들링

이미지 핸들링은 거의 React Key와 비슷한 수준의 중요도를 가지고 있어요.

❗ alt는 필수예요.

스크린 리더를 위해 조금 더 상세한 alt를 작성해줍시다.


불필요한 axios 인스턴스 생성

비추입니다.

import axios from "axios";

const apiClient = axios.create({
  baseURL: REACT_APP_API_URL,
  responseType: "json",
});

export default apiClient;

상태에 대한 오너십은 어디서 가지는가

input에 대하여 비제어냐 제어냐를 확실히 고민합시다.

DOM과 react state를 둘 다 사용 👉 코드가 짬뽕이 됩니다. 👉 관리가 어려워질 거예요.

const [page, setPage] = useState(id); // react state 😨

const handleClickNumber = (
  e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>
) => {
  if (!(e.target instanceof HTMLButtonElement)) return;
  const currentPage = Number(e.target.innerText); // broswer state, DOM 😨

  setPage(currentPage); //react state 😨
  navigate(`/main/${currentPage}`);
};

if-then...

삼항 연산자의 반복은 참 아쉽습니다.

<>
  <Header />
  <PageContainer>
    {isLoading ? (
      <Loading />
    ) : error ? (
      <Error>서버에 연결할 수 없습니다.</Error>
    ) : (
      <ProductListContainer
        data={data}
        handleToggleShoppingCart={handleToggleShoppingCart}
        checkContainedProduct={checkContainedProduct}
      />
    )}
  </PageContainer>
</>

react if

  • <If> <Then> <Else> 이런 것들도 존재합니다.
  • 쓰라는 것이 아니라 이런 고민이 왜 나왔을지 생각해봅시다.

children 활용 주의

children은 다양한 것들이 들어올 수 있어요.

그 만큼 사용 시 어떤 타입이 들어오는지를 꼭 확인하고 사용해야해요.

<ul className={isDrawerOpened ? "drawerOpened" : ""}>
	// ❗ 정말 위험해요. children이 안들어온다면? ❗
  {Array.isArray(children)
    ? children.map((child: React.ReactNode) => <li>{child}</li>)
    : children}
</ul>;

// 아래처럼 체크를 해볼까요?

if (React.Children.count(children)) {
  return React.Children.map(children, (child) => <div>{ ..todo.. }</div>)
}

React.Children

  • React.Children.count(children)
    • children에 포함된 컴포넌트의 개수를 반환합니다.
    • 이를 활용하여 여러 개일 경우 map을 돌 수 있도록 해줍시다.

상태 계산은 어디서할까

function AmountBox({ type = "expect", totalCount, totalPrice }) {
  return (
    <AmountBoxWrapper>
      <AmountBoxHeaderWrapper>
        {type === "expect" ? "결제예상금액" : "결제금액"} // ❓ 애초에 props로 뚫려있었다면?
      </AmountBoxHeaderWrapper>
      <PriceInfoWrapper>
        <p>{type === "expect" ? "결제예상금액" : "총 결제금액"}</p> // ❓ 애초에 props로 뚫려있었다면?
        <p>{totalPrice}</p>
      </PriceInfoWrapper>
      <Button backgroundColor="brown" width="100%" height="73px">
        {type === "expect"
          ? `주문하기(${totalCount}개)`
          : `${totalPrice}원 결제하기`} // ❓ 애초에 props로 뚫려있었다면?
      </Button>
    </AmountBoxWrapper>
  );
}

결제예상금액, 결제금액이 재활용되지 않아요.

🤔 type, expect를 검사하는 것이 있는게 맞을까요?

하위컴포넌트는 바보처럼 만들어야해요.

투명 순수 바보로 만들어줍시다.

위 코드의 컴포넌트는 똑똑해요.

다시는 안쓸것 같았는데.. 한 번 더 쓸까? 👉 예측하기 쉽지 않죠. 하지만 코드를 만들 때 bottom up으로 만들어봅시다.


일관성 있는 컴포넌트 구성

  • import 모아두기
  • useState / 훅으로 불러온 것은 가장 상단에 두기 / useEffect은 바로 return 위에 모아두기
  • 상수 / 스타일 모아두기

의존적인 Hook

Store()

// 커스텀훅 2개
useProductDetail()
useProductList()

// 컨테이너 컴포넌트, 페이지 컴포넌트 수준에서 store에 접근합니다.
// 근데 여기서도 모르게 하시는 분들이 있더라구요. -> hook에서 접근(위의 2개 각각)
ProductPage.jsx
ProudctDetail.jsx

// 작은 컴포넌트
// 이 친구들은 store를 모르는게 좋겠죠.
ItemList.jsx
Item.jsx

😅 정답은 없지만 Hook이 특정 컴포넌트에 종속적인 경우가 있어요.
Hook 1 : Component 1 : 포코는 비추해요! Hook이 그저 import 로직 빼기 위한 거예요? 아니죠.

하지만 분리 안하자니 코드가 개판인데요? 👉 설계가 잘못되었을 수도 있어요.

Hook에 대한 고찰

Hooks는 엄청 많은 것을 할 수 있어요.

  1. 컴포넌트 내부 반복 로직 분리
  2. 상태 관리
  3. 유틸 함수를 리액트와 바인딩
  4. 외부 라이브러리와 리액트의 바인딩 Context API
  5. 컴포넌트가 스토어를 몰라도 커스텀 훅에 접근하여 스토어와 통신
  6. 비동기와 컴포넌트를 분리할 때 커스텀 훅으로 fetch 코드 추상화

👍 fetch + memo + cache + useState + useEffect + Context API
👉 별거없어요. 이게 React Query예요.
👉 어라? 내가 만든 코드들이 알고보니 라이브러리가 되어 버리네! 라는 순간이 올 수 있어요.

하지만 커스텀훅을 특정 컴포넌트에 의존적으로 1:1 관계로 사용하는것은 좀 아쉬워요.

특정 컴포넌트가 없어지면 필요없는 친구? 재미없어요. 1:n 으로 가봅시다.

👇 위의 커스텀훅 2개를 하나로 합쳤어요. 더 의미가 있어집니다. (재활용)

// 위의 코드에서 더 나아가서 합칠게요.
useProduct()

// useProduct Hook 1 : ---Page.jsx, ---Detail.jsx 2
// 이렇게 나아가봅시다.
ProductPage.jsx
ProudctDetail.jsx

ItemList.jsx
Item.jsx

있는 도구 잘 활용하기

  • ESLint 무더기 설치

    • CRA하면 eslint 설정에 여러분이 쓰는 절반 이상은 들어있어요.
  • ESLint 중복 설정

    • package.json에도 있는데 eslint에도 있더라구요. (없앱시다.)
  • <Route exact>

    주니어한테 이런 걸 주의할 것을 기대하죠. 주니어들한테 꼼꼼한 최신 스펙 챙기기를 요구합니다.

    • is gone! v6에서 안써요.,
  • useRoutes 👍

    • react-router-dom v6에 있어요. 좋네요!
  • combineReducer()

    • 직접 구현한 사람이 있더라구요.
    • 있는 도구는 잘 찾아서 쓰는 것이 능력입니다.
  • react-app-env.d.ts

    • CRA하면 설치가 되고
    • 타이핑하기 어려운 건 여기다 넣어 → 전역적으로 해줄게.

왜 포코는 React Redux를 만들어 보라고 했을까?

왜 react redux binding을 만들어보라고 말했을까요?

  • 이것만 이해하면 라이브러리의 동작 원리를 이해할 수 있기 때문이예요.

  • 나인 라우터 → Context → Provide → Consumer(구독) → 그거에 대한 hooks로 땡겨오면 끝이예요.

  • 웬만한 모든 라이브러리들을 수월하게 이해할 수 있겠죠.

생각이 props에만 멈춰있게 하지 맙시다.


개별 피드백

Style

  • 현재
    • styled-component들이 별도의 파일로 관리되는 구조인데,
    • className만 정의하면 되는 SCSS를 사용하고 파일 단위로 한정하고 싶다면 CSS module 기능을 활용하는 것이 좋아보여요.
  • styled-component는 Atomic design과 같은 패턴으로 개발된 아주 잘게 분리된 컴포넌트에 CSS를 적용하는 경우에 보다 적합하다고 생각합니다.

  • Wrapper라는 네이밍과 구조가 계속 반복되고 있습니다.

    • 단순히 Style만 부여하고 있기에 Wrapper라는 이름이 적절하지 않아요.
    • 기능을 묶어야 Wrapper죠!

컴포넌트 재활용

  • 특정 도메인 로직에 종속된 컴포넌트들은 재활용이 불가능합니다.

에러 페이지

  • 에러는 not found만 있는건 아니라서 좀 더 세분화하거나 범용적인 에러페이지면 더 좋을 것 같아요

css rule 우선순위

  • 자손에 대한 css를 정의할 때 selector를 tag(element)로 지정하는건 적용우선순위 때문에 원하는 rule이 적용되지 않을 수 있어서 다소 위험해 입니다.

  • 물론 styled component를 사용하면서 자손에 대한 rule을 정의할 경우에는 class name으로 주더라도 이름의 변환이 발생하지 않기 때문에 동일한 위험성은 존재하지만 element로 접근하는 경우보다는 위험성을 줄일 수 있을 것 같아요

  • (CSS의 단점인) 전역에 정의되서 중첩되는 경우를 적용되는 방식이라 위험성을 인지하고 대안을 고민해봅시다.

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

0개의 댓글