この記事は第2のドワンゴ Advent Calendar 2015の18日目です。
はじめに
こんにちは、2015年新卒入社の@nagisioです。
普段はフロントエンドで開発を行っています。
この記事はjQueryしか書いたことが無かった筆者が、仮想DOMという世界に辿り着くまでの過程を淡々と書いたものです。
過度な期待はしないでください。
また、本記事の内容は私と同じ初心者の方向けとなっています。
フロント開発ってなんなの
まずフロント開発が一体全体何を書くのか抑えておきましょう。
私が普段書いているものは以下です。
- HTML
- CSS
- JavaScript
入社前に私が書いていた(≠書けた)ものも見てみましょう。
- HTML
- CSS
- JavaScript
変わりませんね。
ですが、この大きな枠組みの中に、沢山の技術が含まれているのです。
具体的に述べましょう。
- HTML
- Slim
- Jade
- ECT
- CSS
- Sass(SCSS)
- Less
- Stylus
- JavaScript
- Gulp/Grunt
- AltJS/ES6
- React.js/Riot.js/Mithril.js
- Mocha/Jasmine
ほんの一部を挙げました。
9割以上知っている方はこの記事はいささか冗長かもしれません。
私は入社時に恐らくSassぐらいしか知らなかった気がします。
本記事ではこの中から
- Jade
- Sass(SCSS)
- Riot.js
を取り上げ、一つの簡単なプロダクト(ToDo的なやつ)を作ってみます。
また、成果物については、下記のgitリポジトリをご覧いただければと思います。
nagisio/riot-todo-test
http://nagisio.github.io/riot-todo-test/
1. HTMLを書く
まずはHTML(の雛形)を書いてみましょう。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ToDo</title>
<link rel="stylesheet" href="./styles/app.css">
</head>
<body>
<section id="todo">
<form>
<input type="text" name="title">
<input type="text" name="description">
<input type="submit" value="Add">
</form>
</section>
<script src="./scripts/app.js"></script>
</body>
</html>
簡単ですね。
今回のようなシングルページで完結する場合、ベタにHTMLを書くので良いかもしれません。
ですが、複数ページを作る場合はどうでしょう。
毎回 <head>
内を書くのって面倒じゃないですか?
コピペをすれば簡単?
では <meta>
タグが一つ増えたら?
CSSの読み込みファイルが増えたら?
etcetc...
めんどくさくないですか?
モダンの波に乗りたい!
上の例では <head>
と <body>
の部分を分けることができれば、幸せになれそうです。
そこでいよいよテンプレートエンジン(Jade)の出番です。
詳しい説明は省きます(後述の素晴らしい参考記事やページを見てください)ので、まずはコードを見てください。
doctype html
html(lang='ja')
head
meta(charset='utf8')
title ToDo
link(rel='stylesheet' href='styles/app.css')
body
block bodyElements
block
という見慣れないキーワードがありますが、その他はHTMLのタグ名そのものです。
Jadeではタグの括弧を書かずにインデントで表現します。
さてこのコード、 body
タグ内には一見何も書かれていません。
body // bodyタグ
block bodyElements // 謎のキーワード
Jadeではこの block
というキーワードを用いて、継承関係を作ることができます。
bodyElements
という名前のブロックを作っておいて、継承先に実装を任せているのですね。
それでは新しいjadeファイル(index.jade)を用意して、実際に bodyElements
の中身を書きましょう。
継承には extends
というキーワードを使用します。
extends _layout_ // _layout_.jadeを継承
block bodyElements // block bodyElementsの中身
section#todo // #todoはid="todo"のエイリアス
form
input(type='text' name='title')
input(type='text' name='description')
input(type='submit' value='Add')
script(src='./scripts/app.js')
これがいわゆる コンポーネント化 という考え方です。
新しいページを作るときは _layout_.jade
を継承すれば、 <head>
の中身は気にしなくて良いのです。
この2つのJadeファイルで出来上がったHTMLは、もちろん始めに書いたHTMLファイルそのものです。
参考
- Jade
http://jade-lang.com/ - Jade について。
https://gist.github.com/japboy/5402844 - Node.js - Jadeの記法について(あまりまとまっていない)
http://qiita.com/sasaplus1/items/189560f80cf337d40fdf
2. CSSを書く
HTMLがコンポーネント化できたところで、次はCSSを書いていきましょう。
まずは先ほど書いたHTMLをブラウザで見てみます。
タイトルと説明を入力するテキストボックスと、追加するボタンがあるだけですね。
何だか味気ないので、CSSを使って装飾しましょう。
まずはCSSを書きやすいように、 index.jade
の各タグにクラス名を付けておきます。
extends layouts/_layout_
block body
section#todo
form#form-todo
p.headline Title
input(class='form-title' type='text' name='title')
input(class='form-desc' type='text' name='description' placeholder='Description')
input(class='form-submit' type='submit' value='ToDo')
script(src='./scripts/app.js')
※注
クラスの命名規則に関しては、今回特に考慮していません。
普段はBEMと呼ばれる命名手法に則って書いていますので、興味のある方はそちらもご参照ください。
- BEMを参考にしたCSS設計
http://qiita.com/mrd-takahashi/items/07dc3b4bad027daa2884 - BEMという命名規則とSass 3.3の新しい記法
http://blog.ruedap.com/2013/10/29/block-element-modifier
少し長いですが、以下のように書きました。
section#todo {
font-family: sans-serif;
padding: 10px;
}
p.headline {
color: #40C4FF;
margin: 0;
}
input[type='text'] {
display: block;
outline: none;
border-top: none;
border-right: none;
border-left: none;
border-bottom: 1px solid #EEEEEE;
padding-bottom: 5px;
transition: all .3s ease;
width: 240px;
color: #424242;
}
input[type='text']:focus {
border-bottom: 1px solid #40C4FF;
}
.form-title {
font-size: 2rem;
}
.form-desc {
margin-top: 2rem;
font-size: 1rem;
}
.form-submit {
margin-top: 1rem;
font-size: 1rem;
padding: .5rem;
border: none;
outline: none;
color: #424242;
background-color: #EEEEEE;
cursor: pointer;
}
もう一度ブラウザで見てみましょう。
いい感じになりましたね。
ただし上記のCSSは危険なCSSです。
.form-title {
font-size: 2rem;
}
例えば上記のスタイルは全ての form-title
クラスについて適用されてしまいます。
つまり後で違うフォームを作成し、 form-title
クラスを付けてしまうと、上記のスタイルが適用されてしまいます(CSSあるあるですね)。
ではスコープを適切に設定すれば良いでしょうか?
section#form form .form-title {
font-size: 2rem;
}
これで問題なさそうです。
さてそれでは他のクラスについてもスコープを…
やっぱりめんどくさくないですか?
モダンの波に乗りたい!!
ここではAltCSS(CSSのメタ言語)の一つであるSassを使ってコーディングしてみます。
なお、SassにはSass記法とScss記法と呼ばれる2つの記述方法がありますが、Scss記法の方がもとのCSSの書き方に似ているので、そちらの方が書きやすいと思います。
それではさっそくコードを見てみましょう。
$text-color: #424242;
$accent-color: #40C4FF;
$dividers: #EEEEEE;
section#todo {
font-family: sans-serif;
padding: 10px;
p.headline {
color: $accent-color;
margin: 0;
}
input[type='text'] {
display: block;
outline: none;
border-top: none;
border-right: none;
border-left: none;
border-bottom: 1px solid $dividers;
padding-bottom: 5px;
transition: all .3s ease;
width: 240px;
color: $text-color;
&:focus {
border-bottom: 1px solid $accent-color;
}
}
form {
.form-title {
font-size: 2rem;
}
.form-desc {
margin-top: 2rem;
font-size: 1rem;
}
.form-submit {
margin-top: 1rem;
font-size: 1rem;
padding: .5rem;
border: none;
outline: none;
color: $text-color;
background-color: $dividers;
cursor: pointer;
}
}
}
クラスのスタイル定義が入れ子構造になっているのが分かりますね。
そう、Sassでは入れ子構造=スコープに対応しているのです。
つまり上記の .form-title
は、CSSに変換すると以下のように展開されます。
section#form form .form-title {
font-size: 2rem;
}
また、 app.scss
の初めの数行を見てください。
$text-color: #424242;
$accent-color: #40C4FF;
$dividers: #EEEEEE;
これはいわゆる変数定義です。色を変更したい時は、この行を変更するだけで一括して変えることができるわけですね。
なお、今回はミニマムなページのため、一つの .scss
ファイルしか作りませんでした。Sassにももちろんコンポーネント化するための便利な機能が備わっていますので、複数ページを作ったり、ページの情報量が多い場合は、CSSも積極的にコンポーネント化していきましょう。
参考
- Sass
http://sass-lang.com/ - CSSコーディングで泣かないためのSassの基礎知識と10の利点
http://www.atmarkit.co.jp/ait/articles/1402/17/news102.html
3. JavaScriptを書く
それではいよいよJavaScriptを書いていきましょう。
まずは先ほど書いた index.jade
ファイルに、ToDoリストを追加するための section
タグを用意しておきます。
extends layouts/_layout_
block body
section#todo
form#form-todo
p.headline Title
input(class='form-title' type='text' name='title')
input(class='form-desc' type='text' name='description' placeholder='Description')
input(class='form-submit' type='submit' value='ToDo')
section#list
// このタグ内にToDoの項目(liタグ)が追加されていく
script(src='./scripts/app.js')
フォームの submit
ボタンを押すと、この section#list
タグの中にToDoの項目が追加されるイメージです。
では、どうやって項目を追加すれば良いでしょうか?
もちろん appendChild()
といったメソッドを用いて追加することは可能です。
jQuery
を使用して、DOM要素を指定して、追加するDOMを用意して、 append()
を呼び出して…
めんどくさいですよね!
モダンの波に乗りたい!!!
ついに仮想DOMの出番です。
上記で色々とモダンな(一部モダンではない気もする)波に乗ってきましたが、この仮想DOMは間違いなくモダンな奴です(一年後はどうなっているか分かりません)。
乗るしかない、このビッグウェーブに。
仮想DOMを扱うjsフレームワークはいくつかあり、Reactが有名です。しかしながら、Reactは学習コストが少し高めということもあり、今回は学習コストが低めなRiot.jsを使ってみます。
というのもこのRiot.js、仮想DOMをJadeで書けるのです(!)。
では早速書いてみます。ビッグウェーブに乗りたいのでjsもES6で書きましょう。
※注 ES6については下記の素晴らしい記事をご覧ください…!
- ES6時代のJavaScript
http://techlife.cookpad.com/entry/2015/02/02/094607
'use strict';
import Riot from 'riot'
import Todo from './components/todo.jade'
Riot.mount('section#list', Todo);
app.js
がやっていることはただひとつ、Todoというコンポーネント(=仮想DOM)を section#list
タグ内に追加しているだけです。
Todoコンポーネントを見てみましょう。
TodoコンポーネントはJadeファイルですが、htmlのほかにjs(es6)のコードも含まれています。
todo
ul
li(each='{ todoList }')
div.title { title }
div.description { description }
script.
const self = this
self.todoList = []
self.on('mount', () => {
document.getElementById('todo-form')
.addEventListener('submit', onSubmit, false)
})
onSubmit = (e) => {
e.preventDefault()
let children = e.target.children
let title = children.title
let description = children.description
let todo = {
title: title.value,
description: description.value
}
self.todoList.push(todo)
self.update(self.todoList)
title.value = ''
description.value = ''
}
大事なのは
li(each='{ todoList }')
と
self.todoList.push(todo)
self.update(self.todoList)
です。
このコンポーネントはフォームイベントを受け取り、それぞれの内容(titleとdescription)をオブジェクトとして self.todoList
に追加(push)します。
その後 self.update(self.todoList)
によって、 self.todoList
の内容が変化したことを知らせます。
すると li(each='{ todoList }')
が評価され、self.todoList
の配列の長さ分だけ li
タグが生み出されるのです(それも自動で)!
それでは最終的な成果物を見てみましょう。
自動的にTodoが追加されることが確認できました!
参考
- Riot.js
http://riotjs.com/ - Riot.js 2.0 を触ってみた — まだReactで消耗しているの?
http://qiita.com/cognitom/items/fb1295f3f93911e9e92d - VirtualDom - なぜ仮想DOMという概念が俺達の魂を震えさせるのか
http://qiita.com/mizchi/items/4d25bc26def1719d52e6
おわりに
本記事ではJade/Sass/Riot.jsによる、仮想DOMを用いたモダンなフロント開発について、簡単な例を示しながら紹介しました。
フロントエンドの世界は本記事で紹介しきれない、新しくてわくわくするような技術がまだまだたくさんあります。
まずは小さな世界を覗いて、触れてみて、フロント開発の楽しさを知ってもらえれば幸いです。
私も一フロントエンドエンジニアとして、ますます精進していく所存です。