Can ES6 template literals be substituted at runtim

2019-01-06 10:30发布

tl;dr: Is it possible to make a reusable template literal?

I've been trying to use template literals but I guess I just don't get it and now I'm getting frustrated. I mean, I think I get it, but "it" shouldn't be how it works, or how it should get. It should get differently.

All the examples I see (even tagged templates) require that the "substitutions" be done at declaration time and not run time, which seems utterly useless to me for a template. Maybe I'm crazy, but a "template" to me is a document that contains tokens which get substituted when you use it, not when you create it, otherwise it's just a document (i.e., a string). A template is stored with the tokens as tokens & those tokens are evaluated when you...evaluate it.

Everyone cites a horrible example similar to:

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

That's nice, but if I already know a, I would just return 'Worthless asd' or return 'Worthless '+a. What's the point? Seriously. Okay the point is laziness; fewer pluses, more readability. Great. But that's not a template! Not IMHO. And MHO is all that matters! The problem, IMHO, is that the template is evaluated when it's declared, so, if you do, IMHO:

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

Since expletive isn't declared, it outputs something like My undefined template. Super. Actually, in Chrome at least, I can't even declare the template; it throws an error because expletive is not defined. What I need is to be able to do the substitution after declaring the template:

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

However I don't see how this is possible, since these aren't really templates. Even when you say I should use tags, nope, they don't work:

> 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...

This all has led me to believe that template literals are horribly misnamed and should be called what they really are: heredocs. I guess the "literal" part should have tipped me off (as in, immutable)?

Am I missing something? Is there a [good] way to make a reusable template literal?


I give you, reusable template literals:

> 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!

And here is a naive "helper" function...

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

...to make it "better".

I'm inclined to call them template guterals because of the area from which they produce twisty feelings.

13条回答
叛逆
2楼-- · 2019-01-06 10:40

This is my best attempt:

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

To generalify:

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

If you are not running E6, you could also do:

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

This seems to be a bit more concise than the previous answers.

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

查看更多
我命由我不由天
3楼-- · 2019-01-06 10:41

Probably the cleanest way to do this is with arrow functions (because at this point, we're using ES6 already)

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"

...And for tagged template literals:

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"

This also avoids the use of eval() or Function() which can cause problems with compilers and cause a lot of slowdown.

查看更多
干净又极端
4楼-- · 2019-01-06 10:44

In general I'm against using the evil eval(), but in this case it makes sense:

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

console.log(populated);         // shows 1.2

Then if you change the values and call eval() again you get the new result:

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

console.log(populated);         // shows 3.4

If you want it in a function, then it can be written like so:

function populate(a, b){
  return `${a}.${b}`;
}
查看更多
爱情/是我丢掉的垃圾
5楼-- · 2019-01-06 10:46

2018 answer:

The es6-dynamic-template module on npm does this.

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

Unlike the current answers:

  • It uses ES6 template strings, not a similar format
  • It doesn't need this in the template string
  • You can specify the template string and variables in a single function
  • It's a maintained, updatable module, rather than copypasta from StackOverflow

Usage is simple. Use single quotes as the template string will be resolved later!

const greeting = fillTemplate('Hi ${firstName}', {firstName: 'Joe'});
查看更多
爱情/是我丢掉的垃圾
6楼-- · 2019-01-06 10:47

A simple utility function. No need for external library.

/**
 * @param templateString the string with es6 style template such as "Hello my name is: ${name}"
 * @param params the params which is a key/value pair for the template.
 */
export const fillTemplate = (templateString: string, params: any): string => {
    let completedString = templateString
    Object.keys(params).forEach((eachKeyName) => {
        completedString = completedString.replace('${' + eachKeyName + '}', params[eachKeyName])
    })
    return completedString
}
查看更多
Summer. ? 凉城
7楼-- · 2019-01-06 10:50

You can put a template string in a function:

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

You can do the same thing with a tagged template:

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"

The idea is to let the template parser split out the constant strings from the variable "slots", and then return a function that patches it all back together based on a new set of values each time.

查看更多
登录 后发表回答