오늘은 React(Vite)와 TypeScript를 활용하여 시리얼 통신을 구현하는 방법에 대해 알아보도록 하겠습니다. 최근 웹 개발에서 시리얼 포트를 다루는 경우가 많아지고 있는데요, Web Serial API를 통해 이를 손쉽게 처리할 수 있습니다. 그럼 시작해볼까요?😊

Serialport?

SerialPort는 시리얼 통신(Serial Communication)을 통해 데이터를 주고받기 위한 소프트웨어 인터페이스로, 주로 하드웨어 장치(센서, 마이크로컨트롤러, IoT 장치 등)와 컴퓨터 간의 데이터를 교환할 때 사용됩니다. 특히 임베디드 시스템, 하드웨어 제어 및 다양한 장치들과의 통신에 자주 활용됩니다. 이 과정에서 직렬 통신 포트(시리얼 포트)를 사용하여 바이트 단위의 데이터를 순차적으로 전송합니다.

시리얼 통신(Serial Communication)?

시리얼 통신(Serial Communication)이란 데이터를 한 번에 한 비트씩 순차적으로 전송하는 방식입니다. 이는 병렬 통신과 대비되며, 전송선이 적고 전송 거리가 길어도 안정적인 전송이 가능하다는 장점이 있습니다.
시리얼 통신은 RS-232, USB, SPI, I2C와 같은 여러 프로토콜을 통해 구현되며, 다양한 장치들과 통신할 수 있습니다.

Web Serial API

Web Serial API는 브라우저가 직렬 장치에 접근할 수 있도록 해주는 기능입니다. 이 API를 사용하면 웹 애플리케이션에서 직접 하드웨어 장치와 소통할 수 있게 됩니다.
(Web Serial API는 보안상의 이유로 HTTPS에서만 사용할 수 있습니다.)

환경 설정하기

개발관련 세팅이 되어있다는 가정하에 진행하도록 하겠습니다. (Node.js, npm, ...)
먼저, 프로젝트를 생성하고 필요한 패키지를 설치해야 합니다.

npm create vite@latest serial-communication --template react-ts

✔ Select a framework: › React
✔ Select a variant: › TypeScript

cd serial-communication
npm install
npm run dev


위와 같이 명령어를 작성하면 vite로 구성된 기본 화면이 출력됩니다. 이후 시리얼 통신을 위해서 최소한의 파일만 남겨두고 삭제하도록 하겠습니다.

 

UI 구성하기

시리얼 통신에는 포트 연결, 포트 열기, 포트에 데이터 쓰기, 포트에서 데이터 읽기 등과 같은 기능을 사용해야 합니다. 필요한 버튼 및 연결된 것을 알 수 있게 UI를 구성하겠습니다.

// App.tsx
import "./App.css";

function App() {
  return (
    <section>
      <h1>serial-communication</h1>
      <p>Selected Port: </p>
      <div className="wrapper">
        <button>Port select</button>
        <button>Send Data</button>
      </div>
    </section>
  );
}
export default App;

Serial Communication 구현하기

이제 본격적으로 시리얼 통신을 구현해보겠습니다! 아래 코드를 참고하세요:D

사용 가능한 포트 확인

시리얼 통신을 하기위해 사용 가능한 포트를 확인하여 연결하는 과정의 코드를 작성하겠습니다.
먼저 재사용성 있게 컴포넌트를 구성하기 위해 custom hook을 생성했습니다. 이때, Web Serial API를 사용하기 위해서는 기본적으로 비동기 통신이 되어야하는 것을 주의해서 작성해주시면 됩니다.

navigator.serial.requestPort(): 연결 가능한 포트 목록을 확인할 수 있도록 해주는 메서드입니다.

port.open({ option }): 직렬 포트에 맞는 option을 설정해서 시리얼 통신을 할 수 있도록 포트를 웹브라우저와 연결하는 메서드입니다.

// hooks/useSerialCommunication.tsx
import { useState } from "react";

function useSerialCommunication() {
  const [port, setPort] = useState();

  const initialize = async () => {
    try {
      // @ts-expect-error: navigator.serial is not yet supported in TypeScript
      const port = await navigator.serial.requestPort();
      setPort(port);
      await port.open({ baudRate: 9600 });
    } catch (err) {
      console.error("포트 연결 실패:", err);
    }
  };

  return { port, initialize };
}

export default useSerialCommunication;
// App.tsx
import "./App.css";
import useSerialCommunication from "./hooks/useSerialCommunication";

function App() {
  const { port, initialize } = useSerialCommunication();
  return (
    <section>
      <h1>serial-communication</h1>
      <p>Selected Port: {port ? "connect" : ""}</p>
      <div className="wrapper">
        <button onClick={initialize}>Port select</button>
        <button>Send Data</button>
      </div>
    </section>
  );
}

export default App;

 

포트에 데이터 쓰기

sendData 함수는 React 애플리케이션에서 Web Serial API를 사용하여 시리얼 포트로 데이터를 전송하는 로직을 처리합니다. 이 함수는 사용자가 입력한 문자열 데이터를 시리얼 포트에 인코딩하여 전송하고, 오류 발생 시 적절히 로그를 출력합니다.

 

  • 포트 선택 확인
    port 객체가 없는 경우 에러를 던져 사용자가 시리얼 포트 연결을 먼저 설정하도록 요구합니다.
  • Writable 스트림 가져오기
    port.writable.getWriter()를 호출해 데이터를 전송할 수 있는 Writable 스트림을 생성합니다. 스트림은 단일 작업을 수행하기 위해 잠금(lock) 상태가 되며, 작업이 완료된 후 releaseLock()으로 잠금을 해제해야 합니다.
  • 데이터 인코딩
    사용자가 전송하고자 하는 데이터(messageToSend)는 문자열이므로, 시리얼 포트가 이해할 수 있는 바이너리 형식으로 변환해야 합니다. 이를 위해 Web API의 TextEncoder를 사용하여 문자열을 Uint8Array로 인코딩합니다.
  • 데이터 전송
    writer.write(data)를 호출하여 인코딩된 데이터를 시리얼 포트로 전송합니다.
  • 스트림 해제
    releaseLock()을 호출하여 스트림 잠금을 해제합니다. 이렇게 해야 다른 쓰기 작업이나 리소스 처리가 가능합니다.
  • 에러 처리
    데이터 전송 중 문제가 발생하면 catch 블록에서 에러를 캡처하고, 오류 메시지를 출력합니다.
  • 디버깅 로그 출력
    console.log를 사용해 전송된 데이터(messageToSend)와 변환된 바이너리 데이터(data)를 확인할 수 있도록 로그를 출력합니다.

 

const sendData = async (messageToSend: string) => {
  if (!port) {
    throw new Error("Port is not selected");
  }

  try {
    // 시리얼 포트의 writable 스트림 가져오기
    // @ts-expect-error: navigator.serial은 TypeScript에서 아직 지원되지 않음
    const writer = port.writable.getWriter();

    // TextEncoder를 사용해 문자열 데이터를 Uint8Array 형식으로 인코딩
    const data = new TextEncoder().encode(messageToSend);

    // 데이터를 시리얼 포트로 쓰기
    await writer.write(data);

    // writer 객체의 락 해제 (다른 쓰기 작업 가능하도록)
    writer.releaseLock();

    console.log("Data sent:", messageToSend, data);
  } catch (error) {
    // 데이터 쓰기 중 오류 발생 시 로그 출력
    console.error("Error writing to serial port:", error);
  }
};

요청한 데이터 값

포트에서 데이터 읽기

initialize 함수는 Web Serial API를 사용해 시리얼 포트를 초기화하고, 포트로부터 데이터를 읽는 로직을 구현합니다. 데이터는 TextDecoder를 사용해 문자열로 변환되며, React의 상태를 활용해 UI에서 확인할 수 있도록 처리됩니다.

 

  • 포트 요청 및 초기화
    • navigator.serial.requestPort()를 통해 사용자가 연결하려는 시리얼 포트를 선택합니다.
    • 선택된 포트는 port.open({ baudRate: 9600 })를 호출해 특정 전송 속도로 초기화합니다.
  • Readable 스트림 가져오기
    • port.readable을 통해 포트의 Readable 스트림을 가져옵니다.
    • getReader()를 호출해 데이터를 읽기 위한 Reader 객체를 생성합니다.
  • 데이터 읽기
    • reader.read()를 통해 데이터를 읽습니다.
    • 반환값은 { value, done } 형식이며:
      • value: Uint8Array 형식의 읽은 데이터
      • done: 읽기가 종료되었는지 여부
    • value가 존재하면 TextDecoder를 사용해 Uint8Array 데이터를 문자열로 변환하고, 이전 데이터에 추가하여 상태로 저장합니다.
  • Reader 잠금 해제
    • Reader는 단일 작업을 수행하기 위해 잠금(lock) 상태가 되므로, 작업이 끝나면 reader.releaseLock()을 호출해 잠금을 해제합니다.
  • 에러 처리
    • 포트 요청 실패, 포트 해제, 권한 부족 등의 에러를 try...catch 블록에서 처리하여 디버깅 로그를 출력합니다.

 

const initialize = async () => {
  try {
    // 시리얼 포트 요청 및 초기화
    // @ts-expect-error: navigator.serial은 TypeScript에서 아직 지원되지 않음
    const port = await navigator.serial.requestPort();
    setPort(port); // 선택한 포트를 상태로 저장
    await port.open({ baudRate: 9600 }); // 포트 열기

    // 데이터 읽기 설정
    const reader = port.readable?.getReader(); // 포트의 Readable 스트림 가져오기
    if (reader) {
      while (true) {
        const { value, done } = await reader.read(); // 데이터 읽기
        if (done) break; // 읽기가 종료되면 반복 종료
        if (value) {
          // Uint8Array 데이터를 문자열로 변환하고 상태 업데이트
          setReceivedData((prev) => prev + new TextDecoder().decode(value));
        }
      }
      reader.releaseLock(); // Reader 잠금 해제
    }
  } catch (err) {
    console.error("포트 연결 실패:", err); // 에러 처리
  }
};

Reference

- Web Serial API 공식문서
https://stackoverflow.com/questions/68806607/using-web-serial-with-reactjs

 

 

React 19

React 공식 블로그에 24.02.15에 업데이트 진행사항이 올라왔습니다.

주요 내용은 크게 React Compiler, Actions, New Features in React Canary입니다.

React Compiler

공식블로그에 따르면 React를 위한 컴파일러가 개발중이며 오픈 소스 릴리즈를 준비하고 있다고 한다.

React의 상태를 관리할 때 의도와는 달리 많은 리랜더링이 발생하는 경우가 있는데 이것을 해결하기 위해서는 useMemo, useCallback등과 같은 메모이제이션 방식을 통해 리랜더링을 줄이는 방법을 사용했는데 이를 자동화으로 최적화할 수 있도록 되어있다고 한다.

Actions

아래 코드에서 보이는 것과 같이 form에 action이 추가 되었습니다. 

action은 동기, 비동기식으로 모두 동작하며, 데이터를 제출할 때 useFormStatus, useFormState를 사용하여 데이터의 응답 및 에러처리와 같은 작업을 처리하기 쉽도록 도와준다고 합니다.
또, 한가지 추가되는 훅으로는 useOptimistic가 있습니다. useOptimistic은 말그대로 낙관적인 상태를 가져올 때 사용할 수 있다고 되어있습니다. 실제로 완료되는데 시간이 걸리더라도 사용자에게 결과를 즉시 보여줄 수 있도록 도와준다고 합니다.

import { useFormStatus } from "react-dom";
import { submitForm } from "./actions.js";

function Submit() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Submitting..." : "Submit"}
    </button>
  );
}

function Form({ action }) {
  return (
    <form action={action}>
      <Submit />
    </form>
  );
}

export default function App() {
  return <Form action={submitForm} />;
}

New Features in React Canary

Directives

"use client"와 "use server"를 사용하여 Next.js와 같이 서버, 클라이언트 어느 부분에서 동작하는지에 대한 지시자를 설정할 수 있습니다.

Document Metadata

 <title>, <meta>, and metadata <link>와 같은 태그를 컴포넌트에 추가할 수 있습니다. 이전에는 React Helmet을 사용하던 작업을 지원합니다.

Asset Loading

Suspense를 <style>, <link>, <script> 태그의 요소들이 표시될 준비가 되어 있는지 판단할 수 있도록 하였으며, 리소스가 언제 로드되고 초기화될지에 대한 제어를 할 수 있도록 preload와 preinit와 같은 새로운 리소스 로딩 API를 추가하였습니다.

Actions

이전에 작성한 Actions 파트의 부분과 같습니다. <form>태그에 action을 추가하고, useFormStatus를 사용하여 상태를 관리하며, useFormState로 결과를 처리합니다. 또한, useOptimistic으로 UI를 낙관적으로 업데이트할 수 있습니다.

 

Conclusion

한동안 취업준비를 하면서 이전에 프론트엔드 개발 공부를 하느라 기술 업데이트 관련 내용을 찾아보는 것을 못했던 것 같았다. 그래서 react 공식 문서와 블로그를 보니 24.2.15 날짜로 새로운 글이 올라온 것을 보았고 유튜브에는 이미 내용들이 정리되어 많이 올라와 있었다. 추후 react 19로 버전업을 하는 경우에 어떤점을 학습해야하는지 어떤점이 좋아지는지에 대해 알고 있다면 프로젝트에 적용하는데 많으 도움이 될 것 같다.

위의 내용들을 봤을 때는 Next.js에서 사용하던 것들을 React에서도 사용할 수 있도록 많은 기능이 추가되는 것 같다. Next.js의 기능 뿐만 아니라 React Helmet, TanstackQuery등과 같은 일부 라이브러리의 기능을 사용할 수 있다면 라이브러리에 대한 의존성은 낮출 수 있을 거란 생각이 들었다. 하지만, 언제나 개발시장은 변화하고 있기 때문에 변화에 맞춰나가는 것이 가장 중요하다고 생각한다.

현재 거의 모든 웹 개발 시장이 React로 되어 있는데 이도 사실은 React에 많은 의존을 하고 있다는게 느껴지니 바닐라JS도 다시 학습해야겠다는 생각이 들었다.

Reference

- React 공식 블로그: https://react.dev/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024

React Native CLI로 시작하기

해당 글은 Mac을 기준으로 작성했습니다.

공식 문서 상에서 iOS를 개발하기 위해서는 Expo Go로 생성을 하거나 macOS가 필요하다고 작성되어 있습니다.

Mac OS로 Android 개발 시작하기

  1. 개발 툴 설치 (IDE)
    • InteliJ, Visual Studio Code 등과 같이 Code를 작성할 수 있는 IDE를 설치합니다.
    • 저는 VSCode를 기반으로 작성하겠습니다.
    • https://code.visualstudio.com/
  2. Hombrew 설치
    • Node, Watchman, JDK 등 패키지 설치를 하기위해 Homebrew를 설치합니다.
    • https://brew.sh/ko/
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  3. Hombrew를 사용하여 Node, Watchman 설치
    • Node 18이상 설치.
    • Watchman
      • 파일 시스템의 변경 사항을 감시하기 위한 Facebook의 도구입니다. 파일이나 디렉토리의 변경을 감지하고, 변경 사항에 따라 사용자가 지정한 행동을 수행하는 데 사용합니다.
      • 파일의 변경을 감지하면, 변경된 파일만을 대상으로 변경사항이 즉시 반영됩니다.
brew install node brew install watchman
  1. JDK 설치
brew tap homebrew/cask-versions
brew install --cask zulu17

# Get path to where cask was installed to double-click installer
brew info --cask zulu17
  1. Android Studio 설치
  2. Android Studio SDK 설치
    • 위에서 Android SDK를 선택하지 않으셨다면 Android Studio SDK Manager -> SDK Tools 탭에서 설치하실 수 있습니다.

  1. Android_HOME 환경 변수 구성
    • 애뮬레이터를 실행할 수 있도록 아래의 환경변수를 구성합니다.
    • 이때, $HOME은 사용자의 컴퓨터 환경에 맞게 변경해줍니다.
      • SDK Manager의 Android SDK Location에 나와있는 /Library이전의 경로로 변경하여 주시면됩니다.
    • nano ~/.zprofile 또는 ~/.zshrc 또는 ~/.bash_profile 또는 ~/.bashrc 파일 중 한개에서 설정을 변경합니다.
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
  1. npx로 새 애플리케이션 생성
    • 이전에 react-native-cli를 설치한 적이 있으시다면 예상치 못한 에러를 발생할 수 있기때문에 제거합니다.
npm uninstall -g react-native-cli @react-native-community/cli
  • react-native-cli를 설치한 적이 없으시다면 바로 아래 명령어를 통해 생성해주시면 됩니다.
# 최신버전
npx react-native@latest init AwesomeProject

# 특정버전
npx react-native@X.XX.X init AwesomeProject --version X.XX.X
  1. React Native 애플리케이션 실행
    1. 실행하게 되면 아래 사진과 같은 화면이 나오게 되며 첫 애플리케이션이 실행되게 됩니다.
npm start

# 해당 명령어를 실행하다보면 아래와 같은 선택지중 한가지를 선택하시면 됩니다. 
i - run on iOS 
a - run on Android 
d - open Dev Menu 
r - reload app

RN android studio 애물레이터 실행

React native란 무엇인가?

React Native는 Facebook에서 개발하고 관리하는 오픈 소스 모바일 애플리케이션 프레임워크입니다. 이 프레임워크는 웹 개발에 사용되는 JavaScript와 React를 기반으로 하고 있으며, iOS와 Android 모두에서 사용할 수 있는 앱을 만들 수 있습니다.

React native의 장점/특징

React Native는 한 번의 개발로 iOS와 Android 모두에 대응할 수 있는 앱을 만들 수 있습니다. 이는 개발 시간을 줄이고, 유지 보수를 간편하게 합니다.

React Native는 JavaScript를 사용하기 때문에 React를 사용하던 개발자들이라면 진입장벽이 낮습니다.

React native vs Flutter

React Native

React Native는 기존의 React 웹 개발 경험을 바탕으로 배우기 쉽고, 빠르게 개발할 수 있다는 장점이 있습니다. 또한, React Native는 이미 많은 개발자가 사용하고 있기 때문에, 다양한 리소스와 지원을 받을 수 있습니다.

Flutter

Flutter는 Google에서 개발한 오픈 소스 프레임워크로, Dart 언어를 사용하여 iOS 및 Android 애플리케이션을 개발할 수 있습니다. Flutter는 성능이 뛰어나고, 반응성이 좋으며, 다양한 플랫폼에 일관된 사용자 경험을 제공할 수 있다는 장점이 있습니다. 또한, Flutter는 아직 개발 초기 단계이지만, 빠르게 성장하고 있다는 것을 알 수 있습니다.

실제로 채용공고를 보더라도 23년 상반기에는 React Native를 사용하는 곳이 많았지만 하반기로 올 수록 Flutter를 사용하는 기업이 늘어난다는 것을 확인할 수 있었습니다.

차이점

React Native와 Flutter의 주요 차이점은 다음과 같습니다.

언어 JavaScript, React Dart
빌드 방식 JavaScript 코드를 네이티브 코드로 변환 Dart 코드를 네이티브 코드로 변환
성능 뛰어나지 않음 뛰어남
반응성 뛰어나지 않음 뛰어남
플랫폼 호환성 iOS, Android iOS, Android
개발 난이도 낮음 중간
커뮤니티 활발함 활발함

장단점

React Native와 Flutter의 장단점은 다음과 같습니다.

장점 배우기 쉽고, 빠르게 개발할 수 있음, 다양한 리소스와 지원을 받을 수 있음 성능이 뛰어나고, 반응성이 좋으며, 다양한 플랫폼에 일관된 사용자 경험을 제공할 수 있음
단점 성능이 뛰어나지 않고, 반응성이 뛰어나지 않음 개발 난이도가 높음, 커뮤니티가 아직은 크지 않음

React native 생성하기

React Native를 생성할 때는 Expo Go와 React Native CLI 두 가지 방법이 있습니다.

Expo Go

Expo는 React Native를 기반으로 한 오픈 소스 프레임워크 및 개발 환경입니다. Expo를 사용하면 React Native를 더 쉽게 시작하고 관리할 수 있으며, 앱 개발을 더 빠르게 진행할 수 있습니다.

Expo의 주요 장점은 다음과 같습니다.

  • 빠른 시작: Expo는 기본 설정이 다 구성되어 있으며 native 파일을 숨겨놓고 자동으로 관리를 해줍니다. 또한 개발을 쉽게 해주는 많은 편리하고 유용한 특성을 가지고 있습니다.
  • 간편한 개발: Expo는 React Native의 기본적인 기능을 제공하며, 추가적인 설정이나 작업이 필요하지 않습니다. 따라서 React Native의 기본을 배우는 중간 또는 초보 개발자에게 유용합니다.
  • 빠른 배포: Expo는 웹 브라우저를 통해 앱을 실행할 수 있으며, iOS 및 Android에 앱을 쉽게 배포할 수 있습니다.

Expo의 주요 단점은 다음과 같습니다.

  • 자유도가 낮음: Expo는 기본적인 기능만 제공하며, 더 많은 기능을 추가하기 위해서는 Expo SDK를 사용해야 합니다. 따라서 복잡한 기능이나 더 높은 사용자 지정을 필요로 하는 프로젝트에는 적합하지 않을 수 있습니다.
  • 앱 크기가 크다: Expo는 모든 Expo SDK 솔루션으로 빌드되어 앱의 크기가 크다는 단점이 있습니다.

React Native CLI

CLI는 React Native의 기본 빌드 도구입니다. CLI를 사용하면 React Native 앱을 직접 생성하고 관리할 수 있습니다.

CLI의 주요 장점은 다음과 같습니다.

  • 자유도가 높음: CLI는 Expo와 달리 기본적인 기능만 제공하지 않습니다. 따라서 개발자가 원하는 모든 기능을 추가할 수 있습니다.
  • 앱 크기가 작다: CLI는 Expo와 달리 앱의 크기가 작다는 장점이 있습니다.

CLI의 주요 단점은 다음과 같습니다.

  • 배우기 어렵다: CLI는 Expo와 달리 기본 설정이 되어 있지 않습니다. 따라서 React Native의 기본을 이해하고 있어야 합니다.
  • 개발 시간이 오래 걸린다: Expo와 달리 CLI는 기본적인 기능을 제공하지 않기 때문에, 개발자가 직접 추가해야 합니다. 따라서 개발 시간이 오래 걸릴 수 있습니다.

React Hook Form이란?

React에서 form의 유효성검사를 도와주는 라이브러리이다.

React Hook Form 장점

공식문서에 따르면 아래와 같은 장점이 있다.

  • 기존의 유효성 검사보다 적은 코드를 사용할 수 있다.
  • 불필요한 재렌더링을 제거하고 검증 계산을 최소화하여 마운팅 속도를 높여 성능을 향상 시킨다.
  • 패키지의 종속성이 없어서 작은 라이브러리로 가볍다.

React Hook Form과 yup을 같이 사용한 이유

React Hook Form만으로도 충분히 좋은 퍼포먼스를 낼 수 있지만, yup과 같이 사용한다면 인라인으로 작성되는 코드를 분리하여 사용할 수 있어  가독성이 조금 더 좋기 때문에 같이 사용하게 된다는 것을 알게되어 사용하게 되었다.

 

아래는 React Hook Form 공식문서에 나와있는 예제코드이다.

확실히 예전의 email의 상태 값을 받아와서 유효성 검사하는 함수를 구현한 후 작업하는 것보다 짧게 코드를 작성할 수 있다.

하지만, pattern에 여러가지 항목의 유효성을 검사하게 된다면 인라인으로 구현해야하는 만큼 코드가 지저분해질 수 있다. 

  <input
    type="email"
    {...register("email", {
      required: "Required",
      pattern: {
        value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
        message: "invalid email address"
      }
    })}
  />
  {errors.email && errors.email.message}

yup이란?

스키마 유효성 검사를 도와주는 라이브러리이다.

아래의 사용방법의 3번에 있는 yup 코드는 내가 직접구현하면서 작성했던 yup 스키마 생성이다. 직접 유효성 검사를 할 때보다 확실히 가독성이 좋다는 것을 알 수 있다.

사용방법

아래와 같은 순서로 코드를 작성해서 회원가입 form 유효성 검사를 간단하게 할 수 있었다. 
자세한 내용은 공식문서에 잘 나와 있기 때문에 확인해가며 작성을 한다면 어렵지 않게 유효성 검사를 할 수 있다.

1. 패키지 설치

npm install react-hook-form yup @hookform/resolvers

2. 라이브러리 import

import { useForm, SubmitHandler } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";

3. yup 스키마 생성

const schema = yup.object().shape({
  id: yup
    .string()
    // 최소 글자수 지정
    .min(2, "아이디는 최소 2글자 이상입니다.")
    .matches(
      /^[^ㄱ-ㅎ|ㅏ-ㅣ|가-힣]+$/,
      "아이디는 영문으로만 입력할 수 있습니다."
    )
    .required("아이디는 필수입력 사항입니다."),
  // 이메일 유효성 검사 코드
  email: yup
    .string()
    .email("이메일 형식을 맞춰주세요.")
    .required("이메일을 입력해주세요."),
  password: yup
    .string()
    .min(8, "비밀번호는 8글자 이상입니다..")
    // 최대 글자수 지정
    .max(16, "비밀번호는 16글자 이하입니다.")
    .matches(
      /^(?=.*[a-zA-Z])((?=.*\d)(?=.*\W))/,
      "비밀번호는 영문, 숫자, 특수문자가 포함되어야 합니다."
    )
    // 반드시 입력해야 할 경우 나타날 수 있도록 하는 코드
    .required("비밀번호는 8글자 이상 16글자 이하입니다."),
  confirmPassword: yup
    .string()
    // yup에 작성한 password와 일치여부 확인
    .oneOf([yup.ref("password"), null], "비밀번호가 일치하지 않습니다."),
});

4. React Hook Form을 사용한 Form 구성

const Signup = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>({ resolver: yupResolver(schema), mode: "onChange" });
  // mode: "onChange"를 입력해야 Input에 입력할 때마다 검증해준다.

  const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);

  return (
    <form id="signup" onSubmit={handleSubmit(onSubmit)}>
      <label htmlFor="userId">아이디</label>
      <input
        id="userId"
        type="text"
        placeholder="아이디를 입력해주세요"
        {...register("id")}
      />
      {errors.id && <span>{errors.id.message}</span>}
      <label htmlFor="email">이메일</label>
      <input
        type="email"
        id="email"
        placeholder="example@example.com"
        {...register("email")}
      />
      {errors.email && <span>{errors.email.message}</span>}
      <label htmlFor="password">비밀번호</label>
      <input
        type="password"
        id="password"
        placeholder="비밀번호를 입력해주세요"
        {...register("password")}
      />
      {errors.password && <span>{errors.password.message}</span>}
      <label htmlFor="confrimPassword">비밀번호 확인</label>
      <input
        type="password"
        id="confrimPassword"
        placeholder="비밀번호를 입력해주세요"
        {...register("confirmPassword")}
      />
      {errors.confirmPassword && <span>{errors.confirmPassword.message}</span>}
      <button type="submit">회원가입</button>
    </form>
  );
};

참고자료

React Hook Form 공식문서

 

Home

React hook for form validation without the hassle

react-hook-form.com

yup 공식문서

 

yup

Dead simple Object schema validation. Latest version: 0.32.11, last published: a year ago. Start using yup in your project by running `npm i yup`. There are 3974 other projects in the npm registry using yup.

www.npmjs.com

 

'Coding > React & React-native' 카테고리의 다른 글

[RN]React Native CLI로 시작하기 - macOS(Android Studio)  (0) 2023.12.28
[RN] React Native란?  (0) 2023.12.22
[Redux] Redux-toolkit  (0) 2022.11.21
[React] Vite를 사용해보자  (0) 2022.10.25
[Redux]React-redux 사용하기  (0) 2022.10.25

Redux

  • 상태를 업데이트 할 때는 항상 원본 state는 수정하지 않고 새로운 state를 반환해야한다.
  • 프로젝트가 더 복잡해질수록 리덕스를 올바르게 사용하기도 더 복잡해진다.
  • 리덕스에서 관리해야 할 상태가 더 많아질 때 생길 수 있는 현상
    • 액션 타입에서 문제가 생길 수 있다.
      • 식별자는 오타가 나서는 안되지만 오타가 날 경우 리듀서가 처리하지 못하게 된다.
        • 해결: 상수를 지정하여 오타를 막을 수 있다.
    • 작은 프로젝트에서는 문제가 되지않지만 큰 프로젝트에 많은 개발자가 들어가는 프로젝트에서는 문제가 될 수 있다.
      • 서로 다른 액션이 많을 때 식별자의 충돌이 발생할 수 있다.
    • 관리하는 데이터의 양이 많을 수록 상태 객체도 점점 커지며 많은 상태를 복사해야한다. 모든 상태를 유지하려면 계속 복사해야하고 리듀서의 길이가 길어지고 유지할 수 없을 만큼 리덕스의 파일이 커진다. 이것은 Context의 단점과도 같고, 리덕스에서도 나타날 수 있지만 해결책이 있다.
      • 해결: 리듀서의 파일을 나누어서 관리한다.
    • 상태의 변경 불가성에도 영향을 줄 수 있다.

Redux-toolkit

  • Redux를 더 편리하고 쉽게 사용할 수 있도록 만들어준다.
  • 위와 같은 리덕스의 단점을 해결하는 방안을 조금 더 쉽게 할 수 있고, 해결하지 못하는 단점도 보완할 수 있도록 해준다.

설치

  1. 아래의 코드를 작성하여 라이브러리를 설치한다.
  2. npm install @reduxjs/toolkit react-redux
  3. 기존에 설치한 Redux 라이브러리가 존재한다면 package.json에서 삭제해준다.
    • 이미 Redux toolkit에 포함되어 있기 때문이다.

사용

reducer 설정

  • slice는 항상 이름이 존재해야 한다.
// store -> index.js
import { createSlice } from '@reduxjs/toolkit';

const initialState = { counter: 0, showCounter: true };

const counterSlice = createSlice({
	// 식별자
	name: 'counter',
	initialState,
	reducers:{
		// 자동으로 최신 state를 받는다.
		increment(state) {
			// 기존의 상태를 변경하는 것 같지만 변경되지 않는다.
			// 내부적으로 immer라는 다른 패키지를 사용한다.
			// 이런 코드를 감지하고 자동으로 원래 있느 상태를 복제하고 기존 상태를 변경하지 않도록해준다.
			// 불변성을 신경쓰지 않아도 된다.
			state.counter++;
		},
		decrement(state) {
			state.counter--;
		},
		// action에 붙어있는 데이터가 필요할 때 사용하는 방법.
		increase(state, action) {
			state.counter = state.counter + action.amount;	
		},
		toggleCounter(state) {
			state.showCounter = !state.ShowCounter;
		}
	}
});

// counterSlice.reducer에 접근 가능하다.
// 프로젝트 규모가 커졌을 경우 문제가 생길 수 있다.
// ㄴ 일반적인 redux에서는 combineReducers를 사용한다.
//    ㄴ redux-toolkit에서는 configureStore를 사용한다.
// configureStore는 createStore처럼 store를 생성한다.
// 여러 개의 리듀서를 하나의 리듀서로 쉽게 합칠 수 있다.
// redux
const store = createStore(counterSlice.reducer);

// redux-toolkit
const store = configureStore({
	// reducer가 하나 일 경우
	reducer: counterSlice.reducer
	// reducer가 여러개 일 경우
	reducer: { counter: counterSlice.reducer }
});

Reducer 사용, state 가져오기

export const counterActions = counterSlice.actions;

// reudcer 사용하기
// payload가 없는 경우
dispatch(counterActions.increment());

// payload가 있는 경우
dispatch(counterActions.increase(10));

'Coding > React & React-native' 카테고리의 다른 글

[RN] React Native란?  (0) 2023.12.22
[React] React Hook Form과 yup을 사용한 유효성 검사  (0) 2022.11.21
[React] Vite를 사용해보자  (0) 2022.10.25
[Redux]React-redux 사용하기  (0) 2022.10.25
[React] Portals  (0) 2022.07.16

+ Recent posts