menulogo

링크 공유 기능을 위한 Custom hook 작성하기

@corinthioniaAugust 17, 2023

모두의 시간은 쉽고 빠르게 일정 조율을 도와주는 서비스이다.

'방장'이 일정을 등록할 수 있는 '방'을 생성하면, 모임 참여자들에게 방의 링크를 공유를 해야지만 참여자들이 일정을 등록하고 결과를 확인할 수 있다.


현재 방의 링크를 공유하는 기능이 필요한 곳은 1. 헤더의 공유 아이콘 2. 링크 공유 바텀시트 3. 우선순위 페이지 하단 버튼 이렇게 세 가지 경우이다. 여태까지 각각의 컴포넌트에서 링크 공유 기능 로직을 작성하고 있어 중복된 부분이 상당히 많았다. 따라서 앞으로의 확장성을 위해, 또 코드를 간소화하기 위해 custom hooks를 작성하여 중복을 최소화하고자 했다.


1. 링크 공유 방식

모두의 시간에서는 크게 두 가지 방식의 링크 공유 방식을 사용한다.
첫 번째는 '링크 복사' 기능이고, 두 번째는 'Web share api'를 사용하는 기능이다.


  • 링크 복사 기능은 말 그대로 클립보드에 링크를 복사하여 붙여넣기 할 수 있는 기능이다.
  • Web share api는 아래 사진처럼 모바일 환경에서 손쉽게 다른 애플리케이션으로 링크를 공유할 수 있는 기능이다. (하지만 이 기능은 PC 크롬 등 일부 환경에서는 아직 사용하지 못하는 것 같다!)

위의 기능들은 별도의 써드파티 라이브러리를 사용하지 않아도 손쉽게 구현할 수 있다!


2. 구현하기

먼저 내가 작성한 useShareLink라는 이름의 커스텀 훅이다.

import { ROUTES } from '@/constants/ROUTES';
import { useParams } from 'react-router-dom';

const useShareLink = () => {
  const { roomUUID } = useParams();
  const inviteURL = `${window.location.origin}${ROUTES.INVITE}/${roomUUID}`;

  const handleUseShareAPI = () => {
    const shareData = {
      title: '모두의 시간',
      text: '쉽고 빠른 약속 정하기, 모두의 시간',
      url: inviteURL,
    };

    if (navigator.share) {
      navigator.share(shareData);
    }
  };

  const handleCopyToClipBoard = async () => {
    try {
      await navigator.clipboard.writeText(inviteURL);
      alert('클립보드에 복사되었습니다.');
    } catch {
      alert('링크 복사에 실패했습니다.\n다시 시도해 주세요.');
    }
  };

  return { inviteURL, handleUseShareAPI, handleCopyToClipBoard };
};

export default useShareLink;

1) 클립보드 복사 기능

클립보드 복사를 위한 함수는 handleCopyToClipBoard 이다. 추후에 버튼에 onClick으로 매핑하여 사용하면 클립보드에 링크를 복사할 수 있다.

const handleCopyToClipBoard = async () => {
  try {
    await navigator.clipboard.writeText(inviteURL);
    alert('클립보드에 복사되었습니다.');
  } catch {
    alert('링크 복사에 실패했습니다.\n다시 시도해 주세요.');
  }
};

위 코드의 핵심 부분은 navigator.clipboard.writeText(link)라 할 수 있겠다.
이때 주의해야 할 점은 writeText()가 Promise를 리턴하기 때문에 비동기로 작성해야 한다는 것이다.


먼저 가장 앞에 쓰인 navigator 객체는 브라우저와 관련된 정보를 관리한다. clipboard 외에도 매우 다양한 프로퍼티를 제공한다고 하니 자세한 사항은 mdn web docs를 확인해 보면 좋을 것 같다!

clipboard는 read-only 속성으로, Clipboard 오브젝트를 리턴한다. wirteText를 이용하면 클립보드에 파라미터 값을 복사할 수 있다고 한다. writeText는 2023년 8월 현재 모든 브라우저에서 호환됨을 확인했다!

2) Web share api

const handleUseShareAPI = () => {
  const shareData = {
    title: '모두의 시간',
    text: '쉽고 빠른 약속 정하기, 모두의 시간',
    url: inviteURL,
  };

  if (navigator.share) {
    navigator.share(shareData);
  }
};

먼저 shareData 객체를 따로 작성하여 가독성을 높여 주었다. 이 객체에는 url, text, title 필드가 적어도 하나는 명시되어야 한다.

  • url 공유될 URL을 의미한다.
  • text 공유될 본문을 의미한다.
  • title 공유될 제목을 의미한다.

참고로 text에 값을 입력하면 링크를 공유할 때 아래 사진처럼 자동으로 메시지로 입력이 된다.


이렇게 작성한 shareData 객체를 navigator.share(shareData) 형식으로 작성하면 모바일에서 위에 사진에서 봤던 공유 api를 활용할 수 있다!


Web share api를 사용할 때 주의할 점은, 2023년 8월 기준으로 아직 모든 기기에서 호환되지 않는다는 것이다.

나는 주로 크롬을 이용하는데, PC 크롬에서는 아직 저 기능을 사용할 수는 없고 사파리에서는 아래 사진처럼 사용할 수 있다.

3) 코드 전문 다시 보기

위에 올려두긴 했지만,,, 다시 한번 코드 전문 올리기

import { ROUTES } from '@/constants/ROUTES';
import { useParams } from 'react-router-dom';

const useShareLink = () => {
  const { roomUUID } = useParams();
  const inviteURL = `${window.location.origin}${ROUTES.INVITE}/${roomUUID}`;

  const handleUseShareAPI = () => {
    const shareData = {
      title: '모두의 시간',
      text: '쉽고 빠른 약속 정하기, 모두의 시간',
      url: inviteURL,
    };

    if (navigator.share) {
      navigator.share(shareData);
    }
  };

  const handleCopyToClipBoard = async () => {
    try {
      await navigator.clipboard.writeText(inviteURL);
      alert('클립보드에 복사되었습니다.');
    } catch {
      alert('링크 복사에 실패했습니다.\n다시 시도해 주세요.');
    }
  };

  return { inviteURL, handleUseShareAPI, handleCopyToClipBoard };
};

export default useShareLink;

모두의 시간 서비스에서는 방을 생성할 때마다 고유한 UUID가 부여되기 때문에 초대 링크도 방마다 변하게 된다. 따라서 inviteURL도 이 custom hook에서 관리하도록 했다.

리턴 값으로는 inviteURL을 비롯하여 위에서 작성한 함수들을 넣어 주었다.


먼저 공유 기능이 필요한 곳에 useShareLink 훅을 불러오고, 필요한 값들만 변수에 할당한다.

const { inviteURL, handleUseShareAPI, handleCopyToClipBoard } = useShareLink();

그후 UI 컴포넌트의 onClick 이벤트로 함수를 매핑해 주면 된다. 아래는 ShareButton 이라는 이름의 버튼을 클릭하면 web share api를 통해 공유할 수 있는 코드이다.

<ShareButton onClick={handleUseShareAPI}>지금 공유할게요</ShareButton>

handleCopyToClipBoard도 마찬가지로 onClick 이벤트에 매핑시켜 주면 된다! 다만 비동기 함수로 작성했기 때문에 콜백 형태로 전달해야 한다.

<Icon
  src={share}
  alt="share"
  onClick={() => handleCopyToClipBoard(inviteURL)}
/>

4. 후기

반복되는 로직을 Custom hooks로 추상화함으로써 코드양을 2.3배나 줄일 수 있었다! (+249 -574)

아직도 중복되는 부분이 많기 때문에 차차 추상화 작업을 진행해야겠다 🥳


Reference

← 이전 글CRA에서 Vite로 전환할 결심
다음 글 →모두의 시간 서비스 개발 회고