【初学者向け】テンプレートリテラルの基本と発展的な使い方!タグ付きテンプレートとは?

JavaScript

はじめに

テンプレートリテラルは、ES6で導入された便利な機能の一つです。テンプレートリテラルを使用することで、複雑な構造を持つ文字列を可読性の高い表現で定義することができます。テンプレートリテラルの基本的な使い方は簡単ですが、発展的な使い方もあって初学者向けとはいいにくい機能があります。

この記事では、テンプレートリテラルの基本的な使い方と注意点について解説します。また、テンプレートリテラルの発展的な使い方である「タグ付きテンプレート」の仕組みについて解説し、タグ付きテンプレートがどのようなケースで利用できるかについて、サンプルコードを例に挙げてご紹介いたします。

この記事の対象者

  • JavaScript初学者の方
  • 「“」と「” / “”」の違いについて知りたい方
  • `の前のキーワードが分からない方

この記事で学べること

  • テンプレートリテラルの基本的な使い方
  • テンプレートリテラルの注意点
  • タグ付きテンプレートとは何か
  • タグ付きテンプレートの使いどころ

テンプレートリテラルの基本

テンプレートリテラルはバッククォート(“)で表現され、この中にプレースホルダー(${式})を記述することで、変数や計算結果、関数の戻り値などを文字列内に展開できます。

基本的なテンプレートリテラルの使い方を見ていきましょう。

変数の展開

ダブルクォート(”)やシングルクォート(’)で定義する通常の文字列リテラルでは、文字列内で変数を展開することができません。よって、文字列内で変数の値を扱いたい場合、基本的には文字列連結で文字列と変数を区切って表現します。

const name = 'Taro';

// 文字列を連結する
console.log('Hello, ' + name + '.').
// 結果:Hello, Taro.

テンプレートリテラルでは、プレースホルダー(${式})を使用することでテンプレートリテラル内に変数を展開することができます。テンプレートリテラルはバッククォートで表現しますが、シングルクォートと見分けがつきにくいので注意です。

const name = 'Taro';

// テンプレート文字列
console.log(`Hello, ${name}.`);
// 結果:Hello, Taro.

複数行の文字列を表現

通常の文字列では、文字列内に改行を含めることができません。改行を含める文字列を定義する場合には「\n」を記述することで実現できます。コードの可読性を考慮して\nを文字列連結で使用する場合もあります。

// 一行だと読みづらい
const multiLineText1 = 'This is\na multi-line\ntext using\traditional strings. ';

// コード上で改行したいなら文字列連結を使う
const multiLineText2 =
'This is\n' +
'a multi-line\n' +
'text using\n' +
'traditional strings.';

テンプレートリテラル内では、改行やスペースをそのまま保持することができます。\nのようなエスケープシーケンスが不要なので可読性が高くなります。

const multiLineText = 
`This is
a multi-line
text using
template strings.`;

console.log(multiLineText);
/*
結果:
This is
a multi-line
text using
template strings.
*/

動的な文字列の作成

テンプレートリテラル内には変数だけでなく式も記述でき、関数の呼び出しや値の計算、三項演算子で条件分岐などを実装することで、動的な文字列をシンプルな表現で定義することができます。

// 計算式を埋め込む
const a = 10;
const b = 5;
const result = `${a} + ${b} = ${a + b}`;
console.log(result);
// 結果:10 + 5 = 15

// 三項演算子で条件分岐する
const age = 25;
const message = `You are ${age >= 18 ? 'an adult' : 'a minor'}.`;
console.log(message);
// 結果:You are an adult.

// 関数の結果を展開する
function greet(name) {
  return `Hello, ${name}!`;
}

const userName = 'Taro';
console.log(`${greet(userName)} How are you today?`);
// 結果:Hello, Taro! How are you today?

テンプレートリテラルの基本的な使い方は以上です。このように、テンプレートリテラルは通常の文字列による文字列連結に比べて冗長さが減り、可読性が高くなることが分かるかと思います。

テンプレートリテラルの注意点

テンプレートリテラルはとても便利で強力な機能ですか、いくつかの制約があるため注意も必要です。これらの制約を理解することで、効果的にテンプレートリテラルを活用できます。

インデントも文字列として含まれる

テンプレートリテラルで複数行の文字列を定義する場合、行の先頭にインデント(字下げ)を挿入するとそのインデントも文字列として含まれます。そのため、テンプレートリテラル内のインデントを揃えると出力結果にも影響します。

const indentedString =
` This is
  a multiline
  string.
`;

console.log(indentedString);
/*
出力:
  This is
   a multiline
   string.

同様に、先頭や末尾の改行も文字列として含まれるため、テンプレートリテラルの文字列の長さを計算するときや文字列を配列に変換するときは改行の有無を考慮する必要があります。

const stringWithLineBreaks  = `
Test
`;

console.log(stringWithLineBreaks.length);
// 結果:6

console.log(Array.from(stringWithLineBreaks).join(','));
// 結果:\n,T,e,s,t,\n

プレースホルダーに記述できるのは式のみ

プレースホルダー内に記述できる内容は式のみであり、for文やif文などの制御構造は直接的には使用できません。

const condition = true;
const conditionalExpression = `${ if (condition) { 'foo' } else { 'bar' } }`;
// SyntaxError: Unexpected token 'if'

const loopExpression = `${ for (let i = 0; i < 3; i++) { i } }`;
// SyntaxError: Unexpected token 'for'

条件分岐は三項演算子を利用し、ループ処理は配列化してforEach()やmap()メソッドなどを使用することで実現することができます。

const condition = true;
const conditionalExpression = `${ condition ? 'foo' : 'bar' }`;
console.log(conditionalExpression);
// 出力: foo

const loopExpression = `${ [1, 2, 3].map(num => num * 2) }`;
console.log(loopExpression);
// 出力: 2,4,6

バッククォートには対策が必要になる

テンプレートリテラルはバッククォートで表現するため、テンプレートリテラル内の文字列にバッククォートが記述されている場合は対策が必要です。例えば、バッククォートをエスケープするか、文字列リテラルを使用するなどの方法が考えられます。

// `をエスケープする
const stringWithBackticks = `\`This is a string with backticks\``;

// 文字列リテラルを使用する
const stringWithBackticks = "`This is a string with backticks`";
const templateLiteral = `${stringWithBackticks}`;

以上の制約に注意することで効果的にテンプレートリテラルを使用することができます。

タグ付きテンプレートについて

テンプレートリテラルは関数と組み合わせて使用することができます。この際に使用する関数を「タグ関数」といい、タグ関数が付いたテンプレートリテラルを「タグ付きテンプレート」といいます。タグ付きテンプレートは少し発展的なテンプレートリテラルの使い方になりますが、目にすることも多く可読性や保守性において向上する効果が期待されます。

タグ付きテンプレートの基本的な構文は以下です。

tagFunction`Template string.`;

「tagFunction」がタグ関数の名前です。タグ付きテンプレートでは、テンプレートリテラルで表現された文字列が関数の引数として渡されます。

タグ関数は後ろに続くテンプレートリテラルを解析し、その内容を処理するための仕組みを実装することができます。解析された文字列を任意の方法で操作し、新しい文字列やデータ構造を返すことができます。

タグ関数の構造を見てみます。

function tagFunction(strings, ...values) {
  // strings: 文字列を格納する配列
  // ...values: プレースホルダーを格納する配列
  // タグ関数の処理
  // ...
  // 処理結果を返す
}

タグ関数の第一引数

タグ関数の第一引数には、テンプレートリテラルの静的な部分を含む配列が格納されます。また、この配列の末尾にはrawという配列を値に持ったオブジェクトが格納されます。rawオブジェクトの配列にはエスケープシーケンスを処理する前の文字列が格納されています。

function tagFunction(strings) {
  return strings;
}

const msg = tagFunction`Hello. I'm Taro.`;

console.log(msg);
// 結果:['Hello. I'm Taro.', raw: Array(1)]

テンプレート文字列内にプレースホルダーを追加してみましょう。

function tagFunction(strings) {
  return strings;
}

// プレースホルダーを追加
const name = 'Taro';
const msg = tagFunction`Hello. I'm ${name}.`;

console.log(msg);
// 結果:["Hello. I'm ", '.', raw: Array(2)]

上記のように、テンプレートリテラル内にプレースホルダーがある場合はプレースホルダーの位置で文字列が分割されて第一引数の配列に格納されます。プレースホルダーが複数ある場合も同様です。

function tagFunction(strings) {
  return strings;
}

const name = 'Taro';
const age = 20;
const msg =
tagFunction`Hello. I'm ${name}.
I am ${age} years old.`;

console.log(msg);
// 結果:["Hello. I'm ", '.\nI am ', ' years old.', raw: Array(3)]

このように、第一引数にはテンプレートリテラルの静的な文字列が配列の要素として格納されます。しかし、この配列にはプレースホルダーの値は含まれません。プレースホルダーの値を取得するには、タグ関数に第二引数を指定する必要があります。

タグ関数の第二引数

プレースホルダーの値を取得するにはタグ関数に第二引数を指定します。

// 第二引数を追加してその値を返します
function tagFunction(strings, ...placeholders) {
  return placeholders;
}

const name = 'Taro';
const age = 20;
const msg =
tagFunction`Hello. I'm ${name}.
I am ${age} years old.`;

console.log(msg);
// 結果:['Taro', 20]

上記はスプレッド構文を用いることでプレースホルダーの値を配列として扱っていますが、その必要がない場合は「第二引数、第三引数,,,」のように引数を追加することで複数のプレースホルダーの値にアクセスできます。

// 以下に変更
function tagFunction(strings, name, age) {
  return `name: ${name}, age: ${age}`;
}

const name = 'Taro';
const age = 20;
const msg =
tagFunction`Hello. I'm ${name}.
I am ${age} years old.`;

console.log(msg);
// 結果:name: Taro, age: 20

タグ付きテンプレートを使用することで文字列の解析や処理を柔軟にカスタマイズすることができ、文字列のフォーマット変換、セキュリティ検証、国際化といった処理を実装する際に役立ちます。

タグ付きテンプレートの利用例

タグ付きテンプレートがどのように利用できるかについて解説します。前述のとおり、タグ付きテンプレートを利用することで文字列のフォーマット変換、セキュリティ検証、国際化といった処理を実装することができます。

文字列のフォーマット変換

タグ関数を使用して文字列を特定のフォーマットに変換することができます。例えば、日付や数値のフォーマットを指定したり、特定の文字列の大文字化や小文字化を行うことができます。ここでは、例として以下の3つのフォーマット変換を行う例をご紹介します。

  • 現在の日付を英語表記でフォーマット化
  • 数値を通貨表記でフォーマット化
  • バイトサイズをKB、MB、GB、TBにフォーマット化

以下は現在の日付を英語表記でフォーマット化する例です。

function formatDate(strings, date) {
  const formattedDate = new Intl.DateTimeFormat(
    'en-US'
  ).format(date);

  // フォーマット済みの日付を挿入して返す
  return `${strings[0]}${formattedDate}${strings[1]}`;
}

const today = new Date();
const formattedToday = formatDate`Today is ${today}.`;
console.log(formattedToday);
// 結果:Today is 2/19/2024.

似たような方法で数値を通貨表記にすることができます。

function formatCurrency(strings, price) {
  const formattedPrice = new Intl.NumberFormat(
    'ja-JP',
    {
      style: 'currency',
      currency: 'JPY'
    }
  ).format(price);

  // フォーマット済みの通貨を挿入して返す
  return `${strings[0]}${formattedPrice}${strings[1]}`;
}

const price = 1980;
const msg = formatCurrency`The price is ${price}.`;
console.log(msg);
// 結果: The price is ¥1,980.

少し複雑かも知れませんが、バイトサイズをフォーマット化する例も挙げます。

function formatFileSize(strings, fileSize) {
  const units = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
  let unitIndex = 0;

  while (fileSize >= 1024 && unitIndex < units.length - 1) {
      fileSize /= 1024;
      unitIndex++;
  }

  return `${fileSize.toFixed(3)} ${units[unitIndex]}`;
}

const fileSize = 2048576;
const formattedSize = formatFileSize`${fileSize}`;

console.log(`File size: ${formattedSize}`);
// 結果:File size: 1.954 MB

セキュリティ検証

タグ関数を使用してユーザーからの入力を安全に扱えるようにすることができます。タグ関数内でエスケープ処理を実装することで、XSS(クロスサイトスクリプティング)攻撃などのセキュリティリスクを軽減することができます。

function sanitize(strings, ...values) {
  // 入力値をエスケープする
  const sanitizedValues = values.map(value => escapeHtml(value));
  // エスケープされた文字列を結合して返す
  return strings.reduce((result, str, i) => result + str + (sanitizedValues[i] || ''), '');
}

// HTMLタグをエスケープする関数
function escapeHtml(input) {
  return input.replace(/[&<>"']/g, (match) => {
    switch (match) {
      case '&': return '&amp;';
      case '<': return '&lt;';
      case '>': return '&gt;';
      case '"': return '&quot;';
      case "'": return '&#39;';
    }
  });
}

const userInput = '<script>alert("XSS")</script>';
console.log(sanitize`User input: ${userInput}`);
// 出力: User input: &lt;script&gt;alert("XSS")&lt;/script&gt;

タグ付きテンプレートについては以上です。単純な文字列の変換であれば通常の関数宣言でも問題ありませんが、タグ関数の強力なポイントはテンプレートリテラル内のプレースホルダーを特定して個別に処理を行えるところにあります。これにより、文字列内の特定の部分に対して動的な操作を柔軟に行うことができます。

最後に

この記事では、テンプレートリテラルの基本的な使い方と注意点、タグ付きテンプレートについて解説しました。

初学者の方にとってタグ付きテンプレートは少しとっつきにくいかも知れませんが、タグ関数を利用しなくても通常の関数宣言でだいたいのことは実装でき、タグ付きテンプレートはJavaScriptの学習において重要な要素とは言い難いかも知れません。「このようなものがある」という程度で理解しておくと、ここぞというところで役に立つ場合があります。

テンプレートリテラルそのものは広く利用されているので、こちらの基本と注意点をマスターするほうが初学者の方にとって重要です。テンプレートリテラルにも良くない使い方があります。以下の記事ではこれについて解説しているので、興味のある方は参考にしていただけると幸いです。

タイトルとURLをコピーしました