들어가기 전에
본 글의 vue 버전은 2.0 입니다. vue 컴포넌트를 직접 만들 수 있고 vue-router와 vuex가 무엇인지 알고 기본적인 활용을 할 수 있는 분들께 적합한 내용입니다.
구상해보기
페이지 제목을 바꾸는 코드는 document.title = "제목";
이렇게 쓰면 되기 때문에 아주 간단합니다. 하지만 우리가 원하는 것은 좀 더 일관성있고 효율적으로 페이지 제목을 관리하는 것입니다. 우선 페이지 제목이 바뀌는 시점을 한번 명백히 해봅시다. 아마 다음 세 가지 정도로 추려낼 수 있을 것입니다.
beforeRoute
,afterEach
등의 라우터 가드와 관련된 훅mounted
,beforeMount
등 컴포넌트 라이프사이클과 관련된 훅- 서버로부터 페이지 데이터를 받아올 때
페이지 제목이 컴포넌트에 따라 정적으로 고정되어 있다면 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);
이렇게 하고 나서 페이지를 나타내는 컴포넌트에서 data
에 vuePageTitle
을 추가하고 필요할 때마다 이를 수정하면 이 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
의 사용을 지양하라고 하는 이유는, 의존성을 거꾸로 찾아나기가 굉장히 어렵다는 것입니다. 위 코드에서는 $route
와 vuePageTitle
에 대해서 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
그대로 적용
title
이 string
이라면 제목으로 그대로 설정합니다.
a_5(5.) 함수일 경우 호출하여 적용
title
이 함수라면 this
가 실행하는 함수처럼 실행하여 그 리턴값을 제목으로 설정합니다. call
함수의 인수에서 두 번째 this
는 해당 함수를 호출할 때 첫 번째 인수로 this
를 넣겠다는 뜻입니다.
a_6(6.) vuePageTitle
변수 감지
단순히 변수를 감지하여 제목으로 설정합니다. 결국 이 방법을 사용하게 되었습니다. 다른 방법은 저마다의 문제점이 있고, 이 방법은 장기적인 관리 면에서 전혀 깔끔하지는 않지만 만들려고 하는 페이지의 data
에 vuePageTitle
를 추가하여 수정만 해줘도 페이지 제목이 잘 수정되니 그냥 이렇게 사용했습니다. $router 처럼 Observable 글로벌 인스턴스 변수를 추가하는 방법을 알았다면, 그 편이 차라리 훨씬 깔끔하고 나은 방법일 것입니다. (생각보다 어려운 것 같지는 않은 것 같습니다.)
a_7(7.) mounted
훅 설정
watch
가 아닌 mounted
훅에서 페이지 제목을 갱신합니다. 부모-자식 라우터를 구성할 때 어느 컴포넌트가 먼저 mounted 될지 확신할 수 없고, 마찬가지로 서버로부터 추후에 가져올 때를 대응할 수 없으므로 이 방법은 폐기했습니다.
a_8(8.) 전역 Mixin 등록
모든 컴포넌트에 해당 기능을 추가 삽입합니다.
결론
시간이 남는다면 Observable 한 변수(정식 명칭도 뭔지 모르겠어요)를 활용하는 방법을 적겠지만.. to do 로 남겨두겠습니다. 파이팅