知識の枝

"All is well"

Django向けVue.js 「導入」編

約63日前 2021年8月26日22:34
デジタル
Django JavaScript Vue.js

改訂履歴


2021/8/26 投稿

1. ここに辿り着いた人は


DjangoでWEBアプリを作ったことがある個人開発者の方でしょうか。

それともCSSだけではフロントを作るのに限界を感じてきている方でしょうか。


私は上記のどちらにも当てはまります。

フロントエンドのフレームワークは何も触ったことが無い完全初心者です。


「アプリの動きや見た目をなんかすごくしたいなぁ。簡単にできないかなぁ」と思っていたので、

Javascript, jQuery, Ajaxなどググって出てくる便利な道具をチョコチョコ使っていました。


あるときTwitterやQiitaを見ていて、フロントエンド開発用のJavascriptフレームワークなるものがあることを知りました。


「自分が使っているのはPython(Django)だからJavascriptなんで関係ないなぁ」なんて別の世界の話のように思っていました。


React.js, Vue.js, Node.js, next.js, nuxt.jsのような「●●.js」といったよく分からないJavascript系の「何か」が世の中では流行っているのだなぁ程度の認識だったと思います。


ところがある日、React + Django, Vue + Djangoといった組み合わせのアプリ開発に関する記事を見つけました。

その構成はバックエンドにDRF(Django REST framework)、フロントにVue or React(Javascript)を用いており、フロントとバックの役割を完全に分けて扱うものでした。

その記事を読んでも内容はさっぱり分からなっかのですが、「VueやReactを使えばDjangoのフロントが良くなるんだ!」という認識は持てました。


フロントのWEBフレームワークを使ってみたい!という気持ちになったのですが、イマイチ何をすれば良いか分からない。。。


まずVueとReactどっちを使えばいいんだ?

両者の違いは何?

などと中々着手出来ずにいました。




結論から言うとVueを選択しました。

また上記の開発例とは異なりフロントとバックで役割を切り分けず、慣れているDjangoでのhtml生成を採用しました。


「Vue.jsに慣れる」ということを最優先の目標とし、Vue + Django標準html生成の組み合わせを採用しました。

できる限り導入ハードルを低く、DjangoユーザーがフロントのWEBフレームワークの旨味を知るには丁度いい方法だと思っています。



2. なぜVue.jsにしたのか


フロント系のWEBフレームワークを調べていて感じたのは

理由
①日本語の情報量としては圧倒的にReactとVueが多い

②フレームワークの良し悪しは使用者の好み、又はプロジェクト次第

③Reactはガチガチにフロントをやりたい人向け

④Vueは初心者でもとっかかりやすい



①の理由もあってReactかVueの2択しか考えていませんでした。

②どちらにも支持者がいてそれぞれ異なった良さがあるように感じました。

③これはよく書いてありました。
将来、その業界で働いて食っていくという意思が強ければReactにしていたかもしれないです。

④決め手です。
Vueの公式ドキュメントを読んだ限りでは、特段とっかかりやすいようにも見えませんでした。

が、多くのエンジニアがVueは初心者向け、簡単。と言っているので、信じてこちらを選んでいます。


私のようなライトユーザーなら迷うこと無くVueで良いんじゃないかなと思います。


Reactのチュートリアルも数ページ読みました。

最初はJSXすげぇって思いましたが、ページを読み進めるとだんだんと理解出来なくなったので

「あ、これやめとこう」となりました。


といった経緯でVue.jsを選択することになりましたが、

Reactにも興味はあるので、Vueで十分遊んだらもう一度Reactに挑戦してみたいと思います。



3. とりあえずVueを使ってみる


まだ何も分からない状態ですが、とりあえずVueを使ってみます。


導入は簡単で、CDNを利用します。

下記のスクリプトタグをhtmlファイルの<head>内に入れるだけでokです。
<script src="https://unpkg.com/vue@next"></script>
※この記事を書いた時点ではVue.js 3.2.6が読み込まれています。

注意

本番環境では、新しいバージョンによる意図しない不具合を避けるため、特定のバージョン番号とビルド番号にリンクすることをお勧めします。

Vue.js インストール
つまり上のスクリプトタグはあくまで開発用。

本番環境ではバージョン指定したCDNを使ってね。とのことです。
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>  # 本番用

導入が終わったのでさっそくVueらしいことをしてみます。


適当な.htmlファイルを作成し、下記のhtmlテンプレートを貼り付けましょう。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

</body>
</html>
エディタにVisual Studio Codeを使っている方は ! + Tab で上記のテンプレートが自動で挿入されます。

<head>タグの中に先程の<script>を挿入しましょう。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script> # ここに入れた
</head>
<body>

</body>
</html>

ここからはVue.jsのドキュメントに沿ってVueに触れていこうと思います。

リンク
はじめに | Vue.js

3.1 - 反応的なレンダリング


元となるページのviewは下記のように定義しました。
# views.py
def IndexView(request):
template = "vue_test/vue_index.html"

context = {
'test_text': "This is Django test text!",
}

return render(request, template, context)
テンプレート変数で文字列をhtmlに渡しています。

htmlは下記のように書きました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script> # CDN
</head>
<body>
<h1>This is test page</h1>
<p>{{test_text}}</p> # Djangoのテンプレート変数
<hr>
<div id="counter">
Counter: [[ counter ]] # delimiterで"[[ ]]"を使えるようにしています
</div>

<script>
const Counter = {
data() {
return {
counter: 0
}
},
mounted() {
setInterval(() => {
this.counter++
}, 1000)
},
delimiters: ['[[', ']]'] # デフォルト"{{ }}"から変更
}

Vue.createApp(
Counter
).mount('#counter')
</script>

</body>
</html>
Djangoの書き方とVueの書き方を両立しています。

大切なのは {{ }} の代わりに [[ ]]を使用している点です。

Vueではデフォルトで{{ }}を使う仕様なのですが、この書き方はDjangoのテンプレート変数の書き方と被ってますね。

デフォルトのままではVueの{{ }}が使えないので、「delimiters」というオプションでルールを書き換えます。
delimiters: ['[[', ']]']  # デフォルト"{{ }}"から変更


アウトプットは下記のようになります。

動作例


「counter」の値を1000msごとに+1しており、数字が更新される度にレンダリングされる内容も即座に変化します。


3.2 - 要素の属性をバインド


「要素の属性」というのは<タグ>が持つ情報のことです。

例を挙げます。
<p title="title属性">  #属性の例
これは「title」という属性を<p>タグに与えたものです。

title属性は要素にマウスオーバーした際に属性値が表示される機能を提供します。


Vueでは属性値を書き換え可能な状態にすることが出来ます。

それが「属性のバインド」です。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<h1>This is test page</h1>
<p>Djangoのテンプレート変数</p>
<p>{{test_text}}</p>
<hr>
<p>反応的なレンダリング</p>
<div id="counter">
Counter: [[ counter ]]
</div>
<hr>
<p>要素の属性をバインド</p>
<div id="bind-attribute">
<span v-bind:title="message"> #このspanタグのtitle属性が対象
Hover your mouse over me for a few seconds to see my dynamically bound
title!
</span>
</div>
<script>
const Counter = {
data() {
return {
counter: 0
}
},
mounted() {
setInterval(() => {
this.counter++
}, 1000)
},
delimiters: ['[[', ']]']
}

Vue.createApp(
Counter
).mount('#counter')

const AttributeBinding = { #この部分でtitleの属性値を作る
data() {
return {
message: 'You loaded this page on ' + new Date().toLocaleString()
}
}
}

Vue.createApp(AttributeBinding).mount('#bind-attribute')
</script>

</body>
</html>
「message」という変数が<span>タグのtitle属性に割り当てられています。
 <span v-bind:title="message">
この変数messageの中身を定義しているのが、下記の部分です。
 const AttributeBinding = {
data() {
return {
message: 'You loaded this page on ' + new Date().toLocaleString()
}
}
}
Vue.createApp(AttributeBinding).mount('#bind-attribute')
「message」で定義した内容が<span>タグのtitle属性に反映されます。

アウトプットは下記のようになります。

動作例


マウスオーバーでtitle属性値が表示されていますね。


3.3 - ユーザー入力の制御


簡単な例を挙げると「onclick」属性です。

ユーザーが行った動作を検知して、スクリプトを実行する制御です。

Vueの書き方をみてみましょう。

コードが長くなってきたので抜粋して書きます。
<p>ユーザー入力の制御</p>
<div id="event-handling">
<p>[[ message ]]</p>
<button v-on:click="reverseMessage">Reverse Message</button>
</div>

<script>
const EventHandling = {
data() {
return {
message: 'Hello Vue.js!'
}
},
methods: {
reverseMessage() {
this.message = this.message
.split('')
.reverse()
.join('')
}
},
delimiters: ['[[', ']]']
}

Vue.createApp(EventHandling).mount('#event-handling')
</script>
button要素に「v-on:click="reverseMessage"」という属性がついています。

これはディレクティブと呼ばれ、「v-」という接頭辞がつきます。

Vueのドキュメントでは下記のように説明されています。

引用文

ディレクティブとは、 DOM 要素に対して何かを実行することをライブラリに伝達する、マークアップ中の特別なトークンです。

Vue.js - ディレクティブ

クリックによって「reverseMessage」メソッドが実行されます。
methods: {
reverseMessage() {
this.message = this.message
.split('')
.reverse()
.join('')
}
},
自身の「message」変数の中身をsplit()で分割、reverse()で反転、join()で再結合したものに置き換えてるメソッドです。
# メソッド実行前のmessageの中身
message: 'Hello Vue.js!'

# メソッド実行後
message: '!sj.euV olleH'

アウトプットは下記のようになります。

動作例



3.4 - 双方向バインディング


これは先に動作イメージを見たほうが理解が早いと思います。

動作例


入力と同時に内容がページ上に反映されています。

これを双方向バインディングと呼びます。

「v-model」ディレクティブを使用します。
<p>双方向バインディング</p>
<div id="two-way-binding">
<p>[[ message ]]</p>
<input v-model="message" />
</div>

<script>
const TwoWayBinding = {
data() {
return {
message: 'Hello Vue!'
}
},
delimiters: ['[[', ']]']
}

Vue.createApp(TwoWayBinding).mount('#two-way-binding')
</script>
<input>要素の「v-model」属性に変数messageを割り当てています。

何がどう双方向なのか?
<p>[[ message ]]</p>
「message」という変数が表示されていますね。

このmessageの内容は<script>で定義されています。
 message: 'Hello Vue!'
これがまず1方向目です。


もう1方向は上記で出てきた「v-model」属性のついた<input>要素です。

「v-model="message"」としたことで、この<input>要素と変数messageが繋がります(バインド)。

変数messageを2方向から更新可能なので「双方向バインディング」と呼びます。


「双方向」と書くから紛らわしいのですが、「A ⇔ B」という意味では無いです。

「A → C」および「B → C」という意味です。(Cに対して2方向ある)

英語では「Two Way Binding」と呼びます。

こっちのほうがしっくりきますね。


3.5 - 条件分岐「if」


「v-if」ディレクティブというものを使います。

v-if属性の値がtrueのとき、要素の中身を表示。

値がfalseのときは中身を表示させません。

下記のようなイメージです。

動作例


ちょっとした実験をしました。
<p>条件分岐「if」の実験</p>
<div id="conditional-rendering2">
<button v-on:click="replace">[[ seen ]]</button>
<br>
<span v-if="seen">Now you see me</span>
</div>

<script>
const ConditionalRendering = {
data() {
return {
seen: true
}
},
methods: {
replace() {
if (this.seen == true) {
this.seen = false
} else {
this.seen = true
}
}
},
delimiters: ['[[', ']]']
}

Vue.createApp(ConditionalRendering).mount('#conditional-rendering2')
</script>
先程覚えた「v-on」を使い「v-if」の状態を変えられないかトライします。

動作例


上手くいきました。

v-on:clickでメソッドを呼び出し、trueとfalseを切り替えています。


3.6 - 配列の展開


この機能はDjangoのテンプレートタグ {% for %}に似ています。

Djangoの場合、viewから受け取ったリストを下記のように展開できます。
<div>
<ol>
{% for todo in todos %}
<li>{{ todo.text }}</li>
{% endfor %}
</ol>
</div>

Vueの場合もほぼ同じです。
<p>配列の展開</p>
<div id="list-rendering">
<ol>
<li v-for="todo in todos"> #ここが展開している部分
[[ todo.text ]]
</li>
</ol>
</div>

<script>
const ListRendering = {
data() {
return {
todos: [
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
]
}
},
delimiters: ['[[', ']]']
}

Vue.createApp(ListRendering).mount('#list-rendering')
</script>
「v-for」ディレクティブを使い、配列から要素を1つずつ取り出すことが可能です。

アウトプットは下記のようになります。
動作例



4. 「導入」編のまとめ


導入編はここまでです。

「Vue.jsに慣れる」という目的の通り、それほどハードルを感じること無くVue.jsを扱うことが出来たと思います。


今回の一連のチュートリアルはVue.jsの公式ドキュメントに沿って進めさせて頂きました。

リンク
はじめに | Vue.js


私の学習の進捗次第にはなりますが、次回以降もハードルの低いVueの記事を書こうと思います。


それでは。