SPFx应用自定义器调用组件报错:Cannot read properties of undefined (reading 'id')
SPFx Application Customizer加载报错:Cannot read properties of undefined (reading 'id')
问题场景
开发SPFx Application Customizer,需求是在主组件中调用自定义React组件,但执行gulp serve后,浏览器控制台出现如下报错:
sp-pages-assembly_en-us_3edfa0c17b40c4b8bbbaae5e3a144e6a.js:207 Could not load emergency-banner-application-customizer in require. TypeError: Cannot read properties of undefined (reading 'id')
相关代码
1. src\extensions\emergencyBanner\EmergencyBannerApplicationCustomizer.ts
import { Log } from '@microsoft/sp-core-library'; // import * as React from 'react'; // import * as ReactDom from "react-dom"; import { BaseApplicationCustomizer, PlaceholderContent, PlaceholderName, } from "@microsoft/sp-application-base"; import { Dialog } from '@microsoft/sp-dialog'; import * as strings from 'EmergencyBannerApplicationCustomizerStrings'; import * as React from 'react'; import { IMainComponentProps } from "./components/IMainComponentProps"; import MainComponent from "./components/MainComponent"; // import { spfi, SPFx } from "@pnp/sp"; // import { IMainComponentProps } from "./components/IMainComponentProps"; // import MainComponent from "./components/MainComponent"; const LOG_SOURCE: string = 'EmergencyBannerApplicationCustomizer'; export interface IEmergencyBannerApplicationCustomizerProperties { testMessage: string; } export default class EmergencyBannerApplicationCustomizer extends BaseApplicationCustomizer<IEmergencyBannerApplicationCustomizerProperties> { private static _topPlaceholder: PlaceholderContent | undefined; public onDispose() { if ( EmergencyBannerApplicationCustomizer._topPlaceholder && EmergencyBannerApplicationCustomizer._topPlaceholder.domElement ) { // ReactDom.unmountComponentAtNode( // EmergencyBannerApplicationCustomizer._topPlaceholder.domElement // ); } } private render() { if ( this.context.placeholderProvider.placeholderNames.indexOf( PlaceholderName.Top ) !== -1 ) { if ( !EmergencyBannerApplicationCustomizer._topPlaceholder || !EmergencyBannerApplicationCustomizer._topPlaceholder.domElement ) { EmergencyBannerApplicationCustomizer._topPlaceholder = this.context.placeholderProvider.tryCreateContent( PlaceholderName.Top, { onDispose: this.onDispose, } ); } this.startReactRender(); } else { console.log( `The following placeholder names are available`, this.context.placeholderProvider.placeholderNames ); } } private startReactRender(): void { if ( !EmergencyBannerApplicationCustomizer._topPlaceholder || !EmergencyBannerApplicationCustomizer._topPlaceholder.domElement ) { EmergencyBannerApplicationCustomizer._topPlaceholder = this.context.placeholderProvider.tryCreateContent( PlaceholderName.Top, { onDispose: this.onDispose, } ); } if ( EmergencyBannerApplicationCustomizer._topPlaceholder && EmergencyBannerApplicationCustomizer._topPlaceholder.domElement ) { const element: React.ReactElement<IMainComponentProps> = React.createElement(MainComponent, { description: "Test" }); if (this.properties) { let topString: string = this.properties.testMessage; if (!topString) { topString = "(Top property was not defined.)"; } if (EmergencyBannerApplicationCustomizer._topPlaceholder.domElement) { EmergencyBannerApplicationCustomizer._topPlaceholder.domElement.innerHTML = ` <div> <div> <i class="ms-Icon ms-Icon--Info" aria-hidden="true"></i> ${escape( topString )} </div> </div>`; } } } else { console.log( "DOM element of the header is undefined. Start to re-render." ); this.render(); } } public onInit(): Promise<void> { Log.info(LOG_SOURCE, `Initialized ${strings.Title}`); let message: string = this.properties.testMessage; if (!message) { message = '(No properties were provided.)'; } this.context.application.navigatedEvent.add(this, () => { this.startReactRender(); }); Dialog.alert(`Hello from ${strings.Title}:\n\n${message}`).catch(() => { /* handle error */ }); return Promise.resolve(); } }
2. src\extensions\emergencyBanner\components\MainComponent.tsx
import * as React from "react"; import { IMainComponentProps } from "./IMainComponentProps"; const MainComponent: React.FunctionComponent<IMainComponentProps> = ({ description }) => { return (<div>{description}</div>) }; export default MainComponent;
3. src\extensions\emergencyBanner\components\IMainComponentProps.ts
import { ApplicationCustomizerContext } from "@microsoft/sp-application-base"; import { SPFI } from "@pnp/sp"; export interface IMainComponentProps { // context: ApplicationCustomizerContext; // spfi: SPFI; description: string; }
解决方案
报错核心原因是React组件未正确渲染,且代码逻辑存在冲突,修复步骤如下:
1. 补全ReactDOM依赖
取消注释ReactDOM的导入语句,React需要它来将组件渲染到DOM:
import * as React from 'react'; import * as ReactDOM from "react-dom";
2. 修复组件渲染逻辑
原代码创建了React元素但未执行渲染,反而用innerHTML覆盖占位符DOM,导致React无法正确挂载组件。修改startReactRender方法:
private startReactRender(): void { if ( !EmergencyBannerApplicationCustomizer._topPlaceholder || !EmergencyBannerApplicationCustomizer._topPlaceholder.domElement ) { EmergencyBannerApplicationCustomizer._topPlaceholder = this.context.placeholderProvider.tryCreateContent( PlaceholderName.Top, { onDispose: this.onDispose, } ); } if ( EmergencyBannerApplicationCustomizer._topPlaceholder && EmergencyBannerApplicationCustomizer._topPlaceholder.domElement ) { // 渲染自定义React组件 const element: React.ReactElement<IMainComponentProps> = React.createElement(MainComponent, { description: this.properties?.testMessage || "(Top property was not defined.)" }); ReactDOM.render(element, EmergencyBannerApplicationCustomizer._topPlaceholder.domElement); } else { console.log( "DOM element of the header is undefined. Start to re-render." ); this.render(); } }
3. 恢复组件卸载逻辑
取消onDispose中注释的代码,避免内存泄漏:
public onDispose() { if ( EmergencyBannerApplicationCustomizer._topPlaceholder && EmergencyBannerApplicationCustomizer._topPlaceholder.domElement ) { ReactDOM.unmountComponentAtNode( EmergencyBannerApplicationCustomizer._topPlaceholder.domElement ); } }
4. 触发初始渲染
在onInit方法中调用this.render(),确保页面初始加载时组件能渲染:
public onInit(): Promise<void> { Log.info(LOG_SOURCE, `Initialized ${strings.Title}`); let message: string = this.properties.testMessage; if (!message) { message = '(No properties were provided.)'; } this.context.application.navigatedEvent.add(this, () => { this.startReactRender(); }); // 触发初始渲染 this.render(); Dialog.alert(`Hello from ${strings.Title}:\n\n${message}`).catch(() => { /* handle error */ }); return Promise.resolve(); }
完成以上修改后,重新执行gulp serve,组件即可正常加载渲染。
内容的提问来源于stack exchange,提问作者Hawk
相关产品推荐
相关产品推荐

