Edited at

「jQueryでやっていたことを、サーバーサイド側を何も変えずにフロントエンド側をVuejsにしておくれよ」

どうしてもレンダリングはサーバーサイドがやっているものを変えたくない。でもフロントは変えろ。そんな要望ありますよね?

え?ありませんって?あったんですよ。

コンテンツの中身は可変なのでSSレンダリングしたいけど、APIを作ってFE側から取ってくるほどの内容でもない。その上で、レンダリングするHTMLにはVueのためのバインディングや条件式が書けない。そんな場合を想定します。


例えばアコーディオン

スクリーンショット 2019-05-24 11.43.04.png

スクリーンショット 2019-05-24 11.42.40.png

こんなイメージのものを作って行こうと思います。

まずはサーバーサイドにレンダリングしてもらうHTMLです。


コード


html

<div id="accordion--wrapper">

<div id="accordion--component" class="accordion">
<h2 class="accordion--title">アコーディオンメニュー<i class="icon"></i></h2>
<ul class="accordion--body">
<li>メニュー1</li>
<li>メニュー2</li>
<li>メニュー3</li>
<li>メニュー4</li>
</ul>
</div>
</div>

それではVueコンポーネントを作っていきましょう。


Vueコンポーネント

<script>

export default {
data() {
return {
isOpen: false,
};
},
mounted() {
this.$el.querySelector('.accordion--title').addEventListener('click', this.clicked);
},
methods: {
clicked() {
this.isOpen = !this.isOpen;
},
},
watch: {
isOpen(isOpen) {
const accordion = this.$el;
if (isOpen) {
accordion.classList.add('open');
} else {
accordion.classList.remove('open');
}
},
},
};
</script>

<style lang="scss" scoped>
.accordion {
& .accordion--title {
cursor: pointer;
display: block;
padding: 15px 15px 15px 42px;
margin-bottom: 0;
border: 1px solid #CCC;

& i.icon {
margin-left: 20px;
&:before {
content: "+";
}
}

}
& .accordion--body {
padding: 0;
margin: 0;
visibility: hidden;
opacity: 0;
overflow: auto;
transition: all .2s ease-in;
list-style: none;
& li {
border-bottom: 1px solid #CCC;
border-left: 1px solid #CCC;
border-right: 1px solid #CCC;
}
}
&.open {
& .accordion--title {
& i.icon {
&:before{
content: "-";
}
}
}
& .accordion--body {
visibility: visible;
opacity: 1;
transition: all .8s ease-in;
}
}
}
</style>

今回制約上テンプレートを持てないので、<template> のない単一コンポーネントを作ります。css/scssは見習い中なのでごちゃごちゃしてしまっています。


cssに関する追記

アコーディオンのスタイルをすべてコンポーネントに書いてしまっていますが、見た目を決める記述は画面から読み込むcssに記述して、ここには開閉等ロジックに関わるもののみを記述するべきかもしれません。

追記ここまで。

このコンポーネントでhtmlにVueで命を吹き込んでいきます。


エントリポイントJavaScript

import Accordion from './components/Accordion.vue';

const ready = (fn) => {
if (
document.attachEvent ? document.readyState === 'complete' : document.readyState !== 'loading'
) {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
};

ready(() => {
Accordion.template = '#accordion--wrapper';
new Vue({
el: '#accordion--component',
render: h => h(Accordion),
});
});

Accordion.template = '#accordion--wrapper' こちらで id="accordion-wrapper" の要素の 内側 をAccordionコンポーネントのテンプレートとして注入します。

new Vue する際の el: '#accordion--component' は、指定した要素をコンポーネントで置き換えるため、templateとelで指定するidは親子(template: 親、 el: 子)になる点に注意が必要になります。


よい点、わるい点

またサーバーサイドがレンダリングしたHTMLを前提とした実装になってしまうため、ロジックと見た目の分離ができていないのも少し残念なところで、普段Vueの単一コンポーネントでは利用することのないであろう querySelector$el が入ってしまいます。

ただ、状態管理や watch 等のVueの機能が利用できて、単純にES6だけで書いていくよりは可読性が高い書き方になっていくのではないでしょうか。

また、プロジェクトの一部ではVueを導入できているのにSSレンダリングの都合上Vue化しにくいところにも適用できるので、フレームワークが統一されるメリットも大きいと思います。


まとめ

ちょっとタイトルは雑でしたが、いろいろ制約の多い中でVueが使えないためにフロントエンドコードの一貫性がない状況を改善しようと検討した内容を書いてみました。

これで、今までjQueryで実装していたプロジェクトもこっそりとサーバーサイドに知られずにVueに置き換えることもできるかもしれませんね。


懸念

一度レンダリングしたものを置き換えることになるので、SEO評価に影響がある可能性があります。この辺はおいおい調べていきたいなと思っています。