Wie verwende ich React, um eine mehrseitige App zu erstellen?

Lesezeit: 14 Minuten

Benutzeravatar von Jose Carrillo
José Carrillo

Ich baue eine App mit NodeJS und möchte React für einige der interaktiven Komponenten in der gesamten Anwendung verwenden. Ich möchte es nicht zu einer Single-Page-App machen.

Wie kann ich meine React-Komponenten in einer mehrseitigen App aufteilen oder bündeln?

Derzeit befinden sich alle meine Komponenten in einer Datei, auch wenn ich sie in einigen Abschnitten der App möglicherweise nie lade.

Bisher versuche ich, bedingte Anweisungen zu verwenden, um Komponenten zu rendern, indem ich nach der ID des Containers suche, in dem React rendern wird. Ich bin mir nicht 100% sicher, was die Best Practices mit React sind. Es sieht ungefähr so ​​aus.

if(document.getElementById('a-compenent-in-page-1')) {
    React.render(
        <AnimalBox url="/api/birds" />,
        document.getElementById('a-compenent-in-page-1')
    );
}
    
if(document.getElementById('a-compenent-in-page-2')) {
    React.render(
        <AnimalBox url="/api/cats" />,
        document.getElementById('a-compenent-in-page-2')
    );
}

if(document.getElementById('a-compenent-in-page-3')) {
    React.render(
        <AnimalSearchBox url="/api/search/:term" />,
        document.getElementById('a-compenent-in-page-3')
    );
}

Ich lese immer noch die Dokumentation und habe noch nicht gefunden, was ich für eine mehrseitige App benötige.

  • versuchen Sie, das requirejs-Plugin zu verwenden.

    – amit_183

    11. August 2015 um 8:23 Uhr

  • Wenn es Ihnen nichts ausmacht, dass ReactJs eine sehr große JS-Bibliothek ist, die für jede Seite initialisiert werden muss (wie Sie gesagt haben, dass Sie keine Single-Page-App erstellen), bin ich mir nicht sicher, ob es wichtig ist, dass Sie ‘ Ich habe alle Komponenten in einer Datei zusammengefasst. Es wird auf dem Client zwischengespeichert. Wenn eine Seite geladen wird, haben Sie sie render die richtige Komponente in a script Block.

    – WiredPrairie

    12. August 2015 um 10:32 Uhr

  • Ich habe das gleiche Problem: Ich habe eine App, die andere große Bibliotheken auf verschiedenen Seiten lädt, und ich würde lieber nur reagieren + eine Bibliothek je nach den Bedürfnissen des Besuchers laden, anstatt vier große Bibliotheken nur für den Fall.

    – AJFarkas

    2. Mai 2016 um 16:38 Uhr

Benutzeravatar von webdeb
webdeb

Aktuell mache ich etwas ähnliches.

Die Anwendung ist keine vollständige React-App, ich verwende React für dynamische Dinge, wie z. B. CommentBox, die autark ist. Und kann an jeder Stelle mit speziellen Parametern eingebunden werden.

Alle meine untergeordneten Apps werden jedoch geladen und in einer einzigen Datei enthalten all.jssodass es vom Browser seitenübergreifend zwischengespeichert werden kann.

Wenn ich eine App in die SSR-Vorlagen einfügen muss, muss ich nur ein DIV mit der Klasse „__react-root“ und einer speziellen ID (dem Namen der zu rendernden React-App) einfügen.

Die Logik ist ganz einfach:

import CommentBox from './apps/CommentBox';
import OtherApp from './apps/OtherApp';

const APPS = {
  CommentBox,
  OtherApp
};

function renderAppInElement(el) {
  var App = APPS[el.id];
  if (!App) return;

  // get props from elements data attribute, like the post_id
  const props = Object.assign({}, el.dataset);

  ReactDOM.render(<App {...props} />, el);
}

document
  .querySelectorAll('.__react-root')
  .forEach(renderAppInElement)

<div>Some Article</div>
<div id="CommentBox" data-post_id="10" class="__react-root"></div>

<script src="https://stackoverflow.com/all.js"></script>

Bearbeiten

Da das Webpack Code-Splitting & LazyLoading perfekt unterstützt, hielt ich es für sinnvoll, ein Beispiel beizufügen, bei dem Sie nicht alle Ihre Apps in einem Bundle laden müssen, sondern sie aufteilen und bei Bedarf laden.

import React from 'react';
import ReactDOM from 'react-dom';

const apps = {
  'One': () => import('./One'),
  'Two': () => import('./Two'),
}

const renderAppInElement = (el) => {
  if (apps[el.id])  {
    apps[el.id]().then((App) => {
      ReactDOM.render(<App {...el.dataset} />, el);
    });
  }
}

  • Sieht toll aus, hat das jemand bei der Verwendung von npm create-react-app zum Laufen gebracht? Ich glaube nicht, dass das für mich funktionieren würde … Ich habe es gerade mit der 1-App ausgeführt und kann sehen, wie es funktionieren könnte, aber mein Build erstellt keinen Produktions-Build, der ordnungsgemäß funktioniert.

    – Sprössling

    22. Februar 2017 um 15:30 Uhr

  • @Sprose sollte auch mit create-react-app funktionieren, fehlt mir etwas? Vielleicht können Sie ein kleines Beispiel auf Github teilen, wo dies nicht der Fall ist. Ich sehe keinen Grund, warum dies nicht der Fall sein sollte

    – webdeb

    23. Februar 2017 um 2:00 Uhr

  • @Sprose sry für die späte Antwort, aber ich denke, du versuchst etwas ganz anderes, was dieser Ansatz zu lösen versucht. Meiner Meinung nach create react app bietet Ihnen einige einfach zu erstellende Werkzeuge bundle.js. Der Zweck meiner Antwort besteht also darin, dasselbe zu verwenden bundle.js und verwenden Sie es auf mehreren SSR-Sites und laden Sie viele verschiedene React-Anwendungen auf einer einzigen Seite. Entschuldigung, wenn meine Antwort nicht Ihren Bedürfnissen entspricht, können Sie gerne einen neuen Beitrag erstellen und beschreiben, was Sie zu tun versuchen. Ich würde versuchen, Ihnen zu helfen.

    – webdeb

    1. Mai 2017 um 11:51 Uhr


  • @SorcererApprentice cra verwendet Webpack, Sie müssen die Grundlagen auswerfen und dann auschecken webpack.js.org/guides/getting-started/#creating-a-bundle

    – webdeb

    5. Juni 2020 um 17:46 Uhr


  • @SorcererApprentice im Grunde hat jemand die Webpack-Konfiguration auf dieser Seite gepostet: stackoverflow.com/a/41857199/5004923

    – webdeb

    5. Juni 2020 um 17:54 Uhr

Benutzeravatar von Cococomico
Cocomico

Sie können mehrere Einstiegspunkte für die Anwendung in der Datei webpack.config.js bereitstellen:

var config = {
  entry: {
    home: path.resolve(__dirname, './src/main'),
    page1: path.resolve(__dirname, './src/page1'),
    page2: path.resolve(__dirname, './src/page2'),
    vendors: ['react']
  },
 output: {
    path: path.join(__dirname, 'js'),
    filename: '[name].bundle.js',
    chunkFilename: '[id].chunk.js'
  },
}

dann können Sie in Ihrem src-Ordner drei verschiedene HTML-Dateien mit ihren jeweiligen js-Dateien haben (Beispiel für Seite1):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Page 1</title>
</head>
<body>
  <div id="app"></div>
  <script src="https://stackoverflow.com/questions/31933359/./vendors.js"></script>
  <script src="./page1.bundle.js"></script>
</body>
</html>

JavaScript-Datei:

import React from 'react'
import ReactDom from 'react-dom'
import App from './components/App'
import ComponentA from './components/ReactComponentA'
ReactDom.render(<div>
                  <App title="page1" />
                  <ReactComponentA/>
                 </div>, document.getElementById('app'))

Für jede einzelne Seite können dann unterschiedliche React-Komponenten geladen werden.

  • Ich habe auf der Webex-Seite über Folgendes gelesen. webpack version < 4 it was common to add vendors as separate entrypoint to compile it as separate file (in combination with the CommonsChunkPlugin). This is discouraged in webpack 4. Instead the optimization.splitChunks option takes care of separating vendors and app modules and creating a separate file. Do not create a entry for vendors or other stuff which is not the starting point of execution. Sollte dieses Beispiel also für Webpack 4+ aktualisiert werden?

    – Ramesh

    3. Oktober 2018 um 9:01 Uhr

Scotts Benutzeravatar
Scott

Ich baue eine Anwendung von Grund auf und lerne dabei, aber ich denke, wonach Sie suchen React-Router. React-Router ordnet Ihre Komponenten bestimmten URLs zu. Zum Beispiel:

render((
    <Router>
        <Route path="https://stackoverflow.com/" component={App}>
            <Route path="api/animals" component={Animals}>
               <Route path="birds" component={Birds}/>
               <Route path="cats" component={Cats}/>
            </Route>
        </Route>
        <Route path="api/search:term" component={AnimalSearchBox}>
    </Router>
), document.body)

Im Suchfall ist „Begriff“ als Eigenschaft in der AnimalSearchBox zugänglich:

componentDidMount() {
    // from the path `/api/search/:term`
    const term = this.props.params.term
}

Versuch es. Diese Tutorial ist dasjenige, das mich in Bezug auf mein Verständnis dieses und anderer verwandter Themen überragend gemacht hat.


Ursprüngliche Antwort folgt:

Ich habe den Weg hierher gefunden, auf der Suche nach der gleichen Antwort. Sehen ob Dieser Beitrag inspiriert dich. Wenn Ihre Anwendung meiner ähnlich ist, wird sie Bereiche haben, die sich sehr wenig ändern und nur im Hauptteil variieren. Sie könnten ein Widget erstellen, dessen Aufgabe es ist, je nach Status der Anwendung ein anderes Widget zu rendern. Mit einer Flux-Architektur könnten Sie eine Navigationsaktion auslösen, die den Zustand ändert, in den Ihr Body-Widget wechselt, und effektiv nur den Body der Seite aktualisieren.

Das ist der Ansatz, den ich jetzt versuche.

  • Was ist, wenn der URL-Pfad von meinem Backend-Code (in diesem Fall nodejs) erstellt wird? Werden die Router genauso funktionieren wie in einer Single-Page-App?

    – José Carrillo

    22. November 2015 um 20:22 Uhr

  • @Scott Was ist, wenn ich keine Admin-Seiten-Funktionalität mit speziellen Widgets verfügbar machen möchte? Mit React ist es möglich, Look and Feel ohne echte Authentifizierung nachzubilden.

    – Kairat Kempirbaev

    16. November 2017 um 16:57 Uhr


Benutzeravatar von jennas
Jenna

Verwenden Sie ein CMS? Sie neigen dazu, sich ändernde URLs zu mögen, die Ihre Anwendung beschädigen könnten.

Eine andere Möglichkeit ist die Verwendung von etwas wie Lebensraum reagieren.

Damit können Sie Komponenten registrieren und sie werden automatisch dem Dom ausgesetzt.

Beispiel

Komponente(n) registrieren:

container.register('AnimalBox', AnimalBox);
container.register('AnimalSearchBox', AnimalSearchBox);

Dann sind sie in Ihrem Dom wie folgt verfügbar:

<div data-component="AnimalBox"></div>

<div data-component="AnimalSearchBox"></div>

Die oben genannten werden automatisch durch Ihre Reaktionskomponenten ersetzt.

Sie können dann auch automatisch Eigenschaften (oder Requisiten) an Ihre Komponenten übergeben:

<div data-component="AnimalBox" data-prop-size="small"></div>

Dies wird aussetzen size als Stütze für Ihre Komponente. Es gibt zusätzliche Optionen zum Übergeben anderer Typen wie JSON, Arrays, Ints, Floats usw.

Ich weiß, es ist eine Weile her, seit diese Frage gestellt wurde, aber hoffentlich hilft dies jemandem.

Wie @Cocomico erwähnte, könnten Sie mehrere Einstiegspunkte für die Anwendung in der Datei webpack.config.js bereitstellen. Wenn Sie nach einem einfachen Webpack-Setup suchen (basierend auf der Idee mehrerer Einstiegspunkte), mit dem Sie React-Komponenten zu statischen Seiten hinzufügen können, sollten Sie Folgendes verwenden: https://github.com/przemek-nowicki/multi-page-app-with-react

Benutzeravatar von Simon Briche
Simon Brich

Ich lasse diese alte Frage wieder aufleben, da ich in der gleichen Situation war, ohne eine Antwort zu finden, die meine Bedürfnisse befriedigen könnte. Basierend auf der Antwort von @webdeb habe ich also ein Mini-Framework geschrieben, das CRA (ohne Auswurf) verwendet, um beliebig viele Komponenten in jede HTML-Seite einzufügen und gleichzeitig alle Vorteile von CRA zu erhalten.

TL;DR

Sie können mein öffentliches Repo überprüfen hier die alle benötigten Dateien und einen Link zu einer enthält Mittlerer Artikel wo ich all diese Dinge gründlich erkläre.

Die allgemeine Idee

Der Trick besteht darin, CRA wie gewohnt zu installieren und die index.js Datei wie folgt:

import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import reportWebVitals from './reportWebVitals';

//list here all the components that could be inserted in a web page
const apps = {
  'App': React.lazy(() => import('./App')),
  'TestComponent1': React.lazy(() => import('./TestComponent1')),
  'TestComponent2': React.lazy(() => import('./TestComponent2')),
}

//event manager to communicate between the components
const bridgeEvent = new EventTarget();
//common fallback for all the components
function Fallback() {
  return <div>Loading...</div>;
}
const renderAppInElement = (el) => {
  if(apps[el.dataset.reactComponent] && !el.dataset.rendered){
    //get the component's name stored in the data-react-component attribute
    const App = apps[el.dataset.reactComponent];
    //render the component, inject all the HTML attributes and the Event bridge
    ReactDOM.render(
      <Suspense fallback={<Fallback />}>
        <App  {...el.dataset} bridgeEvent={bridgeEvent}/>
      </Suspense>
    , el);
    el.dataset.rendered = true;
  }
  else if(el.dataset.rendered){
    console.log('el', el, 'is already rendered')
  }
}

//ONLY FOR THE DEV PHASE
const rootEl = document.getElementById('root');
//generate components without attributes
if(process.env.REACT_APP_RENDER_CMP){
  const components = process.env.REACT_APP_RENDER_CMP.split(',');
  
  components.forEach(item => {
    const componentEl = document.createElement('div');
    componentEl.setAttribute("data-react-component", item);
    componentEl.className = "__react-cmp";
    rootEl.append(componentEl);
  });
}
//generate components with attributes
if(process.env.REACT_APP_RENDER_CMP_WITH_ATTRS){
  let componentsWithAttrs;
  try{
    componentsWithAttrs = JSON.parse(process.env.REACT_APP_RENDER_CMP_WITH_ATTRS);
  }
  catch(e){
    console.log('fail to parse REACT_APP_RENDER_CMP_WITH_ATTRS', e);
  }
  if(componentsWithAttrs){
    componentsWithAttrs.forEach(cmp => {
      const componentEl = document.createElement('div');
      componentEl.setAttribute("data-react-component", cmp.class);
      componentEl.className = "__react-cmp";
      Object.keys(cmp.data).forEach(attrKey => {
        componentEl.setAttribute(attrKey, cmp.data[attrKey]);
      });
      rootEl.append(componentEl);
    });
  }
}

//the default name of the global object is ReactComponents, but it could be customized via the REACT_APP_NAMESPACE environment variable
const appNamespace = process.env.REACT_APP_NAMESPACE || "ReactComponents";
window[appNamespace] = {
  ready: false,
  parseComponents(container){
    //parse the container or the whole document and inject all the components in the containers that have a "__react-cmp" class
    (container || document)
    .querySelectorAll('.__react-cmp')
    .forEach(renderAppInElement);
  }
}
window[appNamespace].parseComponents();
window[appNamespace].ready = true;

//if dynamic parsing must be done via the window.ReactComponents.parseComponents() method
//check the availability of window.ReactComponents object via window.ReactComponents.ready property
//or define a window.ReactComponentsAsyncInit() method to be notified of the availability
if(typeof window[`${appNamespace}AsyncInit`] === 'function'){
  window[`${appNamespace}AsyncInit`]();
}

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
reportWebVitals();

Dann können Sie hinzufügen REACT_APP_RENDER_CMP und/oder REACT_APP_RENDER_CMP_WITH_ATTRS Umgebungsvariablen, um Ihre Komponenten zu testen, während Sie den Entwicklungsserver des CRA verwenden. Dein .env.development.local Datei könnte so aussehen:

#this will render the TestComponent1 and TestComponent2 without any attributes
REACT_APP_RENDER_CMP="TestComponent1,TestComponent2"

#this will render TestComponent1 with the data-test-attribute attribute set to "test attribute value"
REACT_APP_RENDER_CMP_WITH_ATTRS="[{"class":"TestComponent1","data":{"data-test-attribute":"test attribute value"}}]"

Nachdem Sie Ihre Dateien erstellt haben, sollten Sie Ihre haben index.html Datei mit allen .js und .css Dateien, die Sie in jede Seite Ihrer mehrseitigen App einfügen müssen, die Ihre React-Komponenten laden soll. Vergessen Sie nicht hinzuzufügen INLINE_RUNTIME_CHUNK=false in deinem .env Datei, um Inline-Javascript zu vermeiden!

Fügen Sie dann die Container der Komponenten in den HTML-Seiten dort hinzu, wo sie angezeigt werden sollen. Zum Beispiel:

<div class="__react-cmp" data-react-component="TestComponent1"></div>

Die parseComponents() in den CRAs deklariert index.js Datei ausgeführt werden sollte, schnappen Sie sich Ihre div mit dem .__react-cmp Klasse, und verwenden Sie sie dann als Container für Ihre TestComponent1 Reaktionskomponente.

Im gewidmet Repo und Artikel Ich erkläre, wie Sie Ihren Build-Pfad mit den CRAs ändern können BUILD_PATH Umgebungsvariable (damit Sie Ihre erstellten Dateien auf Ihrem Server oder in einem CDN hosten können) und ich stelle einen Loader bereit, der die erstellten Dateien analysiert index.html Datei und fügen Sie alle erforderlichen dynamisch ein .js und .css Dateien in Ihrer Seite (Sie müssen also nur den Loader einbinden, anstatt alle Dateien). So sieht der Loader aus, vorausgesetzt, sein Dateiname lautet cmp-loader.js und neben Ihrem Build gehostet index.html Datei:

(async () => {
  const head = document.getElementsByTagName('head')[0];
  const scriptSrcRegexp = new RegExp('<script.*?src="(.*?)"', 'gmi');

  //get the exact script's src as defined in the src attribute
  const scriptSrc = scriptSrcRegexp.exec(document.currentScript.outerHTML);
  //all the resources should be relative to the path of this script
  const resourcesPath = (scriptSrc && scriptSrc.length > 1) ? scriptSrc[1].replace('cmp-loader.js', '') : '';

  //get the index content
  const indexHTML = await (await fetch(resourcesPath+'index.html', {cache:'reload'})).text();

  //assume that all the .js and .css files to load are in the "static" folder
  const reactCSSRegexp = new RegExp(`<link href="${resourcesPath}static\/css\/(.*?)\.css" rel="stylesheet">`, 'gm');
  const reactJSRegexp = new RegExp(`<script (.*?) src="${resourcesPath}static\/js\/(.*?)\.js"><\/script>`, 'gm');

  //grab all the css tags
  const ReactCSS = [].concat(indexHTML.match(reactCSSRegexp)).join('');
  //grab all the js tags
  const ReactJS = [].concat(indexHTML.match(reactJSRegexp)).join('');

  //parse and execute the scripts
  const scriptsDoc = new DOMParser().parseFromString(ReactJS, 'text/html');
  Array.from(scriptsDoc.getElementsByTagName('script')).forEach(item => {
    const script = document.createElement('script');
    [...item.attributes].forEach(attr => {
      script.setAttribute(attr.name, attr.value)
    })
    head.appendChild(script);
  });
  //inject the CSS
  head.insertAdjacentHTML('beforeend', ReactCSS);
})().catch(e => {
  console.log('fail to load react-cmp', e)
});

Benutzeravatar der Community
Gemeinschaft

Ich schlage vor, Sie werfen einen Blick auf InertiaJS: https://inertiajs.com/

Mit Inertia erstellen Sie Apps genau so, wie Sie es immer mit Ihrem serverseitigen Web-Framework Ihrer Wahl getan haben. Sie verwenden die vorhandene Funktionalität Ihres Frameworks für Routing, Controller, Middleware, Authentifizierung, Autorisierung, Datenabruf und mehr.

Das einzige, was anders ist, ist Ihre Ansichtsebene. Anstatt serverseitiges Rendering (z. B. Blade- oder ERB-Vorlagen) zu verwenden, sind die Ansichten JavaScript-Seitenkomponenten. Auf diese Weise können Sie Ihr gesamtes Frontend mit React, Vue oder Svelte erstellen.

1439050cookie-checkWie verwende ich React, um eine mehrseitige App zu erstellen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy