티스토리 뷰

결과 화면

개요

  • 매우 간단한 댓글 컴포넌트를 만들어보았습니다.
  • 백엔드 데이터로 작성일자를 받으면 위와 같은 ISO 형식으로 받게됩니다.
  • 처음 피그마로 작업할 때는 2023-07-06 12:23 의 형식으로 편집하여 출력하려고 하였으나 핀터레스트, 트위터, 인스타그램 등 유명 SNS의 댓글 UI를 보면 좀 더 직관적으로 날짜를 표시해주는 것을 알게 되었습니다.

  • dayjsmoment와 같은 날짜를 다루기 쉽게 도와주는 라이브러리도 있지만 최대한 직접 기능을 구현해보고 싶어서 라이브러리는 이용하지 않았습니다.

 

구현

const elapsedTime = (date: Date) => {
  const start = new Date(date);
  const end = new Date();

  const diff = (end.getTime() - start.getTime()) / 1000;
  • date를 매개변수로 받는 함수입니다. date는 사용하는 곳에서의 작성일자 혹은 수정일자가 들어가겠죠!
  • 대상이 되는 날짜를 start 변수에 담고 현재 날짜는 end 에 담아서 둘의 차이를 계산합니다. 1000으로 나누어주는 이유는 Date()값은 기본적으로 milliseconds로 나오기 때문입니다.
  • 자바스크립트의 경우는 end - start를 하면 알아서 number로 형변환한 후 계산을 해주지만
  • 타입스크립트는 start와 end의 타입을 Date로 알고 있기 때문에 뺄셈 연산을 하지 못한다고 오류를 뱉어냅니다.
  • 따라서 end.getTime() 혹은 Number(end) 혹은 +end 로 형변환을 명시적으로 해주어야 합니다!

 

  const times = [
    { name: "년", milliSeconds: 60 * 60 * 24 * 365 },
    { name: "개월", milliSeconds: 60 * 60 * 24 * 30 },
    { name: "일", milliSeconds: 60 * 60 * 24 },
    { name: "시간", milliSeconds: 60 * 60 },
    { name: "분", milliSeconds: 60 },
  ];

  for (const value of times) {
    const betweenTime = Math.floor(diff / value.milliSeconds);

    if (betweenTime > 0) {
      return `${betweenTime}${value.name} 전`;
    }
  }

  return "방금 전";
  • 대상 시간과 현재 시간의 차이를 구했다면 그 차이에 따라 적절한 문구를 출력해주어야 합니다.
  • 년 ~ 분 의 순서로 탐색을 하며 둘의 차이가 나는 경우에 해당 name을 출력해주게 됩니다.
    • 경과시간에 milliSeconds를 나눠서 해당 값보다 경과시간이 적을 시, 0보다 작은 소수점 값이 나오게 됩니다. ( 1분 / 1년 ) => 0.0000019...
  • 정수가 나올 때까지(0보다 큰 값) 다음 단위로 넘어가서 다시 비교를 반복합니다. 모든 탐색이 끝났음에도 return 되지 않았다는 것은 두 날짜의 차이가 1분보다 작다는 의미이므로 "방금 전"이라는 문자를 반환하도록 하였습니다.

 

Intl API 이용해보기

Intl API의 주요 내용을 정리하기 위해 본문과 상관없는 내용을 정리해두었습니다. 위 내용과 이어지는 부분이 궁금하시다면 아래쪽에 Intl.RelativeTimeFormat 부분부터 봐주세요!

 

Intl API란?

  • 웹 서비스를 여러가지 언어로 설계하고 구현하는 과정을 국제화(Internalization) 라고 합니다. 단순히 문구를 번역하는 것뿐만 아니라 동일한 데이터를 다른 형식으로 보여주어야 하기도 합니다.
  • 예를 들어서 날짜의 경우 한국에서는 연, 월, 일, 요일 순서를 따르지만 영어권에서는 요일, 월, 일, 년 순서를 따릅니다. 시간을 표시할 때도 한국에서는 오전, 오후를 제일 앞에 명시하는 반면 영어에서는 보통 제일 뒤에 명시한다는 차이가 있습니다.
  • 이러한 작업을 일일이 하려면 굉장한 노력이 필요하고 외부 라이브러리를 사용한다면 번들 사이즈에 영향이 갈 수밖에 없겠네요!
  • 이러한 문제점을 해결하기 위해 자바스크립트에서는 Intl API를 제공하고 있습니다. 이 API는 대부분의 모던 브라우저에서 지원되며 Node.js에서도 사용이 가능합니다.

 

Intl API 사용법

  • Intl API는 전역에서 접근이 가능한 Intl 객체를 통해 사용이 가능합니다.
  • 모든 생성자는 공통적으로 2가지 인자를 받는데 첫 번째 인자는 locale 이라는 언어와 지역 정보를 표준화 시킨 코드이며 두 번째 인자는 추가적인 option 을 명시할 수 있습니다.

예시 코드

  • 현재 날짜를 한국 이용자들을 위한 형식으로 변환하려고 한다면 DateTimeFormat() 생성자로 생성한 객체의 format() 함수를 호출하면 됩니다.
const koDtf = new Intl.DateTimeFormat("ko", { dateStyle: "long" });

kodtf.format(new Date());

// '2023년 7월 7일'

 

주요 생성자

Intl.DateTimeFormat

  • 시간이나 날짜를 쉽게 국제화할 수 있는 생성자입니다.
  • 포매팅 옵션으로 dateStyletimeStyle 이 자주 사용되며 full, long, medium, short 을 옵션으로 설정할 수 있습니다.

  • locale 속성에 따라 다른 형식으로 출력되는 것을 볼 수 있습니다!

 

Intl.NumberFormat

  • 통화(currency), 백분율, 무게, 길이, 속도, 온도 등 단위가 있는 숫자 데이터를 다룰 때 유용합니다.
  • style 옵션을 통해 다양한 스타일을 지원하는데 예를 들어 백분율 데이터를 다룰 때는 style: 'percent' 로 설정하고 format() 함수에 소수를 넘겨주는 형식으로 사용이 가능합니다.
new Intl.NumberFormat('ko', { style: 'percent' }).format(0.7)

// '70%'
  • 통화 데이터를 다룰 때는 style: 'currency' 로 설정을 하고 옵션으로 통화 코드를 넘겨줍니다.
new Intl.NumberFormat('ko', { style: 'currency', currency: 'USD' }).format(40.56)

// 'US$40.56'
  • 그 외에 다른 단위를 사용할 때는 style: 'unit' 으로 주고 옵션으로 단위 코드를 넘겨주는 방식으로 사용할 수 있습니다.
new Intl.NumberFormat('ko', { style: 'unit', unit: 'kilogram' }).format(50)

// '50kg'

new Intl.NumberFormat('ko', { style: 'unit', unit: 'pound' }).format(110)

// '110lb'

 

Intl.PluralRules

  • 영어에서는 한국어와 다르게 명사의 단복수 개념이 매우 중요합니다.
  • 예를 들어서 한국에서는 한 사람, 두 사람 이라고 표현한다면 영어에서는 One person, Two people 로 명확하게 구분을 합니다. 또한 숫자 0은 복수로 취급이 된다는 특징도 있죠!
  • 영어와 같이 단복수를 엄격하게 구분하는 언어로 서비스를 한다면 매우 유용하게 사용할 수 있을 것 같습니다.
  • PluralRules() 생성자로 만든 객체의 select() 함수에 숫자를 넘기면 'one' 또는 'other'를 반환하는데 이 결과값을 가지고 단수와 복수를 구분하게 됩니다.
let pr = new Intl.PluralRules("en")

pr.select(0)
'other'

pr.select(1)
'one'

pr.select(2)
'other'

pr.select(3)
'other'
  • 또한 type option 을 기본값 cardinal 에서 ordinal로 변경을 하면 1st, 2nd, 3rd, 4th 와 같은 서수의 어미 처리를 할 수 있습니다.
let pr = new Intl.PluralRules("en", { type: 'ordinal' })

pr.select(0)
'other'

pr.select(1)
'one'

pr.select(2)
'two'

pr.select(3)
'few'

pr.select(4)
'other'
  • 'one'이 반환되면 어미로 'st'를 사용하고(1st) 'two'가 반환되면 'nd'(2nd), 'few' 가 반환되면 'rd'(3rd), 그 외 'other'이 반환되면 'th'(4th)를 어미로 사용하게 됩니다.

 

Intl.RelativeTimeFormat

  • 현재 시간 기준으로 얼마나 시간이 지났는지, 혹은 얼마나 걸릴 것인지를 상대적으로 표현해줍니다.
  • numeric 옵션을 auto 혹은 always 로 설정할 수 있는데 auto인 경우는 숫자없이 표기할 수 있는 부분은 최대한 문자를 사용해서 표현해줍니다.
numeric: 'auto'

numeric: 'always'

 

구현

const formatter = new Intl.RelativeTimeFormat("ko", {
    numeric: "always",
  });

  const times: TimesProps[] = [
    { name: "year", milliSeconds: 60 * 60 * 24 * 365 },
    { name: "month", milliSeconds: 60 * 60 * 24 * 30 },
    { name: "day", milliSeconds: 60 * 60 * 24 },
    { name: "hour", milliSeconds: 60 * 60 },
    { name: "minute", milliSeconds: 60 },
  ];
  • Intl.RelativeTimeFormat() 을 생성해주고 times의 name값들을 API가 지원하는 형식으로 바꾸어줍니다. 해당 형식은 생성자를 타고 들어가서 내부를 들어가보면 다음과 같이 알려줍니다.

  • 그리고 format()을 호출하려고 하면 name의 타입이 맞지 않다며 오류를 내버립니다.
  • format()은 두 번째 인자로 unit을 받기 때문에 그에 맞는 타입을 적용시켜주어야 합니다.
interface TimesProps {
  name: Intl.RelativeTimeFormatUnit;
  milliSeconds: number;
}
  • 따라서 위와 같이 인터페이스를 선언하고 times에 배열로 적용시켜주면 됩니다!

 

전체 코드

interface TimesProps {
  name: Intl.RelativeTimeFormatUnit;
  milliSeconds: number;
}

const elapsedTime = (date: Date) => {
  const start = new Date(date);
  const end = new Date();

  const diff = (end.getTime() - start.getTime()) / 1000;

  const formatter = new Intl.RelativeTimeFormat("ko", {
    numeric: "always",
  });

  const times: TimesProps[] = [
    { name: "year", milliSeconds: 60 * 60 * 24 * 365 },
    { name: "month", milliSeconds: 60 * 60 * 24 * 30 },
    { name: "day", milliSeconds: 60 * 60 * 24 },
    { name: "hour", milliSeconds: 60 * 60 },
    { name: "minute", milliSeconds: 60 },
  ];

  for (const value of times) {
    const betweenTime = Math.floor(diff / value.milliSeconds);

    if (betweenTime > 0) {
      return formatter.format(-betweenTime, value.name);
    }
  }
  return "방금 전";
};

export default elapsedTime;

참고 자료

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함