You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 03:57:15