読者です 読者をやめる 読者になる 読者になる

ES6テンプレートリテラルをテンプレート関数化する

V8にES6テンプレートリテラルが入ったらしいということで、

先に入っているFirefox 34(現beta)で遊んでみた。

埋め込み変数は即時評価

埋め込み変数は即時評価なので、テンプレートリテラルが評価される時点で定義されない変数を埋め込みに使うとエラーになってしまう。

var name = 'Taro';
console.log(`Hello, ${name}.`); // 'Hello, Taro.'
console.log(`Hello, ${hoge}.`); // ReferenceError: hoge is not defined'

そうすると、Viewクラスのプロパティにテンプレートを持っていて任意のタイミングで呼ぶみたいなことができず、同じテンプレートでも使うところで毎回リテラルを書く必要がある*1

// Viewクラスみたいなところでこういう使い方をやりたい。
this.template = `<span class="username">${name}</span>`;
this.$el.html(this.template('Taro'))

タグ付きテンプレートでテンプレートを関数化

Tagged Templatesを使うとテンプレートを引数に取れる関数を定義できるので、こういう関数を定義すれば上記のようなことができる。

function tmpl(s) {
  return function() {
    return String.raw.apply(null, [s].concat(Array.from(arguments).map(htmlEscape));
  }
}

// ここでタグ付きテンプレートを使う
this.template = tmpl`<span class="username">Hello, ${0}</span>`;
this.$el.html(this.template('Taro'));

htmlEscapehtmlspecialcharsみたいなHTMLエスケープ関数です。

もう少し凝ってみる

複数変数になったとき、テンプレートの変更で呼び出し部分が変わるのは困るのでもう少し凝ってみる。

引数の順序で制御

変数を0から始まる数字リテラルで定義して、呼び出し引数の順序で埋め込み変数を指定する。

function tmpl2(s) {
  var args = Array.from(arguments).slice(1);
  return function() {
    var vars = arguments;
    return String.raw.apply(null, [s].concat(args.map(n => vars[n]).map(htmlEscape)));
  };
}

this.template = tmpl2`<span>${0}は本を${1}冊持っています。</span>`;
this.$el.html(this.template('Taro', 5));

これなら国際化や仕様変更で語順を変えたい場合でも、テンプレート側の指定で制御できる。

this.template = tmpl2`<span>本を${1}冊持っている${0}です。</span>`;

変数に名前をつける

次はデータをオブジェクトにして名前をつけてみる。

function tmpl3(s) {
  var args = Array.from(arguments).slice(1);
  return function(data) {
    return String.raw.apply(null, [s].concat(args.map(key => data[key]).map(htmlEscape)));
  };
}

this.template = tmpl3`<span>${'user'}は本を${'books'}冊持っています。</span>`;
this.$el.html(this.template({user: 'Taro', books: 5}));

テンプレートが冗長な感はいなめない。

やってみたけど

ES6テンプレートリテラルってこの目的に使うものではないのかも。そもそも何か勘違いをしているのか。

あとString.rawは連結に便利な組み込み関数という理由で使ったけど、HTMLに使う分には別にrawである必要は無いと思う。

*1:ただしテンプレート文字列のキャッシュはされるのでパフォーマンス上は問題無いっぽい