Magento 2 ES6 Template Literals

原文地址

今天我们将了解 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 的。

chrome console 中 var 声明的也是全局变量

发表评论

电子邮件地址不会被公开。 必填项已用*标注