오랜만의 윙글이다.
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;
이런 곳 최적화 진행하고 이렇게 나눴다.
거의 노가다라 설명이 별로 없지만 이번 기회에 큰 교훈을 얻었다.
이런 교훈을 얻었다. 물론 처음 코드가 내 코드가 아니라서 이렇게 만들었지만 나중에 내가 만든다고 생각하면 꼭 꼭 간단하면서도 로직을 잘 분리시켜 만들어야겠다고 생각했다.. 끝!
저도 항상 일단 급한거부터 하고 리펙토링하자! 마음먹게 되는데 나중에 하는게 훨씬 어렵고 복잡하게 느껴지는 것 같아요 ㅠㅠ 고생하셨습니다!