สามารถแทนที่ตัวอักษรเทมเพลต ES6 ที่รันไทม์ (หรือใช้ซ้ำ) ได้หรือไม่


129

tl; dr: เป็นไปได้ไหมที่จะสร้างเทมเพลตที่ใช้ซ้ำได้?

ฉันพยายามใช้ตัวอักษรเทมเพลต แต่ฉันคิดว่าฉันไม่เข้าใจและตอนนี้ฉันเริ่มหงุดหงิด ฉันหมายถึงฉันคิดว่าฉันเข้าใจแล้ว แต่ "มัน" ไม่ควรเป็นวิธีการทำงานหรือวิธีที่ควรได้รับ มันควรจะได้รับที่แตกต่างกัน

ตัวอย่างทั้งหมดที่ฉันเห็น (แม้แต่เทมเพลตที่ติดแท็ก) กำหนดให้ "การแทนที่" ต้องทำในเวลาประกาศไม่ใช่เวลาทำงานซึ่งดูเหมือนว่าไม่มีประโยชน์สำหรับฉันสำหรับเทมเพลต บางทีฉันอาจจะบ้า แต่ "เทมเพลต" สำหรับฉันคือเอกสารที่มีโทเค็นซึ่งถูกแทนที่เมื่อคุณใช้ไม่ใช่เมื่อคุณสร้างมันมิฉะนั้นจะเป็นเพียงเอกสาร (เช่นสตริง) เทมเพลตจะถูกเก็บไว้กับโทเค็นเนื่องจากโทเค็นและโทเค็นเหล่านั้นจะได้รับการประเมินเมื่อคุณ ... ประเมินมัน

ทุกคนยกตัวอย่างที่น่ากลัวคล้ายกับ:

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

นั่นเป็นเรื่องดี แต่ถ้าผมรู้อยู่แล้วว่าaผมจะเพียงหรือreturn 'Worthless asd' return 'Worthless '+aประเด็นคืออะไร? อย่างจริงจัง. โอเคประเด็นคือความเกียจคร้าน ข้อดีน้อยลงอ่านได้ง่ายขึ้น ยิ่งใหญ่ แต่นั่นไม่ใช่แม่แบบ! ไม่ใช่ IMHO และ MHO คือทุกสิ่งที่สำคัญ! ปัญหา IMHO คือเทมเพลตจะได้รับการประเมินเมื่อมีการประกาศดังนั้นหากคุณทำ IMHO:

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

ทั้งหมดนี้ได้นำให้เชื่อว่าตัวอักษรแม่แบบจะเรียกไม่ถูกอย่างน่ากลัวและควรจะเรียกว่าสิ่งที่พวกเขาเป็นจริง: heredocs ฉันเดาว่าส่วนที่เป็น "ตัวอักษร" น่าจะทำให้ฉันขาดหายไป (เหมือนเดิมไม่เปลี่ยนรูป)?

ฉันพลาดอะไรไปรึเปล่า? มีวิธี (ที่ดี) ในการสร้างเทมเพลตแบบใช้ซ้ำได้หรือไม่?


ฉันให้ตัวอักษรแม่แบบที่ใช้ซ้ำได้ :

> 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!`);

... เพื่อให้ "ดีขึ้น".

ฉันมีแนวโน้มที่จะเรียกพวกเขาว่าแม่แบบ guterals เนื่องจากพื้นที่ที่พวกเขาสร้างความรู้สึกที่บิดเบี้ยว


1
รองรับการขีดทับ (แต่ไม่ใช่ในความคิดเห็นเช่นนี้) ใส่ข้อความของคุณใน<strike>แท็ก
Pointy

ตัวอักษรเทมเพลต ES6 ส่วนใหญ่ใช้สำหรับการแก้ไขสตริงแบบเก่า หากคุณต้องการเทมเพลตแบบไดนามิกให้ใช้ Handlebars และอื่น ๆ หรือโซลูชันเทมเพลตที่ติดแท็กของ Pointy
joews

1
สตริงแม่แบบเป็นแม้จะมีชื่อที่ไม่มีแม่ ดูการเลื่อนการดำเนินการสำหรับสตริงเทมเพลต ES6
Bergi

8
คุณช่วยทำให้โพสต์ของคุณหยาบคายน้อยลงหน่อยได้ไหม นอกจากนี้ก็ดูเหมือนว่าคุณตั้งใจจะเขียนกวดวิชาใน Q & A รูปแบบถ้าคุณทำเช่นนั้นโปรดลบ " ฉันให้คุณ ... " ส่วนหนึ่งจากคำถามและโพสต์เป็นคำตอบ
Bergi

ฉันสังเกตว่ามีคำตอบที่ดีมากมายที่นี่ บางทียอมรับอย่างใดอย่างหนึ่ง
อาบน้ำ

คำตอบ:


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

Quentin 'no template tags' หมายถึงอะไร? ขอบคุณ!
mikemaccana

10
ระวังว่าสตริงแม่แบบนี้เป็นแบบ 'ซ่อน' ในการถ่ายโอน (เช่น webpack) ดังนั้นจะไม่ปรากฏในสิ่งที่เข้ากันได้อย่างเพียงพอ (เช่น IE11)ในฝั่งไคลเอ็นต์ ... !
Frank Nocke

9
ช่องโหว่ XSS ? รายละเอียดในJSFIDDLE นี้
Kamil Kiełczewski

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 ที่อาจจะ; ฉันจะตรวจสอบและแก้ไขหากเป็นเช่นนั้น แก้ไขใช่ดูเหมือนว่าฉันจะทิ้งส่วนสำคัญของตัวอย่างไว้นั่นคือฟังก์ชัน "ใช้ซ้ำได้" :)
Pointy

@FelixKling ฉันไม่แน่ใจว่าจะทำอย่างไรเพราะฉันจำไม่ได้เลยว่าตอนนั้นคิดอะไรอยู่!
Pointy

1
ใช่มันไม่ได้ทำให้รู้สึกมากที่จะเป็นได้ทั้ง TBH;) คุณก็สามารถลบออกได้ .... แต่reusableอาจจะมีการดำเนินการเพื่อให้มันกลับฟังก์ชั่นและคุณต้องการใช้${0}และ${1}ภายในตัวอักษรแทนและ${a} ${b}จากนั้นคุณสามารถใช้ค่านั้นเพื่ออ้างถึงอาร์กิวเมนต์ของฟังก์ชันได้เช่นเดียวกับที่ Bergi ทำในตัวอย่างสุดท้ายของเขา: stackoverflow.com/a/22619256/218196 (หรือฉันเดาว่ามันเหมือนกัน)
Felix Kling

1
@FelixKling ตกลงฉันคิดว่าฉันคิดอะไรบางอย่างที่คลุมเครืออย่างน้อยตามแนวของ OP
Pointy

3
เทมเพลตที่ติดแท็กจะมีประสิทธิภาพมากหากผลลัพธ์ไม่ใช่สตริง ตัวอย่างเช่นในโครงการของฉันฉันใช้มันเพื่อทำการแก้ไขโหนด AST เช่นเราสามารถexpression`a + ${node}`สร้างโหนด BinaryExpression ด้วยโหนด AST ที่มีอยู่nodeได้ ภายในเราจะแทรกตัวยึดตำแหน่งเพื่อสร้างรหัสที่ถูกต้องแยกวิเคราะห์เป็น AST และแทนที่ตัวยึดตำแหน่งด้วยค่าที่ส่งผ่าน
Felix Kling

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

ผมคิดว่านี่คือคำตอบที่ดีที่สุด var reusable = (value: string) => `Value is ${value}`นอกจากนี้คุณยังสามารถเพิ่มพารามิเตอร์ให้กับฟังก์ชั่นลูกศรซึ่งผมคิดว่าทำให้มันยิ่งทำความสะอาด:
haggisandchips

13

คำตอบ 2019 :

หมายเหตุ : เดิมทีไลบรารีคาดหวังให้ผู้ใช้ล้างสตริงเพื่อหลีกเลี่ยง XSS ไลบรารีเวอร์ชัน 2 ไม่จำเป็นต้องมีการล้างสตริงผู้ใช้อีกต่อไป (ซึ่งนักพัฒนาเว็บควรดำเนินการต่อไป) เนื่องจากหลีกเลี่ยงโดยevalสิ้นเชิง

es6-dynamic-templateโมดูล NPMไม่นี้

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

ไม่เหมือนกับคำตอบปัจจุบัน:

  • ใช้สตริงเทมเพลต ES6 ไม่ใช่รูปแบบที่คล้ายกัน การอัปเดตเวอร์ชัน 2 ใช้รูปแบบที่คล้ายกันแทนที่จะเป็นสตริงเทมเพลต ES6 เพื่อป้องกันไม่ให้ผู้ใช้ใช้สตริงอินพุตที่ไม่ได้รับการรับรอง
  • ไม่จำเป็นต้องมีthisในสตริงเทมเพลต
  • คุณสามารถระบุสตริงเทมเพลตและตัวแปรในฟังก์ชันเดียว
  • เป็นโมดูลที่ได้รับการบำรุงรักษาและอัพเดตได้แทนที่จะเป็น copypasta จาก StackOverflow

การใช้งานทำได้ง่าย ใช้เครื่องหมายคำพูดเดียวเนื่องจากสตริงแม่แบบจะได้รับการแก้ไขในภายหลัง!

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

หากคุณใช้สิ่งนี้กับ React Native มันจะแตกเป็นพิเศษบน Android รันไทม์ของโหนด Android ไม่รองรับเทมเพลตแบบไดนามิกเฉพาะเทมเพลตที่เติมไว้ล่วงหน้า
Oliver Dixon

1
นี่เป็นวิธีแก้ปัญหาที่ฉันใช้ในโครงการส่วนตัวและทำงานได้อย่างไม่มีที่ติ ฉันคิดว่าเป็นความคิดที่ดีที่จะใช้ห้องสมุดมากเกินไปโดยเฉพาะอย่างยิ่งสำหรับสาธารณูปโภคขนาดเล็กเช่นนี้
Oliver Dixon

1
ช่องโหว่ XSS ? รายละเอียดในFIDDLE นี้
Kamil Kiełczewski

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 แต่ฉันคาดเดาเนื่องจาก * หมายถึงศูนย์หรือมากกว่า (และคุณต้องการ "มากกว่า" ในกรณีนี้) จึงไม่จำเป็นต้องมีข้อ จำกัด ที่โลภ?
Gen1-1

@ Gen1-1 .*?หมายถึงการไม่โลภ - หากคุณลบ"?"ตัวอย่างจะให้ผลลัพธ์ที่ผิด
Kamil Kiełczewski

คุณถูกต้องความผิดพลาดของฉัน ฉันไม่ใช้ $ ในเทมเพลตของฉันและใช้ 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?
muescha

1
@muescha คุณจะเปลี่ยนบรรทัด: value = 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

สิ่งนี้ควรอนุญาตให้คุณใช้ตัวแปรหรือไฟล์ภายนอก (ใน NodeJS) เป็นเทมเพลตหรือสร้างแบบไดนามิกในขณะรันไทม์ โดยไม่ต้องใช้eval.
01

ช่องโหว่ XSS ? ซอด้วยรหัสที่เป็นอันตราย (ตัวแปรvar hosting) ที่นี่
Kamil Kiełczewski

7

หากคุณไม่ต้องการที่จะใช้พารามิเตอร์สั่งซื้อหรือบริบท / namespaces การอ้างอิงตัวแปรในแม่แบบของคุณเช่น${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()และObject.keys()
mikemaccana

3

คำตอบสั้น ๆ คือใช้_.templateใน lodash

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

ฉันพลาดอะไรไปรึเปล่า? มีวิธี [ที่ดี] ในการสร้างเทมเพลตแบบใช้ซ้ำได้หรือไม่?

บางทีฉันอาจจะพลาดอะไรบางอย่างไปเพราะวิธีแก้ปัญหานี้ดูเหมือนชัดเจนสำหรับฉันจนฉันรู้สึกประหลาดใจมากที่ไม่มีใครเขียนคำถามนั้นในคำถามเก่า

ฉันมีซับเดียวสำหรับมัน:

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'(แทน a '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. ทำให้วัตถุส่งคืนมีพฤติกรรมเหมือน a '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

โดยทั่วไปฉันต่อต้านการใช้ความชั่วร้าย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

@Bergi ทำไม? แตกต่างจากการใช้งานของคุณอย่างไร?
isapir

2
เหตุผลที่ฉัน "ดูเหมือนจะรู้" ใช้กับโค้ดที่สร้างขึ้นแบบไดนามิก การเขียนฟังก์ชันเพื่อให้สร้างผลลัพธ์โดยไม่ต้องเรียกeval()อย่างชัดเจนนั้นเหมือนกันeval()ทุกประการดังนั้นจึงไม่มีประโยชน์ใด ๆ เนื่องจากจะทำให้โค้ดอ่านยากขึ้นเท่านั้น
isapir

1
เผง และเนื่องจากpopulateฟังก์ชันของคุณไม่ได้สร้างโค้ดแบบไดนามิกจึงไม่ควรใช้evalกับข้อเสียทั้งหมด
Bergi

6
ฟังก์ชันของคุณอาจเป็นเพียงแค่function populate(a,b) { return `${a}.${b}`; }eval ไม่เพิ่มอะไรเลย
Vitim.us

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
}

สิ่งนี้จะทำการค้นหาทุกตัวแปร มีอีกวิธีหนึ่งที่แทนที่เหตุการณ์ทั้งหมดพร้อมกันที่ฉันติดตั้งในโมดูลนี้: safe-es6-template
Aalex Gabi

1

ผมรำคาญที่ซ้ำซ้อนพิเศษที่จำเป็นในการพิมพ์this.ทุกเวลาดังนั้นฉันยังเพิ่ม regex เพื่อขยายตัวแปรเช่นการ.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 หนึ่งชุดที่สามารถทำงานนี้ได้ ได้รับแรงบันดาลใจอย่างมากจากคำตอบนี้

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.