Closed10

[キャッチアップ] タグ付きテンプレートリテラル

shingo.sasakishingo.sasaki

タグつきテンプレートリテラルは、リテラルから任意のテキストセグメントの配列と、任意の置換の値を引数として関数(タグ関数)を呼び出します。これは、 DSL に便利です。

日本語でOK

shingo.sasakishingo.sasaki

以下はタグなしテンプレートリテラルなので、文字列中のプレースホルダの式が展開されて文字列が返ってくるだけ。

const person = "Mike";
const age = 28;

const output = `That ${person} is a ${age}.`; // That Mike is a 28.

console.log(output);
shingo.sasakishingo.sasaki

これを myTag というタグを使用するように書き換えると、タグ関数には引数としてテンプレートリテラル内のテキストとプレースホルダに挿入された値が入ってくる。

function myTag(...args) {
  console.log(args); // [ [ 'That ', ' is a ', '.' ], 'Mike', 28 ]
}

const output = myTag`That ${person} is a ${age}.`;

引数に名前をつけて受け取るならこう

function myTag(strings, personExp, ageExp) {
  // ...
}
shingo.sasakishingo.sasaki

タグ関数は任意の値を返す (返さず undefined にもできる) ことで、呼び出し元の評価値になる。

function myTag(strings, personExp, ageExp) {
  return "Hello, Tagged Template Literal";
}

const output = myTag`That ${person} is a ${age}.`;

console.log(output); // Hello, Tagged Template Literal
shingo.sasakishingo.sasaki

この仕組を使ってパラメータに応じて改変した結果を返すことができる。

const person = "Mike";
const age = 28;

function myTag(strings, personExp, ageExp) {
  const str0 = strings[0]; // "That "
  const str1 = strings[1]; // " is a "
  const str2 = strings[2]; // "."

  const argStr = ageExp > 99 ? "centenarian" : "youngster";
  return `${str0}${personExp}${str1}${argStr}${str2}`;
}

const output = myTag`That ${person} is a ${age}.`;

console.log(output); // That Mike is a youngster.
shingo.sasakishingo.sasaki

タグ付きテンプレートリテラルを有効活用してる事例として、 styled-components がある。

https://styled-components.com/

こんな初見だとビビる書き方ができる。

import styled from "styled-components";

const Button = styled.a`
  display: inline-block;
  border-radius: 3px;
  padding: 0.5rem 0;

  ${(props: any) =>
    props.primary &&
    styled.style`
      background: white;
      color: black;
    `}
`;

console.log(Button);

テンプレートリテラルで文字列と関数を埋め込んでいるだけだが、実際はスタイルが適用されたコンポーネントが返ってくる。

{
  '$$typeof': Symbol(react.forward_ref),
  render: [Function: I] { displayName: 'styled.a' },
  attrs: [],
  componentStyle: e {
    rules: [
      '\n' +
        '  display: inline-block;\n' +
        '  border-radius: 3px;\n' +
        '  padding: 0.5rem 0;\n' +
        '\n' +
        '  ',
      [Function (anonymous)],
      '\n',
      isCss: true
    ],
    staticRulesId: '',
    isStatic: false,
    componentId: 'sc-bcXHqe',
    baseHash: 138803661,
    baseStyle: undefined
  },
  shouldForwardProp: undefined,
  foldedComponentIds: [],
  styledComponentId: 'sc-bcXHqe',
  target: 'a',
  withComponent: [Function (anonymous)],
  warnTooManyClasses: [Function (anonymous)],
  toString: [Function (anonymous)]
}
shingo.sasakishingo.sasaki

もう一例、GraphQL クライアントの @apollo/client では GraphQL のクエリをタグ付きテンプレートリテラルで書くことができる。

https://github.com/apollographql/graphql-tag

import { gql } from "@apollo/client";

const query = gql`
  query {
    totalUsers
    user {
      id
      name
    }
  }
`;

console.log(query);

これはプレインテキストのクエリを元に、ASTが生成されるようになる。

{
  kind: 'Document',
  definitions: [
    {
      kind: 'OperationDefinition',
      operation: 'query',
      name: undefined,
      variableDefinitions: [],
      directives: [],
      selectionSet: [Object]
    }
  ],
  loc: Location {
    start: 0,
    end: 67,
    source: Source {
      body: '\n  query {\n    totalUsers\n    user {\n      id\n      name\n    }\n  }\n',
      name: 'GraphQL request',
      locationOffset: [Object]
    }
  }
}
shingo.sasakishingo.sasaki

結論、タグ関数を自前で実装する機会はなさそうだけど、文字列をベースにより複雑なオブジェクトを生成してくれる仕組みをタグ付きテンプレートリテラルで提供しているライブラリは結構あるみたいなので、存在は認識しておけば十分そう。

このスクラップは2022/10/09にクローズされました