ES6テンプレートリテラルを実行時に置換(または再利用)できますか?


129

tl; dr:再利用可能なテンプレートをリテラルにすることは可能ですか?

私はテンプレートリテラルを使用しようとしましたが、私はそれを取得できなかったと思いますが、今はイライラしています。つまり、私はそれを理解していると思いますが、「それ」は、それがどのように機能するか、またはそれがどのように機能するかではありません。別の方法で取得する必要があります。

私が見たすべての例(タグ付きテンプレートを含む)では、「置換」は実行時ではなく宣言時に行う必要があります。これは、私にとってテンプレートにはまったく役に立たないようです。多分私は狂っていますが、私にとっての「テンプレート」は、作成時にではなく、使用時に置換されるトークンを含むドキュメントです。それ以外の場合、それは単なるドキュメント(文字列)です。テンプレートはトークンとともにトークンとして保存され、それらのトークンは評価時に評価されます。

誰もが次のような恐ろしい例を挙げています:

var a = 'asd';
return `Worthless ${a}!`

それはいいですが、もし私がすでに知っているならa、私はただreturn 'Worthless asd'かしreturn 'Worthless '+aます。ポイントは何ですか?真剣に。さて、ポイントは怠惰です。プラスが少なく、読みやすくなります。すごい。しかし、それはテンプレートではありません!私見ではない。そして重要なのはMHOだけです。問題は、私見ですが、テンプレートが宣言されたときに評価されるということです。

var tpl = `My ${expletive} template`;
function go() { return tpl; }
go(); // SPACE-TIME ENDS!

expletiveは宣言されていないため、のようなものが出力されますMy undefined template。素晴らしい。実際、少なくともChromeでは、テンプレートを宣言することすらできません。expletive定義されていないため、エラーがスローされます。テンプレートを宣言した後で置換を実行できるようにする必要があります。

var tpl = `My ${expletive} template`;
function go() { return tpl; }
var expletive = 'great';
go(); // My great template

ただし、これらは実際にはテンプレートではないため、これがどのように可能であるかはわかりません。私がタグを使うべきだと言っても、いや、それらは機能しません:

> explete = function(a,b) { console.log(a); console.log(b); }
< function (a,b) { console.log(a); console.log(b); }
> var tpl = explete`My ${expletive} template`
< VM2323:2 Uncaught ReferenceError: expletive is not defined...

これにより、テンプレートリテラルの名前はひどく誤っており、実際の名前は「ヒアドキュメント」と呼ぶべきだと私は信じるようになりました。「リテラル」の部分は私にヒントを与えたはずだと思います(不変のように)?

何か不足していますか?再利用可能なテンプレートをリテラルにする(良い)方法はありますか?


再利用可能なテンプレートリテラルを提供します。

> function out(t) { console.log(eval(t)); }
  var template = `\`This is
  my \${expletive} reusable
  template!\``;
  out(template);
  var expletive = 'curious';
  out(template);
  var expletive = 'AMAZING';
  out(template);
< This is
  my undefined reusable
  template!
  This is
  my curious reusable
  template!
  This is
  my AMAZING reusable
  template!

そして、ここに素朴な「ヘルパー」関数があります...

function t(t) { return '`'+t.replace('{','${')+'`'; }
var template = t(`This is
my {expletive} reusable
template!`);

...それを「より良く」するため。

それらがツイスティな感情を生み出す領域から、私はそれらをテンプレートの子宮と呼ぶ傾向があります。


1
取り消し線はサポートされます(ただし、このようなコメントではサポートされません)。<strike>タグにテキストを挿入します。
ポインティ

ES6テンプレートリテラルは、主に旧式の文字列補間用です。動的なテンプレートが必要な場合は、ハンドルバーなど、またはPointyのタグ付きテンプレートソリューションを使用してください。
ジョーズ

1
テンプレート文字列は、名前にもかかわらず、テンプレートではありませんES6テンプレート文字列の実行の延期
Bergi

8
あなたの投稿を少し不満にしていただけませんか?あなたはそのようにした場合は、「削除してください、Q&A形式でチュートリアルを書くためのもののようにも、それが見えます、私はあなたを与える...あなたから」の部分を質問し、答えとしてそれを投稿します
ベルギ

ここに良い答えがたくさんあることに気づきました。おそらくそれを受け入れるでしょう。
-abalter

回答:


86

これらのリテラルを他のテンプレートエンジンのように機能させるには、中間フォームが必要です。

これを行う最良の方法は、Functionコンストラクターを使用することです。

const templateString = "Hello ${this.name}!";
const templateVars = {
    name: "world"    
}

const fillTemplate = function(templateString, templateVars){
    return new Function("return `"+templateString +"`;").call(templateVars);
}

console.log(fillTemplate(templateString, templateVars));

他のテンプレートエンジンと同様に、ファイルなどの他の場所からその文字列を取得できます。

テンプレートタグが使いにくいなど、この方法を使用すると問題が発生する可能性がありますが、賢い場合は追加できます。補間が遅いため、インラインJavaScriptロジックを使用することもできません。これは、いくつかの考えをもって改善することもできます。


8
いいね!使用することもできますnew Function(`return \`${template}\`;`)
Ruben Stolk

そして、これらのテンプレートは、メソッドを呼び出すか、別のテンプレートのコンパイル結果を渡すことにより、引数を介して構成または「含める」ことができます。
Quentin Engles 2017

クエンティン「テンプレートタグなし」はどういう意味ですか?ありがとう!
mikemaccana

10
このテンプレート文字列は、トランスパイレーション(つまり、webpack)に対して「隠されている」ため、クライアント側で十分に互換性のあるもの(IE11など)にトランスパイルされないことに注意しください ...!
フランクノッケ


65

テンプレート文字列を関数に入れることができます:

function reusable(a, b) {
  return `a is ${a} and b is ${b}`;
}

タグ付きテンプレートでも同じことができます:

function reusable(strings) {
  return function(... vals) {
    return strings.map(function(s, i) {
      return `${s}${vals[i] || ""}`;
    }).join("");
  };
}

var tagged = reusable`a is ${0} and b is ${1}`; // dummy "parameters"
console.log(tagged("hello", "world"));
// prints "a is hello b is world"
console.log(tagged("mars", "jupiter"));
// prints "a is mars b is jupiter"

アイデアは、テンプレートパーサーに変数「スロット」から定数文字列を分割させ、毎回新しい値のセットに基づいてすべてをパッチする関数を返すことです。


3
@FelixKlingかもしれません。私はそれをチェックして修正します。編集はい、私は例の重要な部分を省略したように見えます、それは「再利用可能な」機能です:)
ポインティング

@FelixKling私が何を考えていたか思い出せないので、どうすればいいかわからない!
先のとがっ

1
あなたはいつもそれを削除することができます....けど);ええ、それはTBHのいずれかにあまり意味がありませんreusableので、それは関数を返すことを実現することができ、あなたが使用したい${0}${1}の代わりに、リテラルの内側${a}${b}。次に、その値を使用して、関数の引数を参照できます。これは、Bergiが彼の最後の例で行ったものと同様です:stackoverflow.com/a/22619256/218196(または、基本的に同じだと思います)。
Felix Kling 2016年

1
@FelixKling OK私は、少なくともOPの線に沿って漠然とした何かを思いついたと思います。
先のとがった

3
結果が実際に文字列でない場合、タグ付きテンプレートは非常に強力です。たとえば、私のプロジェクトの1つで、これを使用してASTノードの補間を行います。たとえばexpression`a + ${node}`、既存のASTノードを使用してBinaryExpressionノードを構築することができますnode。内部的には、プレースホルダーを挿入して有効なコードを生成し、それをASTとして解析して、プレースホルダーを渡された値に置き換えます。
Felix Kling 2016年

45

おそらくこれを行うための最もクリーンな方法は、矢印関数を使用することです(この時点では、すでにES6を使用しているため)。

var reusable = () => `This ${object} was created by ${creator}`;

var object = "template string", creator = "a function";
console.log (reusable()); // "This template string was created by a function"

object = "example", creator = "me";
console.log (reusable()); // "This example was created by me"

...そしてタグ付きテンプレートリテラルの場合:

reusable = () => myTag`The ${noun} go ${verb} and `;

var noun = "wheels on the bus", verb = "round";
var myTag = function (strings, noun, verb) {
    return strings[0] + noun + strings[1] + verb + strings[2] + verb;
};
console.log (reusable()); // "The wheels on the bus go round and round"

noun = "racecars", verb = "fast";
myTag = function (strings, noun, verb) {
    return strings[0] + noun + strings[1] + verb;
};
console.log (reusable()); // "The racecars go fast"

これにより、eval()またはの使用が回避され、Function()コンパイラで問題が発生し、速度が大幅に低下します。


関数内にいくつかのコードを挿入して何かを行うことができるので、これは最高の方法だと思いますmyTag。たとえば、出力をキャッシュするキーとして入力パラメーターを使用します。
WOW

これが最良の答えだと思います。また、arrow関数にパラメーターを追加することもできますvar reusable = (value: string) => `Value is ${value}`
haggisandchips

13

2019答​​え

:ライブラリは、当初、ユーザーがXSSを回避するために文字列をサニタイズすることを求めていました。ライブラリのバージョン2では、eval完全に回避されるため、ユーザー文字列をサニタイズする必要はなくなりました(Web開発者はこれを行う必要があります)。

es6-dynamic-templatenpmモジュールがこれを行います。

const fillTemplate = require('es6-dynamic-template');

現在の回答とは異なり:

  • ES6テンプレート文字列を使用しており、類似した形式ではありません。Updateバージョン2では、ES6テンプレート文字列ではなく、同様の形式を使用して、ユーザーが無害な入力文字列を使用できないようにします。
  • thisテンプレート文字列には必要ありません
  • テンプレート文字列と変数を単一の関数で指定できます
  • StackOverflowからのcopypastaではなく、メンテナンスされた更新可能なモジュールです。

使い方は簡単です。テンプレート文字列は後で解決されるため、一重引用符を使用してください!

const greeting = fillTemplate('Hi ${firstName}', {firstName: 'Joe'});

これをReact Nativeで使用している場合、Androidで特別に機能しなくなります。Androidノードランタイムは動的テンプレートをサポートせず、事前入力されたテンプレートのみをサポート
Oliver Dixon

1
これは私の個人的なプロジェクトで使用するソリューションであり、問​​題なく機能します。特にこのような小さなユーティリティでは、ライブラリを多用するのはよくないと思います。
Oliver Dixon


1
@kamil XSSは、a)ユーザーが作成できるようにする場合b)入力文字列をサニタイズしない場合のみ。ただし、ユーザー入力をサニタイズする必要があるという警告を追加します。
mikemaccana

1
これは、リモートでes6テンプレートリテラルを使用しません。試してみてください10 * 20 = ${10 * 20}、それは同様の形式であるかもしれないので、しかし、それもリモートでES6テンプレートリテラルではありません
gman

12

はい、テンプレートで文字列をJSとしてFunction(またはeval)解析することで実行できますが、これは推奨されておらず、XSS攻撃を許可します

代わりに、次のように動的にテンプレートにオブジェクトフィールドを安全に挿入できます。objstr

let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);


これは私が使う方法で、うまくいきました。良い例え!か?RegExヘルプの*の後ですか?私はRegExの専門家ではありませんが、*は0以上を意味するため(そして、この場合は「more」が必要です)、貪欲な制限は必要ないのではないでしょうか。
Gen1-1

@ Gen1-1は貪欲でない.*?ことを意味します-削除すると、スニペットは間違った結果になります"?"
KamilKiełczewski19年

あなたは正しいです、私の間違い。テンプレートで$を使用せず、RegEx:/ {(\ w *)} / gを使用します。これは、タグにスペースが含まれていないためです。も動作します。私が使用するもの:function taggedTemplate(template, data, matcher) { if (!template || !data) { return template; } matcher = matcher || /{(\w*)}/g; // {one or more alphanumeric characters with no spaces} return template.replace(matcher, function (match, key) { var value; try { value = data[key] } catch (e) { // } return value || ""; }); }
Gen1-1

@ Gen1-1ネストされたデータも可能ですか?のようにdata = { a: 1, b: { c:2, d:3 } }-> b.c
ムエシャ

1
@muescha再帰を使用して、プロパティが見つかるまでデータオブジェクト全体とネストされたオブジェクトを検索するには、値= data [key]の行を変更します。例:codereview.stackexchange.com/questions/73714/…、およびmikedoesweb.com/2016/es6-depth-first-object-tree-search
Gen1-1

9

@metamorphasiが提供する回答を簡素化する。

const fillTemplate = function(templateString, templateVars){
  var func = new Function(...Object.keys(templateVars),  "return `"+templateString +"`;")
  return func(...Object.values(templateVars));
}

// Sample
var hosting = "overview/id/d:${Id}";
var domain = {Id:1234, User:22};
var result = fillTemplate(hosting, domain);

console.log(result);


このコードは、主要な回答よりも自明です。賛成票を獲得しました:)
ymz 2018

これにより、変数または外部ファイル(NodeJS内)をテンプレートとして使用したり、実行時に動的にビルドしたりできるようになります。の使用なしeval
b01

XSSの脆弱性?悪意のあるコード(変数var hosting)をいじるのはここです。
KamilKiełczewski19年

7

あなたは、テンプレート内の変数を参照するように命じたパラメータまたはコンテキスト/名前空間を使用しない場合は、例えば${0}${this.something}あるいは${data.something}、あなたはあなたのためのスコープの世話をするテンプレート機能を持たせることができます。

このようなテンプレートを呼び出す方法の

const tempGreet = Template(() => `
  <span>Hello, ${name}!</span>
`);
tempGreet({name: 'Brian'}); // returns "<span>Hello, Brian!</span>"

テンプレート関数:

function Template(cb) {
  return function(data) {
    const dataKeys = [];
    const dataVals = [];
    for (let key in data) {
      dataKeys.push(key);
      dataVals.push(data[key]);
    }
    let func = new Function(...dataKeys, 'return (' + cb + ')();');
    return func(...dataVals);
  }
}

この場合の問題は、ES6テンプレートリテラルを返す関数(この例では、矢印関数を使用したもの)を渡すだけです。私たちが求めているような再利用可能な補間を取得することは、マイナーなトレードオフだと思います。

これはGitHubにあります:https : //github.com/Adelphos/ES6-Reuseable-Template


3
これは良いことですが、ミニファイ(vals、funcなど)は不要で、「cb」はコールバックではありません(これは完全に同期コードです)。そして、Object.values()andObject.keys()
mikemaccana

3

短い答えは、lodashで_.templateを使用することです

// Use the ES template literal delimiter as an "interpolate" delimiter.
// Disable support by replacing the "interpolate" delimiter.
var compiled = _.template('hello ${ user }!');
compiled({ 'user': 'pebbles' });
// => 'hello pebbles!'

3

何か不足していますか?再利用可能なテンプレートをリテラルにする[良い]方法はありますか?

この問題に対する私の解決策は私には明白であるように見えるので、私は何かを見逃しているのかもしれません。

私はそれについてほぼ1つのライナーを持っています:

function defer([first, ...rest]) {
  return (...values) => rest.reduce((acc, str, i) => acc + values[i] + str, first);
}

それで全部です。テンプレートを再利用して置換の解決を延期したい場合は、次のようにします。

> t = defer`My template is: ${null} and ${null}`;
> t('simple', 'reusable');          // 'My template is: simple and reusable'
> t('obvious', 'late to the party'; // 'My template is: obvious and late to the party'
> t(null);                          // 'My template is: null and undefined'
>
> defer`Choose: ${'ignore'} / ${undefined}`(true, false); // 'Choose: true / false'

このタグを適用すると、リテラルに渡されたパラメータを無視する'function'(の代わりに'string')が返されます。その後、新しいパラメーターを指定して呼び出すことができます。パラメータに対応する置換がない場合は、になり'undefined'ます。


拡張回答

この単純なコードは機能しますが、より詳細な動作が必要な場合は、同じロジックを適用でき、無限の可能性があります。あなたは出来る:

  1. 元のパラメーターを利用する:

リテラルに渡された元の値をコンストラクションに保存し、テンプレートを適用するときにクリエイティブな方法で使用できます。これらはフラグ、型バリデーター、関数などになる可能性があります。これは、これらをデフォルト値として使用する例です。

    function deferWithDefaults([first, ...rest], ...defaults) {
      return (...values) => rest.reduce((acc, curr, i) => {
        return acc + (i < values.length ? values[i] : defaults[i]) + curr;
      }, first);
    }

次に:

    > t = deferWithDefaults`My template is: ${'extendable'} and ${'versatile'}`;
    > t('awesome');                 // 'My template is: awesome and versatile' 
  1. テンプレートファクトリを記述します。

これを行うには、引数として、リダクションに適用できるカスタム関数を期待する関数でこのロジックをラップし(テンプレートリテラルのピースを結合するとき)、カスタム動作の新しいテンプレートを返します。

    const createTemplate = fn => function (strings, ...defaults) {
      const [first, ...rest] = strings;
      return (...values) => rest.reduce((acc, curr, i) => {
        return acc + fn(values[i], defaults[i]) + curr;
      }, first);
    };

次に、たとえば、埋め込まれたhtml、css、sql、bashを書き込むときにパラメーターを自動的にエスケープまたはサニタイズするテンプレートを作成できます...

    function sqlSanitize(token, tag) {
      // this is a gross simplification, don't use in production.
      const quoteName = name => (!/^[a-z_][a-z0-9_$]*$/.test(name) ? `"${name.replace(/"/g, '""')}"` : name);
      const quoteValue = value => (typeof value == 'string' ? `'${value.replace(/'/g, "''")}'` : value);
      switch (tag) {
        case 'table':
          return quoteName(token);
        case 'columns':
          return token.map(quoteName);
        case 'row':
          return token.map(quoteValue);
        default:
          return token;
      }
    }

    const sql = createTemplate(sqlSanitize);

この単純な(繰り返しますが、単純な!)SQLテンプレートを使用すると、次のようなクエリを作成できます。

    > q  = sql`INSERT INTO ${'table'} (${'columns'})
    ... VALUES (${'row'});`
    > q('user', ['id', 'user name', 'is"Staff"?'], [1, "O'neil", true])
    // `INSERT INTO user (id,"user name","is""Staff""?")
    // VALUES (1,'O''neil',true);`
  1. 置換のための名前付きパラメーターを受け入れる:すでに与えられたものに基づいて、それほど難しくない演習。この他の答えには実装があります

  2. 戻りオブジェクトを次のように動作させる'string':さて、これは物議を醸していますが、興味深い結果につながる可能性があります。この他の答えに示されています

  3. 呼び出しサイトのグローバル名前空間内のパラメーターを解決します。

再利用可能なテンプレートリテラルを提供します。

まあ、これはOPを示したものですコマンドを使用して、彼の補遺でevil、私が意味します、eval。これはeval、渡された変数名をグローバル(またはウィンドウ)オブジェクトに検索するだけで、なしで実行できます。気に入らないので、その方法は示しません。クロージャーは正しい選択です。


2

これが私の最善の試みです。

var s = (item, price) => {return `item: ${item}, price: $${price}`}
s('pants', 10) // 'item: pants, price: $10'
s('shirts', 15) // 'item: shirts, price: $15'

一般化するには:

var s = (<variable names you want>) => {return `<template with those variables>`}

E6を実行していない場合は、次のようにすることもできます。

var s = function(<variable names you want>){return `<template with those variables>`}

これは、以前の回答よりも少し簡潔に思われます。

https://repl.it/@abalter/reusable-JS-template-literal


2

一般的に私はevilの使用に反対ですeval()が、この場合は理にかなっています:

var template = "`${a}.${b}`";
var a = 1, b = 2;
var populated = eval(template);

console.log(populated);         // shows 1.2

次に、値を変更してeval()を再度呼び出すと、新しい結果が得られます。

a = 3; b = 4;
populated = eval(template);

console.log(populated);         // shows 3.4

関数で使用したい場合は、次のように記述できます。

function populate(a, b){
  return `${a}.${b}`;
}

テンプレートを含む関数を作成する場合は、絶対にを使用しないでくださいeval
ベルギ

@Bergiなんで?それはあなたの実装とどう違うのですか?
isapir 2017

2
私が「知っているように思われる」理由は、動的に構築されたコードに当てはまります。eval()明示的に呼び出さずに結果を作成するように関数を記述することは、とまったく同じeval()です。したがって、コードを読みにくくするだけなので、関数に利点はありません。
isapir 2017

1
丁度。そして、あなたのpopulate関数は動的にコードを構築しないので、evalそのすべての欠点を使ってはなりません。
Bergi

6
あなたの関数は単にfunction populate(a,b) { return `${a}.${b}`; }evalである可能性があります
Vitim.us '29

1

更新:次の回答は単一の変数名に限定されているため、次のようなテンプレート'Result ${a+b}'はこの場合は無効です。ただし、いつでもテンプレートの値を試すことができます。

format("This is a test: ${a_b}", {a_b: a+b});

元の回答:

以前の回答に基づいていますが、より「友好的な」ユーティリティ関数を作成します。

var format = (template, params) => {
    let tpl = template.replace(/\${(?!this\.)/g, "${this.");
    let tpl_func = new Function(`return \`${tpl}\``);

    return tpl_func.call(params);
}

次のように呼び出すことができます。

format("This is a test: ${hola}, second param: ${hello}", {hola: 'Hola', hello: 'Hi'});

そして、結果の文字列は次のようになります。

'This is a test: Hola, second param: Hi'

このようなテンプレートはどうですか?`Result: ${a+b}`
Atiris

1
こんにちは@Atiris、あなたは正しい、それは制限です、私は私の答えを更新しました。
Roberto

1

かなり単純なもの(単に固定変数フィールド、計算なし、条件式など)を探しているが、IE 8,9,10,11のようなテンプレート文字列をサポートしていないブラウザでもクライアント側で機能する場合 ...

さあ行こう:

fillTemplate = function (templateString, templateVars) {
    var parsed = templateString;
    Object.keys(templateVars).forEach(
        (key) => {
            const value = templateVars[key]
            parsed = parsed.replace('${'+key+'}',value)
        }
    )
    return parsed
}

これにより、すべての変数の検索が行われます。私は、このモジュールに実装されている、一度にすべての出現箇所を置き換える別の方法があります:安全-ES6-テンプレート
Aalexガビ

1

this.毎回入力する必要がある余分な冗長性に悩まされていたので、のように変数を拡張するために正規表現も追加しまし.athis.a

解決:

const interp = template => _thisObj =>
function() {
    return template.replace(/\${([^}]*)}/g, (_, k) =>
        eval(
            k.replace(/([.a-zA-Z0-9$_]*)([a-zA-Z0-9$_]+)/, (r, ...args) =>
                args[0].charAt(0) == '.' ? 'this' + args[0] + args[1] : r
            )
        )
    );
}.call(_thisObj);

そのまま使用:

console.log(interp('Hello ${.a}${.b}')({ a: 'World', b: '!' }));
// outputs: Hello World!

1

私は、この仕事を簡単に実行できるnpmパッケージを1つ公開するだけです。この答えに深く影響を受けています

const Template = require('dynamic-template-string');

var tpl = new Template('hello ${name}');

tpl.fill({name: 'world'}); // ==> 'hello world';
tpl.fill({name: 'china'}); // ==> 'hello china';

その実装は非常に単純です。あなたがそれを好きになることを望みます。


module.exports = class Template {
  constructor(str) {
    this._func = new Function(`with(this) { return \`${str}\`; }`);
  }

  fill(data) {
    return this._func.call(data);
  }
}

1

次のようなインライン矢印関数を使用できます。定義:

const template = (substitute: string) => `[^.?!]*(?<=[.?\s!])${substitute}(?=[\s.?!])[^.?!]*[.?!]`;

使用法:

console.log(template('my replaced string'));

1

ランタイムテンプレート文字列

var templateString = (template, values) => {
    let output = template;
    Object.keys(values)
        .forEach(key => {
        output = output.replace(new RegExp('\\$' + `{${key}}`, 'g'), values[key]);
    });
    return output;
};

テスト

console.debug(templateString('hello ${word} world', {word: 'wonderful'}));

0

const fillTemplate = (template, values) => {
  template = template.replace(/(?<=\${)\w+(?=})/g, v=>"this."+v);
  return Function.apply(this, ["", "return `"+template+"`;"]).call(values);
};

console.log(fillTemplate("The man ${man} is brother of ${brother}", {man: "John", brother:"Peter"}));
//The man John is brother of Peter

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.