はじめに
今回は、Vue.jsの公式チュートリアル「Markdown エディタ」を通して、Vue.js独自の記法と関連技術を解説します。
デモと概要
左カラムにmarkdown記法のテキストを入力すると、右カラムでプレビューできるアプリケーションです。
HTML
<div id="editor"> <textarea :value="input" @input="update"></textarea> <div v-html="compiledMarkdown"></div> </div> <script src="https://unpkg.com/vue@2.1.10/dist/vue.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> <script src="App.js"></script>
読み込むscript
以下の3つを使用します。
- Vue.js(2系の最新を使っています)
- marked.js(markdown記法をhtmlに変換する
marked
関数を使うため) - underscore.js(functionの発火タイミングを調整する
_debounce
を使うため)
:value
v-bind:value="input"
の略記です。
Vue.jsではv-
で始まる属性を用います。これを 「ディレクティブ」 といいます。
v-bind
は、数あるディレクティブの一つで、属性と式を束縛する ためのものです。
「束縛」することで、プロパティの変化に連動して、動的に属性またはプロパティの値が変化 します。
v-bind:value="input"
でいうと、value
プロパティは常にinput
プロパティと同値をとります。
input
プロパティはJavaScript中にて定義しますので、後ほど解説します。
@input=”update”
v-on:input="update"
の略記です。
v-on
もディレクティブの一つです。
これを指定した要素は、イベントリスナー が設定されます。
v-on:input="update"
とはinput
イベントが発生したときにupdate
メソッドを呼び出します。
ちなみにinput
イベントはHTML5から採択されたイベントで、「<input>
や <textarea>
要素の値が変更された時」 に発生します。
input – Web 技術のリファレンス | MDN
update
メソッドはJavaScript中にて定義しますので、後ほど解説します。
v-html=”compiledMarkdown”
v-html
は、指定した要素のinnerHTML
プロパティを更新する ためのディレクティブです。
この例でいうと、compiledMarkdown
プロパティがHTML文字列としてinnerHTML
プロパティに新たに書き込まれます。
innerHTML
とは下記の部分です。
<div>→innerHTMLプロパティを書き換えるとここが書き換わるよ←</div>
compiledMarkdown
プロパティはJavaScript中にて定義しますので、後ほど解説します。
JavaScript
new Vue({ el: '#editor', data: { input: '#hello' }, computed: { compiledMarkdown: function () { return marked(this.input, { sanitize: true }) } }, methods: { update: _.debounce(function (e) { this.input = e.target.value }, 300) } })
書き方の説明
new Vue({ AA:'aa',BB:'bb',CC:{cc1:cc1,cc2:cc2},DD:dd})
のような書き方をします。
これは、Vue
オブジェクトという、Vue.js独自に設計されたオブジェクトのテンプレートを複製し、そこにプロパティやメソッドを記述しているものです。
このひとまとまりを Vueインスタンス と呼びます。
el
el: '#editor',
el
は、どの要素にVueインスタンスを与えるかを記述します。
HTML要素もしくはCSSセレクタで指定することができます。
今回は#editor
にVueインスタンスを与えています。
data
data: { input: '# hello' },
data
はデータを管理するオブジェクトです。
今回はinput
という名前のプロパティが入っています。
ここで指定したプロパティ値は初期値として扱われます。
今回はinput
プロパティの初期値は# hello
です(#
はMarkdown記法で<h1>
要素の意味)
これはHTMLで出て来た:value="input"
に関連する部分です。
束縛関係にあるvalue
プロパティは常にinput
プロパティと同値をとります。
これは、何らかの操作にてinput
プロパティの値が書き換わった時、リアクティブに(反応的に)書き換わっているということです。
後ほど、「何らかの操作」を行う部分を解説します。
注意:textarea要素とvalue属性、textareaオブジェクトとvalueプロパティ
HTMLの<textarea>
要素にはvalue
属性はありません。
ここでのvalue
は、JavaScriptのHTMLTextAreaElement
オブジェクトのvalue
プロパティです。
<textarea>
要素の初期値を決定するときは<textarea>初期テキスト</textarea>
のように記述します。
さもinnerHTML
プロパティに初期テキストを書き込んでいるように見えますが、そうではありません。textarea
オブジェクトにおいては、こinnerHTML
プロパティではなくvalue
プロパティに書き込む必要があります。
少しややこしいですが、HTML要素の「属性」とオブジェクトの「プロパティ」は別もの だということを覚えておきましょう。
「innerHTML
プロパティを書き換えても、textarea
に表示される文字列が変化しない!」 というハマりを避けられます。
computed
computed: { compiledMarkdown: function () { return marked(this.input, { sanitize: true }) } },
computed
は算出プロパティです。内部的に関数で動的に更新されるプロパティと捉えるのがよいでしょう。
ここではcompiledMarkdown
というプロパティがあります。
this.input
の値が変化すると、それに反応して関数内での処理が発生し、値を返します。
this.input
というのは、data
オブジェクトのinput
プロパティのことです。
data
オブジェクト内のプロパティにアクセスしたいときはthis.プロパティ名
のように書けばOKです。
compiledMarkdownで行われていること
marked.jsのmarked
関数を使って、引き渡したmarkdown記法の文字列をhtmlに変換 しています。
chjj/marked: A markdown parser and compiler. Built for speed.
{sanitize:true}
marked()
関数の第二引数に{sanitize:true}
という記述があります。
これは、引き渡した文字列を変換するときに、特殊文字を一般的な文字列に変換する ための記述になります。
「サニタイジング」や「エスケープ」ともいいます。
例えば、渡される文字列が<strong>HTMLの特殊文字列<strong>
だった場合を考えます。
サニタイジングを行うと、<strong>
が<strong>
と置き換えられて返ってきます。
行わない場合は、そのまま返ってきますので、innerHTML
プロパティに書き込むと、そのまま<strong>HTMLの特殊文字列<strong>
となり太字のテキストが表示されます。
MarkdownエディタでHTML要素を記述した時、文字列として処理するかHTML要素として処理するかはエディタによって異なる部分ですが、その部分の調整を担っている引数です。
methods
methods: { update: _.debounce(function (e) { this.input = e.target.value }, 300) }
今回、methods
にはupdate
メソッドが存在しています。
先述した「input
プロパティが何らかの操作によって書き換わる」部分にあたります。
引数e
イベントリスナー指定されている要素ので起こったイベントをオブジェクトとして引き渡しています。
今回はtextarea
で発生するイベントがそれに当たり、e.target.value
ではtextarea
のvalue
プロパティの値にアクセスしています。
updateメソッドで行われていること
このメソッドはdata
のinput
にe.target.value
すなわち「フォームの入力値」を入れるという処理を行っています。
debounce関数
_debounce()
はVue.jsの機能ではありません。
正しくは、Vue.jsのバージョン1系には存在したが2系へのアップデートに伴い削除されました。
underscore.js
に同じものが存在するので、これを読み込むことで使えるようにしています。
debounce – Underscore.js
debounce
は 「連続して関数が呼び出された時、最後の実行から指定した時間が経過するまでの呼び出しが無効になる」 という処理を行う。
今回は@input
としているため、文字が入力されるたびに関数呼び出しがかかります。
しかし、debounce
を用いると、「第二引数で指定した時間が経過する間、同イベントが発生しても処理は行わない」 ということができます。
また、立て続けに発生するイベントは一つにグループ化されます。
ですので、入力中には発火せず、入力後(キータイプをとめたら)実行される という処理が行われます。
延々と長いテキストを入力している間は、プレビュー画面に反映は行われません。
関連:throttle関数
underscore.js
の関連した関数にthrottle()
という関数がある。
これは、立て続けに発生するイベントはグループ化されないので、都度、時間ごとに処理が行われます。
リアルタイムに近いのはこちらです。
今回はさしてどちらでも構いませんが、予測変換表示なんかはthrottle
の方がいいですね。
このようにして 連続して発生するイベントを間引くことで、処理回数を減らし、負荷削減を行なっています。
まとめ:全体の流れ
1.文字入力がおこる
- 文字が入力されるたびに
input
イベントが発生。 @input="update"
によりupdate
メソッドが呼び出される。
2.debounceで間引かれつつ、updateメソッドが呼び出される
data
オブジェクトのinput
プロパティが、入力された値に書き換わる。input
プロパティはv-bind:value
と束縛関係にあるので、textarea
オブジェクトのvalue
プロパティもリアクティブに変化する。
3.inputの変更に反応して、compiledMarkdownが変化
input
プロパティが書き換わるたびにcompiledMarkdown
プロパティの内部の関数が検知して自動で算出する。input
プロパティの値はmarked()
関数に渡され、サニタイズされたhtml文字列が返ってくる。
4.プレビュー画面に反映
- 変化した
compiledMarkdown
はv-html="compiledMarkdown"
により束縛されているので、返された値はinnerHTML
プロパティに随時書き込まれる。
まとめ
ディレクティブ
v-bind
は「HTML要素の属性orオブジェクトのプロパティ」と「dataオブジェクトのプロパティ」を束縛する- 束縛関係にあるものは、プロパティの変化に反応して同じ値をとる
v-on
は「イベントリスナーを設定する」v-html
は「innerHTML
プロパティ」と「dataオブジェクトのプロパティ」を束縛する
Vueインスタンス
el
でVueインスタンスを与える要素を指定するdata
はオブジェクト。束縛するプロパティをここで管理するcomputed
オブジェクト内のプロパティはdata
で管理されるプロパティの変化を検知して動的に変化する。methods
オブジェクト内のメソッドはイベントハンドラを通して呼び出す。
フロントエンドエンジニア積極採用中
株式会社トゥーアールでは現在フロントエンドエンジニア積極的に採用中です!
興味がある人は採用ページをチェック!!