JavaScript
es6
TemplateLiterals

タグ付きテンプレートリテラルで遊ぶ

More than 1 year has passed since last update.

Tagged Template Litterals とは

簡単に説明すると以下がそれぞれ等価です。

fn`this is a ${aVar} day`;
fn([ 'this is a ', ' day' ], aVar);
fn`this is a ${aVar} day${'!'}`;
fn([ 'this is a ', ' day', '' ], aVar, '!');

詳細は以下を見てください。
MDNドキュメント: タグ付けされたTemplate literal


正直使いどころは思い浮かばないです。
知っている限りだと styled-components ではめっちゃ便利に感じてます。

どんなことが出来るのか考えてみたことを書いておきます。

関数の generator

styled-components と似たパターン

function embedFuncBuilder(parts, ...funcs) {
  return params =>
    funcs.reduce((p, c, k) => p + c(params) + parts[k + 1], parts[0]);
}

使う時

const githubURL = embedFuncBuilder`https://github.com/${p => p.name}`;

githubURL({ name: "elzup" });
// => https://github.com/elzup
const explodeHeader = p =>
  Object.keys(p.headers)
    .map(k => `-H '${k}: ${p.headers[k]}'`)
    .join(" ");
const curlCommand = embedFuncBuilder`curl -X ${p => p.method} ${p => p.url} ${explodeHeader}`;

curlCommand({
  method: "GET",
  url: "https://elzup.com",
  headers: { HOGE: "v1", FUGA: "v2" }
});
// => curl -X GET https://elzup.com -H 'HOGE: v1' -H 'FUGA: v2'

リテラルの部分を変数として保持できないのでありがたみがない。

Flow版

// @flow
'use strict'

type EmbedFunc<T> = T => string

function embedFuncBuilder<T>(
    parts: string[],
    ...funcs: EmbedFunc<T>[]
): EmbedFunc<T> {
    return params =>
        funcs.reduce((p, c, k) => p + c(params) + parts[k + 1], parts[0])
}

type Args = { name: string }

const githubURL: EmbedFunc<Args> = embedFuncBuilder`https://github.com/${p =>
    p.name}`

githubURL({ name: 'elzup' })
// => https://github.com/elzup

Fullscreen_2017_10_26_1_06.png
引数の型付けは効かせられそうです。

埋め込みにフィルタ

途中に map をかませる

function mapTemplate(callback) {
  return (parts, ...vals) =>
    vals.reduce((p, c, k) => p + callback(c) + parts[k + 1], parts[0]);
}
const v = {
  a: 0,
  b: 200,
  c: 23
};

const fillTemplate = mapTemplate(v => ("00" + v).slice(-3));
const doubleTemplate = mapTemplate(v => v * 2);

console.log(`a: ${v.a}, b: ${v.b}, c: ${v.c}`)
// => a: 0, b: 200, c: 23
console.log(fillTemplate`a: ${v.a}, b: ${v.b}, c: ${v.c}`)
// => a: 000, b: 200, c: 023
console.log(doubleTemplate`a: ${v.a}, b: ${v.b}, c: ${v.c}`)
// => a: 0, b: 400, c: 46
console.log(mapTemplate(v => -v)`a: ${v.a}, b: ${v.b}, c: ${v.c}`)
// => a: 0, b: -200, c: -23

PrintFormat 関数的なもの

Python2?に似てる

function format(arr) {
  return (parts, ...vals) =>
    vals.reduce((p, c, k) => p + `${arr[c]}` + parts[k + 1], parts[0]);
}
console.log(format([1, 2, 3])`${0} + ${1} - ${0} = ${2} - ${0}`)
// => 1 + 2 - 1 = 3 - 1
console.log(format(['nya-'])`(Ф∀Ф)${0}${0}`)
// => (Ф∀Ф)nya-nya-

拡張する際のUtil

ここまで書いてきて reduce で同じような処理を書いています。
Tagged Template Literals で生まれる引数の特殊なパターンのせいです。
前述の mapTemplate 関数っぽく Array.reduce の Wrap 関数を用意するとそれぞれ簡潔に書けそうです。

function reduceJoin(callback) {
  return (parts, ...vals) => {
    return vals.reduce(
      (p, c, i, a) => p + callback(p, c, i, a) + parts[i + 1],
      parts[0]
    );
  };
}


// Flow type
function reduceJoin<T>(
    callback: (
        previousValue: string,
        currentValue: T,
        index: number,
        array: any[]
    ) => string
) {
    return (parts: string[], ...vals: T[]) => {
        return vals.reduce(
            (p, c, i, a) => p + callback(p, c, i, a) + parts[i + 1],
            parts[0]
        )
    }
}

例えば "PrintFormat的なの" は以下の用に書けるようになりました。

function format(arr) {
    return (parts, ...vals) =>
        vals.reduce((p, c, k) => p + `${arr[c]}` + parts[k + 1], parts[0])
}

// ↓

function format(arr: any[]) {
    return reduceJoin((_, v) => arr[v])
}

おわりに

どのパターンもイマイチ使おうとは思えないです。
今後も思いついたらメモ感覚で追加しようと思ってます。