如何使用自定义SlotFill扩展插件
WordPress 最强大的功能之一是其可扩展性,几乎可以实现任何功能。自 2.0 版本以来,开发者一直使用 Hooks API 来扩展 WordPress 核心。随着 5.0 版本的发布,SlotFill 系统被引入,允许扩展区块编辑器和站点编辑器界面。
如果你不熟悉 SlotFill 系统及其如何用于扩展区块编辑器和站点编辑器,请先阅读 如何通过 SlotFill 系统扩展 WordPress。
SlotFill 系统不仅可用于扩展现有 UI,也可用于扩展你的自定义实现——这正是本文的重点。
使用场景
你可能出于多种原因需要扩展。最突出的例子来自插件生态系统,这类扩展为开发者带来了实际收益。
你可以将用途分为内部和外部。
内部使用的例子是“免费增值”定价模式,即提供一个具有基本功能集的插件。然后,如果用户购买许可证,你可以解锁更高级的功能集。
在外部扩展中,你设置一组扩展点,其他开发者可以使用这些点来为基础插件添加功能。
如果你仅使用 PHP 和 Hooks API 来创建这些扩展点,那么这种区别就无关紧要了,因为基于 PHP 的钩子和过滤器在相关源代码加载后即可用。
然而,对于 SlotFill 来说,情况稍微复杂一些。这两种方法的区别在于自定义 SlotFill 是向外暴露的,还是仅在插件代码库内部可用。
那么,让我们来构建一些自定义 SlotFill 并暴露它们。
创建自定义 Slot 和 Fill
一个 SlotFill 包含两个组件:一个 Slot 和一个 Fill。Slot 控制 Fill 的渲染位置,两者通过一个共同的 name 属性连接。
你可以直接从 @wordpress/components 包导入 Slot 和 Fill 组件并手动添加名称,但辅助函数 createSlotFill 会为你完成所有这些工作——只需传递你的 SlotFill 名称。
import { createSlotFill } from '@wordpress/components';
const { Fill, Slot } = createSlotFill( 'BasicCreateSlotFill' );
现在你需要一个它们可以存在于其中的自定义组件。这将帮助你标识你的 SlotFill,避免命名冲突,并以与 WordPress 核心现有 SlotFill 相同的方式工作。
让我们看一个例子。
首先创建一个名为 BasicCreateSlotFill 的新组件(最佳实践:以传递给 createSlotFill 的值命名你的组件)。你的新组件应包含 Fill 组件并包装传递给它的任何子组件。然后将 Slot 组件分配给组件的一个 Slot 属性,并导出整个内容以供使用。
import { createSlotFill } from '@wordpress/components';
const { Fill, Slot } = createSlotFill( 'BasicCreateSlotFill' );
const BasicCreateSlotFill = ( { children } ) => {
return <Fill>{ children }</Fill>;
};
BasicCreateSlotFill.Slot = Slot;
export default BasicCreateSlotFill;
现在你可以在代码中使用你的自定义 SlotFill 了!第一步是暴露 Slot 属性。
const SettingsScreen = () => (
<Panel>
<PanelBody title="Basic" initialOpen={ false }>
<PanelRow>
<BasicCreateSlotFill.Slot />
</PanelRow>
</PanelBody>
</Panel>
);
接下来,注册一个插件来定位该 Slot。
registerPlugin( 'custom-slot-fills', {
render: () => (
<BasicCreateSlotFill>
<p>{ `This appears where <BasicCreateSlotFill.Slot/> is rendered` }</p>
</BasicCreateSlotFill>
),
} );
还记得你在构建 BasicSlotFill 组件时将 children 包装在 Fill 组件中吗?children 是你在 registerPlugin 调用中包装在 BasicSlotFill 组件内的任何元素。在上面的例子中,p 标签及其文本内容是子元素,它们将渲染在 <BasicCreateSlotFill.Slot /> 所在的位置。
参考代码库
本文附带一个在 GitHub 上可用的代码仓库。它提供了上述两种用例的一些示例。请花点时间按照设置说明在你的计算机上运行示例。
内部示例:“免费增值”功能
在示例代码就位后,导航到 plugins/freemium-inc 文件夹。此文件夹包含此示例的所有代码,是一个使用 @wordpress/create-block 包搭建的标准 WordPress 插件,包含一个区块。
该区块本身除了输出默认消息外没有其他价值。插件中添加了两个新文件:
src/slotfills.js:用于存储我们的自定义 SlotFillwebpack.config.js:更新内置构建过程以创建一个包含我们高级功能的单独文件。
理论
这里的方法是在区块的 InspectorControls 中暴露一个自定义 SlotFill,然后有条件地加载一个单独的文件,该文件使用 registerPlugin 来添加更多控件(如果“高级”功能已解锁)。
实现
打开 src/slotfills.js 文件并创建一个名为 <PremiumFeatures /> 的自定义 SlotFill。
/**
* WordPress dependencies
*/
import { createSlotFill } from '@wordpress/components';
/**
* Create our Slot and Fill components
*/
const { Fill, Slot } = createSlotFill( 'PremiumFeatures' );
const PremiumFeatures = ( { children } ) => <Fill>{ children }</Fill>;
PremiumFeatures.Slot = ( { fillProps } ) => (
<Slot fillProps={ fillProps }>
{ ( fills ) => {
return fills.length ? fills : null;
} }
</Slot>
);
export default PremiumFeatures;
接下来,通过在 src/edit.js 中添加代码,在区块的 InspectorControls 中暴露 PremiumFeatures.Slot。
export default function Edit( props ) {
const {
attributes: { makeItFaster },
setAttributes,
} = props;
return (
<>
<InspectorControls>
<PanelBody
title={ __( 'Freemium Inc. Settings', 'developer-blog' ) }
>
<CheckboxControl
checked={ makeItFaster }
label={ __(
'Make my site a little faster',
'developer-blog'
) }
onChange={ () =>
setAttributes( { makeItFaster: ! makeItFaster } )
}
/>
<PremiumFeatures.Slot fillProps={ { ...props } } />
</PanelBody>
</InspectorControls>
<p { ...useBlockProps() }>
{ __( 'Freemium INC Example', 'developer-blog' ) }
</p>
</>
);
}
请注意,你正在通过 fillProps 属性将所有区块 props 传递给 Slot。这允许你的扩展访问区块可以访问的所有内容,例如其 attributes、setAttributes 函数等。如果你的扩展需要更新区块属性或响应这些属性的变化,这一点非常重要。
接下来,创建一个名为 premium/index.js 的单独文件来保存插件的高级项目。
/**
* WordPress dependencies
*/
import { registerPlugin } from '@wordpress/plugins';
import { __ } from '@wordpress/i18n';
import { CheckboxControl } from '@wordpress/components';
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import PremiumFeatures from '../src/slotfills';
registerPlugin( 'freemium-inc-premium-items', {
render: () => {
return (
<PremiumFeatures>
{ ( { attributes, setAttributes } ) => {
const { tenXMode } = attributes;
return (
<>
<h2>
{ __( 'Premium Features', 'developer-blog' ) }
</h2>
<CheckboxControl
label={ __(
'🔥🔥Enable 10x mode🔥🔥',
'developer-blog'
) }
help={ __(
'10x mode will make your site 10x faster.',
'developer-blog'
) }
checked={ tenXMode }
onChange={ () =>
setAttributes( { tenXMode: ! tenXMode } )
}
/>
</>
);
} }
</PremiumFeatures>
);
},
} );
最后,更新构建过程以包含 premium/index.js 并输出一个我们可以单独加载的文件。由 @wordpress/scripts 包提供的构建过程可以通过在插件根目录添加 webpack.config.js 文件并扩展默认配置来扩展。
在这种情况下,你需要添加一个新的 entry 来告诉 Webpack 它应该处理一个新文件并单独输出它。
// Import the original config from the @wordpress/scripts package.
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
// Import the helper to find and generate the entry points in the src directory
const { getWebpackEntryPoints } = require( '@wordpress/scripts/utils/config' );
// Add any a new entry point by extending the webpack config.
module.exports = {
...defaultConfig,
entry: {
...getWebpackEntryPoints(),
premium: './premium/index.js',
},
};
新的 entry 告诉 Webpack 它应该在 ./premium/index.js 中查找一个新文件,并以 premium 作为基础文件名输出它(以及任何相关文件)。现在使用 npm run start 或 npm run build 重新启动构建过程,让 Webpack 识别配置文件的更改。
查看 build 目录,你应该会看到一些新添加的文件。添加的确切文件取决于你运行的构建命令。
premium.asset.phppremium.jspremium.js.map(仅在使用start命令时添加)
现在构建已更新并正常工作,最后一步是有条件地加载 premium.js 文件。这可能会变得非常复杂,但出于本文的目的,代码使用一个设置为 false 的变量。
/**
* Determine if the plugin has been upgraded and enqueue the assets if so.
*/
function maybe_add_premium_features() {
// This can be done any number of ways.
$user_has_upgraded = false;
$premium_assets_file = plugin_dir_path( __FILE__ ) . 'build/premium.asset.php';
if ( $user_has_upgraded && file_exists( $premium_assets_file ) ) {
$assets = include $premium_assets_file;
wp_enqueue_script(
'freemium-inc-premium',
plugin_dir_url( __FILE__ ) . 'build/premium.js',
$assets['dependencies'],
$assets['version'],
true
);
}
}
add_action( 'enqueue_block_editor_assets', 'maybe_add_premium_features' );
插入区块,然后在代码中将 $user_has_upgraded 设置为 true 以查看高级设置。
外部示例:扩展 Advanced Query Loop
创建外部示例需要一个暴露 SlotFill 以供使用的现有代码库。在本教程中,你将扩展我的 Advanced Query Loop (AQL) 插件。你可以在 GitHub 仓库中查看 AQL 的所有代码,包括 SlotFill 的文档。
理论
外部代码库对扩展者不可用,因此必须采取额外步骤来暴露 Slot 以供使用。幸运的是,这可以通过使用带有自定义 webpack.config.js 文件的 @wordpress/scripts 包来完成。
使用下面取自 AQL 的示例,你可以通过向 output 属性添加 library,创建一个附加到 window 对象的新 JavaScript 对象来存储 Slot(或任何需要的项目)。
// Import the original config from the @wordpress/scripts package.
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
// Import the helper to find and generate the entry points in the src directory
const { getWebpackEntryPoints } = require( '@wordpress/scripts/utils/config' );
// Add any a new entry point by extending the webpack config.
module.exports = {
...defaultConfig,
entry: {
...getWebpackEntryPoints(),
variations: './src/variations/index.js',
},
output: {
...defaultConfig.output,
library: [ 'aql' ],
},
};
现在,从 /src/variations/index.js 导出的任何项目都将暴露在全局 aql 对象中。
/**
* WordPress dependencies
*/
import { registerBlockVariation } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import './controls';
import AQLIcon from '../components/icons';
import AQLControls from '../slots/aql-controls';
import AQLControlsInheritedQuery from '../slots/aql-controls-inherited-query';
const AQL = 'advanced-query-loop';
registerBlockVariation( 'core/query', {
name: AQL,
title: __( 'Advanced Query Loop', 'advanced-query-loop' ),
description: __( 'Create advanced queries', 'advanced-query-loop' ),
icon: AQLIcon,
isActive: [ 'namespace' ],
attributes: {
namespace: AQL,
},
scope: [ 'inserter', 'transform' ],
} );
export { AQL, AQLControls, AQLControlsInheritedQuery };
实现
由于大部分工作是在外部代码库中完成的,你只需要像使用任何其他 Slot 一样导入和使用它们。
在示例仓库中,导航到 plugins/aql-extension 目录以查看此示例的所有代码。这又是一个简单的 WordPress 插件,带有一个自定义的 webpack.config.js,用于构建存储在 aql-extension/slotfills/index.js 中的单个文件,其中自定义 Fill 使用 registerPlugin 注册:
/**
* WordPress dependencies
*/
const { AQLControls, AQLControlsInheritedQuery } = window.aql;
import { registerPlugin } from '@wordpress/plugins';
import { ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
const LoggedInUserControl = ( { attributes, setAttributes } ) => {
const { query: { authorContent = false } = {} } = attributes;
return (
<>
<ToggleControl
label={ __( 'Show content for logged in user only' ) }
checked={ authorContent === true }
onChange={ () => {
setAttributes( {
query: {
...attributes.query,
authorContent: ! authorContent,
},
} );
} }
/>
</>
);
};
registerPlugin( 'aql-extension', {
render: () => {
return (
<>
<AQLControls>
{ ( props ) => <LoggedInUserControl { ...props } /> }
</AQLControls>
<AQLControlsInheritedQuery>
{ ( props ) => <LoggedInUserControl { ...props } /> }
</AQLControlsInheritedQuery>
</>
);
},
} );
Advanced Query Loop 插件暴露了两个 Slot:一个用于查询被继承时,另一个用于未被继承时。此代码将新控件添加到两者中。
从 SlotFill 的角度来看,这是一个完整的示例,你现在将在“高级查询设置”选项卡下获得一个新的控件,用于仅显示登录用户的文章。
此示例中有一些额外的 PHP 代码使其能与 AQL 一起工作。虽然解释该代码超出了本文的范围,但欢迎你探索它!