この連載ではGutenbergがリリースされる前にやっておくべきことをまとめているのだが、Gutenbergがもたらしたもっとも大きな変更点として、Reactの導入がある。これにより、次の新しい要素が導入される。
- ES6/ES Next
- React + JSX
さて、以上の概念について慣れ親しんでいる人は特に驚きはないだろうが、これまでjQueryぐらいでしかJavaScriptを学んでこなかった方は本稿を読んで今後の参考にしてほしい。
TL;DR
- ES6とは、新しいJavaScriptだが、ブラウザ互換性のために色々工夫する必要がある。
- WordPressではES5による記法を提供しているが、ES6(ESNext)で書くべきである。
- 挫折した人はツール類の登場を待つか、Webアセンブラーに転職しよう。
ちなみに、TL;DRとは、”Too Long, Don’t Read”の略で、長文ブログの前につけるまとめをはにかんで表現するもの。なんとなくかっこいいので、使う人が多い。
それではさっそく本文に入る。
ES6 / ESNextとは
ES6とは、ECMA Script Ver.6の略。ECMA ScriptはJavaScriptっぽい言語の共通仕様であるが、実装として使われるのはほぼJavaScriptしかないので、JavaScriptの仕様だと考えてもらって間違いないだろう。現在は呼び方を変え、ES2015などの年をバージョンとして付与する形式になっている。
現在のブラウザで共通して利用できるのはES5である。したがって、そのための後方互換性を取る必要がある。ES5とES6でめちゃくちゃ変わったので、ES6以降のバージョンの総称として、ESNextという呼び方がある。次のスライドが詳しいので、目を通しておいてほしい。
BabelによるES6のトランスパイル
ES6で書かれたJavaScriptをES5でも動くようにすることが必要と書いたが、この処理をトランスパイルと呼ぶ。その他、似たような概念としてポリフィルというものがある。具体的には次のような処理を示す。
// ES6のアロー関数を…… $(document).ready(() => { console.log('Hello World') }) // ↓ // 多くのブラウザが理解できる無名関数に変換する $(document).ready(function(){ console.log('Hello World'); });
この処理を行うのがトランスパイラである。有名なものはいくつかあるが、Gutenbergで採用されたのは、Babelである。
Babelは次のようにコマンドラインで使うツールである。
# srcのjsをdistフォルダにトランスパイルして展開 babel src --out-dir dist
もちろん、NPMスクリプトとしても使えるし、Gulp経由で使うこともできるので、上記のコマンド自体はいったん忘れてもらって構わない。とりあえず、BabelというものがES6をいい感じにしてくれると覚えておこう。後述するJSXについても、同様にBabelがいい感じに変換してくれる。
モジュールローダー
ES6ではサーバーサイドスクリプトであるNodeJSから導入された「モジュール」という機能がある。これは機能の塊を1つのJavaScriptファイルに分割するような書き方だ。たとえば、給与計算を行うライブラリを salary.js
として切り出すような形だ。
// salary.js export function record(user, hours){ // 指定されたユーザーの時給を保存する関数 return true; } export function getTotal(user){ // 指定されたユーザーの給料総額を返す return salary; }
そして、これを利用するmain.js
では、次のようにして呼び出すのがESNextの仕様だ。
// モジュールからrecord関数をインポート import {record } from 'salary'; // ボタンを押すたびに時給をアップ $('.button').click(()=>{ record(currentUser, 1); });
これまでのJavaScriptでは、head
タグ内や終了body
タグの直前にscript
タグを書き込むことで依存関係を自力解決していた。WordPressではwp_enqueue_scripts
という関数があり、この読み込み順を管理する機能を提供している。
ES6ではこのようなアプローチを取らず、パッケージングすることでそれぞれのJavaScriptファイルが他のJavaScriptに影響を与えないような手法を取る。このパッケージングを代替してくれるバンドラーが、webpackだ。
たとえば、上記のsalary.js
を読み込んだmain.js
をwebpackでバンドルしたものは次のようになる。
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";n.r(t);var r={};$(".button").click(function(){!function(e,t){r[e]+=t}(1,1)})}]);
難読化されていてなにがなんだかわからないが、とにかく、このような作業が必要になる。いったんまとめよう。
- ES6/ESNextおよびReactのJSXといった新しい仕様はBabelを使っていい感じにする。
- モジュールによる読み込みはwebpackによって解決する。
それでは、重複する部分もあるが、新しいJavaScriptの書き方をみていこう。
ES6の新しい記法
ES6ではいくつもの新しい記法が導入された。自分で書く分には古い書き方(ES5)でも構わないのだが、他人の書いたソースを読めなくなってしまう。以下の概念を理解しよう。
letとconst
JavaScriptには変数をvar
で宣言していたが、これはif
やfor
のブロックスコープ内で使えなかったので、新たにlet
が追加された。「基本的にはvar
ではなく、let
を使う」と覚えておこう。
// ES5 var hoge = 'fuga'; // ES6 let foo = 'var';
また、JavaScriptに存在しなかった定数としてconst
が導入された。これは上書きするとエラーになるので、「一度定義したら変更しないもの」として理解しよう。
// 定数宣言 const MAX_PAGE = 20; // 上書きするとエラー MAX_PAGE = 10;
アロー関数
無名関数はアロー関数によって書けるようになった。
// ES5 $(document).ready(function(){ // なにかする }); // ES6 $(document).ready(()=>{ // なにかする });
なお、アロー関数は波括弧を省略すると、「中身をreturn」すると解釈される。これはmap関数などで使われる。
// 偶数の配列を定義 let evens = [2, 4, 6, 8] // 配列の要素にそれぞれ1を足して、奇数の配列を作成 let odds = evens.map((number, index) => number + 1)
関数の引数のデフォルト値
JavaScriptで引数を指定しなかった場合、単にundefinedとなるだけだったが、ES6からデフォルト値を指定できるようになった。
// 数字をn乗する。デフォルトは2乗。 function pow(number, exponent = 2){ return Math.pow(number, exponent); }
可変長引数
引数が何個くるかわからない場合、可変長引数として定義できる。ドットが3つ並ぶので、はじめは面食らう。
function sprintf(string, ...args){ args.map((replacer, index) => { string = string.replace('%' + index, replacer); }); return string; }
分割代入
聞きなれない言葉だが、ときおり波括弧で指定された記法を見かけることがあるが、これは「分割代入」というオブジェクトのプロパティなどを取得するための記法である。PHPのlistと似た機能だろうか。
const apple = { color: 'red', price: 100 }; // 上記のオブジェクトを分割代入 const { color, price } = apple; console.log(color); // -> 'red' console.log(price); // -> 100
この分割代入は関数の引数などで見かける。
function zeikomi({price}){ return price * 1.08; } console.log(zeikomi(apple)); // -> 108
文字列挿入
バックティックで文字列を書いた場合、中に変数を埋め込むことができるようになった。
const PI = 3.141592; // ES5 console.log('円周率は' + PI + 'です。'); // ES6 console.log(`円周率は${PI}です。`);
改行を含むテキストも書くことができるので、格段に楽になる。
Classのサポート
JavaScriptでクラスが正式にサポートされた。Reactを利用する場合、コンポーネントはReact.Component
のサブクラスなので、理解しておいた方が良いだろう。
// Shapeクラスを定義 class Shape { // コンストラクタ constructor (id, x, y) { this.id = id this.move(x, y) } // メソッド move (x, y) { this.x = x this.y = y } } // Shapeクラスを継承 class Circle extends Shape { constructor (id, x, y, radius) { super(id, x, y) // 親クラスのコンストラクタを呼び出す this.radius = radius } }
モジュールのimport/export
モジュールはすでに前述したが、読み込むJSを書く場合と、読み込まれるJS(ライブラリなど)を書く場合で勝手が違う。ライブラリなどを書く人は中級者以上だと思われれるので、まずは読み込む側のimportから。
// ライブラリから特定のオブジェクトだけを読み込み import {PI, Radian} from 'my-math';
MDNに様々な記法がまとまっているので、一度目を通しておこう。続いて読み込まれる場合だが、こちらは主にexportを利用する。
// ライブラリlib.jsで名前なしのエクスポート export default function() { // なにかする } // 読み込む側ではどんな名前でもオーケー import util from 'lib'
より複雑なケースでは、名前付きのエクスポートを行う。
// lib.jsにいろいろな機能を用意 function hi(){ return 'Hi!'; } const LANG = 'en_US' function seeYou(){ return 'See you again!'; } // エクスポート export { hi, LANG, seeYou } // main.js // 読み込む側では、選択して読み込むことができる。 import {seeYou} from 'lib' console.log(seeYou()); // -> See you again!
Reactのコンポーネントをたくさん作るようになってくると、export
を上手に使いこなせる必要がある。
その他、様々な新機能が存在するのだが、ES6の新機能をチェックするサイトes6-features.orgなどで確認しよう。日本語情報としては、MDN Developersが一番わかりやすい。
ReactとJSX
Reactについては、日本語訳されたチュートリアルが存在するので、まずはそちらを試してほしい。これは今後の連載でも紹介していくので、Reactそれ自体の利用方法よりも、JSXについて学んでおこう。なお、DeNAが開発したTypeScriptっぽいAltJSであるJSXも存在するが、これは別物。
JSXとは?
JSXはReactで利用される拡張構文である。Reactの公式ページに記載があるように、JavaScript内に直接タグを書くような記法が使われる。
const element = <h1>Hello, world!</h1>;
はじめて見た人は「やだキモい!」となってしまいがちだが、筆者はFlashで使われていたActionScriptでよくこのようなXMLリテラルを書いていたので、あまり違和感はなかった。なお、この仕様E4Xは廃止されている。
var person = <person> <name>Bob Smith</name> <sex>male</sex> </person>;
話を戻すと、JSXはあくまで拡張構文であり、それは最終的にJavaScriptに変換される。Babelの設定でReact拡張を読み込むと、JSXの部分をよしなに変換してくれるのだ。
JSXの使い方
JSXはReactにおいて特定の利用方法を持つ。それはReactコンポーネント(この作り方は次回以降の連載に譲る)において、要素を描画するrender
関数で使われることが多い。次のような感じだ。
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
Reactコンポーネントはprops
というプロパティがあり、属性値を持つことができる。このコンポーネントは別のReactコンポーネントから次のようにJSX内でタグとして書くことができる。
class Greeting extends React.Component { render() { return ( <div class="greeting"> <Welcom name="Cappy" /> <p>My name is {props.name}</p> </div> ); } }
このように、コンポーネント化することによって、Webアプリケーション内部で再利用可能な部品がたくさん作れる。先述したimport/exportを駆使することで、再利用可能なUI作りが実現できるだろう。
JSXの文法
JSX自体にはそれほど文法の縛りはなく、以下の基本的なルールを押させておけば大丈夫だろう。
- 波括弧で囲まれた部分はJavaScriptとして評価される。例:
{props.name}
- コンポーネントはアッパーキャメルケース(例:
<MyCustomComponent>
)、普通のHTMLタグは小文字で書く。 - JSXのタグとHTMLタグはイコールではなく、属性値の書き方が異なる。たとえば
class
属性はclassName
と書く。
JSXのメリットとは?
メリットがどうか以前に、JSXを使わない書き方が大変にめんどくさいのである。
// JSXの場合 class Hello extends React.Component { render() { return <div>Hello {this.props.toWhat}</div>; } } // JSXなしの場合 class Hello extends React.Component { render() { return React.createElement('div', null, `Hello ${this.props.toWhat}`); } }
こんなめんどくさい書き方をするぐらいなら、多少気持ち悪くてもJSXを採用した方がましだろう。
実際の利用のされ方
さて、これまでで新しいJavaScriptに関するファーストステップについては説明し終えた。あとは実際に開発環境を整えるだけなのだが……この環境整備がかなり大変である。筆者が大変好きなはてなブックマークの名言に「はてブ見てみろや。あいつら永遠にvimの設定やってるぞ」というのがあるのだが、こうした環境設定に費やす時間というのはバカにならない。
そこで、参考になりそうな情報をいくつか紹介しよう。
その1. Create Guten Blockを利用する
create-buten-blockはWordPressコミュニティで活発に活動しているAhmad Awaisという人が作ったライブラリである。Gutenbergのカスタムブロックを作るために必要なものが一通り揃う。スタートアップスクリプトといった印象だ。
その2. Gutenberg Meetupの主催者を参考にする
東京で不定期開催しているGutenberg Meetupの主催者@ryo511が開発環境セットアップを紹介している。これを参考にすると、Babel, webpackなどがどのような役割を果たしているかが理解できるだろう。
その3. Capital Pのやり方を参考にする
Capital Pでも前回の連載でGutenbergのカスタムブロックを導入している。Capital Pのpackage.jsonとgulpfile.jsを参考にすれば、同じ構成にできるはずだ。基本的には src/js
フォルダ内のjsxファイルすべてが assets/js
フォルダにJSとしてトランスパイルされる構成になっている。
まったくついていけそうになかったら
さて、ここまでさっぱりわからなかったという人もいるだろう。確かにその気持ちもわからないではない。Reactが便利というのと、jQueryが便利というのは、その意味合いがまったくことなるからだ。
もしあなたがそれほどJavaScriptに明るくなく、今後もそれに注力したくないのであれば、無理してGutenbergのカスタムブロックに向き合う必要はないだろう。もちろん、カスタマイズの要求などには答えられなくなってしまうのだが、様々なブロックがリリースされることを考えられる。
DrupalもGutenbergを採用することに決めたようだし、DrupalチームがGutenberg Cloudという「ブロック専用リポジトリ」を開発中のようだ。
そうなると、エコシステムもこれまでとは一変するだろう。ブロック自体を作ることはできなくても、様々なブロックを組み合わせてサイトを作る仕事は残りそうだ。以前言及したWebアセンブラー的な仕事である。
*
さて、長くなってしまったが、今回紹介したES6およびJSXの新しい機能を理解した上で、次回以降のカスタムブロックに取り掛かろう。