# 탭만 보고 어떤 페이지인지 알 수 있게 하기

백오피스 서비스를 개발하다보면, 장시간 어드민 페이지에서 잔류할 경우가 많은데 이때 여러 다중 탭들을 많이 생성해서 띄어놓게 된다. 그 중에 가장 불편했던 점이 여러 탭들이 있으면 탭 별로 구분이 잘 안가서 탭을 하나씩 클릭하게 되는 상황이 발생한다.

## 같은 탭이 5개 이상 ^,^

데스크탑에 많이 앉아있는 사람이라면 탭을 여러 개 띄어놓는 구조는 당연하다. 근데 기존에 구현되어있는 백오피스는 특히 새 창으로 띄워 상태를 관리하는 기능들이 참 많았고, 메뉴에 들어가 게시글 작성이나, 조회를 할 때마다 새 탭이 열리는 구조였다. 그러다보니, 엄청나게 많은 탭들이 개발을 마친 후나 테스트를 마친 후에는 정~말 많아진다 ^,^

![Image](https://upload.cafenono.com/image/slashpagePost/20260316/171818_ko2805hFFLX8kzDXhg?q=80&s=1280x180&t=outside&f=webp)

탭의 개수가 작으면 클릭할 탭이 몇개없어 문제가 되진 않으나, 탭이 정말 많아지면 이게 어떤 탭인지 공황장애 상태가 온다. 이 문제를 해결하는 방법은 현재 페이지의 상태를 외부로 노출을 시키는 것인데, 클릭을 하지 않고 확인할 수 있는 방법은 탭의 문서(HTML Document) 별 타이틀을 변경하는 것이다.

## 문서 (HTML Document) 타이틀 변경하기

페이지 단에서 접속할 때마다 단순하게 해당 페이지가 어떤 페이지를 의미하는지 변경만 해주면되는 간단한 작업이다.

나 같은 경우, 여러 페이지에서 다중적으로 사용하기 때문에 `useSetDocumentTitle` 컴포저블을 만들어 사용해주었다.

관련 옵션 및 매개변수들에 대한 내용들은 `JSDOC` 에 담아놓았다. 

사실 페이지 별로 일일히 해당 컴포저블의 코드를 달기가 무척이나 힘들다. 구조를 어떻게 설계하느냐에 따라 다르겠지만, `Vue` 에서는 `createRouter` 에서 각 `routes` 에 `name` 값을 지정할 수 있기 때문에 사이드 바에 해당 컴포저블을 삽입해두고 (공통적으로 모두 들어가기 떄문에) 라우트가 변경될 때마다 현재 `route` 의 `name` 값을 가져와 문서의 타이틀을 변경해주는 형식으로 해결했다.

```javascript
import {
  onBeforeUnmount, onMounted, watch, type Ref, unref, ref,
} from 'vue';

type RefGuard<T> = T | Ref<T>;

interface UseSetPageTitleOptions {
  restoreOnUnmount?: boolean;
  enabled?: RefGuard<boolean>;
}

/**
* @description 페이지 타이틀 제어 유틸을 제공합니다.
* @param title 설정할 타이틀(문자열 또는 Ref)
* @param options 추가 옵션
* @param options.restoreOnUnmount 언마운트 시 이전 제목 복구 여부(기본값: true)
* @param options.enabled 사용 여부 (기본 값: true);
*/
export default function useSetDocumentTitle(
  title: RefGuard<string> = import.meta.env.VITE_TITLE,
  options: UseSetPageTitleOptions = {},
) {
  const { restoreOnUnmount = true, enabled = true } = options;
  const prevTitle = ref('');

  /**
   * @description enabled 옵션의 현재 활성화 상태를 반환합니다.
   * @returns 활성화 여부
   */
  const isEnabled = () => Boolean(unref(enabled));

  /**
   * @description 전달된 값으로 페이지 타이틀을 즉시 설정합니다.
   * @param nextTitle 설정할 타이틀
   */
  const setTitle = (nextTitle: string) => {
    document.title = nextTitle || '';
  };

  /**
   * @description 초기 입력(title)의 현재 값을 기준으로 페이지 타이틀을 적용합니다.
   */
  const applyTitle = () => {
    setTitle(unref(title) || '');
  };

  /**
   * @description 페이지 진입 이전 타이틀로 복구합니다.
   */
  const restoreTitle = () => {
    document.title = prevTitle.value || '';
  };

  /**
   * @description 타이틀을 빈 문자열로 초기화합니다.
   */
  const clearTitle = () => {
    document.title = '';
  };

  onMounted(() => {
    prevTitle.value = document.title;
    if (isEnabled()) {
      applyTitle();
    }
  });

  watch(() => unref(title), (nextTitle) => {
    if (!isEnabled()) return;
    setTitle(nextTitle || '');
  });

  watch(() => isEnabled(), (nextEnabled) => {
    if (nextEnabled) {
      applyTitle();
      return;
    }

    if (restoreOnUnmount) {
      restoreTitle();
    }
  });

  onBeforeUnmount(() => {
    if (restoreOnUnmount) {
      restoreTitle();
    }
  });

  return {
    applyTitle,
    setTitle,
    restoreTitle,
    clearTitle,
    prevTitle,
  };
}
```

[Video](https://vz-127031db-d43.b-cdn.net/2804375e-8146-4357-83a0-165dd8408577/playlist.m3u8)

For the site tree, see the [root Markdown](https://slashpage.com/timmy.md).
