데브코스 1차 프로젝트 회고(2)

HappyFrog·2022년 7월 18일

가봤슈

목록 보기
2/2
post-thumbnail

로그인, 회원가입 구현

지난번 회고(1)에서 적었던, 로직을 구현 뒤 모달 컴포넌트, input등을 활용하여서 로그인과 회원가입을 구현하였다. 따라서 이번에 회고하게 될 주요한 사항들은 UI 컴포넌트와, react-hook-form의 사용이다.

로그인과 회원가입의 경우 Navigation 컴포넌트 내에서 해당하는 버튼을 누르면 모달이 등장하게끔 기획하였다. 따라서 input을 만들기 전에 Navigation 컴포넌트를 만들어 주었고, Navigation 컴포넌트도 만들어 주었다.

function Modal({ children, width, height, visible, onClose, ...props }) {
  // custom hooks
  const ref = useClickAway(() => {
    if (onClose) onClose();
  });
  
  const containerStyle = useMemo(() => ({
    width,
    height,
  }));
  
  const elem = useMemo(() => document.createElement("div"), []);
  useEffect(() => {
    document.body.appendChild(elem);
    return () => {
      document.body.removeChild(elem);
    };
  });
  
  return ReactDOM.createPortal(
    <Ms.BackgroundDim style={{ display: visible ? "block" : "none" }}>
      <Ms.ModalContainer
        ref={ref}
        style={{ ...props.style, ...containerStyle }}
      >
        {children}
      </Ms.ModalContainer>
    </Ms.BackgroundDim>,
    elem
  );
}

먼저 효과적인 모달 구현을 위해서 React의 portal을 사용하였다. 공식문서
portal은 DOM의 다른 위치에 컴포넌트를 삽입하고자 할때 사용하면 되는데 위의 모달의 경우 body 아래의 child로 사용하고자 portal을 사용하게 되었다. (이 부분은 useEffect 부분의 코드를 보면 보다 이해하기 쉬울 것이다). 또한 onClose 함수를 받아서 만약 dim, modal 뒤쪽의 까만 배경을 클릭했을 경우 onClose 함수를 실행하게끔 구현하였다.

useClickAway custom hooks

const events = ["mousedown", "touchstart"];

function useClickAway(handler) {
  const ref = useRef(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const handleEvent = (e) => {
      if (!element.contains(e.target)) handler();
    };

    events.forEach((event) => document.addEventListener(event, handleEvent));

    // eslint-disable-next-line
    return () => {
      events.forEach((event) =>
        document.removeEventListener(event, handleEvent)
      );
    };
  }, [ref]);

  return ref;
}

이 custom hooks는 dim 클릭시 모달을 닫게 하게끔 하기 위한 customhooks이다. 따라서 handler 함수를 props로 받았고, 만약 클릭한 지점이 element에 포함되어 있지 않으면 handler 함수를 실행하게끔 구현하였다.

모달이 두개이지만, 모달을 open할 때 사용하는 state는 두개 사용하지 않고 object로 관리 하였다.

const [modalStatus, setModalStatus] = useState({
    visible: false,
    type: "", //login, signup
  });

또한 로그인시, 로그인 상태가 아닌지에 따라 navigation 컴포넌트의 렌더링 내용이 달라져야 하기 때문에 loginBlock과 logoutBlock으로 나누었다. (이전 회고에서 작성한 것 처럼 로그인 여부를 전역 state로 관리하였기 때문에 이 여부에 따라 각기 다른 block 컴포넌트를 렌더링 해주었다.)

const [isLogined, setIsLogined] = useRecoilState(loginStatus);

Login & SignUp

Login과 SignUp 컴포넌트를 구현하기 앞서, 팀원 분께서 Input 컴포넌트를 완성시켜 주셨다.
따라서 위 두개의 컴포넌트를 구현할 때 이 Input 컴포넌트를 활용하기로 하였다.
처음에 이 Input 컴포넌트에 다음과 같이 바로 react-hook-form 코드를 작성하였지만, react-hook-form이 제대로 작동하지 않았다.

// ❌ 잘못된 예시(바로 사용하면 안됨)
<Input {...register("field")} />

다만 공식문서와 구글링을 통해서 이에 대한 해법을 찾을 수 있었다.
react-hook-form같은 경우, component에 이를 적용하고 싶으면 forwardRef를 통해서 ref를 컴포넌트에 전달해주어야 한다.

// 예시
const Input = React.forwardRef((props, ref) => {
	// ...componet 로직
    return (
    	<input {...props} ref={ref} />
    );
});

따라서 위와 같은 방법으로 Input 컴포넌트를 변경하고 나서야 제대로 작동되었다.

react-hook-form의 활용

form 컨트롤을 위해서 react-hook-form에서 useForm을 가져왔고, 여기서 register와 handleSubmit, formState에서 errors를 가져왔다.

// 예시
import { useForm } from "react-hook-form";

const Login = (props) => {
  //input이 들어오는 것을 감지하여 error 메세지를 출력하기 위해서 mode를 all로 설정하였다.
	const { register, handleSubmit, formState: { errors } } = useForm({ mode: "all" });
  
  // login 로직들....
  
  //submit 처리
  const handleSubmit = async (data) => {
  	const { id, password } = data;
    const result = await loginFetchFunction(id, password);
  };
  
  return (
  	<form>
    	<Input {...register("id", { required: "필수" })} />
        // optional chaining을 넣어야 typeerror가 발생하지 않는다
        {errors?.id?.message}
        <Input {...register("password", { required: "필수", ...other rules })} type="password" />
        {errors?.password?.message}
        <button type="submit">로그인</button>
    </form>
  );
}; 

SignUp컴포넌트 또한 위와 유사하게 구현 하였다. 다만, 회원가입의 경우, 로그인에 비해 더 많은 검증요소가 필요하기 때문에 이를 작성해 주었다.

<input {...register("someInput", maxLength: { value: 12, message: "최대 12자리" })} />

react-hook-form을 사용하다가 error message 지정을 어떻게 해야할지 모르겠다면, 위와 같이 value에는 값, message에 메세지를 지정하여 주면 된다.

결과 모습

로그인 모습회원가입 모습

고민거리 || 아쉬운점 🧐

  • Navigation 컴포넌트가 재활용성이 떨어짐
    • props로 navigation에 들어갈 목록의 리스트를 받으면..?
    • 근본적으로 Navigation의 재활용성이 중요한가?
      • 추후 SubNavigation이 필요로 하는 상황이 오면..? 즉 결국 확장성을 고려하여 Navigation 컴포넌트를 수정하는 것이 맞는 것 같다.
  • Navigation 컴포넌트 내에서 컴포넌트 분할이 아쉬웠다. 분할이 효율적으로 안되다 보니, 확실히 Navigation 컴포넌트의 코드량은 증가하였고, 이는 코드 가독성의 저하를 불러 일으켰다.
  • Navigation에서 로직을 수행하는 것이 맞는가?
    • Navigation은 단순하게 컴포넌트를 조합해서 렌더링 역할을 수행하고, 다른 하위 컴포넌트에서 각자 필요한 로직을 수행하게끔 하면 분할도 좀 더 쉽고, 코드의 가독성이 올라갈 것 같다.
profile
성장하고 싶은 긍정 개구리

0개의 댓글