今回もSPAを作る目的ではなく、既存のサイトのJQueryをVue.jsで置き換える方法をしらべて慣れていきたいという思いでまとめました。
今までのJQueryと同様にサーバーに関係なく扱うことが出来るというのを目標にしました。
サーバーに手を加えることはしないという前提になります。(npmのインストールを行わない等)
既存のサイトでも動作することを前提にしているため勿論ビルド等も必要ありません。
私のJSフレームワーク周りの技術レベル
JQuery:長い間使ってきた。今も現役。
Backbone.js:業務で使っていましたが挫折。
React:知らない。
Anguler:Anguler2になった際に勉強しましたが挫折。(学習コストが高く感じ、途中で投げました)
Vue.js:私が触ってきた中ではJQueryについでとっつきやすかったです。
まとめたもの
- CodePenのタイトルをクリックするとCodePenのナビゲーションのないページに行けます。
- 灰色の角丸と濃い灰色の角丸の部分がコンポーネント(vueファイルで切り出したもの)になります。
- 要素の追加削除の部分はコンポーネントの入れ子で作成しています。(index.htmlでlist-wrap.vueを呼び出しlist-wrap.vueからlist.vueを呼び出しています。)
ソースコード
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js" integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/vue@2.5.16/dist/vue.min.js"></script>
<script src="https://unpkg.com/http-vue-loader@1.3.4/src/httpVueLoader.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
new Vue({
el: '#app',
components: {
'component-a': httpVueLoader('component/a.vue'),
'component-b': httpVueLoader('component/b.vue'),
'component-list': httpVueLoader('component/list-wrap.vue'),
},
data: {
currentValue: '入力値',
currentElement: 'A',
currentComponent: 'component-a',
},
methods: {
selectValue(value) {
this.currentValue = value;
},
toggle() {
this.toggleElement();
this.toggleComponent();
},
toggleElement() {
this.currentElement = this.currentElement === 'A' ? 'B' : 'A'
},
toggleComponent() {
this.currentComponent = this.currentComponent === 'component-a' ? 'component-b' : 'component-a'
},
}
});
});
</script>
</head>
<body>
<div id="app" class="container">
<div class="row">
<div class="col-md-12 mt-5">
<div class="card">
<div class="card-header">
<h3 class="mb-0">Vue.js</h3>
</div>
<div class="card-body">
<form class="form" method="POST" action="">
<div class="form-group">
<h2 class="alert alert-info">テキスト/属性値/valueの差し替え</h2>
<ul>
<li>テキストはそのまま{{}}で出力</li>
<li>タグの属性値はv-bind:属性</li>
<li>フォームのvalueはv-model</li>
</ul>
<button type="button" class="btn btn-info" v-on:click="selectValue('alert alert-success')">alert alert-success</button>
<button type="button" class="btn btn-info" v-on:click="selectValue('alert alert-danger')">alert alert-danger</button>
<input v-model="currentValue" type="text" class="form-control" name="select">
<div>
<p v-bind:class="currentValue">{{ currentValue }}</p>
</div>
</div>
<div class="form-group mt-5">
<h2 class="alert alert-info">要素の差し替え</h2>
<button type="button" class="btn btn-info" v-on:click="toggle">差し替え</button>
<ul>
<li class="mt-3">
<p>IFでハードコーディング差し替え</p>
<div v-if="currentElement === 'A'" style="color:red">ハードコーディングA</div>
<div v-if="currentElement === 'B'" style="color:blue">ハードコーディングB</div>
</li>
<li class="mt-3">
<p>IFでコンポーネント差し替え</p>
<component-a v-if="currentComponent === 'component-a'"></component-a>
<component-b v-if="currentComponent === 'component-b'"></component-b>
</li>
<li class="mt-3">
<p>動的コンポーネント差し替え(componentタグにv-bind:is="コンポーネント名")</p>
<component v-bind:is="currentComponent"></component>
</li>
<li class="mt-3">
<p>状態保持をしてコンポーネント差し替え(通常のコンポーネントまたは動的コンポーネントをkeep-aliveタグで囲う)</p>
<keep-alive>
<component v-bind:is="currentComponent"></component>
</keep-alive>
</li>
</ul>
<p><a href="https://jp.vuejs.org/v2/guide/components.html#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AB%E3%82%88%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E4%BC%9D%E9%81%94" target="_blank">コンポーネント間通信</a>は通信ケースによって色々な書き方があります、コンポーネント間通信が複雑になる場合は専用の状態管理パターン(<a href="https://vuex.vuejs.org/ja/intro.html" target="_blank">Vuex</a>)採用した方が良いそうです。</p>
</div>
<div class="form-group mt-5">
<h2 class="alert alert-info">要素追加削除</h2>
<p>コンポーネントの入れ子と<a href="https://jp.vuejs.org/v2/guide/list.html#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%A8-v-for">コンポーネントのリストレンダリング</a>使用しています。削除ボタンの非活性はIFを使用しています。</p>
<component-list></component-list>
</div>
<div class="text-center mt-4">
<button type="submit" class="btn btn-primary">送信</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<template>
<div class="component">
<p>{{ text }}</p>
<input type="text" name="text-a">
</div>
</template>
<script>
module.exports = {
data: () => {
return {
text: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
}
}
}
</script>
<style scoped>
.component {
background-color: #e9ecef;
padding: 10px;
border-radius: 10px;
color: red;
}
</style>
<template>
<div class="component">
<p>{{ text }}</p>
<input type="text" name="text-b">
</div>
</template>
<script>
module.exports = {
data: () => {
return {
text: 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB',
}
}
}
</script>
<style scoped>
.component {
background-color: #e9ecef;
padding: 10px;
border-radius: 10px;
color: blue;
}
</style>
<template>
<div class="component">
<button type="button" class="btn btn-info" v-on:click="addList">追加</button>
<component-list v-for="(input, index) in inputList" v-bind:key="index" v-bind:index="index" v-bind:inputlist="inputList" v-on:remove="removeList(index)"></component-list>
</div>
</template>
<script>
module.exports = {
components: {
'component-list': httpVueLoader('component/list.vue'),
},
data: () => {
return {
inputList: [
{
value: '',
}
]
}
},
methods: {
addList() {
this.inputList.push({
value: ''
});
},
removeList(index) {
if (this.inputList.length <= 1) {
return;
}
this.inputList.splice(index, 1);
},
}
}
</script>
<style scoped>
.component {
background-color: #5a6268;
padding: 10px;
border-radius: 10px;
}
</style>
<template>
<div class="component d-flex mt-2">
<div class="flex-fill">
<input type="text" class="form-control" name="text[]" v-model="inputlist[index].value">
</div>
<div class="ml-2">
<button v-if="inputlist.length === 1" type="button" class="btn btn-danger" disabled="disabled">削除</button>
<button v-else type="button" class="btn btn-danger" v-on:click="$emit('remove')">削除</button>
</div>
</div>
</template>
<script>
module.exports = {
props: ['index', 'inputlist']// キャメルケースでは動作しない、ケバブケースでも動作しなかった
}
</script>
<style scoped>
.component {
background-color: #e9ecef;
padding: 10px;
border-radius: 10px;
color: green;
}
</style>
SPAとか
SPAは作ったことがありませんのでイメージがわきませんでしたがこれをやってみて、コンポーネントの入れ子をしたりコンポーネントの差し替え、コンポーネント間通信というのがメインになるのかなと感じました。
SPAにしていまいたい場合はルーティングや状態管理を取りや取り入れたら良いそうです。
https://router.vuejs.org/ja/essentials/getting-started.html
https://vuex.vuejs.org/ja/intro.html