Использование одного проекта веб-пакета внутри другого с реагирующими хуками приводит к ошибке - PullRequest
6 голосов
/ 17 января 2020

Есть три create-react-app, настроенные с использованием react-app-rewired, оба используют одну и ту же версию следующих пакетов:

"react": "^16.12.0",
"react-app-rewired": "^2.1.5",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",

Приложение 1, приложение "Main" - очень простая оболочка для другие два приложения public/index.html выглядят примерно так:

<body>
    <div class="main-app"></div>
    <div class="sub-app-1"></div>
    <div class="sub-app-2"></div>
    <script src="sub-app-1/static/js/bundle.js" async></script>
    <script src="sub-app-1/static/js/0.chunk.js" async></script>
    <script src="sub-app-1/static/js/main.chunk.js" async></script>
    <script src="sub-app-2/static/js/bundle.js" async></script>
    <script src="sub-app-2/static/js/0.chunk.js" async></script>
    <script src="sub-app-2/static/js/main.chunk.js" async></script>
</body>

Это работает достаточно хорошо, все три приложения отображаются правильно. Теперь требования немного изменились, когда в sub-app-1 должен быть включен один-единственный компонент sub-app-2, например <PictureFrame />, в основном проекте я создал заглушку, например:

const NullPictureFrame = () => {
    return <div></div>;
}

export const PictureFrame = (props: Props) => {
    const forceUpdate = useForceUpdate();
    useEventListener(window, "PictureFrameComponent.Initialized" as string, () => forceUpdate());

    const PictureFrame = window.PictureFrameComponent || NullPictureFrame;
    return <PictureFrame />
}

Я не думаю, что детали крючков имеют значение, но они работают, когда работают автономно. sub-app-2 делает что-то похожее на

window.PictureFrameComponent = () => <PictureFrame />

, что, кажется, работает теоретически, но на практике я получаю следующую ошибку

The above error occurred in the <PictureFrame> component:
    in PictureFrame (at src/index.tsx:17)
    in Router (at src/index.tsx:16)
    in Unknown (at PictureFrames/index.tsx:21)
    in PictureFrame (at src/index.tsx:32) <--- in `main-app`

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/docs/error-boundaries.html to learn more about error boundaries. index.js:1
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.

main-app и sub-app-2 загружаются две разные версии react, и это вызывает проблему при использовании хуков.

Я попытался обновить конфигурацию моего веб-пакета, основываясь на совете в этой ветке github , но это Похоже, что sub-app-2 не может найти ссылку на react, используя этот подход. Мой main-app/config-overrides.js выглядит так:

config.resolve.alias["react"] = path.resolve(__dirname, "./node_modules/react");
config.resolve.alias["react-dom"] = path.resolve(__dirname, "./node_modules/react-dom");

, а мой sub-app-1/config-overrides.js выглядит так:

config.externals = config.externals || {};
config.externals.react = {
    root: "React",
    commonjs2: "react",
    commonjs: "react",
    amd: "react"
};
config.externals["react-dom"] = {
    root: "ReactDOM",
    commonjs2: "react-dom",
    commonjs: "react-dom",
    amd: "react-dom"
};

, что не приводит к ошибкам, но также код из sub-app-2 не инициализируемый webpack как react / react-dom не может быть найден


Редактировать 1 : Уточнение

  • 3 рассматриваемых приложения 3 Полностью отдельные (git) проекты, все они используют одни и те же зависимости.
  • Единственная разрешенная "зависимость" между проектами - это очень слабая связь с использованием глобальных переменных, событий, Window.postMessage или аналогичных.
  • Мой первоначальный подход const PictureFrame = window.PictureFrameComponent || NullPictureFrame; отлично работает без с использованием хуков, которые команды использовали до сих пор, что укрепило идею очень потерянных зависимостей, подробно описанных выше
  • Там это «очевидный» способ заставить «реагировать», «реагировать на дом» и все, что зависит от них, как часть externals в main-app и загружать их таким образом

Ответы [ 4 ]

3 голосов
/ 26 января 2020

Я вижу, что вы применили подход к созданию нескольких проектов в одной директории / repository / codebase, что, на мой взгляд, не лучший подход. Вы должны разделить и иметь все свои проекты независимо, подпроект библиотеки, где у вас есть весь общий код / ​​компоненты, а затем сделать каждый проект доступным к тому, что ему нужно.

Вы можете прочитать мою статью написал о Совместном использовании компонентов React Native для нескольких проектов , в которых я предлагаю 3 способа совместного использования кода между проектами. Я сделаю акцент на способе Webpack / Babel, который вы можете использовать module-resolver package для настройки псевдонимов каталогов. Вы можете использовать .babelrc и module-resolver для настройки внешних зависимостей следующим образом:

{
  "presets": ["@babel/preset-react"]
  "plugins": [
    "@babel/plugin-transform-runtime",
    [
      "module-resolver",
      {
        "root": ["./"],
        "extensions": [".js", ".jsx", ".ts", ".tsx"],
        "stripExtensions": [".js", ".jsx", ".ts", ".tsx"],
        "alias": {
          "@components": "./components",
          "@screens": "./screens",
          "@utils": "./utils",
          "@data": "./data",
          "@assets": "./assets",
          "@app": "./",
          "@myprojectlib": "../MyProject-Library",
          "@myprojecti18n": "../MyProject-Library/i18n/rn",
          "@myprojectbackend": "../MyProject-Backend/firebase/functions",
        }
      }
    ]
  ]
}

После этого вы можете использовать что-то вроде:

import { PictureFrame } from '@myprojectlib/components/PictureFrame';

, и вы получите он доступен для каждого проекта, который вы настроили для доступа к "@myprojectlib": "../MyProject-Library".

Таким образом, вы можете иметь несколько версий React и любого пакета внутри вашего проекта, потому что каждый проект будет автономной базой кода со своей собственной зависимости, но с теми же общими компонентами / функциями.

2 голосов
/ 26 января 2020

Я столкнулся с той же проблемой в моем проекте, решение простое.

вы можете go для всех ваших подпроектов и выполнить команду ниже.

npm link ../main_app/node_modules/react

Ссылка: https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate -реакция

2 голосов
/ 25 января 2020

Как указано в React. js Документация ,


React Hooks:

Крюки в основном построены для доступа к состоянию и другим функциям реакции в Бесклассовая составляющая.

Правила, которые следует учитывать при работе с перехватчиками

  1. Только перехватывать вызовы на верхнем уровне

  2. Только перехватывать вызовы из Реакция функций

Исходя из данных, которые вы опубликовали, я полагаю, что вы уже приняли во внимание вышеперечисленные пункты.

Для root причина анализа, вы, вероятно, чтобы исследовать жизненный цикл компонента, необходимо установить несколько операторов отладки в своем коде или протоколировать сообщения на консоли.


Кроме того, было бы намного лучше, если бы вы могли реконструировать / переставить приложение следующим образом:

public / index. html

<body>
<div id="root" class="main-app"></div>
<script>
<!-- scrpits can be kept here -->
</script>
</body>

Создать другой компонент Главное приложение и включить sub-app-1 и sub-app-2 там.

и, так как вы используете "react-dom": "^16.12.0" и "react-router-dom": "^5.1.2", вам будет проще установить маршруты.


Не могу сказать, что это решит ваше дело, но это хорошая практика для включения.

Всего наилучшего.

0 голосов
/ 28 января 2020

Частичное решение: Используйте плагин expose-loader webpack

Использование expose-loader в main-app позволяет как sub-app-1, так и sub-app-2 использовать библиотеки из main-app

sub-app-1 и sub-app-2 имеют config-overrides.js для указания, что они полагаются на external библиотеки

module.exports = function override(config, env) {
  config.externals = config.externals || {};
  config.externals.axios = "axios";
  config.externals.react = "React";
  config.externals["react-dom"] = "ReactDOM";
  config.externals["react-router"] = "ReactRouterDOM";

  return config;
}

main-app отвечает за предоставление этих библиотек. Это может быть достигнуто либо путем встраивания библиотек с использованием тегов <script />, либо с помощью expose-loader, config-overrides.js эффективно выглядит так:

module.exports = function override(config, env) {
  config.module.rules.unshift({
    test: require.resolve("react"),
    use: [
      {
        loader: "expose-loader",
        options: "React"
      }
    ]
  });
  config.module.rules.unshift({
    test: require.resolve("react-dom"),
    use: [
      {
        loader: "expose-loader",
        options: "ReactDOM"
      }
    ]
  });
  config.module.rules.unshift({
    test: /\/react-router-dom\//, // require.resolve("react-router-dom"), did not work
    use: [
      {
        loader: "expose-loader",
        options: "ReactRouterDOM"
      }
    ]
  });

  return config;
};

Теперь, когда я посещаю моя main-app страница, я могу видеть React, ReactDOM и ReactRouterDOM на объекте окна. Теперь мои приложения могут загружаться не по порядку, но теперь нам нужно убедиться, что main-app загружается первым, к счастью, есть несколько прямой путь. В public/index.html я удаляю свои ссылочные sub-apps

<body>
    <div class="main-app"></div>
    <div class="sub-app-1"></div>
    <div class="sub-app-2"></div>
-    <script src="sub-app-1/static/js/bundle.js" async></script>
-    <script src="sub-app-1/static/js/0.chunk.js" async></script>
-    <script src="sub-app-1/static/js/main.chunk.js" async></script>
-    <script src="sub-app-2/static/js/bundle.js" async></script>
-    <script src="sub-app-2/static/js/0.chunk.js" async></script>
-    <script src="sub-app-2/static/js/main.chunk.js" async></script>
</body>

и в своем index.tsx я могу динамически загружать рассматриваемые скрипты и ждать их

// Will dynamically add the requested scripts to the page
function load(url: string) {
  return new Promise(function(resolve, reject) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.async = true;
    script.src = url;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });
}

// Load Sub App 1
Promise.all([
    load(`${process.env.SUB_APP_1_URL}/static/js/bundle.js`),
    load(`${process.env.SUB_APP_1_URL}/static/js/0.chunk.js`),
    load(`${process.env.SUB_APP_1_URL}/static/js/main.chunk.js`)
]).then(() => {
    console.log("Sub App 1 has loaded");
    window.dispatchEvent(new Event("SubAppOne.Initialized"));
    const SubAppOne = window.SubAppOne;
    ReactDOM.render(<SubAppOne />, document.querySelector(".sub-app-1"))
});

// Load Sub App 2
Promise.all([
    load(`${process.env.SUB_APP_2_URL}/static/js/bundle.js`),
    load(`${process.env.SUB_APP_2_URL}/static/js/0.chunk.js`),
    load(`${process.env.SUB_APP_2_URL}/static/js/main.chunk.js`)
]).then(() => {
    console.log("Sub App 2 has loaded");
    window.dispatchEvent(new Event("PictureFrameComponent.Initialized")); // Kicks off the rerender of the Stub Component
    window.dispatchEvent(new Event("SubAppTwo.Initialized"));
    const SubAppTwo = window.SubAppTwo;
    ReactDOM.render(<SubAppTwo />, document.querySelector(".sub-app-2"))
});

Сейчас main-app имеет все общие зависимости, такие как React, и загрузит две sub-apps, когда зависимости будут готовы. PictureFrameComponent теперь работает как положено с крючками! Было бы предпочтительнее, если бы загрузка могла происходить в любом порядке, то есть sub-app-1 могла быть загружена до main-app, однако, учитывая важность main-app и незначительное добавление в функциональность, обеспечиваемую PictureFrameComponent, это могло бы быть сносное решение для некоторых проектов.

...