<h5 style="color:red;">系统学习magento二次开发,推荐小册:<a style="color:blue;" href="https://www.maxiaoke.com/manual/magento_cn_dev.html" target="_blank">《Magento中文全栈二次开发
》</a></h5>
<div class="image-container">
<p>
<a style="color:blue;" href="https://www.maxiaoke.com/manual/magento_cn_dev.html" target="_blank">
<img src="https://www.maxiaoke.com/uploads/images/20230218/bb9c82995c24d1105676e02f373755f5.jpg" alt="Magento中文全栈二次开发">
</a>
</p>
</div>
<div class="text-container" style="font-size:14px; color:#888">
<p>本小册面向Magento2以上版本,书代码及示例兼容magento2.0-2.4版本。涵盖了magento前端开发,后端开发,magento2主题,magento2重写,magento2 layout,magento2控制器,magento2 block等相关内容,带领您成为magento开发技术专家。</p>
</div>
<hr><p>虽然KnockoutJS将自己标榜为MVVM(模型,视图,视图模型)框架,但PHP开发人员会发现模型部分有点薄。KnockoutJS本身没有数据存储的原生概念,并且像许多现代JavaScript框架一样,它被设计为与仅服务后端配合使用的最佳效果。即 KnockoutJS 的“模型”是其他一些框架,发出 AJAX 请求以填充视图模型值。</p><p>KnockoutJS可能会让你措手不及的另一件事是,它不是一个“全栈”的javascript应用程序框架(值得称赞的是,它不会这样标榜自己)。KnockoutJS对你如何把它包含在你的项目中,或者你如何组织你的代码没有意见(尽管文档清楚地表明KnockoutJS团队成员是RequireJS的粉丝)。</p><p>这给像Magento这样的服务器端PHP框架带来了一个有趣的挑战。不仅有一定程度的JavaScript脚手架需要围绕KnockoutJS,而且Magento 2不是一个纯服务的框架。虽然Magento 2的新API功能正在朝着这个方向大踏步前进,但Magento 2并不是一个纯服务的框架。即后端框架开发人员还需要构建脚手架以将业务对象数据放入 KnockoutJS。</p><p>今天我们将深入探讨Magento 2的KnockoutJS集成。在本教程结束时,您将了解Magento 2如何应用KnockoutJS绑定以及Magento 2如何初始化自己的自定义绑定。您还将了解Magento如何修改一些核心的KnockoutJS行为,为什么他们这样做,以及这些更改为您自己的应用程序和模块打开的其他可能性。</p><p>本文是介绍Magento 2中高级JavaScript概念的较长系列的一部分。虽然阅读之前的文章不是100%强制性的,但如果您在下面的概念上苦苦挣扎,您可能需要在下面的评论中指出您的Magento Stack Exchange问题之前查看以前的文章。</p><p><span style="color: #6a9955;">### 创建Magento模块</span></p><p>虽然这篇文章是javascript的重,但我们希望我们的示例代码在具有Magento基线HTML的页面上运行。这意味着添加一个新模块。我们将按照本系列第一篇文章中相同的方式执行此操作,并使用 pestle 创建一个具有 URL 端点的模块</p><pre class="brush:bash;toolbar:false">$ pestle.phar generate_module Pulsestorm KnockoutTutorial 0.0.1
$ pestle.phar generate_route Pulsestorm_KnockoutTutorial frontend pulsestorm_knockouttutorial
$ pestle.phar generate_view Pulsestorm_KnockoutTutorial frontend pulsestorm_knockouttutorial_index_index Main content.phtml 1column
$ php bin/magento module:enable Pulsestorm_KnockoutTutorial
$ php bin/magento setup:upgrade</pre><p>任何通过Magento 2 for PHP MVC开发人员系列工作的人都应该熟悉这些命令。运行上述操作后,您应该能够在系统中访问以下URL</p><p>http://magento.example.com/pulsestorm_knockouttutorial/</p><p>并查看呈现的模板。Pestle在这里不是强制性的 - 如果您有使用Magento页面的首选方式,请随意使用它。app/code/Pulsestorm/KnockoutTutorial/view/frontend/templates/content.phtml</p><p><span style="color: #6a9955;">### 需要JS初始化</span></p><p>在我们上一篇文章和官方的 KnockoutJS 教程中,KnockoutJS 初始化是一件简单的事情。</p><p><span style="color: #569cd6;"></span></p><pre class="brush:bash;toolbar:false">object = SomeViewModelConstructor();
ko.applyBindings(object);</pre><p>对于教程应用程序,这是有道理的。但是,如果您要将所有视图模型逻辑,自定义绑定,组件等保留在单个代码块中,则KnockoutJS将很快变得难以管理。</p><p>相反,Magento的核心团队创建了RequireJS模块,当它被列为依赖项时,将执行任何和所有KnockoutJS初始化。你可以像这样使用这个模块Magento_Ui/js/lib/ko/initialize</p><p></p><pre class="brush:bash;toolbar:false">requirejs(['Magento_Ui/js/lib/ko/initialize'], function(){
//your program here
});</pre><p>关于这个 RequireJS 模块需要注意的一件有趣的事情是它不返回任何值。相反,将RequireJS模块列为依赖项的唯一目的是启动Magento的KnockoutJS集成。当您在野外看到它时,这可能会让您感到困惑。例如,考虑来自不同Magento RequireJS模块的这段代码。</p><p><span style="color: #6a9955;"></span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/core/app.js
define([
'./renderer/types',
'./renderer/layout',
'Magento_Ui/js/lib/ko/initialize'
], function (types, layout) {
'use strict';
return function (data) {
types.set(data.types);
layout(data.components);
};
});</pre><p>声明了三个 RequireJS 依赖项,</p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/core/app.js
[
'./renderer/types',
'./renderer/layout',
'Magento_Ui/js/lib/ko/initialize'
]</pre><p>但在生成的函数中仅使用两个参数</p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/core/app.js
function (types, layout) {
//...
}</pre><p>我不清楚这是否是一种聪明的编程,或者它是否违反了 RequireJS 的精神。也许两者兼而有之。</p><p>无论如何,当你第一次在你自己的基于RequireJS的程序中使用这个库时,Magento将初始化KnockoutJS。后续包含实际上不会执行任何操作,因为 RequireJS 会在您第一次加载模块时缓存它们。</p><p><span style="color: #6a9955;">### KnockoutJS 初始化</span></p><p>如果我们看一下模块的来源Magento_Ui/js/lib/ko/initialize</p><p><span style="color: #6a9955;"></span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/lib/ko/initialize.js
define([
'ko',
'./template/engine',
'knockoutjs/knockout-repeat',
'knockoutjs/knockout-fast-foreach',
'knockoutjs/knockout-es5',
'./bind/scope',
'./bind/staticChecked',
'./bind/datepicker',
'./bind/outer_click',
'./bind/keyboard',
'./bind/optgroup',
'./bind/fadeVisible',
'./bind/mage-init',
'./bind/after-render',
'./bind/i18n',
'./bind/collapsible',
'./bind/autoselect',
'./extender/observable_array',
'./extender/bound-nodes'
], function (ko, templateEngine) {
'use strict';
ko.setTemplateEngine(templateEngine);
ko.applyBindings();
});</pre><p>我们看到一个相对简单的程序,但它还包括其他十九个模块。介绍每个模块的功能超出了本文的范围。考虑以下精彩片段。</p><p>模块是模块的别名。koknockoutjs/knockout</p><p></p><pre class="brush:bash;toolbar:false">vendor/magento/module-theme/view/base/requirejs-config.js
11: "ko": "knockoutjs/knockout",
12: "knockout": "knockoutjs/knockout"</pre><p>该模块是实际的挖空库文件。,和</p><p>模块是KnockoutJS社区的额外功能。这些都不是正式的 RequireJS 模块。knockoutjs/knockoutknockoutjs/knockout-repeatknockoutjs/knockout-fast-foreachknockoutjs/knockout-es5</p><p>以 开头的模块是 Magento 对 KnockoutJS 的自定义绑定。这些是正式的 RequireJS 模块,但实际上并不返回模块。相反,每个脚本操作全局对象以向 KnockoutJS 添加绑定。我们将在下面讨论绑定,但如果您是好奇的类型,请尝试调查其他绑定的实现细节。这是一个有用的练习。希望Magento尽快获得我们的官方文档。./bind/*koscope</p><p>这两个模块是KnockoutJS功能的Magento核心扩展。extender</p><p>该模块返回 KnockoutJS 模板引擎的自定义版本,是我们将深入研究的第一个自定义版本。./template/engine</p><p><span style="color: #6a9955;">### Magento KnockoutJS 模板</span></p><p>回顾一下,在股票 KnockoutJS 系统中,模板是预先编写的 DOM/KnockoutJS 代码块,您可以通过引用它们的 .这些块通过脚本标记添加到页面的 HTML 中,其类型idtext/html</p><pre class="brush:bash;toolbar:false"><script type="text/html" id="my_template">
<h1 data-bind="text:title"></h1>
</script></pre><p>这是一个强大的功能,但给服务器端框架带来了一个问题——如何在页面上呈现正确的模板?您如何确定模板将在那里而不每次都重新创建它?KnockoutJS的解决方案是将组件绑定与RequireJS等库一起使用,但这意味着您的模板绑定到特定的视图模型对象。</p><p>Magento的核心工程师需要一种更好的方法来加载KnockoutJS模板 - 他们通过将本机KnockoutJS模板引擎替换为从RequireJS模块加载的引擎来实现这一点。Magento_Ui/js/lib/ko/template/engine</p><p><span style="color: #6a9955;"></span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/lib/ko/initialize.js
define([
'ko',
'./template/engine',
//...
], function (ko, templateEngine) {
'use strict';
//...
ko.setTemplateEngine(templateEngine);
//...
});</pre><p>如果我们看一下 RequireJS 模块Magento_Ui/js/lib/ko/template/engine</p><p><span style="color: #6a9955;"></span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/lib/ko/template/engine.js
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
define([
'ko',
'./observable_source',
'./renderer'
], function (ko, Source, Renderer) {
'use strict';
var RemoteTemplateEngine,
NativeTemplateEngine = ko.nativeTemplateEngine,
sources = {};
//...
RemoteTemplateEngine.prototype = new NativeTemplateEngine;
//...
RemoteTemplateEngine.prototype.makeTemplateSource = function (template)
{
//...
}
//...
return new RemoteTemplateEngine;
});</pre><p>我们看到Magento制作了一个新对象,该对象原型继承自本机KnockoutJS渲染引擎,然后修改了一些方法来添加自定义行为。如果你不了解你的javascript内部,这意味着Magento复制了股票KnockoutJS模板系统,对其进行了一些更改,然后将其新模板引擎换成库存模板引擎。</p><p>这些修改的实现细节超出了本文的范围,但最终结果是一个 KnockoutJS 引擎,它可以通过 Magento 模块的 URL 加载模板。</p><p>如果这没有意义,一个例子应该澄清问题。将以下内容添加到我们的文件中。content.phtml</p><pre class="brush:bash;toolbar:false">#File: app/code/Pulsestorm/KnockoutTutorial/view/frontend/templates/content.phtml
<div data-bind="template:'Pulsestorm_KnockoutTutorial/hello'"></div></pre><p>在这里,我们添加了一个 KnockoutJS 绑定并传递给它字符串。如果我们在上述情况下重新加载页面,您将在 javascript 控制台中看到如下错误templatePulsestorm_KnockoutTutorial/hello</p><p>> GET http://magento-2-0-4.dev/static/frontend/Magento/luma/en\_US/Pulsestorm\_KnockoutTutorial/template/hello.html 404 (Not Found)</p><p>Magento采用了我们的字符串()并使用第一部分()来创建视图资源的基本URL,并使用第二部分()和附加的前缀来完成URL。如果我们向以下文件添加 KnockoutJS 视图Pulsestorm_KnockoutTutorial/helloPulsestorm_KnockoutTutorialhellotemplate.html</p><pre class="brush:bash;toolbar:false">#File: app/code/Pulsestorm/KnockoutTutorial/view/frontend/web/template/hello.html
<p data-bind="style:{fontSize:'24px'}">Hello World</p></pre><p>并重新加载页面,我们将看到Magento从上面的URL加载了我们的模板,并应用了其KnockoutJS绑定。</p><p>此功能使我们能够避免在需要新模板时用标签乱扔 HTML 页面,并鼓励在 UI 和 UX 功能之间重用模板。<script <span style="color: #569cd6;">type</span>=<span style="color: #ce9178;">"text/html"</span>></p><p><span style="color: #6a9955;">### 无视图模型</span></p><p>回到模块,在Magento设置自定义模板引擎之后,Magento调用KnockoutJS的方法。这将启动将当前 HTML 页面呈现为视图。如果我们看一下该代码,就会立即弹出一些内容。initialize.jsapplyBindings</p><p><span style="color: #6a9955;"></span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/lib/ko/initialize.js
ko.setTemplateEngine(templateEngine);
ko.applyBindings();</pre><p>Magento在没有视图模型的情况下调用。虽然这是一个合法的 KnockoutJS 调用——告诉 KnockoutJS 在没有数据或视图模型逻辑的情况下应用绑定似乎毫无用处。没有数据的视图有什么用?applyBindings</p><p>在股票KnockoutJS系统中,这将是毫无用处的。理解Magento在这里做什么的关键是在我们的KnockoutJS初始化中备份</p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/lib/ko/initialize.js
define([
//...
'./bind/scope',
//...
],</pre><p>Magento的KnockoutJS团队创建了一个名为的自定义KnockoutJS绑定。下面是一个使用范围的示例 - 从Magento 2主页提升。scope</p><pre class="brush:bash;toolbar:false"><li class="greet welcome" data-bind="scope: 'customer'">
<span data-bind="text: customer().fullname ? $t('Welcome, %1!').replace('%1', customer().fullname) : 'Default welcome msg!'"></span>
</li></pre><p>当你像这样调用 scope 元素时</p><p><span style="color: #569cd6;">data-bind</span>=<span style="color: #ce9178;">"scope: 'customer'"</span></p><p>Magento将客户视图模型应用于此标签及其后代。</p><p>您可能想知道 - 客户视图模型到底是什么?!如果您在主页的源代码中再往下看一点,您应该会看到以下脚本标记</p><pre class="brush:bash;toolbar:false"><script type="text/x-magento-init">
{
"*": {
"Magento_Ui/js/core/app": {
"components": {
"customer": {
"component": "Magento_Customer/js/view/customer"
}
}
}
}
}
</script></pre><p>正如我们从本系列的第一篇文章中知道的那样,当Magento遇到带有属性的脚本标签时,它将text/x-magento-init*</p><p>初始化指定的 RequireJS 模块 (Magento_Ui/js/core/app)</p><p>调用该模块返回的函数,传入数据对象</p><p>RequireJS 模块是一个实例化 KnockoutJS 视图模型以与属性一起使用的模块。它的完整实现超出了本文的“范围”,但在高层次上,Magento将为每个配置为的RequireJS模块实例化一个新的javascript对象,并且该新对象成为视图模型。Magento_Ui/js/core/appscopecomponent</p><p>如果这没有意义,让我们通过上面的示例。Magento查看密钥,并看到一个密钥/对象对。x-magento-initcomponents</p><pre class="brush:bash;toolbar:false">"customer": {
"component": "Magento_Customer/js/view/customer"
}</pre><p>因此,对于密钥,Magento将运行等效于以下内容的代码。customer</p><p></p><pre class="brush:bash;toolbar:false">//gross over simplification
var ViewModelConstructor = requirejs('Magento_Customer/js/view/customer');
var viewModel = new ViewModelConstructor;
viewModelRegistry.save('customer', viewModel);</pre><p>如果特定组件对象中有额外的数据</p><pre class="brush:bash;toolbar:false">"customer": {
"component": "Magento_Customer/js/view/customer",
"extra_data":"something"
}</pre><p>Magento也会将该数据添加到视图模型中。</p><p>完成上述操作后,视图模型注册表将具有一个名为 的视图模型。这是Magento将应用于绑定的视图模型。<span style="color: #569cd6;">customerdata-bind</span>=<span style="color: #ce9178;">"scope: 'customer'"</span></p><p><span style="color: #6a9955;"></span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/lib/ko/bind/scope.js
define([
'ko',
'uiRegistry',
'jquery',
'mage/translate'
], function (ko, registry, $) {
'use strict';
//...
update: function (el, valueAccessor, allBindings, viewModel, bindingContext) {
var component = valueAccessor(),
apply = applyComponents.bind(this, el, bindingContext);
if (typeof component === 'string') {
registry.get(component, apply);
} else if (typeof component === 'function') {
component(apply);
}
}
//...
});</pre><p>它是从视图模型注册表中获取命名视图模型的行,然后以下代码实际上是在 KnockoutJS 中将对象作为视图模型应用的代码registry.get(component, apply)<span style="color: #6a9955;">;</span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/lib/ko/bind/scope.js
//the component variable is our viewModel
function applyComponents(el, bindingContext, component) {
component = bindingContext.createChildContext(component);
ko.utils.extend(component, {
$t: i18n
});
ko.utils.arrayForEach(el.childNodes, ko.cleanNode);
ko.applyBindingsToDescendants(component, el);
}</pre><p>该变量来自模块,该模块是 RequireJS 模块的别名。registryuiRegistryMagento_Ui/js/lib/registry/registry</p><pre class="brush:bash;toolbar:false">vendor/magento/module-ui/view/base/requirejs-config.js
17: uiRegistry: 'Magento_Ui/js/lib/registry/registry',</pre><p>如果很多东西飞过你的头,别担心。如果要查看特定范围的绑定中可用的数据,以下调试代码应指导您。</p><pre class="brush:bash;toolbar:false"><li class="greet welcome" data-bind="scope: 'customer'">
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
<!-- ... -->
</li></pre><p>如果您是有兴趣深入了解创建视图模型的真实代码(而不是我们上面的简化伪代码)的人之一,则可以从该模块开始。Magento_Ui/js/core/app</p><p><span style="color: #6a9955;"></span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/core/app.js
define([
'./renderer/types',
'./renderer/layout',
'Magento_Ui/js/lib/ko/initialize'
], function (types, layout) {
'use strict';
return function (data) {
types.set(data.types);
layout(data.components);
};
});</pre><p>此模块具有名为 的依赖项。正是在这个依赖模块中,Magento初始化视图模型,并将它们添加到视图模型注册表中。Magento_Ui/js/core/renderer/layout</p><p><span style="color: #6a9955;">#File: vendor/magento/module-ui/view/base/web/js/core/renderer/layout.js</span></p><p>代码在那里有点粗糙,但如果你需要知道这些视图模型是如何实例化的,那就是你可以找到它们的地方。</p><p><span style="color: #6a9955;">### 具有任何其他名称的组件</span></p><p>所有这一切中的一个粘性检票口是单词组件。这个绑定+系统基本上是对原生KnockoutJS组件系统的不同看法。scopex-magento-init</p><p>通过使用与KnockoutJS相同的组件术语,Magento开辟了一个混乱的新世界。甚至官方文档似乎对组件是什么或不是什么有点困惑。这就是一个大型软件团队的生活,左手不知道右手在做什么——身体的其他部分对第三只手从背部长出来感到害怕。</p><p>当与同事讨论这些功能或在Magento论坛上提问时,区分KnockoutJS组件和Magento组件非常重要。</p><p><span style="color: #6a9955;">### 2.1 候选版本中的更改</span></p><p>为了结束今天的比赛,我们将讨论Magento 2.1候选版本中对上述内容的一些更改。从概念上讲,系统仍然相同,但细节有一些变化。</p><p>首先,KnockoutJS的初始化现在发生在RequireJS模块中。Magento_Ui/js/lib/knockout/bootstrap</p><p><span style="color: #6a9955;"></span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/lib/knockout/bootstrap.js
define([
'ko',
'./template/engine',
'knockoutjs/knockout-es5',
'./bindings/bootstrap',
'./extender/observable_array',
'./extender/bound-nodes',
'domReady!'
], function (ko, templateEngine) {
'use strict';
ko.uid = 0;
ko.setTemplateEngine(templateEngine);
ko.applyBindings();
});</pre><p>请注意,Magento的核心开发人员将所有绑定加载移动到一个单独的模块,定义在Magento_Ui/js/lib/knockout/bindings/bootstrap</p><p><span style="color: #6a9955;">#File: vendor/magento/module-ui/view/base/web/js/lib/knockout/bindings/bootstrap.js</span></p><p>最后,返回的“Magento Javascript组件”有一个包含参数的更改方法签名,并且函数的参数清楚地表明它的签名也发生了变化。Magento_Ui/js/core/appmergelayoutlayout</p><p><span style="color: #6a9955;"></span></p><pre class="brush:bash;toolbar:false">#File: vendor/magento/module-ui/view/base/web/js/core/app.js
define([
'./renderer/types',
'./renderer/layout',
'../lib/knockout/bootstrap'
], function (types, layout) {
'use strict';
return function (data, merge) {
types.set(data.types);
layout(data.components, undefined, true, merge);
};
});</pre><p>除了对实现细节感兴趣的人感兴趣之外,这些变化还指出了Magento的javascript模块和框架正在迅速变化的事实,与PHP代码不同,Magento的RequireJS模块没有标记来表示稳定性。@api</p><p>除非你绝对需要,否则最好避免动态更改这些核心模块的行为,并尽可能保持你自己的JavaScript独立。</p><p><br/></p>