인디버디 리팩토링하기 (3)
useCheckAuth(PATHURL.LOGIN);
게시글 작성 페이지 리팩토링하기
게시글 작성 및 수정 페이지를 리팩토링 해볼 생각이다. 현재 하나의 컴포넌트에서 게시글을 작성할지 수정할지 경로에 postId로 확인을 하고 게시글 아이디가 있으면 값을 서버에서 받아와 수정할 수 있도록하고 아니면 그냥 입력을 다 받는 식으로 하나의 컴포넌트를 재사용했다.
현재 상황
현재는 아래와 같은 코드로 되어있다. 문제가 없어 보이지만 내부를 확인하면 코드가 많이 더럽다고 생각했다.
const Posting = () => {
const { postId } = useParams();
useCheckAuth(PATH_URL.LOGIN);
return (
<StyledContainer>
<Title text={postId ? '게시글 수정하기' : '게시글 작성하기'} />
<InputSection />
</StyledContainer>
);
};
여기서 먼저 InputSection컴포넌트를 먼저 수정해보겠다.
문제점
1. 상태 구조
현재 해당 컴포넌트에는 너무 많은 상태가 존재한다. 여기서 상태 구조를 바꿀 수 있다면 바꾸는 것이 좋을 것 같다.
const [post, setPost] = useState<PostType>(postInputInitValue);
const [url, setUrl] = useState<string[]>([]);
const [files, setFiles] = useState<File[]>([]);
const { gameId, postId } = useParams();
const [isLoading, setIsLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [errorMsg, setErrorMsg] = useState('');
const navigation = useNavigate();
2. 반복되는 코드
현재 데이터 패칭이 반복되고 있다. 코드 작성 당시에는 함수 추출을 고려했지만, 적용하지 못했다. 그렇기 때문에 같은 코드가 너무 많이 반복되고 있다. 이는 함수를 이용하기 정말 좋은 상황이라는 것이다.
if (!postId) {
// 함수 추출 가능
formData.append(
'post',
new Blob([JSON.stringify(post)], {
type: 'application/json'
})
);
postData(
`${process.env.REACT_APP_API_URL}/api/games/${gameId}/posts`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: localStorage.getItem('access_token')
}
},
() => navigation(`${PATH_URL.GAME}${gameId}`),
() => setErrorMsg('게시글 작성에 오류가 발생했습니다.')
);
} else {
formData.append(
'patch',
new Blob(
[
JSON.stringify({
...post,
fileUrlList: [...url]
})
],
{
type: 'application/json'
}
)
);
patchData(
`${process.env.REACT_APP_API_URL}/api/posts/${postId}`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: localStorage.getItem('access_token')
}
},
() => navigation(`${PATH_URL.GAME}${gameId}`),
() => setErrorMsg('게시글 작성에 오류가 발생했습니다.')
);
};
작성과 수정 페이지를 하나의 컴포넌트에서 담당하고 있기 때문에 위와 같은 문제가 발생했다. 떠오른 해결방법은 컴포넌트를 분리하는 것, 함수를 추출하는 것이다. 일단 함수를 추출할 생각이다.
리팩토링
상태 구조
먼저 상태를 리팩토링 해보자.
const [post, setPost] = useState<PostType>(postInputInitValue);
const [images, setImages] = useState<{ urls: string[]; files: File[] }>({
urls: [],
files: []
});
const { gameId, postId } = useParams();
const [isLoading, setIsLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [errorMsg, setErrorMsg] = useState('');
const navigation = useNavigate();
상태 구조를 변경할 게 url과 files를 합치는 것 밖에 없을 것 같다.
반복되는 코드
반복되는 코드는 함수로 추출할 수 있다. 하지만 항상 이런 방법이 좋은 코드라고 생각하지는 않는다.
const onSubmitHandler = (e: SubmitType) => {
e.preventDefault();
if (!validatePost(post)) {
setIsOpen(true);
setErrorMsg('입력값을 정확하게 입력해주세요.');
return;
}
const formData = new FormData();
if (images.files.length !== 0) {
images.files.forEach((file) => {
formData.append('files', file);
});
}
if (!postId) {
// 함수 추출 가능
formData.append(
'post',
new Blob([JSON.stringify(post)], {
type: 'application/json'
})
);
postData(
`${process.env.REACT_APP_API_URL}/api/games/${gameId}/posts`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: localStorage.getItem('access_token')
}
},
() => navigation(`${PATH_URL.GAME}${gameId}`),
() => setErrorMsg('게시글 작성에 오류가 발생했습니다.')
);
} else {
formData.append(
'patch',
new Blob(
[
JSON.stringify({
fileUrlList: [...images.urls]
})
],
{
type: 'application/json'
}
)
);
patchData(
`${process.env.REACT_APP_API_URL}/api/posts/${postId}`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: localStorage.getItem('access_token')
}
},
() => navigation(`${PATH_URL.GAME}${gameId}`),
() => setErrorMsg('게시글 작성에 오류가 발생했습니다.')
);
}
};
처음 상태는 위와 같다. 급하게 기능 구현만을 생각했기 때문에 게시글 수정 시 실행되는 코드가 반복적으로 추가되었다. 이 부분을 수정할 것이다.
먼저 FormData에 새로운 데이터를 추가하는 함수를 만든다.
const appendFormData = (formData: FormData, name: string, data: PostType) => {
formData.append(
name,
new Blob([JSON.stringify(post)], {
type: 'application/json'
})
);
};
이후 요청 코드와 함께 폼 데이터를 만들어서 넘겨주는 함수를 만든다.
const requestData = (
formData: FormData,
name: string,
data: PostType,
path: string,
fn: requestType
) => {
appendFormData(formData, name, data);
fn(
path,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: localStorage.getItem('access_token')
}
},
() => navigation(`${PATH_URL.GAME}${gameId}`),
() => setErrorMsg('게시글 작성에 오류가 발생했습니다.')
);
};
그 후 반복되는 코드를 줄이면 된다.
const onSubmitHandler = (e: SubmitType) => {
e.preventDefault();
if (!validatePost(post)) {
setIsOpen(true);
setErrorMsg('입력값을 정확하게 입력해주세요.');
return;
}
const formData = new FormData();
if (images.files.length !== 0) {
images.files.forEach((file) => {
formData.append('files', file);
});
}
const method = postId ? 'patch' : 'post';
const data = postId ? { ...post, fileUrlList: [...images.urls] } : post;
const url = postId
? `${process.env.REACT_APP_API_URL}/api/posts/${postId}`
: `${process.env.REACT_APP_API_URL}/api/games/${gameId}/posts`;
const reqFunc = postId ? patchData : postData;
requestData(formData, method, data, url, reqFunc);
};
코드가 눈에 띄게 줄었다. 하지만 좋은 코드일까? 내 생각엔 작성 페이지와 수정 페이지를 분리하는 게 더 좋았다고 생각을 한다. 하나의 컴포넌트에서 두 가지 일을 하는 것이니까 단일 책임 원칙에서 위배되는 것이다.