还在用插件修改 jsLayout 的属性吗?赶紧停下来,学习一种更优雅的方法,使用你自己的布局处理器来定制 Magento 的结账页面。
Magento 中的自定义布局处理器是什么?要回答这个问题,我们需要先回顾一下,了解 Magento 中页面布局的渲染方式。具体来说,就是那些用 JavaScript 渲染的页面,比如 Magento 的单页结账页面。
Magento 结账的 JavaScript 初始化过程概述
Magento 的结账页面被认为是“单页”结账,因为初始页面加载了服务器端路由,并使用布局 XML 进行渲染。我们可以通过打开 Magento Checkoutroutes.xml
文件来确认这一点:
Magento_Checkout::etc/frontend/routes.xml
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="checkout" frontName="checkout">
<module name="Magento_Checkout" />
</route>
</router>
</config>
并确认frontName="checkout"
,它将控制以 开头的所有 URL /checkout
,包括 Magento 的结帐。
因为我们知道该/checkout
路由使用命名约定来加载布局 XML 文件checkout_index_index.xml
,所以此 Checkout 模块中的相应布局文件将应用于此路由:
Magento_Checkout::view/frontend/layout/checkout_index_index.xml
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="checkout" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Magento\Checkout\Block\Onepage" name="checkout.root" template="Magento_Checkout::onepage.phtml" cacheable="false">
<arguments>
<argument name="jsLayout" xsi:type="array">
...
你会注意到,这个布局 XML 文件引用了主内容容器,并向该容器添加了一个名为 checkout.root 的 。该块加载 onepage.phtml 模板文件,然后使用 script 标签初始化 JavaScript 进程:
Magento_Checkout::view/frontend/web/onepage.phtml
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/** @var $block \Magento\Checkout\Block\Onepage */
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
?>
<div id="checkout" data-bind="scope:'checkout'" class="checkout-container">
...
<script type="text/x-magento-init">
{
"#checkout": {
"Magento_Ui/js/core/app": <?= /* @noEscape */ $block->getJsLayout() ?>
}
}
</script>
...
这段神奇的小代码将结帐渲染过程的剩余部分交给了 JavaScript。
如果您对此感兴趣,那么学习更多关于块类的知识可能会帮助您理解它们与视图模型相比如何工作。

jsLayout 从何而来
在上面的 PHTML 模板中,$block
变量被类型提示为Onepage
类:
/** @var $block \Magento\Checkout\Block\Onepage */
这告诉我们$block->getJsLayout()
引用\Magento\Checkout\Block\Onepage::getJsLayout()
。当我们检查这个函数时,我们可以看到这个魔法发生在哪里:
Magento_Checkout::Block/Onepage.php
...
/**
* @inheritdoc
*/
public function getJsLayout()
{
foreach ($this->layoutProcessors as $processor) {
$this->jsLayout = $processor->process($this->jsLayout);
}
return $this->serializer->serialize($this->jsLayout);
}
...
这里我们终于看到了一个对“布局处理器”的引用。这段代码循环遍历所有布局处理器,并依次修改 jsLayout。
布局处理器:一个例子
Magento Checkout 模块中有一个默认的布局处理器。如果我们查看它的process()
功能,我们会注意到它执行了一些与地址属性、送货和账单字段相关的额外任务:
/**
* Process js Layout of block
*
* @param array $jsLayout
* @return array
*/
public function process($jsLayout)
{
$attributesToConvert = [
'prefix' => [$this->options, 'getNamePrefixOptions'],
'suffix' => [$this->options, 'getNameSuffixOptions'],
];
$elements = $this->getAddressAttributes();
$elements = $this->convertElementsToSelect($elements, $attributesToConvert);
// The following code is a workaround for custom address attributes
if (isset(
$jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']
['children']
)) {
$jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
['payment']['children'] = $this->processPaymentChildrenComponents(
$jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
['payment']['children'],
$elements
);
}
if (isset(
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['step-config']['children']['shipping-rates-validation']['children']
)) {
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['step-config']['children']['shipping-rates-validation']['children'] =
$this->processShippingChildrenComponents(
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['step-config']['children']['shipping-rates-validation']['children']
);
}
if (isset(
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']
['shippingAddress']['children']['shipping-address-fieldset']['children']
)) {
$fields = $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']
['children']['shippingAddress']['children']['shipping-address-fieldset']['children'];
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']
['children']['shippingAddress']['children']['shipping-address-fieldset']['children'] = $this->merger->merge(
$elements,
'checkoutProvider',
'shippingAddress',
$fields
);
}
return $jsLayout;
}