React: Implementar la carga lateral de datos

Creado en 13 mar. 2015  ·  136Comentarios  ·  Fuente: facebook/react

Esta es una API de primera clase para la carga lateral de datos sin estado (aunque potencialmente memorizados) desde una tienda/red/recurso global, potencialmente usando accesorios/estado como entrada.

type RecordOfObservables = { [key:string]: Observable<mixed> };

class Foo {

  observe(): RecordOfObservables {
    return {
      myContent: xhr(this.props.url)
    };
  }

  render() {
    var myContent : ?string = this.data.myContent;
    return <div>{myContent}</div>;
  }

}

observe() se ejecuta después de componentWillMount/componentWillUpdate pero antes de renderizar.

Para cada clave/valor en el registro. Suscríbete al Observable en el valor.

subscription = observable.subscribe({ onNext: handleNext });

Permitimos que onNext se invoque sincrónicamente desde subscribe. Si es así, establecemos:

this.data[key] = nextValue;

De lo contrario, lo dejamos como indefinido para el renderizado inicial. (¿Tal vez lo configuramos en nulo?)

Luego, el procesamiento continúa como de costumbre.

Cada vez que se invoca onNext, programamos un nuevo "this.data[key]" que activa de manera efectiva una actualización forzada en este componente. Si este es el único cambio, entonces observe no se vuelve a ejecutar (componentWillUpdate -> render -> componentDidUpdate).

Si props/state cambió (es decir, una actualización de reciveProps o setState), entonces se vuelve a ejecutar observe() (durante la reconciliación).

En este punto, repasamos el nuevo registro y nos suscribimos a todos los nuevos Observables.

Después de eso, cancela la suscripción a los Observables anteriores.

subscription.dispose();

Este orden es importante ya que permite que el proveedor de datos haga un recuento de referencia de su caché. Es decir, puedo almacenar datos en caché mientras nadie los escuche. Si cancelo la suscripción de inmediato, el recuento de referencias se reducirá a cero antes de volver a suscribirme a los mismos datos.

Cuando se desmonta un componente, damos de baja automáticamente todas las suscripciones activas.

Si la nueva suscripción no llamó inmediatamente a Siguiente, seguiremos usando el valor anterior.

Entonces, si mi this.props.url de mi ejemplo cambia y me suscribo a una nueva URL, myContent seguirá mostrando el contenido de la URL anterior hasta que la siguiente URL se haya cargado por completo.

Tiene la misma semántica que la etiqueta <img /> . Hemos visto que, si bien esto puede ser confuso y generar inconsistencias, es un valor predeterminado bastante sensato, y es más fácil hacer que muestre una flecha giratoria que tener el valor predeterminado opuesto.

La mejor práctica podría ser enviar inmediatamente un valor "nulo" si no tiene los datos almacenados en caché. Otra alternativa es que un Observable proporcione tanto la URL (o ID) como el contenido en el resultado.

class Foo {

  observe() {
    return {
      user: loadUser(this.props.userID)
    };
  }

  render() {
    if (this.data.user.id !== this.props.userID) {
      // Ensure that we never show inconsistent userID / user.name combinations.
      return <Spinner />;
    }
    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;
  }

}

Deberíamos usar el contrato RxJS de Observable, ya que es de uso más común y permite la ejecución síncrona, pero una vez que la propuesta de @jhusain sea ​​de uso más común, cambiaremos a ese contrato.

var subscription = observable.subscribe({ onNext, onError, onCompleted });
subscription.dispose();

Podemos agregar más enlaces de ciclo de vida que respondan a estos eventos si es necesario.

Nota: este concepto permite que los datos laterales se comporten como "comportamientos", como accesorios. Esto significa que no tenemos que sobrecargar el estado de la noción para estas cosas. Permite optimizaciones como tirar los datos solo para volver a suscribirse más tarde. Es restaurable.

Component API Big Picture

Comentario más útil

Si alguien quiere jugar con este tipo de API, hice un polyfill realmente tonto por observe como un componente de orden superior:

import React, { Component } from 'react';

export default function polyfillObserve(ComposedComponent, observe) {
  const Enhancer = class extends Component {
    constructor(props, context) {
      super(props, context);

      this.subscriptions = {};
      this.state = { data: {} };

      this.resubscribe(props, context);
    }

    componentWillReceiveProps(props, context) {
      this.resubscribe(props, context);
    }

    componentWillUnmount() {
      this.unsubscribe();
    }

    resubscribe(props, context) {
      const newObservables = observe(props, context);
      const newSubscriptions = {};

      for (let key in newObservables) {
        newSubscriptions[key] = newObservables[key].subscribe({
          onNext: (value) => {
            this.state.data[key] = value;
            this.setState({ data: this.state.data });
          },
          onError: () => {},
          onCompleted: () => {}
        });
      }

      this.unsubscribe();
      this.subscriptions = newSubscriptions;
    }

    unsubscribe() {
      for (let key in this.subscriptions) {
        if (this.subscriptions.hasOwnProperty(key)) {
          this.subscriptions[key].dispose();
        }
      }

      this.subscriptions = {};
    }

    render() {
      return <ComposedComponent {...this.props} data={this.state.data} />;
    }
  };

  Enhancer.propTypes = ComposedComponent.propTypes;
  Enhancer.contextTypes = ComposedComponent.contextTypes;

  return Enhancer;
}

Uso:

// can't put this on component but this is good enough for playing
function observe(props, context) {
  return {
    yourStuff: observeYourStuff(props)
  };
}

class YourComponent extends Component {
  render() {
    // Note: this.props.data, not this.data
    return <div>{this.props.data.yourStuff}</div>;
  }
}

export default polyfillObserve(YourComponent, observe);

Todos 136 comentarios

undefined es probablemente el valor más seguro para asignar a data hasta que el observable proporcione su primer valor a través onNext . Por ejemplo, en Relay asignamos diferentes significados a null (los datos no existen) y undefined (aún no obtenidos), por lo que nuestro valor de datos predeterminado ideal sería undefined . La alternativa es proporcionar un nuevo método, por ejemplo, getInitialData , pero sospecho que esto es innecesario o excesivo.

Esto es bastante interesante, sin embargo, desde el punto de vista de la escritura estática, no estoy tan contento con el sistema clave/valor, su tipo es prácticamente imposible de expresar.
¿Por qué no hacer que observe devuelva un solo observable y establecer/combinar el valor resuelto en this.data ?

class Foo {

  observe() {
    return (
      loadUser(this.props.userID)
        .map(user => { user })
  }

  render() {
    if (this.data.user.id !== this.props.userID) {
      // Ensure that we never show inconsistent userID / user.name combinations.
      return <Spinner />;
    }
    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;
  }

}

Y para el caso de búsqueda múltiple algo como:

class Foo {

  observe() {
    return (
     combineLatest(
      loadUser(this.props.userID),
      loadSomethingElse(this.props.somethingElseId),
      (user, somethingElse) => ({ user, somethingElse})
     )
  }

  render() {
    ..
  }

}

Esto es quizás un poco más detallado, pero permite tener un buen tipo estático:

interface Comp<T> {
  observe(): Observable<T>;
  data: T;
}

Además, en lugar de volver a ejecutar observe cuando los accesorios/estado cambian, podemos tener acceso a 'accesorios' 'estado' como un observable:

class Foo {

  observe(propsStream) {
    return (
      propsStream
        .flatMap(({ userID }) => loadUser(userId))
        .map(user => { user })
    );
  }

  render() {
    if (this.data.user.id !== this.props.userID) {
      // Ensure that we never show inconsistent userID / user.name combinations.
      return <Spinner />;
    }
    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;
  }
}

La razón es porque no queremos requerir el uso de combinadores y la comprensión de RxJS para poder suscribirnos a (múltiples) Observables. Combinar dos Observables de esta manera es bastante confuso. De hecho, al menos para nuestras fuentes de datos, probablemente implementaremos la API de suscripción pero ni siquiera incluiremos los combinadores en el prototipo de los Observables. Eso no es un requisito, pero puede usar combinadores si lo necesita.

Sin embargo, para suscribirse a una tienda Flux simple, no debería necesitar hacerlo.

Creo que Flow probablemente podrá manejar este tipo estático usando restricciones, pero consultaré con esos muchachos para asegurarme. Creo que será suficiente escribir la propiedad de datos y luego se puede implicar el tipo de observación.

class Foo extends React.Component {
  data : { user : User, content : string };
  observe() /* implied as { user : Observable<User>, content : Observable<string> } */ {
  }
}

Este cambio no tiene que ver con Observables como una forma de describir el estado de la aplicación. Eso se puede implementar además de esto, como lo ha hecho antes. Esto no se trata explícitamente del estado de la aplicación, ya que este método es idempotente. El marco es libre de cancelar la suscripción y volver a suscribirse según sea necesario.

cc @ericvicenti

Al menos en el caso de mecanografiado, no habría forma de restringir el tipo de retorno de observe función del tipo de data , al menos hasta que algo como https://github.com/ Se implementa

Me encantaría usar esto para la próxima versión de React DnD, pero obviamente esto requiere esperar a React 0.14.
Me pregunto si puedo "polyfill" esto por el momento con un componente de orden superior que establece this.data en una instancia de ref . Sin embargo, podría ser demasiado loco.

¿Sería posible observar Promesas? ¡Entonces uno podría usar un árbol de Promesas para resolver los datos de todo el árbol de componentes antes del primer renderizado! Esto sería muy útil para React del lado del servidor.

¿Cuáles son los beneficios de hacer de esta una API de primera clase? Esencialmente, podría lograrse utilizando un "componente de orden superior".

¿Cuáles son los beneficios de hacer de esta una API de primera clase? Esencialmente, podría lograrse utilizando un "componente de orden superior".

Envolver en 5 HOC para obtener 5 suscripciones es un poco difícil de manejar y más difícil de entender para los principiantes. Comprender componentWillReceiveProps tampoco es trivial. Esto arregla ambos.

Yo, por mi parte, doy la bienvenida a nuestros nuevos señores supremos observables.

Me pregunto si esto puede ayudar a acercar https://github.com/chenglou/react-state-stream a la API vainilla de React.

¿No solo tomaría un HOC? En el ejemplo de su publicación de Medium , itera sobre stores y se suscribe a cada uno.

stores.forEach(store =>
  store.addChangeListener(this.handleStoresChanged)
);

@aaronshaf Depende del caso de uso, seguro. A veces son diferentes tipos de fuentes estatales, no solo "varias tiendas". Pero no puedo decir en nombre del equipo de React, escuchemos lo que dice @sebmarkbage .

Me encantaría algún tipo de polyfill para jugar con esto ahora. Todavía no entendí la idea por completo. ¿Cuál es el mecanismo involucrado en el tratamiento de futuras actualizaciones? Pasaré un poco más de tiempo entendiéndolo. Creo que debería ser factible con una simple mezcla.

( ¡@vjeux me dijo que debería intervenir! Así que aquí estoy).

No pretendo promocionar mi propio trabajo, pero creo que este gancho es muy similar al gancho getNexusBindings en React Nexus . Usted declara dependencias de datos a nivel de componente a través de un enlace de ciclo de vida (que puede depender de los accesorios).

La API se parece a:

class UserDetails {
  getNexusBindings(props) {
    return {
      // binding to data in the datacenter
      posts: [this.getNexus().remote, `users/${this.props.userId}/posts`],
      // binding to data in the local flux
      mySession: [this.getNexus().local, `session`],
    }
  }
}

El enlace se aplica/actualiza durante componentDidMount y componentWillReceiveProps . En el último caso, los enlaces siguientes se diferencian de los enlaces anteriores; los enlaces eliminados se dan de baja, los enlaces añadidos se suscriben. El mecanismo subyacente de obtención/actualización se describe en la implementación de Nexus Flux . Básicamente, con la misma API, puede suscribirse a datos locales (tiendas locales tradicionales) o datos remotos (obtener usando GET y recibir parches a través de Websockets/polyfill). En realidad, podría suscribirse a datos desde otra ventana (usando postWindow) o un WebWorker/ServiceWorker, pero todavía no he encontrado un caso de uso realmente útil para esto.

Para resumir, usted describe sincrónicamente las dependencias de datos a nivel de componente usando una abstracción Flux y los ganchos se aseguran de que sus dependencias se suscriban automáticamente, se inyecten en actualizaciones y se den de baja.

Pero también viene con una buena característica: se aprovechan exactamente las mismas funciones del ciclo de vida para realizar la captura previa de datos en el momento de la representación del lado del servidor. Básicamente, comenzando desde la raíz y recurrentemente desde allí, React Nexus obtiene previamente los enlaces, procesa el componente y continúa con los descendientes hasta que se procesan todos los componentes.

@aaronshaf @gaearon El beneficio de hacerlo de primera clase es:

1) No elimina el espacio de nombres de accesorios. Por ejemplo, el componente de orden superior no necesita reclamar un nombre como data de su objeto de accesorios que no se puede usar para nada más. El encadenamiento de múltiples componentes de orden superior sigue consumiendo más nombres y ahora debe encontrar una manera de mantener esos nombres únicos. ¿Qué sucede si está componiendo algo que ya podría estar compuesto y ahora tiene un conflicto de nombres?

Además, creo que la mejor práctica para los componentes de orden superior debería ser evitar cambiar el contrato del componente envuelto. Es decir, conceptualmente deberían ser los mismos apoyos de entrada que de salida. De lo contrario, es confuso usar y depurar cuando el consumidor proporciona un conjunto de accesorios completamente diferente al que recibe.

2) No tenemos que usar state para almacenar el último valor. El concepto data es similar a props en el sentido de que es puramente una memorización. Somos libres de tirarlo en cualquier momento si necesitamos recuperar la memoria. Por ejemplo, en un pergamino infinito podríamos limpiar automáticamente los subárboles invisibles.

@RickWong Sí, sería bastante trivial admitir Promises ya que son un subconjunto de Observables. Probablemente deberíamos hacer eso para no tener opiniones. Sin embargo, probablemente recomendaría no usarlos. Encuentro que son inferiores a los Observables por las siguientes razones:

A) No pueden ser cancelados automáticamente por el marco. Lo mejor que podemos hacer es ignorar una resolución tardía. Mientras tanto, la Promesa se aferra a recursos potencialmente costosos. Es fácil entrar en una situación agitada de suscribirse/cancelar/suscribirse/cancelar... de temporizadores de ejecución prolongada/solicitudes de red y si usa Promises, no se cancelarán en la raíz y, por lo tanto, solo tiene que esperar el recursos para completar o tiempo de espera. Esto puede ser perjudicial para el rendimiento en páginas de escritorio grandes (como facebook.com) o aplicaciones críticas de latencia en entornos con limitaciones de memoria (como react-native).

B) Te estás encerrando en obtener solo un valor único. Si esos datos cambian con el tiempo, no puede invalidar sus vistas y terminará en un estado inconsistente. No es reactivo. Sin embargo, para un solo renderizado del lado del servidor que podría estar bien, en el cliente lo ideal sería diseñarlo de manera que pueda transmitir nuevos datos a la interfaz de usuario y actualizarse automáticamente para evitar datos obsoletos.

Por lo tanto, creo que Observable es la API superior para compilar, ya que no lo bloquea para solucionar estos problemas si es necesario.

@elierotenberg ¡ Gracias por intervenir! Realmente parece muy similar. Mismo tipo de beneficios. ¿Ve alguna limitación con mi propuesta? Es decir, ¿falta algo, que tiene React Nexus, que no pudiste construir encima de esto? Sería bueno si no nos cerráramos de los casos de uso importantes. :)

Desde el punto de vista del procesamiento del servidor, es importante que podamos posponer el renderToString final hasta que el Observable/Promise se haya resuelto con datos que podrían obtenerse de forma asíncrona. De lo contrario, todavía estamos en la posición de tener que hacer toda la obtención de datos asincrónicos fuera de React sin saber qué componentes estarán en la página todavía.

Creo que react-nexus permite que ocurra una carga asíncrona dentro de un componente antes de continuar hacia abajo en el árbol de renderizado.

Sí, react-nexus separa explícitamente:
1) declaración vinculante como getNexusBindings (que es un método de ciclo de vida síncrono y sin efectos secundarios, similar a render; en realidad solía llamarse renderDependencies pero pensé que era confuso),
2) suscripción/actualización de enlace como applyNexusBindings (que es síncrono y difiere de los enlaces de nexo anteriores para determinar qué nuevos enlaces deben suscribirse y cuáles deben cancelarse)
3) precarga vinculante como prefetchNexusBindings (que es asíncrono y se resuelve cuando el valor "inicial" (lo que sea que esto signifique) está listo)

ReactNexus.prefetchApp(ReactElement) devuelve un Promise(String html, Object serializableData) . Este gancho imita la construcción del árbol React (usando instantiateReactComponent ) y construye/precarga/renderiza recursivamente los componentes. Cuando todo el árbol de componentes está 'listo', finalmente llama a React.renderToString , sabiendo que todos los datos están listos (errores de módulo). Una vez resuelto, el valor de esta Promesa se puede inyectar en la respuesta del servidor. En el cliente, el ciclo React.render() vida normal de

Si alguien quiere jugar con este tipo de API, hice un polyfill realmente tonto por observe como un componente de orden superior:

import React, { Component } from 'react';

export default function polyfillObserve(ComposedComponent, observe) {
  const Enhancer = class extends Component {
    constructor(props, context) {
      super(props, context);

      this.subscriptions = {};
      this.state = { data: {} };

      this.resubscribe(props, context);
    }

    componentWillReceiveProps(props, context) {
      this.resubscribe(props, context);
    }

    componentWillUnmount() {
      this.unsubscribe();
    }

    resubscribe(props, context) {
      const newObservables = observe(props, context);
      const newSubscriptions = {};

      for (let key in newObservables) {
        newSubscriptions[key] = newObservables[key].subscribe({
          onNext: (value) => {
            this.state.data[key] = value;
            this.setState({ data: this.state.data });
          },
          onError: () => {},
          onCompleted: () => {}
        });
      }

      this.unsubscribe();
      this.subscriptions = newSubscriptions;
    }

    unsubscribe() {
      for (let key in this.subscriptions) {
        if (this.subscriptions.hasOwnProperty(key)) {
          this.subscriptions[key].dispose();
        }
      }

      this.subscriptions = {};
    }

    render() {
      return <ComposedComponent {...this.props} data={this.state.data} />;
    }
  };

  Enhancer.propTypes = ComposedComponent.propTypes;
  Enhancer.contextTypes = ComposedComponent.contextTypes;

  return Enhancer;
}

Uso:

// can't put this on component but this is good enough for playing
function observe(props, context) {
  return {
    yourStuff: observeYourStuff(props)
  };
}

class YourComponent extends Component {
  render() {
    // Note: this.props.data, not this.data
    return <div>{this.props.data.yourStuff}</div>;
  }
}

export default polyfillObserve(YourComponent, observe);

¿Es Observable algo concreto y acordado aparte de las implementaciones de la biblioteca? ¿Cuál es el contrato, es lo suficientemente simple de implementar sin necesidad de usar tocino o Rxjs? Tan agradable como sería una API de primera clase para la carga lateral de datos, parece extraño que React agregue una API basada en una primitiva de especificación muy inicial/no especificada, dado el movimiento constante de React hacia js simples. ¿Algo como esto nos vincularía a una implementación específica de la tierra del usuario?

aparte, ¿por qué no Streams? No tengo ningún caballo en la carrera, pero sinceramente me pregunto; ya hay trabajo realizado en flujos web y, por supuesto, hay un nodo

@jquense Hay trabajo activo en una propuesta para agregar Observable a ECMAScript 7 (+), por lo que idealmente esto se convertiría en JS simple. https://github.com/jhusain/asyncgenerator (actualmente desactualizado).

No asumiríamos una dependencia de RxJS. La API es trivial de implementar sin usar RxJS. RxJS es el más cercano a la propuesta ECMAScript activa.

most.js también parece factible.

La API de Bacon.js parece difícil de consumir sin depender de Bacon debido al uso de los tipos Bacon.Event para separar valores.

Las API de Stream tienen un nivel demasiado alto y están muy alejadas de este caso de uso.

¿Hay algún tipo de opción de "esperar antes de renderizar"? Quiero decir que en el cliente no es necesario esperar todos los Observables antes de renderizar, pero en el servidor querrás esperar a que se resuelvan para que el render() de cada componente sea completa , no parcial.

[parent] await observe(). full render(). -> [foreach child] await observe(). full render().

En todas mis exploraciones, descubrí que este es el gancho de ciclo de vida más importante que falta en React del lado del servidor.

Siguiendo esta discusión, he tratado de resumir lo que hace React Nexus en la siguiente publicación:

Aplicaciones ismórficas bien hechas con React Nexus

Aquí está el diagrama de la rutina principal de captación previa:

React Nexus

No asumiríamos una dependencia de RxJS. La API es trivial de implementar sin usar RxJS. RxJS es el más cercano a la propuesta ECMAScript activa.

:+1: esta es la gran preocupación para mí, pensar en, digamos, promesas en las que implementar las tuyas es extremadamente difícil a menos que sepas lo que estás haciendo. Creo que, de lo contrario, terminas con un requisito implícito en una librería específica en el ecosistema. Tangencialmente... una de las cosas buenas del mundo de las promesas es el conjunto de pruebas A+, por lo que incluso entre las bibliotecas había al menos una garantía de una funcionalidad común de .then , que fue útil para la interoperabilidad de las promesas antes de que fueran estandarizado.

esta es la gran preocupación para mí, pensar en, por ejemplo, promesas en las que implementar las tuyas es extremadamente difícil a menos que sepas lo que estás haciendo. Creo que, de lo contrario, terminas con un requisito implícito en una librería específica en el ecosistema.

Totalmente de acuerdo. Afortunadamente, los observables tienen un contrato realmente simple y ni siquiera tienen métodos integrados como then por lo que, en cierto modo, son incluso más simples que las promesas.

Pueden volverse más complicados (y más lentos) si el comité insiste en que llamar a next programa una microtarea como Promises.

Eso molestaría a algunos, muchos patrones se basan en el hecho de que onNext es síncrono en RxJS:/

Creo que un patrón común de la tienda Flux podría ser mantener un Mapa de Observables por clave para que puedan reutilizarse. Luego límpielos cuando todos hayan cancelado su suscripción.

De esa forma puedes hacer cosas como: MyStore.get(this.props.someID) y siempre obtener el mismo Observable.

De esa manera, puede hacer cosas como: MyStore.get(this.props.someID) y siempre recuperar el mismo Observable.

¿Tendría sentido usar this.props.key (desaparecido, lo sé)? En la mayoría de los casos, ya pasa dicho identificador único con <... key={child.id} .. /> .

De esa manera, puede hacer cosas como: MyStore.get(this.props.someID) y siempre recuperar el mismo Observable.

Ese es el patrón que uso también para React Nexus. Store#observe devuelve un observador memorizado e inmutable; se limpia (incluido el mecanismo de limpieza específico del back-end relevante, como el envío de un mensaje real de "cancelar suscripción" o lo que sea) cuando todos los suscriptores se han ido por al menos un tic.

@sebmarkbage @gaearon ¿Cómo observaría el trabajo en el servidor en v0.14?
¿Sería capaz de esperar adecuadamente a que todos los observadores se resuelvan antes de renderizar en una cadena de forma similar a como lo hace react-nexus (pero integrado para reaccionar)?

En mi opinión, sería genial si los componentes esperaran el primer valor observado antes de estar "listos" para renderizarse en el servidor.

@gaearon : En mi opinión, sería genial si los componentes esperaran el primer valor observado antes de estar "listos" para renderizarse en el servidor.

Sí, :+1: para renderizado asíncrono. Mientras tanto react-async de @andreypopp es una alternativa, pero requiere fibers para "piratear" React. Sería genial si React pudiera soportar el renderizado asíncrono listo para usar.

La representación asíncrona es algo que nos gustaría admitir, pero no es parte de este problema. L

Probablemente no lo logrará en 0.14 desafortunadamente. Se necesitan muchos diseños diferentes para considerar y refactorizar.

Siéntase libre de crear y emitir una descripción de los cambios arquitectónicos en los elementos internos necesarios para que eso suceda.

Pensé lo mismo que @gaearon re: react-streaming-state . Dadas todas las aplicaciones potenciales además de la carga lateral, ¿podría haber un nombre mejor que data ? Por ejemplo, observed lo asociaría más claramente con el método.

No pretendo descarrilar con bikeshedding pero quería tirar esto por ahí.

No puedo esperar a los observables en React. esto debería hacer que React sea reactivo tal como lo entiendo

Estoy experimentando con una idea similar mientras reescribo react-async , vea README .

La diferencia notable es que introduzco una identidad de proceso/observable explícita para reconciliar los procesos, de forma similar a como lo hace React con key prop y componentes con estado.

Cuando id del proceso nombrado cambia, React Async detiene la instancia del proceso anterior e inicia una nueva.

La API se parece a:

import React from 'react';
import Async from 'react-async';

function defineFetchProcess(url) {
  return {
    id: url,
    start() {
      return fetch(url)
    }
  }
}

function MyComponentProcesses(props) {
  return {
    user: defineFetchProcess(`/api/user?user${props.userID}`)
  }
}

@Async(MyComponentProcesses)
class MyComponent extends React.Component {

  render() {
    let {user} = this.props
    ...
  }
}

La API del proceso ahora sigue la API de ES6 Promises sintácticamente y en cuanto al nombre, pero semánticamente no se espera que process.then(onNext, onError) se llame solo una vez por proceso en vivo. Está hecho para acomodar el caso de uso más popular (?) de obtener datos a través de promesas. Pero para ser honesto, ahora creo que necesito cambiarlo para evitar confusiones.

Tal vez me equivoque, pero creo que lo único que impide implementar la API propuesta (en este problema) en el espacio del usuario es la falta del enlace del ciclo de vida que se ejecuta justo antes de renderizar como componentWillUpdate pero con props nuevos y state ya instalado en la instancia.

Una cosa que aún no se ha discutido es el manejo de la devolución de llamada onError . Si un observable produce un error, esa información debería estar disponible para el componente de alguna manera. Debido a que React maneja la llamada real subscribe(callbacks) , necesitaríamos un método estandarizado para que se inyecte en ese objeto de devolución de llamada.

Para proporcionar la mayor flexibilidad a los desarrolladores de aplicaciones, veo dos enfoques. El primero es colocar los errores en un atributo de nivel superior, similar a this.data . Esto parece increíblemente pesado y consume aún más el espacio de nombres del componente.
El segundo permitiría a los desarrolladores definir su propia devolución de llamada onError como una función similar al ciclo de vida. Si quisiera tener un manejo de errores personalizado para mis observables, podría agregar algo como

onObserveError(key, error) {
  // do something with the error
  this.state.errors[key] = error;
  this.setState({ errors: this.state.errors });
}

Esto es similar a lo que hemos hecho en la próxima iteración de Parse+React. Creamos nuestro propio manejo de errores para producir la API que queríamos. Los errores se agregan a un mapa privado { nombre => error } y los componentes tienen un método público de nivel superior, queryErrors() , que devuelve un clon del mapa si no está vacío, y null de lo contrario (permitiendo un simple if (this.queryErrors()) .

Por supuesto, definir nuevos métodos reservados también es un asunto complicado; No voy a pretender que no lo es. Pero necesitamos una forma de hacer que los errores estén disponibles implícita o explícitamente para el componente al renderizar.

@andrewimm La idea es introducir eso en un sistema genérico de propagación de errores que hace que el error suba en la jerarquía hasta que sea manejado por un límite de error. https://github.com/facebook/react/issues/2928

Esto también debería manejar los errores en los métodos que arrojan y recuperan con gracia. Me gusta si el método render() arroja. Esto es mucho más trabajo y llevará algún tiempo implementarlo correctamente, pero la idea era unificar el manejo de errores de esta manera.

Yo diría que esto debería dejarse fuera de React propiamente dicho, y 1 o 2 puntos de integración clave deberían coordinarse con proyectos como react-async y react-nexus para que esto se pueda hacer limpiamente sobre React propiamente dicho....

Estoy de acuerdo, parece que tener una forma sugerida de hacer esto es mejor que
hornear esto en el propio marco

El martes 21 de abril de 2015 a las 23:38 Rodolfo Hansen [email protected]
escribió:

Yo diría que esto debería dejarse fuera de la reacción adecuada, y 1 o 2 teclas
los puntos de integración deben coordinarse con proyectos como react-async y
react-nexus para que esto se pueda hacer limpiamente encima de React propiamente dicho....


Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/facebook/react/issues/3398#issuecomment-95048028 .

Durante el fin de semana, construí otra implementación de Flux, llamada Flexy . En esto, mira el código de la tienda. Expone un método .getObservable que se ajusta a la API observable, aunque en realidad no tiene observables u otro marco reactivo en uso.

Entonces, diría que la API es bastante fácil de crear con Observables reales.

Dicho esto, no juzgues el código con dureza, se hizo durante un fin de semana para:

  • divertida
  • comprensión
  • usando js-csp
  • utilizando la API de observación

Nota al margen, un sistema como este y Flux en realidad hace que la representación del lado del servidor sea menos dolorosa. Usando un sistema similar a React-Nexus, podemos inicializar las tiendas y pasarlas a una aplicación React. Luego podemos monitorear las tiendas y el despachador y seguir renderizando hasta que no se activen más acciones (todos los datos requeridos ya están en las tiendas).

Yo diría que este es el punto de integración más pequeño para obtener la nueva semántica de las suscripciones de datos sin estado. ¿Qué otros puntos de integración sugeriría? Sin incluir la representación asíncrona, que es un problema mucho más complejo y merece su propio hilo, y este gancho podría usarse con la representación asíncrona independientemente.

Todo lo demás ya es posible de implementar sobre React por componente. Tenga en cuenta que no vamos a hacer inyecciones globales de complementos, ya que eso interrumpe la reutilización de componentes en todo el entorno, por lo que los marcos creados sobre React deben ser contextuales para los componentes individuales.

¿Qué ganchos nos faltan?

Oye,

Para ser honesto, si bien los nuevos ganchos habrían facilitado la implementación, sin duda podemos lograr la carga lateral de datos sin ellos, como hemos demostrado con react-async y react-nexus .

En todo caso, sería útil exponer y respaldar el mantenimiento del ciclo de vida de las instancias de los componentes de React fuera de una jerarquía de React montada. En react-nexus , uso el instanciateReactComponent interno y llamo a componentWillMount , componentWillUnmount , etc., yo mismo, y considero que este enfoque es frágil (¿y si instanciateReactComponents se basa en invariantes internas que cambian en la próxima versión de React?).

En cuanto al enfoque sin estado, me parece que la obtención de datos asíncronos _es_ con estado y, por lo tanto, almacenar el estado pendiente/completado/fallido en el estado de algunos componentes es relevante. Cuando usamos react-nexus en aplicaciones del mundo real, tenemos componentes de orden superior que realizan la obtención de datos e inyectan su estado de obtención a sus componentes secundarios como accesorios. El componente interno es, por lo tanto, "sin estado" (lo que es deseable), y el componente externo tiene "estado" (lo que también es deseable, por ejemplo, para mostrar una rueda de carga o un marcador de posición).

¿Qué otros puntos de integración sugeriría?

me parece que @ANDREYPOPP hizo la pregunta correcta. ¿No es lo único que necesitamos para implementar esto en el espacio del usuario el enlace del ciclo de vida antes de renderizar? eso parece ser el cambio de API mínimo más pequeño que se necesita, el resto es configurar y activar forceUpdate de manera adecuada a medida que cambia data función de cualquier flujo de entrada/emisor/observable. ¿A menos que me esté perdiendo algo más especial al respecto (totalmente posible)?

No me estoy metiendo en la discusión más amplia aquí.
Pero para responder a @sebmarkbage , creo que uno de los ganchos más importantes que me gustaría es algo que solo se necesita cuando no se usan observables reales.

  • Un gancho que puede proporcionar funciones que pueden manejar los valores empujados por el observable _antes_ de que se establezcan en datos. Con observables reales, esto sería solo un .map

Si la situación fuera un poco más abierta, creo que debería haber ganchos para reemplazar el comportamiento específico de Observable con ganchos personalizados. De esta manera, podríamos usar emisores de eventos o canales CSP en su lugar.

Me parece que los últimos comentarios dicen que el punto de extensión más pequeño es en realidad ganchos de ciclo de vida "conectar" y "desconectar", lo que facilita adjuntar datos asíncronos a un componente y administrar esas suscripciones.

Entonces, los observables podrían construirse de manera bastante trivial sobre eso (dentro o fuera del núcleo), pero ¿exponer esos puntos del ciclo de vida tiene un atractivo más amplio?

¿Es un resumen razonable?

Tampoco estoy convencido de que esto deba resolverse en React. Estoy mucho más inclinado a pensar que React debería proporcionar los enlaces necesarios para construir esta funcionalidad sobre React.

Pero por un momento, digamos que vamos con observe() . Algunos pensamientos:

Tenemos this.props , this.state , this.context y ahora this.data , todas como posibles fuentes de nuevos datos en render() . Esto me parece excesivo. ¿La idea es separar el estado de la aplicación del estado del componente? Esto podría aclarar y separar algunos problemas relacionados con el estado, pero creo que el costo de introducir una nueva entrada puede no ser mayor que las ganancias. Si queremos que this.state se centre únicamente en el estado del componente, ¿por qué no dejar que los campos en this.data sumen this.props o this.context ?

El nombre this.data es demasiado genérico. Las propiedades son datos, el estado son datos y cualquier variable local son datos. El nombre no agrega ningún significado y confunde los significados existentes. Preferiría mucho más this.observed o cualquier otro nombre que realmente signifique algo. Así que +1 al comentario de @matthewwithanm :

¿Podría haber un nombre mejor que data ? Por ejemplo, observed lo asociaría más claramente con el método.

Si permitimos que observe() ejecute en el servidor, necesitamos algún tipo de enlace que limpie cualquier pérdida de memoria que esto pueda causar, porque nunca se desmontará.

Si volvemos a llamar a observe() cada vez que props o state cambian (¿y context ?), entonces observe debe optimizarse para el rendimiento e idealmente no se puede encarecer accidentalmente. Se convierte en parte del camino caliente. Me gusta esto de React Nexus:

los enlaces siguientes se diferencian de los enlaces anteriores; los enlaces eliminados se dan de baja, los enlaces añadidos se suscriben.

He llegado a creer que el estado complica los componentes y he estado tratando de usarlo menos en mis propios componentes y mejorarlo . Es por eso que, además de las preocupaciones planteadas por @fisherwebdev (con las que estoy de acuerdo ahora), no estoy convencido de que dejar que observe dependa de state sea ​​una buena idea. El estado depende de los accesorios, lo observado depende del estado _y_ los accesorios... ¿No es demasiado complicado? Prefiero tener observe(props) .

También me doy cuenta de que observe no debería depender de state , principalmente porque no es necesario. Como señala @gaearon , es bastante fácil elevar el estado a un nivel superior, lo que se siente más limpio después de separar las preocupaciones. Cuando observe puede depender potencialmente de state , la lógica en torno al manejo de actualizaciones dentro del componente se vuelve significativamente más compleja; cuando solo depende de props , puede tener controladores sin horquillas en componentDidMount / componentWillReceiveProps . Menos código en la ruta crítica se traduce en un ciclo de renderizado más simple y también reduce la cantidad de actualizaciones no deseadas que reactivan las suscripciones.

+1 para observar (accesorios)

Cuanto menos nos ocupemos del estado, mejor en mi opinión.

Soy de la opinión de que, como biblioteca, React debería tratar de ser lo más flexible posible. Estoy de acuerdo en que no es una buena idea que observe dependa del estado. Sí, puede ser complicado. Sí, la mejor práctica debería ser no depender del estado.
Pero esa es una elección que los usuarios de React deberían poder hacer.
Recomendaría dejar la API actual sin cambios (aparte de los posibles ganchos para agregar más flexibilidad, para que funcione con más que solo observables, por ejemplo) y probar la documentación que explica que no se recomienda usar el estado en el método de observación.

Creo que todos quieren hacer lo correcto y que queremos que nos apunten en la dirección correcta. Tener el estado de observación y no aceptación hace que sea más fácil para el usuario que encontrar accidentalmente algo como "esto es un antipatrón" en los documentos.

EDITAR: lo siento, me acabo de dar cuenta de que estaba buscando flatMap . Me confundí porque estaba pensando que aplanaría la variedad de mensajes, pero está operando a un nivel más alto (los mensajes observables).

¿La API propuesta manejaría el caso en el que el resultado de un campo de datos depende del resultado de otro? Es decir, mapeas el primer resultado y devuelves un observable:

observe(props, context) {
  if (!props.params.threadID) {
    return {};
  }

  const observeThread = ThreadStore.observeGetByID(
    {id: props.params.threadID}
  );
  return {
    thread: observeThread,
    messages: observeThread.map(thread => {
      return MessageStore.observeGetByIDs({ids: thread.messageIDs});
    })
  };
}

Soy bastante nuevo en observables en general, por lo que puedo estar haciendo esto completamente mal. En la tierra de promesas, esto es extremadamente simple, ya que devolver una promesa de then hará que los then subsiguientes se basen en esa promesa.

No entiendo los comentarios de que no debería depender de this.state . El estado encapsulado seguramente hace que React sea mucho más complicado, pero eso es todo. Si no hubiera un estado encapsulado, solo necesitaríamos una biblioteca de modo inmediato memorizada. Si va con todo en "Tiendas", entonces sí, no necesita estado, pero eso no es lo que React prescribe que haga.

Tenemos varios patrones que requieren que cree envoltorios adicionales, pero para un uso normal no debería ser necesario. Aparte de las tiendas, sus datos observados siempre dependen del estado, aunque sea indirectamente. No creo que sea una mala práctica depender de ello en la observación. P.ej

observe() {
  return { items: Items.getPagedItems({ pageIndex: this.state.currentPage }) };
}

Incluso si observe no dependiera de state , seguiría dependiendo de props y context . Incluso si no dependiera de context , aún tendría una indirección que usa context para representar los accesorios de un componente que lo usa en observe .

observe tendría que ser reevaluado cada vez que se produce un pase de renderizado porque los accesorios podrían haber cambiado. Sin embargo, definitivamente diferenciaríamos los observables resultantes y no daríamos de baja/volveríamos a suscribirnos si se devuelve el mismo observable. Sin embargo, no podemos hacer diferencias en las propiedades individuales (aparte de shouldComponentUpdate), por lo que lo ideal es que implemente su propio caché utilizando Map como función de potencia. De esa manera, puede devolver el mismo observable a múltiples componentes en el árbol. Por ejemplo, múltiples componentes cargando al mismo usuario. Incluso si no lo hace, simplemente vuelve a crear el Observable y finalmente llega al caché inferior. Así es como funciona la reconciliación de React de todos modos. No es tan lento.

Este gancho observe no está diseñado para hacer que React sea completamente reactivo en el sentido de que el estado se captura en Observables. Actualmente, un objetivo de diseño clave es evitar atrapar el estado en cierres y combinadores y, en cambio, tener un árbol de estado limpio y separado que se pueda congelar y revivir, y potencialmente compartir entre los trabajadores.

Lo cual me lleva a mi último punto...

Ciertamente no _necesitamos_ agregar esto a la biblioteca central. La interfaz "pública" original era mountComponent/receiveComponent y se podía construir todo el sistema de componentes compuestos sobre ella. Sin embargo, no mucha gente dijo que es mucho más poderoso elevar la barra de abstracción, ya que ahora podemos construir otras cosas que están habilitadas por una barra de abstracción más alta. Tales como optimizaciones de todo el componente.

El propósito principal de React es crear un contrato entre componentes para que puedan coexistir diferentes abstracciones en el ecosistema. Una parte importante de esa función es elevar el nivel de abstracción de los conceptos comunes para que podamos habilitar nuevas funciones de componentes cruzados. Por ejemplo, guardar todo el estado de un subárbol, luego revivir el subárbol. O posiblemente incluir el desmontaje automático en el servidor o cambiar los aspectos de tiempo de la reconciliación en el servidor.

También es importante aportar unas pilas incluidas para que todo esto sea apetecible y uniforme.

Es importante darse cuenta de que la micromodularización (como agregar un nuevo enlace de ciclo de vida) no es estrictamente una victoria pura sobre la construcción en el marco. También significa que ya no es posible razonar sobre abstracciones de todo el sistema.

Desearía que algo como esto estuviera en los documentos como "filosofía / objetivos de diseño / no objetivos".

El propósito principal de React es crear un contrato entre componentes para que puedan coexistir diferentes abstracciones en el ecosistema. Una parte importante de esa función es elevar el nivel de abstracción de los conceptos comunes para que podamos habilitar nuevas funciones de componentes cruzados.

Me encanta esto. Estoy de acuerdo con @gaearon en que sería bueno si esto estuviera en algún tipo de documento.

Ciertamente, no necesitamos agregar esto a la biblioteca central... Sin embargo, no mucha gente dijo que es mucho más poderoso para elevar la barra de abstracción, ya que ahora podemos construir otras cosas que están habilitadas por una barra de abstracción más alta. Tales como optimizaciones de todo el componente.

Siento que la reticencia (al menos para mí) no es agregar otra API, sino agregar una que dependa de una construcción que no sea de lenguaje (todavía se está definiendo) para que funcione. Esto podría funcionar perfectamente, pero me preocupan los problemas a los que se enfrentan las bibliotecas Promise, en las que ninguna biblioteca de promesas (incluso las que se quejan de especificaciones) pueden confiar entre sí, lo que lleva a un ajuste innecesario y un trabajo defensivo para asegurarse de que se resuelvan correctamente, lo que limita las oportunidades de optimización. . O peor aún, te quedas atascado como jQuery con una implementación rota que nunca puede cambiar.

@jquense Totalmente de acuerdo. Quería agregar este gancho hace mucho tiempo. (Experimento original: https://github.com/reactjs/react-page/commit/082a049d2a13b14199a13394dfb1cb8362c0768a)

La vacilación de hace dos años era que todavía estaba demasiado lejos de la estandarización. Quería un protocolo estándar antes de que lo agregáramos también al núcleo.

Creo que estamos llegando a un punto en el que muchos marcos están de acuerdo en la necesidad de algo como Observables y la estandarización está llegando a un punto en el que se ha propuesto una API aceptable. Estoy seguro de que tendremos que terminar ajustándolo un poco, pero mientras la arquitectura de alto nivel funcione, debería ser intercambiable y eventualmente converger.

Creo que lo que sucedió con Promises es que la API y la historia de depuración faltaban gravemente en ciertas áreas que Observables no sufre. Es una historia más completa lista para usar en la que Promises tuvo que estandarizar una solución incompleta mínima.

La única diferencia de opinión con respecto a: Observables que he observado (no pude resistirme, lo siento) es el potencial de Zalgo. Si los Observables pueden o no generar valor sincrónicamente en respuesta a una suscripción. Algunas personas parecen estar en contra, pero el uso de Observables por parte de React dependerá de esto, según tengo entendido. ¿Puedes comentar sobre eso?

En general, no encuentro que Zalgo sea un problema con los Observables porque el consumidor siempre tiene el control y puede optar por siempre asíncrono con algo como observeOn .

Me alegro de que finalmente haya algún consenso sobre esto. Personalmente, prefiero los canales a los Observables, pero si se van a agregar Observables al idioma, estoy de acuerdo en que no hay necesidad de esperar más.

Dicho esto, asegurémonos de mantener la API lo suficientemente abierta para trabajar con no observables que se ajusten a la API básica.

Tampoco encuentro que Zalgo sea un problema. Sin embargo, con Observables puede usar un programador para garantizar la sincronización si lo desea, el programador predeterminado ahora es asíncrono, por lo que puede usarlo según sea necesario.

@sebmarkbage Creo que ha abordado la mayoría de mis inquietudes y ahora veo el beneficio de agregar esto al marco. Sin embargo, ¿puede comentar sobre this.data -- (1) podemos/deberíamos plegar esos campos en accesorios/contexto/estado o (2) podemos cambiarle el nombre?

Ligeramente bikeshed-y pero va a por ello de todos modos... el problema de Zalgo no es realmente uno sobre si importa en términos de expectativa de API, es uno de interoperabilidad observable y facilidad de implementación. No tener un acuerdo temprano sobre Zalgo que ha puesto al mundo de la biblioteca Promise en la molesta posición de tener que estar súper a la defensiva cuando se trata de Promises de otras bibliotecas. (mi punto anterior se repite a continuación)

... donde ninguna biblioteca de promesas (incluso las de quejas de especificaciones) puede confiar entre sí, lo que lleva a un ajuste innecesario y un trabajo defensivo para asegurarse de que se resuelvan correctamente

Debido a que las primeras promesas no cumplían todas con la resolución asíncrona, ahora estamos en una posición en la que, incluso si las bibliotecas están especificadas, no pueden asumir que thenables son dignos de confianza, eliminando posibles optimizaciones. Esto me parece particularmente relevante aquí, donde React no proporcionará una implementación de Observable para usar (¿quién querría eso de todos modos?), y lo más probable es que estemos a años de poder confiar únicamente en un navegador provisto por Observable, una biblioteca tan fácil la interoperabilidad es importante. Además del punto de @gaearon , si React depende de las llamadas de sincronización y está especificado para que siempre sea asíncrono, eso nos coloca en una posición similar a jquery de quedar atrapados con una implementación deshonesta.

Estoy completamente de acuerdo. Quería agregar este gancho hace mucho tiempo. La vacilación de hace dos años era que todavía estaba demasiado lejos de la estandarización. Quería un protocolo estándar antes de que lo agregáramos también al núcleo.

Me alegro de que también sea atendido y pensado, eso sin duda es reconfortante. :) y, en general, creo que la adopción temprana de las promesas ha valido la pena por las desventajas que estoy discutiendo aquí, así que no tomes mi preocupación como disgusto o desaprobación, estoy muy entusiasmado con la perspectiva de una API de primera clase para esto. y también veo cómo Observable es realmente una opción buena/más razonable aquí para ello.

"Deberíamos usar el contrato RxJS de Observable, ya que es de uso más común y permite la ejecución sincrónica, pero una vez que la propuesta de @jhusain sea ​​de uso más común, cambiaremos a ese contrato".

Solo para agregar un poco más de contexto. Existe una iniciativa Reactive Streams (http://www.reactive-streams.org/) para proporcionar un estándar para el procesamiento de flujo asíncrono con contrapresión sin bloqueo. Esto abarca los esfuerzos destinados a los entornos de tiempo de ejecución (JVM y JavaScript), así como a los protocolos de red.

Las principales implementaciones actuales son fe Akka Streams o RxJava. No sé si los RxJ ya cumplen con la misma interfaz ahora, la interfaz actual para suscriptores.es onSubscribe(Suscripción s), onNext(T t), onCompleted(), onError(Throwable t).

¿Puedes arrojar más luz sobre cuál es la propuesta de @jhusain ?

No sé si React debería cumplir estrictamente con esta iniciativa porque si lo necesito, probablemente pueda poner RxJs (suponiendo que cumpla) en el medio y adaptarme a la interfaz de React y dejar conceptos más avanzados como contrapresión a RxJs (aunque lo haría Prefiero no tener que adaptarme mucho).

¿Hay alguna posición u objetivo con respecto a esta iniciativa?

@vladap Creo que esta es la propuesta mencionada de @jhusain

He leído a través de @jhusain y no estoy seguro de la motivación para cambiar posiblemente a esta especificación en el futuro. ¿Hay alguna ventaja específica?

La especificación de flujos reactivos tiene mayor soporte y ya está en la versión 1.0 . Debido a que RxJava ya implementa esta especificación, supongo que RxJs seguirá (pero no lo he comprobado).

Este blog resume las interfaces con algunos ejemplos usando transmisiones Akka.

Puedo ver alguna ventaja en tener las mismas interfaces en el backend y el frontend, principalmente porque trabajo en ambos. Posiblemente podría ayudar a cooperar entre los grupos de backend y frontend pero, por otro lado, asumo que websocket o sse son los puntos de integración reales para la transmisión.

No puedo encontrar la lista de implementadores en www.reactive-streams.org en este momento, pero la última vez que la revisé fue:

Björn Antonsson – Typesafe Inc.
Gavin Bierman-Oracle Inc.
Jon Brisbin – Pivotal Software Inc.
George Campbell – Netflix, Inc.
Ben Christensen – Netflix, Inc.
Mathias Doenitz – spray.io
Marius Eriksen-Twitter Inc.
Tim Fox – Red Hat Inc.
Viktor Klang – Typesafe Inc.
Dr. Roland Kuhn – Typesafe Inc.
Doug Lea – SUNY Oswego
Stéphane Maldini – Pivotal Software Inc.
Norman Maurer – Red Hat Inc.
Erik Meijer – Dualidad Aplicada Inc.
Todd Montgomery – Kaazing Corp.
Patrik Nordwall – Typesafe Inc.
Johannes Rudolph – spray.io
Endre Varga – Typesafe Inc.

Tal vez voy demasiado lejos aquí, pero creo que el contexto más amplio puede ayudar en las decisiones futuras.

@vladap Por lo que entiendo y lo que veo en los problemas de github, @jhusain ya está trabajando con ellos, así que supongo que no tendremos tanto problema.
Desde la perspectiva de la interfaz, por lo que también pude comprender en diferentes problemas de github y otros documentos de especificaciones, el observador ciertamente respetará la interfaz del generador, por lo que algo así como:

{
  next(value),
  throw(e),
  return(v)
}

Simplemente implementando un observable muy básico con un solo método de 'suscripción' que respete esa interfaz debería ser seguro para reaccionar.

Parece solo un nombre diferente con la misma funcionalidad, en ese sentido está bien. Probablemente preferiría el mismo nombre que en la especificación, pero al final no me importa tanto en la medida en que estos métodos hagan lo mismo.

No puedo evaluar el equivalente faltante a onSubscribe(). En el blog que he mencionado el autor dice que es una clave para controlar la contrapresión. No sé, tiene otros casos de uso. A partir de esto, asumo que a React no le importa controlar la contrapresión o que existe otra estrategia para ello. Es algo complejo, por lo tanto, entiendo que no es una preocupación de React.

¿Entiendo correctamente que la estrategia es algo a lo largo de las líneas? O la aplicación es compleja y pueden surgir problemas de contrapresión, luego use algo intermedio para resolverlo, como RxJS, o conecta el componente React directamente a fe websocket y no No tendrás problemas de contrapresión porque la aplicación es simple y tiene actualizaciones lentas.

¿Dónde puedo encontrar interfaces observables propuestas para el futuro ECMAScript? Si ya hay alguno.

La propuesta actual se puede encontrar aquí:

https://github.com/jhusain/asyncgenerator

J H

El 7 de mayo de 2015, a las 2:32 a. m., vladap [email protected] escribió:

¿Dónde puedo encontrar interfaces observables propuestas para el futuro ECMAScript? Si ya hay alguno.


Responda a este correo electrónico directamente o véalo en GitHub.

La Propuesta de flujos reactivos (RSP) va más allá que la propuesta TC-39 porque presenta un Observable que maneja la contrapresión. El RSP Observable está optimizado para enviar transmisiones de manera eficiente a través de la red, respetando la contrapresión. Se basa en parte en el trabajo realizado en RxJava, que es una pieza de ingeniería muy impresionante (divulgación completa: fue diseñado por un colega de Netflix, Ben Christensen).

La razón principal de la decisión de estandarizar el tipo Observable más primitivo es la precaución. El Observable más primitivo es el dual del contrato Iterable ES2015, que nos brinda valiosas garantías de que el tipo es al menos tan flexible como un tipo ya estandarizado en ES2015. Además, hay una amplia variedad de casos de uso en JS para Observables que no requieren contrapresión. En el navegador, el DOM es el sumidero más común para transmisiones push y actúa efectivamente como un búfer. Dado que el tipo de RSP es más complejo, nuestro enfoque es estandarizar primero el tipo más primitivo y luego dejar espacio para implementar el tipo más avanzado más adelante. Idealmente, esperaríamos hasta que se validara en la tierra del usuario.

FYI RxJS actualmente no tiene planes para implementar RSP Observable.

J H

El 7 de mayo de 2015, a las 2:30 a. m., vladap [email protected] escribió:

Parece solo un nombre diferente con la misma funcionalidad, en ese sentido está bien. Probablemente preferiría el mismo nombre que en la especificación, pero al final no me importa tanto en la medida en que estos métodos hagan lo mismo.

No puedo evaluar el equivalente faltante a onSubscribe(). En el blog que he mencionado el autor dice que es una clave para controlar la contrapresión. No sé, tiene otros casos de uso. A partir de esto, asumo que a React no le importa controlar la contrapresión o que existe otra estrategia para ello. Es algo complejo, por lo tanto, entiendo que no es una preocupación de React.

¿Entiendo correctamente que la estrategia es algo a lo largo de las líneas? O la aplicación es compleja y pueden surgir problemas de contrapresión, luego use algo intermedio para resolverlo, como RxJS, o conecta el componente React directamente a fe websocket y no No tendrás problemas de contrapresión porque la aplicación es simple y tiene actualizaciones lentas.


Responda a este correo electrónico directamente o véalo en GitHub.

Muchas gracias por los valiosos detalles. Tiene mucho sentido.

@gaearon Te copié. Quería usar ParseReact con las clases de ES6, así que necesitaba volver a implementar la API de observación de mixin como un componente de orden superior.

https://gist.github.com/amccloud/d60aa92797b932f72649 (uso a continuación)

  • Permito que observe se defina en el componente o se pase al decorador. (Podría fusionar los dos)
  • Fusiono observables como accesorios (no this.data, o this.props.data)

@aaronshaf @gaearon El beneficio de hacerlo de primera clase es:

1) No elimina el espacio de nombres de accesorios. Por ejemplo, el componente de orden superior no necesita reclamar un nombre como datos de su objeto de utilería que no se puede usar para nada más. El encadenamiento de múltiples componentes de orden superior sigue consumiendo más nombres y ahora debe encontrar una manera de mantener esos nombres únicos. ¿Qué sucede si está componiendo algo que ya podría estar compuesto y ahora tiene un conflicto de nombres?

Además, creo que la mejor práctica para los componentes de orden superior debería ser evitar cambiar el contrato del componente envuelto. Es decir, conceptualmente deberían ser los mismos apoyos de entrada que de salida. De lo contrario, es confuso usar y depurar cuando el consumidor proporciona un conjunto de accesorios completamente diferente al que recibe.

En lugar de un HOC, puede ser un componente regular:

import Observe from 'react/addons/Observe';

class Foo {
  render() {
    return (
      <Observe
        render={this.renderData}
        resources={{
          myContent: xhr(this.props.url)
        }} />
    );
  }

  renderData({ myContent }) {
    if (myContent === null) return <div>Loading...</div>;
    return <div>{myContent}</div>;
  }
}

Debido a que usted controla la función que se pasa como render prop, no hay forma de que los nombres colisionen. Por otro lado, no contamina el estado del propietario.

¿Que me estoy perdiendo aqui?

Esto podría incluso ser menos detallado si el componente Observe simplemente tomara Observables de sus accesorios:

render() {
  return (
    <Observe myContent={Observable.fetch(this.props.url)}
             render={this.renderData} />
  );
}

renderData({ myContent }) {
  if (myContent === null) return <div>Loading...</div>;
  return <div>{myContent}</div>;
}

Lo que también es bueno de esto es que evitará nuevas suscripciones si shouldComponentUpdate devolvió falso porque no entraremos en render en absoluto.

Finalmente, se podría escribir un decorador para render que lo envuelva en un componente Observe :

@observe(function (props, state, context) {
  myContent: Observable.fetch(props.url)
})
render({ myContent }) {
  if (myContent === null) return <div>Loading...</div>;
  return <div>{myContent}</div>;
}

Preferiría mantener render como una función de representación pura en lugar de inyectarle lógica de obtención de datos.
La propuesta inicial me parece buena en mi opinión. Está muy cerca de cómo funciona el estado con rx-react y permitirá separar la administración del estado de la lógica de obtención de datos, lo que parece muy coherente.

Lo único que me molesta es el uso del mapa de observables en lugar de un observable, porque no le da al usuario la posibilidad de elegir cómo se componen los observables, pero este es un problema menor.

Realmente no está inyectando la lógica de obtención de datos, solo ahorra algo de escritura. Desazucarará a la versión anterior, que solo representa el componente <Observe /> . No es inusual representar componentes con estado, por lo que no creo que haga que render sea ​​menos puro de lo que es ahora.

Traté de combinar lo mejor del #3858 y esta propuesta.

En cualquier enfoque HOC, el beneficio es la claridad, pero @sebmarkbage describe las desventajas: los nombres de los accesorios pueden entrar en conflicto en algún momento.

En la propuesta actual, el beneficio es la claridad, pero el lado negativo es el ciclo de vida más complicado y la superficie API del componente principal más grande.

En #3858, el beneficio es colocar las dependencias de "renderizado memorizado" con el renderizado mismo (no se usan en ningún otro lugar, por lo que tiene sentido), pero me preocupa el "parece sincronizado pero es asíncrono" , y no entiendo cómo puede trabajar con modelos inmutables si se basa tanto en this . También me molesta el hecho de que React era fácil de razonar porque no hay nada fácil en razonar sobre el seguimiento manual de los cambios y acoplar las fuentes de datos a React (o envolverlas para que funcionen con React). Sin embargo, entiendo que existe una presión para implementar algo que funcione y reduzca el modelo.

En mi propuesta mantengo la colocación y la explicitación, pero:

  • <Observe /> (o el decorador observe() que envuelve el renderizado con <Observe /> ) es solo un complemento, no estoy proponiendo _ningún_ cambio en el núcleo de React.
  • Todos pueden implementar su propia lógica de observación para sus casos de uso. Puede tener su propio <Observe /> que use solo un observable, si eso es lo que prefiere. Puede o no usar el decorador.
  • El ciclo de vida del componente sigue siendo el mismo.
  • No hay conflictos de prop porque los datos se pasan como un parámetro.
  • Estamos dogfooding resolviendo los problemas con las herramientas que tenemos.
  • Para reducir el modelo, utilizamos herramientas de reducción de modelos (decoradores) en lugar de introducir nuevos conceptos básicos.

Gran discusión y trabajo por aquí, mucho respeto. :)

Estoy de acuerdo en que esto no merece ser incluido en el núcleo. Tal vez un complemento le dé a esta propuesta suficiente tracción para que la gente pueda intentar converger y estandarizarla antes de comprometerse más plenamente. Dicho esto, encuentro esta propuesta mejor que https://github.com/facebook/react/issues/3858 y https://github.com/facebook/react/pull/3920 por su minimalismo.

Esto es algo que he estado usando en proyectos paralelos (así que granos de sal): es similar al increíble trabajo de @elierotenberg pero no toma el control del ciclo de vida, ya que esta aplicación no está 100% en React y tiene que interoperar.

Prepárese para CoffeeScript y mixins, o siga entrecerrando los ojos hasta que se vea como ES6 si lo prefiere. :)

_ = require 'lodash'

module.exports = DeclareNeedsMixin = 
  componentDidMount: ->
    <strong i="12">@needsConsumerId</strong> = _.uniqueId @constructor.displayName
    <strong i="13">@sinkNeeds</strong> <strong i="14">@props</strong>, <strong i="15">@state</strong>

  componentWillUpdate: (nextProps, nextState) ->
    <strong i="16">@sinkNeeds</strong> nextProps, nextState

  componentWillUnmount: ->
    @props.flux.declareNeeds <strong i="17">@needsConsumerId</strong>, []

  sinkNeeds: (props, state) ->
    if not @declareNeeds?
      return console.warn 'Missing method required for DeclareNeedsMixin: `declareNeeds`', @

    needs = <strong i="18">@declareNeeds</strong> props, state
    props.flux.declareNeeds <strong i="19">@needsConsumerId</strong>, needs

  # Intended to be overridden by the host class.
  # Returns a set of facts, stored as an array.
  # Yes, immutable data is awesome, that's not the point here though. :)
  # Facts are serializable data, just values.
  # declareNeeds: (props, state) ->
  #   []

Y usado así:

module.exports = EmailThreads = React.createClass
  displayName: 'EmailThreads'
  mixins: [DeclareNeedsMixin]

  propTypes:
    flux: PropTypes.flux.isRequired

  declareNeeds: (props, state) ->
    [Needs.GmailData.myThreads({ messages: 20 })]

  ...

Entonces declareNeeds es una función unidireccional de accesorios y estado para una descripción de lo que necesita este componente. En la implementación real aquí, el extremo receptor de @props.flux.declareNeeds , que se configura en un componente de nivel superior, absorbe estas necesidades en un objeto ProcessSink . Se procesa por lotes como le gusta, desduplica needs en componentes que comparten el mismo flux y luego realiza efectos secundarios para satisfacer esas necesidades (como conectarse a un socket o realizar solicitudes HTTP). Utiliza el conteo de referencias para limpiar elementos con estado, como conexiones de socket, cuando ya no hay componentes que los necesiten.

Los datos fluyen desde bits con estado como eventos de socket y solicitudes al despachador (y luego a las tiendas o donde sea) y de vuelta a los componentes para satisfacer sus necesidades. Esto no es síncrono y, por lo tanto, todos los componentes manejan la representación cuando los datos aún no están disponibles.

Estoy compartiendo esto aquí solo como otra voz que dice que explorar este tipo de soluciones es posible en el espacio del usuario, y que la API actual está sirviendo muy bien para ese tipo de experimentación. En términos de un paso mínimo que podría tomar el núcleo para admitir la interoperabilidad entre diferentes enfoques, creo que @elierotenberg lo logró:

En todo caso, sería útil exponer y respaldar el mantenimiento del ciclo de vida de las instancias de los componentes de React fuera de una jerarquía de React montada.

En cuanto al enfoque sin estado, me parece que la obtención de datos asíncronos tiene estado y, por lo tanto, es relevante almacenar el estado pendiente/completado/fallido en el estado de algunos componentes.

@elierotenberg y @andrewimm hacen un excelente comentario sobre el manejo de errores de primera clase. @sebmarkbage Creo que su instinto hacia el punto mínimo de interoperabilidad es correcto, pero no estoy seguro de cómo la adición de semántica para que los errores aparezcan en el árbol de componentes se ajuste a ese requisito. Debe haber una historia igualmente simple aquí sobre cómo acceder a los valores de onError y onCompleted , incluso si es solo que las ranuras en this.observed contienen el último valor de next/error/completed como { next: "foo" } . Sin apoyar el contrato observable como una característica de primera clase, soy un poco escéptico acerca de esta propuesta.

Y dado que esto es Internet, y una de las primeras veces que participo aquí: el feed de problemas de React es una de las mejores lecturas y una fuente completa de excelentes trabajos e ideas. :+1:

me huele a ataduras.

No estoy seguro de cómo se relaciona esto con la propuesta/implementación actual, pero descubrí que habilitar la composición y la manipulación de orden superior es en realidad una característica clave del seguimiento de la dependencia de datos, especialmente si usa una fuente de datos reactiva (flujo, flujo por cable, o cualquier otra cosa que no le proporcione actualizaciones).

Tome el siguiente ejemplo con react-nexus@^3.4.0 :

// the result from this query...
@component({
  users: ['remote://users', {}]
})
// is injected here...
@component(({ users }) =>
  users.mapEntries(([userId, user]) =>
    [`user:${userId}`, [`remote://users/${userId}/profile`, {}]]
  ).toObject()
))
class Users extends React.Component {
  // ... this component will receive all the users,
  // and their updates.
}

Considerándolo todo, me pregunto si debería haber un enlace de datos en la API del componente. Me parece que los decoradores que devuelven componentes de orden superior proporcionan una forma muy agradable de expresar el enlace de datos sin contaminar el espacio de nombres de los métodos del componente.

Sin embargo, como señaló @sebmarkbage , existe el riesgo de contaminar el espacio de nombres de accesorios. Por ahora, estoy usando un decorador de transformadores de accesorios ( react-transform-props ) para limpiar/cambiar el nombre de los accesorios antes de pasarlos al componente interno, pero reconozco que podría volverse problemático en el futuro si los componentes de orden superior se vuelven más común y aumenta el riesgo de que se produzcan discrepancias entre nombres.
¿Podría esto resolverse usando símbolos que son claves de propiedad? ¿La verificación de propTypes admite Symbol accesorios con llave? ¿JSX admitirá claves de accesorios en línea calculadas (aunque siempre se pueden usar propiedades calculadas + distribución de objetos)?

Lo siento si esto se sale un poco del tema, pero me parece que todavía tenemos que encontrar la abstracción/API adecuada para expresar las dependencias de datos a nivel de componente.

mis 2¢

Me he divertido mucho con el patrón 'niños como función' para valores que cambian con el tiempo. ¿Creo que a @elierotenberg se le ocurrió por primera vez? Mi uso actual es modelar resortes (a través del rebote) en resortes de reacción. Ejemplo -

<Springs to={{x: 20, y: 30}} tension={30}>
  {val => <div style={{left: val.x, top: val.y}}>moving pictures</div>}
</Springs>

Sin colisión de accesorios y sin uso del estado del propietario. También puedo anidar múltiples resortes y reaccionar maneja todas las partes duras. Estos 'observables' (¡ja!) también podrían aceptar onError , onComplete y otros accesorios (¿consultas de graphql?).

Un intento aproximado de esbozar esto para 'flujo'

<Store 
  initial={0}
  reduce={(state, action) => action.type === 'click'? state+1 : state} 
  action={{/* assume this comes as a prop from a 'Dispatcher' up somewhere */}}> 
    {state => <div onClick={() => dispatch({type: 'click'})}> clicked {state} times</div>}
</Store>

Usamos este patrón, lo que llamamos _render callbacks_, en Asana, pero finalmente nos estamos alejando de él.

ventajas

  • Sin posibilidad de colisión de puntales.
  • Fácil de implementar tanto como autor de StoreComponent como usuario de StoreComponent

Contras

  • shouldComponentUpdate es extremadamente difícil de implementar ya que la devolución de llamada de procesamiento puede cerrarse sobre el estado. Imagina que tuviéramos un árbol de componentes de A -> Store -> B . A tiene un contador en su estado al que se accede durante la devolución de llamada de representación como accesorios para B . Si A actualiza debido al contador y Store no se actualiza, B tendrá una versión obsoleta del contador. Como resultado, nos vimos obligados a actualizar siempre la Tienda.
  • Probar componentes de forma aislada se volvió muy difícil. Inevitablemente, los desarrolladores pusieron una lógica compleja en las devoluciones de llamada de renderizado para qué componente renderizar y querían probar la lógica. La forma natural de hacer esto era renderizar todo el árbol y probar _a través_ del componente de la tienda. Esto hizo imposible usar el renderizador superficial.

Ahora nos estamos moviendo a un modelo donde el StoreComponent toma un ReactElement y cuando la tienda recibe nuevos datos, la tienda clona el ReactElement anulando un accesorio específico. Hay muchas maneras de hacer este patrón, como tomar un constructor de componentes y accesorios. Elegimos este enfoque porque era el más fácil de modelar en TypeScript.

Excelentes puntos, no puedo pensar en una forma de evitarlo sin romper el requisito de 'lado'.

@threepointone Suena exactamente como una de las propuestas de implementación para potenciar https://github.com/reactjs/react-future/pull/28

@ pspeter3 en su ejemplo, ¿hay algún inconveniente grave en hacer que Store siempre actualice / shouldComponentUpdate: ()=> true ? Estoy pensando que sus 'hijos' se habrían renderizado sin Store en la cadena de todos modos. ¡Gracias por tu tiempo!

@threepointone Eso es exactamente lo que hicimos con nuestros límites. No estaba claro cuál fue exactamente el impacto, pero hubo preocupaciones por parte de los miembros del equipo. Las preocupaciones combinadas con la dificultad de las pruebas obligaron a cambiar a usar React.cloneElement(this.props.child, {data: this.state.data})

@ pspeter3 el ángulo de prueba es definitivamente un problema. ¿Qué pasaría si el renderizador superficial reconociera las 'devoluciones de llamada de renderizado'? ¿Eso ayudaría?

Pd- 'renderizar devolución de llamada' :thumbs_up:

@sebmarkbage, la discusión actual sobre es-observable es que subscribe se garantizaría asíncrono, con un método [Symbol.observer] proporcionado para atajar y suscribirse sincrónicamente, aunque la validez de tener ambos apis sincronizados/asincrónicos es actualmente en debate.

Había intervenido en este ticket con el caso de uso mencionado anteriormente a favor de la suscripción síncrona, no sabía si podría agregar algo allí.

Creo que las ideas aquí son un patrón muy limpio para manejar el estado externo, aunque después de jugar un poco con él, me inclino a favor del enfoque HOC.

También @gaearon , simplifiqué tu HOC arriba, usando una estática - ComposedComponent.observe y usando this.state en lugar de this.state.data - https://gist.github.com/tgriesser/d5d80ade6f895c28e659

Se ve muy bien con los decoradores es7 propuestos:

<strong i="20">@observing</strong>
class Foo extends Component {
  static observe(props, context) {
    return {
      myContent: xhr(props.url)
    };
  }
  render() {
    var myContent = this.props.data.myContent;
    return <div>{myContent}</div>;
  }
}

El decorador de la clase podría incluso agregar un getter por data para que se acerque mucho a la API propuesta original (menos el estado local que afecta las suscripciones observables, lo cual estoy de acuerdo es algo bueno, mucho menos ruido alrededor suscribirse/darse de baja).

la falta de suscripción síncrona podría ser un gran problema.

Creo que sé cómo resolver "el problema del estado" y la "carga de datos lateral" (datos derivados) de manera consistente. Lo hace en un "React-way" sin estado. Encontré una manera de mantener la consistencia del estado en cualquier momento y se ajusta al patrón UI = React(state) . Es poco probable que esté fuera de control para hacerlo completamente a prueba de balas, agregar más ejemplos y hacer una buena presentación. https://github.com/AlexeyFrolov/slt . Por otro lado, está bien probado y lo estoy usando en mis proyectos de producción de forma iterativa. Las mentes inteligentes son bienvenidas a contribuir.

Hola a todos,

Es curioso que no me haya topado con este hilo antes, ya que en nuestra empresa nos estábamos encontrando con los mismos problemas abordados por esta propuesta hace medio año.
Empezamos a usar React para un proyecto a gran escala (piensa en un editor con la complejidad de Microsoft Visio, con datos muy cíclicos). Vanilla reacciona no se mantiene al día con nuestras demandas de rendimiento,
y flux fue un poco imposible para nosotros debido a su gran cantidad de repeticiones y la propensión a errores de todas las suscripciones. Así que nos dimos cuenta de que también necesitábamos estructuras de datos observables.

Como no pudimos encontrar nada disponible que estuviera listo para usar, construimos nuestra propia lib de observables, basada en los principios de los observables knockout (especialmente: suscripciones automáticas).
En realidad, esto encaja muy bien en el ciclo de vida actual de los componentes de React, y no necesitábamos trucos raros o incluso devoluciones de llamada de representación infantil (el ObserverMixin que se usa a continuación es de aproximadamente 10 loc).
Esto mejoró mucho nuestro DX y funcionó tan bien para nuestro equipo que decidimos publicarlo como código abierto . Mientras tanto, está bastante probado en batalla (proporciona un polyfill de matriz observable ES7, por ejemplo) y altamente optimizado.
Aquí hay un ejemplo de temporizador corto (también disponible como JSFiddle ), en mi humilde opinión, el DX no podría ser mucho mejor... :aliviado:

var store = {};
// add observable properties to the store
mobservable.props(store, {
    timer: 0
});

// of course, this could be put flux-style in dispatchable actions, but this is just to demo Model -> View
function resetTimer() {
    store.timer = 0;
}

setInterval(function() {
    store.timer += 1;
}, 1000);

var TimerView = React.createClass({
    // This component is actually an observer of all store properties that are accessed during the last rendering
    // so there is no need to declare any data use, nor is there (seemingly) any state in this component
    // the combination of mobservable.props and ObserverMixin does all the magic for us.
    // UI updates are nowhere forced, but all views (un)subscribe to their data automatically
    mixins: [mobservable.ObserverMixin],

    render: function() {
        return (<span>Seconds passed: {this.props.store.timer}</span>);
    }
});

var TimerApp = React.createClass({
    render: function() {
        var now = new Date(); // just to demonstrate that TimerView updates independently of TimerApp
        return (<div>
            <div>Started rendering at: {now.toString()}</div>
            <TimerView {...this.props} />
            <br/><button onClick={resetTimer}>Reset timer</button>
        </div>);
    }
});

// pass in the store to the component tree (you could also access it directly through global vars, whatever suits your style)
React.render(<TimerApp store={store} />, document.body);

Para obtener más detalles sobre este enfoque, consulte este blog . Por cierto, me aseguraré de que se agregue un decorador y/o contenedor a lib , para aquellos que usan clases ES6.

Lamentablemente, no vi este hilo antes de react-europe; de ​​lo contrario, habríamos tenido una buena oportunidad para hablar sobre React y los observables. ¡Pero muchas gracias por las charlas inspiradoras! :+1: Me encantaron especialmente las abstracciones de GraphQL y el trabajo mental detrás de Redux :)

@mweststrate Siento que la comunidad terminará con la necesidad de elegir entre las soluciones "Observables" y "Inmutable data" en última instancia. Tal vez necesitemos mezclar de alguna manera para tener las fortalezas de ambos enfoques en una solución (https://github.com/AlexeyFrolov/slt/issues/4). En mi solución, implementé el enfoque de "datos inmutables" centrado en la consistencia del estado en cualquier momento. También planeo admitir Observables y Generadores. Este es un ejemplo de las reglas que se utilizan para obtener datos "derivados" o "complementarios" (como el cuerpo de una página, activos, recomendaciones, comentarios, Me gusta) y mantener la coherencia del estado de la aplicación.

https://github.com/AlexeyFrolov/slt#rules -ejemplo

Es un ejemplo complejo del mundo real (mi código de producción) que muestra cómo manejar los redireccionamientos de API con un encabezado de ubicación.

import r from "superagent-bluebird-promise";
import router from "./router";

export default {
    "request": function (req)  {
        let route = router.match(req.url);
        let session = req.session;
        route.url = req.url;
        return this
            .set("route", route)
            .set("session", req.session);
    },
    "route": {
        deps: ["request"],
        set: function (route, request) {
            let {name, params: { id }} = route;
            if (name === "login") {
                return this;
            }
            let url = router.url({name, params: {id}});
            let method = request.method ? request.method.toLowerCase() : "get";
            let req = r[method]("http://example.com/api/" + url);
            if (~["post", "put"].indexOf(method)) {
                req.send(request.body);
            }
            return req.then((resp) => {
                let ctx = this.ctx;
                let path = url.substr(1).replace("/", ".");
                if (!resp.body) {
                    let location = resp.headers.location;
                    if (location) {
                        ctx.set("request", {
                            method: "GET",
                            url: location.replace('/api', '')
                        });
                    }
                } else {
                    ctx.set(path, resp.body);
                }
                return ctx.commit();
            });
        }
    }
}

En el resto, es el mismo enfoque que el suyo, excepto que no hay un vínculo directo con React (no es necesario en mi caso). Creo que necesitamos unir fuerzas de alguna manera para lograr el objetivo común.

Creo que es una mala idea porque habrá muchos casos extremos que serán difíciles de considerar.

De los comentarios anteriores, puedo enumerar los posibles alcances en los que el usuario final debe pensar cada vez que quiera crear un componente "con datos":

  • Representación del lado del servidor
  • Inicialización de .state y .data
  • .context , .props , .state y .observe enredo
  • Representación asíncrona

Creo que esta propuesta conducirá a componentes propensos a errores, inestables y difíciles de depurar.

Estoy de acuerdo con los ganchos de ciclo disconnect vida propuestos por @glenjamin connect y disconnect .

Disculpas si esto está fuera de tema (no puedo decirlo). Pero aquí hay un ejemplo de por qué creo que exponer la API para interactuar con el árbol de componentes sería una forma interesante de abordar este problema: https://github.com/kevinrobinson/redux/blob/feature/loggit-todomvc/examples/loggit -todomvc/loggit/renderers/precompute_react_renderer.js#L72

Este método es mi truco para caminar por el árbol, por lo que mi pregunta es cómo tener una API pública para hacer este tipo de cosas permitiría la innovación aquí en "carga lateral de datos", que creo que se reduce a "un renderizador que no t funciona de arriba hacia abajo": https://github.com/kevinrobinson/redux/blob/feature/loggit-todomvc/examples/loggit-todomvc/loggit/react_interpreter.js#L8

Pero aquí hay un ejemplo de por qué creo que exponer la API para interactuar con el árbol de componentes sería una forma interesante de abordar este problema.

Creo que @swannodette habló sobre esto en ReactConf. Me pregunto si Om Next lo hace.

Sí, sugirió el mismo tipo de cosas en la primera ReactConf. No he visto el último Om.next ni he oído hablar de EuroClojure, pero anteriormente Om usaba su propia estructura que los usuarios tenían que construir para hacer esto.

Este método es mi truco para caminar por el árbol, por lo que mi pregunta es cómo tener una API pública para hacer este tipo de cosas permitiría la innovación aquí en "carga lateral de datos", que creo que se reduce a "un renderizador que no t trabajo de arriba hacia abajo"

Creo que esto es más o menos equivalente a la representación superficial que se encuentra en las utilidades de prueba. Mencioné hacer de esta una API de primera clase como un par de la representación DOM y String para @sebmarkbage en ReactEurope. Los cambios de 0.14 parecen allanar el camino para esto.

La reacción inicial es que podría tener un nivel un poco bajo, pero de manera similar a las cosas web extensibles, creo que esto facilitaría la experimentación en el espacio del usuario.

Estoy de acuerdo, si tenemos una forma de renderizar para obtener el árbol, entonces podemos recorrerlo y encontrar todos los datos necesarios y transmitirlos.

Obtener acceso a todo el árbol DOM virtual es una característica increíblemente poderosa y útil a la que me encantaría tener acceso, incluso si se trata como un problema completamente separado.
Creo que tiene mucho sentido considerando el camino que está tomando React con 0.14 en adelante.

Dada la complejidad de los observables, sugiero humildemente a los buenos caballeros de este hilo que analicen la implementación de datos reactivos de Meteor: https://github.com/meteor/meteor/wiki/Tracker-Manual. Reconciliarlo con React requiere literalmente de 3 a 5 líneas de código en el método componentWillMount , algo así como:

  componentWillMount() {
    if (typeof this.getState === 'function') {
      Tracker.autorun(() => {
        // Assuming this.getState() calls some functions that return
        // reactive data sources
        this.setState(this.getState());
      });
    }
  }

No sé si Tracker (que se extrae fácilmente como una biblioteca separada) evita la necesidad de un soporte observable en React, pero parece que sí.

MOBservable sigue un patrón muy similar para actualizar un componente lateralmente una vez que se cambian algunos valores observables, por lo que parece que los métodos de ciclo de vida actuales + decoradores ofrecen suficiente flexibilidad para que las bibliotecas de terceros expresen este tipo de patrones y, en mi humilde opinión, un tercer concepto de fuente de datos. solo complicaría las cosas.

    componentWillMount: function() {
        var baseRender = this.render;
        this.render = function() {
            if (this._watchDisposer)
                this._watchDisposer();
            var[rendering, disposer] = mobservableStatic.watch(() => baseRender.call(this), () => {
                    this.forceUpdate();
            });
            this._watchDisposer = disposer;
            return rendering;
        }
    },

@Mitranim Estoy de acuerdo, es una muy buena lectura, ¡gracias por encontrarla! Es efectivamente lo que sugiere https://github.com/facebook/react/pull/3920 .

Estamos indecisos sobre qué propuesta es mejor. Después de haber jugado con ambos, estoy mayormente convencido de que la programación reactiva (como vinculaste; https://github.com/meteor/meteor/wiki/Tracker-Manual) es el camino a seguir, pero no hemos llegado a un consenso y todavía estamos tratando de descubrir qué tiene más sentido, por lo que se agradecen los comentarios.

@Mitranim @jimfb Soy un gran fanático de Meteor desde hace varios años. Tracker es realmente genial y muy fascinante. Creé una presentación que demuestra cómo funciona una versión muy simple del rastreador:

https://github.com/ccorcos/meteor-track/blob/master/client/main.js

E incluso creé una biblioteca de flujos observables con Tracker:

https://github.com/ccorcos/meteor-tracker-streams

Pero a medida que hice la transición completa al uso de React en lugar de Blaze, me di cuenta de que Tracker es demasiado complicado y, a veces, un simple método de publicación/suscripción/onChange es 100 veces más fácil.

Además, para todos los fanáticos de la programación funcional, Tracker viene con algunos efectos secundarios que tienen sentido el 99% del tiempo, pero que a veces te atrapan. Así es como se vería en un componente React

componentWillMount: ->
  <strong i="15">@c</strong> = Tracker.autorun =>
    @setState({loading: true})
    Meteor.subscribe 'users', => @setState({loading: false})
    Tracker.autorun =>
      @setState({users: Users.find({}, {sort:{name:-1}}).fetch()})
componentWillUnmount: ->
  @c.stop()

Mi punto sobre los efectos secundarios es que c.stop() detendrá la suscripción y también la ejecución automática interna.

@ccorcos Sí, en https://github.com/facebook/react/pull/3920 todos los efectos secundarios serían completamente internos a React. De hecho, React core podría implementarlos de una manera completamente inmutable/funcional que no realizaría ninguna mutación. Pero ese es un detalle de implementación, ya que los efectos secundarios no serían visibles externamente de todos modos.

Interesante... Entonces, ¿por qué no usar las devoluciones de llamada onChange? He encontrado que esos son los más compatibles. Ya sea que esté utilizando Meteor (Rastreador), RxJS, Highland.js, etc., siempre puede integrarse con ellos de manera trivial con una devolución de llamada de evento. Y lo mismo con un componente React de orden superior.

Lo que me gusta de Redux es que mantiene esta lógica fuera del marco y mantiene los componentes de React como funciones puras.

@ccorcos El principal problema es que cuando se destruye una instancia de componente, todas las "suscripciones" deben limpiarse. Los autores de componentes a menudo se olvidan de esta limpieza, lo que genera una fuga de memoria. Queremos que sea automático, lo que hace que sea más fácil de escribir (menos repetitivo) y menos propenso a errores (la limpieza es automática).

@jimfb Exactamente. La cancelación automática de la suscripción es una de las cosas realmente interesantes del enfoque de Tracker. Cada llamada a la fuente de datos reactiva restablece la suscripción, y una vez que el componente deja de solicitar datos, ¡no hay nada que limpiar!

Sí, pero en realidad no se cancela la suscripción "automáticamente". Tienes que llamar a c.stop() en el componente se desmontará. Sin embargo, puedes abstraer eso con una mezcla.

componentWillMount: ->
  <strong i="7">@autorun</strong> =>
    @setState({loading: true})
    Meteor.subscribe 'users', => @setState({loading: false})
    Tracker.autorun =>
      @setState({users: Users.find({}, {sort:{name:-1}}).fetch()})

Pero esto realmente no es diferente de cualquier otra API. Puede crear un método incorporado que cancela automáticamente la suscripción al desmontar, etc. Mire, soy un gran fanático de Meteor. Así que no quiero disuadirte de esto. Es solo que a veces, encuentro que es realmente difícil explicar estas cosas a alguien que no está familiarizado con ellas. Mientras tanto, usar oyentes simples/emisores de eventos es mucho más simple de entender e implementar, y tienden a ser muy compatibles con cualquier sistema de reactividad que quieras usar...

@ccorcos Podríamos estar hablando de diferentes mecanismos. La última vez que lo comprobé, Tracker no tenía suscripciones permanentes; cada función que establece una dependencia en una fuente de datos reactiva (al acceder a ella) se vuelve a ejecutar _una vez_ cuando cambia, y ese es el final de la suscripción. Si vuelve a acceder a la fuente de datos, se restablece la "suscripción" para un cambio más. Y así.

@Mitranim es correcto, la semántica de # 3920 es más "automática" que Meteor (la cancelación de suscripción realmente es automática), y mucho más simple ya que literalmente no hay área de superficie de API en el caso de uso común.

@ccorcos @Mitranim Para obtener una biblioteca inspirada en Tracker / Vue.js lista para usar, puede probar Mobservable , observa todos los datos a los que se accede durante _render_ y elimina todas las suscripciones al desmontar (hasta entonces, las suscripciones se mantienen activas). Lo aplicamos con éxito a proyectos bastante grandes hasta ahora en Mendix. Es bastante discreto para sus datos y decora los objetos existentes en lugar de proporcionar sus propios objetos de modelo.

La última vez que lo comprobé, Tracker no tenía suscripciones permanentes

Las suscripciones @Mitranim pueden ser permanentes. Mira esto.

sub = Meteor.subscribe('chatrooms')
# this subscription lasts until...
sub.stop()

Ahora hay algunas cosas interesantes con Tracker. Mira esto.

comp = Tracker.autorun ->
  Meteor.subscribe('chatrooms')
# this subscription lasts until...
comp.stop()

Sin embargo, el último ejemplo no es terriblemente útil. Hasta que hagamos algo como esto.

roomId = new ReactiveVar(1)
comp = Tracker.autorun ->
  Meteor.subscribe('messages', roomId.get())
# when I change the roomId, the autorun will re-run
roomId.set(2)
# the subscription to room 1 was stopped and now the subscription to room 2 has started
# the subscription is stopped when I call stop...
comp.stop()

cada función que establece una dependencia en una fuente de datos reactiva (al acceder a ella) se vuelve a ejecutar una vez cuando cambia, y ese es el final de la suscripción.

La suscripción dura hasta que se vuelve a ejecutar la ejecución automática (se cambia una dependencia reactiva) o hasta que se detiene el cómputo en el que vive. Ambos llaman al gancho computation.onInvalidate.

Aquí hay una versión súper artificial que logró exactamente lo mismo. Esperamos que le ayude a comprender cómo funciona Tracker. Y tal vez también vea lo complicado que puede llegar a ser.

comp = Tracker.autorun ->
  Meteor.subscribe('chatrooms')

# is the same as

comp = Tracker.autorun (c) ->
  sub = null
  Tracker.nonreactive ->
    # dont let comp.stop() stop the subscription using Tracker.nonreactive
    sub = Meteor.subscribe('chatrooms')
  c.onInvalidate ->
    # stop the subscription when the computation is invalidated (re-run)
    sub.stop()
# invalidate and stop the computation
comp.stop()

@ccorcos Veo la fuente de confusión ahora; era mi vocabulario. Cuando hablo de suscripciones, no me refiero a _suscripciones de Meteor_. Determinan qué datos se envían desde el servidor al cliente, pero no son relevantes para las actualizaciones de la capa de vista, que es el tema de esta discusión. Al decir _suscripción_, estaba dibujando un paralelo entre los detectores de eventos tradicionales y la capacidad de Tracker para volver a ejecutar una función que depende de una fuente de datos reactiva. En el caso de los componentes de React, ese sería un método de componente que obtiene datos y luego llama a setState o forceUpdate .

@mweststrate Gracias por el ejemplo, parece interesante.

Ah, sí. Así que tienen una forma inteligente de hacerlo.

componentWillMount: function() {
  this.comp = Tracker.autorun(() => {
    let sub = Meteor.subscribe('messages')
    return {
      loading: !sub.ready(),
      messages: Messages.find().fetch()
    }
  })
componentWillUnmount: function() {
  this.comp.stop()
}

La suscripción simplemente se volverá a suscribir cada vez sin problemas. sub.ready y Messages.find.fetch son ambos "reactivos" y activarán la ejecución automática para volver a ejecutarse cada vez que cambien. Lo bueno de Tracker es cuando comienzas a ocultar las ejecuciones automáticas y solo tienes en la documentación que cierta función está dentro de un "contexto reactivo".

podrías tirar esto en una mezcla

componentWillMount: function() {
  this.comp = Tracker.autorun(() => {
    return this.getReactiveData()
  })
componentWillUnmount: function() {
  this.comp.stop()
}

¡Y luego te queda esta función mágicamente reactiva que simplemente funciona!

getReactiveData: function() {
  let sub = Meteor.subscribe('messages')
  return {
    loading: !sub.ready(),
    messages: Messages.find().fetch()
  }
}

Tracker es genial así...

@ccorcos Resulta que me equivoqué un poco acerca de las cancelaciones de suscripción automáticas al usar eventos estilo Tracker. Aún debe detener al oyente en componentWillUnmount , de lo contrario, seguirá buscando fuentes de datos y llamando a setState (a menos que verifique con isMounted() que ahora está obsoleto).

En una nota al margen, puede crear decoradores elegantes para hacer que los métodos de componentes sean reactivos. He aquí algunos ejemplos: [1] , [2] . Se ve como esto:

export class Chat extends React.Component {
  <strong i="13">@reactive</strong>
  updateState () {
    this.setState({
      auth: auth.read(),
      messages: messages.read()
    })
  }

  /* ... */
}

@Mitranim bastante ordenado: aunque prefiero las funciones de alto orden. ;)

@sebmarkbage @jimfb He estado siguiendo este hilo y el hilo alternativo (# 3858) durante algunos meses, y tengo curiosidad por saber si el equipo central ha llegado a algún consenso sobre este problema, o al menos a una dirección general.

@oztune Sin actualizaciones; nos hemos centrado en otras prioridades. Publicaremos en uno de los hilos cuando haya una actualización sobre este tema.

Chicos, he creado una API universal para componer contenedores. Compruebe react-komposer . Que eso, podríamos crear contenedores con una función de orden superior.

import { compose } from `react-komposer`;

// Create a component to display Time
const Time = ({time}) => (<div>{time}</div>);

// Create the composer function and tell how to fetch data
const composerFunction = (props, onData) => {
    const handler = setInterval(() => {
    const time = new Date().toString();
    onData(null, {time});
  }, 1000);

  const cleanup = () => clearInterval(handler);
  return cleanup;
};

// Compose the container
const Clock = compose(composerFunction)(Time);

// Render the container
ReactDOM.render(<Clock />, document.getElementById('react-root'));

Aquí está la versión en vivo: https://jsfiddle.net/arunoda/jxse2yw8/

También tenemos algunas formas sencillas de componer contenedores con Promises, Rx.js Observables y With Meteor's Tracker .

Consulte también mi artículo sobre esto: Compongamos algunos contenedores React

@arunoda Terminamos haciendo algo muy similar. Una cosa que me pregunto es cómo evita que se llame a composerFunction en cada cambio de accesorio.

@oztune En realidad, ahora se ejecutará de nuevo. Usamos este Lokka y Meteor. Ambos tienen cachés locales y no llegan al servidor incluso cuando llamamos a composerFunction varias veces.

Pero creo que podríamos hacer algo como esto:

const options =  {propsToWatch: ["postId"]};
const Clock = compose(composerFunction, options)(Time);

¿Algunas ideas?

@arunoda Eso es lo que también intentamos, pero crea un poco de desconexión entre la acción y sus dependencias. Ahora hacemos algo similar a react-async, donde, en lugar de realizar inmediatamente la acción dada, composerFunction devolvería una función y una clave. Si la clave es diferente de la clave anterior devuelta por composerFunction, se realizará la nueva función. No estoy seguro de si esto es una tangente de este hilo de github, por lo que me encantaría continuar en Twitter (mismo nombre de usuario).

@oztune Creé un nuevo número de GH y continuemos nuestro chat allí. Mucho mejor que Twitter, supongo.

¿Por qué no dejar que JSX entienda y represente los Observables directamente para que pueda pasar Observable en accesorios, por ejemplo, this.props.todo$ e incrustarlos en JSX? Entonces no necesitaría ninguna API, el resto se administra fuera de React y HoC se usa para completar los observables. No debería importar si los accesorios contienen datos simples o un observable, por lo tanto, no se necesita este dato especial.

{this.props.todo$

Además, React render podría generar Oservable [JSX], lo que permitiría el diseño descrito en los enlaces sin una biblioteca adicional.

https://medium.com/@milankinen/containers-are-dead-long-live-observable-combinators-2cb0c1f06c96#.yxns1dqin

https://github.com/milankinen/react-combinators

Hola,
Por ahora, podríamos usar fácilmente componentes sin estado con flujos rxjs.
No entiendo la necesidad de otra API.
Escribí un ejemplo: un tablero que puede mover el mouse sobre él y cuando llega a 26 cambia para reiniciar.
Me encantaría saber lo que piensas de esta manera.
Aquí está el código:
https://jsfiddle.net/a6ehwonv/74/

@giltig : Últimamente también estoy aprendiendo de esta manera y me gusta. Junto con Cycle.js.

Apreciaría poder escuchar los controladores de eventos definidos en los componentes de alguna manera fácilmente sin tener que pasar Sujetos para el puente. Si entiendo correctamente, es más o menos al revés de lo que se sugiere aquí. Alternativamente, si pudiéramos observar React vdom para sus eventos sintéticos, tal vez se podrían usar "refs" para observar y evitar tener etiquetas CSS solo para observar.

Más RxJ podrían admitir objetos en combineLatest para que podamos usar los componentes funcionales de React directamente para crear componentes más grandes.

const myFancyReactComponent = ({surface, number, gameover}) => (
        <div> 
          {gameover ? gameover : surface}
          {number}
        </div>
)

const LiveApp = Rx.Observable.combineLatest(
    LiveSurface, DynamicNumberView, DynamicGameOver,
    myFancyReactComponent
)

Hola,
La razón por la que no usamos Cycle ni ningún otro marco es que prefiero las bibliotecas a los marcos (más control para el desarrollador) y también quiero disfrutar del poder de la comunidad que tiene React.
Muchos motores de renderizado ahora están desarrollados para React y es una pena no usarlos (ReactNative, ReactDom, ReactThree, etc.). Es bastante simple usar solo Rxjs y reaccionar como en el ejemplo que mostré arriba.

Habría hecho pensar más fácilmente si los componentes de reacción pudieran aceptar pojos siempre que sean observables como accesorios. Por ahora no es posible, así que lo que he mostrado arriba es la forma que elegimos.

Por cierto, lo que hizo con MyFancyReactComponent es posible y, de hecho, también lo hicimos en algunos casos, aunque también puede escribir jsx directamente.

Con respecto a los temas, creo que es una forma válida porque eventualmente en el componente React solo estoy usando una función de controlador que podría ser cualquier cosa. Elijo implementarlo con un sujeto dentro que recibe eventos, pero otra persona puede elegir lo contrario: es flexible.

Habría hecho pensar más fácilmente si los componentes de reacción pudieran aceptar pojos siempre que sean observables como accesorios. Por ahora no es posible, así que lo que he mostrado arriba es la forma que elegimos.

los apoyos observables no tienen ningún sentido a largo plazo. No tiene ningún sentido en ese contexto, en realidad...

Me parece que terminamos con un modelo diferente con Suspense (caché + contexto). El caché en sí mismo podría obtener soporte para suscripciones. Puede realizar un seguimiento del trabajo restante para Suspense en https://github.com/facebook/react/issues/13206.

También proporcionamos un paquete de suscripción para casos más aislados.

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