使用 react 作为内容脚本时多次重新渲染

问题描述

我目前正在学习如何使用 React 开发 chrome 扩展。但是,我遇到了一个问题,即使使用非常基本的组件,react 也会重新渲染 3 次。

因此,内容脚本创建了 3 次嵌套在彼此之上的 iframe —— 我注意到的一件事是查询 iframe 在每次重新渲染时总是返回 null。

这是将作为内容脚本注入 iframe 的组件。

import { render } from 'react-dom';

interface Props {}

const Sidebar: FC<Props> = () => {
  return <div>some sidebar</div>;
};

// render first on the entry point
const app = document.getElementById('app');


if (app) {
  render(
    <StrictMode>
      <Sidebar />
    </StrictMode>,document.getElementById('app')
  );
}

console.log('check');

const id = 'extensionWrapper';
const iframe = document.getElementById(id);
if (!iframe) {
  const iframe = document.createElement('iframe');
  // get the index.html from chrome extension directory
  const content = chrome.extension.getURL('index.html');
  console.log({ content });
  iframe.src = content;
  iframe.id = id;
  document.body.appendChild(iframe);
}

这是webpack.config.js

const { resolve } = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const copyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

const tsRule = {
  test: /\.tsx?$/,exclude: /node_modules/,use: 'ts-loader',};

const plugins = [
  new HTMLWebpackPlugin({
    template: 'src/index.html',filename: 'index.html',chunks: ['contentScript'],inject: 'body',}),new copyWebpackPlugin({
    patterns: [
      {
        from: 'public',// to is relative to output folder set in output.path
        to: '.',},],new CleanWebpackPlugin(),];

module.exports = {
  mode: 'development',devtool: 'cheap-module-source-map',entry: {
    // popup: './src/popup-page/popup.tsx',contentScript: './src/content-scripts/content-script.tsx',output: {
    filename: '[name].js',path: resolve(__dirname,'dist'),module: {
    rules: [tsRule],plugins,};

manifest.json

{
  "short_name": "react chrome","name": "react chrome extension","version": "1.0","manifest_version": 2,"content_scripts": [
    {
      "matches": ["<all_urls>"],"js": ["contentScript.js"]
    }
  ],"web_accessible_resources": ["*.html"]
}

解决方法

我还没有找到关于多重渲染的答案,但万一有人在这篇文章中偶然发现 - 而不是在 manifest.json 中指定内容脚本,我决定只使用动态注入内容脚本下面的片段:

在 background.ts 中

const isWebProtocol = (url: string | undefined): boolean => {
  return !!url && url.startsWith('http');
};

chrome.tabs.onUpdated.addListener((tabId,changeInfo,tab) => {
  if (changeInfo.status === 'complete' && tab.active === true && isWebProtocol(tab.url)) {
    chrome.tabs.executeScript(tabId,{
      file: 'injectScript.js',runAt: 'document_idle',});
  }
});

将在每个选项卡上注入的内容脚本

injectScript.js

const id = 'extensionWrapper';
const iframe = document.getElementById(id);
if (!iframe) {
  const iframe = document.createElement('iframe');
  // get the index.html from chrome extension directory
  const content = chrome.extension.getURL('index.html');

  iframe.src = content;
  iframe.id = id;
  document.body.appendChild(iframe);
}

初始化主要反应组件的主要内容脚本


contentScript.tsx

import React,{ FC,StrictMode } from 'react';
import { render } from 'react-dom';

// import Sidebar from "./content-script.module.css";
import "../style.css";
import "./content-script.css";

interface Props {}

const Sidebar: FC<Props> = () => {
  return <div className="sidebar">some sidebar</div>;
};

// render first on the entry point
const app = document.getElementById('app');


if (app) {
  render(
    <StrictMode>
      <Sidebar />
    </StrictMode>,document.getElementById('app')
  );
}

最后,包含主要反应组件将被注入到的元素(即 index.html)的 #app 文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>