Skip to main content

管理面板 API:钩子

🌐 Admin Panel API: Hooks

Page summary:

Hooks API 允许插件创建扩展点(createHookregister 中)并订阅它们(registerHookbootstrap 中)。Hooks 可以串行、瀑布式或并行运行。Strapi 为内容管理器的列表和编辑视图包含了预定义的 hooks。

🌐 The Hooks API lets plugins create extension points (createHook in register) and subscribe to them (registerHook in bootstrap). Hooks run in series, waterfall, or parallel. Strapi includes predefined hooks for the Content Manager's List and Edit views.

Hooks API 允许插件创建和注册钩子,即应用中插件可以添加个性化行为的位置。

🌐 The Hooks API allows a plugin to create and register hooks, i.e. places in the application where plugins can add personalized behavior.

Prerequisites

在深入了解本页的概念之前,请确保你已经:

🌐 Before diving deeper into the concepts on this page, please ensure you have:

创建钩子

🌐 Creating hooks

register生命周期期间使用createHook()创建钩子扩展点。这表明你的插件提供了一个其他插件可以订阅的扩展点。

🌐 Create hook extension points with createHook() during the register lifecycle. This declares that your plugin provides an extension point that other plugins can subscribe to.

my-plugin/admin/src/index.js
export default {
register(app) {
app.createHook('My-PLUGIN/MY_HOOK');
},
};
Note

为了插件之间可预测的互操作性,请使用稳定的命名空间钩子 ID,例如 my-plugin/my-hook

🌐 For predictable interoperability between plugins, use stable namespaced hook IDs such as my-plugin/my-hook.

订阅钩子

🌐 Subscribing to hooks

在所有插件加载完成后,在bootstrap生命周期中使用registerHook()订阅钩子。回调函数会接收来自钩子调用者的参数,并应返回(可选修改的)数据。

🌐 Subscribe to hooks with registerHook() during the bootstrap lifecycle, once all plugins are loaded. The callback receives arguments from the hook caller and should return the (optionally mutated) data.

my-other-plugin/admin/src/index.js
export default {
bootstrap(app) {
app.registerHook('My-PLUGIN/MY_HOOK', (...args) => {
console.log(args);

// important: return the mutated data
return args;
});
},
};

异步回调也被支持:

🌐 Async callbacks are also supported:

my-other-plugin/admin/src/index.js
export default {
bootstrap(app) {
app.registerHook('My-PLUGIN/MY_HOOK', async (data) => {
const enrichedData = await fetchExternalData(data);

// always return data for waterfall hooks
return enrichedData;
});
},
};

运行钩子

🌐 Running hooks

钩子可以在三种模式下运行:

🌐 Hooks can be run in 3 modes: | 模式 | 功能 | 返回值 ||---|---|---|| 串行 | runHookSeries | 每个函数的结果数组,按顺序 || 并行 | runHookParallel | 已解决的 Promise 结果数组,按顺序 || 瀑布 | runHookWaterfall | 应用所有转换后得到的单个值 |

Caution

对于 runHookWaterfall,每个订阅者必须返回转换后的值,以便链中的下一个订阅者接收它。不返回值将会打断链。

🌐 For runHookWaterfall, each subscriber must return the transformed value so that the next subscriber in the chain receives it. Failing to return a value will break the chain.

使用预定义的钩子

🌐 Using predefined hooks

Strapi 包含为内容管理器的列表和编辑视图预定义的钩子。

🌐 Strapi includes predefined hooks for the Content Manager's List and Edit views.

INJECT-COLUMN-IN-TABLE

Admin/CM/pages/ListView/inject-column-in-table 钩子可以在 内容管理器 的列表视图中添加或修改列:

🌐 The Admin/CM/pages/ListView/inject-column-in-table hook can add or mutate columns in the List View of the Content Manager:

runHookWaterfall(INJECT_COLUMN_IN_TABLE, {
displayedHeaders: ListFieldLayout[],
layout: ListFieldLayout,
});

以下示例订阅此钩子以添加自定义“外部 ID”列:

🌐 The following example subscribes to this hook to add a custom "External id" column:

my-plugin/admin/src/index.js
export default {
bootstrap(app) {
app.registerHook(
'Admin/CM/pages/ListView/inject-column-in-table',
({ displayedHeaders, layout }) => {
return {
displayedHeaders: [
...displayedHeaders,
{
attribute: { type: 'custom' },
label: 'External id',
name: 'externalId',
searchable: false,
sortable: false,
cellFormatter: (document) => document.externalId,
},
],
layout,
};
}
);
},
};

ListFieldLayout 和 ListLayout 类型定义

interface ListFieldLayout {
/**
* The attribute data from the content-type's schema for the field
*/
attribute: Attribute.Any | { type: 'custom' };
/**
* Typically used by plugins to render a custom cell
*/
cellFormatter?: (
data: Document,
header: Omit<ListFieldLayout, 'cellFormatter'>,
{ collectionType, model }: { collectionType: string; model: string }
) => React.ReactNode;
label: string | MessageDescriptor;
/**
* the name of the attribute we use to display the actual name e.g. relations
* are just ids, so we use the mainField to display something meaninginful by
* looking at the target's schema
*/
mainField?: string;
name: string;
searchable?: boolean;
sortable?: boolean;
}

interface ListLayout {
layout: ListFieldLayout[];
components?: never;
metadatas: {
[K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['list'];
};
options: LayoutOptions;
settings: LayoutSettings;
}

type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object;

interface LayoutSettings extends Contracts.ContentTypes.Settings {
displayName?: string;
icon?: never;
}

MUTATE-EDIT-VIEW-LAYOUT

Admin/CM/pages/EditView/mutate-edit-view-layout 钩子可以修改 内容管理器 的编辑视图布局。

🌐 The Admin/CM/pages/EditView/mutate-edit-view-layout hook can mutate the Edit View layout of the Content Manager.

下面的示例订阅这个钩子以将所有字段强制为全宽:

🌐 The following example subscribes to this hook to force all fields to full width:

my-plugin/admin/src/index.js
export default {
bootstrap(app) {
app.registerHook(
'Admin/CM/pages/EditView/mutate-edit-view-layout',
({ layout, ...rest }) => {
// Force all fields to full width in the default edit layout
const updatedLayout = layout.map((rowGroup) =>
rowGroup.map((row) => row.map((field) => ({ ...field, size: 12 })))
);

return {
...rest,
layout: updatedLayout,
};
}
);
},
};

EditLayout 和 EditFieldLayout 类型定义:

interface EditLayout {
layout: Array<Array<EditFieldLayout[]>>;
components: {
[uid: string]: {
layout: Array<EditFieldLayout[]>;
settings: Contracts.Components.ComponentConfiguration['settings'] & {
displayName?: string;
icon?: string;
};
};
};
metadatas: {
[K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['edit'];
};
options: LayoutOptions;
settings: LayoutSettings;
}

interface EditFieldSharedProps extends Omit<InputProps, 'hint' | 'type'> {
hint?: string;
mainField?: string;
size: number;
unique?: boolean;
visible?: boolean;
}

/**
* Map over all the types in Attribute Types and use that to create a union of new types where the attribute type
* is under the property attribute and the type is under the property type.
*/
type EditFieldLayout = {
[K in Attribute.Kind]: EditFieldSharedProps & {
attribute: Extract<Attribute.Any, { type: K }>;
type: K;
};
}[Attribute.Kind];

type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object;

interface LayoutSettings extends Contracts.ContentTypes.Settings {
displayName?: string;
icon?: never;
}
Note

此处记录的 EditLayoutListLayout 形状来自 useDocumentLayout 钩子(参见 source code)。内部包命名可能有所不同,但插件作者应依赖本页中公开的 EditLayoutListLayout 形状。