¿Cómo usar React para crear una aplicación de varias páginas?

14 minutos de lectura

Avatar de usuario de Jose Carrillo
José Carrillo

Estoy creando una aplicación con NodeJS y me gustaría usar React para algunos de los componentes interactivos de la aplicación. No quiero que sea una aplicación de una sola página.

¿Cómo divido o agrupo mis componentes de React en una aplicación de varias páginas?

Actualmente, todos mis componentes están en un archivo, aunque es posible que nunca los cargue en algunas secciones de la aplicación.

Hasta ahora estoy tratando de usar declaraciones condicionales para renderizar componentes buscando la ID del contenedor donde React renderizará. No estoy 100% seguro de cuáles son las mejores prácticas con React. Se parece a esto.

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')
    );
}

Todavía estoy leyendo la documentación y aún no he encontrado lo que necesito para una aplicación de varias páginas.

  • intente usar el complemento requirejs.

    – amit_183

    11 de agosto de 2015 a las 8:23

  • Si no le importa que ReactJs sea una biblioteca JS muy grande que deberá inicializarse para cada página (como dijo que no está creando una aplicación de una sola página), entonces no estoy seguro de que le importe que He combinado todos los componentes en un solo archivo. Se almacenará en caché en el cliente. Cuando se carga una página, haz que render el componente correcto en un script bloquear.

    – Pradera alambrada

    12 de agosto de 2015 a las 10:32

  • Tengo el mismo problema: tengo una aplicación que carga otras bibliotecas grandes en diferentes páginas, y prefiero cargar React + una biblioteca según las necesidades del visitante, en lugar de cuatro bibliotecas grandes por si acaso.

    – AJ Farkas

    2 mayo 2016 a las 16:38

avatar de usuario de webdeb
webdeb

Actualmente, estoy haciendo algo similar.

La aplicación no es una aplicación React completa, estoy usando React para cosas dinámicas, como CommentBox, que es autárquico. Y se puede incluir en cualquier Punto con parámetros especiales.

Sin embargo, todas mis aplicaciones secundarias se cargan e incluyen en un solo archivo all.jspor lo que el navegador puede almacenarlo en caché en todas las páginas.

Cuando necesito incluir una aplicación en las plantillas SSR, solo tengo que incluir un DIV con la clase “__react-root” y una identificación especial (el nombre de la aplicación React que se representará)

La lógica es realmente simple:

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>

Editar

Dado que webpack es perfectamente compatible con la división de código y LazyLoading, pensé que tenía sentido incluir un ejemplo en el que no necesita cargar todas sus aplicaciones en un solo paquete, sino dividirlas y cargarlas a pedido.

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);
    });
  }
}

  • Se ve genial, ¿alguien lo ha hecho funcionar cuando usa npm create-react-app? No creo que esto funcione para mí … Lo tengo funcionando con la aplicación 1 en este momento y puedo ver cómo podría funcionar, pero mi compilación no creará una compilación de producción que funcione correctamente.

    – Sprosa

    22 de febrero de 2017 a las 15:30

  • @Sprose también debería funcionar con create-react-app, ¿me estoy perdiendo algo? Tal vez pueda compartir un pequeño ejemplo en github donde no lo hace, no veo una razón por la que no debería

    – webdeb

    23 de febrero de 2017 a las 2:00

  • @Sprose sry por la respuesta tardía, pero creo que estás intentando algo completamente diferente, lo que este enfoque intenta resolver. OMI create react app le brinda algunas herramientas fáciles de crear bundle.js. Entonces, el propósito de mi respuesta es usar el mismo bundle.js y úselo en múltiples sitios SSR, y cargue muchas aplicaciones React diferentes en una sola página. Lo siento, si mi respuesta no se ajusta a sus necesidades, siéntase libre de crear una nueva publicación y describir lo que está tratando de hacer, trataría de ayudarlo.

    – webdeb

    1 mayo 2017 a las 11:51


  • @SorcererApprentice cra usa webpack, debe expulsar y luego verificar los conceptos básicos webpack.js.org/guides/getting-started/#creating-a-bundle

    – webdeb

    5 de junio de 2020 a las 17:46


  • @SorcererApprentice básicamente alguien publicó la configuración del paquete web en esta página: stackoverflow.com/a/41857199/5004923

    – webdeb

    5 de junio de 2020 a las 17:54

Avatar de usuario de Cocomico
Cocomico

Puede proporcionar varios puntos de entrada para la aplicación en el archivo webpack.config.js:

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'
  },
}

entonces puede tener en su carpeta src tres archivos html diferentes con sus respectivos archivos js (ejemplo para la página 1):

<!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>

Archivo JavaScript:

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'))

Luego se pueden cargar diferentes componentes de React para cada página individual.

  • Leí sobre lo siguiente en la página webex. 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. Entonces, ¿debería actualizarse esta muestra para webpack 4+?

    – Ramesh

    3 de octubre de 2018 a las 9:01

Avatar de usuario de Scott
scott

Estoy creando una aplicación desde cero y estoy aprendiendo sobre la marcha, pero creo que lo que estás buscando es Enrutador de reacción. React-Router asigna sus componentes a URL específicas. Por ejemplo:

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)

En el caso de búsqueda, se puede acceder a ‘término’ como una propiedad en AnimalSearchBox:

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

Pruébalo. Esto tutorial es el que me puso en la cima en términos de mi comprensión de este y otros temas relacionados.


La respuesta original sigue:

Encontré mi camino aquí buscando la misma respuesta. Ve si esto la publicación te inspira. Si su aplicación es como la mía, tendrá áreas que cambiarán muy poco y variarán solo en el cuerpo principal. Podría crear un widget cuya responsabilidad sea representar un widget diferente según el estado de la aplicación. Usando una arquitectura de flujo, puede enviar una acción de navegación que cambia el estado en el que se activa el widget de su cuerpo, actualizando de manera efectiva solo el cuerpo de la página.

Ese es el enfoque que estoy intentando ahora.

  • ¿Qué pasa si la ruta de URL es creada por mi código de back-end (nodejs en este caso)? Será el Router funciona de la misma manera que lo hace en una aplicación de una sola página?

    – José Carrillo

    22 de noviembre de 2015 a las 20:22

  • @Scott ¿Qué pasa si no quiero exponer la funcionalidad de la página de administración que tiene widgets especiales? Usando reaccionar es posible recrear la apariencia sin autenticación real.

    – Kairat Kempirbaev

    16 de noviembre de 2017 a las 16:57


avatar de usuario de jennas
Jennas

¿Está utilizando un CMS? Les suele gustar cambiar las URL, lo que podría romper su aplicación.

Otra forma es usar algo como Hábitat de reacción.

Con él, puede registrar componentes y automáticamente quedan expuestos al dom.

Ejemplo

Registrar componente(s):

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

Entonces están disponibles en tu dominio así:

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

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

Lo anterior se reemplazará automáticamente con sus componentes de reacción.

Luego, también puede pasar automáticamente propiedades (o accesorios) a sus componentes:

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

Esto expondrá size como apoyo a su componente. Existen opciones adicionales para pasar otros tipos como json, array’s, ints, floats, etc.

Sé que ha pasado un tiempo desde que se hizo esta pregunta, pero espero que esto ayude a alguien.

Como mencionó @Cocomico, podría proporcionar varios puntos de entrada para la aplicación en el archivo webpack.config.js. Si está buscando una configuración simple de Webpack (basada en la idea de múltiples puntos de entrada) que le permita agregar componentes de React a páginas estáticas, puede considerar usar esto: https://github.com/przemek-nowicki/multi-page-app-with-react

Avatar de usuario de Simon Briche
Simón Briche

Retomo esta vieja pregunta ya que me encontraba en la misma situación, sin encontrar una respuesta que pudiera satisfacer mis necesidades. Entonces, según la respuesta de @webdeb, escribí un mini marco que usa CRA (sin expulsión) para inyectar tantos componentes como desee en cualquier página HTML mientras conserva todos los beneficios de CRA.

TL;RD

Puedes consultar mi repositorio público aquí que contiene todos los archivos necesarios y un enlace a un Artículo mediano donde explico detalladamente todo esto.

la idea general

El truco consiste en instalar CRA como lo haría normalmente y actualizar el index.js archivo de la siguiente manera:

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();

Entonces puedes agregar REACT_APP_RENDER_CMP y/o REACT_APP_RENDER_CMP_WITH_ATTRS variables de entorno para probar sus componentes mientras usa el servidor de desarrollo de CRA. Su .env.development.local archivo podría verse como:

#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"}}]"

Después de crear sus archivos, debe tener su index.html archivo con todos los .js y .css archivos que debe incluir en cada página de su aplicación de varias páginas que deben cargar sus componentes de React. No olvides agregar INLINE_RUNTIME_CHUNK=false en tus .env archivo para evitar cualquier javascript en línea!

Luego, agregue los contenedores de los componentes en las páginas HTML donde desea que se muestren. Por ejemplo:

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

Él parseComponents() declarado en el CRA index.js el archivo debe ser ejecutado, agarrando su div con el .__react-cmp class, luego utilícelo como un contenedor para su TestComponent1 Componente de reacción.

en el dedicado repositorio y artículo Explico cómo podría cambiar su ruta de compilación con los CRA BUILD_PATH variable de entorno (para que pueda alojar sus archivos creados en su servidor o en un CDN) y proporciono un cargador que analizará los archivos creados index.html archivo e inserte dinámicamente todo lo necesario .js y .css archivos en su página (por lo que solo tiene que incluir el cargador, en lugar de todos los archivos). Así es como se ve el cargador, asumiendo que su nombre de archivo es cmp-loader.js y alojado junto a su construido index.html expediente:

(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)
});

Avatar de usuario de la comunidad
Comunidad

Te sugiero que eches un vistazo a InertiaJS: https://inertiajs.com/

Con Inertia, crea aplicaciones como siempre lo ha hecho con el marco web del lado del servidor de su elección. Utiliza la funcionalidad existente de su marco para el enrutamiento, los controladores, el middleware, la autenticación, la autorización, la obtención de datos y más.

Lo único que es diferente es su capa de vista. En lugar de utilizar la representación del lado del servidor (por ejemplo, plantillas Blade o ERB), las vistas son componentes de página de JavaScript. Esto le permite construir todo su front-end usando React, Vue o Svelte.

¿Ha sido útil esta solución?