今天我们将了解 ES6 template literals (模板字符串)。Template literals 是 javascript 的新特性,Magento 2 的 UI Component 系统部分核心功能依赖它。Template literals 本身就是一个需要理解的重要概念,但是更重要的是要理解 Magento 在 template literals 之上创建的额外抽象层。还有 Magento 的 uiClass
object system 是怎么把它吸收进来的。
开始前,需要感谢 “bassplayer7” ,他在 StackExchange 上的回答,提供了完成该篇文章所需要的重要信息。
ES6 Template Literals
很多人的第一个问题可能是 —— ES6 是什么鬼?
ES 是 ECMAScript 的缩写,ECMA 是 Ecma_International 的缩写,他是一个将各种 javascript/jscript/actionscript 编程语言标准化的机构。
ECMAScript 6 是该标准的最新版本。浏览器厂商(Apple, Google, Firefox)正在根据 ES6 标准实现功能(类似于 HTML 是怎样推出的)。
其中一个功能正是 template literals 。Template literals 让 javascript 具备了简单的、内置的模板语言。我们将在 Google Chrome Console 中运行一些 template literal 示例代码,但是你是可以在任何支持 ES6 标准的 javascript 环境中运行这些示例代码的。
Template literals 最简单的形式和字符串的区别很小。看下面的Hello World
文本:
> results = `Hello World`
> console.log(results);
Hello World
注意,我们把 “Hello World” 放在了重音符中(就是这个符号:` 中)
放在重音符中的文本表示,这个字符串是一个 template literal
到目前为止,字符串和模板字符串(Template literal)没什么特别大的区别。Template literals are (from a userland perspective) immediately rendered as strings
> var results = `Hello World`
> var type = typeof results;
> console.log(type)
string
results
变量看起来和 string 没什么区别,反正到目前为止,template literals 看起来没什么用。
当然了,如果他们没有什么用的话,我们也不会写文章说它了。Template literals 支持 template variables/placeholders ,看下面的例子:
> var salutation = "Goodbye"
> var results = `${salutation} World`
> console.log(results);
${salutation}
就是一个 template literal variable (or placeholder) 这些变量从 js 当前作用域中获得。所以 js 会把 ${salutation}
替换成 Goodbye
。 js 会这么做是因为我们给全局变量 salutation
赋值为 Goodbye
。
要了解更多 template literals 请访问 Mozilla developer network ,会讲更多高级特性,比如 tag
。
Browser Support and Magento 2
跟其他 js 新特性一样,客户端开发者在使用前需要特别注意浏览器是否支持该特性。目前不是所有浏览器都支持 Template literals ,在老的浏览器上使用他们会导致错误。
Magento 2 的开发者通过创建 RequireJS 模块来 rendering template literals 去绕过使用限制。这个模块的 id 就是 mage/utils/template
,所以你可以像下面这样使用:
// 注意要在 Magento 页面中打开 debugger
//requires an enviornment bootstrapped with Magento 2
//javascript. i.e. open you debugger on a Magento page
> requirejs(['mage/utils/template'], function(templateRenderer){
window.salutation = 'Brave New';
var templateLiteral = '${salutation} World';
var results = templateRenderer.template(templateLiteral);
console.log(results);
});
Brave New World
幕后,mage/utils/template
模块会检查浏览器对 template literal 的支持。如果支持,模块将使用浏览器原生的实现方式,如果不支持,模块将使用纯用户级的实现方式(速度更慢一点)。这被称为 polyfill
这个模块很有用,但是有几点需要说明。首先,你会注意到我们需要把 salutation
变量搁到全局的命名空间上(在浏览器的 js 环境下指的是 “window”)
window.salutation = 'Brave New';
var templateLiteral = '${salutation} World';
原生的 template literals 会从当前作用域取值,但是 Magento 2 的模块(任何用户级代码)则不能自动地访问该作用域,因此,template literals 只能从全局作用域上取值。
看下面的代码:
> requirejs(['mage/utils/template'], function(templateRenderer){
var salutation = 'Brave New';
var templateLiteral = '${salutation} World';
var results = templateRenderer.template(templateLiteral);
console.log(results);
});
这里会报错(除非你定义了一个全局变量 salutation):
VM1627:1 Uncaught ReferenceError: salutation is not defined
(这一段我也没看懂,所以放上原文)
The second mage/utils/template caveat, and likely a direct result of the above scope problem, is Magento 2’s template literals have extra abilities that go above and beyond those defined in the standard.
Binding View Variables to a Template Literal
Magento 2 的 template litearls 允许你绑定特定的对象到特定的 template literal 上,然后通过特别的语法引用变量。就像下面这样:
> requirejs(['mage/utils/template'], function(templateRenderer){
var viewVars = {
'salutation':'What a Crazy'
};
var templateLiteral = '${salutation} World';
var results = templateRenderer.template(templateLiteral, viewVars);
console.log(results);
});
就是说,template
方法第二个参数接受一个对象。
templateRenderer.template(templateLiteral, viewVars);
但是上面的代码还是会报错。这是因为我们前面说过要用特殊的语法的。你需要在 ${}
里面用第二个 $
符号来引用对象。
就是说下面这样
var templateLiteral = '${salutation} World';
需要变成这样:
var templateLiteral = '${$.placeholder} World';
这有点笨拙,不过还是很好理解的,只要明白 $.
指代你传递过去的对象。
下面这样就对了。
requirejs(['mage/utils/template'], function(templateRenderer){
var viewVars = {
'salutation':'What a Crazy'
};
var templateLiteral = '${$.salutation} World';
var results = templateRenderer.template(templateLiteral, viewVars);
console.log(results);
});
What a Crazy World
Connection to UI Components
为什么我们要在讲 Magento 2 的 UI Component 系列文章中讲 template literals 呢?这是因为 template literals 已经被植入了 view model constructor object 系统中。
我们知道,Magento UI Components 使用的 Knockout.js view model constructor objects 是基于 uiElement/Magento_Ui/js/lib/core/element/element
模块的。(不明白戳这里)
下面是一个简单的例子,我们先创建一个 view model constructor ,然后用这个 constructor 去创建 view model
> requirejs(['uiElement'], function(Element){
viewModelConstructor = Element.extend({});
viewModel = new viewModelConstructor;
console.log(viewModel);
});
UiClass {_super: undefined, ignoreTmpls: Object, ...}
这个 view model constructor 有能力带 default 值进行实例化(因为 uiElement
继承自 uiClass/Magento_Ui/js/lib/core/class
,这个父类是拥有这个能力的)如果你为 view model constructor 提供 default object ,像下面这样:
viewModelConstructor = Element.extend({
'defaults':{
'ourDefaultValue':'Look at our value!'
}
});
那么所有通过这个 constructor 实例化的对象都会拥有 default 值 ourDefaultValue
> requirejs(['uiElement'], function(Element){
viewModelConstructor = Element.extend({
'defaults':{
'ourDefaultValue':'Look at our value!'
}
});
viewModel = new viewModelConstructor;
console.log(viewModel.ourDefaultValue);
});
Look at our value!
Magento 对象系统会是检查所有的 default 字符串,发现 template literal 就会自动计算出他们。
> requirejs(['uiElement'], function(Element){
window.salutation = 'Hello';
viewModelConstructor = Element.extend({
'defaults':{
'message':'${salutation} World. ',
'salutation':'Goodbye'
}
});
viewModel = new viewModelConstructor;
console.log(viewModel.message);
});
Hello World.
注意这里使用的是全局的 salutation
值。
看下面的例子:
requirejs(['uiElement'], function(Element){
viewModelConstructor = Element.extend({
'defaults':{
'message':'${$.salutation} World. ',
'salutation':'Goodbye'
}
});
viewModel = new viewModelConstructor;
console.log(viewModel.message);
});
Goodbye World.
给 view model constructor 传递对象
requirejs(['uiElement'], function(Element){
viewModelConstructor = Element.extend({
'defaults':{
'message':'${$.salutation} World. ',
'salutation':'Goodbye'
}
});
viewModel = new viewModelConstructor({
'salutation':'This is still a crazy'
});
console.log(viewModel.message);
});
This is still a crazy World.
Wrap Up
本文揭示了想把 Magento 当作平台使用的开发者遇到的挑战之一。不仅有前沿 javaScript 概念,还有 Magento 通过不那么标准的方式(binding view variables)去扩展了这些概念,还有 Magento 是如何将这些概念吸收到自己的系统中去的(uiClass
object system)。 如果没有上面的这些概念,那么你遇到下面这些代码的时候:
return Element.extend({
defaults: {
clientConfig: {
urls: {
save: '${ $.submit_url }',
beforeSave: '${ $.validate_url }'
}
}
},
就只能摸不着头脑干着急了。
现在我们了解了 Magento 的 ES6-like template literals .我们距离更进一步的探索又近了一步,下面我们将探索 Magento 是如何将后端数据放入 view model constructor 的 default
数组中的,还有 Magento 是如何处理 UI Component 生成的 data sources 的。