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
埋め込みにフィルタ
途中に 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])
}
おわりに
どのパターンもイマイチ使おうとは思えないです。
今後も思いついたらメモ感覚で追加しようと思ってます。