오랜만의 윙글이다.
API 연결 이후 그동안 자잘한 수정점은 있었지만 기록을 따로 하진 않았다.
QA를 진행하면서 디자인 수정점이 있었고, 디자인 수정 뿐만 아니라 공통 UI 제작이 있었다. 적용을 하기 전 코드 분리를 먼저 해 운영 보수를 보다 쉽게 하겠다.
500줄 가까이나 되는 엄청 큰 파일이고, 이메일부터 패스워드까지 모든 로직과 UI가 한 파일에 있었다. 사실 만들면서도 리팩토링해야겠다 생각은 했지만 API 연결이 급선무였기 때문에 먼저 연결을 하고, 리팩토링을 진행하려고 했다.
sendEmail 함수를 호출하여 이메일 인증메일을 보냄. useMutation 훅을 사용하여 비동기 요청을 처리하고, onMutate, onSuccess, onError 콜백을 사용하여 요청의 상태에 따라 UI를 업데이트.verifyEmail 함수를 호출하여 이메일 인증번호를 확인. 마찬가지로 useMutation 훅을 사용하여 비동기 요청을 처리하고, onSuccess, onError 콜백을 사용하여 요청의 상태에 따라 UI를 업데이트.useEffect 훅을 사용하여 비밀번호, 비밀번호 확인, 이름이 유효할 경우 회원가입 폼 데이터를 저장. 이를 위해 setSignUpFormData 함수를 사용하여 Recoil 상태를 업데이트.CheckNickname 함수를 호출하여 입력한 닉네임이 중복되는지 확인. useMutation 훅을 사용하여 비동기 요청을 처리하고, onSuccess, onError 콜백을 사용하여 요청의 상태에 따라 UI를 업데이트.이 외에도 스타일 코드 등 나눠줘야할 게 많다..
일단 유효성 검사, 리액트 쿼리 등의 비즈니스 로직 구역과 리턴에 쓴 UI 구역으로 나눠서 리팩토링 규칙을 세울 것이다.
먼저 리턴을 보고 input마다 구역을 나눠서 리팩토링 할 것이다.
이렇게 나눠서 리팩토링 할 것이다. src/components/authpage/signup/signUpInput 폴더를 만들고 index.tsx를 만들어 구역을 먼저 만들자.
import { Margin, Text } from "@/src/components/ui";
import EmailVerify from "./emailVerify";
import PasswordVerify from "./passwordVerify";
import NameInput from "./nameInput";
import NicknameVerify from "./nicknameVerify";
export default function InputBox() {
return (
<>
<Text.Title1 color="gray900">학생 정보</Text.Title1>
<Margin direction="column" size={16} />
<EmailVerify />
<PasswordVerify />
<NameInput />
<NicknameVerify />
</>
);
}
물론 이렇게 하고 emailVerify.tsx 식으로 파일을 만들고
export default function EmailVerify() {
return (
<>
<Text.Body1 color="gray700">이메일</Text.Body1>
<Margin direction="column" size={8} />
... // Input
</>
);
}
이렇게 리턴에 UI를 배치했다. 나머지 3개 파일도 똑같이 만들었다.
여기는 되게 귀찮다. 유효성 검사는 UI 구역 별로 나눠서 그 구역에 검사 코드를 배치하면 되고, 나머지 상태들도 나눠주면 된다.
const [inputData, setInputData] = useState<SignupInputData>({
email: "",
emailCertification: "",
password: "",
passwordCheck: "",
name: "",
nickname: "",
});
const { email, emailCertification, password, passwordCheck, name, nickname } =
inputData;
const setSignUpFormData = useSetRecoilState(signUpFormDataAtom);
const [isErrorEmailCertify, setErrorEmailCertify] = useState(true);
const [isErrorPassword, setErrorPassword] = useState(true);
const [isErrorPasswordCheck, setErrorPasswordCheck] = useState(true);
const [isErrorName, setErrorName] = useState(true);
const [isErrorNickName, setErrorNickName] = useState(true);
const [isCheckedNickname, setCheckedNickname] = useState(false);
const [isVerifiedNickname, setVerifiedNickname] = useState(false);
const handleInputData = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setInputData((prevData) => ({
...prevData,
[e.target.name]: e.target.value,
}));
},
[]
);
... // 유효성 검사 코드
이 부분들을 각각 상태에 맞춰서 해야한다..
또한 기억해둬야할 것이 이메일 검증 및 인증이나 닉네임 확인 시 요청이 성공한다면 리코일 데이터에 넣어서 갱신시켜준다.
// useEffect로 비밀번호, 비밀번호 확인, 이름 존재 시 회원가입 폼 데이터 저장
useEffect(() => {
if (!isErrorPassword && !isErrorPasswordCheck && !isErrorName) {
setSignUpFormData((prev) => ({
...prev,
password: password,
name: name,
}));
}
}, [
isErrorName,
isErrorPassword,
isErrorPasswordCheck,
name,
password,
setSignUpFormData,
]);
하지만 비번, 비번 확인, 이름은 입력만 하기에 데이터 입력 시 감지하여 넣어주는 것이라 이것도 나눠줘야한다. 이런 걸 유의하면서 나눠주자.
import { signUpFormDataAtom } from "@/src/atoms/auth/signUpAtoms";
import { Margin, Text } from "@/src/components/ui";
import { useCallback, useState } from "react";
import { useSetRecoilState } from "recoil";
import styled from "styled-components";
import { ErrorMent } from "../errorMent";
import {
sendEmailAuth,
verifyEmailCertification,
} from "@/src/api/auth/emailAPI";
import { useMutation } from "react-query";
interface StyledInputProps {
small: boolean;
error: boolean;
}
export default function EmailVerify() {
const [buttonMessage, setButtonMessage] = useState("인증 전송");
const [emailMent, setEmailMent] = useState("");
const [inputData, setInputData] = useState({
email: "",
emailCertification: "",
});
const { email, emailCertification } = inputData;
const setSignUpFormData = useSetRecoilState(signUpFormDataAtom);
const [isErrorEmailCertify, setErrorEmailCertify] = useState(true);
const handleInputData = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setInputData((prevData) => ({
...prevData,
[e.target.name]: e.target.value,
}));
},
[]
);
// 이메일 인증메일 보내기
const { mutate: sendEmail } = useMutation(() => sendEmailAuth(email), {
onMutate: () => {
setButtonMessage("전송 중");
},
onSuccess: () => {
setButtonMessage("재전송");
setEmailMent("인증메일을 전송했습니다.");
},
onError: (error) => {
setErrorEmailCertify(true);
alert(error);
throw error;
},
});
const handleSendEmail = useCallback(() => {
if (email === "") {
alert("이메일을 입력해주세요.");
return;
}
sendEmail();
}, [email, sendEmail]);
// 이메일 인증번호 확인
const { mutate: verifyEmail, isLoading: isLoadingVerifyEmail } = useMutation(
() => verifyEmailCertification({ email, emailCertification }),
{
onSuccess: () => {
setErrorEmailCertify(false);
setSignUpFormData((prev) => ({
...prev,
email,
}));
},
onError: (error) => {
setErrorEmailCertify(true);
throw error;
},
}
);
const handleVerifyEmail = useCallback(() => {
if (email === "") {
alert("이메일을 입력해주세요.");
return;
}
verifyEmail();
}, [email, verifyEmail]);
return (
<>
<Text.Body1 color="gray700">이메일</Text.Body1>
<Margin direction="column" size={8} />
<S.ContentWrapper>
<S.Content>
<S.InputField small={true} error={false}>
<input
name="email"
value={email}
type="email"
placeholder="abc@naver.com"
onChange={(e) => {
handleInputData(e);
}}
/>
</S.InputField>
<S.ButtonWrapper small={true} error={false}>
<S.Button onClick={() => handleSendEmail()}>
{buttonMessage}
</S.Button>
</S.ButtonWrapper>
</S.Content>
<ErrorMent error={false} errorMent="" ment={emailMent} />
</S.ContentWrapper>
<Text.Body1 color="gray700">인증번호 입력</Text.Body1>
<Margin direction="column" size={8} />
<S.ContentWrapper>
<S.Content>
<S.InputField small={true} error={isErrorEmailCertify}>
<input
name="emailCertification"
value={emailCertification}
type="string"
placeholder="인증번호"
onChange={(e) => {
handleInputData(e);
}}
/>
</S.InputField>
<S.ButtonWrapper small={true} error={isErrorEmailCertify}>
<S.Button onClick={() => handleVerifyEmail()}>인증 확인</S.Button>
</S.ButtonWrapper>
</S.Content>
{isLoadingVerifyEmail ? (
<ErrorMent error={false} errorMent="" ment="인증 확인 중 입니다." />
) : (
<ErrorMent
error={isErrorEmailCertify}
errorMent="인증정보가 일치하지 않습니다."
ment="인증이 완료되었습니다."
/>
)}
</S.ContentWrapper>
</>
);
}
약간 이런 식으로 나눴다. 상태 값도 나눠줬고, 나머지 비즈니스 로직도 그대로 옮겨 왔다. 다른 파일도 비슷하게 했다.
아래는 위 이메일과는 다른 성격인 이름 입력 관련 컴포넌트도 보여주겠다.
import { signUpFormDataAtom } from "@/src/atoms/auth/signUpAtoms";
import { Margin, Text } from "@/src/components/ui";
import { useCallback, useEffect, useState } from "react";
import { useSetRecoilState } from "recoil";
import styled from "styled-components";
import { ErrorMent } from "../errorMent";
interface StyledInputProps {
small: boolean;
error: boolean;
}
export default function NameInput() {
const [inputData, setInputData] = useState({
name: "",
});
const { name } = inputData;
const setSignUpFormData = useSetRecoilState(signUpFormDataAtom);
const [isErrorName, setErrorName] = useState(true);
// useEffect로 이름 존재 시 회원가입 폼 데이터 저장
useEffect(() => {
if (!isErrorName) {
setSignUpFormData((prev) => ({
...prev,
name: name,
}));
}
}, [isErrorName, name, setSignUpFormData]);
const handleInputData = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setInputData((prevData) => ({
...prevData,
[e.target.name]: e.target.value,
}));
},
[]
);
// 이름 유효성 검사
const handleErrorName = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const special_pattern = /^[a-zA-Z가-힣\s]+$/;
if (!special_pattern.test(e.target.value)) {
setErrorName(true);
} else {
setErrorName(false);
}
},
[]
);
return (
<>
<Text.Body1 color="gray700">이름</Text.Body1>
<Margin direction="column" size={8} />
<S.ContentWrapper>
<S.Content>
<S.InputField small={false} error={isErrorName}>
<input
name="name"
value={name}
type="string"
placeholder="김윙글"
onChange={(e) => {
handleInputData(e);
handleErrorName(e);
}}
/>
</S.InputField>
</S.Content>
<ErrorMent
error={isErrorName}
errorMent="실명을 입력하세요 (한글, 영어 대/소문자 사용 가능) "
ment=" 실명을 입력하세요 (한글, 영어 대/소문자 사용 가능) "
/>
</S.ContentWrapper>
</>
);
}
const { name } = inputData; 이런 곳 최적화 진행하고 이렇게 나눴다.
거의 노가다라 설명이 별로 없지만 이번 기회에 큰 교훈을 얻었다.
이런 교훈을 얻었다. 물론 처음 코드가 내 코드가 아니라서 이렇게 만들었지만 나중에 내가 만든다고 생각하면 꼭 꼭 간단하면서도 로직을 잘 분리시켜 만들어야겠다고 생각했다.. 끝!
저도 항상 일단 급한거부터 하고 리펙토링하자! 마음먹게 되는데 나중에 하는게 훨씬 어렵고 복잡하게 느껴지는 것 같아요 ㅠㅠ 고생하셨습니다!