본문 바로가기
웹 프로그래밍/👨‍👨‍👧‍👧소셜 가계부

[React/Node.js/Express/MongoDB] 소셜 가계부 프로젝트 구현 일지: JavaScript → TypeScript 마이그레이션 5(Auth: Test 계정 Auto Fill 구현, Community: pagination 마이그레이션)

by 청량리 물냉면 2024. 4. 14.
반응형

이전 마이그레이션 관련 포스팅

  1. JavaScript → TypeScript 마이그레이션 1(세팅, App, AppRouter)
  2. JavaScript → TypeScript 마이그레이션 2(대시보드 페이지, 대시보드 관련 컴포넌트: state 빈 배열 초기화 never type 에러 해결)
  3. JavaScript → TypeScript 마이그레이션 3(가계부 페이지, Button 컴포넌트: onClick 함수 타입 지정, 자잘한 오류들 해결)
  4. JavaScript → TypeScript 마이그레이션 4(가계부 페이지 관련 컴포넌트: TransactionList, Modal)

 

🔐 Auth 페이지

Form 태그 내부 데이터 수정 및 submit

 //수정
  const onEditChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    ...
  };

  //추가(POST)
  const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    ...
  };

form 내부 데이터 change의 경우 `ChangeEvent<HTMLInputElement>`로 타입 지정

submit의 경우 `FormEvent<HTMLFormElement>`로 타입 지정

👉 IDE에서 타입을 추천해 주어서 그대로 입력했다. 이 부분에 대해서 검색해봤는데, 리액트 자체에서 정한 타입이므로 따로 이해할 건 없고 그냥 외우면 된다고 한다.

👉 꺾쇠 안에는 내부 데이터의 형태를 넣어준다. 추후 다른 컴포넌트를 마이그레이션하면서 `HTMLInputElement`만으로는 TextArea까지 커버되지 않는다는 사실을 알게 되었다. 이 부분은 `e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>` 코드를 사용하여 해결했다.

 

 

테스트 계정 정보 Auto Fill 구현 

프로젝트 테스트 계정으로의 접근을 수월하게 만들고 싶어, 버튼 하나만 누르면 자동으로 input field에 테스트 계정(이메일, 비밀번호)이 채워지도록 하는 기능을 구현했다.

먼저 useRef를 이용해 input value에 접근하는 코드를 고쳐주었다. useRef의 타입은 IDE에서 추천해주는 대로 `<HTMLInputElement>`로 지정해 주었다.

  const nameInputRef = useRef<HTMLInputElement>(null);
  const emailInputRef = useRef<HTMLInputElement>(null);
  const passwordInputRef = useRef<HTMLInputElement>(null);

다음으로는 로그인/회원가입 창에 테스트 계정 버튼을 추가했다. 

        <h1>{isLoginMode ? "로그인" : "회원가입"}</h1>
        {isLoginMode ? (
          <span className="auto-fill" onClick={autoFillTestAuth}>
            테스트 계정 로그인을 원할 시 클릭해주세요
          </span>
        ) : (
          ""
        )}

로그인 모드일 때 `<span>` 태그를 이용해 테스트 계정 autofill 문구를 넣어주었다.

이 태그 부분을 클릭하면 자동으로 input field에 테스트 계정의 이메일, 비밀번호가 채워질 것이다. 

`autoFillTestAuth` 함수는 아래와 같다. 

  const autoFillTestAuth = () => {
    emailInputRef.current!.value = "112@naver.com";
    passwordInputRef.current!.value = "qwer1234";
  };

우선 useRef를 사용하여, 채워진 value값이 사용자의 눈에 명시적으로 보이도록 했다.

이 값은 input에 채워진 것일 뿐 form 내부의 값은 변화가 없다. 따라서 이대로 로그인 버튼을 누른다면, 여전히 form 내부 값은 비어있는 상태이기 때문에 로그인이 되지 않는다. 따라서 form의 값을 직접 변경해 주어야 한다.

  const autoFillTestAuth = () => {
    emailInputRef.current!.value = "112@naver.com";
    passwordInputRef.current!.value = "qwer1234";

    form.email = "112@naver.com"; //form 값 변경
    form.password = "qwer1234"; //form 값 변경
  };

여기까지 완성.

이제 제대로 동작하겠지? 했지만... 에러가 발생했다.

 

(property) React.RefObject<HTMLInputElement>.current: HTMLInputElement | null

emailInputRef.current.value = "112@naver.com";

대강 해석해 보니, current의 타입이 null일지도 모르므로 타입을 한번 더 확인하라는 뜻인 것 같다.

current에 값이 없다면 당연히 value 값도 없을테니 거기 접근할 수는 없겠지...

 

✅ 해결

타입스크립트 단언연산자Assertion Operator(`!`)를 이용해 문제를 해결했다.

단언연산자는 적용된 피연산자가 null 이나 undefined 가 아니라고 타입스크립트 컴파일러에게 단언하여, 일시적으로 Null 제약조건을 완화시켜주는 연산자이다.

만약 단일연산자를 이용해 제약조건을 완화한 값이 null 또는 undefined인 경우 이 값에 접근하려 하면 런타임 에러가 발생할 수 있다.

나의 경우 current.value에 새로운 값을 채워주는 코드를 작성하고 있었기 때문에 current가 null이거나 undefined이거나 큰 상관은 없었다. 해당 값에 접근하려는 게 아니라 값을 채워주고 있었으니까!

하지만 값에 접근하려 할 때는 해당 값이 null일 때를 대비하여 조금 더 신중하게 추가 장치를 고려하는 것이 좋다. 대개 아래와 같이 type guard를 사용한다.

    if (emailInputRef.current) {
      emailInputRef.current.value = "112@naver.com";
    }

type guard를 사용하면 이렇게 조건문을 이용하여 current 값이 null이 아닌 경우에만 내부 코드를 실행시킬 수 있다. 

완성된 Auto Fill 기능

 

 

👪 Community 페이지

Community 페이지와 관련된 컴포넌트들 대부분은 이전에 포스팅한 것과 TS 마이그레이션 한 내용이 크게 다르지 않았다.

하지만 Pagination 컴포넌트는 마이그레이션 진행하면서 새롭게 알게 된 내용이 있어서 기록해보기로 했다. 

Pagination 마이그레이션

// Community.tsx

<Pagination postsPerPage={postsPerPage} totalPosts={board.length} paginate={setCurrentPage}></Pagination>

먼저 Community 메인 페이지를 확인해 보면, 페이지네이션 컴포넌트를 사용할 때 paginate 프로퍼티에 set함수를 전달하고 있다.

useState의 set함수 타입은 어떻게 지정해야 하는 걸까?

해당 프로퍼티 위에 마우스를 가져다 대니 IDE가 친절하게 타입을 알려준다.

useState의 set함수 타입은 `React.Dispatch<React.SetStateAction<T>>`으로 타입을 지정해 주면 된다.

// Pagination.tsx
...

interface PaginationType {
  postsPerPage: number;
  totalPosts: number;
  currentPage: number;
  paginate: React.Dispatch<React.SetStateAction<number>>;
}

const Pagination = ({ postsPerPage, totalPosts, paginate }: PaginationType) => {
  const pageNumbers: number[] = [];
  ...
};

타입 지정은 위와 같이 해주었다. 

props 코드 아래 pageNumbers 변수도 number 타입의 배열로 타입을 정해주고 마이그레이션 종료.

 

➕ 추가로 Styled Components의 "&" (Ampersand)에 대해 배웠다.

...
  return (
         ...
            <PageLi key={number} className={currentPage === number ? "page-item current" : "page-item"}>
              ...
            </PageLi>
          ...
    </div>
  );
};

export default Pagination;

...

const PageLi = styled.li`
  display: inline-block;
  font-size: 15px;
  padding: 5px;
  border-radius: 5px;
  width: 30px;

  /* PageLi가 current이라는 클래스를 갖고 있음 */
  &.current {
    font-weight: bold;
  }
`;

...

`&`는 상위 스타일 컴포넌트 아래 하위 클래스를 지정해 줄 때 쓰인다.

현재 페이지의 폰트 글씨를 bold체로 지정해 주고 싶어 고민하다 `&`를 알게 되었고, 이를 이용해 PageLi 스타일 컴포넌트의 하위 클래스인 current 클래스를 스타일링 해주는 방법으로 기능을 구현했다.

참고: https://tinyurl.com/2574nabc

반응형