[Vue.js] 페이지 제목 바꾸기

들어가기 전에

본 글의 vue 버전은 2.0 입니다. vue 컴포넌트를 직접 만들 수 있고 vue-routervuex가 무엇인지 알고 기본적인 활용을 할 수 있는 분들께 적합한 내용입니다.


구상해보기

페이지 제목을 바꾸는 코드는 document.title = "제목"; 이렇게 쓰면 되기 때문에 아주 간단합니다. 하지만 우리가 원하는 것은 좀 더 일관성있고 효율적으로 페이지 제목을 관리하는 것입니다. 우선 페이지 제목이 바뀌는 시점을 한번 명백히 해봅시다. 아마 다음 세 가지 정도로 추려낼 수 있을 것입니다.

  1. beforeRoute, afterEach 등의 라우터 가드와 관련된 훅
  2. mounted, beforeMount 등 컴포넌트 라이프사이클과 관련된 훅
  3. 서버로부터 페이지 데이터를 받아올 때

페이지 제목이 컴포넌트에 따라 정적으로 고정되어 있다면 1번과 2번 중 아무거나 선택해도 문제가 없습니다. 다만 1번과 2번은 데이터가 어디에 저장되냐가 유일한 차이점입니다. 1번은 라우터를 구성할 때 meta 등에 제목 정보를 저장해둔 후, 훅에서 제목을 처리하는 방법입니다. 2번은 mounted 될 때 그냥 제목을 수정하는 방법입니다. 1번과 2번 모두 document.title = "제목"; 등으로 페이지 제목을 직접 변경할 수 있습니다.

하지만 1번과 2번의 방법은 한 가지 맹점이 있습니다. 바로 3번과 같은 경우입니다. 위 방법으로는 동일한 컴포넌트라도 페이지의 실제 내용에 따라 제목이 바뀌는 경우 능동적으로 대처하기가 어렵게 됩니다. 예를 들어 게시글을 나타내는 Post.vue 싱글 컴포넌트를 상상해봅시다. 이 컴포넌트는 mounted 훅 뿐만 아니라 사용자의 동작에 의해서도 서버로부터 데이터를 불러오고, 해당 게시물의 이름을 페이지 제목으로 지정하고자 하겠지요. 그렇다면 이 경우를 어떻게 처리를 해줘야 할까요? 아예 라우터 훅이나 라이프사이클 훅이 아닌 상태를 관리하는 Vuex를 활용해보는 것도 한가지 방법이 될 것 같습니다.

하지만 생각만 해보고 실제로는 간단한 watch 를 갖는 Mixin 을 만들어 전역으로 등록해서 사용하였습니다. ㅋㅋㅋ


최종적으로 활용했던 코드

import Vue from 'vue';

const suffix = ' - 회사 이름'; 
const pageTitle = {
  watch: { 
    vuePageTitle(to) { 
      document.title = `${to}${suffix}`;
    },
  },
};

Vue.mixin(pageTitle);

이렇게 하고 나서 페이지를 나타내는 컴포넌트에서 datavuePageTitle 을 추가하고 필요할 때마다 이를 수정하면 이 Mixin 이 페이지를 갱신합니다.


여러가지 시도들

처음 했던 구상은 실제 코드를 짜고 난 이후에 더 좋은 방법이 없을까 하고 혼자서 해본 구상이었기 때문에 해당 구상에 대한 코드는 존재하지 않습니다… 하하 위 코드도 깔끔하지는 않지만 간단하다는 장점이 있기에 그냥 이렇게 사용했습니다. 아래 코드는 실제로 해봤던 여러가지 코드입니다. (아래 코드에서 ##숫자##를 누르면 해당 코드에 대한 설명으로 넘어갑니다.)

import Vue from 'vue';

const suffix = ' - 회사 이름'; // ##a_1##
const pageTitle = {
  watch: { // ##a_2##
    $route(to) {
      const { title } = this.$options; // ##a_3##
      if (typeof title === 'string') {
        document.title = `${title}${suffix}`; // ##a_4##
      } else if (typeof title === 'function') {
        document.title = `${title.call(this, this)}${suffix}`; // ##a_5##
        // console.log('# vue-page-title watch $route title function');
        // console.log(document.title);
      } else {
        // document.title = '영화배급협동조합 씨네소파';
        // 이건 바뀌지 않아야 하는 상황에서 바뀔 수도 있으므로 폐기.
      }
    },
    vuePageTitle(to) { // ##a_6##
      // 이 방법을 제일 많이 쓴다.
      document.title = `${to}${suffix}`;
    },
  },
  mounted() { // ##a_7##
    const { title } = this.$options;
    if (typeof title === 'string') {
      document.title = `${title}${suffix}`;
    } else if (typeof title === 'function') {
      document.title = `${title.call(this, this)}${suffix}`;
      // console.log('# vue-page-title title function');
      // console.log(document.title);
    } else {
      // document.title = '영화배급협동조합 씨네소파';
    }
  },
};

Vue.mixin(pageTitle); // ##a_8##

a_1(1.) 제목의 꼬리말 설정

제목의 꼬리말을 변수로 만들어 설정하는 부분입니다. 추후 제목을 변경할 때 이 꼬리말을 매번 뒤에 붙여주는 식으로 동작할 겁니다.

a_2(2.) watch 활용

watch 는 변수간 의존성을 손쉽게 만들어줍니다. 자세한 내용은 공식 문서를 참조해주세요. watch 의 사용을 지양하라고 하는 이유는, 의존성을 거꾸로 찾아나기가 굉장히 어렵다는 것입니다. 위 코드에서는 $routevuePageTitle에 대해서 watch 를 걸었는데, 미래에 어떤 사람이 (내가 될 수도 있습니다.) vuePageTitle 변수를 변경했을 때 그로 인한 상태 변경이 어디서 어떻게 어느 코드에서 일어나는지 찾아내가 다소 껄끄러워집니다. 모든 Mixin 을 모두 뒤져서 vuePageTitle의 변경을 감지하는 부분을 모두 찾아야겠지요. 반면 computed 는 해당 computed 변수를 선언할 때 의존되는 것들이 전부 명시적으로 적혀져 있으므로 의존성을 관리하기가 한층 쉬워집니다. 하여튼 위 예제에서 watch 는 의존성의 연쇄 효과를 일으키지 않으므로 (document.title 를 변경하면 페이지의 제목만 변경되지, 다른 동작들이 연쇄적으로 일어날 가능성이 없으므로) 적당한 선에서 watch 를 활용했다고 볼 수 있습니다. $route 가 변경되는 시점 뿐만 아니라 데이터를 서버로부터 추가적으로 가져왔을 때(앞서 언급했음)에도 제목이 변경되어야 하기에 이 $route 에 대한 watch 는 결국 전부 사용하지 않게 되었습니다.

a_3(3.) $options 에서 데이터 가져오기

공식 문서에 뭐 자세히 나와있는 건 아니지만, $options 에는 해당 컴포넌트를 선언할 때 공식적으로 지원하는 속성들 (data, props, components, created 와 같은 각종 Hook Functions 등)을 제외한 나머지 사용자가 그냥 추가한 속성들을 담습니다. 즉, 이 Mixin 은 사용자 정의 속성으로 title을 불러오라고 하는 것입니다.

a_4(4.) title 그대로 적용

titlestring 이라면 제목으로 그대로 설정합니다.

a_5(5.) 함수일 경우 호출하여 적용

title 이 함수라면 this 가 실행하는 함수처럼 실행하여 그 리턴값을 제목으로 설정합니다. call 함수의 인수에서 두 번째 this 는 해당 함수를 호출할 때 첫 번째 인수로 this를 넣겠다는 뜻입니다.

a_6(6.) vuePageTitle 변수 감지

단순히 변수를 감지하여 제목으로 설정합니다. 결국 이 방법을 사용하게 되었습니다. 다른 방법은 저마다의 문제점이 있고, 이 방법은 장기적인 관리 면에서 전혀 깔끔하지는 않지만 만들려고 하는 페이지의 datavuePageTitle 를 추가하여 수정만 해줘도 페이지 제목이 잘 수정되니 그냥 이렇게 사용했습니다. $router 처럼 Observable 글로벌 인스턴스 변수를 추가하는 방법을 알았다면, 그 편이 차라리 훨씬 깔끔하고 나은 방법일 것입니다. (생각보다 어려운 것 같지는 않은 것 같습니다.)

a_7(7.) mounted 훅 설정

watch 가 아닌 mounted 훅에서 페이지 제목을 갱신합니다. 부모-자식 라우터를 구성할 때 어느 컴포넌트가 먼저 mounted 될지 확신할 수 없고, 마찬가지로 서버로부터 추후에 가져올 때를 대응할 수 없으므로 이 방법은 폐기했습니다.

a_8(8.) 전역 Mixin 등록

모든 컴포넌트에 해당 기능을 추가 삽입합니다.


결론

시간이 남는다면 Observable 한 변수(정식 명칭도 뭔지 모르겠어요)를 활용하는 방법을 적겠지만.. to do 로 남겨두겠습니다. 파이팅

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다

Scroll to top