리액트는 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를 분리하는 것이 좋아요.
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 };
}
무지성 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);
};
Number.isNaN 쓰세요
일단 관리 포인트가 늘어요.
😱 리액트는 custom props가 있어서 쓰면 되는데 굳이 data-name을 쓰나요?
data attribute를 좋아서 쓰라는 것이 아닙니다. → 다시 말해 브라우저에서 쓰라고 만든 것이 아닙니다.
👆 하도 약속을 안 지키니까 에혀..그냥 data-attribute 써라.
요런 느낌!
data attribute를 쓰면 일단 DOM에 노출되잖아요. 사용자가 알 필요가 없는 정보예요.
무조건 쓰지 말라는 건 아닌데 무지성으로 쓰지는 맙시다.
근데 코드 까보셨나요?
이런 걸 궁금해야합니다. (최소 배포할 때에는 없애야죠)
// 이렇게 할 필요없어요.
<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>
);
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
}
😀😀 아래 코드가 복잡하다구요? 하지만 좋아요.
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 활용은 필수죠. 이제 입이 아프네요.
<Header title="보유 카드 목록 💳" />
<DescriptionIconButton iconImage={QuestionMarkIcon} description={SECURITY_CODE_DESCRIPTION} />
--------------------vs--------------------
<Header>
보유 카드 목록 💳
</Header>
<DescriptionIconButton iconImage={QuestionMarkIcon} description={SECURITY_CODE_DESCRIPTION} />
theme 활용 좋아요!
const colors = {
pageDefault: "#fff",
// ...
}
<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 이구낭
const Palette = ({ onClickCardSelector, isModalOpened }) => {
return (
// 이 경우는 굳이 리액트 컴포넌트? css로도 충분하겠죠.
<Container className={isModalOpened ? 'is-active' : ''}>
<SelectorContainer>
<!-- {...some code} -->
</SelectorContainer>
</Container>
);
};
어느 경우를 js, css, react 가 좋을까요? 삽질해보면 좋을 것 같아요.
export const decorators = [
(Story) => (
<>
<GlobalStyle />
<Story />
</>
),
(Story) => {
window.localStorage.setItem("card-info", "[]");
return <Story />
},
];
👆 css를 미리 처리하는 globalStyle을 적용했군요. (storybook에도 적용해줬네요.)
<React.Fragment>
</React.Fragment>
<!-- vs -->
<>
</>
React.Fragment가 나오고 empty tag가 나왔어요.
const ModalPortal = ({ children }) => {
const modalElement = document.getElementById("modal");
return ReactDOM.createPortal(children, modalElement);
};
<Suspense fallback={<Loading />}>
useEffect와의 차이점을 알아봅시다.
useLayoutEffect(() => {
apiRequest();
}, []);
태태는 useLayoutEffect
를 통해 width와 height를 미리 계산해서 가져오더라구요!