Language/Typescript

React 환경에서 서버로 올바른 데이터 전송하는 방법 (with Typescript)

Joonfluence 2021. 12. 8.

서론

대상독자

데이터를 서버로 전송하기 전에 전송하기 위한 데이터 검증 과정을 추가하려는 프론트엔드 개발자

학습목표

dto를 정의하고 dto에 맞게 데이터가 폼에서 전송 수 있도록 역직렬화 할 수 있다.
react-hook-form(이하 훅폼)을 통해, 올바른 형식이 아닐 경우 에러를 띄울 수 있다.
역직렬화된 데이터를 서버로 전송하기 위해, 다시 직렬화할 수 있다.

본론

지난 시간에는 [웹 프로그래밍/기타] - Typescript 환경에서 역직렬화하는 방법을 통해, 서버로부터 받아온 json 데이터를 클래스 형태로 변환하는 과정에 대해서 살펴보았습니다. 이번 시간에는 기본 폼에서 직접 서버로 전송되는 과정과 훅폼을 활용한 데이터 검증 과정을 비교하며, 올바른 데이터가 서버로 전송될 수 있도록 데이터 검증을 하는 방법에 대해서 살펴보겠습니다.

 

직렬화란 무엇일까?

직렬화란 네트워크 전송 시에 데이터를 전송하기 위해 값의 형태를 json과 같이 클라이언트와 서버 간 주고 받을 수 있는 형태로 데이터를 변환하는 것을 말합니다. 폼 전송 시, 직렬화를 하는 까닭은 클래스 데이터를 서버로 전송할 순 없기 때문입니다. 클래스 데이터는 메모리 영역에서도 Heap 영역에 저장됩니다. 따라서 참조하고 있는 메모리 주소가 PC 환경에 따라 달라질 수 밖에 없죠. 따라서 메모리 참조가 필요 없는 형태로 데이터를 변환한 후 전송해줘야 합니다. 그렇기 때문에 데이터 전송 과정에서는 직렬화가 필요 한 것입니다. (해당 설명에 대해서 부족할 수 있습니다. 더 자세히 알고 싶은 분은 이 글을 참조해주세요.) json과 같이 일종의 문자열의 형태로 변환하면, 여러 컴퓨터에서 호환될 수 있도록 처리해줄 수 있습니다.

 

우리는 데이터를 어떻게 서버로 전송할까?

먼저 기본 form의 데이터 전송 방식과 데이터 형태에 관하여 살펴봅시다. 일반적으로 form에서 데이터를 전송한다고 하면, 제출 버튼을 누르고 action 속성에 지정한 페이지로 이동하면서 데이터를 전송하는 방식입니다. 전송 메서드는 GET 아니면 POST이죠. 하지만 서버로 값을 전달한다고 할 때는 주로 POST 방식을 활용합니다. URL로 값을 전달하는 GET 방식보다는 body 값으로 전송할 수 있는 POST 방식이 조금 더 안전하기 때문이죠. 그래서 다음과 같이 코드를 작성하는 것이 보통입니다.

    <form action="/create_process" method="POST">
        국가 <input type="text" placeholder="ex) 대한민국" name="country"><br>
        도시 <input type="text" placeholder="ex) 부산" name="city"><br>
        방문일자 <input type="text" placeholder="ex) 2015-08-30" name="date"><br>
        후기 <textarea type="text" placeholder="ex) 정말 마음에 드네요." name="review"></textarea><br>
        <input type="submit" value="저장하기">
    </form>

 

HTML form에서 POST 메서드로 전송되는 데이터 형태는 application/x-www-form-urlencoded 입니다. 다음과 같은 형태이죠.

country=%EB%8C%80%ED%95%9C%EB%AF%BC%EA%B5%AD&city=%EB%B6%80%EC%82%B0&date=2015-08-30&review=%EB%84%88%EB%AC%B4+%EB%A7%88%EC%9D%8C%EC%97%90+%EB%93%9C%EB%84%A4%EC%9A%94%21

 

그리고 해당 url을 파싱하면, 우리에게 익숙한 json 형태로 변환되는 것을 알 수 있습니다. JSON 형태로 값을 전송한다면, 백엔드 개발자와 좀 더 원활한 소통을 할 수 있기 때문에 url 형태보다는 json 형태로 전송하게 됩니다. (Json 형태로 변환하는 과정은 아래에서 설명하겠습니다)

{
  country: '대한민국',
  city: '부산',
  date: '2015-08-30',
  review: '너무 마음에 드네요!',
  score : '5',
}

 

이러한 기본 form의 데이터 전송 format은 한가지 문제점이 있습니다. 바로 사용자가 올바른 형식의 값을 입력하지 않더라도 validate 해줄 수 있는 방법이 없다는 것입니다. 예를 들면, 사용자가 로그인 폼에서 로그인을 잘못 입력해도 해당 원인에 대해서 파악할 수 없다는 의미입니다. 그래서 이번에는 react-hook-form 라이브러리를 활용해, 해당 과정을 처리하는 방법과 역직렬화의 필요성에 대해 다시 설명을 드리겠습니다.

 

패키지 설치

 

yarn add react-hook-form or npm install react-hook-form

먼저, 다음 라이브러리를 설치해줍니다. react-hook-form 라이브러리는 폼 데이터 검증 라이브러리입니다. 이를 통해, form에 들어가는 state 관리에서부터, data validation, error 처리, 복잡한 배열 값 전송까지 처리해줄 수 있습니다.

 

react-hook-form의 기본 Form

react-hook-form의 기본적인 폼을 구성하고 미리 정의한 Dto(계층 간 데이터 교환을 하기 위해 사용하는 객체로, form 데이터 타입을 정의해주는 역할을 해줍니다)에 맞게 역직렬화 과정을 거친 후 데이터 검증 과정까지 진행해보도록 하겠습니다.

먼저 훅폼을 활용한 기본적인 form을 작성하고 이에 대한 기본적인 설명을 덧붙이는 방식으로 진행하겠습니다. 먼저 훅폼에서 불러와야 할 두가지 요소가 있습니다. useForm과 FormProvider 입니다. 전자는 리액트 훅폼에 폼 컴포넌트를 등록하기 위한 메소드들을 불러올 수 있는 함수이며, 후자는 Form 컴포넌트 안에서 전달되는 Conetxt에 대한 Provider 역할을 해줍니다.

가장 먼저 해줘야 할 작업은 훅폼에 작성된 폼을 등록하는 작업입니다. 이를 위해선, 두 가지 방법이 있는데요, 첫째는 훅폼에서 제공하는 FormProvider로 폼을 감싸주는 것. 둘째는, useForm에서 제공하는 register 함수로 각각의 Input을 등록하는 과정을 거치는 것입니다. (둘 중 전자의 경우, 사용하기 편리하기 때문에 전자의 방법을 사용하겠습니다.) 이를 위해선, FormProvider 컴포넌트의 Props로 useForm에서 불러온 form(곧, 새로 등록되는 폼)을 등록해줍니다. 또 태그에 존재하는 name 속성을 빼먹지 않는 것이 중요합니다. 해당 form의 하위 컴포넌트들은 하나의 Context에서 관리됩니다.

두번째로 해줘야 할 작업은 해당 Form이 전송될 때 호출될 함수를 등록하는 작업입니다. 이를 처리해주는 작업은 onSubmit Props에 등록해주게 됩니다. 지금 예시에서는 해당 함수에선 console.log로 데이터를 처리했지만, 실제로는 POST 메소드로 API 통신을 거쳐 서버에 값을 전송하게 될 것입니다.

import { useForm, FormProvider } from 'react-hook-form';

function Component<TValues>({
  form,
  onSubmit,
}: Props<TValues>) {
  const form = useForm();
  const onSubmit = async (data: any) => {
    console.log(data);
  }
  return (
    <FormProvider form={form} onSubmit={onSubmit}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        국가 <input type="text" placeholder="ex) 대한민국" name="country"><br>
        도시 <input type="text" placeholder="ex) 부산" name="city"><br>
        방문일자 <input type="text" placeholder="ex) 2015-08-30" name="date"><br>
        후기 <textarea type="text" placeholder="ex) 정말 마음에 드네요." name="review"></textarea><br>
        사진 업로드 <input type="file" name="Imgname" multiple><br>
        <input type="submit" value="저장하기">      
      </form>
    </FormProvider>
  );
}

 

Data Validation과 DTO 적용하기

자, 기본적인 골격은 나왔습니다. 이제 서버로 데이터를 전송할 준비가 되어 있습니다. 본격적인 데이터 전송에 앞서, 데이터 검증을 위한 준비를 하겠습니다. 검증을 위해선, DTO를 생성하고 이에 맞는 데이터 검증을 위해, 지난번 역직렬화 과정에서 사용했던 class-validator와 class-transformer 라이브러리를 사용합니다. 간단하게 설명드리면, 역직렬화 과정을 거친 데이터를 검증함으로써 데이터가 validate 조건(이는 데코레이터 함수로 정의된다는 것을 이야기한 바 있습니다)을 만족하는지 확인해주는 과정을 거치도록 처리해주는 것입니다.

또한 한가지 빠뜨려서는 안되는 점이 있습니다. React 환경에서 class-validator 라이브러리를 실행하려면 앱의 시작 파일의 맨 꼭대기에 import "reflect-metadata"를 추가해줘야합니다. 그래야, 프로젝트 전체에서 해당 라이브러리를 사용할 수 있게 될 것입니다. 참고

class CreatePostDto {
    @IsString()
    @IsNotEmpty()
    country: string;

    @IsString()
    @IsNotEmpty()
    city: string;

    @IsDate()
    @IsNotEmpty()
    date: Date;

    @IsString()
    @IsNotEmpty()
    review: string;

    @IsNumber()
    @IsNotEmpty()
    score : number;
}

 

각각에 관해 간단하게 이야기 드리겠습니다. 먼저 폼에서 전송된 항목 중, 필수항목인지 아닌지 체크하는 과정을 거칩니다. 그런 뒤, 해당 항목들이 해당 형식에 부합하는 값으로 전송되었는지 따지기 위한 조건을 추가해줍니다.

 

import { ClassConstructor, plainToClass } from "class-transformer";
import { validate } from "class-validator";

const classValidatorResolver =
  (schema: ClassConstructor<unknown>) => {
    async (values: any[], _: any) => {
        const user = plainToClass(schema, values);
        const errors = await validate(user);

        if (errors.length) {
        return {
            values: {},
            errors: errors,
        };
        }

    return { values: user, errors: {} };
    }
};

export default classValidatorResolver;

 

현재 우리는 class-trasnformer와 class-validator로 직접 역직렬화 및 데이터 검증을 진행하고 있기 때문에, 이를 Form에 등록해주기 위해선 Validtaion에 대한 커스텀 훅을 만들어 form에 등록해야 합니다. 이는 Validation Resolver를 통해, 커스텀 훅으로 제공한 형태가 될 것입니다. 자세한 내용은 해당 내용을 참고하시길 바랍니다.

 

function Component<TValues>({
  form,
  onSubmit,
}: Props<TValues>) {
  const resolver = classValidatorResolver(CreatePostDto);
  const form = useForm({
    resolver,
  });
  const onSubmit = async (data: CreatePostDto) => {
      await fetch("http://www.server.com:5000", {
          method: 'POST', 
        headers: {
         'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
     })
   }

  return (
        ... 생략
    );
}

 

이제 마지막 단계입니다. 실제 로그를 찍어보면 onSubmit의 data 항목에는 해당 객체가 성공적으로 나타남을 아실 것입니다. 이제 이 값을 서버로 전송해주기만 하면 됩니다(서버 쪽 처리는 되어 있다고 가정해봅시다). 이를 위해, 데이터 검증을 위해 역직렬화 처리해두었던 값을 다시 직렬화해주는 과정을 거칩니다. 이로써 폼에서 데이터 검증하는 모든 과정이 끝이 났습니다.

 

에러 메세지 화면에 출력하기

이건 보너스 단계입니다. Form에서 앞서 작성한 custom resolver를 등록한 이후, 전송된 데이터는 json 형태에서 Form 형태로 변환되는 과정을 거칠 것입니다. 또한 이는 validation 과정을 거쳐, 올바른 값을 사용자로부터 입력 받은 경우에는 값이 성공적으로 전송되고 그렇지 않을 경우에는 에러를 나타냅니다. 그렇다면, 에러가 떴을 때 사용자의 화면에 에러를 띄우려면 어떻게 해야 할까요?

 

훅폼에서는 단순히 form 데이터만 관리하는 것이 아니라, 각각에 등록된 Input 요소에 대한 state도 관리합니다. 이는 값 전송이 성공한 경우 / 실패한 경우로 나뉘게 될 것입니다. 이 때, 실패한 경우 formState의 errors 항목에서는 에러가 검출 될 것입니다. 이는 useForm에서 제공하는 get 함수를 통해 컴포넌트 레벨에서 불러올 수 있게 됩니다.

 

import { get, useForm } from 'react-hook-form';

function Component<TValues>({
  form,
  onSubmit,
}: Props<TValues>) {

  ...이하생략

  const { register } = form;

  const countryError = get(form.formState.errors, 'country');
  const cityError = get(form.formState.errors, 'city');
  const dateError = get(form.formState.errors, 'date');
  const reviewError = get(form.formState.errors, 'review');

  return (
    <FormProvider form={form} onSubmit={onSubmit}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        국가 <input type="text" placeholder="ex) 대한민국" name="country"><br>
        {countryError && <p>에러 발생</p>}
        도시 <input type="text" placeholder="ex) 부산" name="city"><br>
        {cityError && <p>에러 발생</p>}
        방문일자 <input type="text" placeholder="ex) 2015-08-30" name="date"><br>
        {dateError && <p>에러 발생</p>}        
        후기 <textarea type="text" placeholder="ex) 정말 마음에 드네요." name="review"></textarea><br>
        {reviewError && <p>에러 발생</p>}        
        <input type="submit" value="저장하기">      
      </form>
    </FormProvider>
  );
}

 

마지막으로 위의 방식대로 해당 에러메세지를 출력하기 위한 처리를 해주면, 사용자가 원하는 조건을 만족시키지 못한 값을 전송할 때, 에러메시지를 화면에 출력 해줄 것입니다. 이상으로 긴 설명을 들어주셔서 감사하며, 폼 요소에서의 데이터 검증은 이것으로 마치도록 하겠습니다.

반응형

댓글