通过


将 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://appsforoffice.microsoft.com/lib/1/hosted/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://appsforoffice.microsoft.com/lib/1/hosted/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;
  });
}

另请参阅