React: Implementar carregamento lateral de dados

Criado em 13 mar. 2015  ·  136Comentários  ·  Fonte: facebook/react

Esta é uma API de primeira classe para carregamento lateral de dados sem estado (embora potencialmente memorizados) de uma loja/rede/recurso global, potencialmente usando props/state 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() é executado após componentWillMount/componentWillUpdate, mas antes de renderizar.

Para cada chave/valor no registro. Assine o Observable no valor.

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

Permitimos que onNext seja invocado de forma síncrona a partir da assinatura. Se for, definimos:

this.data[key] = nextValue;

Caso contrário, deixamos como indefinido para a renderização inicial. (Talvez tenhamos definido como nulo?)

Em seguida, a renderização continua como de costume.

Toda vez que onNext é invocado, agendamos um novo "this.data[key]" que efetivamente aciona uma atualização forçada nesse componente. Se essa for a única alteração, observe não será executado novamente (componentWillUpdate -> render -> componentDidUpdate).

Se props / state mudou (ou seja, uma atualização de recieveProps ou setState), então observe() é reexecutado (durante a reconciliação).

Neste ponto, fazemos um loop sobre o novo registro e assinamos todos os novos Observables.

Depois disso, cancele a assinatura dos Observables anteriores.

subscription.dispose();

Essa ordenação é importante, pois permite que o provedor de dados faça a contagem de referência de seu cache. Ou seja, posso armazenar dados em cache enquanto ninguém os ouvir. Se eu cancelasse a assinatura imediatamente, a contagem de referência cairia para zero antes de eu assinar os mesmos dados novamente.

Quando um componente é desmontado, cancelamos automaticamente todas as assinaturas ativas.

Se a nova assinatura não chamou onNext imediatamente, continuaremos usando o valor anterior.

Portanto, se meu this.props.url do meu exemplo mudar e eu estiver assinando uma nova URL, myContent continuará mostrando o conteúdo da URL anterior até que a próxima URL seja totalmente carregada.

Isso tem a mesma semântica que a tag <img /> . Vimos que, embora isso possa ser confuso e levar a inconsistências, é um padrão bastante sensato, e é mais fácil fazê-lo mostrar um spinner do que seria ter o padrão oposto.

A melhor prática pode ser enviar imediatamente um valor "nulo" se você não tiver os dados armazenados em cache. Outra alternativa é um Observable fornecer a URL (ou ID) e o conteúdo no 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>;
  }

}

Devemos usar o contrato RxJS do Observable, pois é mais de uso comum e permite execução síncrona, mas uma vez que a proposta de @jhusain esteja em uso mais comum,

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

Podemos adicionar mais ganchos de ciclo de vida que respondem a esses eventos, se necessário.

Nota: Este conceito permite que os dados laterais se comportem como "comportamentos" - assim como adereços. Isso significa que não precisamos sobrecarregar o estado de noção para essas coisas. Ele permite otimizações como jogar fora os dados apenas para assinar novamente mais tarde. É restaurável.

Component API Big Picture

Comentários muito úteis

Se alguém quiser brincar com esse tipo de API, fiz um polyfill muito burro para observe como um componente de ordem 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 comentários

undefined é provavelmente o valor mais seguro para atribuir a data até que o observável forneça seu primeiro valor via onNext . Por exemplo, no Relay, atribuímos significados diferentes a null (dados não existem) e undefined (ainda não buscados), então nosso valor de dados padrão ideal seria undefined . A alternativa é fornecer um novo método, por exemplo, getInitialData , mas suspeito que isso seja desnecessário/exagerado.

Isso é muito interessante, no entanto, do ponto de vista da digitação estática, não estou tão feliz com o sistema de chave/valor, seu tipo é praticamente impossível de expressar.
Por que não ter observe retornar um único observável e definir/mesclar o valor resolvido para 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>;
  }

}

E para o caso de múltiplos buscar algo como:

class Foo {

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

  render() {
    ..
  }

}

Isso talvez seja um pouco mais detalhado, mas permite ter um bom tipo estático:

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

Além disso, em vez de reexecutar observe quando props/state mudar, podemos ter acesso a 'props' 'state' como um observável:

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

A razão é porque não queremos exigir o uso de combinadores e entender RxJS para poder assinar (múltiplos) Observáveis. Combinar dois Observáveis ​​dessa maneira é bastante confuso. De fato, pelo menos para nossas fontes de dados, provavelmente implementaremos a API de assinatura, mas nem incluiremos os combinadores no protótipo do Observables. Isso não é um requisito, mas você pode usar combinadores se precisar.

No entanto, para se inscrever em uma loja Flux simples, você não precisa.

Acho que o Flow provavelmente será capaz de lidar com esse tipo estático usando restrições, mas vou verificar com esses caras para ter certeza. Acho que será suficiente digitar a propriedade data e então o tipo observe pode ser implícito.

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

Essa mudança não se trata de entrar em tudo nos Observables como uma maneira de descrever o estado do aplicativo. Isso pode ser implementado em cima disso, como você fez antes. Isso não é explicitamente sobre o estado do aplicativo, pois esse método é idempotente. A estrutura é livre para cancelar e assinar novamente conforme necessário.

cc @ericvicenti

Pelo menos no caso de typescript, não haveria como restringir o tipo de retorno de observe com base no tipo de data , pelo menos até algo como https://github.com/ Microsoft/TypeScript/issues/1295 é implementado.

Eu adoraria usar isso para a próxima versão do React DnD, mas obviamente isso requer esperar pelo React 0.14.
Gostaria de saber se eu posso "polyfill" isso por enquanto com um componente de ordem superior que define this.data em uma instância ref . Mas pode ser muito louco.

Seria possível observar as Promessas? Então, pode-se usar uma árvore de promessas para resolver os dados de toda a árvore de componentes antes da primeira renderização! Isso seria muito útil para o React do lado do servidor.

Quais são os benefícios de tornar essa API de primeira classe? Isso poderia essencialmente ser realizado usando um "componente de ordem superior".

Quais são os benefícios de tornar essa API de primeira classe? Isso poderia essencialmente ser realizado usando um "componente de ordem superior".

Envolver 5 HOCs para obter 5 assinaturas é um pouco complicado e mais difícil de entender para iniciantes. Compreender componentWillReceiveProps também não é trivial. Isso corrige ambos.

Eu, por exemplo, saúdo nossos novos senhores observáveis.

Gostaria de saber se isso pode ajudar a aproximar https://github.com/chenglou/react-state-stream da API vanilla do React

Não seria necessário apenas um HOC? No exemplo em sua postagem no stores e se inscreve em cada um.

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

@aaronshaf Depende do caso de uso, com certeza. Às vezes são diferentes tipos de fontes estatais, não apenas “várias lojas”. Mas não posso dizer em nome da equipe React, vamos ouvir o que @sebmarkbage diz.

Adoraria algum tipo de polyfill para brincar com isso agora. Eu não entendi a ideia completamente, ainda. Qual é o mecanismo envolvido para lidar com atualizações futuras. Vou passar mais tempo entendendo. Eu acho que deve ser factível com um simples mixin.

( @vjeux me disse que eu deveria

Não quero promover meu próprio trabalho, mas acho que esse gancho é muito parecido com o gancho getNexusBindings do React Nexus . Você declara deps de dados no nível do componente por meio de um gancho de ciclo de vida (que pode depender de props).

A API se parece com:

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`],
    }
  }
}

A vinculação é aplicada/atualizada durante componentDidMount e componentWillReceiveProps . No último caso, as próximas ligações são diferenciadas das ligações anteriores; as ligações removidas são canceladas, as ligações adicionadas são assinadas. O mecanismo de busca/atualização subjacente é descrito na implementação do Nexus Flux . Basicamente com a mesma API você pode assinar dados locais (lojas locais tradicionais) ou dados remotos (buscar usando GET e receber patches via Websockets/polyfill). Você pode realmente assinar dados de outra janela (usando postWindow) ou um WebWorker/ServiceWorker, mas ainda não encontrei um caso de uso realmente útil para isso.

Para encurtar a história, você descreve de forma síncrona os deps de dados no nível do componente usando uma abstração do Flux e os ganchos garantem que suas dependências sejam automaticamente inscritas, injetadas em atualizações e canceladas.

Mas também vem com um bom recurso: exatamente as mesmas funções de ciclo de vida são aproveitadas para realizar a pré-busca de dados no tempo de renderização do lado do servidor. Basicamente, começando da raiz e recursivamente a partir daí, o React Nexus pré-busca as ligações, renderiza o componente e continua com os descendentes até que todos os componentes sejam renderizados.

@aaronshaf @gaearon O benefício de torná-lo de primeira classe é:

1) Não corrói o namespace props. Por exemplo, o componente de ordem superior não precisa reivindicar um nome como data do seu objeto props que não pode ser usado para mais nada. O encadeamento de vários componentes de ordem superior continua consumindo mais nomes e agora você precisa encontrar uma maneira de manter esses nomes exclusivos. E se você estiver compondo algo que já pode ter sido composto e agora você tem um conflito de nomes?

Além disso, acho que a melhor prática para componentes de ordem superior deve ser evitar alterar o contrato do componente encapsulado. Ou seja, conceitualmente, deve ser os mesmos adereços dentro e fora. Caso contrário, é confuso usar e depurar quando o consumidor fornece um conjunto de adereços completamente diferente do que é recebido.

2) Não precisamos usar state para armazenar o último valor. O conceito data é semelhante a props no sentido de que é puramente uma memorização. Somos livres para jogá-lo fora a qualquer momento se precisarmos recuperar a memória. Por exemplo, em uma rolagem infinita, podemos limpar automaticamente as subárvores invisíveis.

@RickWong Sim, seria bastante trivial oferecer suporte a Promises, pois são um subconjunto de Observables. Provavelmente deveríamos fazer isso para não ter opinião. No entanto, eu provavelmente ainda não recomendaria usá-los. Acho que eles são inferiores aos Observables pelos seguintes motivos:

A) Eles não podem ser cancelados automaticamente pelo framework. O melhor que podemos fazer é ignorar uma resolução tardia. Enquanto isso, a Promessa mantém recursos potencialmente caros. É fácil entrar em uma situação thrashy de assinar/cancelar/assinar/cancelar... de temporizadores/requisições de rede de longa duração e se você usar Promises, eles não serão cancelados na raiz e, portanto, você terá que apenas esperar o recursos para completar ou tempo limite. Isso pode ser prejudicial ao desempenho em grandes páginas da área de trabalho (como facebook.com) ou aplicativos críticos de latência em ambientes com restrição de memória (como react-native).

B) Você está se trancando para obter apenas um único valor. Se esses dados mudarem com o tempo, você não poderá invalidar suas visualizações e acabará em um estado inconsistente. Não é reativo. Para uma única renderização do lado do servidor que pode ser boa, no entanto, no cliente, você deve idealmente projetá-lo de forma que possa transmitir novos dados para a interface do usuário e atualizar automaticamente para evitar dados obsoletos.

Portanto, acho que o Observable é a API superior para criar, pois não o prende para corrigir esses problemas, se necessário.

@elierotenberg Obrigado por

Do ponto de vista de renderização do servidor, é importante que possamos adiar o renderToString final até que o Observable/Promise seja resolvido com dados que possam ser buscados de forma assíncrona. Caso contrário, ainda estamos na posição de ter que fazer todas as buscas assíncronas de dados fora do React sem saber quais componentes estarão na página ainda.

Acredito que o react-nexus permite que o carregamento assíncrono aconteça dentro de um componente antes de continuar na árvore de renderização.

Sim, react-nexus separa explicitamente:
1) declaração de ligação como getNexusBindings (que é um método de ciclo de vida síncrono e sem efeitos colaterais, semelhante ao render - na verdade, costumava ser o nome renderDependencies, mas achei confuso),
2) assinatura/atualização de vinculação como applyNexusBindings (que é síncrona e diferencia as ligações de nexo anteriores para determinar quais novas ligações devem ser assinadas e quais devem ser canceladas)
3) vinculação de pré-busca como prefetchNexusBindings (que é assíncrona e resolve quando o valor "inicial" (o que quer que isso signifique) está pronto)

ReactNexus.prefetchApp(ReactElement) retorna um Promise(String html, Object serializableData) . Este gancho imita a construção da árvore React (usando instantiateReactComponent ) e recursivamente constrói/pré-busca/renderiza os componentes. Quando toda a árvore de componentes está 'pronta', ela finalmente chama React.renderToString , sabendo que todos os dados estão prontos (erros de módulo). Uma vez resolvido, o valor desta Promessa pode ser injetado na resposta do servidor. No cliente, o ciclo de vida normal React.render() funciona normalmente.

Se alguém quiser brincar com esse tipo de API, fiz um polyfill muito burro para observe como um componente de ordem 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);

O Observable é algo concreto e acordado além das implementações de bibliotecas? Qual é o contrato, é simples o suficiente para implementar sem precisar usar bacon ou Rxjs? Por mais legal que seja uma API de primeira classe para dados de sideload, parece estranho para o React adicionar uma API baseada em uma primitiva de especificação não especificada/muito inicial, dado o movimento constante do React em direção ao js simples. Algo assim nos vincularia a uma implementação de terra de usuário específica?

como um aparte, por que não Streams? Não tenho nenhum cavalo na corrida, mas honestamente estou me perguntando; já há trabalho feito em fluxos da web e, claro, há node

@jquense Há um trabalho ativo em uma proposta para adicionar Observable ao ECMAScript 7 (+), portanto, idealmente, isso se tornaria JS simples. https://github.com/jhusain/asyncgenerator (Atualmente desatualizado.)

Não assumiríamos uma dependência do RxJS. A API é trivial para implementar sem usar RxJS. O RxJS é o mais próximo da proposta ECMAScript ativa.

most.js parece factível também.

A API do Bacon.js parece difícil de consumir sem depender do Bacon devido ao uso dos tipos Bacon.Event para separar valores.

As APIs de fluxo são de nível muito alto e estão longe desse caso de uso.

Existe algum tipo de opção "aguardar antes de renderizar"? Quero dizer, no cliente não é necessário esperar por todos os Observables antes de renderizar, mas no servidor você deseja esperar que eles resolvam para que o render() de cada componente seja completo , não parcial.

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

Em todas as minhas explorações, descobri que esse é o gancho do ciclo de vida mais importante que falta no React do lado do servidor.

Seguindo esta discussão, tentei resumir o que o React Nexus faz no seguinte post:

Aplicativos ismórficos feitos corretamente com o React Nexus

Aqui está o diagrama da rotina de pré-busca principal:

React Nexus

Não assumiríamos uma dependência do RxJS. A API é trivial para implementar sem usar RxJS. O RxJS é o mais próximo da proposta ECMAScript ativa.

:+1: esta é a grande preocupação para mim, pensando em, digamos, promessas onde implementar as suas próprias é extremamente difícil, a menos que você saiba o que está fazendo. Acho que, caso contrário, você acaba com um requisito implícito em uma lib específica no ecossistema. Tangencialmente... uma das coisas boas do mundo das promessas é o conjunto de testes A+, então mesmo entre as bibliotecas havia pelo menos uma garantia de uma funcionalidade comum de .then , o que foi útil para a interoperabilidade de promessas antes de serem padronizado.

essa é a grande preocupação para mim, pensando, digamos, em promessas em que implementar as suas próprias é extremamente difícil, a menos que você saiba o que está fazendo. Acho que, caso contrário, você acaba com um requisito implícito em uma lib específica no ecossistema.

Completamente acordado. Felizmente, os observáveis ​​têm um contrato muito simples e nem mesmo têm métodos embutidos como then então, de certa forma, são ainda mais simples que as promessas.

Eles podem se tornar mais complicados (e mais lentos) se o comitê insistir que chamar next agenda uma microtarefa como Promises.

Isso incomodaria muito o padrão baseado no fato de que onNext é síncrono no RxJS :/

Eu acho que um padrão comum de armazenamento de fluxo pode ser manter um mapa de observáveis ​​por chave para que eles possam ser reutilizados. Em seguida, limpe-os quando todos tiverem cancelado a assinatura.

Dessa forma, você pode fazer coisas como: MyStore.get(this.props.someID) e sempre receber de volta o mesmo Observável.

Dessa forma, você pode fazer coisas como: MyStore.get(this.props.someID) e sempre obter de volta o mesmo Observable.

Usar this.props.key (que eu sei) faria sentido? Na maioria dos casos você já passa esse identificador exclusivo com <... key={child.id} .. /> .

Dessa forma, você pode fazer coisas como: MyStore.get(this.props.someID) e sempre obter de volta o mesmo Observable.

Esse é o padrão que eu uso para o React Nexus também. Store#observe retorna um observador memorizado e imutável; ele é limpo (incluindo mecanismo de limpeza específico de back-end relevante, como enviar uma mensagem real de "cancelamento de inscrição" ou qualquer outra coisa) quando todos os assinantes desaparecem por pelo menos um tique.

@sebmarkbage @gaearon Como observaria o trabalho no servidor na v0.14?
Seria capaz de esperar corretamente que todos os observadores resolvessem antes de renderizar para string semelhante a como o react-nexus faz (mas embutido para reagir)?

IMO, seria ótimo se os componentes esperassem pelo primeiro valor observado antes de estarem “prontos” para renderização no servidor.

@gaearon : IMO, seria ótimo se os componentes esperassem pelo primeiro valor observado antes de estarem “prontos” para renderização no servidor.

Sim, :+1: para renderização assíncrona. Enquanto isso react-async por @andreypopp é uma alternativa, mas requer fibers para "hackear" o React. Seria ótimo se o React pudesse suportar renderização assíncrona pronta para uso.

A renderização assíncrona é algo que gostaríamos de oferecer, mas não faz parte deste problema. eu

Provavelmente não vai fazer isso em 0,14, infelizmente. Muitos designs diferentes a serem considerados e refatorados são necessários.

Sinta-se à vontade para criar e emitir descrevendo as mudanças arquitetônicas necessárias para que isso aconteça.

Eu tive o mesmo pensamento que @gaearon re: react -streaming-state . Dadas todas as aplicações potenciais além do carregamento lateral, poderia haver um nome melhor do que data ? Por exemplo, observed o associaria mais claramente ao método.

Não quero descarrilar com bikeshedding, mas queria jogar isso lá fora.

mal posso esperar por observáveis ​​no React. isso deve tornar o React reativo como eu o entendo

Estou experimentando uma ideia semelhante ao reescrever react-async , consulte README .

A diferença notável é que eu introduzo a identidade observável/processo explícita para reconciliar os processos, semelhante a como o React faz com os componentes key prop e stateful.

Quando id do processo nomeado é alterado, o React Async interrompe a instância do processo antigo e inicia uma nova.

A API se parece com:

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
    ...
  }
}

A API do processo agora segue a API ES6 Promises sintaticamente e no que diz respeito ao nome, mas semanticamente não se espera que process.then(onNext, onError) seja chamado apenas uma vez por processo ativo. Ele é feito para acomodar o caso de uso mais popular (?) de buscar dados por meio de promessas. Mas, para ser honesto, agora acho que preciso alterá-lo para evitar confusão.

Talvez eu esteja enganado, mas acho que a única coisa que impede a implementação da API proposta (neste problema) no userland é a falta do gancho do ciclo de vida que é executado logo antes da renderização como componentWillUpdate mas com o novo props e state já instalado na instância.

Uma coisa que ainda não foi discutida é o tratamento do callback onError . Se um observável produz um erro, essa informação deve estar disponível para o componente de alguma forma. Como o React lida com a chamada subscribe(callbacks) real, precisaríamos de um método padronizado para injetar nesse objeto de retorno de chamada.

Para fornecer o máximo de flexibilidade para desenvolvedores de aplicativos, vejo duas abordagens. A primeira é colocar os erros em um atributo de nível superior, semelhante a this.data . Isso parece incrivelmente pesado e consome ainda mais o namespace do componente.
O segundo permitiria que os desenvolvedores definissem seu próprio retorno de chamada onError como uma função de ciclo de vida. Se eu quisesse ter um tratamento de erros personalizado para meus observáveis, poderia adicionar algo como

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

Isso é semelhante ao que fizemos na próxima iteração do Parse+React. Criamos nosso próprio tratamento de erros para produzir a API que queríamos. Erros são adicionados a um mapa privado { name => error } e os componentes têm um método público de nível superior, queryErrors() , que retorna um clone do mapa se não estiver vazio e null caso contrário (permitindo um simples if (this.queryErrors()) .

Claro, definir novos métodos reservados também é um negócio complicado; Não vou fingir que não. Mas precisamos de uma maneira de tornar implícita ou explicitamente os erros disponíveis para o componente durante a renderização.

@andrewimm A ideia é alimentar isso em um sistema genérico de propagação de erros que borbulha o erro na hierarquia até que seja tratado por um limite de erro. https://github.com/facebook/react/issues/2928

Isso também deve lidar com erros em métodos lançados e recuperar normalmente. Como se o método render() fosse lançado. Isso é significativamente mais trabalhoso e levará algum tempo para ser implementado corretamente, mas a ideia era unificar o tratamento de erros dessa maneira.

Eu diria que isso deve ser deixado de fora do react apropriado, e 1 ou 2 pontos-chave de integração devem ser coordenados com projetos como react-async e react-nexus para que isso possa ser feito de forma limpa em cima do React adequado ....

Concordo, parece que ter uma maneira sugerida de fazer isso é melhor do que
assando isso no próprio framework

Em terça-feira, 21 de abril de 2015, 23h38 Rodolfo Hansen [email protected]
escreveu:

Eu argumentaria que isso deveria ser deixado de fora da reação adequada, e 1 ou 2 teclas
pontos de integração devem ser coordenados com projetos como react-async e
react-nexus para que isso possa ser feito de forma limpa em cima do React adequado ....


Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/facebook/react/issues/3398#issuecomment -95048028.

No fim de semana, construí mais uma implementação do Flux, chamada Flexy . Neste, confira o código para a loja. Ele expõe um método .getObservable que está em conformidade com a API observável, mesmo que realmente não tenha observáveis ​​ou outra estrutura reativa sendo usada.

Então, eu diria que a API é fácil de criar com Observables reais.

Dito isso, não julgue o código com severidade, ele foi feito em um fim de semana para:

  • Diversão
  • compreensão
  • usando js-csp
  • usando a API de observação

Nota lateral, um sistema como este e o Flux realmente tornam a renderização do lado do servidor menos dolorosa. Usando um sistema semelhante ao React-Nexus, podemos inicializar as lojas e passá-las para um aplicativo React. Podemos então monitorar os armazéns e o despachante e continuar a renderizar até que nenhuma ação seja disparada (todos os dados necessários já estão nos armazéns).

Eu diria que este é o menor ponto de integração para obter a nova semântica de assinaturas de dados sem estado. Que outros pontos de integração você sugeriria? Não incluindo renderização assíncrona, que é um problema muito mais complexo e merece seu próprio thread, e esse gancho pode ser usado com renderização assíncrona independentemente.

Todo o resto já é possível implementar em cima do React por componente. Observe que não faremos injeções globais de plugins, pois isso interrompe a reutilização de componentes em todo o ambiente, de modo que os frameworks construídos em cima do React devem ser contextuais para componentes individuais.

Que ganchos nos faltam?

Ei,

Para ser honesto, embora os novos ganchos tenham facilitado a implementação, certamente podemos obter o carregamento lateral de dados sem eles, como demonstramos com react-async e react-nexus .

De qualquer forma, expor e dar suporte à manutenção do ciclo de vida de instâncias de componentes React fora de uma hierarquia React montada ajudaria. Em react-nexus eu uso o instanciateReactComponent interno e chamo componentWillMount , componentWillUnmount , etc, eu mesmo, e considero essa abordagem frágil (e se instanciateReactComponents depende de invariantes internos que mudam na próxima versão do React?).

Quanto à abordagem sem estado, parece-me que a busca de dados assíncronos _is_ com estado e, portanto, armazenar o status pendente/concluído/falha no estado de alguns componentes é relevante. Quando usamos react-nexus em aplicativos do mundo real, temos componentes de ordem superior que realizam a busca de dados e injetam seu estado de busca em seus componentes filhos como props. O componente interno é, portanto, "sem estado" (o que é desejável) e o componente externo é "com estado" (o que também é desejável, por exemplo, para exibir um spinner de carregamento ou um espaço reservado).

Que outros pontos de integração você sugeriria?

parece-me que @ANDREYPOPP fez a pergunta certa. não é a única coisa que precisamos para implementar isso no userland o gancho do ciclo de vida antes da renderização? que parece ser a menor mudança mínima de API necessária, o resto é configurar e acionar forceUpdate apropriadamente à medida que você altera data com base em qualquer fluxo de entrada/emissor/observável. A menos que eu esteja perdendo algo especial sobre isso (totalmente possível)?

Eu não estou sendo atraído para a discussão maior aqui.
Mas para responder @sebmarkbage , acho que um dos ganchos mais importantes que eu gostaria é algo que só é necessário quando não se usa observáveis ​​reais.

  • Um gancho que pode fornecer funções que podem lidar com os valores enviados pelo observável _antes_ de serem definidos como dados. Com observáveis ​​reais, isso seria apenas um .map

Se a situação fosse um pouco mais aberta, acho que deveria haver ganchos para substituir o comportamento específico do Observable por ganchos personalizados. Dessa forma, poderíamos usar emissores de eventos ou canais CSP.

Parece-me que os últimos comentários estão dizendo que o menor ponto de extensão é realmente "conectar" e "desconectar" os ganchos do ciclo de vida - o que facilita a anexação de dados assíncronos a um componente e o gerenciamento dessas assinaturas?

Os observáveis ​​poderiam então ser construídos de forma bastante trivial sobre isso (dentro ou fora do núcleo) - mas expor esses pontos do ciclo de vida tem um apelo mais amplo?

É um resumo razoável?

Também não estou convencido de que isso precise ser resolvido no próprio React. Estou muito mais inclinado a pensar que o React deve fornecer todos os ganchos necessários para construir essa funcionalidade em cima do React.

Mas por um momento, digamos que vamos com observe() . Alguns pensamentos:

Temos this.props , this.state , this.context e agora this.data , todos como fontes potenciais de novos dados em render() . Isso me parece excessivo. A ideia é separar o estado do aplicativo do estado do componente? Isso pode esclarecer e separar algumas questões em torno do estado, mas sinto que o custo de introduzir um novo insumo pode não superar os ganhos. Se queremos que this.state se concentre apenas no estado do componente, por que não deixar os campos em this.data somarem this.props ou this.context ?

O nome this.data é muito genérico. Adereços são dados, estado são dados e qualquer variável local é dados. O nome não acrescenta nenhum significado e confunde os significados existentes. Eu preferiria muito mais this.observed ou qualquer outro nome que realmente significasse alguma coisa. Então +1 ao comentário de @matthewwithanm :

pode haver um nome melhor do que data ? Por exemplo, observed o associaria mais claramente ao método.

Se deixarmos observe() executar no servidor, precisamos de algum tipo de gancho que limpe qualquer vazamento de memória que isso possa causar, porque a desmontagem nunca acontecerá.

Se chamarmos observe() novamente toda vez que props ou state mudarem (e context ?) então observe deve ser otimizado para desempenho e idealmente não pode ser caro acidentalmente. Torna-se uma parte do caminho quente. Eu gosto disso do React Nexus:

as próximas ligações são diferenciadas das ligações anteriores; as ligações removidas são canceladas, as ligações adicionadas são assinadas.

Passei a acreditar que o estado complica os componentes e tenho tentado usá-lo menos em meus próprios componentes e aprimorá-lo . É por isso que, além das preocupações levantadas pelo @fisherwebdev (com as quais concordo agora), não estou convencido de que deixar observe depender de state seja uma boa ideia. State depende de props, observado depende de state _and_ props.. Não é muito complicado? Eu prefiro ter observe(props) .

Também estou percebendo que observar não deve ser dependente de state , principalmente porque não precisa. Como @gaearon aponta, é fácil elevar o estado para um nível acima, o que parece mais limpo depois de separar as preocupações. Quando observe pode depender potencialmente de state , a lógica em torno da manipulação de atualizações dentro do componente se torna significativamente mais complexa; quando depende apenas de props , você pode ter manipuladores sem bifurcação em componentDidMount / componentWillReceiveProps . Menos código no caminho crítico se traduz em um ciclo de renderização mais simples e também reduz o número de atualizações não intencionais que acionam novamente as assinaturas.

+1 para observar(props)

Quanto menos lidarmos com o estado, melhor IMO.

Eu sou da opinião que, como uma biblioteca, o React deveria tentar ser o mais flexível possível. Concordo que não é uma boa ideia observar depender do estado. Sim, pode ser complicado. Sim, a melhor prática deve ser não depender do estado.
Mas essa é uma escolha que os usuários do React devem ter permissão para fazer.
Eu recomendaria deixar a API atual inalterada (além de quaisquer ganchos possíveis para adicionar mais flexibilidade, para fazê-la funcionar com mais do que apenas observáveis, por exemplo), e provar a documentação que explica que o uso de state no método observe não é recomendado.

Acredito que todos querem fazer a coisa certa e que queremos ser apontados na direção certa. Observar o estado de não aceitação torna isso mais fácil para o usuário do que encontrar acidentalmente algo como "este é um antipadrão" nos documentos.

EDIT: desculpe, acabei de perceber que estava procurando flatMap . Eu me confundi porque estava pensando que iria achatar o conjunto de mensagens, mas está operando em um nível mais alto (as mensagens observáveis).

A API proposta lidaria com o caso em que o resultado de um campo de dados dependesse do resultado de outro? Ou seja, você mapeia o primeiro resultado e retorna um observável:

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

Eu sou muito novo para observáveis ​​em geral, então posso estar fazendo isso completamente errado. Na área da promessa, isso é extremamente simples, pois retornar uma promessa de then fará com que then s subsequentes sejam baseados nessa promessa.

Não entendo os comentários de que não deve depender de this.state . O estado encapsulado certamente torna o React muito mais complicado, mas isso também é tudo. Se não houvesse estado encapsulado, precisaríamos apenas de uma biblioteca de modo imediato memorizada. Se você for all-in em "Stores", então sim, você não precisa de estado, mas não é isso que o React prescreve que você faça.

Temos vários padrões que exigem que você crie wrappers extras, mas para uso normal você não precisa. Além de armazenamentos, seus dados observados sempre dependem do estado, mesmo que indiretamente. Eu não acho que seja uma má prática depender disso em observação. Por exemplo

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

Mesmo que observe não dependesse de state , ainda dependeria de props e context . Mesmo que não dependesse de context você ainda teria uma indireção que usa context para renderizar as props de um componente que o usa em observe .

observe precisaria ser reavaliado toda vez que uma passagem de renderização fosse interrompida porque os adereços poderiam ter mudado. No entanto, definitivamente diferenciamos os observáveis ​​resultantes e não cancelamos/reassinamos se o mesmo observável for retornado. No entanto, não podemos fazer diferenças em propriedades individuais (exceto com shouldComponentUpdate), então o ideal é que você implemente seu próprio cache usando um Map como um recurso de energia. Dessa forma, você pode retornar o mesmo observável para vários componentes na árvore. Por exemplo, vários componentes carregando o mesmo usuário. Mesmo se você não fizer isso, você apenas recria o Observable e, eventualmente, atinge o cache inferior. É assim que a reconciliação do React funciona de qualquer maneira. Não é tão lento.

Este gancho observe não foi projetado para tornar o React completamente reativo no sentido de que o estado é capturado em Observables. Atualmente, um dos principais objetivos de design é evitar aprisionar o estado em closures e combinadores e, em vez disso, ter uma árvore de estado separada e limpa que possa ser congelada e revivida e potencialmente compartilhada entre os trabalhadores.

O que me leva ao meu ponto final...

Certamente não _precisamos_ adicionar isso à biblioteca principal. A interface "pública" original era mountComponent/receiveComponent e você poderia construir todo o seu sistema de componentes compostos em cima dela. No entanto, poucas pessoas usaram que é muito mais poderoso aumentar a barra de abstração, pois agora podemos construir outras coisas que são habilitadas por uma barra de abstração mais alta. Como otimizações em todo o componente.

O objetivo principal do React é criar um contrato entre os componentes para que diferentes abstrações no ecossistema possam coexistir. Uma parte importante desse papel é aumentar o nível de abstração de conceitos comuns para que possamos habilitar novos recursos de componentes cruzados. Por exemplo, salvar todo o estado de uma subárvore, então reviver a subárvore. Ou possivelmente incluindo a desmontagem automática no servidor ou alterando os aspectos de tempo da reconciliação no servidor.

Também é importante fornecer algumas baterias incluídas para tornar tudo isso palatável e uniforme.

É importante perceber que a micromodularização (como adicionar um novo gancho de ciclo de vida) não é estritamente uma vitória pura sobre a construção da estrutura. Isso também significa que não é mais possível raciocinar sobre abstrações em todo o sistema.

Eu gostaria que algo assim estivesse nos documentos como “filosofia/metas de design/não-metas”.

O objetivo principal do React é criar um contrato entre os componentes para que diferentes abstrações no ecossistema possam coexistir. Uma parte importante desse papel é aumentar o nível de abstração de conceitos comuns para que possamos habilitar novos recursos de componentes cruzados.

Amo isso. Eu concordo com @gaearon que seria bom se isso estivesse em algum tipo de documento.

Certamente não precisamos adicionar isso à biblioteca principal.... No entanto, poucas pessoas usaram que é muito mais poderoso aumentar a barra de abstração, pois agora podemos construir outras coisas que são habilitadas por uma barra de abstração mais alta. Como otimizações em todo o componente.

Sinto que a reticência (pelo menos para mim) não é adicionar outra API, mas adicionar uma que dependa de uma construção que não seja de linguagem (ainda sendo definida) para funcionar. Isso pode funcionar totalmente bem, mas eu me preocupo com os problemas que as bibliotecas Promise enfrentam em que nenhuma biblioteca Promise (mesmo as de reclamação de especificação) pode confiar uma na outra, o que leva a encapsulamento desnecessário e trabalho defensivo para garantir que elas sejam resolvidas corretamente, o que limita as oportunidades de otimização . Ou pior ainda, você fica preso como o jQuery com uma implementação quebrada que nunca pode mudar.

@jquense concordo plenamente. Eu queria adicionar este gancho há muito tempo. (Experiência original: https://github.com/reactjs/react-page/commit/082a049d2a13b14199a13394dfb1cb8362c0768a )

A hesitação dois anos atrás era que ainda estava muito longe da padronização. Eu queria um protocolo padrão antes de adicioná-lo também ao núcleo.

Acho que estamos chegando a um ponto em que muitos frameworks concordam com a necessidade de algo como Observables e a padronização está chegando a um ponto em que uma API palatável foi proposta. Tenho certeza de que precisaremos ajustá-la um pouco, mas enquanto a arquitetura de alto nível funcionar, ela poderá ser trocada e eventualmente convergir.

Acho que o que aconteceu com o Promises é que a API e a história de depuração estavam severamente ausentes em certas áreas das quais o Observables não sofre. É uma história mais completa e pronta para uso, onde as Promises tiveram que padronizar uma solução incompleta mínima.

A única diferença de opinião re: Observables que observei (não pude resistir, desculpe) é o potencial do Zalgo. Se os Observables podem ou não enviar valor de forma síncrona em resposta a uma assinatura. Algumas pessoas parecem contra, mas o uso de Observables pelo React dependerá disso até onde eu entendo. Você pode comentar nisso?

Geralmente, não acho que o Zalgo seja um problema com Observables porque o consumidor está sempre no controle e pode optar por sempre assíncrono com algo como observeOn .

Ainda bem que finalmente há algum consenso sobre isso. Pessoalmente, prefiro canais a Observables, mas se Observables forem adicionados ao idioma, concordo que não há necessidade de esperar mais.

Dito isso, vamos manter a API aberta o suficiente para trabalhar com não-Observáveis ​​que estejam em conformidade com a API básica.

Também não acho o Zalgo um problema. No entanto, com Observables, você pode usar um agendador para garantir a assíncrona, se desejar, o agendador padrão agora é assíncrono para que você possa usá-lo conforme necessário.

@sebmarkbage Acho que você abordou a maioria das minhas preocupações e agora vejo o benefício de adicionar isso à estrutura. No entanto, você pode comentar this.data -- (1) podemos/devemos dobrar esses campos em props/context/state ou (2) podemos renomeá-lo?

A questão do Zalgo não é realmente sobre se importa em termos de expectativa da API, mas sim de interoperabilidade observável e facilidade de implementação. Não ter um acordo antecipado sobre o Zalgo que colocou o mundo da biblioteca Promise na posição irritante de ter que ser super defensivo ao lidar com Promises de outras libs. (meu ponto acima repetido abaixo)

...onde nenhuma biblioteca de promessas (mesmo as de reclamação de especificações) podem confiar umas nas outras, o que leva a encapsulamento desnecessário e trabalho defensivo para garantir que elas sejam resolvidas corretamente

Como as promessas iniciais nem todas estavam em conformidade com a resolução assíncrona, estamos em uma posição agora em que, mesmo que sejam especificadas, as bibliotecas não podem assumir que thenables são confiáveis, matando otimizações em potencial. Isso me parece particularmente relevante aqui, onde o React não fornecerá uma implementação Observable para usar (quem iria querer isso de qualquer maneira?), e provavelmente estamos a anos de poder confiar apenas em um navegador fornecido Observable, biblioteca tão fácil interoperabilidade é importante. Além disso, para o ponto de @gaearon , se o React depender de chamadas de sincronização e for especificado para ser sempre assíncrono, isso nos coloca em uma posição semelhante a jquery de ficar preso a uma implementação desonesta.

Eu concordo completamente. Eu queria adicionar este gancho há muito tempo. A hesitação dois anos atrás era que ainda estava muito longe da padronização. Eu queria um protocolo padrão antes de adicioná-lo também ao núcleo.

Fico feliz que também esteja sendo atendido e pensado, isso certamente é reconfortante. :) e, em geral, acho que a adoção antecipada de promessas valeu os contras que estou discutindo aqui, então não tome minha preocupação como desaprovação ou desaprovação, estou muito animado com a perspectiva de uma API de primeira classe para isso e também vejo como Observable é realmente uma escolha boa/mais razoável aqui para isso.

"Devemos usar o contrato RxJS do Observable, pois é mais de uso comum e permite execução síncrona, mas uma vez que a proposta de @jhusain esteja em uso mais comum, mudaremos para esse contrato."

Só para adicionar um pouco mais de contexto. Existe uma iniciativa Reactive Streams (http://www.reactive-streams.org/) para fornecer um padrão para processamento de fluxo assíncrono com contrapressão sem bloqueio. Isso engloba esforços direcionados a ambientes de tempo de execução (JVM e JavaScript), bem como protocolos de rede.

As implementações líderes atuais são fe Akka Streams ou RxJava. Não sei se os RxJs já estão em conformidade com a mesma interface agora, a interface atual para Assinanteé onSubscribe(Subscription s), onNext(T t), onCompleted(), onError(Throwable t).

Você pode lançar mais luz sobre qual é a proposta de @jhusain ?

Eu não sei se o React deve cumprir estritamente com esta iniciativa porque se eu precisar eu provavelmente posso colocar RxJs (assumindo que vai cumprir) no meio e me adaptar à interface React e deixar conceitos mais avançados como back-pressure para RxJs (embora eu preferem não ter que se adaptar muito).

Existe alguma posição ou objetivo em relação a esta iniciativa?

@vladap acredito que esta seja a proposta mencionada de @jhusain

Eu li @jhusain e não tenho certeza sobre a motivação para possivelmente mudar para essa especificação no futuro. Existe alguma vantagem específica?

A especificação de fluxos reativos tem suporte maior e já está na versão 1.0 . Como o RxJava já implementa essa especificação, suponho que os RxJs seguirão (mas não verifiquei).

Este blog resume as interfaces com alguns exemplos usando fluxos Akka.

Vejo alguma vantagem em ter as mesmas interfaces no backend e no frontend, principalmente porque trabalho em ambos. Possivelmente, pode ajudar a cooperar entre os grupos de back-end e front-end, mas, por outro lado, presumo que websocket ou sse sejam os pontos de integração reais para streaming.

Não consigo encontrar a lista de implementadores em www.reactive-streams.org agora, mas a última vez que verifiquei foi:

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
Stephane Maldini – Pivotal Software Inc.
Norman Maurer – Red Hat Inc.
Erik Meijer – Applied Duality Inc.
Todd Montgomery – Kaazing Corp.
Patrik Nordwall – Typesafe Inc.
Johannes Rudolph – spray.io
Endre Varga – Typesafe Inc.

Talvez eu vá longe demais aqui, mas acredito que o contexto mais amplo pode ajudar nas decisões futuras.

@vladap Pelo que entendi e pelo que vejo nos problemas do github o @jhusain já está trabalhando com eles, então acho que não teremos tantos problemas.
Do ponto de vista da interface, pelo que também pude entender em diferentes problemas do github e outros documentos de especificação, o observador certamente respeitará a interface do gerador, algo como:

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

Simplesmente implementar um observável muito básico com um único método 'subscribe' que respeite essa interface deve ser seguro para reagir.

Parece apenas uma nomenclatura diferente com a mesma funcionalidade, nesse sentido está tudo bem. Eu provavelmente preferiria a mesma nomenclatura da especificação, mas no final não me importo tanto se esses métodos fizerem o mesmo.

Não consigo avaliar o equivalente ausente a onSubscribe(). No blog que mencionei, o autor diz que é uma chave para controlar a contrapressão. Não sei se tem outros casos de uso. A partir disso, presumo que o React não se importe em controlar a contrapressão ou que exista outra estratégia para isso. É uma coisa complexa, portanto, entendo que não é uma preocupação do React.

Eu entendi corretamente que a estratégia é algo ao longo das linhas - ou o aplicativo é complexo e pode surgir problemas de contrapressão, então use algo intermediário para resolvê-lo, como RxJS, ou você conecta o componente React diretamente ao fe websocket e você não não tem problemas de contrapressão porque o app é simples e tem atualizações lentas.

Onde posso encontrar interfaces observáveis ​​propostas para o futuro ECMAScript? Se já existe.

A proposta atual pode ser encontrada aqui:

https://github.com/jhusain/asyncgenerator

JH

Em 7 de maio de 2015, às 2h32, vladap [email protected] escreveu:

Onde posso encontrar interfaces observáveis ​​propostas para o futuro ECMAScript? Se já existe.


Responda a este e-mail diretamente ou visualize-o no GitHub.

A Proposta de Fluxos Reativos (RSP) vai além da proposta TC-39 porque introduz um Observável que lida com a contrapressão. O RSP Observable é otimizado para enviar fluxos com eficiência pela rede, respeitando a contrapressão. É baseado em parte no trabalho feito em RxJava, que é uma peça de engenharia muito impressionante (divulgação completa: foi projetada pelo colega da Netflix, Ben Christensen).

A principal razão para a decisão de padronizar o tipo Observable mais primitivo é a cautela. O Observable mais primitivo é o dual do contrato ES2015 Iterable, que nos fornece garantias valiosas de que o tipo é pelo menos tão flexível quanto um tipo já padronizado no ES2015. Além disso, há uma grande variedade de casos de uso em JS para Observables que não requerem contrapressão. No navegador, o DOM é o coletor mais comum para fluxos de push e atua efetivamente como um buffer. Dado que o tipo RSP é mais complexo, nossa abordagem é padronizar primeiro o tipo mais primitivo e depois deixar espaço para implementar o tipo mais avançado posteriormente. Idealmente, esperaríamos até que fosse validado na terra do usuário.

FYI RxJS atualmente não tem planos para implementar RSP Observable.

JH

Em 7 de maio de 2015, às 2h30, vladap [email protected] escreveu:

Parece apenas uma nomenclatura diferente com a mesma funcionalidade, nesse sentido está tudo bem. Eu provavelmente preferiria a mesma nomenclatura da especificação, mas no final não me importo tanto se esses métodos fizerem o mesmo.

Não consigo avaliar o equivalente ausente a onSubscribe(). No blog que mencionei, o autor diz que é uma chave para controlar a contrapressão. Não sei se tem outros casos de uso. A partir disso, presumo que o React não se importe em controlar a contrapressão ou que exista outra estratégia para isso. É uma coisa complexa, portanto, entendo que não é uma preocupação do React.

Eu entendi corretamente que a estratégia é algo ao longo das linhas - ou o aplicativo é complexo e pode surgir problemas de contrapressão, então use algo intermediário para resolvê-lo, como RxJS, ou você conecta o componente React diretamente ao fe websocket e você não não tem problemas de contrapressão porque o app é simples e tem atualizações lentas.


Responda a este e-mail diretamente ou visualize-o no GitHub.

Muito obrigado pelos valiosos detalhes. Faz muito sentido.

@gaearon eu meio que copiei você. Eu queria usar ParseReact com classes ES6, então precisava reimplementar a API observe do mixin como um componente de ordem superior.

https://gist.github.com/amccloud/d60aa92797b932f72649 (uso abaixo)

  • Eu permito que observe ser definido no componente ou passado para o decorador. (Eu poderia mesclar os dois)
  • Eu mesclo em observáveis ​​como adereços (sem this.data ou this.props.data)

@Aaronshaf @Gaearon O benefício de fazer a primeira classe é:

1) Não se afasta no namespace dos adereços. Por exemplo, o componente de ordem superior não precisa reivindicar um nome como dados do objeto Adereço que não pode usar para qualquer outra coisa. Encadear vários componentes de ordem superior continuam a comer mais nomes e agora você precisa encontrar uma maneira de manter esses nomes únicos. E se você estiver compondo algo que já possa ser composto e agora você tem um conflito de nome?

Além disso, acho que a melhor prática para componentes de ordem superior deve ser evitar a mudança do contrato do componente embrulhado. Ou seja, conceitualmente, deve ser os mesmos adereços em que. Caso contrário, é confuso usar e depurar quando o consumidor fornece um conjunto completamente diferente de adereços do que é recebido.

Em vez de um hoc, pode ser um 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>;
  }
}

Porque você controla a função passada como render prop, não há como os nomes colidirem. Por outro lado, não polui o estado do proprietário.

O que estou perdendo aqui?

Isso poderia até ser menos detalhado se o componente Observe apenas pegasse Observables de seus adereços:

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

O que também é bom nisso é que evitará novas assinaturas se shouldComponentUpdate retornar false porque não entraremos em render .

Finalmente, pode-se escrever um decorador para render que o envolva em um 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>;
}

Eu preferiria manter render como uma função de renderização pura em vez de injetar lógica de busca de dados nela.
A proposta inicial parece boa na minha opinião. É muito próximo de como o estado funciona com rx-react e permitirá separar o gerenciamento de estado da lógica de busca de dados que parece muito coerente.

A única coisa que me incomoda é o uso de mapa de observáveis ​​em vez de um observável, pois não dá ao usuário a possibilidade de escolher como os observáveis ​​são compostos, mas isso é uma questão menor.

Não está realmente injetando a lógica de busca de dados, apenas economiza digitação. Ele irá desaçucar para a versão acima, que apenas renderiza o componente <Observe /> . Não é incomum renderizar componentes com estado, então não acho que isso torne render menos puro do que é agora.

Tentei combinar o melhor do #3858 e esta proposta.

Em qualquer abordagem HOC, o benefício é a clareza, mas as desvantagens são descritas por @sebmarkbage : os nomes dos adereços podem entrar em conflito em algum momento.

Na proposta atual, o benefício é a clareza, mas o lado negativo é o ciclo de vida mais complicado e a superfície da API do componente principal maior.

Em #3858, o benefício é colocar dependências de "renderização memorizada" com a renderização em si (elas não são usadas em nenhum outro lugar, então faz sentido), mas estou preocupado com a "parece sincronizada, mas é assíncrona" e não entendendo como isso pode trabalhar com modelos imutáveis ​​se depender tanto de this . Isso também me incomoda no modo React-foi-fácil-de-raciocinar, porque não há nada fácil em raciocinar sobre o rastreamento manual de alterações e o acoplamento das fontes de dados ao React (ou envolvê-los para trabalhar com o React). Eu entendo que existe uma pressão para implementar algo com desempenho e redução do clichê.

Na minha proposta, estou mantendo a colocação e explicitação, mas:

  • <Observe /> (ou o decorador observe() que envolve a renderização com <Observe /> ) é apenas um complemento, não estou propondo _any_ alterações no núcleo do React.
  • Todos podem implementar sua própria lógica de observação para seus casos de uso. Você pode ter seu próprio <Observe /> que usa apenas um observável, se preferir. Você pode ou não usar o decorador.
  • O ciclo de vida do componente permanece o mesmo.
  • Não há conflitos de prop porque os dados são passados ​​como parâmetro.
  • Estamos fazendo dogfood resolvendo os problemas com as ferramentas que temos.
  • Para reduzir o clichê, usamos ferramentas de redução clichê (decoradores) em vez de introduzir novos conceitos básicos.

Ótima discussão e trabalho por aqui, muito respeito. :)

Concordo que isso não merece torná-lo em núcleo. Talvez um complemento dê a essa proposta tração suficiente para que as pessoas possam tentar convergir e padronizá-la antes de se comprometer mais completamente. Dito isto, acho esta proposta melhor do que https://github.com/facebook/react/issues/3858 e https://github.com/facebook/react/pull/3920 por seu minimalismo.

Isso é algo que tenho usado em projetos paralelos (então grãos de sal) - é semelhante ao trabalho incrível de @elierotenberg , mas não

Prepare-se para CoffeeScript e mixins, ou continue apertando os olhos até que pareça ES6, se preferir. :)

_ = 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) ->
  #   []

E usado assim:

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

  propTypes:
    flux: PropTypes.flux.isRequired

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

  ...

Então declareNeeds é uma função unidirecional de props e state para uma descrição do que esse componente precisa. Na implementação real aqui, a extremidade receptora de @props.flux.declareNeeds , que é configurada em um componente de nível superior, afunda essas necessidades em um objeto ProcessSink . Ele agrupa como quiser, desduplica needs em componentes que compartilham o mesmo flux e, em seguida, executa efeitos colaterais para atender a essas necessidades (como conectar-se a um soquete ou fazer solicitações HTTP). Ele usa a contagem de referência para limpar coisas com estado, como conexões de soquete, quando não há mais componentes que precisem delas.

Os dados fluem de bits com estado, como eventos de soquete e solicitações, para o dispatcher (e depois para Stores ou qualquer outro lugar) e voltam para os componentes para atender às suas necessidades. Isso não é síncrono e, portanto, todos os componentes lidam com a renderização quando os dados ainda não estão disponíveis.

Estou compartilhando isso aqui apenas como outra voz de que explorar esses tipos de soluções é possível no espaço do usuário e que a API atual está atendendo muito bem a esse tipo de experimentação. Em termos de uma etapa mínima que o núcleo pode dar para oferecer suporte à interoperabilidade entre diferentes abordagens, acho que @elierotenberg acertou em cheio :

De qualquer forma, expor e dar suporte à manutenção do ciclo de vida de instâncias de componentes React fora de uma hierarquia React montada ajudaria.

Quanto à abordagem sem estado, parece-me que a busca de dados assíncrona é com estado e, portanto, armazenar o status pendente/concluído/falha no estado de alguns componentes é relevante.

@elierotenberg e @andrewimm fazem uma excelente observação sobre o tratamento de erros de primeira classe. @sebmarkbage Acho que seu instinto em relação ao ponto de interoperabilidade mínimo está certo, mas não tenho certeza de como adicionar semântica para erros para borbulhar a árvore de componentes se encaixa nesse requisito. É preciso haver uma história igualmente simples aqui sobre como acessar valores de onError e onCompleted , mesmo que seja apenas que os slots em this.observed contenham o último valor de next/error/completed como { next: "foo" } . Sem apoiar o contrato observável como um recurso de primeira classe, estou um pouco cético sobre essa proposta ser cortada.

E já que esta é a internet, e uma das minhas primeiras vezes entrando aqui: o feed de questões do React é uma das melhores leituras e uma fonte completa para ótimos trabalhos e ideias. :+1:

cheira a ligações para mim.

Não tenho certeza de como isso se relaciona com a proposta/implementação atual, mas descobri que habilitar a composição e a manipulação de ordem superior é, na verdade, um recurso importante do rastreamento de dependência de dados, especialmente se você usar uma fonte de dados reativa (fluxo, fluxo over the wire, ou qualquer outra coisa que não forneça atualizações).

Veja o seguinte exemplo com 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.
}

Em suma, gostaria de saber se deve haver uma vinculação de dados na API do componente. Parece-me que os decoradores que retornam componentes de ordem superior fornecem uma maneira muito boa de expressar a vinculação de dados sem poluir o namespace dos métodos do componente.

No entanto, como @sebmarkbage observou, existe o risco de poluição do namespace props. Por enquanto, estou usando um decorador de transformador de props ( react-transform-props ) para limpar/renomear props antes de passá-los para o componente interno, mas reconheço que pode se tornar problemático no futuro se componentes de ordem superior se tornarem mais comum e os riscos de conflito de nomes aumentam.
Isso poderia ser resolvido usando símbolos são chaves de propriedade? A verificação de propTypes Symbol suporta adereços com chave de Symbol? O JSX suportará chaves de props inline computadas (embora sempre se possa usar propriedades computadas + propagação de objeto)?

Desculpe se isso ficar um pouco offtopic, mas parece-me que ainda temos que encontrar a abstração/API certa para expressar deps de dados no nível do componente.

meus 2¢

Tenho me divertido muito com o padrão 'crianças como função' para valores que mudam com o tempo. Eu acredito que @elierotenberg foi o primeiro a

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

Nenhuma colisão de adereços e nenhum uso do estado do proprietário. Eu também posso aninhar várias molas e reagir gerencia todos os bits difíceis. Esses 'observáveis' (ha!) também podem aceitar onError , onComplete e outros adereços (consultas graphql?).

Uma tentativa grosseira de esboçar isso para 'fluxo'

<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 esse padrão, o que chamamos de _render callbacks_, na Asana, mas estamos nos afastando dele.

Prós

  • Sem potencial para colisão de adereços.
  • Fácil de implementar tanto como autor de StoreComponent quanto como usuário de StoreComponent

Contras

  • shouldComponentUpdate é extremamente difícil de implementar, pois o retorno de chamada de renderização pode fechar sobre o estado. Imagine que tivéssemos uma árvore de componentes de A -> Store -> B . A tem um contador em seu estado que é acessado durante o retorno de chamada de renderização como props para B . Se A for atualizado por causa do contador e o Store não for atualizado, B terá uma versão obsoleta do contador. Como resultado, fomos forçados a sempre atualizar a Loja.
  • Testar componentes isoladamente tornou-se muito difícil. Inevitavelmente, os desenvolvedores colocaram lógica complexa nos retornos de chamada de renderização para qual componente renderizar e queriam testar a lógica. A maneira natural de fazer isso era renderizar a árvore inteira e testar _através_ do componente store. Isso tornou impossível usar o renderizador superficial.

Agora estamos migrando para um modelo em que StoreComponent recebe um ReactElement e quando a loja recebe novos dados, a loja clona o ReactElement substituindo uma prop específica. Há muitas maneiras de fazer esse padrão, como usar um construtor Component e props. Optamos por essa abordagem porque era a mais fácil de modelar no TypeScript.

Pontos fantásticos, também não consigo pensar em uma maneira de contornar sem quebrar o requisito 'de lado'.

@threepointone Soa exatamente como uma das propostas de implementação para poder https://github.com/reactjs/react-future/pull/28

@pspeter3 no seu exemplo, existe uma séria desvantagem em fazer com que a Loja sempre atualize / shouldComponentUpdate: ()=> true ? Estou pensando que seus 'filhos' teriam sido renderizados sem Store na cadeia de qualquer maneira. Obrigado pelo seu tempo!

@threepointone Isso é exatamente o que fizemos para nossos limites. Não ficou claro qual foi exatamente o impacto, mas havia preocupações por parte dos membros da equipe. As preocupações combinadas com o teste de dificuldade forçaram a mudança para o uso de React.cloneElement(this.props.child, {data: this.state.data})

@pspeter3 o ângulo de teste é definitivamente um problema. E se o rasoRenderer reconhecesse 'retornos de chamada de renderização'? Isso ajudaria?

Ps- 'render callback' :thumbs_up:

@sebmarkbage a discussão atual sobre es-observable é que subscribe seria garantido assíncrono, com um método [Symbol.observer] fornecido para atalho e assinatura síncrona, embora a validade de ter ambas as APIs de sincronização/assíncrona seja atualmente em debate.

Eu tinha entrado nesse ticket com o caso de uso mencionado acima em favor da assinatura síncrona, não sabia se você poderia ter algo a acrescentar lá.

Acho que as idéias aqui são um padrão muito limpo para lidar com o estado externo, embora depois de brincar um pouco com isso, estou inclinado a favor da abordagem HOC.

Também @gaearon eu simplifiquei seu bit HOC acima, usando um estático - ComposedComponent.observe e usando this.state em vez de this.state.data - https://gist.github.com/tgriesser/d5d80ade6f895c28e659

Parece muito bom com os decoradores es7 propostos:

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

O decorador de classe pode até adicionar um getter para data para torná-lo muito próximo da API proposta original (menos o estado local afetando as assinaturas observáveis, o que eu concordo é uma coisa boa - muito menos ruído ao redor Inscrever-se Cancelar inscrição).

a falta de assinatura síncrona pode ser um grande problema.

Acho que entendi como resolver "o problema do estado" e "Carregamento de dados lateral" (dados derivados) de maneira consistente. Faz isso em um "modo React-way" sem estado. Eu encontrei uma maneira de manter a consistência do estado a qualquer momento e se encaixa no padrão UI = React(state) . É improvável que eu esteja fora de mãos para torná-lo completamente à prova de balas, adicionar mais exemplos e fazer uma boa apresentação. https://github.com/AlexeyFrolov/slt . Por outro lado, está bem testado e estou usando-o em meus projetos de produção de forma iterativa. Mentes inteligentes são bem-vindas para contribuir.

Olá a todos,

É engraçado que eu não tropecei neste tópico antes, pois em nossa empresa estávamos enfrentando os mesmos problemas abordados por esta proposta há meio ano.
Começamos a usar o React para um projeto de grande escala (pense em um editor com a complexidade do Microsoft Visio, com dados muito cíclicos). A reação de baunilha não acompanha nossas demandas de desempenho,
e o fluxo foi um pouco inviável para nós devido à sua grande quantidade de clichê e propensão a erros de todas as assinaturas. Então descobrimos que também precisávamos de estruturas de dados observáveis.

Como não encontramos nada disponível que estivesse pronto para uso, construímos nossa própria lib observables, baseada nos princípios dos knockout observables (especialmente: assinaturas automáticas).
Isso se encaixa muito bem no ciclo de vida atual dos componentes do React, e não precisamos de hacks estranhos ou mesmo callbacks de renderização filho (o ObserverMixin usado abaixo é de cerca de 10 loc).
Isso melhorou muito nosso DX e funcionou tão tremendamente bem para nossa equipe, que decidimos publicá-lo como código aberto . Nesse meio tempo, é bastante comprovado em batalha (fornece um polyfill de matriz observável ES7, por exemplo) e altamente otimizado.
Aqui está um exemplo de timer curto, (também disponível como JSFiddle ), IMHO o DX não poderia ser muito melhor... :relieved:

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 obter mais detalhes sobre essa abordagem, consulte este blog . BTW, vou garantir que um decorador e/ou contêiner seja adicionado à lib , para aqueles que usam classes ES6.

Infelizmente, eu não vi este tópico antes do react-europe, caso contrário teríamos uma boa oportunidade para discutir React & observáveis. Mas muito obrigado pelas palestras inspiradoras! :+1: Adorei especialmente as abstrações do GraphQL e o trabalho de pensamento por trás do Redux :)

@mweststrate Sinto que a comunidade acabará precisando escolher entre as soluções "Observáveis" e "Dados imutáveis". Talvez precisemos misturar de alguma forma para ter os pontos fortes de ambas as abordagens em uma solução (https://github.com/AlexeyFrolov/slt/issues/4). Na minha solução, implementei a abordagem "Dados imutáveis" com foco na consistência do estado em qualquer ponto do tempo. Estou planejando dar suporte a Observables e Generators também. Este é um exemplo de regras usadas para buscar dados "derivados" ou "suplementos" (como corpo de página, recursos, recomendações, comentários, curtidas) e manter a consistência do estado do aplicativo.

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

É um exemplo complexo do mundo real (meu código de produção) que mostra como lidar com redirecionamentos de API com um cabeçalho Location.

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

No resto, é a mesma abordagem que a sua, exceto que não há ligação direta ao React (não é necessário no meu caso). Acredito que precisamos unir forças de alguma forma para arquivar o objetivo comum.

Eu acho que é uma má ideia porque haverá muitos casos extremos difíceis de considerar.

A partir dos comentários acima, posso listar possíveis escopos que precisam ser pensados ​​pelo usuário final cada vez que ele deseja criar um componente "dataful":

  • Renderização do lado do servidor
  • Inicialização de .state e .data
  • .context , .props , .state e .observe emaranhamento
  • Renderização assíncrona

Acho que essa proposta levará a componentes propensos a erros, instáveis ​​e difíceis de depurar.

Estou de acordo com os ganchos de ciclo de vida propostos por @glenjamin connect e disconnect .

Peço desculpas se isso é off-topic (não posso dizer). Mas aqui está um exemplo de por que acho que expor a API para interagir com a árvore de componentes seria uma maneira interessante de abordar esse problema: https://github.com/kevinrobinson/redux/blob/feature/loggit-todomvc/examples/loggit -todomvc/loggit/renderers/precompute_react_renderer.js#L72

Esse método é meu hacker para andar na árvore, então minha pergunta é como ter uma API pública para fazer esse tipo de coisa permitiria a inovação aqui no "carregamento de dados lateral", que eu acho que se resume a "um renderizador que não t trabalhe de cima para baixo": https://github.com/kevinrobinson/redux/blob/feature/loggit-todomvc/examples/loggit-todomvc/loggit/react_interpreter.js#L8

Mas aqui está um exemplo de por que acho que expor a API para interagir com a árvore de componentes seria uma maneira interessante de abordar esse problema.

Acredito que @swannodette falou sobre isso na ReactConf. Gostaria de saber se Om Next faz isso?

Sim, ele sugeriu o mesmo tipo de coisa na primeira ReactConf. Eu não vi o último Om.next ou ouvi o EuroClojure falar, mas anteriormente Om usava sua própria estrutura que os usuários tinham que construir para fazer isso.

Esse método é meu hacker para andar na árvore, então minha pergunta é como ter uma API pública para fazer esse tipo de coisa permitiria a inovação aqui no "carregamento de dados lateral", que eu acho que se resume a "um renderizador que não t trabalho de cima para baixo"

Eu acho que isso é aproximadamente equivalente à renderização superficial encontrada nos utilitários de teste - eu mencionei fazer disso uma API de primeira classe como um par de renderização DOM & String para @sebmarkbage no ReactEurope - as alterações de 0,14 parecem abrir o caminho para isso.

A reação inicial é que pode ser um nível um pouco baixo - mas de uma maneira semelhante ao material extensível da Web, acho que isso tornaria mais fácil experimentar no espaço do usuário.

Concordo, se tivermos uma maneira de renderizar para obter a árvore, podemos percorrê-la e encontrar todas as necessidades de dados e transmiti-las.

Obter acesso a toda a árvore virtual do DOM é um recurso incrivelmente poderoso e útil ao qual eu adoraria ter acesso, mesmo que isso seja tratado como um problema completamente separado.
Eu acho que faz muito sentido considerando o caminho que o React está tomando com 0.14 em diante.

Dada a complexidade dos observáveis, sugiro humildemente aos senhores neste tópico que analisem a implementação de dados reativos do Meteor: https://github.com/meteor/meteor/wiki/Tracker-Manual. A reconciliação com o React leva literalmente 3-5 linhas de código no método componentWillMount , algo como:

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

Não sei se o Tracker (que é facilmente extraído como uma biblioteca separada) evita a necessidade de suporte observável no React, mas com certeza parece assim.

O MOBservable segue um padrão muito semelhante para atualizar um componente lateralmente uma vez que alguns valores observáveis ​​são alterados, então parece que os métodos + decoradores atuais do ciclo de vida oferecem flexibilidade suficiente para que bibliotecas de terceiros expressem esses tipos de padrões e imho um terceiro conceito de fonte de dados só iria complicar as coisas.

    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 Concordo, é uma leitura muito boa, obrigado por encontrar isso! É efetivamente o que https://github.com/facebook/react/pull/3920 está sugerindo.

Estamos indecisos sobre qual proposta é melhor. Tendo jogado com ambos, estou convencido de que a programação reativa (como você linkou; https://github.com/meteor/meteor/wiki/Tracker-Manual) é o caminho a seguir, mas não chegamos a um consenso e ainda estamos tentando descobrir o que faz mais sentido, então o feedback é bem-vindo.

@Mitranim @jimfb Sou um grande fã do Meteor há vários anos. Tracker é muito legal e muito fascinante. Criei uma apresentação demonstrando como funciona uma versão bem simples do tracker:

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

E até criei uma biblioteca de streams observáveis ​​com o Tracker:

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

Mas como eu fiz a transição completa para usar o React em vez do Blaze, percebi que o Tracker é muito complicado e às vezes um simples método de publicação/assinatura/onChange é 100x mais fácil.

Além disso, para todos os fãs de programação funcional, o Tracker vem com alguns efeitos colaterais que fazem sentido 99% das vezes, mas às vezes eles pegam você. Veja como ficaria em um 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()

Meu ponto sobre os efeitos colaterais é que c.stop() interromperá a assinatura e a execução automática interna também.

@ccorcos Sim, em https://github.com/facebook/react/pull/3920 todos os efeitos colaterais seriam completamente internos ao React. Na verdade, o núcleo do React poderia implementá-los de uma maneira completamente imutável/funcional que não realizaria nenhuma mutação. Mas isso é um detalhe de implementação, já que os efeitos colaterais não seriam visíveis externamente de qualquer maneira.

Interessante... Então por que não usar callbacks onChange? Achei os mais compatíveis. Esteja você usando Meteor (Tracker), RxJS, Highland.js, etc., você sempre pode integrá-los trivialmente com um retorno de chamada de evento. E o mesmo com um componente React de alta ordem.

O que eu gosto no Redux é que ele mantém essa lógica fora do framework e mantém os componentes do React como funções puras.

@ccorcos O principal problema é que quando uma instância de componente é destruída, todas as "assinaturas" precisam ser limpas. Os autores de componentes geralmente se esquecem dessa limpeza, criando assim um vazamento de memória. Queremos que seja automático, o que torna mais fácil escrever (menos clichê) e menos propenso a erros (a limpeza é automática).

@jimfb Exatamente. O cancelamento automático de assinatura é uma das coisas realmente interessantes sobre a abordagem do Tracker. Cada chamada para a fonte de dados reativa restabelece a assinatura e, uma vez que o componente para de solicitar dados, não há nada para limpar!

Sim, mas não é realmente "automaticamente" cancelado. Você tem que chamar c.stop() no componente irá desmontar. Você pode abstrair isso com um mixin.

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

Mas isso realmente não é diferente de qualquer outra API. Você pode criar um método embutido que automaticamente cancela a inscrição para você ao desmontar, etc. Olha, eu sou um grande fã do Meteor. Então eu não quero falar com você sobre isso. É só que, às vezes, acho muito difícil explicar essas coisas para alguém que não está familiarizado com isso. Enquanto isso, usar ouvintes/emissores de eventos simples é muito mais simples de entender e implementar, e eles tendem a ser muito compatíveis com qualquer sistema de reatividade que você queira usar...

@ccorcos Podemos estar falando de mecanismos diferentes. Da última vez que verifiquei, o Tracker não tinha assinaturas permanentes; cada função que estabelece uma dependência em uma fonte de dados reativa (ao acessá-la) é executada novamente _uma vez_ quando muda, e esse é o fim da assinatura. Se ele acessar a fonte de dados novamente, isso restabelece a "assinatura" para mais uma alteração. E assim por diante.

@Mitranim está correto, a semântica de #3920 é mais "automática" do que Meteor (o cancelamento de assinatura é realmente automático) e muito mais simples, pois há literalmente zero área de superfície da API no caso de uso comum.

@ccorcos @Mitranim Para uma biblioteca inspirada no Tracker / Vue.js pronta para usar, você pode tentar Mobservable , ele observa todos os dados acessados ​​durante o _render_ e descarta todas as assinaturas na desmontagem (até então as assinaturas são mantidas vivas). Nós o aplicamos com sucesso em projetos muito grandes até agora na Mendix. É bastante discreto para seus dados, além de decorar objetos existentes em vez de fornecer seus próprios objetos de modelo.

Da última vez que verifiquei, o Tracker não tinha assinaturas permanentes

As assinaturas do

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

Agora há algumas coisas interessantes com Tracker. Veja isso.

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

O último exemplo não é muito útil. Até que façamos algo assim.

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 função que estabelece uma dependência em uma fonte de dados reativa (acessando-a) é executada novamente uma vez quando muda, e esse é o fim da assinatura.

A assinatura dura até que a execução automática seja executada novamente (uma dependência reativa alterada) ou a computação em que ela vive pare. Ambos chamam o hook computation.onInvalidate.

Aqui está uma versão super elaborada que conseguiu exatamente a mesma coisa. Espero que ajude você a entender como o Tracker funciona. E talvez você também veja como isso pode ficar confuso.

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 vejo a fonte da confusão agora; era o meu vocabulário. Ao falar sobre assinaturas, não quis dizer _assinaturas do Meteoro_. Eles determinam quais dados são enviados do servidor para o cliente, mas não são relevantes para as atualizações da camada de visualização, que é o assunto desta discussão. Ao dizer _subscription_, eu estava traçando um paralelo entre os ouvintes de eventos tradicionais e a capacidade do Tracker de executar novamente uma função que depende de uma fonte de dados reativa. No caso de componentes React, isso seria um método de componente que busca dados e então chama setState ou forceUpdate .

@mweststrate Obrigado pelo exemplo, parece interessante.

Ah sim. Então eles têm uma maneira inteligente de fazer isso.

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

A assinatura será refeita a cada vez, sem problemas. sub.ready e Messages.find.fetch são ambos "reativos" e acionarão a execução automática para reexecutar sempre que forem alterados. O legal do Tracker é quando você começa a esconder os autoruns e só tem na documentação que determinada função está dentro de um "contexto reativo"

você poderia jogar isso em um mixin

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

E então você fica com essa função magicamente reativa que simplesmente funciona!

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

Tracker é bem legal assim...

@ccorcos Acontece que eu estava um pouco errado sobre unsubs automáticos ao usar eventos no estilo Tracker. Você ainda precisa parar o ouvinte em componentWillUnmount , caso contrário, ele continuará alcançando fontes de dados e chamando setState (a menos que você verifique com isMounted() que agora está obsoleto).

Em uma nota lateral, você pode criar decoradores elegantes para tornar os métodos de componentes reativos. Aqui estão alguns exemplos: [1] , [2] . Se parece com isso:

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

  /* ... */
}

@Mitranim muito legal - eu prefiro funções de alta ordem. ;)

@sebmarkbage @jimfb Acompanho este tópico e o tópico alt (#3858) há alguns meses e estou curioso para saber se a equipe principal chegou a algum consenso sobre esse problema, ou pelo menos uma direção geral.

@oztune Sem atualizações; estamos focados em outras prioridades. Postaremos em um dos tópicos quando houver uma atualização sobre este tópico.

Pessoal, criei uma API universal para compor containers. Verifique react-komposer . Que isso, poderíamos criar contêineres com uma função de ordem 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'));

Aqui está a versão ao vivo: https://jsfiddle.net/arunoda/jxse2yw8/

Também temos algumas maneiras fáceis de compor contêineres com Promises, Rx.js Observables e With Meteor's Tracker .

Confira também meu artigo sobre isso: Vamos compor alguns contêineres React

@arunoda Acabamos fazendo algo muito parecido. Uma coisa que eu estou querendo saber é como você impede que composerFunction seja chamado em cada mudança de prop?

@oztune Na verdade, agora ele será executado novamente. Usamos este Lokka e Meteor. Ambos têm caches locais e não atingem o servidor mesmo quando chamamos o composerFunction várias vezes.

Mas acho que poderíamos fazer algo assim:

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

Alguma ideia?

@arunoda Foi o que tentamos também, mas cria uma certa desconexão entre a ação e suas dependências. Agora fazemos algo semelhante ao react-async, onde, em vez de executar imediatamente a ação dada, composerFunction retornaria uma função e uma chave. Se a chave for diferente da chave anterior retornada por composerFunction, a nova função será executada. Não tenho certeza se isso é uma tangente deste tópico do github, então ficaria feliz em continuar no Twitter (mesmo nome de usuário).

@oztune Criei uma nova edição do GH e vamos continuar nosso bate-papo por lá. Muito melhor que o twitter, eu acho.

Por que não deixar o JSX entender e renderizar Observables diretamente para que eu possa passar Observable em props, por exemplo this.props.todo$ e incorporá-los no JSX. Então eu não precisaria de nenhuma API, o resto é gerenciado fora do React e o HoC é usado para preencher observáveis. Não importa se as props contêm dados simples ou observáveis, portanto, nenhum this.data especial é necessário.

{this.props.todo$

Além disso, o React render pode renderizar Oservable[JSX], o que permitiria o design descrito nos links sem biblioteca adicional.

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

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

Olá,
Por enquanto, poderíamos usar componentes sem estado com fluxos rxjs facilmente.
Eu não entendo a necessidade de outra API.
Eu escrevi um exemplo - uma placa que você pode mover o mouse sobre ela e quando chega a 26 ela muda para reiniciar.
Eu adoraria ouvir o que você pensa sobre esta forma.
Aqui está o código:
https://jsfiddle.net/a6ehwonv/74/

@giltig : Também estou aprendendo assim ultimamente e gosto. Junto com Cycle.js.

Eu gostaria de poder ouvir manipuladores de eventos definidos em componentes de alguma forma facilmente sem ter que passar Assuntos para ponte. Se bem entendi, é praticamente o contrário do que é sugerido aqui. Alternativamente, se pudéssemos observar o React vdom para seus eventos sintéticos, talvez "refs" pudesse ser usado para observar para evitar ter tags CSS apenas para observar.

RxJs adicionais podem suportar objeto em combineLatest para que possamos usar componentes funcionais React diretamente para criar componentes maiores.

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

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

Oi,
A razão pela qual não usamos Cycle ou qualquer outro framework é que eu prefiro bibliotecas ao invés de frameworks (mais controle para o desenvolvedor) e também quero aproveitar o poder da comunidade que o React tem.
Tantos motores de renderização agora são desenvolvidos para React e é uma pena não usá-lo (ReactNative, ReactDom, ReactThree etc.). É bem simples usar apenas Rxjs e reagir conforme o exemplo que mostrei acima.

Seria mais fácil pensar se os componentes de reação pudessem aceitar pojos enquanto observáveis ​​como adereços. Por enquanto não é possível então o que mostrei acima é a forma que escolhemos.

BTW o que você fez com MyFancyReactComponent é possível e nós realmente fizemos isso também em alguns casos, embora você também possa escrever o jsx diretamente.

Em relação aos assuntos - acho que é uma maneira válida porque eventualmente no componente React estou apenas usando uma função de manipulador que pode ser qualquer coisa. Eu escolho implementá-lo com assunto dentro que recebe eventos, mas outra pessoa pode escolher de outra forma - é flexível.

Seria mais fácil pensar se os componentes de reação pudessem aceitar pojos, desde que observáveis ​​​​como adereços. Por enquanto não é possível então o que mostrei acima é a forma que escolhemos.

adereços observáveis ​​não têm sentido a longo prazo. Não tem o menor sentido nesse contexto, na verdade..

Parece-me que acabamos com um modelo diferente com Suspense (cache + contexto). O próprio cache pode obter suporte para assinaturas. Você pode acompanhar o trabalho restante do Suspense em https://github.com/facebook/react/issues/13206.

Também fornecemos um pacote de assinatura para casos mais isolados.

Esta página foi útil?
0 / 5 - 0 avaliações