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

ReactJS项目中彻底规避Office.js未完全加载错误的方案咨询

问题

我们开发Excel插件时偶尔会遇到以下错误:

Error: Office.js has not fully loaded. Your app must call "Office.onReady()" as part of it's loading sequence (or set the "Office.initialize" function). If your app has this functionality, try reloading this page.

之前这个错误影响不大,但在Mac版Excel中出现时,任务窗格会变成空白页面,用户必须重新加载才能恢复正常。

我们的插件基于ReactJS和dva开发,现有核心代码如下:

frontend/src/index.tsx

import 'react-app-polyfill/ie11';
import 'react-app-polyfill/stable';
import dva from 'dva';
import router from './router';
import AuthModel from './models/auth';
import SubscribtionModel from './models/subscription';
import AppModel from './models/app';
import SpreadsheetModel from './models/spreadsheet';
import UsageModel from './models/usage';
import SettingsModel from './models/settings';
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';

initializeIcons();

const app = dva();

app.model(AuthModel);
app.model(SubscribtionModel)
app.model(AppModel);
app.model(SpreadsheetModel);
app.model(UsageModel);
app.model(SettingsModel);

app.router(router);

app.start('#root');

src/router.tsx

import React from 'react';
import { routerRedux, Switch, Route } from 'dva/router';
import Layout from './layouts';
import LoginPage from './components/LoginPage';
import Welcome from './components/welcome';
import SocialLoginSuccess from './components/socialLoginSuccess';
import { AwaitPromiseThenRender } from './components/AwaitPromiseThenRender';

const { ConnectedRouter } = routerRedux;
function RouterConfig({ history }: any) {
  //@ts-ignore
  return (
    <AwaitPromiseThenRender
      //@ts-ignore
      promise={typeof window.__$$notInOffice !== "undefined" ? Promise.resolve(true) : Office.onReady()}
    >
      <ConnectedRouter history={history}>
        <Layout>
          <Switch>
            <Route path="/sign">
              <LoginPage />
            </Route>
            <Route path="/home">
              <Welcome />
            </Route>
            <Route path="/app">
              <App />
            </Route>
            ... ...
            ... ...
        </Layout>
      </ConnectedRouter>
    </AwaitPromiseThenRender>
  );
}

export default RouterConfig;

src/components/AwaitPromiseThenRender/index.tsx

import React, { Component } from 'react';

interface IProps {
  promise: Promise<any>;
  children: React.ReactNode;
}

interface IState {
  promiseHasResolved: boolean;
}

export class AwaitPromiseThenRender extends Component<IProps> {
  state: IState = { promiseHasResolved: false };
  constructor(props: IProps) {
    super(props);
    props.promise
      .then(() => this.setState({ promiseHasResolved: true }))
      .catch(e => console.log("invokeGlobalErrorHandler(e)"))
  }

  render() {
    const { children } = this.props;
    const { promiseHasResolved } = this.state;

    return promiseHasResolved ? children : null;
  }
}

解决方案

要彻底规避这个错误,核心是确保Office.onReady()在应用初始化的最早期执行,同时处理Office.js加载失败的重试逻辑,避免因单次加载失败导致任务窗格空白。具体修改如下:

1. 调整index.tsx:将Office初始化逻辑前置到dva实例创建之前

修改后的frontend/src/index.tsx:

import 'react-app-polyfill/ie11';
import 'react-app-polyfill/stable';
import dva from 'dva';
import router from './router';
import AuthModel from './models/auth';
import SubscribtionModel from './models/subscription';
import AppModel from './models/app';
import SpreadsheetModel from './models/spreadsheet';
import UsageModel from './models/usage';
import SettingsModel from './models/settings';
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';

initializeIcons();

// 定义Office初始化函数,带指数退避重试逻辑
const initOffice = async (retries = 3, delay = 1000): Promise<void> => {
  try {
    // 先检查Office对象是否存在,避免直接调用报错
    if (typeof window.Office === 'undefined') {
      throw new Error('Office.js not loaded');
    }
    await window.Office.onReady();
  } catch (e) {
    if (retries > 0) {
      console.warn(`Office初始化失败,剩余重试次数:${retries}`, e);
      await new Promise(resolve => setTimeout(resolve, delay));
      return initOffice(retries - 1, delay * 2);
    }
    throw e;
  }
};

// 先完成Office初始化,再创建dva应用
(async () => {
  try {
    // 非Office环境直接跳过初始化
    if (typeof window.__$$notInOffice === 'undefined') {
      await initOffice();
    }

    const app = dva();

    app.model(AuthModel);
    app.model(SubscribtionModel);
    app.model(AppModel);
    app.model(SpreadsheetModel);
    app.model(UsageModel);
    app.model(SettingsModel);

    app.router(router);

    app.start('#root');
  } catch (e) {
    console.error('应用初始化失败', e);
    // 初始化失败时显示友好提示,避免空白
    const root = document.getElementById('root');
    if (root) {
      root.innerHTML = '<div style="padding: 20px; text-align: center;">加载失败,请刷新重试</div>';
    }
  }
})();

2. 简化router.tsx:移除重复的Office初始化逻辑

因为已经在index.tsx中完成了Office初始化,路由中无需再重复处理:

import React from 'react';
import { routerRedux, Switch, Route } from 'dva/router';
import Layout from './layouts';
import LoginPage from './components/LoginPage';
import Welcome from './components/welcome';
import SocialLoginSuccess from './components/socialLoginSuccess';

const { ConnectedRouter } = routerRedux;
function RouterConfig({ history }: any) {
  return (
    <ConnectedRouter history={history}>
      <Layout>
        <Switch>
          <Route path="/sign">
            <LoginPage />
          </Route>
          <Route path="/home">
            <Welcome />
          </Route>
          <Route path="/app">
            <App />
          </Route>
          ... ...
          ... ...
        </Switch>
      </Layout>
    </ConnectedRouter>
  );
}

export default RouterConfig;

3. 优化AwaitPromiseThenRender(可选):完善错误与加载状态处理

如果应用还有其他需要等待的异步操作,可以保留该组件,同时优化状态处理避免静默失败:

import React, { Component } from 'react';

interface IProps {
  promise: Promise<any>;
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

interface IState {
  promiseHasResolved: boolean;
  hasError: boolean;
}

export class AwaitPromiseThenRender extends Component<IProps> {
  state: IState = { promiseHasResolved: false, hasError: false };
  
  componentDidMount() {
    this.props.promise
      .then(() => this.setState({ promiseHasResolved: true }))
      .catch(e => {
        console.error('等待Promise失败', e);
        this.setState({ hasError: true });
      });
  }

  render() {
    const { children, fallback = <div style="padding:20px;text-align:center;">加载中...</div> } = this.props;
    const { promiseHasResolved, hasError } = this.state;

    if (hasError) {
      return <div style="padding:20px;text-align:center;">加载失败,请刷新重试</div>;
    }
    return promiseHasResolved ? children : fallback;
  }
}

关键优化点说明

  • 前置Office初始化:确保Office.js完全加载后再初始化dva和React应用,从根源避免"Office.js未加载"错误。
  • 重试机制:针对Office.js加载失败的情况,添加指数退避重试,解决网络波动或Office客户端加载延迟问题。
  • 错误兜底:初始化失败时显示友好提示,避免任务窗格空白。
  • 移除重复初始化:避免在路由中重复调用Office.onReady(),减少冲突可能。

内容的提问来源于stack exchange,提问作者SoftTimur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 07:57:11