이번 미션은 redux를 사용하는것이 필수 요구사항이였어요.
😨 redux를 한 번 사용해본 적이 있긴한데 클론코딩하면서 너무 어려웠던 기억이 있어요.
😀 그래서 겁 먹고 시작했는데 알고보니 괜찮았어요! (너무 겁먹지 말아야겠어요 야생학습의 기본 태도죠!)
(아니 우테코를 나가고 싶다는 건 아니고) 뭐든지 할 수 있을 것이다! 라는 마인드를 가지겠습니다ㅎㅎ
😅 또, redux가 리액트만을 위한 것이 아니였다는 것도 알게 되었어요ㅋㅋ (리액트에서만 쓰이는 건줄)
🦸♂️ 자 그럼 피드백을 살펴보러갈까요?
const initialState = {
getProductLoading: false,
getProductSuccess: false,
getProductFail: '',
};
👆 get-
인데 false, '' 값을 할당받고 있는것이 이상해요!
// 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는 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>
);
}
extends냐 props냐 기준을 정해볼까요?
물어볼 때에는 어떤 케이스에서는 이게 좀 괜찮더라, 이런 사고를 가져서 생각해봅시다.
💪 정답은 없어요.
✨ 포코 개인적으로는 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, 크루
처럼 우테코 크루 내부에서만 아는 문구들을 한글 상수로 하는 거죠~
너무 복잡한 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를 작성해줍시다.
비추입니다.
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}`);
};
삼항 연산자의 반복은 참 아쉽습니다.
<>
<Header />
<PageContainer>
{isLoading ? (
<Loading />
) : error ? (
<Error>서버에 연결할 수 없습니다.</Error>
) : (
<ProductListContainer
data={data}
handleToggleShoppingCart={handleToggleShoppingCart}
checkContainedProduct={checkContainedProduct}
/>
)}
</PageContainer>
</>
react if
<If> <Then> <Else>
이런 것들도 존재합니다.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>)
}
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으로 만들어봅시다.
Store()
// 커스텀훅 2개
useProductDetail()
useProductList()
// 컨테이너 컴포넌트, 페이지 컴포넌트 수준에서 store에 접근합니다.
// 근데 여기서도 모르게 하시는 분들이 있더라구요. -> hook에서 접근(위의 2개 각각)
ProductPage.jsx
ProudctDetail.jsx
// 작은 컴포넌트
// 이 친구들은 store를 모르는게 좋겠죠.
ItemList.jsx
Item.jsx
😅 정답은 없지만 Hook이 특정 컴포넌트에 종속적인 경우가 있어요.
Hook 1 : Component 1
: 포코는 비추해요! Hook이 그저 import 로직 빼기 위한 거예요? 아니죠.
하지만 분리 안하자니 코드가 개판인데요? 👉 설계가 잘못되었을 수도 있어요.
Hooks는 엄청 많은 것을 할 수 있어요.
👍 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 무더기 설치
ESLint 중복 설정
<Route exact>
주니어한테 이런 걸 주의할 것을 기대하죠. 주니어들한테 꼼꼼한 최신 스펙 챙기기를 요구합니다.
useRoutes 👍
combineReducer()
react-app-env.d.ts
왜 react redux binding을 만들어보라고 말했을까요?
이것만 이해하면 라이브러리의 동작 원리를 이해할 수 있기 때문이예요.
나인 라우터 → Context → Provide → Consumer(구독) → 그거에 대한 hooks로 땡겨오면 끝이예요.
웬만한 모든 라이브러리들을 수월하게 이해할 수 있겠죠.
생각이 props에만 멈춰있게 하지 맙시다.
SCSS를 사용하고
파일 단위로 한정하고 싶다면 CSS module
기능을 활용하는 것이 좋아보여요.styled-component는 Atomic design과 같은 패턴으로 개발된 아주 잘게 분리된 컴포넌트에 CSS를 적용하는 경우에 보다 적합하다고 생각합니다.
Wrapper라는 네이밍과 구조가 계속 반복되고 있습니다.
자손에 대한 css를 정의할 때 selector를 tag(element)로 지정하는건 적용우선순위 때문에 원하는 rule이 적용되지 않을 수 있어서 다소 위험해 입니다.
물론 styled component를 사용하면서 자손에 대한 rule을 정의할 경우에는 class name으로 주더라도 이름의 변환이 발생하지 않기 때문에 동일한 위험성은 존재하지만 element로 접근하는 경우보다는 위험성을 줄일 수 있을 것 같아요
(CSS의 단점인) 전역에 정의되서 중첩되는 경우를 적용되는 방식이라 위험성을 인지하고 대안을 고민해봅시다.