치춘짱베리굿나이스
[프리온보딩] 220510 강의 메모 01 (간단 코드리뷰) 본문
각잡고 정리하다가 조금 오래걸렸다...
서론
- 린터, 프리티어 절대 끄지 마시오 빨갛게 노랗게 뜨면 다 0점 드리겠습니다
- 린터와 타입스크립트가 50% 이상 들어간다
- 코드리뷰 중요
- 11년차와 1년차의 차이: 삽질을 먼저 해봤느냐 처음해보느냐의 차이
타입스크립트 샘플
- 참고해가면서 사용하세요
초대손님
환영합니다
코드리뷰
코드리뷰 #1
Readme 꼼꼼히 적기
- CRA 디폴트 리드미 사용하지 말고 프로젝트 개요나 설명 등을 적어놓기
- 폴더구조 같은 걸 첨부하면 코드 읽기에 더 좋다
Router 폴더명
- Router는 폴더명 소문자로 해놓는 게 좋다
- 그리고 파일 하나밖에 없는 상태니까 그냥 폴더로 만들지 말고 파일 하나로 빼자 (
router.tsx
)
폴더 구조
- 컴포넌트 이름을 제외하고는 파일명은 어지간하면 다 소문자로 시작하도록 하기
- 유틸리티 함수들, 커스텀 훅, 타입들, scss 모듈 파일들, 라우터 등...
- 특히 scss 모듈은 빌드하면 모듈명이 클래스명으로 들어가기 때문에, 클래스명을 소문자로 시작하게 하기 위해 모듈도 소문자로 시작하도록 (camelCase) 하는 것이 좋다
컴포넌트 분리
src/
├─ components/
│ ├─ common/
│ │ ├─ TestComponent3/ // 공용 컴포넌트
│ │ ├─ TestComponent4/ // 공용 컴포넌트
│ ├─ TestComponent1/ // 개별 컴포넌트
│ ├─ TestComponent2/ // 개별 컴포넌트
- 모든 페이지나 컴포넌트에서 불러와서 사용하는 공용 컴포넌트는
components/common
폴더 안에 정리 - 그 외의 컴포넌트는
components
폴더 안에 하위 컴포넌트로 정리
src/
├─ components/
│ ├─ TestComponent1/
│ ├─ TestComponent2/
├─ pages/
│ ├─ TestPage1/
│ │ ├─ TestComponent4/
│ │ ├─ TestComponent3/
│ ├─ TestPage2/
│ │ ├─ TestComponent5/
│ │ ├─ TestComponent6/
- 아니면 각 페이지별로 해당 페이지 내에서만 쓰는 컴포넌트는 해당 페이지 폴더에 넣고, 공용 컴포넌트는
components
에 넣는 것도 하나의 방법이다 - 어찌 됐건
components
폴더에 다 몰아넣는건 썩 좋지 않음... 취향껏
Template 이용한 페이지 관리
import { Outlet } from 'react-router-dom';
import NavigationBar from 'components/NavigationBar';
const PageTemplate = () => {
return (
<>
<NavigationBar />
<main>
<Outlet />
</main>
</>
);
}
- 내비게이션 바와 같이 모든 페이지에서 보여지는 컴포넌트는 라우터 안에 바로 때려박지 말고, 페이지 템플릿을 만들자
- react-router-dom v6의
Outlet
을 사용하면children
을 사용해서 라우터에 연결할 필요 없이 페이지 레이아웃을 구성할 수 있다 BrowserRouter
내의Route
사이에 컴포넌트를 고정시켜 버리는 것보다 라이브러리 자체에서Outlet
사용을 권장함
Loading 을 구현할 땐 Suspense를 사용하기
- Suspense를 이용하면 특정 컴포넌트에서 데이터를 불러오는 동안 fallback 컴포넌트로 로딩 스피너 등을 띄워 보여줄 수 있다
- 굳이 전역 상태로 Loading을 구현한다던지 할 필요 없이 깔끔하게 만들 수 있다
axios 사용할 때
- 직접 주소를 axios 안에 입력해서 사용하지 않기
- api 불러오는 함수들은 인스턴스화해서 사용하는 것을 추천
- 이렇게 하면 api 주소가 변경되거나 다른 데이터를 불러올 때도 통일된 schema로 작업을 할 수 있다
- 어디서든 똑같이 생긴 함수들을 불러와서 사용하면 되니까 코드 읽기에도 편함
try / catch 사용할 때 finally 애용하기
try {
foo();
setState(true);
} catch {
bar();
setState(true);
}; // setState 중복
try {
foo();
} catch {
bar();
} finally {
setState(true);
}
setState()
가try
와catch
에 중복되어 들어가고 있음finally
는try
/catch
와 관계없이 무조건 실행되어야 하는 코드를 넣자
custom hook 관련
const useTestHook = () => {
const [isloading, setIsLoading] = useRecoilState(loadingState);
const [searchResult, setSearchResult] = useState(null);
...
return searchResult;
}
// hook 사용하는 부분
const TestComponent = () => {
const searchResult = useTestHook();
const isLoading = useRecoilValue(loadingState);
...
- 어차피
isloading
을 사용하는 컴포넌트가 하나밖에 없기 때문에, Recoil을 쓸 바에는useTestHook
내에서 선언해놓고 useTestHook이 그를 반환하는 형태가 낫지 않나?
const useTestHook = () => {
const [isloading, setIsLoading] = useState(false);
const [searchResult, setSearchResult] = useState(null);
...
return { searchResult, isLoading };
}
// hook 사용하는 부분
const TestComponent = () => {
const { searchResult, isLoading } = useTestHook();
...
- 요로코롬
기타 팁
- and 연산자 (조건문
&&
동작) 대신If
/else
사용하기 then
vstry
/catch
⇒ 취향 차이- axios나 fetch 관련 파일들은 services 폴더 안에 넣기 (진작 말했는데! 나쁜사람
~) - axios는 타임아웃을 제대로 처리하지 못하는 경우가 많은데, ky는 그럴일이 없어서 좋더라
then
과try
/catch
는 몇 줄 차이 안 나기 때문에 취향차이다
코드리뷰 #2
any 금지
...
.then(res: any) //이 부분
- 정말 왠만하면 금지!!
- Generic 에서 타입을 추론하기 전에 기본값으로 넣는 등을 제외하고는 금지
- 위와 같은 경우는 에디터에서 자동으로 타입을 추론해주기 때문에 굳이 타입을 안 써도 된다
- any 너무 쓰지 말자
onChange 시에 서버에 요청 보내는 등의 이벤트 처리
- throttling, debouncing 등으로 이벤트에 제한을 걸어야 서버에 부하가 안 생긴다
onChange
같은 경우는 요청이 정말 빠르게 변하기 때문에 서버 부하가 걸릴 위험이 큼
interface 지정 시
interface IProps {
setState = Dispatch<SetStateAction<string>>;
}
interface IProps {
setState = (value: SetStateAction<string>) => void;
}
- 굳이 dispatch, setpatch 적을 필요 없이 함수 형태로 작성해도 타입스크립트가 뭐라 안 한다
시맨틱한 태그 사용
main
,header
,footer
와 같은 태그 애용하기- MDN 에 태그 검색하면 용례같은게 잘 나온다
import 순서
import { 어쩌구1 } from '라이브러리명1';
import { 어쩌구2 } from '라이브러리명2';
import { 어쩌구3 } from '라이브러리명3';
import { 저쩌구1 } from '../../파일명1';
import { 저쩌구2 } from '../../파일명2';
- 라이브러리
import
를 맨 위로 올리고, 상대경로import
를 아랫줄로 내리자
debounse 추천 딜레이
- 영어냐 한글이냐 따라 다르다
- 300을 추천하나 보통 기획 담당자와 서버 개발자와 맞춰가야 하는 부분임 (서버의 부하에 따라 다름)
- 300ms는 연타가 나올 때만 막아주고 아닐 경우엔 딱히 안 막아줌
- 일단은 300쓰시면됩니다
기타 팁
console.log
왠만하면 푸시 전에 지워주기- 타입스크립트에서는
propTypes
굳이 지정해주지 않아도 된다 - 린터 무조건 켜야합니다 린터 필수
- Prop 타입 지정할 때 dispatch, setPatch 안 붙이고 함수형태로 작성해도 된다
propTypes
가지고 린터에서 뭐라 하면 타입스크립트에서는 꺼버려도 된다 (보통은 타입스크립트에서 꺼져 있음)
코드리뷰 #3
Route 안에 슬래시 빼기
<Route path='/testlink' element={<TestComponent />} />
<Route path='testlink' element={<TestComponent />} />
react-router-dom v6부터는 슬래시 빼고 아래처럼 하는게 맞다
구조분해 할당 안에서 구조분해 할당하기
const { dataSet, testVal } = result;
const { id } = dataSet;
const { { id }, testVal } = result;
- 굳이 코드를 두 번 쓰는 것보다 구조분해할당을 통해 분리해주는 걸 추천
props 수정할 때 map 사용해보기
setTodoList((prev) => {
const targetIndex = prev.findIndex((todo) => todo.id === Number(id));
const newList = [...prev];
newList[targetIndex].done = checked;
return newList;
});
setTodoList((prev) => prev.map(
(todo) => todo.id === Number(id)
? {...todo.done: checked}
: todo
)
setState
내에서const
변수를 잔뜩 선언할 필요없이 삼항연산자와map
으로 해결가능하다
기타 팁
- 린터 무조건 되도록 해야합니다... 안먹혀도 먹히도록 만들어야
코드리뷰 #4
함수 표현식 vs 선언식
const foo = () => { }; // 표현식
function foo() { }; // 선언식
- 예전에는 타입이나 반환값 알아보는 데에 차이가 있었지만 요즘은 차이가 거의 없다
- 다만 선언식은
forwardRef
,Memo
등 중첩해서 반환할 때 사용하기 편함 - 뭘 해도 문제없다 취향껏 쓰되 통일성만 지키기
배열 상태값의 초기값은 빈 배열로 하는 것을 추천
map
을 쓸 때 undefined
여부를 구별하는 조건문을 굳이 달아둘 필요가 없어짐
(어차피 배열의 모든 값들을 돌 때 배열에 값이 없으면 그냥 안 돌고 말기 때문)
초기값을 undefined
로 해버리면 조건문 안 다는 순간 터져버림..
Axios 값
- 응답의 형태에는 키가 정해져 있는데, 이게 snake-case, PascalCase, camelCase, kebab_case 아주 제멋대로 온다
- axios 인스턴스를 만드는 것이 좋은 이유도 request response가 왔을 때 키값을 프론트엔드에서 보기 좋은 형태로 다듬을 수 있기 때문이다
camelCase
라이브러리를 쓰면 API 응답의 키값들을 다 통일된 카멜케이스로 바꿔줄 수 있다
Axios 쿼리스트링을 일일히 만드는 것보다 객체로 넘겨주는것 추천
axios.get(`${process.env.REQUEST_URL}?apikey=${process.env.API_KEY}?value=${value}`);
axios.get(process.env.REQUEST_URL, {
params: {
apikey: process.env.API_KEY,
value,
}
});
- 쬐끔 더 깔끔해 보입니다
기타 팁
props
변수명들도 camelCase로 작명하자- axios는 Promise 객체를 반환하므로
async
,await
쓸 필요가 없다 console.log
지우는 습관 들이기response
undefined
날아오면 타입에string[] | undefined
넣는 것보단response
를state
에 넣기 전에undefined
검사를 하고, undefined가 아닐 때만 상태값을 갱신하는 게 타입이 적절히 들어가기 때문에 더 좋아보임
코드리뷰 #5
react-query 팁
<ReactQueryDevtools initialOpen={process.env.DEVTOOL_OPEN} />
ReactQueryDevtools
컴포넌트를 사용할지 말지 결정하는 환경변수를 꼭 작성해야 한다- 배포 시에 저 컴포넌트가 보이면 안 되기 때문 (좌하단에 꽃모양 아이콘이 등장하기 때문에 좀 거슬린다)
- API를 누구나 쉽게 볼 수 있도록 만들어주는 툴이라 배포단계에서 무조건 지워줘야 함
- 어떤 서비스에서 Redux 개발 툴을 열어버리는 바람에 사람들이 값을 직접 조작해서 사용하는 참사가 있었음 (조심)
isLoading
,isFetching
둘 중 하나만 사용하기isFetching
은 데이터를 불러올 때마다 변하는 값isLoading
은 데이터를 처음 불러올 때만 변하는 값 (이후에는 계속false
)
- API
response
에서 데이터 가져올 때,data.data.key
이런 식으로 길어지면 지저분해지기 때문에 내부 API에서data.data
정도까지 반환하게 하기
const { data } = useQuery(
[param1, param2],
() => fetchApi({ param1, param2 })
.then(
//가공
),
{ enabled: true }
);
React-query
에서 받아온response
값을useEffect
에서 가공하는 것보다,useQuery
내에서 가공해서 반환하는 게 낫다- 관심사가
useQuery
,useEffect
로 흩어지는 경향이 있어 코드를 읽기 어려워짐 + 코드가 괜히 복잡해짐 then
을 이용하여 데이터 fetch, 가공 후 반환까지 하자
- 관심사가
const { data } = useQuery(
...
{ suspense : true }
);
react-query
를 이용하면suspense: true
옵션을 통해Suspense
를 아주 쉽게 사용할 수 있다- 굳이
isLoading
이런 식으로 할 필요 없이Suspense
+fallback
함수로 쉽게 구현할 수 있다
- 굳이
Outlet Template 사용할 때 팁
<Routes>
<Route path='category' element={<TestTemplate />}>
<Route path=':id' element={<TestComponent />}>
</Route>
</Routes>
Outlet
을 이용하여 템플릿을 만들었다면, 요로코롬 템플릿으로 특정Route
를 감싸 주면 해당Route
에 템플릿이 적용되게 된다Router.js
파일에 이런 식으로 정리해두면 어떤 컴포넌트가 어떤 템플릿으로 작성되었는지 알아보기도 편하기 때문에 추천
!! 기호
const boolVariable = boolean(variable);
const boolVariable = !!variable;
- 객체를
bool
형태로 바꿔줌 - 그냥
!
기호 두번 썼다고 생각하면 된다 !
기호 한 번으로는false
를true
로 반환하고true
를false
로 반환하니,!!
는 그 과정을 두번 하여 원래 의도했던bool
값을 반환하는 것undefined
,null
이런 애들은 애초에false
를 반환하기 때문에!!
를 쓰면false
가 된다
findIndex + slice ⇒ filter 메서드
const idx = arr.findIndex((v) => v.id === id);
const newArr = [...prev.slice(0, idx), ...prev.slice(idx + 1)];
const newArr = arr.filter((v) => v.id !== id);
- 조건에 맞는 값만 배열에서 삭제하려고 하는 거면
filter
가 더 깔끔
여러 컴포넌트에서 props로 상태값을 전달할 땐 recoil 추천
- 부모 컴포넌트에서 자식 컴포넌트 다수에 상태값을 넘겨주는 상황이면, 전역 상태관리가 적절하다
- 그리고 매번
data.a
,data.b
이런식으로 사용하기보단 필요한 값만 구조분해 할당으로a
,b
같은 값만 받아와서 쓰는 게 더 깔끔하다
기타 팁
+variable
이런 식으로 숫자 형변환을 하는 것은 비추,Number(variable)
을 사용하자
코드리뷰 #6
type vs interface
type ITestType = 'aaa' | 'bbb' | 'ccc';
interface ITestInterface {
var1: string;
var2: boolean;
var3: number;
}
- 취향차이가 좀 있음
type
은 가능한 타입을 나열할 때 사용하는 게 좋다interface
는 여러 변수의 타입을 묶을 때 사용하는 것이 좋고, 다른곳에서 상속을 받을 일이 생길 때 요긴하다
린터의 중요성
- 휴먼에러를 린터가 어지간하면 잡아주기 때문
- 에러가 나면 렌더 자체가 안될 수 있기 때문에..
컴포넌트 최상단 상태값, 변수 등 묶어주기
const foo1 = useNavigate();
const foo2 = useLocation();
const foo3 = useAppSelector();
const foo4 = useAppSelector();
const foo5 = useAppSelector();
const [foo6, foo7] = useState(false);
const [foo8, foo9] = useState(1);
const foo10 = useRecoilValue(fooState);
- 모듈 훅끼리 묶기, 커스텀 훅끼리 묶기, 상태값 묶기, 전역상태값 묶기...
코드리뷰 #7
클릭 핸들러에 setState랑 상태값 사용하는 부분이 동시에 들어가면
const handleButtonClick = () => {
store.set('name', value);
setValue((prevState) => prevState + 1);
};
- 클릭 이벤트가 빠르게 이루어질 경우 값이 의도한 것과 다르게 들어갈 수 있다
prevState
사용하는 함수 안에서 상태값을 사용하면 동작은 올바르게 하겠지만, 어지간하면useEffect
사용을 추천prevState
함수에서 상태값 사용하게 되면 린터가 싫어할 수도
기타 팁
dnd
직접 만들어보는 것도 재밌는 경험이 될 것- 비슷한 코드는 하나로 줄이고 그것을 활용하자 ⇒ 코드 양 대폭 줄일 수 있음
코드리뷰 #8
기타 팁
useRef
를 많이 썼다는 것은 상태값이 과도하게 사용되었을 가능성이 있음 ⇒ 동작을 위해 억지로 넣은 코드일 수도 있다, 코드수 줄이는 방향으로 리팩토링 추천res.data.value
이런 식으로 객체의 값을 길게 가져오기보단const
로 변수지정을 해서 사용하거나, 구조분해할당으로 분해해서 사용하는 것을 추천ul
밑에div
로 감싼li
말고 어지간하면ul
밑에 바로li
넣기- 재활용가능한 코드 만들기
'프로젝트 > 원티드 프리온보딩' 카테고리의 다른 글
[프리온보딩] 220516 그룹과제 #2 시작 (0) | 2022.05.17 |
---|---|
[프리온보딩] 220510 강의 메모 02 (날씨앱 + 팁) (0) | 2022.05.17 |
[프리온보딩] 220515 개인과제 #1 종료 (0) | 2022.05.16 |
[프리온보딩] 220514 개인과제 #1 완성 및 배포 (0) | 2022.05.15 |
[프리온보딩] 220513 개인과제 #1 (0) | 2022.05.14 |
Comments