치춘짱베리굿나이스

[프리온보딩] 220510 강의 메모 01 (간단 코드리뷰) 본문

프로젝트/원티드 프리온보딩

[프리온보딩] 220510 강의 메모 01 (간단 코드리뷰)

치춘 2022. 5. 16. 21:19

각잡고 정리하다가 조금 오래걸렸다...

서론

  • 린터, 프리티어 절대 끄지 마시오 빨갛게 노랗게 뜨면 다 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()trycatch에 중복되어 들어가고 있음
  • finallytry / 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 vs try / catch ⇒ 취향 차이
  • axios나 fetch 관련 파일들은 services 폴더 안에 넣기 (진작 말했는데! 나쁜사람~)
  • axios는 타임아웃을 제대로 처리하지 못하는 경우가 많은데, ky는 그럴일이 없어서 좋더라
  • thentry / 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 넣는 것보단 responsestate에 넣기 전에 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 형태로 바꿔줌
  • 그냥 ! 기호 두번 썼다고 생각하면 된다
  • ! 기호 한 번으로는 falsetrue로 반환하고 truefalse로 반환하니, !!는 그 과정을 두번 하여 원래 의도했던 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 넣기
  • 재활용가능한 코드 만들기
Comments