问题描述
我在我的Chrome扩展中有一个简单的Reaction组件作为内容脚本注入到DOM中,运行在一个不是我的网站上。
我要更新(重新呈现)用户在我的Reaction应用注入到的网站上所做的每个导航(URL更改)上的组件。
我认为Reaction路由器应该是这项工作的最佳工具。
然而,它似乎并没有奏效。我发现了一些相关问题:this、this、this,但这些解决方案都不适合我。
我尝试将唯一的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)更改时发生的情况。
有一些窗口事件可以监听并重新呈现以响应:hashchange
和popstate
。性能最差的解决方案是简单地投票:如果没有其他方法奏效,这是你最后的选择。
以下是如何更改您的内容脚本:
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组件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!