utils 폴더에 들어갈 꿀 함수들

윤뿔소·2023년 10월 15일
1

이모저모

목록 보기
7/12
post-thumbnail

오늘은 utils 폴더에 자주 들어가는 함수들을 공유하는 시간을 갖겠다.

보통 utils 폴더에 넣는 코드들은 전역적으로 쓰이는 함수들을 넣는다. 주로 코드를 포맷팅하고 중복 코드를 줄이며, 여러 부분에서 재사용할 수 있는 함수들 말이다.

예를 들어 콤마를 붙이는 함수라든지, CDN을 붙이는 함수 등등이 있다.

Next.JS 13 App Router - TypeSctipt 기준이다.

포맷팅 함수

가장 기본적인 유틸 함수이다. 보통 숫자, 문자열, 날짜 등을 원하는 모양 대로 만들기 위한 유틸 함수가 들어간다.

1. 숫자 콤마 넣기

숫자 콤마 넣기는 가장 기본적이고 자주 쓰이는 유틸 함수라고 들 수 있다.

보통 2가지 방법이 있다.

toLocaleString()

JS 기본 내장 함수 toLocaleString()를 사용하는 것이다. 가장 간편하고 빠른 방법이다.

export default function formatNumberWithCommas(number: number | undefined) {
  if (!number) {
    return 0;
  }
  return number.toLocaleString();
}

포맷팅 함수 formatNumberWithCommas를 작성해보았다.
기본값과 undefined를 넣어 undefined 에러가 나지 않게 조건을 주고, return.toLocaleString()을 적용해 넣어두었다.

안정화를 더 위한다면 추가로 파라미터인 number에 타입 검사를 해도 좋다.

.toLocaleString() 특성 상 현지화가 잘돼있기 때문에 현지화를 원하거나, 시간이 부족하다면 .toLocaleString()를 써보자

정규식

이번엔 toLocaleString()보다는 복잡한 방법인 정규식으로 대체하는 방법이 있다.

export default function formatNumberWithCommas(number: number | undefined) {
  if (!number || !/^[0-9,]/.test(String(number))) {
    return 0;
  }
  return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}

위 함수는 추가로 test를 통해 number가 아닌 다른 타입의 데이터가 들어오면 0으로 반환되게 해놨다.

toLocaleString()보다는 복잡한 방법이지만 확실하게 고정해두어 현지화 별로 변함을 원하지 않거나 정규식으로 확실하게 고정된 리턴값을 원한다면 정규식 방법을 써보자.

2. 날짜 포맷팅 함수

날짜 포맷팅도 많이들 쓴다. 리스트에서 일시 및 일자라든지 쓸 곳이 많기 때문에 일단 만들어 놓으면 엄청 편한 함수다.

export function formatDateWithTime(inputDateString: string) {
  const inputDate = new Date(inputDateString);

  const year = inputDate.getFullYear();
  const month = String(inputDate.getMonth() + 1).padStart(2, '0');
  const day = String(inputDate.getDate()).padStart(2, '0');
  const hours = String(inputDate.getHours()).padStart(2, '0');
  const minutes = String(inputDate.getMinutes()).padStart(2, '0');

  const formattedDate = `${year}.${month}.${day} ${hours}:${minutes}`;

  return formattedDate;
}
export function formatDate(inputDateString: string) {
  const inputDate = new Date(inputDateString);

  const year = inputDate.getFullYear();
  const month = String(inputDate.getMonth() + 1).padStart(2, '0');
  const day = String(inputDate.getDate()).padStart(2, '0');

  const formattedDate = `${year}.${month}.${day}`;

  return formattedDate;
}

나는 날짜를 쓰는 곳이 일자, 일시 2개기 때문에 날짜와 시간이 같이 있는 formatDateWithTime, 날짜만 있는 formatDate가 있다.

각각 들어온 inputDateString: string 파라미터를 new Date(inputDateString)로 새 Date 데이터를 만든다.
그다음 각각 원하는 년, 월, 일, 시, 분 이렇게 나눠서 정규식을 사용해 연결하고 그 연결한 데이터를 리턴하는 방식이다.

padStart 함수는 JS 내장 메소드 함수로, 1번째 파라미터보다 길이가 작은 문자열이 있다면 1번째 파라미터만큼 왼쪽부터 2번째 파라미터로 채워주는 함수다. MDN
그래서 1개로 들어오면 0으로 채워주기 위해 사용했다.

YY-MM-DDYY.MM.DD HH:MM:SS 이렇게 하고싶다면 각 분야를 선언 후 정규식으로 이어주고 리턴하면 된다!

3. 전화번호 포맷팅 함수

export default function formatPhoneNumber(
  phoneNumber: string | undefined | null,
): string {
  if (phoneNumber) {
    // 모든 숫자 제외하고 제거
    const cleaned = phoneNumber.replace(/[^0-9]/g, '');

    // 형식에 맞게 '-' 삽입
    let formatted = '';

    if (cleaned.length === 10) {
      formatted = cleaned.replace(/(\d{3})(\d{3,4})(\d{4})/, '$1-$2-$3');
    } else if (cleaned.length === 11) {
      formatted = cleaned.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
    } else if (cleaned.length === 9) {
      formatted = cleaned.replace(/(\d{2})(\d{3})(\d{4})/, '$1-$2-$3');
    } else {
      // 기타 길이의 번호는 그냥 반환
      formatted = cleaned;
    }

    return formatted;
  }

  return '-';
}

이 함수는 입력된 전화번호에서 숫자 이외의 모든 문자를 제거한 후, 원하는 형식에 맞게 하이픈(-)을 삽입하여 반환한다.
전화번호의 길이가 다양하니, 길이가 9, 10, 11자리를 처리하고, 나머지는 원래 번호를 반환한다.
이를 통해 일관성과 재사용을 늘릴 수 있다.

유효성 검사 함수

유효성 검사도 본인 프로젝트가 검사할 곳이 많다면 유틸 함수로 만들어 재사용을 많이 해주면 더욱 고도화된 프로젝트를 만들 수 있다.

function isEmailValid(email: string): boolean {
  const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
  return emailRegex.test(email);
}

간단하게 이메일 유효성 검사 함수를 만들었다. 정규식을 만들어 .test()하고, boolean을 반환하는 함수다.

이메일 말고도 비밀번호, 전화번호 등등 2개 이상 쓰인다면 조건만 달리 해줘 유틸 함수로 만들어 보자.

네트워크 요청 함수

API를 통해 통신하려면 fetch API, axios등을 쓴다. 근데 여러번 쓰인다면, 중복이 많다면 네트워크 요청 함수도 유틸 함수로 써서 재사용을 줄 수 있다.

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';

interface RequestOptions<T> {
  method: HttpMethod;
  headers?: { [key: string]: string };
  body?: T;
}

async function sendRequest<T>(url: string, options: RequestOptions<T>): Promise<T> {
  try {
    const response = await fetch(url, {
      method: options.method || 'GET',
      headers: options.headers,
      body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
    });

    if (!response.ok) {
      throw new Error(`HTTP Error! Status: ${response.status}`);
    }

    // 원하는 데이터 형식으로 변환 (예: JSON)
    const data: T = await response.json();

    return data;
  } catch (error) {
    throw error;
  }
}

// GET 요청 예시
const getResponse = await sendRequest<{ exampleData: string }>('https://api.example.com/data', {
  method: 'GET',
});

// POST 요청 예시
const postData = { key: 'value' };
const postResponse = await sendRequest<{ responseMessage: string }>('https://api.example.com/post', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: postData,
});

이런 식으로 써서 할 수 있다. 보통 GET은 옵션이 별로 없으니 메소드가 undefined일 시 GET으로 해두긴 한다.

Next.JS App Router라 axios를 쓰지 않고 기본적으로 fetch를 추천하는 편이라 fetch API로 작성해보았다.

이런 식으로 하면 중복 코드를 줄일 수 있어 간편하게 할 수 있다.
여기에 401 처리, 리액트 쿼리, Interceptor 개념 등이 들어간다면 달라질 수 있다. 자기 프로젝트에 맞게 바꿔보자!

토큰 핸들 함수

웹에서 가장 많이 쓰는 인증 수단 중 하나인 JWT를 많이 쓰면서 유틸 함수에서도 토큰 핸들을 잡을 수 있다.

import { accessTokenStore } from '#/store/auth/access-token-store';

export const saveRefreshTokenToLocalStorage = (refreshToken: string) => {
  if (typeof window !== 'undefined') {
    localStorage.setItem('refreshToken', refreshToken);
  }
};
export const getRefreshTokenFromLocalStorage = () => {
  if (typeof window !== 'undefined') {
    return localStorage.getItem('refreshToken') || '';
  }
};

export const saveAccessTokenToZustand = (accessToken: string) => {
  accessTokenStore.getState().setAccessToken(accessToken);
};
export const getAccessTokenFromZustand = () => {
  return accessTokenStore.getState().accessToken;
};

리프레쉬는 Local Storage에, 엑세스는 외부에 노출되지 않게 로컬 전역 상태 라이브러리인 Zustand에 넣는 편이라 유틸 함수를 이렇게 작성해보았다.

Next.JS-TS를 쓰는데 그냥 localStorage를 사용한다면 타입에러가 나는 모습을 볼 수 있다.
그래서 조건에 typeof window !== 'undefined'를 작성하여 넣고, 타입에러가 나지 않게 했다. 근데 이렇게 쓰면 모바일에서 에러 일으킨다고 한다?? 일단 여기 주제가 아니니 패스

또한 주스탄드를 사용하여 유틸 함수를 만들었다. 이렇게 하면 간편하게 함수 1개로 조작하고, 불러올 수 있다.


그 외 디바운스, 쓰로틀 함수, Provider 제공 함수, 암호화-복호화 및 해싱 함수, 배열 조작, 난수 생성 함수 등을 만들어서 utils 폴더에 넣어둘 수 있다.

또한 유틸 함수들을 모아놓은 Lodash, Underscore, Ramda, Remeda 등을 쓰는 것도 또다른 방법이다.

보통 1개만의 프로젝트에 쓰이는 함수가 아니기 때문에 새 프로젝트를 시작하면 나는 먼저 utils 폴더를 복사해 바로 붙여두는 편이다. 독자도 자기만의 utils 폴더를 만들어 자신만의 프로젝트 수행 능력을 올려보면 좋겠다.

profile
코뿔소처럼 저돌적으로

6개의 댓글

comment-user-thumbnail
2023년 10월 19일

꿀팁 많이 알아갑니당 ㅎㅎ

답글 달기
comment-user-thumbnail
2023년 10월 20일

앗 요즘 저도 자주 쓰는 함수들 util 분리 해놓는 작업 함께 진행하고 있어서 더 와닿았어요! 잘 보고 갑니당

답글 달기
comment-user-thumbnail
2023년 10월 22일

태연님꺼 보고 공부 많이 해야할 것 같습니다.. 후..

답글 달기
comment-user-thumbnail
2023년 10월 22일

저도 분리를 잘 해야하는데 연습을 많이 해둬야겠어요 ㅎㅎ ㅠㅠ

답글 달기
comment-user-thumbnail
2023년 10월 22일

오오 개인 프로젝트할 때 참고하면 좋을 것 같아요..!!

답글 달기
comment-user-thumbnail
2023년 10월 22일

지인들 프로젝트 보면 실제로 많이 넣는 함수들 같아요! 꿀팁! 감사합니다~!

답글 달기