将 Office.js 连接到任何 JavaScript 框架

Office.js 与框架无关,可与任何客户端 JavaScript 框架或库无缝配合工作。 无论是使用 React、Angular、Vue、Svelte 还是任何其他框架进行生成,集成模式都是相同的:确保在应用程序呈现之前 Office.js 初始化。

注意

还可以使用服务器端框架(如 ASP.NET、PHP 和 Java)来生成 Office 加载项,但本文未介绍它们。 本文重点介绍在浏览器中运行的客户端 JavaScript 框架。

本文介绍用于将 Office.js 与客户端 JavaScript 框架集成的通用模式、重要注意事项,并提供跨多个框架的示例。

提示

本文面向使用首选 JavaScript 框架从头开始创建 Office 加载项的开发人员,或将 Office.js 集成到现有框架项目中。 如果使用 Office 加载项或 Microsoft 365 代理工具包的 Yeoman 生成器,则这些工具已提供正确的 Office.js 配置。

先决条件

快速入门:通用模式

无论选择哪种框架,都使用以下模式。

  1. 在 HTML <head>中引用 CDN 中的 Office.js。
  2. 调用 Office.onReady() 并等待完成。
  3. Office.js 准备就绪后初始化框架。
// Universal pattern - works with any framework.
Office.onReady((info) => {
  // Office.js is now ready.
  // Initialize your framework here.
  initializeYourFramework();
});

从 CDN 加载 Office.js

必须从 HTML 文件中的内容分发网络 (CDN) 引用 Office JavaScript API。 在 <head> HTML 页面的 部分中,在任何其他脚本标记或框架捆绑包引用之前添加以下<script>标记。

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>My Office Add-in</title>

  <!-- Office.js must be loaded from CDN, not bundled -->
  <script src="https://officeapis.public.onecdn.static.microsoft/1/office.js" type="text/javascript"></script>

  <!-- Your framework bundle loads after Office.js -->
</head>

重要

  • 从 CDN 加载 Office.js,并在 HTML 文件中引用它。 请勿在 JavaScript 或 TypeScript 代码中导入它。
  • Office.js 引用必须出现在 节中 <head> ,以确保在加载任何正文元素之前完全初始化 API。
  • 不要将 Office.js 与应用程序代码捆绑在一起。 始终从 CDN 引用它。

有关引用 Office.js(包括预览 API 和备用 CDN 终结点)的详细信息,请参阅 引用 Office JavaScript API 库。

在 Office.onReady 之后初始化框架

将 Office.js 与任何框架集成的关键是在回调中 Office.onReady() 初始化应用程序。 此方法可确保在框架开始呈现之前完全初始化 Office.js。 此初始化非常重要,因为 Office.js 需要:

  • 从 CDN 下载和缓存 API 库文件。
  • 初始化 Office 运行时环境。
  • 与 Office 应用程序建立通信。

如果框架在 Office.js 准备就绪之前呈现,则对 Office API 的调用会失败。 通过在 中 Office.onReady()初始化应用程序,可以保证应用程序代码运行时 Office.js 准备就绪。

示例

以下示例显示了跨不同框架的相同集成模式。 模式相同 - 只有框架的初始化方法更改。

React

// src/index.tsx
Office.onReady(() => {
  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(<App />);
});

Angular

// src/main.ts
Office.onReady(() => {
  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.error(err));
});

Vue

// src/main.ts
Office.onReady(() => {
  createApp(App).mount('#app');
});

苗条

// src/main.ts
Office.onReady(() => {
  new App({ target: document.getElementById('app') });
});

没有框架的简单 JavaScript

// src/app.js
Office.onReady((info) => {
  document.getElementById('run-button').onclick = run;

  if (info.host === Office.HostType.Excel) {
    console.log('Running in Excel');
  }
});

在应用程序中使用 Office.js API

完成) Office.js 初始化 (Office.onReady() 后,可以在外接程序中的任何位置调用 Office API。 根据需要使用框架的生命周期挂钩或事件处理程序调用 Office API。

// React example: Call an Office JS API in the useEffect lifecycle hook.
import { useEffect, useState } from 'react';

function MyComponent() {
  const [data, setData] = useState('');

  useEffect(() => {
    loadData();
  }, []);

  async function loadData() {
    await Excel.run(async (context) => {
      const range = context.workbook.getSelectedRange();
      range.load('values');
      await context.sync();

      // Update component state with the data from Excel.
      const value = range.values[0][0];
      setData(value);
    });
  }

  return <div>Selected cell: {data}</div>;
}

// Similar patterns for other frameworks:
// Angular: ngOnInit() { this.loadData(); }
// Vue: onMounted(() => { loadData(); })
// Svelte: onMount(() => { loadData(); })

TypeScript 支持

若要为 TypeScript 项目中 Office.js 启用 IntelliSense 和类型检查,请安装 DefinitelyTyped 中的类型定义。

npm install --save-dev @types/office-js

TypeScript 自动识别类型。 代码中不需要 import 语句,因为 Office.js 是从 CDN 全局加载的。

// TypeScript automatically recognizes Office types.
Office.onReady((info: Office.OfficeInfo) => {
  if (info.host === Office.HostType.Excel) {
    // TypeScript provides IntelliSense for Excel APIs.
  }
});

有关详细信息,请参阅 引用 Office JavaScript API 库。

其他注意事项

加载指示器

如果要在初始化 Office.js 时显示加载指示器,请在调用 Office.onReady() 之前显示它,并在回调中隐藏它。

// Show loading indicator.
document.getElementById('loading')!.style.display = 'block';

Office.onReady((info) => {
  // Hide loading indicator.
  document.getElementById('loading')!.style.display = 'none';

  // Initialize framework.
  initializeYourFramework();
});

若要使用具有自己的加载状态的框架提供更好的用户体验,请使用可立即显示的简单 HTML/CSS 加载程序。 然后,让框架在装载后接管它。

对话 API 和组件生命周期

Office 对话框 API 在单独的浏览器窗口中打开页面。 此行为对框架应用程序有重要影响:

  • 每个对话使用单独的框架实例创建新的 执行上下文 。
  • 该对话框运行自己的应用程序代码副本。
  • 必须在对话框页中调用 Office.onReady() 。
  • 主页和对话框窗口 不共享状态。
  • 上下文之间 不共享 会话存储。

如果使用框架路由器导航到对话路由,请记住,对话窗口会创建应用程序的全新实例。 它不会重复使用现有实例。

// Main page - opens a dialog.
Office.context.ui.displayDialogAsync(
  'https://localhost:3000/dialog-route',
  { height: 50, width: 50 },
  (result) => {
    if (result.status === Office.AsyncResultStatus.Succeeded) {
      const dialog = result.value;
      dialog.addEventHandler(Office.EventType.DialogMessageReceived, (arg) => {
        // Handle message from dialog.
      });
    } else {
      // Handle error opening the dialog.
      console.error(result.error);
    }
  }
);

// Dialog page - must also call Office.onReady.
Office.onReady(() => {
  // This is a separate framework instance.
  initializeYourFramework();
});

历史记录 API 解决方法

Office.js 将默认 的 Window.history 方法和 replaceStatepushStatenull替换为 。 如果框架或路由器依赖于这些方法 (React路由器、Vue 路由器、Angular路由器和其他) 中常见的方法,则需要缓存和还原它们。

将此代码添加到 HTML 文件,包装 Office.js 脚本标记:

<head>
  <!-- Cache history methods before Office.js loads -->
  <script type="text/javascript">
    window._historyCache = {
      replaceState: window.history.replaceState,
      pushState: window.history.pushState
    };
  </script>

  <!-- Load Office.js -->
  <script type="text/javascript" src="https://officeapis.public.onecdn.static.microsoft/1/office.js"></script>

  <!-- Restore history methods after Office.js loads -->
  <script type="text/javascript">
    window.history.replaceState = window._historyCache.replaceState;
    window.history.pushState = window._historyCache.pushState;
  </script>
</head>

注意

仅当应用程序使用客户端路由 (React 路由器、Vue 路由器、Angular路由器和其他) 时,才需要此解决方法。 不带路由的静态应用程序不需要此解决方法。

在 Office 应用程序外部进行测试

可以使用浏览器开发人员工具开发和测试加载项的 UI,而无需旁加载到 Office 中。 此方法可在开发过程中实现更快的迭代,并更易于调试 UI 组件。

在 Office 应用程序) 外部的常规浏览器中 (打开加载项时, Office.onReady() 仍会执行,但会同时 null 解析主机和平台属性。

Office.onReady((info) => {
  if (info?.host) {
    console.log(`Running in ${info.host} on ${info.platform}`);
  } else {
    console.log('Running outside of Office (development mode)');
  }

  // Initialize your framework, regardless of whether the add-in is running inside or outside of Office.
  initializeYourFramework();
});

生成工具和捆绑程序

新式 JavaScript 框架通常使用 Webpack、Vite、Rollup 或 esbuild 等生成工具。 配置生成时:

  • 请勿在 JavaScript 或 TypeScript 代码中导入或捆绑 Office.js。
  • 使用 <script> HTML 中的标记从 CDN 加载 Office.js。
  • 将捆绑程序配置为视为 Office 全局变量。

示例:使用 Vite 进行 TypeScript 配置

如果将 Vite 与 TypeScript 配合使用,则通常不需要为 Office.js 进行特殊的 Vite 配置。 包 @types/office-js 提供必要的类型定义。 但是,如果需要确保 Office.js 类型可用,请验证 :tsconfig.json

// tsconfig.json
{
  "compilerOptions": {
    "types": ["office-js"]
    // ... your other compiler options ...
  }
}

示例:Webpack 配置

// webpack.config.js
module.exports = {
  externals: {
    'office': 'Office'
  }
};

默认情况下, Office 外接程序的 Yeoman 生成器 生成的外接程序项目包括正确的生成配置。

网络阻止和防火墙

如果网络筛选器、防火墙或浏览器扩展阻止 Office.js CDN, Office.onReady() 则永远不会解析。 考虑为网络策略可能阻止 CDN 的企业方案实现超时。

let officeInitialized = false;

// Set a timeout.
setTimeout(() => {
  if (!officeInitialized) {
    console.error('Office.js failed to initialize. Network may be blocking CDN.');
    // Show error message to user.
  }
}, 10000); // 10 second timeout

Office.onReady((info) => {
  officeInitialized = true;
  initializeYourFramework();
});

有关 CDN 注意事项的详细信息,请参阅 引用 Office JavaScript API 库。

特定于框架的区域或反应性问题

某些框架使用区域或反应系统来跟踪状态更改。 在极少数情况下,Office API 调用不会触发 UI 更新,因为它们在框架的更改检测区域之外运行。

Angular:如果在调用 Office API 后 UI 未更新,请将代码包装在 中NgZone.run():

import { NgZone } from '@angular/core';

constructor(private zone: NgZone) {}

async loadDataFromExcel() {
  let cellValue: string;

  // Make Office API call
  await Excel.run(async (context) => {
    const range = context.workbook.getSelectedRange();
    range.load('values');
    await context.sync();
    cellValue = range.values[0][0];
  });

  // Update Angular component state inside zone
  this.zone.run(() => {
    this.myData = cellValue;
  });
}

另请参阅