【TS】今さら聞けないタグ付きテンプレートリテラル
はじめに
いきなりですがJS
で以下のようなコードを見たことがないでしょうか?
const data1 = "test";
const data2 = "てすと";
hoge`ほげほげ${data1}ふがふが${data2}`
これは 「タグ付きテンプレートリテラル」 と呼ばれるもので、実態としてはhoge
という関数を呼び出しています。
よく見かけるのがReact
やReactNative
でstyled-components
を使っている時だと思います。
今回はこの「タグ付きテンプレートリテラル」について、内部的にどんな挙動をしているかや、どんな時に使うかをTypescript
のサンプルを交えて解説していこうと思います。
テンプレートリテラルとは
タグ付きの話をする前に 「テンプレートリテラル」 についておさらいします。
テンプレートリテラルはES2015
で追加された機能で、文字列中に変数を組み込む際に使用します。
通常、シングルクォートやダブルクォートで文字列を扱うかと思いますが、テンプレートリテラルではバッククォートで記載します。
const name: string = 'Zenn太郎';
// バッククォート``で囲い、変数部分は${}で記載する
const text: string = `おはようございます。 ${name} さん`;
console.log(text); // --> おはようございます。 Zenn太郎 さん
タグ付きテンプレートリテラル
MDN
の同じページに 「タグ付きテンプレートリテラル」 の記載があります。
以下、引用です。
タグ付きテンプレートは、テンプレートリテラルのより高度な形式です。
タグを使用すると、テンプレートリテラルを関数で解析できます。タグ関数の最初の引数には、文字列リテラルの配列を含みます。残りの引数は式に関連付けられます。
タグ関数は、これらの引数に対して何でも望み通りの操作を実行することができ、加工された文字列を返します。 (または、以下の例の一つで示しているように、まったく異なるものを返すこともできます。)
ポイントは 「テンプレートリテラルを関数で解析」 できるという点です。
通常のテンプレートリテラルは指定されたフォーマットに従って文字列を返すだけですが、解析を行うことで全く別な値を返すこともできます。
呼び出し方は冒頭のコードのように関数名の後にテンプレートリテラルを記載して呼び出します。
tag`hogehoge${fuga}`;
どういう時に使う?
先ほどのテンプレートリテラルの例の場合、name
が空文字だった場合は以下のように表示されてしまいます。
おはようございます。 さん
この内容を画面に表示することを考えた場合、name
が空の場合は「何も表示させない」方が正解のように思えます(もしくは何かしらの警告を表示する等です)。
このように 「テンプレートリテラル中の変数部分を解析して何らかの処理をしたい」 場合にタグ付きテンプレートリテラルを利用します。
タグ付きテンプレートリテラルの書き方
関数として通常通り定義します。
この時、引数として2つの値が与えられます。
1つ目はTemplateStringsArray
型の値で、これは 「テンプレートリテラルの固定文字部分」 を区切った配列です。
先の例のおはようございます。 ${name} さん
の場合は["おはようございます。 ", " さん"]
という2つの要素を持った配列になります。
2つ目はany
型の配列で 「テンプレートリテラルの変数部分」 の配列になります。
先の例では["Zenn太郎"]
のような値が入ってきます。
TemplateStringsArray
はテンプレートリテラルを「変数部分」で区切ったものになります。
従って${hoge}てすと${fuga}テスト${puni}
というテンプレートリテラルの場合は第一引数と第二引数の値はそれぞれ以下のようになります。
index=0 | index=1 | index=2 | index=3 | |
---|---|---|---|---|
第1引数 | "" | "てすと" | "テスト" | "" |
第2引数 | hoge | fuga | puni | なし |
タグ付きテンプレートリテラルを使って書き換える
先ほど例を書き換えていきます。
greet
という関数を定義します。上記の解説にある通り引数は2つです。
第2引数のvalues
内にname
の値が格納されているので、これが空なら空文字をreturn
します。
そうでない場合は通常通りテンプレートリテラルの出力結果を返します。
第1引数も第2引数も 「テンプレートリテラルの中身を分割した配列」 なので、index
が若い順に結合していきます。
const greet = (hashes: TemplateStringsArray, ...values: string[]): string => {
// values[0]が空なら空文字を返す
if(!values?.[0]) return '';
// 正常系は従来通りのテンプレートリテラルの結果を返せば良い
// hashes[0] -> values[0] -> hashes[1]のように結合していく
return values.map((value, index) => hashes[index] + value)
.concat(hashes.slice(values.length))
.join("")
}
name
の値がある時と無い時で比較してみましょう。
無い場合はコンソールになにも表示されなくなりました。
const name1: string = 'Zenn太郎';
const text1: string = greet`おはようございます。 ${name1} さん`;
console.log(text1); // --> "おはようございます。 Zenn太郎 さん"
const name2: string = '';
const text2: string = greet`おはようございます。 ${name2} さん`;
console.log(text2); // --> ""
まとめ
今回はTypescript
の実装例を見ながら 「タグ付きテンプレートリテラル」 について解説しました。
通常のテンプレートリテラルは知っているけど、こちらは馴染みが無い方も多いのではないでしょうか。
その理由のひとつに、使用されるケースが限定的であるということがあると思います。
実際、目にするのはstyled-components
のようにライブラリの仕様として使われているケースがほとんどなので、自作するケースは稀だと思います。
いざ使うようになった時にこの記事が参考になれば幸いです。
Discussion