[TS] 로그인 기능 서버 통신 과정

김zunyange·2023년 9월 15일
0

TypeScript

목록 보기
1/3
post-thumbnail

이전까지는 JavaScript, React로만 로그인 기능을 작성했었는데, 이번에 처음으로 TypeScript 로 시도를 하게 되었다. 리액트에서 타입스크립트로 변환하기 위해서는 타입 정의 및 인터페이스를 사용하기만 하면 되는 줄 알았기에 아래와 같이 작성했었다.

수정 전

const navigate = useNavigate();
  const [userInfo, setUserInfo] = useState<LoginProps>({
    email: '',
    password: ''
  });

  const { email, password } = userInfo;

  const idCondition = email.includes('@') && email.includes('.');
  const pwCondition = new RegExp(
    /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*?]).{8,}$/
  ).test(password);

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

  const enterKeyUp = event => {
    if (event.keyCode === 13) {
      login();
    }
  };

  const login = () => {
    
    if (idCondition && pwCondition) {
      fetch('http://192.168.0.30:3000/users/signIn', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json;charset=utf-8' },
        body: JSON.stringify({ email: email, password: password })
      })
        .then(response => response.json())
        .then(data => {
          if (data.message) {
            alert(data.message);
          } else {
            localStorage.setItem('token', data.token);
            alert('로그인 성공');
            navigate('/');
          }
        })
        .catch(error => {
          console.error('Fetch error:', error);
          alert('Fetch error. Please check the console for details.');
        });
    }
  };

return (
        <S.LoginContainer>
        <StyledInput
          placeholder="아이디(이메일)"
          name="email"
          value={email}
          onChange={updateUserInfo}
          errorMessage={
            !idCondition ? '아이디(이메일)를 입력해주세요.' : undefined}
        />
        <S.InputPasswordWrap>
          <StyledInput
            placeholder="비밀번호"
            name="password"
            value={password}
            onChange={updateUserInfo}
            errorMessage={!pwCondition ? '비밀번호를 입력해주세요.' : undefined}
          />
          <S.StyledEye fillColor={theme.gray} />
        </S.InputPasswordWrap>
          <StyledButton 
            type="submit"
            disabled={!idCondition || !pwCondition}
			onClick={login}
          >
            로그인
          </StyledButton>
)

🤔 통신을 확인하기 위해 백엔드의 api 주소를 받아 fetch문에 넣어봤지만, 404 에러가 발생했다.

🤩 리액트에서 로그인 기능을 타입스크립트로 변환할 때, 추가해줘야 할 부분이 있었다! 이벤트 핸들러 함수에 대한 타입을 명시적으로 지정해야 한다.
위 코드에서 이벤트 핸들러 함수에 대한 타입을 명시해야할 부분은 다음 두 가지 함수이다.

타입 지정 1. updateUserInfo 함수

// 타입 지정 전
const updateUserInfo = e => {
  const { value, name } = e.target;
  setUserInfo({ ...userInfo, [name]: value });
};

이 함수의 인자 e는 이벤트 객체이며, 명시적인 타입이 지정되어 있지 않다. 이 경우, TypeScript에서는 타입 추론을 사용하여 자동으로 이벤트 객체의 타입을 추론하게 된다. 하지만 TypeScript를 더 엄격하게 사용하고 타입을 명시적으로 지정하려면 다음과 같이 함수의 타입을 정의할 수 있다.

// 타입 지정 후
const updateUserInfo = (e: React.ChangeEvent<HTMLInputElement>) => {
  const { value, name } = e.target;
  setUserInfo({ ...userInfo, [name]: value });
};

타입 지정 2. enterKeyUp 함수

const enterKeyUp = event => {
  if (event.keyCode === 13) {
    login();
  }
};

이 함수는 키보드의 키를 누를 때 호출되는 이벤트 핸들러로서, Enter 키(키 코드 13)를 누를 때 login 함수를 호출하게 된다.

이 함수의 인자 e 또한 이벤트 객체이며, 명시적인 타입이 지정되어 있지 않다. 따라서 위와 같이 TypeScript에서는 타입 추론을 사용하여 자동으로 이벤트 객체의 타입을 추론하게 된다. 하지만 TypeScript를 더 엄격하게 사용하고 타입을 명시적으로 지정하려면 다음과 같이 함수의 타입을 정의하면 된다.

const enterKeyUp = (event: React.KeyboardEvent) => {
  if (event.keyCode === 13) {
    login();
  }
};

사실 이렇게 해도 404 에러가 났었는데, 회원가입한 다른 팀원분의 통신은 성공했기에 여쭤보았다. 필수로 바꿀 필요는 없으나 form 태그를 사용해보자고 하셨다.

//form 태그 사용 전
<StyledButton 
  type="submit"
  disabled={!idCondition || !pwCondition}
  onClick={login}>
  로그인
</StyledButton>

//form 태그 사용 후
<S.Onsubmit onSubmit={login}>
  <StyledButton
    type="submit"
    disabled={!idCondition || !pwCondition}>
    로그인
  </StyledButton>
</S.Onsubmit>

form 태그로 변경

form 태그 사용 전 StyledButton 은 그냥 div태그 였는데, 클릭 이벤트 함수가 포함된 자식 태그의 부모 태그를 form로 변경하면 된다.
그리고 login 함수에 대한 타입을 FormEventHandler<HTMLFormElement> 타입을 사용하여 명시적으로 정의했습니다. 이는 해당 함수가 폼 이벤트 핸들러로 사용될 것임을 뜻한다.

양식 제출 처리 변경

form 태그로 변경했다면 또 하나 바꿔줘야 될 것이 있다.
Enter 키(keyCode 13)로 키 누르기 이벤트를 수신하는 'enterKeyUp'이라는 함수는 Enter 키를 누르면 '로그인' 기능이 호출되고, 이 방식을 사용하면 사용자는 입력 필드 중 하나에서 Enter 키를 눌러 양식을 제출할 수 있다.
하지만 login 함수는 form 요소의 onSubmit 속성을 사용하여 양식 제출 핸들러로 구체적으로 설정되고, 또한 e.preventDefault()를 사용하여 기본 양식 제출 동작을 방지한다. 즉, 사용자가 양식 내에서 "로그인" 버튼을 클릭하여 명시적으로 양식을 제출하는 경우에만 양식이 제출된다. Enter 키 누르기 이벤트는 여기에서 양식 제출에 사용되지 않는다. (출처 : GPT!)

즉, 로그인 버튼이 있는 태그에 onClick 버튼 삭제하고 그 부모의 태그를 form 태그로 바꾼 후 거기에 onSubmit을 login 함수로 설정하면 된다.


그렇다면 React.ChangeEvent 와 FormEventHandler 에 대해 좀 더 알아보자❗️❗️

🌐 React.ChangeEvent

React.ChangeEvent<HTMLInputElement>
React 구성 요소의 이벤트 핸들러 함수에 대한 매개 변수로 예상되는 이벤트 객체(e)의 유형을 지정하는 TypeScript의 유형 주석

React.ChangeEvent: React.ChangeEvent는 React에서 제공하는 일반 유형이다. React 컴포넌트의 변경 이벤트에 대한 이벤트 객체를 나타낸다. 변경 이벤트는 입력 요소(예: input 또는 textarea) 값이 변경될 때 발생한다.

React 구성 요소에서 이벤트 핸들러 함수에 대한 매개 변수 유형으로 (e: React.ChangeEvent)를 사용하면 해당 함수가 의 변경 이벤트에 해당하는 이벤트 객체를 수신해야 함을 TypeScript에 알리는 것이다. 이를 통해 TypeScript는 React의 이벤트 시스템과 함께 이 핸들러를 사용할 때 유형 검사 및 자동 완성 지원을 제공할 수 있다.

🌐 FormEventHandler

FormEventHandler<HTMLFormElement>
HTML form 요소의 onSubmit 속성에 할당할 수 있는 이벤트 핸들러 함수의 유형을 지정하기 위해 TypeScript에서 사용되는 유형 주석

FormEventHandler<HTMLFormElement>를 사용하면 본질적으로 함수가 양식 제출(form 요소의 onSubmit 이벤트)을 위한 이벤트 핸들러이고 양식 제출과 관련된 이벤트 객체를 예상해야 함을 TypeScript에 알리는 것이다.

즉, login 함수는 양식 제출 이벤트 핸들러로 선언되었으며 양식 제출과 관련된 이벤트 개체를 매개변수(이 경우 'e')로 사용한다. 이를 통해 양식의 onSubmit 속성과 함께 이 핸들러를 사용할 때 TypeScript가 유형 검사 및 자동 완성 지원을 제공할 수 있다.

최종 결과물

export interface LoginProps {
  email: string;
  password: string;
}
  
const navigate = useNavigate();
  const [userInfo, setUserInfo] = useState<LoginProps>({
    email: '',
    password: ''
  });

  const { email, password } = userInfo;

  const idCondition = email.includes('@') && email.includes('.');
  const pwCondition = new RegExp(
    /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*?]).{8,}$/
  ).test(password);

  const updateUserInfo = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value, name } = e.target;
    setUserInfo({ ...userInfo, [name]: value });
  };

  const login: FormEventHandler<HTMLFormElement> = e => {
    e.preventDefault();

    if (idCondition && pwCondition) {
      fetch('http://192.168.0.30:3000/users/signIn', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json;charset=utf-8' },
        body: JSON.stringify({ email: email, password: password })
      })
        .then(response => response.json())
        .then(data => {
          if (data.message) {
            alert(data.message);
          } else {
            localStorage.setItem('token', data.token);
            alert('로그인 성공');
            navigate('/');
          }
        })
        .catch(error => {
          console.error('Fetch error:', error);
          alert('Fetch error. Please check the console for details.');
        });
    }
  };

return (
        <S.LoginContainer>
        <StyledInput
          placeholder="아이디(이메일)"
          name="email"
          value={email}
          onChange={updateUserInfo}
          errorMessage={
            !idCondition ? '아이디(이메일)를 입력해주세요.' : undefined}
        />
        <S.InputPasswordWrap>
          <StyledInput
            placeholder="비밀번호"
            name="password"
            value={password}
            onChange={updateUserInfo}
            errorMessage={!pwCondition ? '비밀번호를 입력해주세요.' : undefined}
          />
          <S.StyledEye fillColor={theme.gray} />
        </S.InputPasswordWrap>
        <S.Onsubmit onSubmit={login}> //form 태그
          <StyledButton
            type="submit"
            disabled={!idCondition || !pwCondition}
          >
            로그인
          </StyledButton>
        </S.Onsubmit>
)

번외로 form 태그로 변경해도 통신이 안됐었는데 멍청하게도 fetch 문의 헤더를 작성하지 않아서 에러가 발생한 것이었다 ㅎㅎ;;

profile
배움은 즐거워 ~(*ૂ❛ᴗ❛*ૂ)

0개의 댓글