蛇ノ目の記

技術のことも。そうでないことも。

Vue.jsサンプルのMarkdown Editorを読み解く

フロントエンドアレルギーを少しでも治すべく「手を動かして学ぶ!Vue.js」を片手にVue.jsに入門し始めた。

www.shoeisha.co.jp

今までの人生でほとんどJavaScriptやってきたことないマンでもわかりやすい良書。

今回はこの書籍と公式ドキュメントから学んだ内容で、Vue.js公式Webサイトのサンプルに載っているMarkdown Editorの仕組みを理解していく。

jp.vuejs.org

コード全体

え、これだけでMarkdownエディタ書けるの。Vue.jsマジやばい(語彙力

<!DOCTYPE html>
<html>
  <head>
    <title>Markdown Editor</title>
    <script src="https://unpkg.com/vue"></script>
    <script src="https://unpkg.com/marked@0.3.6"></script>
    <script src="https://unpkg.com/lodash@4.16.0"></script>
    <link rel="stylesheet" type="text/css" href="/style.css" />
  </head>
  <body>
    <div id="editor">
      <textarea :value="input" @input="update"></textarea>
      <div v-html="compiledMarkdown"></div>
    </div>

    <script>
      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)
        }
      });
    </script>
  </body>
</html>

テンプレート

Markdownエディタを描画しているのはこの4行。

    <div id="editor">
      <textarea :value="input" @input="update"></textarea>
      <div v-html="compiledMarkdown"></div>
    </div>

「el」プロパティーで、Vueの表示をはめ込む場所=HTML要素のセレクター(id)を定義してるんですね?

Vue.jsの「el」とは? - JavaScript勉強会

とあるように、editorという名前でdivタグの内側を「Vueの表示をはめ込む場所」として定義。

textarea要素

次にtextarea要素。

<textarea :value="input" @input="update"></textarea>

:value="input"

:valuev-bind:valueの省略形。「手を動かして学ぶ!Vue.js」50ページ目には

要素の属性をデータで指定するときは、v-bind

とある。つまり、textarea要素のvalue属性にinputというデータを指定する、ということになる。

いや、でもtextarea要素にvalue属性なくね?

<textarea> - HTML: HyperText Markup Language | MDN

Vue.js公式ドキュメントでは複数行テキストに対してはv-modelを使っている。Vue.jsなにもわからない。

フォーム入力バインディング — Vue.js

試しに v-bind:inputv-modelsで書いてみた。しかしどちらも動作する。やっぱりVue.jsなにもわからない。

https://jsfiddle.net/nao_y/rj28vd7t/5/

@input="update"

@inputv-on:inputの省略形。「手を動かして学ぶ!Vue.js」50ページ目には

イベントとメソッドをつなぐときは、v-on

とある。つまり入力というイベントとupdateというメソッドを繋いでいる。入力が発生すると、updateメソッドが動く、というイメージだろうか?

div要素

<div v-html="compiledMarkdown"></div>

v-htmlはHTMLとして表示する。ここではcompiledMarkdownプロパティをHTMLとして表示する。

Vueインスタンス

      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)
        }
      });

elオプション

elオプションは#editorとなっている。ここはHTML部分の<div id="editor">と対応していて、このdiv要素の中にVueの表示をはめ込む。

dataオプション

dataオプションのinputプロパティはtextarea要素の:value="input"と紐付いていて、inputプロパティのデータがtextarea要素に表示される。

computedオプション

computedオプション(算出プロパティ)compiledMarkdownプロパティが定義されている。このプロパティには関数が指定されている。

function() {
  return marked(this.input, { sanitize: true });
}

Marked.jsライブラリを使って、inputプロパティの内容をHTMLにパースしている。

パースしたHTMLは<div v-html="compiledMarkdown"></div> で表示される。

ところで、{ sanitize: true }となっていてHTMLのサニタイズをしているけど、Marked.jsのUsageには以下のようにある。

Warning: 🚨 Marked does not sanitize the output HTML. Please use a sanitize library, like DOMPurify (recommended), sanitize-html or insane on the output HTML! 🚨

どうやら出力するHTMLをサニタイズしないのでDOMPurifyなどを使うことが推奨されている。

DOMPurifyを使って書き直すとこんな感じになる。

function() {
  var html = marked(this.input);
  return DOMPurify.sanitize(html);
}

methodsオプション

methodsオプションでは、イベントに対して処理を行う。

methods: {
  update: _.debounce(function(e) {
    this.input = e.target.value;
  }, 300)
}

ここでは入力(@input="update")に対して、updateというメソッドが働く。

_.debounce()lodashというライブラリの関数。なんか便利関数の集まり的なライブラリらしい。

Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.

https://lodash.com/docs/4.17.15#debounce

指定された秒数(ミリ秒)待った後、関数を呼び出す、というような役割。

_.debounce()に渡される関数ではeという引数がある。これはイベントのことを指すのだろうか。Pythonしかわからないマンなのでeと言われると例外を連想してしまう。

ここでは300ミリ秒待ったあとに、this.input = e.target.value;でinputプロパティに対してイベント(入力)の値(?)を代入する。

ここで更新されたinputプロパティはcomputedオプションのcompliedMarkdownプロパティでHTMLへとパースされる。

つまり、updateメソッドの働きによってMarkdownエディタへの入力から300ミリ秒後にプレビューが更新される。

やってみた

Django Girls TutorialのブログにMarkdownエディタを実装してみた。

github.com

これがMarkdownエディタを実装した投稿画面。

f:id:Nao_Y:20200309025641p:plain

投稿画面のtemplateは以下。省略形を元の形に直したり、サニタイズをDOMPurifyにやらせたり、プレビューへの反映待ち時間を150ミリ秒に縮めたりしている。

Django Girls Tutorial with Vue.js