本文介绍了基于用户导航在Chrome扩展中更新注入DOM的Reaction组件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我的Chrome扩展中有一个简单的Reaction组件作为内容脚本注入到DOM中,运行在一个不是我的网站上。

我要更新(重新呈现)用户在我的Reaction应用注入到的网站上所做的每个导航(URL更改)上的组件。
我认为Reaction路由器应该是这项工作的最佳工具。
然而,它似乎并没有奏效。我发现了一些相关问题:thisthisthis,但这些解决方案都不适合我。

我尝试将唯一的key添加到Route,(就像使用useLocation或简单地使用location.href或甚至Math.random()进行测试)-但这些都不会使组件更新。

奇怪的是,如果我附加一个onclick将组件中的状态更改为div,它确实会成功地使组件重新呈现和更新。

我的内容脚本如下所示:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

const myDiv = document.createElement('div');
myDiv.id = 'my-id';
document.body.appendChild(myDiv);

ReactDOM.render(<App />, myDiv);

我的路由(Reaction-路由器v6):

import {
    BrowserRouter,
    Routes,
    Route,
    useParams,
    useLocation
} from "react-router-dom";

function App() {
    return (
        <BrowserRouter>
            <Routes>
                <Route path="/user/:id/" element={<MyComponent />} />
            </Routes>
        </BrowserRouter>
    );
}

我的组件:

function MyComponent(props) {
    const { id } = useParams();

    React.useEffect(() => {
       // Some stuff
    }, [id])

    return <div> User {id}</div>

我的manifest.json脚本如下:

  "background": {
    "scripts": [
      "build/background.js"
    ]
  },
  "content_scripts": [
    {
      "matches": [
        "https://example-website.com/*"
      ],
      "js": [
        "build/content.js"
      ],
      "css": [
        "build/css/content.css"
      ]
    },
    {
      "matches": [
        "https://example-website.com/*"
      ],
      "js": [
        "build/reactApp.js"
      ],
      "css": [
        "build/css/content.css"
      ]
    }
  ],

推荐答案

您的问题的答案完全取决于每个导航(url更改)在代码方面的确切定义。听起来您对此并不十分确定(但如果您是,请使用此信息更新问题)。

当匹配的URL发生页面重新加载事件时,您的应用程序已再次注入,因此涵盖范围。

如果站点是单页面应用程序,并且正在修改其内容而不重新加载页面(例如,Reaction),则取决于视图数据(和URL)更改时发生的情况。

有一些窗口事件可以监听并重新呈现以响应:hashchangepopstate。性能最差的解决方案是简单地投票:如果没有其他方法奏效,这是你最后的选择。

以下是如何更改您的内容脚本:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

const myDiv = document.createElement('div');
myDiv.id = 'my-id';
document.body.appendChild(myDiv);

const render = () => ReactDOM.render(<App />, myDiv);

render();

const handleLocationUpdates = () => {
  window.addEventListener("hashchange", render);
  window.addEventListener("popstate", render);

  // or, if neither of those work, you might have to poll:

  // const aDelayValueThatWorksForYou = 500;
  // setInterval(render, aDelayValueThatWorksForYou);
};

handleLocationUpdates();


编辑

因为您尚未共享要向其中注入扩展的站点的URL(为了重现该问题),所以我创建了一个自包含的示例,您可以在本地Web服务器上运行该示例以查看其工作情况:

example.html

<!doctype html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Example</title>

  <script type="module">
    // Imagine that this is the JS in the example website (that you don't control)
    const delay = (ms) => new Promise(res => setTimeout(res, ms));
    const updateUrl = (relativePath) => window.history.pushState({}, '', new URL(relativePath, window.location.href));

    await delay(1500);
    updateUrl('/user/2');

    await delay(1500);
    updateUrl('/about');

    await delay(1500);
    updateUrl('/user/3');

  </script>

  <!-- This examples uses UMD modules instead of ESM or bundling -->
  <script src="https://unpkg.com/[email protected]/umd/react.development.js"></script><script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script>
  <script type="text/babel" data-type="module" data-presets="env,react">
    // This is your entire React app

    const {useEffect, useState} = React;

    function parseId () {
      const idRegex = /^/user/(?<id>[^?/#]+)/;
      const id = window.location.pathname.match(idRegex)?.groups?.id;
      return id;
    }

    function useId () {
      const [id, setId] = useState();

      useEffect(() => {
        const parsed = parseId();
        if (parsed !== id) setId(parsed);
      });

      return id;
    }

    function IDComponent () {
      const id = useId();

      useEffect(() => {
        if (!id) return; // No ID in current location
        console.log('New ID:', id);
      }, [id]);

      return id ?
        (<div>User ID: {id}</div>)
        : (<div>User ID not found</div>);
    }

    function App () {
      return (
        <div>
          <h1>Detecting ID changes...</h1>
          <IDComponent />
        </div>
      );
    }

    // The logic of the body of this function would be in your content script
    function main () {
      const reactRoot = document.body.appendChild(document.createElement('div'));
      const render = () => ReactDOM.render(<App />, reactRoot);

      const applyCallback = (object, method, callback) => {
        object[method] = new Proxy(object[method], {
          apply: (target, thisArg, args) => {
            callback();
            return target.apply(thisArg, args);
          },
        });
      };

      const handleLocationUpdates = (usePolling) => {
        if (usePolling) {
          const aDelayValueThatWorksForYou = 500;
          setInterval(render, aDelayValueThatWorksForYou);
          return;
        }
        window.addEventListener('hashchange', render);
        window.addEventListener('popstate', render);
        applyCallback(window.history, 'pushState', render);
        applyCallback(window.history, 'replaceState', render);
      };

      render();

      // If the event listeners and proxy interceptor don't handle the
      // "navigation" events created by the page you're injecting into,
      // then you'll need to set the polling option to `true`
      handleLocationUpdates(false);
    }

    main();
  </script>
</head>

<body></body>
</html>

这篇关于基于用户导航在Chrome扩展中更新注入DOM的Reaction组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-15 23:16