【问题标题】:HTML script is loading AFTER react componentsHTML 脚本在反应组件之后加载
【发布时间】:2017-11-21 19:55:21
【问题描述】:

我的 index.html

<!DOCTYPE html>
<html lang="en">
    <head>

        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta name="google-signin-client_id" content= "my_client_id.apps.googleusercontent.com">

        <meta name="google-signin-scope" content="profile email">
        <script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
        <script>
            function start() {
                console.log('script running')
                gapi.load('auth2', function() {
                    auth2 = gapi.auth2.init({
                        client_id: 'my_client_id.apps.googleusercontent.com',
                        scope: 'profile email'
                    });
                });
            }
        </script>
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>


  </body>
</html>

start() 函数中,我打印到控制台以查看它何时运行。

当我加载页面时,start() 每隔一段时间就会加载 反应组件。

登录.js

    componentDidMount() {
        console.log(gapi.auth2.getAuthInstance())
    }

在调试器中你可以看到脚本在组件之后加载:

如果我刷新页面几次,它工作正常。但有时有效,有时无效。

为什么?

【问题讨论】:

  • 你问为什么? 但实际上,我认为没有为什么。这只是一个竞赛条件。有时代码块按一种顺序处理,有时另一种。你怎么打电话给start(),你是怎么初始化反应组件的?它们是同步完成的吗?
  • start() 在 HTML 文件中被调用 "https://apis.google.com/js/client:platform.js?onload=start"
  • 啊,是的,所以这是异步的。如果您从该标签中删除asyncdefer,它会起作用吗?我想不是因为 google apis 强制异步加载的方式
  • 即使它确实开始工作,就 console.log 而言,实际的 google API 可能仍会遭受相同的竞争条件。真的,您需要在 gapi.auth2.init() 的回调中初始化反应组件,或者确保反应在尝试访问其任何信息之前调用它
  • 可能想要混淆client_id

标签: javascript html reactjs


【解决方案1】:

我认为在 react 中加载脚本的最佳方式是使用容器组件。这是一个非常简单的组件,它允许您编写将脚本导入组件而不是 index.html 的逻辑。您还需要通过在检入 componentDidMount 后调用 loadScript 来确保不会多次包含脚本。

本文改编自:https://www.fullstackreact.com/articles/how-to-write-a-google-maps-react-component/

类似的东西。 . .

  componentDidMount() {
    if (!window.google) {
      this.loadMapScript();
    }
    else if (!window.google.maps) {
      this.loadMapScript();
    }
    else {
      this.setState({ apiLoaded: true })
    }
  }

  loadMapScript() {
    // Load the google maps api script when the component is mounted.

    loadScript('https://maps.googleapis.com/maps/api/js?key=YOUR_KEY')
      .then((script) => {
        // Grab the script object in case it is ever needed.
        this.mapScript = script;
        this.setState({ apiLoaded: true });
      })
      .catch((err: Error) => {
        console.error(err.message);
      });
  }

  render() {
    return (
      <div className={this.props.className}>
        {this.state.apiLoaded ? (
          <Map
            zoom={10}
            position={{ lat: 43.0795, lng: -75.7507 }}
          />
        ) : (
          <LoadingCircle />
        )}
      </div>
    );
  }

然后在一个单独的文件中:

const loadScript = (url) => new Promise((resolve, reject) => {
  let ready = false;
  if (!document) {
    reject(new Error('Document was not defined'));
  }
  const tag = document.getElementsByTagName('script')[0];
  const script = document.createElement('script');

  script.type = 'text/javascript';
  script.src = url;
  script.async = true;
  script.onreadystatechange = () => {
    if (!ready && (!this.readyState || this.readyState === 'complete')) {
      ready = true;
      resolve(script);
    }
  };
  script.onload = script.onreadystatechange;

  script.onerror = (msg) => {
    console.log(msg);
    reject(new Error('Error loading script.'));
  };

  script.onabort = (msg) => {
    console.log(msg);
    reject(new Error('Script loading aboirted.'));
  };

  if (tag.parentNode != null) {
    tag.parentNode.insertBefore(script, tag);
  }
});


export default loadScript;

我知道这很多,但是当我第一次这样做时,当我发现有一种(相当)简单的方法可以将任何脚本包含在任何反应组件中时,我感到非常放松。

编辑:其中一些我只是复制粘贴,但如果你不使用 create-react-app,你可能需要替换一些 ES6 语法。

【讨论】:

  • 啊,我认为这比我的答案更好,但我们在同一页上
  • 如果您对它的工作原理感兴趣,那篇文章写得非常好,但我不能太相信它。
  • 非常感谢。这很有帮助。我将标记为正确并尝试实施它:-)
【解决方案2】:

我的建议:

将您的 google API 脚本标签更改为这样,您将在其中删除 asyncdefer

<script src="https://apis.google.com/js/client:platform.js"></script>

去掉你的 start 函数,它现在可以很好地运行console.log,但是第二段代码会导致同样的问题,因为它也会异步运行。

修改您的反应代码,以便 componentWillMount 调用该函数的内容:

componentWillMount() {
  gapi.load('auth2', () => {
    auth2 = gapi.auth2.init({
      client_id: 'urhaxorid.apps.googleusercontent.com',
      scope: 'profile email',
      onLoad: () => {
        this.setState({ mapLoaded: true });
      }
    });
  });
}

componentDidMount() {
  if (this.state.mapLoaded) {
    console.log(gapi.auth2.getAuthInstance());
  }
}

请记住,我不知道 onLoad 是什么用于 google api,我也不能 100% 确定如何最好地完成 setState 的工作,但这可能是一个起点。

【讨论】:

    猜你喜欢
    • 2018-09-25
    • 1970-01-01
    • 1970-01-01
    • 2020-05-17
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    • 1970-01-01
    • 2017-10-20
    相关资源
    最近更新 更多