[React] 로그인/회원가입 리팩토링

SangHeon·2023년 1월 26일
0

현재 상황

현재 로그인 / 회원가입 페이지의 경우 <User /> 라는 컴포넌트 하나로 관리되고 있고
라우팅에 따라 다른 화면이 나타나도록 구현되어 있었다.

login.jpg1signUp.jpg2

그리고 크게 3개의 기능으로 이루어져 있는데

  1. 인풋값 핸들링
  2. 유효성 검사
  3. 서버와의 통신이다.

위의 기능을 포함한채 <User /> 라는 로그인 회원가입 페이지의 공통 컴포넌트를 구현하다 보니
관련 기능을 구현한 내용이 한눈에 보이지 않는 단점이 나타났다

따라서 이전에 수정한 이력이 있는 유효성 검사를 제외한 두 가지 기능에 대해
custom hookfetch함수의 분리 로 리팩토링을 진행하였다.


리팩토링

인풋값 핸들

As-is

🔸 User.js

const initialUserInfo = {
  email: '',
  password: '',
  userName: '',
  phoneNumber: '',
};
const [userInfo, setUserInfo] = useState(initialUserInfo);

🔸 Input.js

const saveUserInfo = (e) => {
  const { name, value } = e.target;
  setUserInfo(prev => ({ ...prev, [name]: value }));
};

To-be

🔸 useUserInfo.js

import { useState } from 'react';

export const initialUserInfo = {
  email: '',
  password: '',
  userName: '',
  phoneNumber: '',
};

export const useUserInfo = () => {
  const [userInfo, setUserInfo] = useState(initialUserInfo);

  const saveUserInfo = e => {
    const { name, value } = e.target;
    setUserInfo(prev => ({ ...prev, [name]: value }));
  };

  return [userInfo, setUserInfo, saveUserInfo];
};

🔸 User.js

const [userInfo, setUserInfo, saveUserInfo] = useUserInfo();

userInfosetUserInfo, saveUserInfo를 리턴해주는 Custom Hook 을 만들어 줌으로써
사용하는 쪽의 컴포넌트에서는 각각 필요한 값만 뽑아서 사용할 수 있도록 변경하였다.
<User /> 컴포넌트의 코드가 1차적으로 단순하게 변하여 코드의 30%가량이 줄어들었다


서버와의 통신

As-is

🔸 User.js

const handleFormSubmit = e => {
    e.preventDefault();

    if (allVaild) {
      let userData =
        title === '로그인'
          ? {
              email: userInfo.email,
              password: userInfo.password,
            }
          : {
              email: userInfo.email,
              password: userInfo.password,
              name: userInfo.userName,
              phoneNumber: userInfo.phoneNumber,
            };

      let fetchUrl = title === '로그인' ? FETCH_SIGN_IN_API : FETCH_SIGN_UP_API;

      fetch(fetchUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json;charset=utf-8',
        },
        body: JSON.stringify(userData),
      })
        .then(res => res.json())
        .then(data => {
          console.log(data);
          if (title === '회원가입') {
            alert('회원가입이 완료되었습니다!');
            setUserInfo(initialUserInfo);
            navigate('/login');
          }
          if (data.accessToken) {
            alert('로그인이 완료되었습니다!');
            localStorage.setItem('accessToken', data.accessToken);
            navigate('/');
          }
        });
    } else {
      alert('Validation Error!');
    }
  };

To-be

🔸 config.js

import { FETCH_SIGN_IN_API, FETCH_SIGN_UP_API } from '../../../config';

export const fetchUser = async (title, userInfo) => {
  let userData =
    title === '로그인'
      ? {
          email: userInfo.email,
          password: userInfo.password,
        }
      : {
          email: userInfo.email,
          password: userInfo.password,
          name: userInfo.userName,
          phoneNumber: userInfo.phoneNumber,
        };

  let fetchUrl = title === '로그인' ? FETCH_SIGN_IN_API : FETCH_SIGN_UP_API;

  try {
    const res = await fetch(fetchUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
      },
      body: JSON.stringify(userData),
    });
    return res.json();
  } catch (e) {
    return e;
  }
};

🔸 User.js

const handleFormSubmit = async e => {
  e.preventDefault();
  
  const allVaild = validCondition.email && validCondition.password;
  
  if (!allVaild) {
    alert('Validation Error!');
    return;
  }

  const data = await fetchUser(title, userInfo);
  
  if (data.accessToken) {
    alert('로그인이 완료되었습니다!');
    localStorage.setItem('accessToken', data.accessToken);
    navigate('/');
  }
  
  if (data.message === 'SIGNUP_SUCCESS') {
    alert('회원가입이 완료되었습니다!');
    setUserInfo(initialUserInfo);
    navigate('/login');
  }
};

코드의 가독성을 위해 else문에 해당하는 사항을 위쪽으로 끌어올려 예외상황에서 return문이 먼저 실행되도록 변경하였고, fetch 함수를 페이지 내에 별도의 config 파일로 저장하여 Promise 핸들링이 완료된 결과값만 받도록 코드를 간소화 시켜주었다.
마지막으로 allValid 조건은 위 함수에서만 사용하므로 안쪽으로 이동시켜 주었다.

마무리

이번 리팩토링은 코드가 쉽게 읽히는 것과 관심사의 분리 부분만 신경쓰며 진행하였지만
다음 번에는 리렌더링을 최소화하는 방향도 같이 확인해야겠다는 생각이 머리를 스친다.

우선 전체 코드량 감소 및 가독성이 증가하였으므로 만족하고 넘어가야겠다.

login.jpg1 As-IssignUp.jpg2 To-Be
profile
Front-end Developer

0개의 댓글