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

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


おわりに

どのパターンもイマイチ使おうとは思えないです。

今後も思いついたらメモ感覚で追加しようと思ってます。