React: Admite renderizado de servidor asíncrono (esperando datos antes de renderizar)

Creado en 24 jun. 2014  ·  139Comentarios  ·  Fuente: facebook/react

Facilitaría seriamente el proceso de creación de algo isomorfo si el componente WillMount pudiera devolver una promesa y esa reacción retrasara la representación hasta que se resuelva esa promesa. He visto intentos de hacer algo así en el enrutador de reacción y el enrutador, sin embargo, dar esta responsabilidad a cada componente en lugar de un módulo de enrutador tendría más sentido para mí.

Component API Server Rendering Backlog Feature Request

Comentario más útil

Hace unos meses di una charla en JSConf Islandia que describe las próximas funciones de renderizado asíncrono en React (ver la segunda parte): https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react -16.html. Se trata de la obtención de datos del lado del cliente.

Ahora, @acdlite dio una charla sobre cómo se pueden aplicar los mismos conceptos para habilitar la espera asincrónica de datos en el servidor en los componentes de React y la eliminación progresiva del marcado a medida que está listo: https://www.youtube.com/watch?v= z-6JC0_cons

¡Espero que disfrutes viendo estas charlas! Creo que en un año más o menos podríamos cerrar este problema y tener una estrategia oficial para esto.

Todos 139 comentarios

La razón principal (creo) por la que esto no existe ya es que, en el lado del cliente, básicamente siempre desea mostrar algún tipo de indicador de carga en lugar de diferir la representación. (También haría que el código fuera significativamente más complejo, pero probablemente podamos lidiar con eso).

Hay 2 casos que encuentro difíciles de resolver sin eso:

  • En el lado del servidor, si desea obtener datos antes de renderizar, no puede delegar la recuperación de datos a los componentes, ya que no tiene la información de qué componente se renderizará.
  • En el lado del cliente, la primera vez que monte su aplicación después de recibir html prerenderizado, incluso si tiene algún tipo de caché de los datos recuperados en el servidor, es posible que desee utilizar el método asíncrono para recuperar esos datos, y eso evitaría reaccionar. de reutilizar el html.

react-async resuelve esos problemas con fibras y caché. Eso funciona, pero en mi punto de vista, esas son solo soluciones _hackish_ para resolver un problema que solo se puede resolver en el núcleo.

Coloréame desinformado sobre este tema, componentWillMount es asíncrono y no devuelve nada de inmediato, ¿qué se supone que React debe procesar hasta que no haya nada? Si es así, ¿por qué no devolver nada en el renderizado si aún no hay datos? (Sí, entiendo del lado del servidor) Además, ¿qué debería pasar si los accesorios cambian antes de que se dispare componentWillMount ?

Personalmente, parece que está mal enviar solicitudes asíncronas durante componentWillMount menos que el componente sea realmente una caja negra aislada y también implemente un indicador de carga. Según tengo entendido, los componentes de React no deben confundirse con instancias OOP más convencionales. En el mejor de los casos, un componente React es una herramienta para visualizar los datos en accesorios, si es interactivo, entonces posiblemente también indique. Es una vista, no una vista y un modelo.

A mis oídos, ese suena como el problema, los componentes de React no deberían ser los que envían las solicitudes asíncronas, obtienes todos los datos y cuando esos datos están listos, solo entonces llamas a React.renderComponent . Misma solución del lado del cliente y del lado del servidor. También tiene la capacidad de abortar con un resultado de su elección si falla alguna solicitud asíncrona.

Siéntase libre de descartarme si entendí mal algo, pero parece que está tratando los componentes de React como una vista y un modelo, cuando (parece) están destinados solo a la vista.

Coloréame desinformado sobre este tema, WillMount es asíncrono y no devuelve nada de inmediato, ¿qué se supone que debe procesar React hasta que no haya nada? Si es así, ¿por qué no devolver nada en el renderizado si aún no hay datos? (Sí, tengo el lado del servidor) Además, ¿qué debería suceder si los accesorios cambian antes de que se active el componenteWillMount?

Debo admitir que no pensé en todos los casos ^^.
Esta función sería útil solo la primera vez que montemos el componente de nivel superior y en el servidor, es cierto que, de lo contrario, en la mayoría de las transmisiones le gustaría mostrar un indicador de cargador.

Personalmente, parece que no es correcto enviar solicitudes asíncronas durante el montaje del componente, a menos que el componente sea realmente una caja negra aislada y también implemente un indicador de carga. Según tengo entendido, los componentes de React no deben confundirse con instancias OOP más convencionales. En el mejor de los casos, un componente React es una herramienta para visualizar los datos en accesorios, si es interactivo, entonces posiblemente también indique. Es una vista, no una vista y un modelo.

De una forma u otra, querrá que un componente de "nivel superior" pueda recuperar datos, como se hace en la muestra de Flux .
En este ejemplo, las cosas son bastante simples porque recuperar la lista de tareas pendientes es una operación síncrona, si no lo fuera, y en caso de renderizado previo en el servidor, renderizaríamos una primera vez sin datos (y perderíamos el renderizado previo). marcado del servidor).

En el caso de una aplicación simple con un conjunto de datos mostrados por una jerarquía de vista, todavía no hay tanto problema, puede precargar datos y aún mantener la propiedad síncrona de su tienda.
Ahora, en el caso de una aplicación compuesta por múltiples módulos que reutiliza en su aplicación, me gustaría poder tratar esos módulos como aplicaciones separadas que pueden 'suscribirse' en diferentes tiendas (que serían responsables de obtener datos).

Tal vez entiendo las cosas de manera incorrecta, pero alguna discusión/muestra en la web me hace pensar que falta algo en alguna parte:

  • En alguna muestra me parece que @petehunt intentó lograr algo similar.
  • react-nested-router promueve algún mecanismo similar en willTransitionTo , y algunas discusiones me hacen sentir que nadie ha encontrado una solución adecuada.
  • RRouter también proporciona algún tipo de mecanismo para obtener datos previamente a medida que el componente se procesa/monta.
  • y finalmente react-async como dije antes.

@fdecampredon Para ser claros, el propósito de willTransitionTo en el enrutador anidado de reacción es _no_ para cargar datos, específicamente porque devolver una promesa de ese método bloqueará la representación de una nueva interfaz de usuario, lo cual no desea hacer a menos que sea absolutamente necesario.

@fdecampredon Todos todavía están tratando de resolver las cosas, por lo que no me sorprendería si nadie tiene una respuesta definitiva. Pero supongo que los desarrolladores de Facebook deben haberse topado con esto varias veces.

¿Algún avance en esto? Acabo de empezar a explorar React e inmediatamente me encuentro con esto. Muchas personas recomiendan React como una solución para crear aplicaciones isomorfas, pero mientras esto no se resuelva, creo que simplemente no puede hacer el trabajo.

A mis oídos, eso suena como el problema, los componentes de React no deberían ser los que envían las solicitudes asíncronas, obtienes todos los datos y cuando esos datos están listos, solo entonces llamas a React.renderComponent. Misma solución del lado del cliente y del lado del servidor. También tiene la capacidad de abortar con un resultado de su elección si falla alguna solicitud asíncrona.

Siéntase libre de descartarme si entendí mal algo, pero parece que está tratando los componentes de React como una vista y un modelo, cuando (parece) están destinados solo a la vista.

Si esto es cierto, entonces React no es más que una solución de plantilla / capa de vista ligeramente diferente. Y eso sería una pena porque existe tal potencial. Realmente entiendo a @fdecampredon cuando menciona esas aplicaciones complejas compuestas por múltiples módulos. React sería perfecto para esto.

No creo que este enfoque signifique tratar un componente como vista y modelo. Si observa la arquitectura de Flux, piensan en los componentes de React como _vistas de controlador_ que no solo muestran datos (de las tiendas) sino que también invocan acciones basadas en las interacciones del usuario. Y las acciones luego actualizan las tiendas (= modelo). Para mí, suena como una arquitectura MVC obvia.

El problema está en la acción inicial que llena la tienda. Es bastante simple en el lado del cliente donde se puede invocar la acción desde el método componentDidMount() como se recomienda. En el lado del servidor, por otro lado, realmente necesitamos algún lugar especial y algún tipo de mecanismo que retrase el renderizado hasta que finalice la acción y se llenen las tiendas.

En este momento, me parece que la única forma es React-async/Fibers + Flux. Lo bueno de Flux es que no necesitamos un caché artificial para transportar los estados de los componentes del servidor al cliente (como se hizo en el ejemplo original de reacción asíncrona), simplemente podemos inicializar las tiendas y luego enviarlas al cliente con el marcado html (ver este ejemplo ).

Pero esta solución es de hecho _hackish_.

No creo que necesariamente tenga que ser componenteWillMount que sea asíncrono; Ni siquiera estoy seguro de que tenga que ser un evento del ciclo de vida. El verdadero problema es que en el lado del servidor no hay forma de analizar el árbol de componentes hasta después de que todo se haya convertido en una cadena.

Mi solución ideal para resolver esto sería permitir la "renderización" que solo construiría el árbol de componentes, luego podríamos recorrer el árbol para encontrar componentes que necesitan datos adicionales, permitirnos cargar más datos de forma asincrónica y "volver a renderizar". " ese subárbol de componentes, y luego, una vez que estemos listos para vaciar el marcado, permítanos convertir ese árbol en una cadena.

Esto replica lo que podemos hacer en el navegador: tener un DOM virtual que podemos volver a renderizar como queramos. La diferencia es que en el navegador las actualizaciones del DOM pueden ser implícitas. En el servidor, debemos ser explícitos acerca de cuándo representamos una cadena para que podamos realizar actualizaciones en el DOM virtual en función de los datos asíncronos.

Mi solución ideal para resolver esto sería permitir la "renderización" que solo construiría el árbol de componentes, luego podríamos recorrer el árbol para encontrar componentes que necesitan datos adicionales, permitirnos cargar más datos de forma asincrónica y "volver a renderizar". " ese subárbol de componentes, y luego, una vez que estemos listos para vaciar el marcado, permítanos convertir ese árbol en una cadena. — @mridgway

Sí, eso sería bueno. Actualmente, el componente de renderizado dos veces en un lado del servidor se puede usar como una solución alternativa.

Quiero hacer referencia a react-nexus como un ejemplo de lo que me gustaría que fuera compatible con React. Es esencialmente una reescritura de cómo funciona mountComponent excepto que construye el árbol de componentes sin montarlo en el DOM o escribir una cadena. Esto le permite atravesar el árbol de componentes y activar métodos asincrónicos mientras se construye el árbol. El problema con esta implementación tal como es, es que desecha ese primer árbol y luego llama a React.renderToString todos modos, mientras que sería bueno tomar ese árbol de procesamiento previo y renderizarlo/montarlo.

Estoy dispuesto a trabajar en esto dentro del núcleo, pero necesitaría algunos consejos. Creo que uno de los requisitos es hacer que ReactContext no se haga referencia globalmente para que asíncrono no cause problemas. ¿Hay otros globales que también podrían tener problemas con esto?

@mridgway Si no me equivoco, global ReactContext podría ser compatible y desaparecerá en 0.14. Pero _podría_ estar equivocado.

@gaearon Sí, ese es el sentido que obtuve de la desaprobación de withContext.

:+1: Usando react-router para este cajero automático. El enfoque de @mridgway suena muy razonable.

Una versión ligeramente diferente de este problema es cómo manejar la carga asíncrona de fragmentos de código producidos por un paquete (como un paquete web).

Como se discutió en este ticket - https://github.com/rackt/react-router/issues/1402 - el problema con el soporte de renderizado asíncrono es que el renderizado inicial parece ser nulo ya que los fragmentos relevantes aún no se han cargado en el primero pase de procesamiento, lo que da como resultado un <noscript> en la raíz del DOM y un error de suma de comprobación. Todo encaja rápidamente después, pero da como resultado un parpadeo en la interfaz de usuario cuando se trabaja localmente y peor en el campo, especialmente si los fragmentos que se descargarán tienen un tamaño razonable (digamos> 30 KB)

La capacidad de dividir aplicaciones grandes en varios fragmentos es importante para nosotros y, habiendo resuelto el problema de obtención de datos (utilizamos un enrutador de reacción y rutas anidadas que se pueden atravesar antes de renderizar en el servidor para obtener dependencias de datos), esta es la última pieza. del rompecabezas que nos impide pasar completamente a una solución React para nuestro front-end.

@anatomic Esa no es responsabilidad de React, es su trabajo fragmentar adecuadamente y diferir el renderizado hasta que se hayan cargado todos los fragmentos necesarios. Para decirlo de otra manera, si uno de sus componentes tiene una dependencia de alguna biblioteca externa, obviamente es su problema satisfacer antes de intentar usarlo, React no podría hacerlo incluso si lo intentara, por lo que se aplica lo mismo en todos los ámbitos.

Siéntase libre de implementar estrategias alternativas que le convengan mejor, digamos <WaitFor for={MyAsyncLoadedCompSignal} until={...} then={() => <MyAsyncLoadedComp ... />} /> . Pero estos son inherentemente obstinados y no es algo que React deba o incluso necesite ofrecer, por lo que es mejor dejarlo en manos de la comunidad.

Es mejor mantener las cosas asíncronas fuera del alcance de los componentes de React, aquí hay un ejemplo:

import React from 'react';
import Layout from './components/Layout';
import NotFoundPage from './components/NotFoundPage';
import ErrorPage from './components/ErrorPage';

const routes = {

  '/': () => new Promise(resolve => {
    require(['./components/HomePage'], HomePage => { // Webpack's script loader
      resolve(<Layout><HomePage /></Layout>);
    });
  }),

  '/about': () => new Promise(resolve => {
    require(['./components/AboutPage'], AboutPage => { // Webpack's script loader
      resolve(<Layout><AboutPage /></Layout>);
    });
  })

};

const container = document.getElementById('app');

async function render() {
  try {
    const path = window.location.hash.substr(1) || '/';
    const route = routes[path];
    const component = route ? await route() : <NotFoundPage />;
    React.render(component, container);
  } catch (err) {
    React.render(<ErrorPage error={err} />, container);
  }
}

window.addEventListener('hashchange', () => render());
render();

Ver Webpack Code Splitting , React.js Routing from Scratch y react-routing (próximamente)

@syranide Seguiré trabajando en eso, pero no creo que sea tan binario como lo pusiste arriba. Estamos utilizando el enrutador de reacción, por lo que puede introducir algunos problemas en la mezcla, ya que el enrutador es un componente en lugar de estar fuera del entorno de React.

Si hicimos el enfoque de <WaitFor ... /> , seguramente obtendremos un DOM diferente en el primer renderizado que aún causará el parpadeo/desaparición del contenido.

si hiciéramos el enfoque, seguramente seguiremos obteniendo un DOM diferente en el primer renderizado que aún causará el parpadeo/desaparición del contenido.

Si no desea el parpadeo (algunos lo hacen), es simplemente una cuestión de esperar a que se carguen todos los fragmentos de los que depende antes de renderizar, el paquete web proporciona esto listo para usar con require.resolve .

PD. Sí, el enrutador de reacción y cualquier otra cosa que esté usando seguramente complica la solución, pero aún no es un problema de React para resolver.

Si no desea el parpadeo (algunos lo hacen), es simplemente una cuestión de esperar a que se carguen todos los fragmentos de los que depende antes de renderizar, webpack proporciona esto listo para usar con require.resolve.

Lo investigaré, mi entendimiento de require.resolve era que era una búsqueda sincrónica de la identificación de un módulo y no implicaba un viaje al servidor. Estamos usando require.ensure para administrar nuestra carga de fragmentos.

Mirando nuestro código nuevamente, creo que podemos sortear el problema haciendo que el enrutador de reacción piense que se está procesando en el servidor, pero luego siguiendo el enfoque estándar del lado del cliente:

const history = new BrowserHistory();

if (typeof history.setup === "function") {
    history.setup();
}

Router.run(routes, history.location, (err, initialState, transition) => {
    React.render(<Provider redux={redux}>{() => <Router key="ta-app" history={history} children={routes} />}</Provider>, document.getElementById('ta-app'));
});

Lo investigaré, mi comprensión de require.resolve fue que era una búsqueda sincrónica de la identificación de un módulo y no implicaba un viaje al servidor. Estamos usando require.ensure para administrar nuestra carga de fragmentos.

Lo siento, sí, quise decir require.ensure . La devolución de llamada se ejecuta solo cuando todas las dependencias están satisfechas, por lo que solo se trata de poner render/setState dentro de él.

Ok, así es como lo estábamos haciendo, gracias por sus respuestas. Esto se siente como algo que debe abordarse en el enrutador de reacción, por lo que continuaré la discusión allí. ¡Disculpe si este ha sido el lugar equivocado para tener esta conversación!

Tienes razón en que require.ensure es el camino a seguir, supongo que nuestro último problema fue que debería estar vinculado a la ruta que se está visitando actualmente, por lo que está directamente vinculado al enrutador. Con react-router basado en componentes que lo vincula al árbol de renderizado. Sin mi truco anterior, nos quedamos luchando por encontrar una forma de ver el árbol antes de que todo se cargue de forma asíncrona (o duplicar la lógica de enrutamiento para permitir que se carguen fragmentos relevantes en el nivel superior).

Tiene razón en que require.ensure es el camino a seguir, supongo que nuestro último problema fue que debería estar vinculado a la ruta que se está visitando actualmente, por lo que está directamente vinculado al enrutador. Con react-router basado en componentes que lo vincula al árbol de renderizado. Sin mi truco anterior, nos quedamos luchando por encontrar una forma de ver el árbol antes de que todo se cargue de forma asíncrona (o duplicar la lógica de enrutamiento para permitir que se carguen fragmentos relevantes en el nivel superior).

No estoy íntimamente familiarizado con el enrutador de reacción, pero aún me imagino que es simplemente un caso de setRoute(...) => require.ensure([], function() { require(...); setRoute(...); }) que realmente no es práctico, por lo que introduce otro nivel de direccionamiento indirecto. Un mapa de rutas y el cargador asíncrono require.ensure para cada una. Escriba un ayudante function asyncSetRoute(...) { loadRoute(route, function() { setRoute(...); }} , ahora llame a asyncSetRoute lugar y pospondrá la actualización del enrutador hasta que todo esté listo.

Pseudo-código y algo genérico, pero ese me parece el enfoque general. Tal vez el enrutador de reacción debería proporcionar esto, tal vez no... tal vez se proporcione idealmente como un ayudante externo, no estoy seguro.

Entonces, después de horas de investigación, acabo de confirmar que la representación del lado del servidor es _imposible_ a menos que alimente todo de arriba hacia abajo (?).

Posibles soluciones a corto plazo:
A. Llene previamente una tienda y haga que la carga del lado del servidor sea sincrónica

B. Alimente todo desde la parte superior como una entrada de datos después de obtener asíncronamente su único objeto de datos.

re: 'A'. Esto no funcionará a menos que ya sepa cómo se verá su estructura de renderizado. Necesito renderizarlo para conocer mis diversas dependencias de colecciones/modelos/api. Además, ¿cómo hago para que la búsqueda sea asíncrona en el cliente, pero sincronizada en el servidor, sin tener dos API diferentes?

re: 'B'. Básicamente el mismo problema que el anterior. ¿Las personas tienen que hacer JSON de poca dependencia para ir con cada una de sus rutas?

¿Hay otras soluciones a corto plazo mientras esperamos una solución compatible con React? ¿Algún usuario aterriza bifurcaciones o complementos? https://www.npmjs.com/package/react-async ?

@NickStefan

no entiendo el problema :-(

  1. Use un contenedor Flux o similar a Flux (Alt, Redux, Flummox, etc.) donde las tiendas no son singletons.
  2. Defina métodos estáticos como fetchData en sus controladores de ruta que devuelven promesas.
  3. En el servidor, haga coincidir la ruta con los componentes, tome fetchData de ellos y espere a que se completen antes de renderizar. Esto precargará su instancia de tienda Flux o Redux. Tenga en cuenta que la instancia de la tienda no es un singleton: está vinculada a una solicitud en particular, por lo que las solicitudes permanecen aisladas.
  4. Cuando las promesas estén listas, renderice de forma sincrónica con la instancia de la tienda que acaba de completar previamente.

Aquí hay un buen tutorial para Redux que describe este enfoque: https://medium.com/@bananaoomarang/handcrafting -an-isomorphic-redux-application-with-love-40ada4468af4

@gaearon Me disculpo si te confundí. Gracias por responder. De su lista, parece que tengo razón al suponer que la solución a la dependencia de datos del servidor es definir solo las necesidades de datos en su componente raíz (métodos estáticos/el artículo al que se vinculó). Si sus dependencias de datos están definidas en la raíz, es mucho más fácil llenar previamente las tiendas, etc.

Esta es una buena práctica de flujo, pero ¿no es potencialmente limitante? Si agrego un pequeño componente en la parte inferior de su árbol de vista que necesita datos muy diferentes, entonces necesito editar las dependencias de datos en la raíz.

Lo que pido es una forma de que los componentes profundamente anidados definan las necesidades de datos asincrónicos.

Si tengo que agregar sus necesidades al componente raíz, ¿no estoy acoplando la raíz a las necesidades de ese subcomponente?

@NickStefan con react-routing , por ejemplo, la obtención de datos asíncronos se ve así:

import Router from 'react-routing/lib/Router';
import http from './core/http';

const router = new Router(on => {
  on('/products', async () => <ProductList />);
  on('/products/:id', async (state) => {
    const data = await http.get(`/api/products/${state.params.id`);
    return data && <Product {...data} />;
  });
});

await router.dispatch('/products/123', component => React.render(component, document.body));

Esas soluciones funcionan, pero solo porque toda la búsqueda previa de datos está vinculada al enrutador. Eso no es un problema en la mayoría de los casos (tiene sentido, la URL define qué debe estar en la página y, por lo tanto, qué datos se necesitan), pero en general es una limitación. Nunca podrá crear un componente reutilizable independiente (es decir, transmisión de Twitter, comentarios, calendario) que manejaría todo por sí mismo y todo lo que tenía que hacer era insertarlo en la jerarquía de componentes. La única forma en que me he encontrado que hace que esto sea posible es reaccionar asíncronamente, pero eso es más o menos un truco.

Supongo que los componentes de React simplemente no están destinados a usarse de esta manera, todavía son más vistas que _controller_-views. Probablemente tendrá que surgir una biblioteca completamente nueva sobre la base de React.

Mi sueño es que algún día podamos escribir un CMS completo usando React, algo como Wordpress, Drupal o Modx. Pero en lugar de fragmentos de HTML/PHP, crearemos el sitio web a partir de componentes de React.

@NickStefan

De su lista, parece que tengo razón al suponer que la solución a la dependencia de datos del servidor es definir solo las necesidades de datos en su componente raíz (métodos estáticos/el artículo al que se vinculó). Si sus dependencias de datos están definidas en la raíz, es mucho más fácil llenar previamente las tiendas, etc.

Si tengo que agregar sus necesidades al componente raíz, ¿no estoy acoplando la raíz a las necesidades de ese subcomponente?

No exactamente en la raíz, en el nivel del controlador de ruta. Puede haber muchos controladores de ruta en su aplicación. Además, bibliotecas como React Router admiten controladores de ruta anidados . Esto significa que su controlador de ruta anidado puede definir una dependencia, y la coincidencia del enrutador contendrá una matriz de controladores de ruta jerárquicos coincidentes, por lo que puede Promise.all . ¿Esto tiene sentido?

No es tan granular como "cada componente puede declarar una dependencia", pero descubrí que, en la mayoría de los casos, limitar la obtención de datos a los controladores de ruta (superiores y anidados) es todo lo que quiero. Tengo componentes puros a continuación que aceptan datos a través de props para que ni siquiera _sepan_ que se está recuperando. Tiene sentido que no puedan solicitarlo.

El enfoque de "cada componente puede declarar una dependencia" no es práctico a menos que tenga algún tipo de solución de procesamiento por lotes de solicitudes. Imagínese si <User> declara que necesita una llamada a la API y ahora presenta una lista de 100 <User> s. ¿Le gustaría esperar 100 solicitudes? Este tipo de granularidad de requisitos de obtención de datos solo funciona cuando tiene una solución de procesamiento por lotes de solicitudes. Si eso te conviene, Relay es exactamente eso. Pero necesitarías un backend especial para ello.

@NickStefan Actualmente no

@gaearon

El enfoque de "cada componente puede declarar una dependencia" no es práctico a menos que tenga algún tipo de solución de procesamiento por lotes de solicitudes.

Después de reflexionar un poco más sobre esto, estoy de acuerdo contigo. Lo que estaba buscando no es realmente práctico.

Originalmente tuve la idea de que incluso tus 100el ejemplo estaría bien con la disciplina del equipo (es decir, solo haga un <UsersContainer> que haga 1 solicitud para las 100). Pero eso no es "escalable para personas" simplemente para tener una convención. Probablemente sea mejor aplicar todas las dependencias a la raíz o al enrutador. Incluso Relay lo obliga a declarar dependencias en la raíz con sus tres contenedores diferentes (RelayContainer, RelayRootContainer, RelayRouter, etc.). De alguna manera prueba el punto de que la única forma es esencialmente "elevar" sus declaraciones de dependencia en el árbol.

¡Hola!
¿Hay alguna actualización sobre esto?

+1

Sostengo que si realmente piensas en esto, no quieres hacer esto. Originalmente pensé que quería que React hiciera un renderizado asíncrono. Pero otros en este hilo me han convencido de lo contrario.

Incluso en Relay, básicamente tiene que "elevar" sus dependencias en el árbol con las declaraciones del contenedor raíz, los contenedores de retransmisión, etc. La representación síncrona es el camino a seguir.

El renderizado asíncrono puede ser una pesadilla. Hablo desde la experiencia de trabajar en una base de código de la empresa con una columna vertebral que fue pirateada para realizar actualizaciones individuales en los cuadros de animación de solicitud.

Acordado. Solía ​​pensar que necesitábamos esto, pero en realidad es al revés que la vista especifique qué datos cargar. La vista es una función del estado de la aplicación, que a su vez es una función de la solicitud (y posiblemente del estado del usuario, si hay una sesión). De esto se trata React; permitir que los componentes especifiquen qué datos deben cargarse va en contra de la idea de "flujo de datos unidireccional", en mi opinión.

en realidad es al revés hacer que la vista especifique qué datos cargar.

No estoy completamente seguro de que esto sea cierto. A veces, el componente es lo único que sabe qué datos cargar. Por ejemplo, supongamos que tiene una vista de árbol expandible que le permite a un usuario navegar por un gráfico masivo de nodos: es imposible saber de antemano qué datos deben cargarse; solo el componente puede darse cuenta de eso.

Independientemente, esta discusión podría volverse mucho más relevante si perseguimos la idea de ejecutar el código React dentro de un trabajador web (#3092), donde se requiere asincronismo para comunicarse a través del puente.

En el ejemplo de una vista de árbol grande, si realmente quisiera poder representarlo con una ruta ya abierta en el lado del servidor, agregaría esa ruta a la estructura de la URL. Si hubiera varios componentes complejos como este, entonces representaría sus estados con parámetros GET. De esta manera, esos componentes obtienen todos los beneficios de SSR: se pueden rastrear, se puede navegar usando el historial, los usuarios pueden compartir enlaces a un nodo dentro de ellos, etc. Ahora, también tenemos una forma para que el servidor determine qué datos necesita. a buscar para dar una respuesta.

actualizar Mi mal confundiendo un gráfico con un árbol, pero sigo pensando que el estado de la vista de un usuario de ese gráfico debe estar representado por la estructura de URL. Además, no me di cuenta de que en realidad trabajas en React. Si está trabajando en algún marco para integrar una capa de datos con la vista de forma isomórfica, eso es increíble. Pero creo que podemos estar de acuerdo en que eso va más allá del dominio de la vista y que React no debería asumir el papel de un controlador de pila completo.

No leí todo el hilo, lo siento si ya se habló de esto.

Una cosa que realmente ayudaría sería si hubiera un método react-dom/server que simplemente "iniciara/creara una instancia" de un componente y activara los métodos del ciclo de vida, pero dejaría que el desarrollador decidiera cuándo está listo para convertir el componente en una cadena html . Por ejemplo, el desarrollador podría usar setTimeout o algo más confiable para esperar a que se completen los métodos asincrónicos.

Este es el "truco" que estoy usando con redux en este momento para lograr esto:

  // start/instantiate component
  // fires componentWillMount methods which fetch async data
  ReactDOM.renderToString(rootEle)

  // all my async methods increment a `wait` counter 
  // and decrement it when they resolve
  const unsubscribe = store.subscribe(() => {
    const state = store.getState()
    // as a result, when there are no more pending promises, the wait counter is 0
    if (state.wait === 0) {
      unsubscribe()
      // all the data is now in our redux store
      // so we can render the element synchronously
      const html = ReactDOM.renderToString(rootEle)
      res.send(html)
    }
  })

El problema con este enfoque es que el segundo ReactDOM.renderToString activa todos los métodos componentWillMount nuevamente, lo que resulta en una recuperación de datos innecesaria.

¡Hola chicos! ¿Esto es algo en lo que se está trabajando actualmente? Creo que este tema está creciendo en importancia. Recientemente creé una biblioteca que extiende connect de react redux a dataConnect donde también puede especificar los requisitos de datos del contenedor. Estos requisitos de datos luego se agrupan en tiempo de ejecución y se consultan con GraphQL en una sola solicitud. Creo que este es el futuro de las especificaciones de datos, ya que promueve la componibilidad y el aislamiento y también garantiza una recuperación más eficaz. (todos los conceptos vistos en Relay)

El problema con lo anterior es la representación del lado del servidor. Dado que no puedo analizar estáticamente con qué requisitos de datos terminaré, actualmente, necesitaría renderizar una vez para obtener el paquete para consultar desde la base de datos, hidratar la tienda redux y luego volver a renderizar para obtener la cadena final . El problema es similar al de @olalonde .

Sería fantástico poder desencadenar acciones y actualizaciones en el árbol de elementos de reacción y obtener el resultado de la cadena DOM a pedido. Así es como me lo imagino:

const virtualTree = ReactDOM.renderVirtual(rootEle);

// get the bundled query from redux store for example
const bundle = store.getState().bundle;

// Fetch the data according to the bundle
const data = fetchDataSomehow(bundle);

// hydrate store
store.dispatch({type: 'hydrate', data});
// components that should update should be marked on the virtual tree as 'dirty'

virtualTree.update(); // this would only update the components that needed update

const domString = virtualTree.renderToString(); // final result

Otra opción es simplemente dejar que se actualice a voluntad como si estuviera en el lado del cliente, teniendo todos los ganchos presentes y funcionando como didMount. Pero en lugar de mutar el DOM, solo mutaría la representación del dom virtual y también se renderizaría en una cadena a pedido.

¿Qué piensan ustedes? ¿Algo a tener en cuenta o lo estoy viendo completamente mal?

Hola a todos. He estado suscrito a este número durante un año más o menos. En aquel entonces, pensé que necesitaba poder especificar los datos que deberían cargarse desde los componentes, porque los componentes eran mi principal unidad de modularidad. Este problema era muy importante para mí y pensé que seguramente se resolvería en la próxima versión de React.

Desde entonces, he desarrollado un respeto mucho más profundo por los ideales detrás de REST/HATEOS, debido a la simplicidad a gran escala que surge en un sistema de aplicaciones (navegadores, rastreadores, servidores de aplicaciones, proxies, CDN, etc.) cuando sus principios rectores son seguido. En lo que respecta a este problema, _la URL debe ser la única representación verdadera del estado de la aplicación_. Este, y solo él, debe determinar qué información se requiere para atender una solicitud. La capa de visualización, React, no debería determinar esto; debe construirse sobre la base de los datos. La vista es una función de los datos, y los datos son una función de la URL .

He dudado en lanzar esto en el pasado, porque sigo escuchando que es una buena idea, pero que el mundo real es demasiado complejo para que esto funcione. Y ocasionalmente, escucho ejemplos que me hacen dar un paso atrás y me pregunto si estoy siendo demasiado pedante o demasiado idealista. Pero mientras reflexiono sobre estos ejemplos, inevitablemente encuentro una forma razonable de representar el estado de la aplicación en la URL.

  • ¿Su estado tiene parámetros que varían independientemente? Represéntelos como parámetros de consulta separados.
  • ¿Es su estado (o parte de él) demasiado grande para caber en una URL? Nómbrelo y guárdelo en un almacén de datos inmutable. Refiérase a él por nombre / ID.
  • ¿Tu estado está cambiando tan rápido que no puedes almacenarlo todo para siempre? Felicidades, tiene grandes datos legítimos, busque almacenamiento y comience a pensar en cosas inteligentes que hacer con ellos. O bien, puede rehuir y simplemente mutar sus datos con consultas de ACTUALIZACIÓN, y aceptar que no puede almacenar en caché para siempre todas las cosas más tarde (*).
  • ¿Tiene vistas que son diferentes para diferentes usuarios, pero que se muestran en la misma URL, por ejemplo, una página de inicio personalizada? Navegue tras la identificación del usuario a una URL que incluya la identificación del usuario.
  • ¿Está construyendo sobre una aplicación anterior, donde no tiene la opción de romper la estructura de URL anterior? Eso es doloroso, estoy en el mismo barco. Redirija la antigua estructura de URL a una estructura de URL bien diseñada, traduciendo los datos de sesión (o lo que sea) a segmentos y parámetros de ruta de URL.
  • ¿Tiene poco o ningún control sobre la arquitectura de su aplicación? Ese no es el caso para el que se diseñó React, y no queremos que React sea destrozado para que encaje en algo como una arquitectura Wordpress / Magnolia / Umbraco compatible entre sí.

¿Qué obtienes por todo ese trabajo, que de otro modo no hubieras tenido?

  • La capacidad de un usuario de llevar a otro usuario al mismo lugar que él en la aplicación, compartiendo una URL.
  • La capacidad de navegar por la aplicación utilizando todas las herramientas que proporciona el navegador. El cliente estándar está en su elemento.
  • La capacidad de ofrecer una paginación compleja de una manera sencilla de seguir para el cliente: un enlace next en una respuesta. La API gráfica de FB es un gran ejemplo de esto.
  • Un gráfico detallado de los flujos de trabajo de su usuario en Google Analytics.
  • La opción de construir dicho gráfico usted mismo a partir de nada más que registros de solicitudes.
  • Un escape de la ruta estelar del caos: en lugar de hacer coincidir todas las solicitudes como un app.get("*", theOneTrueClientonaserverEntryPoint) , puede usar el marco de la aplicación de su servidor como se pretendía. Puede devolver los códigos de estado HTTP correctos de las solicitudes, en lugar de 200 OK\n\n{status: "error"} . La ruta de las estrellas es tentadora, pero conduce a la locura.

Entonces, ahora que React, nuestra herramienta estrella, no está controlando la operación, ¿cómo obtenemos nuestros datos? ¿Cómo sabemos qué componentes renderizar?

  1. Dirija a una función de acceso a datos y obtenga datos para los parámetros de solicitud relevantes. Repita hasta que haya analizado toda la URL y tenga un objeto de contexto completo.
  2. Transforme el contexto en el objeto de respuesta más simple posible, como si fuera a responder a una solicitud de API. ¿Está atendiendo una solicitud de API? Entonces estás listo y SECO.
  3. Pase ese objeto mínimamente complejo, pero posiblemente grande, al componente de nivel superior. Desde aquí, es la composición del componente React hasta el final.
  4. Renderizar a cadena, responder. Hágale saber a su CDN que puede almacenar esto en caché para siempre (* a menos que haya sacrificado esta opción anterior), porque la CDN se identifica por URL y todos sus estados tienen una URL. Claro, los CDN no tienen almacenamiento infinito, pero priorizan.

No pretendo trolear aquí, pero creo firmemente que el tema central de las solicitudes en este problema está equivocado y que React no debería implementar nada para acomodarlo, al menos no por las razones que he visto aquí. el año pasado. Algunos aspectos de una buena arquitectura de aplicaciones no son obvios, y la web es especialmente difícil de hacer bien. Debemos aprender y respetar la sabiduría de nuestros antepasados, quienes construyeron Internet y la Web, en lugar de sacrificar la elegancia en aras de la comodidad.

Eso es lo que hace que React sea genial: es la vista. La mejor vista. Solo la vista. λ

Tendré que estar en desacuerdo contigo @d4goxn. No estoy tratando de hacer que React sea más que una capa de vista, y el enrutamiento es importante pero definitivamente no es suficiente para especificar todos los requisitos de datos. Al especificar los requisitos de datos a nivel de componente/contenedor, no está haciendo que React sea más que una capa de visualización. Esto solo le proporciona un aislamiento y un flujo de trabajo mucho mejores para su aplicación. No es solo una simplificación del trabajo, sino la necesidad de una gran aplicación. Imagine que mi aplicación tiene 30 rutas y quiero agregar un componente de perfil de usuario en cada una. Siguiendo una ruta alternativa donde se especifican todos los requisitos de datos para cada uno, tendría que pasar por las 30 rutas para agregar esa dependencia de datos. Cuando especifica sus necesidades de datos en un contenedor para ese componente, solo tiene que agregar el componente donde desee. Es conectar y usar. No solo esto, sino que también se puede optimizar la consulta de todas las dependencias de datos de los componentes. El relé es un buen ejemplo de esto, y las conversaciones al respecto lo explican mucho mejor que yo.

Tengo mucho respeto por la vieja sabiduría, pero eso no debería ser un límite para la evolución y la creación de nuevos estándares, al menos así lo veo.

Mi propuesta es básicamente no cambiar React, pero tener una forma 'solo virtual' de alterar el árbol dom/components, básicamente un React que se puede 'ejecutar' en el lado del servidor, lo que creo que es bastante simple de hacer (solo bloquear acciones para alterar el DOM). Todavía puede tener almacenamiento en caché y tener un CDN en su lugar. Con la creciente dinámica de los sitios y las aplicaciones hoy en día, el almacenamiento en caché estático tenderá a reducirse, pero ese es otro tema 😄

Si la vista especifica dependencias en los datos, necesitará alguna forma de optimizar el gráfico de dependencia y transformarlo en un número mínimo de consultas; no puede preparar los datos antes de construir la vista, aunque podría dividirlos en dos fases, que es lo que entiendo que es el plan en este hilo. Tome una colección de componentes moderadamente complejos que tendrían cada uno una consulta propia, por ejemplo; tal vez no sean todas instancias del mismo componente y no haya un patrón que pueda colapsarse en una sola consulta. Supongo que esto es lo que GraphQL está abordando. Aún necesitará implementar o integrar servidores GQL para cada uno de sus almacenes de datos. Clasificar qué partes de la consulta GQL optimizada debe manejar qué almacén de datos suena bastante complejo, pero mi propuesta también tiene algo de esa complejidad.

En el ejemplo, donde hay una gran cantidad de rutas que necesitan los mismos datos, en realidad no vería eso como una razón para excluir el identificador de esa fuente de datos de la URL. Montaría un pequeño módulo de middleware de obtención de datos bastante cerca de la raíz de la pila, que adjuntaría ese objeto de usuario a un contexto y luego pasaría el contexto a más middleware, en su camino hacia un controlador de ruta final. Es posible que el componente raíz de React no se preocupe por esa parte particular del contexto, pero lo pasaría al siguiente nivel de niños que podrían preocuparse por eso, o tener descendientes propios que se preocupen. Si esto introduce un cableado irrazonablemente complejo o profundo, entonces se podría requerir algo como una tienda Flux, pero ese es un gran tema en sí mismo.

Separación y aislamiento: ahí siento tu dolor. En mi propuesta, tenemos dos sistemas muy diferentes, transformando datos de un formulario optimizado para almacenamiento y recuperación, a un formulario optimizado para simplicidad abstracta. Luego, mi vista es transformar eso en una forma que se adapte a él, a medida que se transmite a la jerarquía de componentes. Mantener esos dos sistemas acoplados libremente mientras se agrega una función que requiere adiciones a ambos sistemas no es lo que yo llamaría difícil, pero tampoco es el camino de menor resistencia. El cambio mental entre la programación de un almacén de datos y la programación de una interfaz de usuario dinámica es real. Solíamos tener desarrolladores backend y frontend separados, y HTTP servía como interfaz entre estos dos paradigmas. Ahora estoy tratando de internalizarlo en una aplicación y tú estás tratando de delegarlo.

No estoy convencido de que la creciente complejidad y actividad dentro de las aplicaciones impida que el estado del cliente sea representado por un objeto muy pequeño, que hace referencia a un conjunto de datos mucho más grande en el servidor. Considere un juego de disparos en primera persona multijugador masivo: suceden muchas cosas rápidamente y desea transmitir la cantidad mínima de información necesaria desde el cliente al host/servidor del juego. ¿Qué tan pequeño podrías conseguir eso? Un mapa de todos los estados de entrada; una marca de tiempo + rango de incertidumbre, un campo de ~110 bits para el teclado y unas pocas docenas de bytes para el mouse/joystick y la orientación de los auriculares VR. El cliente estaría prediciendo, representando y analizando de forma optimista, y el servidor obtendría una gran cantidad de información del pequeño estado y el historial de estado del cliente, y devolvería una cantidad bastante grande de datos para corregir y actualizar todos los clientes ( todos los mismos parámetros para todos los agentes cercanos, inserción de activos), pero ese flujo de solicitudes de clientes aún podría encajar cómodamente en solicitudes de 2 KB. Y eso podría ser una bendición para la arquitectura de la aplicación.

No le pediré que me eduque sobre Relay y GraphQL; Solo los exploré superficialmente, antes de que sus API alcanzaran una versión estable (¿están bloqueadas ahora?) Si todavía está convencido de que usar componentes para seleccionar esquemas de GraphQL, que especifican dependencias de datos, que determina qué datos reales deben obtenerse, es un buen plan, entonces tal vez sea hora de que les eche otro vistazo. Tengo algunas preguntas difíciles sobre esa arquitectura, pero estoy a punto de salirme del tema.

:cervezas:

PD: no quise sugerir que HTTP sería un buen protocolo de comunicación para un MMOFPS

@d4goxn Entiendo completamente su vacilación y escepticismo sobre esto. Nunca pensé en esto hasta que comencé a trabajar con GraphQL y luego la introducción de los conceptos de Relay. Realmente te recomiendo que le des un vistazo. E implementar GraphQL en un servidor nuevo o existente no es tan difícil como podría imaginar, incluso si tiene múltiples almacenes de datos. No quiero salirme del tema también, pero esta es una discusión importante.

Creo que este nivel de abstracción es el camino a seguir. Lo he estado usando en Relax CMS y es un placer trabajar con el flujo de trabajo y el aislamiento. No solo eso, sino que los datos solicitados no son ni más ni menos lo que necesita la interfaz de usuario, y eso se hace automáticamente al recopilar qué datos necesita cada componente y fusionarlos. Esto lo hace Relate http://relax.github.io/relate/how-it-works.html si está interesado en verificar. Con esto también puedo incluir cualquier componente en cualquier lugar de mi aplicación y estar seguro de que funcionará como un bloque independiente de mi aplicación.

Lo único que queda por resolver es la representación del lado del servidor. Después de revisar el código fuente de reaccionar, creo que ya podría haber una solución como la describí, si no, podría trabajar en una solución si alguien del equipo de reacción está de acuerdo con esto.

@d4goxn También me atreveré a discutir con usted :) Su publicación original contiene una declaración de que la vista es una función de los datos y los datos son una función de la URL . Yo no llamaría a esto exactamente revelador. Se aplica prácticamente a cualquier sitio web o aplicación web que no sea completamente aleatoria (existe la duda de si, por ejemplo, el estado de las ventanas de diálogo abiertas también debe ser parte de la URL). Como todos teníamos matemáticas en la escuela, sabemos que la función se puede componer. Entonces, la declaración real podría ser que lo que ve en la pantalla es una función de la URL . Nuevamente, nada realmente revelador, es solo una formalización de lo obvio.

Para mí, la pregunta principal es cómo construir tal función .

El enfoque que sugiere se parece mucho a las buenas aplicaciones MVC del lado del servidor (por ejemplo, Spring MVC es un buen ejemplo). La URL actual activa el método de controlador correspondiente que _hace la lógica comercial_. Obtiene todos los datos requeridos y los pasa a la vista. Mi problema con esto es el siguiente: cuando observa la página web generada, es una especie de jerarquía de componentes (no necesariamente componentes de React). Lo que tienes que hacer es crear o generar la jerarquía a partir de la URL. ¡Pero tienes que hacerlo dos veces! En primer lugar, debe decodificar la jerarquía en el controlador para saber qué datos necesita obtener y, en segundo lugar, debe decodificar la jerarquía de la vista para... bueno... representar la vista real. No creo que sea un enfoque muy _DRY_.

No estoy tan familiarizado con Spring MVC, pero uso muy a menudo otro marco MVC (marco PHP llamado Nette) que aborda este problema de una manera muy similar a cómo me gustaría usar React. Este marco también admite componentes. La idea es que para cada componente (por ejemplo, un formulario) defina una fábrica en su código que sea responsable de instanciar el componente y especialmente de _cargar todos los datos necesarios_. Entonces es posible incluir dicho componente en cualquier parte de la vista. Entonces, mientras escribe el código HTML y crea la _jerarquía de vista_, simplemente puede insertar un componente y la fábrica subyacente se encarga de la inicialización necesaria. El componente se comporta como un pequeño controlador independiente, tiene su propio ciclo de vida, maneja sus dependencias e incluso se puede parametrizar desde la vista lo que aumenta su reutilización.

También he estado usando este enfoque con React en el lado del cliente y parece muy viable. Los componentes de React se han denominado desde el principio como _controller-views_ y así es como los uso. Tengo mis componentes _controller_ que son responsables de la obtención de datos y luego tengo mis componentes _view_ que solo se preocupan por las imágenes. Toda la página está compuesta por componentes de ambos tipos. Están muy bien desacoplados y son fácilmente reutilizables.

Mi aplicación no es isomorfa (o universal como la llaman hoy) ya que no la necesitaba, pero la pila que estoy usando debería ser capaz de eso (y creo que, aparte de Relay, que aún es experimental, esta pila caminó el camino más largo en este asunto). FYI, así es como se ve:

  • La herramienta principal es react-router . Permite enganchar solicitudes de datos asíncronos en cada URL y funciona tanto del lado del cliente como del lado del servidor. Hablando formalmente, este enfoque adolece del problema que mencioné anteriormente. Esos componentes _controller_ no son posibles. Pero lo que importa aquí es el diseño de react-router . Permite definir tanto la _jerarquía de datos_ como la _jerarquía de vista/componentes_ en el mismo lugar y las definiciones de rutas reales también son jerárquicas. Se siente muy _React-like_.
  • Todo el estado (los datos) se maneja con redux . Además de todas las demás ventajas, mantiene todo el estado en un solo objeto, lo que significa que es muy simple y natural crear el objeto en el servidor y enviarlo al cliente. Además, gracias al enfoque connect() , no es necesario pasar todos los datos desde el nivel superior hacia abajo usando accesorios (eso sería una locura teniendo en cuenta el tamaño actual de mi aplicación).
  • Otra pieza del rompecabezas es volver a seleccionar, lo que nos permite mantener el estado lo más pequeño posible. Y también aumenta en gran medida el factor de desacoplamiento.

Todavía no es perfecto, pero es un progreso significativo de lo que estaba disponible cuando encontré este hilo por primera vez. La implementación de muestra se puede encontrar aquí .

Yo también tengo casi todo el trabajo isomorfo, excepto que necesito una forma de lograr la representación del lado del servidor cuando las recuperaciones de datos están a nivel de componente. Sin agregar al argumento filosófico en este momento, durante mucho tiempo he estado usando con éxito Vanilla React (más el enrutamiento de cosecha propia) con componentes que obtienen sus propios datos en componentDidMount. Realmente no me gusta la idea de mantener una estructura redundante para definir mi componente y las dependencias de datos; eso ya está definido en mi árbol de componentes, y aquí de alguna manera necesito quizás múltiples pases de renderizado que incorporen datos a medida que se obtienen de forma asíncrona, hasta el componente completo El árbol ha sido resuelto.

En este momento, solo quería secundar la creciente importancia de esto para la reacción isomófica, en mi opinión. Intentaré encontrar una solución mientras tanto. Gracias.

@decodeman en caso de que esté interesado, para Relate, hice un recorrido de reacción mínimo para obtener dependencias de datos de los componentes https://github.com/relax/relate/blob/master/lib/server/get-data -dependencias.js. De esta manera, puedo obtener todas las dependencias de datos -> buscar los datos -> reaccionar renderizar a la cadena.

@decodeman , FWIW, hemos estado usando el enfoque de doble renderizado (como lo que mencionas) con éxito en una gran aplicación de producción. Está inspirado en este ejemplo de redux-saga , pero el enfoque general debería funcionar bien con cualquier tipo de carga asíncrona. Solo necesita mover la carga a componentWillMount . Para ayudar a mantenerlo manejable, también tenemos componentes de orden superior que se encargan de los escenarios de carga comunes (por ejemplo, verifique el accesorio x y cargue si es nulo.

También creo que sería muy importante poder cargar datos dentro de los componentes y tener algún tipo de método de renderizado perezoso. Tal vez para hacer felices a ambas partes, cree un nuevo método llamado asyncRenderToString y agregue un nuevo evento de "ciclo de vida" llamado asyncOnLoad o algo así. Esto solo se llamará si llama a un método asyncRender. En términos de duplicación de código entre el servidor y el lado del cliente, los desarrolladores pueden solucionarlo simplemente moviendo el código de carga común a un método separado y llamando a ese método desde asyncOnLoad y componentMount . Detectar si se realiza asyncOnLoad probablemente debería usar una Promesa devuelta (si se devuelve indefinido/método no definido, debe llamar directamente a los eventos del ciclo de vida aplicables y luego renderizar), de lo contrario, espera hasta que se resuelva la promesa.

Creo que una solución como esa resolvería todas las formas complicadas que tenemos que usar en este momento para una representación adecuada del lado del servidor. Incluyendo, por ejemplo, permitir una fácil representación del lado del servidor de las aplicaciones de reacción con el enrutador de reacción v4 (que ya no usa una configuración de ruta centralizada que en este momento es la única forma de hacer que las aplicaciones isomórficas funcionen)

Esto se ha resuelto, su flujo llamado check out react stream.

El jueves 3 de noviembre de 2016, Florian Krauthan [email protected]
escribió:

También creo que sería muy importante poder cargar datos dentro
componentes y tienen algún tipo de método de renderizado perezoso. Tal vez para hacer ambos
lados felices crean un nuevo método llamado asyncRenderToString y agregan un nuevo
evento de "ciclo de vida" llamado asyncOnLoad o algo así. Esta voluntad
solo se llamará si llama a un método asyncRender. En términos de código
duplicación entre el servidor y el lado del cliente, esto puede ser arreglado por los desarrolladores
de simplemente mover el código de carga común a un método separado y llamarlo
método de asyncOnLoad y componentMount. Detectar si asyncOnLoad es
hecho probablemente debería usar una Promesa devuelta (si indefinido es
devuelto/método no definido, debe llamar directamente al
eventos del ciclo de vida y luego renderizar) de lo contrario, espera hasta que la promesa
resuelve

Creo que una solución como esa resolvería todos los trucos que tenemos para usar
en este momento para la representación adecuada del lado del servidor. Incluyendo por ejemplo permitir
renderizado fácil del lado del servidor de aplicaciones de reacción con el enrutador de reacción v4
(que ya no usa una configuración de ruta centralizada que ahora es la
única forma de hacer que funcionen las aplicaciones isomorfas)


Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/facebook/react/issues/1739#issuecomment-258198533 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ATnWLUIEJw4m1Y3A4oGDOBzP6_ajDcqIks5q6g4_gaJpZM4CHAWq
.

@yamat124 si busco la secuencia de reacción, solo encuentro este proyecto: https://github.com/aickin/react-dom-stream

Al menos según la documentación, no hay forma de tener un evento de ciclo de vida para precargar datos que retrase la próxima llamada de procesamiento para subhijos hasta que se resuelva la Promesa. ¿O estás hablando de otra cosa?

Una vez más, todas son formas muy hacky. Requiere que el enrutamiento sea independiente del árbol de componentes. Y el componente "Principal" que incluye la ruta debe conocer todos los datos que deben cargarse (funciona el 99% del tiempo pero no siempre). Ambos serían obsoletos si react tiene un renderizado de componente retrasado.

Estoy de acuerdo, esto es probablemente lo que más odio en este momento sobre reaccionar. Odio cuando la gente trata de defender el framework de no tener esta función diciendo que se puede hacer manualmente, por supuesto que se puede, como todo lo relacionado con el desarrollo de software, pero eso no significa que deba hacerlo. El hecho de que haya montones de módulos por ahí que se ocupen de si esta cosa precisa debe señalar por sí mismo que es algo que debe resolverse desde dentro del propio marco.

Next.js tiene un async getInitialProps para hacer la representación del lado del servidor, desafortunadamente solo necesita llamar al getInitialProps del niño desde los padres hasta la raíz. El cliente Apollo GraphQL también permite recorrer todo el árbol para recopilar los requisitos de datos del lado del servidor y luego enviar todo de vuelta. Ejemplo de Next + Apollo: https://github.com/sedubois/realate.

Sería genial tener métodos de ciclo de vida de componentes React asíncronos para combinar estas cosas.

Acabo de enterarme de SSR esta semana, espero que lo que escribo tenga sentido 😄

/cc @nkzawa @stubailo

Por cierto, componentWill/DidMount ya son asíncronos, ¿eso ayuda?

https://twitter.com/Vjeux/status/772202716479139840

@sedubois, ¿React espera a que se resuelva la promesa antes de renderizar? De lo contrario, ¡realmente no ayuda!

@olalonde parece que sí: https://github.com/sedubois/react-async-poc/blob/1d41b6f77e789c4e0e9623ba1c54f5ed8d6b9912/src/App.js

const getMessage = async () => new Promise((resolve) => {
  setTimeout(() => {
    resolve('Got async message!');
  }, 3000);
});
class App extends Component {
  state = {
    message: 'Loading...',
  };
  async componentWillMount() {
    this.setState({ message: await getMessage() });
  }
  render() {
    return (... {this.state.message} ...);
  }
}
loadinggot

@sedubois Tenga en cuenta que, en su ejemplo, componentWillMount

  async componentWillMount() {
    this.setState({ message: await getMessage() });
  }

devuelve undefined y una promesa, pero las capturas de pantalla muestran que React no espera (no podría) la promesa, porque representa el texto Loading... .

Edito: Corregido.

@sedubois @polytypic También hará que todos los errores arrojados dentro de la función se ignoren silenciosamente, ya que la Promesa no se maneja en absoluto.

@polytypic @dantman No estoy muy seguro de lo que significa "la promesa no se maneja en absoluto". Lo espera el operador await . Debe intentar detectar el error, actualicé el ejemplo (usando la firma de carga/error similar a lo que hace Apollo).

    try {
      this.setState({ message: await getMessage() });
    } catch(error) {
      this.setState({ error });
    }

Y renderizar:

{error ? error : loading ? 'Loading...' : message}
screen shot 2016-11-07 at 13 25 46

@sedubois Cuando async una función, implícitamente devuelve una promesa, cualquier throw en la función implícitamente se convierte en un rechazo de esa promesa en lugar de tirar la pila. Esto significa que componentWillMount ahora está devolviendo una promesa a la persona que llama.

React no hace nada con el valor de retorno, por lo que su función ahora devuelve una Promesa esperando que alguien esté haciendo algo con ella, pero en su lugar se descarta.

Debido a que no se maneja, eso significa que cualquier rechazo de esa promesa no será detectado por ningún código en React y cualquier error no manejado no aparecerá en su consola.

E incluso hacer un intento... atrapar no te protegerá perfectamente de esto. Si setState arroja un error de su captura o comete un error tipográfico en la captura, ese error desaparecerá silenciosamente y no se dará cuenta de que algo está roto.

@sedubois En realidad, un método asíncrono siempre parece devolver una promesa en JavaScript (bueno, futuro JavaScript). Estaba confundido al pensar en problemas con async-await en C#. _Disculpas por el ruido!_

Sin embargo, las capturas de pantalla muestran claramente que React no espera a que se resuelva la promesa. Llama a render inmediatamente después de llamar a componentWillMount y muestra el texto Loading... . Luego llama a render nuevamente después de la llamada a setState . Si React esperó a que se resolviera la promesa, no mostraría el texto Loading... en absoluto. En su lugar, esperaría hasta que se resuelva la promesa y solo entonces llamaría a render , que es el tipo de comportamiento del que se trata este problema: poder realizar E/S asíncrona durante la representación del lado del servidor.

@dantman @polytypic gracias 👍 Sí, seguro que se necesitan cambios dentro de React para esperar la respuesta y manejar los errores correctamente. Tal vez agregar await aquí y allá en su código podría hacerlo 😄

Personalmente, estoy usando Web Workers para almacenar el estado de la aplicación y emitir accesorios en caso de cambio (ya que separé completamente el almacenamiento de datos y la obtención de los componentes de React, y trato a React como un renderizador, para preservar el principio de flujo unidireccional), por lo que solo espera hasta que un mensaje con los requisitos esperados (como que una propiedad específica sea verdadera) para representar.

De esa manera, incluso si los primeros mensajes son estados de "carga" intermedios, no se usan para las representaciones estáticas del lado del servidor, sin la necesidad de métodos de componentes asíncronos.

@sedubois Tendría que ser una pila diferente de métodos; o un cambio radical. Los métodos de procesamiento actuales de React están sincronizados, por lo que necesitaríamos un método de procesamiento de servidor diferente que devuelva una promesa.

@dantman No quiero dar ninguna opinión aquí ya que estas aguas son demasiado profundas para mí (mi objetivo inicial era solo señalar el componente asincrónico WillMount de Vjeux...), pero hmm, no puedo Reaccionar mira el tipo de objeto devuelto por los métodos del ciclo de vida y si es una promesa, manejarlo asíncrono y, de lo contrario, manejarlo sincronizado (como actualmente, es decir, de manera compatible con versiones anteriores)?

Me gusta lo que propuso @fkrauthan . Un método de ciclo de vida load que puede devolver una promesa o undefined y una función asíncrona adicional renderToStringAsync . Pero siempre se debe llamar a load , en el cliente y en el servidor. Si usamos render o renderToString la promesa devuelta se ignora para que coincida con el comportamiento que tenemos hoy. Si usamos renderToStringAsync y load devuelve una promesa, la promesa debe resolverse antes de renderizar.

¿Por qué no hacer como Next.js , agregar una función de ciclo async getInitialProps vida React

const props = await (Component.getInitialProps ? Component.getInitialProps(ctx) : {});
...
const app = createElement(App, {
  Component,
  props,
  ...
});

Parece que Next.js logra hacer el trabajo, excepto que su getInitialProps no está vinculado al ciclo de vida del componente React, por lo que solo se puede llamar en el componente de nivel superior. Entonces, ¿las ideas están ahí y podrían unirse?

@sedubois No creo que getInitialProps sea el lugar correcto. En primer lugar, las personas que usan ES6 no tienen/usan ese método. En segundo lugar, no desea trabajar en initalProps (que son solo los valores predeterminados), desea trabajar sobre la combinación de initalProps + accesorios pasados. Por lo tanto, mi sugerencia de un nuevo evento de ciclo de vida que se envíe antes de componentWillMount .

Creo que la idea de @Lenne231 podría causar algunos problemas, ya que las personas podrían transmitir otros eventos del ciclo de vida para que esa promesa ya esté resuelta. Tener ahora dos comportamientos diferentes del mismo evento del ciclo de vida lo hace un poco complicado.

Por lo tanto, debe haber en el servidor y en el cliente un método de procesamiento de sincronización que no llame a ese nuevo ciclo de vida en absoluto. Y una versión asíncrona que llama a ese ciclo de vida y siempre espera hasta que se resuelve la promesa antes de continuar. En mi opinión, eso le daría la mayor flexibilidad, ya que ahora puede decidir si necesita ese ciclo de vida también en el renderizado de su cliente o solo para el renderizado de su servidor.

las personas que usan ES6 no tienen/usan ese método

@fkrauthan tal vez te refieres a getInitialState ? Aquí estaba hablando de uno nuevo, getInitialProps (el nombre usado en Next.js).

En cuanto a su segundo punto, sí, podría ser mejor tener el nuevo método de ciclo de vida antes de componentWillMount lugar de antes de constructor . Supongo que no es así en Next porque no tienen el lujo de modificar el ciclo de vida de React como se mencionó anteriormente. Por lo tanto, sería genial traer esas ideas dentro de React.

@sedubois Incluso ese. Uso, por ejemplo, la función es7 y defino static props = {}; en el cuerpo de mi clase. En mi opinión, eso hace que el código sea mucho más agradable de leer y estoy seguro de que más y más personas cambiarán a eso tan pronto como se publique oficialmente es7.

Nota: las propiedades de clase no son una característica de "ES7" porque ES7 ya se ha finalizado y la única característica nueva del idioma es el operador de exponenciación. A lo que te refieres es a la propuesta de propiedades de clase de la etapa 2. Todavía no es parte del idioma y podría cambiar o eliminarse.

No veo la necesidad de funciones asincrónicas en reaccionar para obtener datos, en cambio, estaría feliz con un renderizador virtual que renderizaría ToString a pedido. Por lo tanto, podría establecer acciones y el árbol virtual se actualizaría en consecuencia antes de representar la cadena. Esto creo que sería ideal para lograr un buen SSR en react. Deja al desarrollador cómo y dónde obtener los datos.

Lo mencioné antes, pero una buena API sería algo como lo siguiente (con una pequeña actualización con lo que sugerí antes):

const virtualTree = ReactDOM.renderVirtual(rootEle);

// get the bundled query from redux store for example
const bundle = store.getState().bundle;

// Fetch the data according to the bundle
const data = await fetchDataSomehow(bundle);

// hydrate store (this will set updates on the virtual tree)
store.dispatch({type: 'hydrate', data});

// final result
const domString = virtualTree.renderToString();

Esto evitaría el problema que tienen algunos clientes de GraphQL, como Apollo, que los obliga a hacer un doble renderToString. Siendo el primero en obtener dependencias de datos, y el segundo renderizado con los datos poblados. Creo que esto es un gran desperdicio de procesamiento ya que renderToString es bastante costoso.

@bruno12mota Veo su punto, pero nuevamente, solo la cantidad de bibliotecas que intentan simular eso (y con su solución todavía habrá miles de bibliotecas) es para mí una señal de que tal vez esa debería ser una característica central en lugar de algo que tiene que construir encima. Su solución aún requeriría muchas más llamadas de procesamiento (virtuales pero aún llamadas de procesamiento) para obtener el resultado.

@fkrauthan sí, pero los renderizados virtuales tienen mucho más rendimiento que tener que hacer dos renderizados en una cadena, que es el problema principal aquí, en mi opinión (rendimiento en SSR), es la parte débil de React. Ha habido algunos experimentos para mejorar los renderToString pero el equipo de React no ha avanzado mucho en este asunto (sin criticar aquí, hacen un trabajo increíble).

Estoy de acuerdo con @bruno12mota , después de haber deliberado sobre este tema durante un tiempo, ese es el enfoque más simple. Deja la libertad al desarrollador de determinar cuándo debe "descargarse" su renderizado.

Hace que todo sea mucho más complicado.

1.) Desde el punto de vista del código (revisé el código y hacer que el renderizado sea asíncrono debería ser mucho más fácil que crear un renderizador que solo use VDom y luego pueda volcarse en un punto)
2.) Ahora también debe reconsiderar los eventos de desmontaje. Y hace que el código personalizado sea mucho más complicado de lo necesario. Por ejemplo, la carga de datos puede resultar en la representación de un nuevo componente que también necesita cargar datos. Ahora, de repente, la lib necesita averiguar qué componente en qué posición ya cargó datos y qué componente contiene nuevos datos que se cargan y otras cosas.

Estoy con @bruno12mota. Podría decirse que un VDom es una solución más de "bajo nivel", pero probablemente sería más fácil de implementar para los desarrolladores de React, no tendría problemas de compatibilidad con versiones anteriores y podría permitir a la comunidad experimentar con diferentes soluciones de nivel superior.

Por ejemplo, una solución de nivel superior podría ser un "componente de nivel superior" que envuelva métodos asíncronos componentWillMount , espere todas las promesas pendientes y active VDom.renderToString() cuando todo se resuelva. Estoy usando este tipo de estrategia para SSR en mi aplicación, aunque desafortunadamente hago muchas búsquedas en este momento porque no hay VDom.

Creo que la mejor manera sería no cambiar ninguna de las funciones que tenemos actualmente. Lo que significa no hacer que getInitialState, componentWillMount o renderToString se comporten de manera diferente.

En cambio, deberíamos agregar nuevas funciones para resolver el problema discutido aquí. Al igual que hay funciones que solo se llaman en el cliente, podría haber funciones que solo se llaman en el servidor.

Creo que podríamos agregar una función "renderToStringAsync" a react-dom/server. La diferencia con el renderToString normal sería que devuelve una Promesa.

En el lado de la implementación, la única diferencia sería que la versión asíncrona, cada vez que crea una instancia de Componente, llama y espera "inicializar ()" primero, antes de llamar a render (). Si el componente no tiene un método de "inicializar", podría llamar a render() inmediatamente.

Entonces, si tenemos la siguiente estructura de aplicación de ejemplo:

    ComponentB
    ComponentC
        ComponentD
        ComponentE

el proceso seria:

1) instanciate componentA
2) await componentA.initialize();
3) componentA.render()
4) do in parallel(
    instanciate componentB, await componentB.initialize(), componentB.render()
    instanciate componentC, await componentC.initialize(), componentC.render(), do in parallel(
        instanciate componentD, await componentD.initialize(), componentD.render()
        instanciate componentE, await componentE.initialize(), componentE.render()
    )
)
5) render to string

Entonces, básicamente, solo necesitamos una nueva función "renderToStringAsync" que haga uso de una nueva función opcional "inicializar". No sería tan difícil.

@VanCoding tiene la misma idea en la que he estado pensando durante un tiempo. Me preguntaba si initialize también podría llamarse automáticamente en componentDidMount en el cliente.

Así que en el servidor sería:

initialize()
render()

Y en el cliente sería:

render() // without data (unless available synchronously)
componentDidMount()
initialize()
render() // with data

Tengo una implementación funcional de la representación del servidor de reacción con react-router v4 y react-apollo con GraphQL.

se inspiró en server-side-rendering-code-splitting-and-hot-reloading-with-react-router .

Básicamente, la representación del servidor usa un componente sincronizado, la representación del cliente usa un componente asincronizado para que el paquete web asincrónico cargue y ejecute el módulo javascript.

Por cierto, la representación del servidor se implementa con Nashorn Script Engine (JVM).

Código de representación del cliente
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/main.js#L63 -L82

Código de representación del servidor
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/main.js#L128 -L155

Hay un lugar importante para distinguir el componente sincronizado o asincronizado en la ruta.
Componente sincronizado en Ruta
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/Route.js#L10

Componente asincronizado en RouteAsync
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/RouteAsync.js#L7 -L23

NOTA: el nombre del archivo DEBE ser Route.js o RouteAsync.js. El código siempre debe importar RouteAsync.js. Webpack lo reemplazará con Route.js si está diseñado para la representación del servidor.
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/build/webpack.config.js#L72 -L80

Por lo tanto, habrá dos versiones de compilación dist y dist_ssr , dist es para el cliente mientras que dist_ssr es para el servidor que solo tiene dos partes: proveedor y aplicación. . El estado de la tienda se exporta y se incrusta en <script> de index.html como __INITIAL_STATE__ . La razón para tener dos versiones de compilación es que ReactDomServer.renderToString() aún no es compatible con el componente asíncrono.

El único problema con el enfoque de compilación de dos versiones es que hay una advertencia en el modo de desarrollo:
React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:

Pero dado que la única diferencia entre la versión del cliente y la del servidor es Route.js y RouteAsync.js, por lo que se puede ignorar la advertencia, la advertencia no se muestra en el modo de producción.

Para la obtención de datos asíncronos antes de la representación de componentes en el lado del servidor:

  • Rest API: se agregaron datos de carga estáticos en el uso del componente
const { matchedRoutes, params } = matchRoutesToLocation(rootRoute.routes, 
                                                        location, [], {}, rootRoute.pattern)
matchedRoutes.filter(route => route.component.loadData).map(route =>
              route.component.loadData(store, params))

para sondear y ejecutar loadData . matchedRoutes es una matriz de rutas coincidentes cuyo padre es del índice 0 . loadData se puede ejecutar en secuencia o en paralelo.

  • Consulta GraphQL: simplemente use getDataFromTree de react-apollo/server para ejecutar todas las consultas GraphQL.

Por cierto: el uso de Nashorn Script Engine tiene una oportunidad de optimización del rendimiento: dado que el proveedor contiene código de biblioteca y polyfills que se ejecutan una vez y se reutilizan, por lo que para solicitudes posteriores, solo se ejecuta el fragmento de la aplicación. Dado que las bibliotecas se mueven a la porción del proveedor, la aplicación contiene solo el código JavaScript en src, que es pequeño, por lo que es un aumento del rendimiento en comparación con la evaluación de la porción del proveedor cada vez. (Todo javascript se compila una vez y se almacena en caché, se ejecuta para cada solicitud).

Mentí que el fragmento de la aplicación solo contiene código src. también contiene babel-runtime y core-js/library duplicados a los que hace referencia babel-runtime . babel-runtime no se puede mover a la parte del proveedor debido a que no tiene index.js o entrada principal, por lo que el paquete web no pudo reconocerlo como un módulo. pero el tamaño del código es pequeño.

Profundicemos más en el ejemplo de @VanCoding: https://github.com/facebook/react/issues/1739#issuecomment -261577586

Tal vez inicializar no sea un buen nombre: el nombre debe dejar en claro que está destinado a usarse solo en el servidor, o en un entorno donde la representación se retrasa hasta que esta función regrese, es decir, no en el cliente. Ideas: getStaticProps , getServerProps , getInitialProps , getAsyncProps ...

También sería bueno saber del equipo central si existen impedimentos técnicos o de otro tipo para desarrollar dicho método renderToStringAsync.

@nmaro Bueno, creo que la función no debería devolver nada, por lo que preferiría un nombre que no comience con "get", pero aparte de esto, sigo pensando que esta sería la mejor manera. No me importa cómo se llame al final.

Además, hice una prueba de concepto para esto , en unas 40 líneas de código. No está diseñado para uso en producción, pero mis pruebas con varios componentes fueron exitosas.

Entonces, si el equipo de reacción aún no quiere que esto suceda, no sería tan difícil hacerlo en una biblioteca de terceros.

Alternativamente, podríamos agregar una opción para renderToString, como en
renderToString(Component, {async: true})

@nmaro Eso haría que el tipo de devolución de la función dependiera de las opciones proporcionadas, lo que no considero una buena práctica. Creo que una función siempre debe devolver un resultado del mismo tipo.

@VanCoding , ¿tiene un ejemplo de cómo usar su código como una biblioteca de terceros?

@VanCoding genial! Pero, sinceramente, creo que será difícil integrar eso en el algoritmo de renderizado interno de React. Consulte https://github.com/facebook/react/blob/master/src/renderers/dom/stack/server/ReactServerRendering.js y piérdase en las partes internas de React... En mi opinión, realmente necesitamos la opinión de alguien del centro.

@sedubois puede usar la función como si usara la función renderToString normal. Pero devuelve una Promesa que se resuelve en una cadena, en lugar de devolver una cadena directamente.

Entonces usarlo se vería así:

var react = require("react");
var renderAsync = require("react-render-async");
var MyComponent = require("./MyComponent");

renderAsync(react.createElement(MyComponent,{some:"props"}).then(function(html){
    console.log(html);
});

En teoría, debería poder representar cualquier componente de reacción existente de la misma manera que lo hace el renderToString normal. La única diferencia es que los componentes asíncronos también son compatibles.

@nmaro Tienes razón. Eso sería útil.

¿Cuál es el estado de esto?

@firasdib Todavía estamos esperando que comenten algunos desarrolladores de reacción, creo.

+1

Los problemas de indexación de Google exigen SSR y todo el equipaje que se describe a continuación es para nosotros y, por lo tanto, este problema es muy importante para que React tenga éxito como biblioteca general.

Creo que toda la representación asíncrona no es necesaria si JavaScript proporciona una forma adecuada de esperar una promesa. Luego, podríamos pasar un parámetro que indique el clima o no, queremos que la carga de datos sea síncrona (para SSR) o asíncrona (para un navegador web real). La lógica de carga de datos podría iniciar una promesa en el constructor de cualquier componente de React y la espera podría realizarse en el componenteWillMount.

Sin embargo, según tengo entendido, los desarrolladores de React han mostrado dudas sobre la utilidad de componentWillMount y recomendaron que el constructor haga todo el trabajo. De cualquier manera funcionará.

Algunas personas han argumentado que hacer que los componentes sean vistas puras soluciona el problema. Son parcialmente correctos. Desafortunadamente, esto no funciona en el caso más genérico en el que no sabemos qué datos cargar hasta que se realiza el árbol de representación.

El núcleo del problema es que la función de renderizado puede tener lógica para seleccionar qué componentes agregar al DOMTree. Eso es lo que hace que React sea tan poderoso y tan poco adecuado para la representación del lado del servidor.

En mi opinión, la verdadera solución es poder registrar todas las promesas de carga de datos emitidas con reaccionar. Una vez que se completen todas las promesas de carga de datos, reaccionar nos devuelve la llamada con el resultado de la representación. Esto tiene una serie de implicaciones:

  • el DOMtree se renderizará varias veces. Es como un sistema vivo que "se asienta" desde un estado de carga de datos inestable a un estado "cargado" estable.
  • existe el riesgo de que el sistema no llegue a un estado "cargado" estable en un sistema con errores. El tiempo fuera tiene que ser introducido para lidiar con eso.

El resultado es un uso mucho más "intenso de computación" de la biblioteca en el lado del servidor con un proceso que no es rápido.

Por lo tanto, todavía estoy pensando en una solución alternativa que comprometa el caso genérico a favor de hacer las cosas :)

Mientras tanto, tenemos que resolver SSR en un entorno de carga de datos asíncrono.

  1. Las llamadas de carga de datos deben derivarse de la URL de solicitud (react-router es un ejemplo). Recopile todas las promesas de carga de datos en una sola lista de promesas.
  2. Use Promise.all(), que en sí mismo es una promesa. Cuando esta promesa se complete, pase los datos cargados a la función de representación del servidor.

Este modelo no está vinculado a una arquitectura de diseño en particular. Parece que flux/redux podría beneficiarse un poco, pero no estoy seguro porque abandoné el modelo redux en mi aplicación.

El problema en este modelo es el artículo #1 arriba. Una aplicación simple conoce todas las llamadas de carga de datos. En una aplicación compleja con "módulos" independientes, este modelo requiere algún tipo de replicación del árbol de componentes original de React e incluso la lógica de renderizado.

No veo cómo se puede hacer que SSR funcione en un proyecto complejo sin repetir la lógica ya escrita en la función render(). Flux podría ser una solución, pero no tengo experiencia para estar seguro.

La implementación del elemento #1 es vaga. En el ejemplo de react-router, tenemos que como enrutador para que nos devuelva los componentes de nivel superior que pertenecen a esa ruta. Creo que podemos instanciar estos componentes:

app.use(function(req, res, next) {
    match({ routes, location: req.url }, (error, redirectLocation, renderProps: any) => {
    if (error) {
        res.status(500).send(error.message)
    } else if (redirectLocation) {
        res.redirect(302, redirectLocation.pathname + redirectLocation.search)
    } else if (renderProps) {
        // You can also check renderProps.components or renderProps.routes for
        // your "not found" component or route respectively, and send a 404 as
        // below, if you're using a catch-all route.
        console.log("renderProps", renderProps)
        for (let eachComp of renderProps.components) {
            // create an instance of component
            // ask component to load its data
            // store data loading promise in a collection
            console.log("eachComp: ", eachComp)
        }
        res.status(200).send(renderToString(<RouterContext {...renderProps} />))
        } else {
        res.status(404).send('Not found')
        }
    })
});

Por lo tanto, los componentes de nivel superior ya no son componentes "puros". Estos componentes ahora juegan un papel de controlador de nivel superior, debido a la capacidad de activar la carga de datos. El modelo Flux no lo hace mejor. Simplemente mueve la ruta a la asociación de la función de carga de datos a un módulo diferente.

Desafortunadamente, continuar con la carga de datos de esta manera a los hijos de los mejores controladores da como resultado una duplicación del gráfico de objetos creado para el propósito de renderizado de reacciones. Para mantener las cosas simples (si es posible), tenemos que poner toda la carga de datos en el nivel superior del controlador.

Eso es lo que veo.

Si me di cuenta de estas implicaciones de SSR, habría pensado dos veces sobre la utilidad de React para las aplicaciones indexables de Google. Quizás flux sea una solución, pero flux hace que la aplicación sea mucho más complicada que una simple aplicación React. He estado allí. En lugar de una simple función de carga de datos, tengo que perseguir mi lógica en varios archivos. A nivel teórico se veía muy, muy bien hasta que tuve un proyecto en marcha. La gente nueva tuvo dificultades para empezar. No hubo tal problema después de sacar redux de la base del código. Pensé que era la respuesta a todas las complejidades del diseño de la interfaz de usuario :)

Desde la React Conf de esta semana, después de que React Fiber (React 16) salga, planean trabajar en lo siguiente (supuestamente para React 17):

screen shot 2017-03-16 at 15 55 51

¿Significa que podrían venir métodos de ciclo de vida de React asíncronos?

Bueno, no es el material de fibra sobre todo de más rápido / más suave re-hacer? En el servidor, renderizamos solo una vez, por lo que no estoy seguro de si esos cambios tendrán algún efecto en el renderizado del lado del servidor.

@VanCoding actualizó mi mensaje anterior, que no estaba claro. Me refiero a sus planes para después de que se acabe la fibra.

Para las personas que aterrizan aquí como yo, terminé creando una implementación personalizada usando reaccionar que podría ayudar.

https://github.com/siddharthkp/reaqt

la clave es asyncComponentWillMount que se ejecuta solo en el servidor. está disponible solo para componentes de punto de entrada, no para todos los componentes para evitar los problemas mencionados aquí.

@siddharthkp ¿Realmente no veo qué intenta resolver su componente? Si solo es compatible con asyncComponentWillMount para el componente React de la aplicación de nivel superior, ¿por qué debería usar su biblioteca? ¿Podría simplemente resolver esa promesa afuera y luego llamar a render? ¿O me perdí algo?

@fkrauthan

¿Podría simplemente resolver esa promesa afuera y luego llamar a render?

Tienes razón, eso es exactamente lo que hace.

El atractivo es que obtienes ssr asíncrono (+ división de código + hmr) con un solo comando. Vi a muchas personas que no implementaron ssr porque no es muy fácil, y esa fue la motivación.

solo es compatible con asyncComponentWillMount para el componente React de la aplicación de nivel superior

  1. No tiene que un componente de aplicación. Puede obtener datos para cada página/pantalla/punto de entrada. (promover múltiples puntos de entrada aquí )

  2. El patrón es intencional, si cada componente del árbol quiere sus propios datos, podríamos terminar alentando un mal patrón de rendimiento. Obtener datos en el componente de nivel superior y pasárselos a los niños como accesorios lo mantiene bajo control.

@siddharthkp, ¿cómo mejora su repositorio con respecto a Next.js (que también se presentó en React Conf por cierto)?

https://github.com/zeit/next.js

De todos modos, la discusión sobre bibliotecas de terceros está fuera de tema en mi humilde opinión, lo que nos interesa es el soporte dentro de React.

la discusión sobre bibliotecas de terceros está fuera de tema en mi humilde opinión, lo que nos interesa es el soporte dentro de React.

Estoy completamente de acuerdo. Puede discutir preguntas específicas en problemas de reaqt

Un caso de uso que tengo para esto es el uso de iconos de vector de reacción nativa y reacción nativa. Quiero que la propiedad de un componente esté determinada por los datos recibidos de una promesa.

const AsyncUser = props => fetchUser()
  .then(data => (
     <User {...data} />
   ))

Si es compatible, creo que esto haría que muchos códigos complejos fueran más fáciles de entender y posiblemente abriría nuevos patrones asincrónicos.

tenemos el mismo problema, quiero hacer el ssr, no solo buscar los accesorios de predicción sino también el contenido obtenido por la acción en componentWillMount, ¿cómo puedo esperar que los accesorios estén preparados y luego convertirlos en una cadena?

Si alguien encuentra esto útil, he escrito una biblioteca llamada react-frontload que le permite vincular una función de retorno de promesa a un componente para que se ejecute cuando el componente se procesa, tanto en el servidor como en el cliente.

La función se ejecuta 'sincrónicamente' en el renderizado del servidor (bueno, no realmente ;-) pero el renderizado del componente final se bloquea hasta que se resuelve), y de forma asíncrona como es normal en el renderizado del cliente, y no se ejecuta de nuevo en el cliente si la página fue solo renderizado por el servidor. También hay más opciones que puede especificar para un control más detallado de cuándo se ejecuta, por componente.

Es realmente útil para la carga de datos, más o menos el caso de uso para el que lo escribí. ¡Pruébalo! 😄

https://github.com/davnicwil/react-frontload

<AsyncComponent 
  delayRendering={LoadingComponent}
> 
   {/*return a promise that returns a component here*/}
</AsyncComponent>

Piense en toda la representación condicional que no tendría que hacer con el enfoque anterior porque sabe que tiene sus datos

Uniéndome a este. Creo que sigue siendo una característica esencial de la que carece React. También acepte que incluso si las bibliotecas de terceros pueden cubrir este tema, esto debe provenir directamente de adentro.

Se me ocurrió una implementación https://github.com/timurtu/react-render-async

Muy buena interfaz de componente asíncrono, @timurtu.

...Hola a todos, solo quería intervenir aquí ya que tengo varias cosas que se relacionan muy directamente con este hilo, y he estado siguiendo este hilo durante mucho tiempo:

  • Por un lado, considerando SSR, creo que la obtención de datos a nivel de ruta tiene más sentido, he aquí por qué:
    Obtención de datos del primer enrutador Redux: resolución del caso de uso del 80 % para el middleware asíncrono
  • La renderización del servidor con Redux ahora es estúpidamente simple, aquí hay una guía paso a paso con Redux-First Router de cómo hacerlo como nunca antes lo había visto: Server-Render like a Pro /w Redux-First Router en 10 pasos
  • por último, para dividir el código correctamente, también debe hacer ambas cosas (es decir, SSR + división) . Resulta que hacer ambas cosas al mismo tiempo ha sido un gran problema , logrado con éxito por unos pocos, y nunca hasta ahora con un paquete general para ayudar a la comunidad. React Universal Component + babel-plugin-universal-import + webpack-flush-chunks (y extract-css-chunks-webpack-plugin ) son una familia de paquetes que realmente resuelven este problema. Y por primera vez. Básicamente, le permite renderizar componentes sincrónicamente en el servidor, pero lo que es más importante, transporta los fragmentos exactos renderizados al cliente para un renderizado síncrono inicial también en el cliente. Incluso obtiene hojas de estilo css chunk a través del complemento webpack. Esto es diferente a lo que compartió @timurtu y muchos de esos componentes, que requieren cargar main.js , analizar, evaluar, renderizar y, finalmente, unos segundos más tarde, solicitar las importaciones dinámicas. Estas son soluciones para resolver el flash de contenido sin estilo (FOUC), pero no para incrustar en su página los fragmentos exactos representados en el servidor. Universal, el nombre de mi familia de paquetes, le permite hacerlo con un tiempo mucho más rápido React Universal Component 2.0 & babel-plugin-universal-import

Sé que no abordé los pros/contras del nivel de ruta frente al componente , pero el primer enlace se enfoca principalmente en eso. Básicamente, se reduce a que el nivel de componente es malo para SSR si tiene componentes anidados cuyos datos deben obtenerse en secuencia (en lugar de en paralelo). Y si correctamente solo tiene que realizar una búsqueda por ruta, es mejor que formalice el contrato adjuntando sus dependencias de datos a una entidad de ruta, en lugar de hacerlo en componentDidMount . Muchos otros arriba llegaron a esta misma conclusión. Redux-First Router formaliza esto muy bien en la línea del tipo de "contratos" que hacen que Redux sea tan agradable para trabajar.

Redux-First Router hace que muchos viejos paradigmas vuelvan a ser nuevos, pero sorprendentemente encaja como una pieza faltante en el ecosistema de Redux. Si alguna vez ha querido un enrutador nativo para el flujo de trabajo de Redux, pruebe Redux-First Router. Es relativamente nuevo, y soy un novato en el código abierto, pero es algo en lo que he trabajado básicamente durante un año y ha tenido mucha tracción en los 2 meses que ha estado fuera. A la gente le encanta. Realmente espero que le eches un vistazo y le des una oportunidad :).

Aquí también está su artículo de lanzamiento que explica cómo es una solución mucho mejor para Redux que React Router, y cómo obtiene la visión correcta de una manera que, lamentablemente, el también excelente Redux-Little Router se perdió:
Prelanzamiento: Redux-First Router: un paso más allá de Redux-Little-Router

La principal diferencia entre RLR y RFR es que RFR envía un tipo de acción diferente por ruta (es decir, cambio de URL), en lugar de siempre solo LOCATION_CHANGED . Esto le permite cambiar los cambios de URL como tipos únicos en sus reductores, al igual que cualquier otra acción. Puede parecer pequeño, pero hace una gran diferencia. Por eso, no requiere componentes de <Route /> , algo que no es necesario en la tierra de Redux. También hay una gran lista de características que Redux-First Router admite, desde la obtención de datos en la ruta thunks hasta React Native, React Navigation, división de código, precarga y más.

Me encantaría escuchar los pensamientos de la gente.

@faceyspacey gracias, creo que soluciona el problema que estaba teniendo, que es cuando desea configurar accesorios basados ​​en datos asincrónicos, por ejemplo, react-native-vector-icons puede devolver un ícono como promesa. Definitivamente es una forma diferente de ver las cosas, pero reduce componentWillMount o reduce X_GET_START , X_GET_END y X_GET_ERROR repetitivo de acción que vincula los componentes asincrónicos al estado, cuando realmente solo desea realizar esa solicitud una vez para renderizar el componente. Si está haciendo cosas como sondear datos, entonces tener estado y usar redux probablemente tenga más sentido

@faceyspacey Veo lo que está diciendo sobre el nivel de la ruta, pero ¿no sería mejor tener componentes más pequeños que solo dependan de obtener los datos que necesitan mientras representan todo lo demás a su alrededor de forma sincrónica?

¿Mejor? ¿Donde? ¿En React Native? ¿En grandes organizaciones como Facebook en sus aplicaciones React Native?

Tal vez en React Native. Cuando hay RSS en mi opinión no hay debate. Lo que desea hacer simplemente no es posible sin múltiples recuperaciones por solicitud, a menos que, nuevamente, lo limite a un componente que pueda realizar la recuperación. En ese momento, es mejor formalizar el contrato con las entidades de ruta.

En cuanto a React Native y SPA, seguro. Pero probar componentes que tienen depósitos de datos integrados es una molestia. Apollo tiene algunas cosas aquí, pero la última vez que revisé tenían una larga lista de tareas pendientes para que las pruebas de componentes emparejados con datos fueran realmente correctas, es decir, sin problemas. Prefiero una configuración en la que tenga una tienda que sea fácil de configurar y simular. Puede reutilizar cómo lo hace en todas sus pruebas. Luego, a partir de ahí, probar los componentes es una representación síncrona simple con la toma de instantáneas. Una vez que agrega dependencias de datos en los componentes, probar React se vuelve menos intuitivo. Pero la gente lo hace y lo automatiza. Hay menos de un estándar y ahora está más cerca del código ad-hoc personalizado. Es más productivo tener una tienda que se pueda llenar de forma asíncrona de una manera muy obvia, en lugar de tener estrategias potencialmente diferentes para cada componente asíncrono que se necesita llenar.

No estoy en contra del nivel de componente si no hay SSR y tiene su estrategia de prueba perfecta y fuera del camino. Si eres una mega corporación, tiene sentido porque entonces cada desarrollador no necesita tocar el nivel de ruta superior y potencialmente romperlo para otros. Es por eso que FB fue pionera en esta ruta con Relay y GraphQL. El hecho es que solo 100 empresas en el mundo están realmente en este barco. Por lo tanto, considero que la colocación es más una exageración que una realidad para la mayoría de las personas/aplicaciones/empresas. Una vez que dominas el tema del nivel de ruta y tienes un paquete que lo hace realmente bien como Redux-First Router , entonces tienes un enfoque mucho menos enredado y más fácil de desarrollar para los equipos cuyos miembros pueden tocar el nivel de ruta . Solo echa un vistazo a Demo en la página de inicio de codesandbox:

https://www.codesandbox.io

Básicamente, una vez que vea cómo se hace, me encantaría escuchar su opinión. Tenga en cuenta que esto es un SPA. Por lo tanto, tendrá que revisar el repositorio de demostración o el modelo estándar para ver un ejemplo de SSR. Pero este es un buen comienzo.

Creo que la forma más fácil será admitir el método de representación asíncrona (la representación devuelve una promesa). Permitirá que el componente raíz espere la representación de los componentes secundarios y, por supuesto, el resultado de la representación será una promesa. Implementé algo como esto en preact aquí https://github.com/3axap4eHko/preact-async-example

@faceyspacey Gracias por la respuesta detallada anterior. Realmente me gusta la idea de Redux-First Router, definitivamente resuelve todos mis problemas y hace que las cosas sean mucho más transparentes y limpias que las que teníamos antes.

¡Muchas gracias!

@raRaRa me alegra que te guste! ... uno de los mayores desafíos es que hasta ahora no ha habido una buena abstracción para la obtención de datos a react-router-config ), pero no es obvio ni de primera clase en la v4. Es una ocurrencia tardía. Sin embargo, lo más importante es que no vive en Redux. Entonces, solo ahora es la primera vez que los usuarios de redux tienen una abstracción completa de "nivel de ruta".

Creo que ahora con RFR, veremos un replanteamiento de la importancia del "nivel de ruta" frente al "nivel de componente". Cosas como Relay y Apollo han creado mucha expectación sobre el emparejamiento de dependencias de datos con componentes, pero si no está usando esos 2, es probable que termine en un mundo de dolor en algún momento al hacer cosas en los controladores de ciclo de vida. .

Dicho esto, eventualmente RFR tendrá una integración a nivel de ruta con Apollo. Hay muy pocos beneficios por hacer las cosas a nivel de componente, menos las raras excepciones antes mencionadas. Al menos el 80 % de las veces (y quizás el 100 % en tu aplicación), puedes determinar todos los datos que necesitas en función de la ruta. Si no es posible, es probable que se deba a que está derivando datos en su "capa de vista" que muy bien podrían repensarse para que todos sean interpretados por la URL. Entonces, incluso con herramientas como Apollo, comienza a involucrarse en antipatrones problemáticos que probablemente eventualmente le causen dolor al dejar que el "estado" viva en ningún otro lugar que no sea la capa de visualización. Es decir, el estado necesario para realizar esas búsquedas de datos. A menudo tiene que refinar los parámetros exactos utilizados para obtener sus datos. Entonces, toma el estado y los accesorios y transforma los 2 para obtener los parámetros justo antes de obtener los datos. Eso es lo que ahora vive en la capa de visualización y está sujeto a sus mecanismos de renderizado (es decir, cuando se llama a los controladores del ciclo de vida). Cada aplicación, en un momento u otro, termina con los datos que no se obtienen cuando deberían o se obtienen en respuesta a accesorios no relacionados recibidos/cambiados. Y en todas las aplicaciones, debe escribir un código adicional para protegerse contra accesorios no relacionados que hayan cambiado. Es decir, accesorios que no afectan los parámetros utilizados para obtener datos.

En cuanto a React Native + Apollo, el problema es menor porque no tiene SSR y, por lo tanto, no tiene potencial para obtener datos secuenciales en el servidor. Sin embargo, ¿realmente necesita la obtención de datos a nivel de componente cuando las pantallas son tan pequeñas? Por lo general, sabe lo que necesita cada "escena" en términos de sus datos. No es como un tablero de panel web de escritorio con un millón de gráficos, varias tablas, etc. Ese caso de uso es quizás el lugar principal más grande donde el nivel de componente comienza a tener sentido. Pero incluso allí puede especificar todos sus requisitos para todos esos widgets en un solo lugar y obtenerlos a través de Apollo. Todavía no lo he pensado lo suficiente, pero la idea básica es adjuntar los requisitos de datos de GraphQL a una ruta y luego, en sus componentes, conectarse a los datos como cualquier otro dato de Redux. Entonces, más específicamente, la idea es eliminar tener tanto el connect react-redux como el equivalente de Apollo. Solo quiero uno, y queremos que sea más plano. El uso de Apollo requiere la desestructuración de datos anidados más profundos para obtener acceso a lo que realmente desea en sus componentes. La visión es una forma que se parece a Redux regular, más especificaciones de graphql a nivel de ruta.

Es un poco prematuro hablar de ello, ya que apenas lo he investigado, pero si el nivel de ruta es el enfoque correcto para su aplicación, Apollo + GraphQL no debería ser una excepción.

Por último, las pruebas son mucho más fáciles si todas las dependencias de datos y el trabajo asíncrono están separados de los componentes. La separación de preocupaciones es un importante impulsor de la productividad cuando puede probar sus componentes sin tener que preocuparse por la obtención de datos. Por supuesto, en sus pruebas debe llenar su tienda haciendo esas búsquedas asíncronas con anticipación, pero con ellas separadas de sus componentes, puede formalizar fácilmente dicho trabajo bajo algunas funciones auxiliares de setup que todas sus pruebas usan para pre- llene la tienda antes de probar cómo se procesan los componentes :)

@faceyspacey Estoy completamente de acuerdo. Pero debo admitir que no he probado Apollo ni GraphQL, definitivamente los revisaré y veré cómo se adaptan a mis casos de uso.

Estoy muy acostumbrado a escribir aplicaciones web utilizando el patrón MVC, donde básicamente extraigo todos los datos y las dependencias del controlador. Luego inyecto los datos a las vistas o la lógica de negocios. Como dijiste, eso hace que las pruebas sean mucho más fáciles ya que la vista no está acoplada con la obtención de datos.

He visto aplicaciones MVC en las que la parte de Vista está obteniendo datos y haciendo lógica comercial, y es terrible. No tengo idea de qué datos se obtendrán porque están ocultos en alguna vista/componente y las pruebas se vuelven más difíciles/imposibles.

Para mí, Redux-First Router es muy parecido a la parte del controlador en MVC, más las rutas. Lo cual tiene mucho sentido :)

Gracias.

Al final del día, nuestra nueva pila es esta:

M: Redux
V: reaccionar
C: Redux-First Router (que pronto será renombrado como "Rudy" )

Cuando salió React (y particularmente Redux), todos parecíamos querer deshacernos de la antigua sabiduría de MVC, como si de alguna manera significara que pudiéramos despejarnos por completo de los dolores en el desarrollo de aplicaciones anteriores. Pero creo que, en última instancia, no es más que simplemente no tener las herramientas construidas para admitirlo. Al final del día, todavía nos beneficiamos mucho de MVC, incluso en aplicaciones reactivas de cliente primero .

Puede funcionar un poco diferente en la nueva pila de cliente primero frente a los MVC tradicionales del lado del servidor, pero básicamente es la misma separación de 3 preocupaciones.

En cuanto a cuál es la diferencia en la interacción/comportamiento entre los 3, es básicamente que en el pasado el controlador obtenía modelos en una fase inicial y luego representaba las vistas con ellos; y ahora es el controlador (RFR) que muestra inmediatamente una vista (elegida a través de un "modelo de interfaz de usuario", es decir, estado Redux), y luego vuelve a mostrar la vista por segunda vez una vez que el controlador encuentra modelos de dominio (también almacenados en estado Redux).

Hola chicos, miren este enlace. aquí hay un ejemplo de representación del lado del servidor de datos https://github.com/bananaoomarang/isomorphic-redux

@harut55555 idk, puede que solo sea yo, pero esto parece mucho código para hacer una solicitud de obtención y una solicitud de publicación

¿Algún cambio? Lanzado 16 reaccionan y la mayoría de las soluciones no funcionan

Creo que el enfoque actual es usar redux con herramientas como redux observable o redux thunk

Mi caso de uso

<App>
    <Page>
        <AsyncModule hre="different.com/Button.react.js" /> downloaded from external url on server or client
    </Page>
</App>

El problema es que no sé antes de renderizar la App qué tipo de componentes tendré

¿Por qué no descargar datos/contenido en lugar de un componente por curiosidad?

¿Cuándo? Tengo páginas dinámicas y el componente puede o no ser

Parece que quieres renderizado condicional https://reactjs.org/docs/conditional-rendering.html

Creo que es un problema de enrutamiento, reaccionar solo debe mostrar la página, no administrar y solicitar datos de procesamiento

El uso de @graphql de Apollo hace que la carga de datos sea muy sencilla, y la API basada en componentes de React-Router v4 facilita el enrutamiento componible. Ambos son ortogonales al estado de la aplicación en Redux.

Para hacer un renderizado de transmisión de un solo paso que espera la obtención de datos, debería ser posible que render() devuelva una promesa (en el cliente que acaba de hacer como ahora, solo en el servidor devuelve una promesa).

Entonces @graphql puede simplemente devolver una promesa para el render() después de cargar los datos; todo lo demás es simplemente Reaccionar.

Hace unos meses di una charla en JSConf Islandia que describe las próximas funciones de renderizado asíncrono en React (ver la segunda parte): https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react -16.html. Se trata de la obtención de datos del lado del cliente.

Ahora, @acdlite dio una charla sobre cómo se pueden aplicar los mismos conceptos para habilitar la espera asincrónica de datos en el servidor en los componentes de React y la eliminación progresiva del marcado a medida que está listo: https://www.youtube.com/watch?v= z-6JC0_cons

¡Espero que disfrutes viendo estas charlas! Creo que en un año más o menos podríamos cerrar este problema y tener una estrategia oficial para esto.

Recuerdo anillo. Nosotros casándonos planeando un futuro juntos. Por favor, acaba con esto ahora y ven a verme. Tiempo trabajado siempre amor

Es posible usar ReactDOMServer.renderToStaticMarkup para recorrer el árbol, colocar un marcador donde llegue a las dependencias asincrónicas (por lo general, cargando datos desde una API) y continuar recorriendo el árbol a medida que se resuelven esas dependencias, hasta que se resuelva todo el árbol. y luego haga un ReactDOMServer.renderToString final para renderizar realmente a HTML, que será síncrono porque todas las dependencias ahora están resueltas.

Tomé este enfoque en react-baconjs : render-to-html.js . Se inspiró en un comentario que hizo @gaearon en otra edición recomendando tal enfoque.

@ steve-taylor sí, esto funciona. También es la solución alternativa que uso en react-frontload, que es una solución de propósito más general para esto que funcionará en cualquier aplicación React.

Como usted dice, esencialmente solo está fingiendo la representación asíncrona ejecutando la lógica de representación síncrona dos veces y esperando que se resuelvan todas las promesas de carga de datos que acierta en la primera representación antes de ejecutar la segunda.

Funciona lo suficientemente bien a pesar de que obviamente es un poco derrochador (la salida del primer renderizado es más que solo promesas, también es el HTML real que simplemente se desecha). Será increíble cuando la verdadera representación del servidor asincrónico llegue a React.

@davnicwil ¡ buen trabajo! He pensado en generalizar la solución SSR asíncrona específica. ¿React-frontload maneja una profundidad asíncrona ilimitada? Preguntando porque dijiste “dos veces”.

@steve-taylor saludos! Sí, si por profundidad te refieres a la profundidad del componente en el árbol, es ilimitado. Le permite declarar una función de carga de datos en el componente mismo (con un componente de orden superior) y luego, cuando se encuentra en el primer renderizado, se ejecuta y se recopila la promesa.

Lo que no funcionará es cuando hay más componentes secundarios que también cargan datos de forma asincrónica que luego se procesarían dinámicamente según el resultado, si eso tiene sentido. Simplemente significa que la aplicación tiene que estar estructurada para que no haya este tipo de anidamiento, que he encontrado en la práctica que no es realmente una gran limitación. De hecho, en muchos sentidos, es mejor en cuanto a UX porque, por supuesto, la lógica asíncrona anidada significa solicitudes en serie y tiempos de espera más largos, mientras que el aplanamiento significa solicitudes paralelas.

Dicho esto, el problema de anidamiento (de hecho, tal vez todo este problema de renderizado del servidor asíncrono) puede resolverse con las cosas de Suspense que llegan a React en un futuro cercano. 🤞

FYI hemos comenzado a trabajar en esto.

https://reactjs.org/blog/2018/11/27/react-16-roadmap.html#suspense -for-server-rendering

Impresionante @gaearon!

@davnicwil react-baconjs admite profundidad ilimitada.

@gaearon Me pregunto si esto haría posible el soporte para observables en la suscripción de creación, de modo que pueda convertir un JSX de activación observable en un componente de reacción.

Me encantaría tener esta característica por dos razones. Primero, en mi aplicación, el estado se almacena en un trabajador web. Entonces, mientras que recuperar bits de ese estado que requiere el componente es una operación asíncrona, toma como 5 ms y no tiene sentido que React procese nada mientras espera los datos. En segundo lugar, en este momento no hay una forma universal de convertir un observable en un componente: si su observable emite el primer valor de forma sincrónica, entonces lo que debe hacer es suscribirse para obtener ese valor, luego darse de baja y luego suscribirse nuevamente para escuchar los cambios (esa es la Reproducción ejemplo de asunto en documentos create-observable). Y si emite el primer valor de forma asincrónica, inicialmente se vuelve nulo y se escuchan los cambios.

@steve-taylor react-frontload ahora también admite una profundidad ilimitada de anidamiento de componentes, con el lanzamiento de 1.0.7 .

Espero que esta biblioteca sea útil para algunas personas que llegan a este hilo: si está buscando una solución de carga de datos asíncrona que funcione en el renderizado de cliente/servidor, con un esfuerzo de integración mínimo, debe consultarla.

Nos habíamos encontrado con este problema antes, ya que nuestra aplicación se creó con React Hooks, y luego creamos un paquete react-use-api para resolverlo, que es un enlace personalizado que obtiene datos API y es compatible con SSR. Espero que el paquete pueda ayudar a las personas que lo necesitan.

Sin embargo, todavía estamos ansiosos por esperar una solución oficial, tal como dice react-frontload , no hay una forma integrada de esperar a que ocurra la carga de datos asincrónicos una vez que comienza el procesamiento actualmente.

¿Fue útil esta página
0 / 5 - 0 calificaciones