在其他反应项目中嵌入反应小部件

embed react widget in other react project

我通过本教程构建了 React 小部件 https://tekinico.medium.com/build-a-react-embeddable-widget-c46b7f7999d8 我使用名为 parcel 的打包工具压缩了所有文件。

在我拥有两个文件(js 和 css)之后,我试图在其他 React 项目中托管这个小部件,所以我打开了 React 项目并在 index.html 中添加了 js 和css 个文件

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

<head>
  <meta charset="utf-8" />
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />

  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

  <title>React App</title>
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>

  <link href="https://tekinico.com/demo/react-widget/index.css" rel="stylesheet" />
  <script src="https://tekinico.com/demo/react-widget/index.js"></script>
  <!-- <link href="https://firebasestorage.googleapis.com/v0/b/fir-firestore-9702f.appspot.com/o/index.css?alt=media&token=62a7013c-185c-4f9b-ae56-fd2572c769ef" rel="stylesheet"/>
  <script  src="https://firebasestorage.googleapis.com/v0/b/fir-firestore-9702f.appspot.com/o/index.js?alt=media&token=55069135-ea09-480c-a297-354f009c497b"></script> -->

</html>

在 app.js 我添加了 div 和 class "nicoraynaud-finance-widget" 小部件

 import './App.css';
    function App() {
      return (
        <div className="App">
        <div  data-symbol="GME" className="nicoraynaud-finance-widget" style={{'width': '250px', 'margin': 'auto', 'marginTop': '20px'}}></div>
        </div>
      );
    }
    
    export default App;

但在浏览器中我什么也没看到。它加载了 css 和 js 文件,我得到了 devtools 的错误,在网络中它显示它正确加载了脚本和 css 但我看到空白 window。

这就是我要托管的小部件

您的小部件未显示在您的 React 应用程序中,因为小部件脚本已加载并且 运行 在您的 React 应用程序被注入 DOM 之前。因此,您的小部件正在尝试查询 html 并将其注入 'finance-widget' div,但是 div 在 DOM 中尚不存在(在页)。

您需要将脚本的加载和小部件 React 应用程序的初始化分开,以确保您的主要 App 组件在小部件初始化之前已安装在页面中。

index.js:

中转到您的小部件应用程序

您可以更改:

// Inject our React App into each class
widgetDivs.forEach(div => {
    ReactDOM.render(
      <React.StrictMode>
        <App symbol={div.dataset.symbol}/>
      </React.StrictMode>,
        div
    );
});

至:

// wrap the react app injection in an init function
window.initFinanceWidget = function() {
  // important to query for 'nicoraynaud-finance-widget' class when we are ready to initialize the widget
  const widgetDivs = document.querySelectorAll('.nicoraynaud-finance-widget');
  // Inject our React App into each class
  widgetDivs.forEach(div => {
    ReactDOM.render(
      <React.StrictMode>
        <App symbol={div.dataset.symbol}/>
      </React.StrictMode>,
        div
    );
  });
}

然后,回到您的主项目,在您的 App 组件 (app.js) 中初始化小部件,在组件安装后,使用 useEffect 挂钩

import { useEffect } from 'react';
import './App.css';
function App() {
  useEffect(() => {
    window.initFinanceWidget();
  }, [])
  return (
    <div className="App">
      <div  data-symbol="GME" className="nicoraynaud-finance-widget" style={{'width': '250px', 'margin': 'auto', 'marginTop': '20px'}}></div>
    </div>
  );
}
    
export default App;

回答

您加载小部件的时间是它要在其中呈现的元素(<div />class nicoraynaud-finance-widget)未加载。 script 标记位于呈现应用程序的 div 之后,但这不会立即发生。

解决方案

您可以使用React Helmetstylescript标签的加载移动到您需要的文件中。

Helmet takes plain HTML tags and outputs plain HTML tags

头盔 允许您更改 html <head /> 标签。你可以这样做:

function App() {
  return (
    <div className="App">
      <Helmet>
        <link
          href="https://tekinico.com/demo/react-widget/index.css"
          rel="stylesheet"
        />
        <script async src="https://tekinico.com/demo/react-widget/index.js"></script>
      </Helmet>
      <h1>Test</h1>
      <div
        data-symbol="GME"
        className="nicoraynaud-finance-widget"
        style={{
          width: "250px",
          margin: "auto",
          marginTop: "20px",
          outline: "10px"
        }}
      ></div>
    </div>
  );
}

备注

重要的是将 script 标记为 async 以确保在呈现组件的其余部分之前完全加载它。如果时间太长,你也可以实现加载状态。

Here 是一个沙盒,可以看到它的实际效果。