管理面板 API:Hooks
页面摘要: Hooks API 让插件能够创建扩展点(在
register中调用createHook)并订阅它们(在bootstrap中调用registerHook)。Hook 可以按 series(串行)、waterfall(瀑布流)或 parallel(并行)方式运行。Strapi 为内容管理器的列表视图和编辑视图提供了预定义的 hooks。
Hooks API 允许一个插件创建并注册 hooks,即应用中插件可以添加个性化行为的位置。
在深入阅读本页概念之前,请确保你已经:
- 创建了一个 Strapi 插件,
- 阅读并理解了 Admin Panel API 的基础知识。
创建 hooks
在 register 生命周期内,使用 createHook() 创建 hook 扩展点。这会声明你的插件提供了一个扩展点,其他插件可以订阅它。
JavaScript
export default {
register(app) {
app.createHook('My-PLUGIN/MY_HOOK');
},
};
TypeScript
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
register(app: StrapiApp) {
app.createHook('My-PLUGIN/MY_HOOK');
},
};
为保证插件之间的可预期互操作性,请使用稳定的、带命名空间的 hook ID,例如 my-plugin/my-hook。
订阅 hooks
在 bootstrap 生命周期内(即所有插件都已加载后),使用 registerHook() 订阅 hooks。回调函数会接收到来自 hook 调用方的参数,并应返回(可选地被修改过的)数据。
JavaScript
export default {
bootstrap(app) {
app.registerHook('My-PLUGIN/MY_HOOK', (...args) => {
console.log(args);
// important: return the mutated data
return args;
});
},
};
TypeScript
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook('My-PLUGIN/MY_HOOK', (...args: unknown[]) => {
console.log(args);
// important: return the mutated data
return args;
});
},
};
异步回调同样受支持:
JavaScript
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;
});
},
};
TypeScript
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
app.registerHook('My-PLUGIN/MY_HOOK', async (data: unknown) => {
const enrichedData = await fetchExternalData(data);
// always return data for waterfall hooks
return enrichedData;
});
},
};
运行 hooks
Hook 可以按 3 种模式运行:
| Mode | Function | Return value |
|---|---|---|
| Series | runHookSeries | 按顺序返回每个函数结果的数组 |
| Parallel | runHookParallel | 按顺序返回的已决议 promise 结果数组 |
| Waterfall | runHookWaterfall | 依次应用所有转换后的单一值 |
对于 runHookWaterfall,每个订阅者都必须返回转换后的值,以便链中的下一个订阅者能够接收到它。未能返回值将导致链中断。
使用预定义 hooks
Strapi 为内容管理器的列表视图和编辑视图提供了预定义的 hooks。
INJECT-COLUMN-IN-TABLE
Admin/CM/pages/ListView/inject-column-in-table hook 可以向 内容管理器 的列表视图添加或修改列:
runHookWaterfall(INJECT_COLUMN_IN_TABLE, {
displayedHeaders: ListFieldLayout[],
layout: ListFieldLayout,
});
以下示例订阅此 hook,以添加一个自定义的 “External id” 列:
JavaScript
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,
};
}
);
},
};
TypeScript
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
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 hook 可以修改 内容管理器 的编辑视图布局。
以下示例订阅此 hook,以强制所有字段占据整行宽度:
JavaScript
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,
};
}
);
},
};
TypeScript
import type { StrapiApp } from '@strapi/admin/strapi-admin';
export default {
bootstrap(app: StrapiApp) {
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;
}
此处记录的 EditLayout 与 ListLayout 形状来自 useDocumentLayout hook(见 源代码)。内部包命名可能有所不同,但插件作者应依赖本页面中暴露的 EditLayout 和 ListLayout 形状。