🐈오늘 배운 것
✔️EsLint, husky 그리고 자동화
첫 주차 프로젝트 셋팅을 위해 기존 프로젝트에 초기 세팅을 추가하고, 리팩토링을 진행했다.
이번 프로젝트에서 EsLint와 Prettier 그리고 husky를 사용하게 되었는데, 설정 방법 등을 간단하게 복기해보려고 한다.
EsLint
Lint 란 코드 문법 교정을 위해서 사용하는 툴로, 여러 사람이 함께 프로젝트 작업을 할 때 문법, 코드 스타일 등을 맞춰 가독성을 높이고 협업 능률을 향상시키기 위해서 사용한다. 주로 문법 교정의 용도로 EsLint를 많이 사용하고, 코드 교정의 경우 prettier 를 사용한다. 충돌 가능성이 있기 때문에 eslint-config-prettier 같은 별도의 플러그인을 설치하여 esLint 의 formatting 관련 룰을 해제한다.
처음 EsLint 세팅을 할 때 타입스크립트 프로젝트에 있는 세팅을 그대로 가져와서 사용했는데, 우리의 자바스크립트 프로젝트에 parser가 꼭 필요한지 여부가 궁금해서 찾아보았다.
우선 parser 란 브라우저가 알아들을 수 있는 언어 즉 javascript로 jsx 혹은 typescript 등의 언어를 변환시켜 주는 역할을 한다. EsLint 에도 기본적으로 default parser 가 내장되어 있지만, ECMAScript 에서 정의한 최종적인 규칙에 대해서만 작동하기 때문에, 최신 문법(실험적인 기능) 혹은 Flow, Typescript 에 대해서는 지원하지 않는다. 그래서 별도의 parser를 사용한다. ex) typescript-eslint/parser 등.
우리 프로젝트에서는 jsx 에 대한 부분만 추가되면 되는데, 이미 default parser 에 이 기능이 내장되어 있기 때문에 별도의 parser 항목을 추가하지 않아도 정상 동작했다.
* @babel/eslint-parser란 ?
- parser 로 babel을 사용하는 경우에만 적용할 수 있다.
- 더 자세한 내용은 공식 문서에 잘 나와있다.
기본적으로 extends 에 커스터마이징을 할 수 있도록 plugin을 추가해주고, 그 아래에 룰을 추가하여 esLint 규칙을 만들 수 있다. 예전에 클론코딩 프로젝트 당시에 airbnb style guide 를 적용했을 때 발생한 이해할 수 없는 오류가 다 이 rule 에 있어서 그런거라는 것을 깨닫게 되었다. esLint 에 어떤 오류가 발생했을 때는 에러 구문과 함께 어떤 rule 에 위배가 되었는지 다 표시가 되기 때문에 이를 확인해서 불필요한 속성이라면 'off' 하여 커스터마이징을 할 수 있다.
예시로, 우리는 함수형 컴포넌트 사용시 함수 표현식 + 화살표 함수를 사용하기로 했는데, 화살표 함수를 사용할 때 중괄호 + return 문을 사용하니 EsLint가 error를 뿜어냈다.
우리는 이 룰의 사용 필요성을 느끼지 못해서 해당 룰을 .eslintrc 파일에 추가하여 off 로 변경해주었다.
"rules": {
"arrow-body-style": "off",
// 화살표 함수 중괄호 적용 혹은 생략 둘 다 가능
}
생각보다 세부적인 것까지 신경쓰는 rule 이 정말 많이 있는데, 이미 있는 설정을 그냥 복붙해서 가져다 쓰기 보다는 어떤 rule 이 무엇에 대한 것인지를 하나씩 파악하면서 사용하는게 좋을 것 같다. EsLint 하면 정말 막연한 두려움이 많았는데 생각보다 하나씩 뜯어보면 다 무슨말인지 이해가 가고 또 친절하게 어떤 rule 에서 에러가 났다고 알려주기 때문에 이 rule 이 정말 필요한 것인지 고민하면서 fix 하기 좋았다.
husky + 자동화를 위한 package.json scripts 설정
정말 어디선가 들어만 봤던 툴인데 어떤 녀석이고 왜 사용하는지에 대해서 이번 라이브 세션에서 처음 듣게 되었고, 직접 적용까지 해볼 수 있었다.
일단 esLint 와 prettier 를 도입하더라도 작업자가 이를 무시하고 사용해버리지 않으면 맞춰서 사용하는 의미가 없기 때문에 git 에 commit 을 하거나 push 를 하려면 꼭 esLint 와 prettier 에 위배된 내용이 없는지 확인하도록 자동화를 해야한다. 이런 자동화를 하는 방법이 git hook 인데 이것 또한 설정이 까다롭기 때문에 쉽게 husky 를 사용한다.
# 허스키 초기 셋팅
npm i husky -D
git init
npx husky install # 초기 셋팅할 때
// package.json
{
"scripts": {
"postinstall": "husky install",
"format": "prettier --cache --write .",
"lint": "eslint --cache .",
},
}
# 커밋 전 후 포맷팅, 린트 실행 자동화 설정
npx husky add .husky/pre-commit "npm run format"
npx husky add .husky/pre-push "npm run lint"
✔️프리온보딩 사전과제(todo list) 리팩토링
이번 과제가 지원을 위해 제출했던 사전 과제에서 서로의 코드를 보고 Best Practice를 모아 리팩토링 하는 것이었다. 과제가 크게 로그인 부분, todo list 부분으로 나뉘어 있었는데 나는 todo list 부분을 맡고싶다고 했고, 총 9명의 인원이 3 파트로 쪼개졌다.
폴더 구조 변경
먼저 기준으로 잡았던 프로젝트의 폴더 구조가 api, components, pages 로만 나뉘어 있어서 추가적으로 hooks, router, utils 폴더를 추가해주었다. router 는 별도로 분리했는데 이렇게 분리되어 있는 편이 관심사 분리에 더 적합한 것 같아서 그렇게 진행했다. 또 axios instance 도 만들었는데 이 친구도 utils 폴더에 넣어주었다.
폴더 구조를 잡으면서 든 생각이, 이번에 메인 프로젝트를 할 때 왜 api 요청하는 함수에 대해서 api 폴더를 별도로 만들지 않았을까...였다. 생각해보면 데이터를 업데이트하는 useSWR 훅을 제외하면 나머지 요청 (post, put, delete 등...) 에 대해서는 api를 만들어서 관리하는 편이 훨씬 깔끔하고 재사용성도 좋았을텐데 말이다. 물론 이상하게 이번 사전 과제에서는 그렇게 진행했지만...
API 객체 형태로 변경
나는 각각의 api 콜을 함수로 만들어서 해당 함수를 각각 import 해서 호출하는 방식을 사용했는데, 팀원 중 어떤 분의 코드는 이 api 콜을 각각 객체의 메서드로 만들어서 예를들면 todoAPI.createTodo 이런식으로 접근할 수 있도록 만들어 두셨다. 이런 방식을 처음 봤고 정말 깔끔하고 알기 쉽다고 생각해서 이번 프로젝트의 BP로 선정하게 되었다.
handler 함수명 변경
이것은 정말 생각하지 못했던 부분인데, 전체 팀원의 코드를 보면 event를 핸들링하는 함수의 네이밍을 onChangeInput / onPostTodo 이런 식으로 작성하거나 handleCreateTodo / handleChangeInput 이런 방식으로 작성하는 두 부류가 있었다. 나는 전자였는데 (왜냐면 이벤트 핸들러의 이름이 보통 onSubmit, onChange 이기 때문에 on이라는 이름을 많이 사용했다.) 이번에 리팩토링을 하면서 공통적으로 handle<동사><명사> 이렇게 정하기로 했다. handle 이라는 이름이 어떤 이벤트를 핸들링 한다 혹은 이벤트 핸들러와 관련된 내용이다 라는 측면에서 더 맥락이 알기 쉽다고 생각했고, 이런 이름의 함수가 눈에 더 잘 띈다고 생각했기 때문이다.
사실 지금까지 프로젝트를 진행하면서 이런 변수 네이밍, 함수 네이밍에 대해서 어떤 공통의 규칙을 정해본 적이 없는 것 같다. 살짝 충격을 받았지만 이 기회를 통해서 다른 사람들의 코드도 뜯어볼 수 있어서 좋다.
useFetch 훅 데이터 업데이트
다른 분들은 데이터 fetch 에 커스텀 훅을 사용하지 않고 getTodo 같은 네이밍의 get 요청 함수를 별도로 만들어서 사용하셨는데, 내가 사용한 useFetch 훅을 적용해보고 싶다는 의견이 나와서 코드를 수정했다. useFetch 훅을 사용하게 되면 컴포넌트에서 별도로 state를 선언하는 코드를 작성할 필요 없이 훅의 리턴된 값으로 data state를 사용할 수 있다.
또 로딩이 오래 걸리는 경우 isLoading 같은 상태를 추가적으로 제공해서 로딩이 되는 동안 로딩 상태를 띄워주는 등 추가적인 조건부 동작을 할 수 있다는 이점이 있다. url 만 넘겨주면 되기 때문에 재사용성이 높다는 이점도 있다. 이번 프로젝트에서는 get 요청이 todo 하나 밖에 없었지만 여러번 get 요청을 하게 된다면 사용성이 좋다.
✔️리액트에서 서버 상태 동기화
이번 사전 과제를 진행하면서 todo 에 C,U,D 요청을 보낸 뒤 변경된 데이터를 동기화 하기 위해서 사용한 방법들이 크게 두 가지로 나뉘었다.
먼저 서버에 요청을 보내고, get요청을 보내서 동기화를 하지 않고 직접 로컬 상태를 변경시켜서 화면을 업데이트 하는 방식이 있었다. 나도 이 방식을 사용해서 화면에 업데이트를 했는데, 이렇게 했을 때 문제는 서버에 요청을 보내고 성공을 했을 때만 업데이트가 되도록 로직을 짜 주어야 하고, delete가 되었을 때의 데이터 리스트를 만들어서 업데이트를 한다거나, 특정 부분이 수정된 리스트 데이터를 만들어서 업데이트 하는 식으로 추가적인 코드를 작성해주어야 한다. 예를 들면 아래와 같이...
const onEditTodo = async (id, updatedTodo) => {
editTodo(id, updatedTodo).then(res => {
const newTodos = data.map(todo => (todo.id === id ? res.data : todo));
setData(newTodos);
});
};
투두리스트야 간단해서 이런 방식으로 진행해도 큰 무리가 없다고 해도, 변경되는 내용이 조금만 복잡해도 이렇게 만들어주기가 힘들 것 같다는 생각이 들었다. 또 내 코드의 경우에는 try...catch 로 요청 성공과 실패에 대한 상황을 가정하고 있지 않기 때문에 더 정확하지 않다고 느꼈다. 그러니까 만약 update 요청이 실패하면 어떻게 처리할건데? 에 대한 대답이 없다는 것이다.
그래서 이번에는 useFetch 훅 안에 state를 만들어서 이 상태가 업데이트 될 때마다 get 요청을 보내어 data state를 업데이트 하도록 코드를 작성해주었다. 그래서 업데이트 요청 => 상태 변경 => 상태에 의존성을 가진 함수가 실행되어 data 업데이트 후 리렌더링
// useFetch.js
import { useState, useEffect } from 'react';
import client from '../utils/httpClient';
export const useFetch = (url) => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(true);
const [error, setError] = useState(null);
const [refetch, setRefetch] = useState(0);
useEffect(() => {
client
.get(url)
.then((res) => {
return res.data;
})
.then((data) => {
setIsPending(false);
setData(data);
setError(null);
})
.catch((err) => {
setIsPending(false);
setError(err.message);
});
}, [refetch]);
return { isPending, data, setData, error, setRefetch };
};
// Todo.jsx
const { data: todos, setRefetch } = useFetch('/todos');
const handleCreateTodo = async (todo) => {
try {
await todoAPI.createTodo(todo);
setRefetch((prev) => prev + 1);
} catch (err) {
return err;
}
};
근데 사실 이 방법도 best practice 라고 해야될지 모르겠다. 이렇게 업데이를 하는 프로젝트를 여러번 해봤지만 처음에는 아예 새로고침...을 했었고, 이번 사전과제처럼 직접 상태를 만들어서 업데이트를 해주거나, swr 같은 훅을 사용해서 자동으로 업데이트 하도록 했기 때문에..
근데 이 훅이 문제인지 뭐가 문제인지 모르겠지만 데이터 요청을 해서 화면을 업데이트 할 때 변경이 있는 부분에 대해서만 리렌더링이 일어나야 하는데, 페이지 컴포넌트 전체에서 리렌더링이 발생하는 기현상이 있다... memo를 사용해서 줄여보려고 했는데 되지 않아서 조금 더 고민이 필요할 것 같다 T.T....
🐈Feedback
1. 오늘 밥을 제대로 먹지 않았다. 이 말을 피드백에 진짜 오랜만에 쓰는 것 같은데, 피자 세 조각과 오트밀 과자 조가리 네 개 정도를 먹은게 전부이다;;; 내일은 밥 좀 잘 챙겨먹자.
2. 중간중간 그래도 재미있게 하려고 노력한 것 같다. 아직 막 엄청 어렵진 않아서 그래도 즐길 수 있는 듯. 더 어려운 문제가 나와도 즐기면서 할 수 있도록하자!
🐈To Do
1. 기술 회고 초안 작성 (이번 주 마무리)
2. React.memo 혹은 렌더링 최적화 관련 방법 찾기
'TIL' 카테고리의 다른 글
[TIL] 2023-0106 : 프리온보딩) 2주차 과제 제출, Custom hook과 관심사 분리 (0) | 2023.01.07 |
---|---|
[TIL] 2023-0104 : 프리온보딩) 1주차 과제 리뷰, 리액트 렌더링 최적화 (0) | 2023.01.04 |
[TIL] 2022-1220 : 알고리즘, 프리온보딩 인턴십, 알루 모임 시작 (0) | 2022.12.21 |
[TIL] 2022-1212 : 이력서 작성을 위한 에피소드 정리 / 뽑히는 개발자의 포트폴리오 라이브세션 / 모락모락 프로젝트 코멘트 리팩토링 (3) | 2022.12.12 |
[TIL] 2022-1111 : UI 프로토타입 작업 / 타입스크립트 공부 (1) | 2022.11.12 |