React: Реализовать боковую загрузку данных

Созданный на 13 мар. 2015  ·  136Комментарии  ·  Источник: facebook/react

Это первоклассный API для боковой загрузки данных без сохранения состояния (хотя и потенциально запомненных) из глобального хранилища/сети/ресурса, потенциально использующих реквизиты/состояние в качестве входных данных.

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

}

наблюдать() выполняется после componentWillMount/componentWillUpdate, но до рендеринга.

Для каждого ключа/значения в записи. Подпишитесь на Observable в значении.

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

Мы разрешаем синхронный вызов onNext из подписки. Если да, то устанавливаем:

this.data[key] = nextValue;

В противном случае мы оставляем его неопределенным для начального рендеринга. (Может быть, мы установили его в ноль?)

Затем рендеринг продолжается как обычно.

Каждый раз, когда вызывается onNext, мы планируем новый «this.data[key]», который эффективно запускает принудительное обновление этого компонента. Если это единственное изменение, то наблюдение не выполняется повторно (componentWillUpdate -> render -> componentDidUpdate).

Если реквизиты/состояние изменились (т. е. обновление из reciveProps или setState), тоObserv() выполняется повторно (во время согласования).

В этот момент мы перебираем новую запись и подписываемся на все новые Observables.

После этого отпишитесь от предыдущих Observables.

subscription.dispose();

Этот порядок важен, поскольку он позволяет поставщику данных выполнять подсчет ссылок в своем кеше. Т.е. я могу кэшировать данные до тех пор, пока их никто не слушает. Если я немедленно отпишусь, то счетчик ссылок упадет до нуля, прежде чем я снова подпишусь на те же данные.

Когда компонент размонтирован, мы автоматически отписываемся от всех активных подписок.

Если новая подписка не вызвала onNext сразу, мы будем продолжать использовать предыдущее значение.

Поэтому, если мой this.props.url из моего примера изменится, и я подпишусь на новый URL-адрес, myContent будет продолжать показывать содержимое предыдущего URL-адреса до тех пор, пока следующий URL-адрес не будет полностью загружен.

Имеет ту же семантику, что и тег <img /> . Мы видели, что, хотя это может сбивать с толку и приводить к несоответствиям, это довольно разумное значение по умолчанию, и проще заставить его показывать счетчик, чем было бы иметь противоположное значение по умолчанию.

Лучшей практикой может быть немедленная отправка «нулевого» значения, если у вас нет кэшированных данных. Другой альтернативой является то, что Observable предоставляет как URL-адрес (или идентификатор), так и содержимое в результате.

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

}

Мы должны использовать контракт RxJS Observable, поскольку он более распространен и допускает синхронное выполнение, но как только предложение

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

Мы можем добавить дополнительные крючки жизненного цикла, которые реагируют на эти события, если это необходимо.

Примечание. Эта концепция позволяет боковым данным вести себя как «поведение» — точно так же, как свойства. Это означает, что нам не нужно перегружать состояние понятия для этих вещей. Это позволяет выполнять такие оптимизации, как удаление данных только для повторной подписки позже. Это восстановимо.

Component API Big Picture

Самый полезный комментарий

Если кто-то хочет поиграть с этим видом API, я сделал действительно тупой полифилл для observe в качестве компонента более высокого порядка:

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

Использование:

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

Все 136 Комментарий

undefined , вероятно, является самым безопасным значением для присвоения data до тех пор, пока наблюдаемое не предоставит свое первое значение через onNext . Например, в Relay мы присваиваем разные значения null (данные не существуют) и undefined (еще не извлечены), поэтому идеальным значением данных по умолчанию будет undefined . Альтернативой является предоставление нового метода, например, getInitialData , но я подозреваю, что это не нужно/излишне.

Это довольно интересно, однако с точки зрения статической типизации я не очень доволен системой ключ/значение, их тип почти невозможно выразить.
Почему бы observe вернуть один наблюдаемый объект и не установить/объединить значение, разрешенное в 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>;
  }

}

И для случая множественной выборки что-то вроде:

class Foo {

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

  render() {
    ..
  }

}

Это, возможно, немного более многословно, но позволяет иметь хороший статический тип:

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

Также вместо повторного выполнения observe при изменении реквизита/состояния мы можем получить доступ к «состоянию» реквизита как к наблюдаемому:

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

Причина в том, что мы не хотим требовать использования комбинаторов и понимания RxJS, чтобы иметь возможность подписываться на (несколько) Observables. Объединение двух Observable таким образом довольно запутанно. На самом деле, по крайней мере для наших источников данных, мы, вероятно, реализуем API подписки, но даже не включим комбинаторы в прототип Observables. Это не обязательно, но вы можете использовать комбинаторы, если вам это нужно.

Однако для подписки на простой магазин Flux в этом нет необходимости.

Я думаю, что Flow, вероятно, сможет обрабатывать этот статический тип с помощью ограничений, но я уточню у этих ребят, чтобы убедиться. Я думаю, что достаточно будет ввести свойство data и тогда можно будет подразумевать тип наблюдения.

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

Это изменение не связано с использованием Observables как способа описания состояния приложения. Это может быть реализовано поверх этого, как вы делали раньше. Это явно не о состоянии приложения, поскольку этот метод является идемпотентным. Платформа может свободно отписываться и повторно подписываться по мере необходимости.

копия @ericvicenti

По крайней мере, в случае машинописного текста не было бы возможности ограничить возвращаемый тип observe на основе типа data , по крайней мере, до чего-то вроде https://github.com/ Реализован

Я бы хотел использовать это для следующей версии React DnD, но очевидно, что для этого нужно дождаться React 0.14.
Интересно, могу ли я на данный момент «полифилить» это с помощью компонента более высокого порядка, который устанавливает this.data $ для экземпляра ref .. Хотя это может быть слишком безумно.

Можно ли будет соблюдать Promises? Затем можно было бы использовать дерево Promises для разрешения данных для всего дерева компонентов до первого рендеринга! Это было бы очень полезно для React на стороне сервера.

Каковы преимущества создания первоклассного API? По сути, это может быть достигнуто с использованием «компонента более высокого порядка».

Каковы преимущества создания первоклассного API? По сути, это может быть достигнуто с использованием «компонента более высокого порядка».

Оборачивать 5 HOC, чтобы получить 5 подписок, немного громоздко и сложнее для понимания новичками. Понимание componentWillReceiveProps также нетривиально. Это исправляет оба.

Я, например, приветствую наших новых наблюдаемых повелителей.

Интересно, может ли это помочь приблизить https://github.com/chenglou/react-state-stream к ванильному API React?

Разве это не займет всего один HOC? В примере в вашем посте на Medium вы перебираете stores и подписываетесь на каждый.

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

@aaronshaf Конечно, зависит от варианта использования. Иногда это разного рода государственные источники, а не просто «несколько магазинов». Но я не могу сказать от имени команды React, давайте послушаем, что говорит @sebmarkbage .

Хотел бы какой-нибудь полифилл, чтобы поиграть с этим сейчас. Я еще не до конца понял идею. Каков механизм работы с будущими обновлениями. Я потрачу еще немного времени, чтобы понять это. Я думаю, что это должно быть выполнимо с помощью простого миксина.

( @vjeux сказал мне, что я должен вмешаться! так что я здесь.)

Я не хочу продвигать свою собственную работу, но я думаю, что этот хук очень похож на хук getNexusBindings в React Nexus . Вы объявляете хранилища данных на уровне компонентов с помощью хука жизненного цикла (который может зависеть от свойств).

API выглядит так:

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

Привязка применяется/обновляется в течение componentDidMount и componentWillReceiveProps . В последнем случае следующие привязки отличаются от предыдущих привязок; удаленные привязки отписываются, добавленные привязки подписываются. Базовый механизм выборки/обновления описан в реализации Nexus Flux . По сути, с одним и тем же API вы можете либо подписаться на локальные данные (традиционные локальные хранилища), либо на удаленные данные (извлекать с помощью GET и получать исправления через Websockets/polyfill). На самом деле вы можете подписаться на данные из другого окна (используя postWindow) или WebWorker/ServiceWorker, но я до сих пор не нашел действительно полезного варианта использования для этого.

Короче говоря, вы синхронно описываете отложенные данные на уровне компонентов, используя абстракцию Flux, а хуки гарантируют, что ваши зависимости автоматически подписываются, внедряются при обновлениях и отписываются.

Но у него также есть приятная особенность: те же самые функции жизненного цикла используются для выполнения предварительной выборки данных во время рендеринга на стороне сервера. По сути, начиная с корня и рекурсивно оттуда, React Nexus предварительно выбирает привязки, визуализирует компонент и продолжает работу с потомками, пока не будут визуализированы все компоненты.

@aaronshaf @gaearon Преимущество того, чтобы сделать это первоклассным, заключается в следующем:

1) Он не разъедает пространство имен реквизита. Например, компонент более высокого порядка не должен запрашивать имя типа data из вашего объекта реквизита, который не может использоваться ни для чего другого. Объединение в цепочку нескольких компонентов более высокого порядка требует все больше имен, и теперь вам нужно найти способ сохранить эти имена уникальными. Что, если вы сочиняете что-то, что, возможно, уже было сочинено, и теперь у вас конфликт имен?

Кроме того, я думаю, что лучшая практика для компонентов более высокого порядка должна состоять в том, чтобы избегать изменения контракта обернутого компонента. Т.е. концептуально это должны быть одни и те же реквизиты на входе и выходе. В противном случае это сбивает с толку при использовании и отладке, когда потребитель предоставляет совершенно другой набор реквизитов, чем полученный.

2) Нам не нужно использовать state для хранения последнего значения. Концепция data аналогична концепции props в том смысле, что это просто запоминание. Мы можем выбросить его в любой момент, если нам нужно восстановить память. Например, в бесконечной прокрутке мы можем автоматически очищать невидимые поддеревья.

@RickWong Да, было бы довольно тривиально поддерживать Promises, поскольку они являются подмножеством Observables. Мы, вероятно, должны сделать это, чтобы быть равнодушными. Тем не менее, я бы все же, вероятно, рекомендовал бы не использовать их. Я считаю, что они уступают Observables по следующим причинам:

A) Они не могут быть автоматически отменены фреймворком. Лучшее, что мы можем сделать, это игнорировать запоздалое решение. Тем временем Promise удерживает потенциально дорогие ресурсы. Легко попасть в неприятную ситуацию подписки/отмены/подписки/отмены... длительных таймеров/сетевых запросов, и если вы используете промисы, они не будут отменены в корне, и поэтому вам нужно просто ждать ресурсы для завершения или тайм-аут. Это может отрицательно сказаться на производительности на больших страницах рабочего стола (например, facebook.com) или в критически важных приложениях с задержкой в ​​средах с ограниченным объемом памяти (например, в среде с ограниченным объемом памяти (например, в среде с реактивным кодом).

Б) Вы ограничиваете себя получением только одного значения. Если эти данные со временем изменяются, вы не можете сделать свои представления недействительными, и в конечном итоге вы окажетесь в несогласованном состоянии. Это не реактивно. Однако для одного рендеринга на стороне сервера, который может подойти, на клиенте вы должны в идеале проектировать его таким образом, чтобы вы могли передавать новые данные в пользовательский интерфейс и автоматически обновлять, чтобы избежать устаревших данных.

Поэтому я считаю, что Observable — это лучший API для сборки, поскольку он не блокирует вас, чтобы исправить эти проблемы, если вам это нужно.

@elierotenberg Спасибо за участие! Действительно очень похоже. Такие же льготы. Вы видите какие-либо ограничения в моем предложении? Т.е. не хватает чего-то, что есть в React Nexus, чего нельзя было бы построить поверх этого? Было бы неплохо, если бы мы не закрывались от важных вариантов использования. :)

С точки зрения обработки сервера важно, чтобы мы могли отложить окончательный renderToString до тех пор, пока Observable/Promise не будет разрешен с данными, которые могут быть получены асинхронно. В противном случае нам все равно придется выполнять все асинхронные выборки данных за пределами React, не зная, какие компоненты будут на странице.

Я считаю, что react-nexus позволяет выполнять асинхронную загрузку внутри компонента перед продолжением дерева рендеринга.

Да, react-nexus явно разделяет:
1) объявление привязки как getNexusBindings (это синхронный метод жизненного цикла без побочных эффектов, аналогичный рендерингу — на самом деле раньше это было имя renderDependencies, но я подумал, что это сбивает с толку),
2) привязка подписки/обновления как applyNexusBindings (которая является синхронной и различает предыдущие привязки нексуса, чтобы определить, какие новые привязки должны быть подписаны, а какие должны быть отменены)
3) предварительная выборка привязки как prefetchNexusBindings (которая является асинхронной и разрешается, когда «начальное» (что бы это ни значило) значение было готово)

ReactNexus.prefetchApp(ReactElement) возвращает Promise(String html, Object serializableData) . Этот хук имитирует построение дерева React (используя instantiateReactComponent ) и рекурсивно создает/предварительно выбирает/рендерит компоненты. Когда все дерево компонентов «готово», оно, наконец, вызывает React.renderToString , зная, что все данные готовы (по модулю ошибок). После разрешения значение этого промиса может быть введено в ответ сервера. На клиенте обычный жизненный цикл React.render() работает как обычно.

Если кто-то хочет поиграть с этим видом API, я сделал действительно тупой полифилл для observe в качестве компонента более высокого порядка:

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

Использование:

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

Является ли Observable конкретной, согласованной вещью, помимо реализаций библиотек? Что такое контракт, достаточно ли он прост для реализации без использования бекона или Rxjs? Каким бы хорошим ни был первоклассный API для загрузки данных, для React кажется странным добавлять API, основанный на примитиве с неуказанными/очень начальными спецификациями, учитывая неуклонное движение React к простому js. Привязывает ли что-то подобное к конкретной реализации пользовательской земли?

кстати, почему не Streams? У меня нет лошади на скачках, но я честно задаюсь вопросом; уже проделана работа над веб-потоками, и конечно же есть узел

Еще два для рассмотрения: https://github.com/cujojs/most и https://github.com/caolan/highland .

@jquense Ведется активная работа над предложением добавить Observable в ECMAScript 7 (+), поэтому в идеале это должен быть простой JS. https://github.com/jhusain/asyncgenerator (в настоящее время устарело.)

Мы бы не стали зависеть от RxJS. API тривиально реализовать самостоятельно без использования RxJS. RxJS наиболее близок к активному предложению ECMAScript.

Most.js тоже кажется выполнимым.

API Bacon.js кажется трудным для использования без зависимости от Bacon из-за использования типов Bacon.Event для разделения значений.

API-интерфейсы Stream слишком высокоуровневы и далеки от этого варианта использования.

Есть ли какая-то опция «ожидание перед рендерингом»? Я имею в виду, что на клиенте нет необходимости ждать всех Observables перед рендерингом, но на сервере вы хотите дождаться их разрешения, чтобы рендеринг () каждого компонента был полный , а не частичный.

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

Во всех моих исследованиях я обнаружил, что это самый важный хук жизненного цикла, отсутствующий в серверной части React.

После этого обсуждения я попытался подвести итог тому, что делает React Nexus, в следующем посте:

Изоморфные приложения, сделанные правильно с React Nexus

Вот схема основной процедуры предварительной выборки:

React Nexus

Мы бы не стали зависеть от RxJS. API тривиально реализовать самостоятельно без использования RxJS. RxJS наиболее близок к активному предложению ECMAScript.

:+1: это очень беспокоит меня, думая, скажем, о обещаниях, когда реализация ваших собственных чрезвычайно чревата, если вы не знаете, что делаете. Я думаю, что в противном случае вы получите неявное требование к конкретной библиотеке в экосистеме. Попутно... одна из приятных вещей в мире промисов - это набор тестов A+, так что даже между библиотеками была по крайней мере гарантия общей функциональности .then , которая была полезна для взаимодействия с промисами до того, как они были стандартизированный.

это очень беспокоит меня, думая, скажем, о обещаниях, когда реализация ваших собственных чрезвычайно чревата, если вы не знаете, что делаете. Я думаю, что в противном случае вы получите неявное требование к конкретной библиотеке в экосистеме.

Полностью согласен. К счастью, observables имеют очень простой контракт и даже не имеют встроенных методов, таких как then так что в некотором смысле они даже проще, чем promises.

Они могут стать более сложными (и медленными), если комитет будет настаивать на том, чтобы вызов next планировал микрозадачу, такую ​​как Promises.

Это может беспокоить некоторых, многие шаблоны основаны на том факте, что onNext является синхронным в RxJS:/

Я думаю, что общий шаблон хранилища Flux может заключаться в том, чтобы хранить карту наблюдаемых для каждого ключа, чтобы их можно было использовать повторно. Потом почистить их, когда все отпишутся.

Таким образом, вы можете делать такие вещи, как: MyStore.get(this.props.someID) и всегда получать один и тот же Observable.

Таким образом, вы можете делать такие вещи, как: MyStore.get(this.props.someID) и всегда получать один и тот же Observable.

Будет ли смысл использовать this.props.key (ушел, я знаю)? В большинстве случаев вы уже передаете такой уникальный идентификатор с помощью <... key={child.id} .. /> .

Таким образом, вы можете делать такие вещи, как: MyStore.get(this.props.someID) и всегда получать один и тот же Observable.

Это шаблон, который я использую и для React Nexus. Store#observe возвращает запомненный неизменяемый наблюдатель; он очищается (включая соответствующий механизм очистки, специфичный для серверной части, такой как отправка фактического сообщения «отписаться» или что-то еще), когда все подписчики ушли хотя бы на один тик.

@sebmarkbage @gaearon Как наблюдать за работой сервера в v0.14?
Сможет ли он должным образом дождаться разрешения всех наблюдателей перед рендерингом в строку, подобно тому, как это делает react-nexus (но встроенный для реагирования)?

IMO, было бы здорово, если бы компоненты ждали первого наблюдаемого значения, прежде чем быть «готовыми» к рендерингу на сервере.

@gaearon : ИМО, было бы здорово, если бы компоненты ждали первого наблюдаемого значения, прежде чем быть «готовыми» к рендерингу на сервере.

Да, :+1: для асинхронного рендеринга. В то же время react-async от @andreypopp является альтернативой, но для «взлома» React требуется fibers . Было бы здорово, если бы React мог поддерживать асинхронный рендеринг «из коробки».

Мы хотели бы поддерживать асинхронный рендеринг, но это не является частью этой проблемы. л

К сожалению, в 0.14 этого не будет. Необходимо рассмотреть множество различных дизайнов и провести рефакторинг.

Не стесняйтесь создавать и описывать архитектурные изменения внутренних компонентов, необходимые для этого.

У меня была та же мысль, что и у @gaearon в react -streaming-state . Учитывая все потенциальные приложения, кроме боковой загрузки, может ли быть имя лучше, чем data ? Например, observed будет более четко ассоциировать его с методом.

Не хотел сходить с рельсов из-за отказа от велосипедов, но хотел выбросить это.

не могу дождаться наблюдаемых в React. это должно сделать React реактивным, насколько я понимаю

Я экспериментирую с похожей идеей, переписывая react-async , см. README .

Заметное отличие заключается в том, что я ввожу явную наблюдаемую/процессную идентичность для согласования процессов, подобно тому, как React делает это с компонентами key prop и stateful.

Когда id именованного процесса изменяется, React Async останавливает старый экземпляр процесса и запускает новый.

API выглядит так:

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

API-интерфейс процесса теперь синтаксически и по имени следует API-интерфейсу ES6 Promises, но семантически не ожидается, что process.then(onNext, onError) будет вызываться только один раз для каждого процесса в реальном времени. Он создан для наиболее популярного (?) случая получения данных через промисы. Но, честно говоря, теперь я думаю, что мне нужно изменить это, чтобы избежать путаницы.

Возможно, я ошибаюсь, но я думаю, что единственное, что мешает реализовать предложенный (в этом выпуске) API в пользовательской среде, — это отсутствие хука жизненного цикла, который выполняется непосредственно перед рендерингом, как componentWillUpdate но с новым props и state уже установлены на экземпляре.

Одна вещь, которая еще не обсуждалась, — это обработка обратного вызова onError . Если наблюдаемая выдает ошибку, эта информация должна быть каким-то образом доступна компоненту. Поскольку React обрабатывает фактический вызов subscribe(callbacks) , нам потребуется стандартизированный метод для его внедрения в этот объект обратного вызова.

Чтобы обеспечить максимальную гибкость для разработчиков приложений, я вижу два подхода. Во-первых, ошибки помещаются в атрибут верхнего уровня, аналогичный this.data . Это кажется невероятно тяжелым и еще больше съедает пространство имен компонента.
Второй позволит разработчикам определить свой собственный обратный вызов onError как функцию жизненного цикла. Если бы я хотел иметь собственную обработку ошибок для своих наблюдаемых, я мог бы добавить что-то вроде

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

Это похоже на то, что мы сделали в предстоящей итерации Parse+React. Мы создали собственную обработку ошибок для создания нужного нам API. Ошибки добавляются в приватную карту { name => error }, а компоненты имеют общедоступный метод верхнего уровня queryErrors() , который возвращает клон карты, если она не пуста, и null в противном случае (с учетом простого if (this.queryErrors()) .

Конечно, определение новых зарезервированных методов тоже непростое дело; Я не буду притворяться, что это не так. Но нам нужен способ неявно или явно сделать ошибки доступными для компонента при рендеринге.

@andrewimm Идея состоит в том, чтобы https://github.com/facebook/реагировать/issues/2928

Это также должно корректно обрабатывать ошибки при вызове и восстановлении методов. Например, если метод render() выбрасывает. Это значительно больше работы и потребует некоторого времени для правильной реализации, но идея заключалась в том, чтобы таким образом унифицировать обработку ошибок.

Я бы сказал, что это следует оставить за пределами собственно реакции, и 1 или 2 ключевые точки интеграции должны быть скоординированы с такими проектами, как react-async и react-nexus, чтобы это можно было аккуратно сделать поверх собственно React....

Я согласен, кажется, что предложенный способ сделать это лучше, чем
запекание этого в самой структуре

Вторник, 21 апреля 2015 г., 23:38 Родольфо Хансен[email protected]
написал:

Я бы сказал, что это следует оставить за пределами правильного реагирования, а 1 или 2 ключа
точки интеграции должны быть согласованы с такими проектами, как react-async и
react-nexus, чтобы это можно было сделать поверх самого React....


Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/facebook/react/issues/3398#issuecomment-95048028 .

На выходных я создал еще одну реализацию Flux, названную Flexy . В этом проверьте код для магазина. Он предоставляет метод .getObservable , который соответствует наблюдаемому API, даже если он действительно не использует наблюдаемые или другую реактивную среду.

Итак, я бы сказал, что API достаточно легко создать с помощью реальных Observables.

Тем не менее, не судите строго код, он был сделан за выходные для:

  • веселье
  • понимание
  • используя js-csp
  • с помощью API наблюдения

Кстати, такая система и Flux на самом деле делают рендеринг на стороне сервера менее болезненным. Используя систему, аналогичную React-Nexus, мы можем инициализировать хранилища и передавать их в приложение React. Затем мы можем отслеживать хранилища и диспетчер и продолжать повторный рендеринг до тех пор, пока не перестанут выполняться действия (все необходимые данные уже находятся в хранилищах).

Я бы сказал, что это наименьшая точка интеграции для получения новой семантики подписок на данные без сохранения состояния. Какие еще точки интеграции вы могли бы предложить? Не включая асинхронный рендеринг, который является гораздо более сложной проблемой и заслуживает отдельного потока, и этот хук можно использовать с асинхронным рендерингом в любом случае.

Все остальное уже можно реализовать поверх React для каждого компонента. Обратите внимание, что мы не собираемся делать глобальные инъекции плагинов, поскольку это нарушает повторное использование компонентов в среде, поэтому фреймворки, построенные поверх React, должны быть контекстуальны для отдельных компонентов.

Каких крючков нам не хватает?

Привет,

Честно говоря, несмотря на то, что новые хуки упростили бы реализацию, мы, безусловно, можем добиться боковой загрузки данных без них, как мы продемонстрировали с помощью react-async и react-nexus .

Во всяком случае, раскрытие и поддержка поддержки жизненного цикла экземпляров компонентов React за пределами смонтированной иерархии React могут помочь. В react-nexus я использую внутренний instanciateReactComponent и сам вызываю componentWillMount , componentWillUnmount и т. д., и я считаю этот подход ненадежным (что, если instanciateReactComponents зависит от внутренних инвариантов, которые изменятся в следующей версии React?).

Что касается подхода без сохранения состояния, мне кажется, что асинхронная выборка данных _is_ с сохранением состояния и, таким образом, сохранение состояния ожидания/завершения/ошибки в состоянии некоторых компонентов имеет значение. Когда мы используем react-nexus в реальных приложениях, у нас есть компоненты более высокого порядка, которые выполняют выборку данных и внедряют свое состояние выборки в свои дочерние компоненты в качестве реквизита. Таким образом, внутренний компонент «не имеет состояния» (что желательно), а внешний компонент «с состоянием» (что также желательно, например, для отображения счетчика загрузки или заполнителя).

Какие еще точки интеграции вы могли бы предложить?

мне кажется, что @ANDREYPOPP задал правильный вопрос. не единственное, что нам нужно для реализации этого в пользовательской среде, — это хук жизненного цикла перед рендерингом? это похоже на наименьшее минимальное необходимое изменение API, остальное - установка и запуск forceUpdate соответствующим образом, когда вы меняете data на основе любого входного потока/эмиттера/наблюдаемого. Если я не упускаю из виду что-то особенное (вполне возможно)?

Я не буду втягиваться в большую дискуссию здесь.
Но, чтобы ответить @sebmarkbage, я думаю, что один из самых важных крючков, который я хотел бы, - это то, что нужно только тогда, когда не используются настоящие наблюдаемые.

  • Ловушка, которая может предоставить функции, которые могут работать со значениями, переданными наблюдаемым _до_ того, как они будут установлены в данные. С реальными наблюдаемыми это будет просто .map

Если бы ситуация была немного более открытой, я думаю, должны быть хуки, чтобы заменить поведение, специфичное для Observable, на пользовательские хуки. Таким образом, вместо этого мы могли бы использовать эмиттеры событий или каналы CSP.

Мне кажется, что последние несколько комментариев говорят о том, что наименьшая точка расширения на самом деле является «подключением» и «отключением» крючков жизненного цикла, которые упрощают присоединение асинхронных данных к компоненту и управление этими подписками?

Тогда Observables можно было бы довольно тривиально построить на этом (в ядре или за его пределами), но раскрытие этих точек жизненного цикла имеет более широкую привлекательность?

Это разумное резюме?

Я также пока не уверен, что это нужно решать в самом React. Я гораздо более склонен думать, что React должен предоставить все необходимые хуки для создания этой функциональности поверх React.

Но на мгновение предположим, что мы собираемся с observe() . Несколько мыслей:

У нас есть this.props , this.state , this.context и теперь this.data , все как потенциальные источники новых данных в render() . Мне это кажется чрезмерным. Есть ли идея отделить состояние приложения от состояния компонента? Это может прояснить и разделить некоторые проблемы, связанные с состоянием, но мне кажется, что затраты на введение новых входных данных могут не перевесить выгоды. Если мы хотим, чтобы this.state фокусировался исключительно на состоянии компонента, почему бы вместо этого не добавить поля в this.data к this.props или this.context ?

Имя this.data слишком общее. Реквизиты — это данные, состояние — это данные, и любая локальная переменная — это данные. Имя не добавляет смысла и путает существующие значения. Я бы предпочел this.observed или любое другое имя, которое действительно что-то значило бы. Итак, +1 к комментарию @matthewwithanm :

может ли быть лучшее имя, чем data ? Например, observed будет более четко ассоциировать его с методом.

Если мы позволим observe() выполняться на сервере, нам понадобится какой-то хук, который очистит любые утечки памяти, которые могут возникнуть, потому что размонтирование никогда не произойдет.

Если мы снова будем вызывать observe() каждый раз при изменении props или statecontext ?), то observe должны быть оптимизированы для повышения производительности и в идеале нельзя случайно сделать дорогим. Он становится частью горячего пути. Мне нравится это из React Nexus:

следующие привязки отличаются от предыдущих привязок; удаленные привязки отписываются, добавленные привязки подписываются.

Я пришел к выводу, что состояние усложняет компоненты, и я пытался меньше использовать его в своих собственных компонентах и ​​вместо этого поднять его . Вот почему, в дополнение к опасениям, поднятым @fisherwebdev (с которыми я согласен сейчас), я не уверен, что позволить observe зависеть от state — это хорошая идея. Состояние зависит от реквизита, наблюдаемое зависит от состояния _и_ реквизита. Не слишком ли это сложно? Я бы предпочел observe(props) .

Я также начинаю понимать, что наблюдение не должно зависеть от state , в основном потому, что в этом нет необходимости. Как указывает @gaearon , достаточно легко поднять состояние на один уровень выше, что кажется чище после разделения задач. Когда observe потенциально может зависеть от state , логика обработки обновлений внутри компонента становится значительно сложнее; когда это зависит только от props , у вас могут быть обработчики без форка в componentDidMount / componentWillReceiveProps . Меньшее количество кода в критическом пути приводит к более простому циклу рендеринга, а также уменьшает количество непреднамеренных обновлений, которые повторно инициируют подписку.

+1 за наблюдение (реквизит)

Чем меньше мы имеем дело с состоянием, тем лучше ИМО.

Я считаю, что React как библиотека должен быть максимально гибким. Я согласен с тем, что для наблюдения не рекомендуется зависеть от состояния. Да, это может быть сложно. Да, лучше всего не зависеть от состояния.
Но это выбор, который должны сделать пользователи React.
Я бы порекомендовал оставить текущий API без изменений (кроме любых возможных хуков, чтобы добавить больше гибкости, например, заставить его работать не только с наблюдаемыми), и предоставить документацию, объясняющую, что использование состояния в методе наблюдения не рекомендуется.

Я считаю, что все хотят поступать правильно, и что мы хотим, чтобы нас указывали в правильном направлении. Наблюдение за непринятием состояния делает это проще для пользователя, чем случайно найти что-то вроде «это антишаблон» в документации.

РЕДАКТИРОВАТЬ: извините, только что понял, что искал flatMap . Я запутался, потому что думал, что это сгладит массив сообщений, но он работает на более высоком уровне (наблюдаемые сообщения).

Будет ли предлагаемый API обрабатывать случай, когда результат одного поля данных зависит от результата другого? Т.е. вы сопоставляете первый результат и возвращаете наблюдаемое:

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

Я довольно новичок в наблюдаемых в целом, поэтому, возможно, я делаю это совершенно неправильно. В мире обещаний это чрезвычайно просто, так как возврат обещания из then приведет к тому, что последующие then будут основаны на этом обещании.

Я не понимаю комментариев о том, что это не должно зависеть от this.state . Инкапсулированное состояние, безусловно, делает React намного сложнее, но на этом все. Если бы не было инкапсулированного состояния, нам понадобилась бы только мемоизированная библиотека непосредственного режима. Если вы идете ва-банк в «Магазинах», то да, вам не нужно состояние, но это не то, что React предписывает вам делать.

У нас есть несколько шаблонов, которые требуют от вас создания дополнительных оболочек, но для обычного использования вам это не нужно. Помимо хранилищ, ваши наблюдаемые данные всегда зависят от состояния, даже если косвенно. Я не думаю, что это плохая практика, чтобы полагаться на это в наблюдении. Например

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

Даже если бы observe не зависело от state оно все равно зависело бы от props и context . Даже если бы это не зависело от context вас все равно была бы косвенность, которая использует context для рендеринга свойств компонента, который использует его в observe .

observe нужно будет переоценивать каждый раз, когда проход рендеринга завершается, потому что свойства могли измениться. Тем не менее, мы определенно будем различать полученные наблюдаемые и не будем отписываться/переподписываться, если возвращается тот же самый наблюдаемый. Однако мы не можем сравнивать отдельные свойства (кроме как с shouldComponentUpdate), поэтому в идеале вам следует реализовать собственный кеш, используя Map в качестве мощной функции. Таким образом, вы можете вернуть один и тот же наблюдаемый объект нескольким компонентам в дереве. Например, несколько компонентов загружают одного и того же пользователя. Даже если вы этого не сделаете, вы просто воссоздаете Observable и в конечном итоге попадете в нижний кеш. Так или иначе работает согласование React. Это не так уж и медленно.

Этот хук observe не предназначен для того, чтобы сделать React полностью реактивным в том смысле, что состояние фиксируется в Observables. В настоящее время ключевой целью разработки является избежание перехвата состояния в замыканиях и комбинаторах, а вместо этого иметь красивое чистое отдельное дерево состояний, которое можно замораживать и оживлять, а также потенциально совместно использовать для рабочих процессов.

Что подводит меня к моему последнему пункту...

Нам определенно не нужно добавлять это в основную библиотеку. Первоначальный «общедоступный» интерфейс был mountComponent/receiveComponent, и вы могли построить всю систему составных компонентов поверх него. Тем не менее, не многие люди использовали тот факт, что поднять планку абстракции гораздо эффективнее, поскольку теперь мы можем создавать другие вещи, которые разрешены более высокой планкой абстракции. Например, оптимизация компонентов.

Основная цель React — создать контракт между компонентами, чтобы различные абстракции в экосистеме могли сосуществовать. Важной частью этой роли является повышение уровня абстракции для общих концепций, чтобы мы могли включить новые кросс-компонентные функции. Например, сохранение всего состояния поддерева, а затем восстановление поддерева. Или, возможно, включая автоматическое размонтирование на сервере или изменение временных аспектов согласования на сервере.

Также важно предусмотреть несколько батареек в комплекте, чтобы сделать все это вкусным и однородным.

Важно понимать, что микромодуляризация (например, добавление нового хука жизненного цикла) не является чистой победой над ее встраиванием во фреймворк. Это также означает, что больше невозможно рассуждать об общесистемных абстракциях.

Я бы хотел, чтобы что-то подобное было в документах как «философия / цели дизайна / нецели».

Основная цель React — создать контракт между компонентами, чтобы различные абстракции в экосистеме могли сосуществовать. Важной частью этой роли является повышение уровня абстракции для общих концепций, чтобы мы могли включить новые кросс-компонентные функции.

Люблю это. Я согласен с @gaearon , что было бы неплохо, если бы это было в каком-то документе.

Нам, конечно, не нужно добавлять это в основную библиотеку... Однако немногие люди использовали то, что поднять планку абстракции намного мощнее, поскольку теперь мы можем создавать другие вещи, которые разрешены более высокой планкой абстракции. Например, оптимизация компонентов.

Я чувствую сдержанность (по крайней мере, для меня) не в добавлении другого API, а в добавлении того, который зависит от неязыковой (все еще определяемой) конструкции для работы. Это может полностью сработать, но я беспокоюсь о проблемах, с которыми сталкиваются библиотеки обещаний, когда ни одна библиотека обещаний (даже те, которые жалуются на спецификации) не могут доверять друг другу, что приводит к ненужной работе по упаковке и защите, чтобы убедиться, что они разрешаются правильно, что ограничивает возможности оптимизации . Или, что еще хуже, вы застрянете, как jQuery, со сломанной реализацией, которая никогда не изменится.

@jquense Я полностью согласен. Давно хотел добавить этот хук. (Исходный эксперимент: https://github.com/reactjs/react-page/commit/082a049d2a13b14199a13394dfb1cb8362c0768a)

Два года назад возникло сомнение, что до стандартизации еще слишком далеко. Я хотел стандартный протокол, прежде чем мы добавим его в ядро.

Я думаю, что мы подошли к моменту, когда многие фреймворки согласны с необходимостью чего-то вроде Observables, а стандартизация достигает точки, когда был предложен приемлемый API. Я уверен, что в конечном итоге нам нужно будет немного его подправить, но пока работает высокоуровневая архитектура, она должна быть заменяемой и в конечном итоге сходиться.

Я думаю, что с Promises произошло то, что API и история отладки сильно отсутствовали в определенных областях, от которых не страдают Observables. Это более полная история «из коробки», в то время как Promises должен был стандартизировать минимальное незавершенное решение.

Единственная разница во мнениях относительно наблюдаемых, которые я наблюдал (не удержался, извините), - это потенциал Zalgo. Могут ли Observables синхронно передавать значение в ответ на подписку. Некоторые люди кажутся против этого, но, насколько я понимаю, использование React Observables будет зависеть от этого. Вы можете это прокомментировать?

Как правило, я не считаю Zalgo проблемой с Observables, потому что потребитель всегда находится под контролем и может выбрать всегда-асинхронный с чем-то вроде observeOn .

Рад, что наконец-то есть консенсус по этому поводу. Я лично предпочитаю каналы Observables, но если Observables будут добавлены в язык, я согласен, что нет необходимости больше ждать.

Тем не менее, давайте удостоверимся, что мы оставляем API достаточно открытым для работы с ненаблюдаемыми объектами, которые соответствуют базовому API.

Я тоже не считаю Zalgo проблемой. Однако с Observables вы можете использовать планировщик для обеспечения асинхронности, если это необходимо, планировщик по умолчанию теперь является асинхронным, поэтому вы можете использовать его по мере необходимости.

@sebmarkbage Я думаю, что вы решили большую часть моих проблем, и теперь я вижу преимущество добавления этого в структуру. Однако не могли бы вы прокомментировать this.data -- (1) можем ли мы свернуть эти поля в props/context/state или (2) можем ли мы переименовать их?

Слегка байкшед, но в любом случае... проблема Zalgo на самом деле не в том, имеет ли она значение с точки зрения ожиданий от API, а в наблюдаемом взаимодействии и простоте реализации. Отсутствие раннего соглашения о Zalgo поставило мир библиотек Promise в раздражающее положение, когда ему приходится защищаться при работе с Promises из других библиотек. (мой вышеприведенный пункт повторен ниже)

... где ни одна библиотека промисов (даже те, которые жалуются на спецификации) не могут доверять друг другу, что приводит к ненужной работе по упаковке и защите, чтобы убедиться, что они разрешаются правильно

Поскольку ранние промисы не все соответствовали асинхронному разрешению, сейчас мы находимся в положении, когда даже при наличии спецификаций библиотеки не могут предполагать, что thenables заслуживают доверия, убивая потенциальные оптимизации. Это кажется мне особенно актуальным здесь, где React не будет предоставлять реализацию Observable для использования (кто бы это вообще хотел?), и мы, скорее всего, пройдем годы, прежде чем сможем полагаться исключительно на браузер, предоставляемый Observable, так что простая библиотека взаимодействие важно. Кроме того, к точке @gaearon , если React зависит от вызовов синхронизации, и он всегда должен быть асинхронным, что ставит нас в положение, похожее на jquery, с мошеннической реализацией.

Я полностью согласен. Давно хотел добавить этот хук. Два года назад возникло сомнение, что до стандартизации еще слишком далеко. Я хотел стандартный протокол, прежде чем мы добавим его в ядро.

Я рад, что его посещают и об этом думают, это, безусловно, утешительно. :) и в целом я думаю, что раннее принятие промисов стоило минусов, которые я здесь обсуждаю, так что не воспринимайте мою озабоченность как неприязнь или неодобрение, я очень взволнован перспективой первоклассного API для этого и я также вижу, что Observable действительно является хорошим/наиболее разумным выбором для этого.

«Мы должны использовать контракт RxJS Observable, поскольку он более распространен и допускает синхронное выполнение, но как только предложение @jhusain станет более распространенным, мы вместо этого переключимся на этот контракт».

Просто чтобы добавить немного больше контекста. Существует инициатива Reactive Streams (http://www.reactive-streams.org/), призванная обеспечить стандарт для асинхронной обработки потоков с неблокирующим обратным давлением. Сюда входят усилия, направленные на среды выполнения (JVM и JavaScript), а также на сетевые протоколы.

В настоящее время ведущими реализациями являются, например, Akka Streams или RxJava. Я не знаю, соответствуют ли уже RxJ тому же интерфейсу, текущему интерфейсу для подписчикаэто onSubscribe(Subscription s), onNext(T t), onCompleted(), onError(Throwable t).

Не могли бы вы пролить больше света на предложение @jhusain ?

Я не знаю, должен ли React строго соблюдать эту инициативу, потому что, если мне нужно, я, вероятно, могу поместить RxJ (при условии, что он будет соответствовать) между ними и адаптироваться к интерфейсу React и позволить более продвинутым концепциям, таким как противодавление RxJ (хотя я бы предпочитаю не сильно приспосабливаться).

Есть ли какая-то позиция или цель в отношении этой инициативы?

@vladap Я считаю, что это упомянутое предложение от @jhusain

Я прочитал @jhusain и не уверен в мотивации возможного перехода на эту спецификацию в будущем. Есть какое-то конкретное преимущество?

Спецификация Reactive-streams имеет большую поддержку и уже находится в версии 1.0 . Поскольку RxJava уже реализует эту спецификацию, я предполагаю, что RxJs будет следовать (но не проверял).

Этот блог суммирует интерфейсы с некоторыми примерами, использующими потоки Akka.

Я вижу некоторое преимущество в том, чтобы иметь одинаковые интерфейсы на бэкэнде и фронтэнде, в основном потому, что я работаю над обоими. Возможно, это может помочь взаимодействовать между бэкэнд- и фронтенд-группами, но, с другой стороны, я предполагаю, что websocket или sse являются фактическими точками интеграции для потоковой передачи.

Я не могу найти список разработчиков на www.reactive-streams.org прямо сейчас, но в последний раз, когда я проверял, это было:

Бьорн Антонссон — Typesafe Inc.
Гэвин Бирман, Oracle Inc.
Джон Брисбин — Pivotal Software Inc.
Джордж Кэмпбелл – Netflix, Inc.
Бен Кристенсен – Netflix, Inc.
Матиас Дениц – spray.io
Мариус Эриксен – Twitter Inc.
Тим Фокс — Red Hat Inc.
Виктор Кланг — Typesafe Inc.
Доктор Роланд Кун – Typesafe Inc.
Дуг Ли — SUNY Oswego
Стефан Мальдини — Pivotal Software Inc.
Норман Маурер — Red Hat Inc.
Эрик Мейер, Applied Duality Inc.
Тодд Монтгомери — Kaazing Corp.
Патрик Нордволл — Typesafe Inc.
Йоханнес Рудольф – spray.io
Эндре Варга — Typesafe Inc.

Возможно, я зашел слишком далеко, но я считаю, что более широкий контекст может помочь в будущих решениях.

@vladap Из того, что я понимаю и вижу по проблемам github, @jhusain уже работает с ними, так что я думаю, у нас не будет такой большой проблемы.
С точки зрения интерфейса, из того, что я также мог понять в различных проблемах github и другом документе спецификации, наблюдатель, безусловно, будет уважать интерфейс генератора, поэтому что-то вроде:

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

Простая реализация очень простого наблюдаемого с одним методом «подписки», который уважает этот интерфейс, должен быть безопасным для реагирования.

Похоже, просто другое название с той же функциональностью, в этом смысле все в порядке. Я, вероятно, предпочел бы то же имя, что и в спецификации, но, в конце концов, мне все равно, насколько эти методы делают то же самое.

Я не могу оценить отсутствующий эквивалент onSubscribe(). В блоге, о котором я упоминал, автор говорит, что это ключ к контролю обратного давления. Я не знаю, у него есть другие варианты использования. Из этого я предполагаю, что React не заботится о контроле обратного давления или для этого есть другая стратегия. Это сложная вещь, поэтому я понимаю, что это не касается React.

Правильно ли я понимаю, что стратегия примерно такая: либо приложение сложное, и могут возникнуть проблемы с обратным давлением, а затем использовать что-то среднее для его решения, например, RxJS, либо вы подключаете компонент React напрямую к веб-сокету, и вы не у меня нет проблем с обратным давлением, потому что приложение простое и имеет медленные обновления.

Где я могу найти предлагаемые наблюдаемые интерфейсы для будущего ECMAScript? Если они уже есть.

Текущее предложение можно найти здесь:

https://github.com/jhusain/asyncgenerator

ДХ

С 7 мая 2015 года, в 2:32 утра, vladap [email protected] писал:

Где я могу найти предлагаемые наблюдаемые интерфейсы для будущего ECMAScript? Если они уже есть.


Ответьте на это письмо напрямую или просмотрите его на GitHub.

Предложение Reactive Streams Proposal (RSP) идет дальше, чем предложение TC-39, потому что оно вводит Observable, который обрабатывает противодавление. RSP Observable оптимизирован для эффективной отправки потоков по сети с учетом обратного давления. Он частично основан на работе, проделанной в RxJava, которая является очень впечатляющим произведением инженерной мысли (полное раскрытие: она была разработана коллегой из Netflix Беном Кристенсеном).

Основной причиной решения стандартизировать более примитивный тип Observable является осторожность. Более примитивный Observable является двойником контракта Iterable ES2015, что дает нам ценные гарантии того, что этот тип по крайней мере так же гибок, как тип, уже стандартизированный в ES2015. Кроме того, существует множество вариантов использования JS для Observables, которые не требуют обратного давления. В браузере DOM является наиболее распространенным приемником для push-потоков и эффективно действует как буфер. Учитывая, что тип RSP является более сложным, наш подход состоит в том, чтобы сначала стандартизировать более примитивный тип, а затем оставить место для реализации более сложного типа позже. В идеале мы бы подождали, пока это не будет проверено в пользовательской среде.

К вашему сведению, RxJS в настоящее время не планирует внедрять RSP Observable.

ДХ

7 мая 2015 г., в 2:30, [email protected] написал:

Похоже, просто другое название с той же функциональностью, в этом смысле все в порядке. Я, вероятно, предпочел бы то же имя, что и в спецификации, но, в конце концов, мне все равно, насколько эти методы делают то же самое.

Я не могу оценить отсутствующий эквивалент onSubscribe(). В блоге, о котором я упоминал, автор говорит, что это ключ к контролю обратного давления. Я не знаю, у него есть другие варианты использования. Из этого я предполагаю, что React не заботится о контроле обратного давления или для этого есть другая стратегия. Это сложная вещь, поэтому я понимаю, что это не касается React.

Правильно ли я понимаю, что стратегия примерно такая: либо приложение сложное, и могут возникнуть проблемы с обратным давлением, а затем использовать что-то среднее для его решения, например, RxJS, либо вы подключаете компонент React напрямую к веб-сокету, и вы не у меня нет проблем с обратным давлением, потому что приложение простое и имеет медленные обновления.


Ответьте на это письмо напрямую или просмотрите его на GitHub.

Большое спасибо за ценные подробности. Это имеет большой смысл.

@gaearon Я как бы скопировал тебя. Я хотел использовать ParseReact с классами ES6, поэтому мне нужно было повторно реализовать API наблюдения миксина как компонент более высокого порядка.

https://gist.github.com/amccloud/d60aa92797b932f72649 (использование ниже)

  • Я разрешаю наблюдать за компонентом или передавать его в декоратор. (Я мог бы объединить два)
  • Я объединяю наблюдаемые в качестве реквизита (без this.data или this.props.data)

@aaronshaf @gaearon Преимущество того, чтобы сделать это первоклассным, заключается в следующем:

1) Он не разъедает пространство имен реквизита. Например, компонент более высокого порядка не должен требовать имени, такого как данные из вашего объекта реквизита, который нельзя использовать ни для чего другого. Объединение в цепочку нескольких компонентов более высокого порядка требует все больше имен, и теперь вам нужно найти способ сохранить эти имена уникальными. Что, если вы сочиняете что-то, что, возможно, уже было сочинено, и теперь у вас конфликт имен?

Кроме того, я думаю, что лучшая практика для компонентов более высокого порядка должна состоять в том, чтобы избегать изменения контракта обернутого компонента. Т.е. концептуально это должны быть одни и те же реквизиты на входе и выходе. В противном случае это сбивает с толку при использовании и отладке, когда потребитель предоставляет совершенно другой набор реквизитов, чем полученный.

Вместо HOC это может быть обычный компонент:

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

Поскольку вы управляете функцией, переданной как реквизит render , имена не могут конфликтовать. С другой стороны, это не загрязняет состояние владельца.

Что мне здесь не хватает?

Это могло бы быть даже менее подробным, если бы компонент Observe просто захватил Observables из своих реквизитов:

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

Что еще хорошо, так это то, что это позволит избежать повторных подписок, если shouldComponentUpdate вернет false, потому что мы вообще не попадем в render .

Наконец, можно написать декоратор для render , который оборачивает его в компонент Observe :

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

Я бы предпочел сохранить render как чистую функцию рендеринга, а не внедрять в нее логику выборки данных.
Первоначальное предложение кажется хорошим, на мой взгляд. Это очень близко к тому, как состояние работает с rx-react и позволяет отделить управление состоянием от логики выборки данных, что кажется очень последовательным.

Единственное, что меня беспокоит, - это использование карты наблюдаемых вместо одной наблюдаемой, потому что это не дает пользователю возможности выбирать, как будут составлены наблюдаемые, но это незначительная проблема.

На самом деле это не внедрение логики выборки данных, а просто экономия на наборе текста. Он будет обесценивать версию выше, которая просто отображает компонент <Observe /> . В рендеринге компонентов с состоянием нет ничего необычного, поэтому я не думаю, что это делает render менее чистым, чем сейчас.

Я попытался совместить лучшее из #3858 и этого предложения.

В любом подходе HOC преимуществом является явность , но недостатки свойств могут конфликтовать в какой-то момент.

В текущем предложении преимуществом является явность, но отрицательной стороной является более сложный жизненный цикл и большая поверхность API основных компонентов.

В # 3858 преимущество заключается в совместном размещении зависимостей «запоминаемого рендеринга» с самим рендерингом (они больше нигде не используются, поэтому это имеет смысл), но меня беспокоит «выглядит синхронно, но асинхронно» , и я не понимаю, как это может работать с неизменяемыми моделями, если он так сильно зависит от this . Это также раздражает меня тем, что React-было-просто-обоснованно, потому что нет ничего простого в том, чтобы рассуждать о ручном отслеживании изменений и связывании источников данных с React (или обертывании их для работы с React). Я понимаю, что существует давление, чтобы реализовать что-то одновременно производительное и уменьшающее шаблон.

В своем предложении я сохраняю совместное размещение и ясность, но:

  • <Observe /> (или декоратор observe() который оборачивает рендеринг с помощью <Observe /> ) — это просто надстройка, я не предлагаю никаких изменений в ядре React.
  • Каждый может реализовать свою собственную логику наблюдения для своих вариантов использования. Вы можете иметь свой собственный <Observe /> который использует только одну наблюдаемую, если вы предпочитаете это. Вы можете или не можете использовать декоратор.
  • Жизненный цикл компонента остается прежним.
  • Конфликтов пропсов нет, потому что данные передаются как параметр.
  • Мы тестируем, решая проблемы с помощью инструментов, которые у нас есть.
  • Для сокращения шаблонов мы используем инструменты сокращения шаблонов (декораторы) вместо того, чтобы вводить новые основные концепции.

Большое обсуждение и работа вокруг здесь, большое уважение. :)

Я согласен, что это не заслуживает включения в ядро. Возможно, надстройка придает этому предложению достаточную поддержку, чтобы люди могли попытаться свести его воедино и стандартизировать, прежде чем приступать к более полной реализации. При этом я нахожу это предложение лучше, чем https://github.com/facebook/react/issues/3858 и https://github.com/facebook/react/pull/3920, из- за его минимализма.

Это то, что я использовал в сторонних проектах (так что это крупицы соли) — это похоже на потрясающую работу @elierotenberg, но не

Приготовьтесь к CoffeeScript и миксинам или продолжайте прищуриваться, пока это не будет похоже на ES6, если хотите. :)

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

И используется так:

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

  propTypes:
    flux: PropTypes.flux.isRequired

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

  ...

Итак, declareNeeds — это односторонняя функция реквизита и состояния для описания того, что нужно этому компоненту. В реальной реализации здесь принимающая сторона @props.flux.declareNeeds , настроенная на компоненте верхнего уровня, погружает эти потребности в объект ProcessSink . Он группирует данные по своему усмотрению, устраняет дубликаты needs между компонентами, которые используют одни и те же flux , а затем выполняет побочные эффекты для удовлетворения этих потребностей (например, подключение к сокету или выполнение HTTP-запросов). Он использует подсчет ссылок для очистки вещей с состоянием, таких как соединения сокетов, когда нет компонентов, которым они больше не нужны.

Данные передаются от битов с состоянием, таких как события сокетов и запросы, в диспетчер (а затем в хранилища или куда-либо еще) и обратно к компонентам для удовлетворения их потребностей. Это не синхронно, поэтому все компоненты обрабатывают рендеринг, когда данные еще недоступны.

Я делюсь этим здесь только в качестве еще одного мнения о том, что изучение подобных решений возможно в пользовательском пространстве, и что текущий API действительно хорошо подходит для такого рода экспериментов. С точки зрения минимального шага, который ядро ​​может предпринять для поддержки взаимодействия между различными подходами, я думаю, что @elierotenberg справился :

Во всяком случае, раскрытие и поддержка поддержки жизненного цикла экземпляров компонентов React за пределами смонтированной иерархии React могут помочь.

Что касается подхода без сохранения состояния, мне кажется, что асинхронная выборка данных зависит от состояния, и поэтому сохранение состояния ожидания/завершения/ошибки в состоянии некоторых компонентов имеет значение.

@elierotenberg и @andrewimm прекрасно заявляют о первоклассной обработке ошибок. @sebmarkbage Я думаю, что ваш инстинкт в отношении минимальной точки взаимодействия правильный, но я не уверен, как добавление семантики для ошибок, чтобы всплывать в дереве компонентов, соответствует этому требованию. Здесь должна быть столь же простая история о том, как получить доступ к значениям из onError и onCompleted , даже если просто слоты в this.observed содержат последнее значение либо next/error/completed как { next: "foo" } . Не поддерживая наблюдаемый контракт как первоклассную функцию, я немного скептически отношусь к тому, что это предложение попадет в список.

А так как это Интернет, и я один из первых раз обращаюсь сюда: лента проблем React — одна из лучших книг и универсальный источник отличной работы и идей. :+1:

мне пахнет связкой.

Я не уверен, как это связано с текущим предложением/реализацией, но я обнаружил, что включение композиции и манипуляции более высокого порядка на самом деле является ключевой функцией отслеживания зависимостей данных, особенно если вы используете реактивный источник данных (поток, поток по сети или что-то еще, кроме предоставления вам обновлений).

Возьмем следующий пример с 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.
}

В общем, мне интересно, должна ли вообще быть привязка данных в API компонента. Мне кажется, что декораторы, возвращающие компоненты более высокого порядка, обеспечивают очень хороший способ выражения привязки данных, не загрязняя пространство имен методов компонентов.

Однако, как отметил @sebmarkbage , вместо этого существует риск загрязнения пространства имен props. На данный момент я использую декоратор преобразователя реквизита ( react-transform-props ) для очистки/переименования реквизита перед передачей его внутреннему компоненту, но я признаю, что в будущем это может стать проблематичным, если компоненты более высокого порядка станут более распространенным явлением, и возрастает риск конфликта имен.
Можно ли решить эту проблему, используя символы, являющиеся ключами свойств? Поддерживает ли проверка $# propTypes Symbol реквизиты с ключом Symbol? Будет ли JSX поддерживать вычисляемые встроенные ключи реквизита (хотя всегда можно использовать вычисляемые свойства + распространение объекта)?

Извините, если это немного не по теме, но мне кажется, что нам все еще нужно найти правильную приятную абстракцию/API для выражения данных на уровне компонентов.

мои 2 цента

Мне очень понравился шаблон «дети как функция» для значений, которые меняются со временем. Я полагаю, @elierotenberg первым придумал это? В настоящее время я использую для моделирования пружин (через отскок) на реактивных пружинах. Пример -

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

Нет коллизии реквизита и нет использования состояния владельца. Я также могу вложить несколько пружин, и реакция управляет всеми жесткими битами. Эти «наблюдаемые» (ха!) Также могут принимать onError , onComplete и другие реквизиты (запросы graphql?).

Грубая попытка набросать это для «потока».

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

Мы использовали этот шаблон, который мы назвали обратными вызовами рендеринга, в Asana, но в конечном итоге отказываемся от него.

Плюсы

  • Отсутствие возможности столкновения реквизита.
  • Легко реализовать как автору StoreComponent , так и пользователю StoreComponent

Минусы

  • shouldComponentUpdate чрезвычайно сложно реализовать, поскольку обратный вызов рендеринга может закрыться по состоянию. Представьте, что у нас есть дерево компонентов A -> Store -> B . A имеет в своем состоянии счетчик, доступ к которому осуществляется во время обратного вызова рендеринга в качестве реквизита для B . Если A обновляется из-за счетчика, а Store не обновляется, B будет иметь устаревшую версию счетчика. В результате мы были вынуждены всегда обновлять Магазин.
  • Тестировать компоненты по отдельности стало очень сложно. Разработчики неизбежно вкладывали сложную логику в обратные вызовы рендеринга для того, какой компонент отображать, и хотели проверить логику. Естественным способом сделать это было визуализировать все дерево и протестировать _через_ компонент хранилища. Это сделало невозможным использование мелкого рендерера.

Теперь мы переходим к модели, в которой StoreComponent принимает ReactElement , а когда хранилище получает новые данные, хранилище клонирует ReactElement, переопределяя конкретную опору. Есть много способов сделать этот шаблон, например, взять конструктор компонента и свойства. Мы выбрали этот подход, потому что его проще всего моделировать в TypeScript.

Потрясающие очки, я не могу придумать обходной путь, не нарушая требование «вбок».

@threepointone Звучит точно так же, как одно из предложений по реализации для питания https://github.com/reactjs/react-future/pull/28 .

@pspeter3 в вашем примере, есть ли серьезный недостаток в том, чтобы постоянно обновлять Магазин / shouldComponentUpdate: ()=> true ? Я думаю, что его «дети» в любом случае были бы отображены без Store в цепочке. Спасибо за ваше время!

@threepointone Это именно то, что мы сделали для наших границ. Было неясно, как именно это повлияло, но члены команды беспокоились о производительности. Беспокойство в сочетании с тестированием сложности вынудило перейти на использование React.cloneElement(this.props.child, {data: this.state.data})

@ pspeter3 угол тестирования определенно проблема. Что, если smallRenderer распознает «обратные вызовы рендеринга»? Это поможет?

Ps- 'рендеринг обратного вызова' :thumbs_up:

@sebmarkbage Текущее обсуждение es-observable заключается в том, что subscribe будет гарантированно асинхронным, с методом [Symbol.observer] , предоставленным для синхронного сокращения и подписки, хотя действительность наличия как синхронного, так и асинхронного API в настоящее время обсуждается.

Я присоединился к этому билету с упомянутым выше вариантом использования в пользу синхронной подписки, не зная, есть ли у вас что добавить.

Я думаю, что представленные здесь идеи представляют собой очень чистый шаблон для обработки внешнего состояния, хотя, немного поэкспериментировав с ним, я склоняюсь к подходу HOC.

Также @gaearon я немного упростил ваш HOC выше, используя static - ComposedComponent.observe и используя this.state вместо this.state.data - https://gist.github.com/tgriesser/d5d80ade6f895c28e659

Это выглядит очень красиво с предложенными декораторами es7:

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

Декоратор класса может даже добавить геттер для data , чтобы сделать его очень близким к исходному предлагаемому API (минус локальное состояние, влияющее на наблюдаемые подписки, что, я согласен, хорошо - гораздо меньше шума вокруг подписаться/отписаться).

отсутствие синхронной подписки может стать огромной проблемой.

Я думаю, что понял, как последовательно решить «проблему состояния» и «боковую загрузку данных» (производные данные). Это делает это без учета состояния "React-way". Я нашел способ поддерживать согласованность состояния в любой момент времени, и он соответствует шаблону UI = React(state) . Вряд ли мне не по силам сделать его полностью пуленепробиваемым, добавить больше примеров и сделать хорошую презентацию. https://github.com/АлексейФролов/slt . С другой стороны, он хорошо протестирован, и я периодически использую его в своих производственных проектах. Умные умы приветствуются.

Всем привет,

Забавно, что я не наткнулся на эту тему раньше, так как в нашей компании мы столкнулись с теми же проблемами, которые решались этим предложением полгода назад.
Мы начали использовать React для крупномасштабного проекта (подумайте о редакторе со сложностью Microsoft Visio, с очень циклическими данными). Vanilla React count не поспевает за нашими требованиями к производительности,
и поток был для нас чем-то вроде запрета из-за большого количества шаблонов и подверженности ошибкам всех подписок. Итак, мы поняли, что нам также нужны наблюдаемые структуры данных.

Поскольку мы не смогли найти ничего доступного, готового к использованию, мы создали собственную библиотеку наблюдаемых объектов, основанную на принципах нокаутирующих наблюдаемых объектов (особенно: автоматические подписки).
На самом деле это очень хорошо вписывается в текущий жизненный цикл компонентов React, и нам не нужны странные хаки или даже дочерние обратные вызовы рендеринга (используемый ниже ObserverMixin составляет около 10 loc).
Это значительно улучшило наш DX и так хорошо сработало для нашей команды, что мы решили опубликовать его с открытым исходным кодом . В то же время он довольно хорошо зарекомендовал себя в бою (например, он предоставляет полифилл наблюдаемого массива ES7) и хорошо оптимизирован.
Вот пример короткого таймера (также доступен как JSFiddle ), ИМХО, DX не может быть намного лучше... :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);

Подробнее об этом подходе читайте в этом блоге . Кстати, я позабочусь о том, чтобы в библиотеку был добавлен декоратор и/или контейнер для тех, кто использует классы ES6 .

К сожалению, я не видел эту ветку до react-europe, иначе у нас была бы прекрасная возможность обсудить React и observables. Но большое спасибо за вдохновляющие доклады! :+1: Мне особенно понравились абстракции GraphQL и интеллектуальная работа Redux :)

@mweststrate Я чувствую, что сообществу в конечном итоге придется выбирать между решениями «Observables» и «Immutable data». Возможно, нам нужно каким-то образом смешать сильные стороны обоих подходов в одном решении (https://github.com/AlexeyFrolov/slt/issues/4). В своем решении я реализовал подход «Неизменяемые данные» с упором на согласованность состояния в любой момент времени. Я также планирую поддерживать Observables и Generators. Это пример правил, которые используются для извлечения «производных» или «дополнительных» данных (таких как тело страницы, активы, рекомендации, комментарии, лайки) и поддержания согласованности состояния приложения.

https://github.com/AlexeyFrolov/slt#rules - пример

Это сложный пример из реальной жизни (мой производственный код), который показывает, как обрабатывать перенаправления API с заголовком 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();
            });
        }
    }
}

В остальном такой же подход, как и у вас, за исключением того, что нет прямой привязки к React (в моем случае это не требуется). Я считаю, что нам нужно как-то объединить усилия для достижения общей цели.

Я думаю, что это плохая идея, потому что будет много крайних случаев, которые трудно рассмотреть.

Из приведенных выше комментариев я могу перечислить возможные области, о которых конечный пользователь должен думать каждый раз, когда он хочет создать компонент с данными:

  • Рендеринг на стороне сервера
  • Инициализация .state и .data
  • .context , .props , .state и .observe запутанность
  • Асинхронный рендеринг

Я думаю, что это предложение приведет к подверженным ошибкам, нестабильным и трудным для отладки компонентам.

Я согласен с предложенными @glenjamin крючками жизненного цикла connect и disconnect .

Извините, если это не по теме (я не могу точно сказать). Но вот пример того, почему я думаю, что раскрытие API для взаимодействия с деревом компонентов было бы интересным способом решения этой проблемы: https://github.com/kevinrobinson/redux/blob/feature/loggit-todomvc/examples/loggit. -todomvc/loggit/renderers/precompute_react_renderer.js#L72

Этот метод является моей хакерской прогулкой по дереву, и поэтому мой вопрос заключается в том, как наличие общедоступного API для выполнения такого рода вещей позволит внедрять здесь инновации в «боковой загрузке данных», что, я думаю, сводится к «рендереру, который не работает сверху вниз": https://github.com/kevinrobinson/redux/blob/feature/loggit-todomvc/examples/loggit-todomvc/loggit/react_interpreter.js#L8

Но вот пример того, почему я думаю, что раскрытие API для взаимодействия с деревом компонентов было бы интересным способом решения этой проблемы.

Кажется, @swannodette говорил об этом на ReactConf. Интересно, делает ли это Ом Некст?

Да, он предложил то же самое на первом ReactConf. Я не видел последнюю версию Om.next и не слышал выступления EuroClojure, но раньше Om использовал свою собственную структуру, которую пользователи должны были использовать для этого.

Этот метод является моей хакерской прогулкой по дереву, и поэтому мой вопрос заключается в том, как наличие общедоступного API для выполнения такого рода вещей позволит внедрять здесь инновации в «боковой загрузке данных», что, я думаю, сводится к «рендереру, который не работать сверху вниз"

Я думаю, что это примерно эквивалентно поверхностному рендерингу, найденному в тестовых утилитах — я упомянул, что это первоклассный API как аналог рендеринга DOM и String для

Первоначальная реакция заключается в том, что это может быть немного низкий уровень, но, как и в случае с расширяемыми веб-вещами, я думаю, что это облегчит эксперименты в пользовательском пространстве.

Я согласен, если у нас есть способ рендеринга для получения дерева, мы можем пройтись по нему, найти все необходимые данные и передать их вниз.

Получение доступа ко всему виртуальному дереву DOM — это удивительно мощная и полезная функция, к которой я хотел бы получить доступ, даже если это рассматривается как совершенно отдельная проблема.
Я думаю, что это имеет смысл, учитывая путь, по которому идет React с 0.14 и выше.

Учитывая сложность наблюдаемых, я смиренно предлагаю господам в этой теме взглянуть на реализацию реактивных данных Meteor: https://github.com/meteor/meteor/wiki/Tracker-Manual. Согласование с React занимает буквально 3-5 строк кода в методе componentWillMount , что-то вроде:

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

Я не знаю, устраняет ли Tracker (который легко извлекается как отдельная библиотека) необходимость в наблюдаемой поддержке в React, но, похоже, так оно и есть.

MOBservable следует очень похожему шаблону, чтобы обновить компонент сбоку после изменения некоторых наблюдаемых значений, поэтому кажется, что текущие методы жизненного цикла + декораторы предлагают достаточную гибкость для сторонних библиотек, чтобы выражать такие шаблоны и имхо концепция третьего источника данных только бы все усложнил.

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

@Mitran Я согласен, это действительно хорошее чтение, спасибо, что нашли это! Фактически это то, что предлагает https://github.com/facebook/react/pull/3920 .

Мы не решили, какое предложение лучше. Поиграв с обоими, я в основном убежден, что реактивное программирование (как вы указали; https://github.com/meteor/meteor/wiki/Tracker-Manual) — это путь, но мы не достигли консенсуса и мы все еще пытаемся выяснить, что имеет наибольший смысл, поэтому отзывы приветствуются.

@Mitrani @jimfb Я большой поклонник Meteor уже несколько лет. Трекер действительно классный и очень увлекательный. Я создал презентацию, демонстрирующую, как работает очень простая версия трекера:

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

И я даже создал библиотеку наблюдаемых потоков с помощью Tracker:

https://github.com/ccorcos/метеор-трекер-потоки

Но когда я полностью перешел на использование React вместо Blaze, я понял, что Tracker слишком сложен, и иногда простые методы публикации/подписки/onChange просто в 100 раз проще.

Кроме того, для всех поклонников функционального программирования Tracker имеет некоторые побочные эффекты, которые имеют смысл в 99% случаев, но иногда они вас достают. Вот как это будет выглядеть в компоненте 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()

Моя точка зрения о побочных эффектах заключается в том, что c.stop() остановит подписку и внутренний автозапуск.

@ccorcos Да, в https://github.com/facebook/react/pull/3920 все побочные эффекты будут полностью внутренними для React. На самом деле ядро ​​React могло бы реализовать их полностью неизменяемым/функциональным способом, который не производил бы никаких мутаций. Но это детали реализации, так как побочные эффекты все равно не будут видны извне.

Интересно... Так почему бы просто не использовать обратные вызовы onChange? Я обнаружил, что они наиболее совместимы. Независимо от того, используете ли вы Meteor (Tracker), RxJS, Highland.js и т. д., вы всегда можете легко интегрироваться с ними с помощью обратного вызова события. И то же самое с высокоуровневым компонентом React.

Что мне нравится в Redux, так это то, что он держит эту логику вне фреймворка и сохраняет компоненты React как чистые функции.

@ccorcos Основная проблема заключается в том, что при уничтожении экземпляра компонента необходимо очистить все «подписки». Авторы компонентов часто забывают об этой очистке, что приводит к утечке памяти. Мы хотим, чтобы это было автоматически, что упрощает написание (меньше шаблонов) и меньше подвержено ошибкам (очистка выполняется автоматически).

@jimfb Точно. Автоматическая отписка — одна из действительно полезных вещей в подходе Tracker. Каждый вызов реактивного источника данных восстанавливает подписку, и как только компонент прекращает запрашивать данные, очищать нечего!

Да, но на самом деле это не "автоматически" отписаться. Вы должны вызвать c.stop() компонент размонтировался. Однако вы можете абстрагироваться от этого с помощью миксина.

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

Но на самом деле это ничем не отличается от любого другого API. Вы можете создать встроенный метод, который будет автоматически отписываться за вас при размонтировании и т. д. Послушайте, я большой поклонник Meteor. Поэтому я не хочу отговаривать тебя от этого. Просто иногда я нахожу, что очень трудно объяснить это тому, кто с этим не знаком. Между тем, использование простых слушателей/эмиттеров событий гораздо проще понять и реализовать, и они, как правило, очень совместимы с любой системой реактивности, которую вы хотите использовать...

@ccorcos Мы можем говорить о разных механизмах. Последнее, что я проверял, у Tracker не было постоянных подписок; каждая функция, которая устанавливает зависимость от реактивного источника данных (путем доступа к нему), повторно запускается _один раз_ при его изменении, и на этом подписка заканчивается. Если он снова обращается к источнику данных, это восстанавливает «подписку» для еще одного изменения. И так далее.

@Mitran прав, семантика # 3920 более «автоматична», чем Meteor (отказ от подписки действительно происходит автоматически), и намного проще, поскольку в обычном случае использования буквально нулевая поверхность API.

@ccorcos @Mitrani Для готовой к использованию библиотеки, вдохновленной Tracker / Vue.js, вы можете попробовать Mobservable , он

Последнее, что я проверял, у Трекера не было постоянных подписок

Подписки @Mitrani могут быть постоянными. Проверь это.

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

Теперь есть кое-что интересное с Tracker. Проверь это.

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

Хотя последний пример не очень полезен. Пока мы не сделаем что-то подобное.

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

каждая функция, которая устанавливает зависимость от реактивного источника данных (путем доступа к нему), повторно запускается один раз при его изменении, и на этом подписка заканчивается.

Подписка действует до тех пор, пока автозапуск не будет перезапущен (изменена реактивная зависимость) или пока вычисления, в которых он живет, не остановятся. Оба из них вызывают хук computing.onInvalidate.

Вот супер надуманный вариант, который сделал то же самое. Надеюсь, это поможет вам понять, как работает Tracker. И, возможно, вы также увидите, насколько грязным он может стать.

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 Теперь я вижу источник путаницы; это был мой словарный запас. Говоря о подписках, я не имел в виду _подписки Meteor_. Они определяют, какие данные передаются с сервера клиенту, но не имеют отношения к обновлениям уровня представления, что является предметом этого обсуждения. Говоря _subscription_, я проводил параллель между традиционными прослушивателями событий и способностью Tracker повторно запускать функцию, которая зависит от реактивного источника данных. В случае компонентов React это будет метод компонента, который извлекает данные, а затем вызывает setState или forceUpdate .

@mweststrate Спасибо за пример, выглядит интересно.

О да. Так что у них есть хитрый способ сделать это.

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

Подписка будет просто переподписываться каждый раз без проблем. sub.ready и Messages.find.fetch являются «реактивными» и запускают повторный запуск автозапуска всякий раз, когда они изменяются. Что круто в Трекере, так это то, что вы начинаете скрывать автозапуски и просто указываете в документации, что определенная функция находится в «реактивном контексте».

вы могли бы бросить это в миксин

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

И тогда у вас остается эта волшебно реактивная функция, которая просто работает!

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

Трекер прикольный...

@ccorcos Оказывается, я несколько ошибался в отношении автоматических отписок при использовании событий в стиле Tracker. Вам по-прежнему нужно остановить прослушиватель в componentWillUnmount , иначе он будет продолжать обращаться к источникам данных и вызывать setState (если только вы не проверите с помощью isMounted() который сейчас устарел).

Кстати, вы можете создавать элегантные декораторы, чтобы сделать методы компонентов реактивными. Вот несколько примеров: [1] , [2] . Выглядит так:

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

  /* ... */
}

@Mitraim довольно аккуратно - хотя я предпочитаю функции высокого порядка. ;)

@sebmarkbage @jimfb Я веткой (#3858) уже несколько месяцев, и мне любопытно, достигла ли основная команда какого-либо консенсуса по этой проблеме или, по крайней мере, общего направления.

@oztune Нет обновлений; мы были сосредоточены на других приоритетах. Мы опубликуем в одной из тем, когда появится обновление по этой теме.

Ребята, я создал универсальный API для создания контейнеров. Проверьте реакцию-композитор . Что, мы могли бы создать контейнеры с функцией более высокого порядка.

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

Вот живая версия: https://jsfiddle.net/arunoda/jxse2yw8/

У нас также есть несколько простых способов создания контейнеров с помощью Promises, Rx.js Observables и Meteor's Tracker .

Также проверьте мою статью об этом: Давайте составим несколько контейнеров React.

@arunoda Мы сделали что-то очень похожее. Мне интересно, как вы предотвращаете вызов composerFunction при каждом изменении реквизита?

@oztune На самом деле теперь он снова запустится. Мы используем это Lokka и Meteor. Оба они имеют локальные кеши и не обращаются к серверу, даже когда мы вызываем composerFunction несколько раз.

Но я думаю, что мы могли бы сделать что-то вроде этого:

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

Любые идеи?

@arunoda Это то, что мы тоже пробовали, но это создает некоторый разрыв между действием и его зависимостями. Теперь мы делаем что-то похожее на react-async, где вместо немедленного выполнения заданного действия composerFunction возвращает функцию и ключ. Если ключ отличается от предыдущего ключа, возвращаемого composerFunction, будет выполнена новая функция. Я не уверен, что это касается этой ветки на github, поэтому я был бы рад продолжить в Twitter (то же имя пользователя).

@oztune Я создал новый выпуск GH, и давайте продолжим наш разговор там. Думаю, намного лучше, чем твиттер.

Почему бы просто не позволить JSX понять и отобразить Observables напрямую, чтобы я мог передать Observable в свойствах, например, this.props.todo$, и встроить их в JSX. Тогда мне не понадобился бы какой-либо API, остальное управляется вне React, а HoC используется для заполнения наблюдаемых. Не имеет значения, содержат ли реквизиты простые данные или наблюдаемые, поэтому никаких специальных данных this.data не требуется.

{this.props.todo$

Кроме того, React render может отображать Oservable [JSX], что позволяет создавать дизайн, описанный в ссылках, без дополнительной библиотеки.

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

https://github.com/milankinen/реакт-комбинаторы

Привет,
На данный момент мы можем легко использовать компоненты без состояния с потоками rxjs.
Я не понимаю необходимости в другом API.
Я написал пример - доска, на которую вы можете навести мышь, и когда она доходит до 26, она меняется на перезагрузку.
Я хотел бы услышать, что вы думаете об этом пути.
Вот код:
https://jsfiddle.net/a6ehwonv/74/

@giltig : я тоже так учусь в последнее время, и мне это нравится. Вместе с Cycle.js.

Я был бы признателен за возможность легко прослушивать обработчики событий, определенные в компонентах, без необходимости передавать предметы для соединения. Если я правильно понимаю, то это в значительной степени наоборот, чем то, что предлагается здесь. В качестве альтернативы, если бы мы могли наблюдать за синтетическими событиями React vdom, возможно, для наблюдения можно было бы использовать «ссылки», чтобы избежать использования тегов CSS только для наблюдения.

Дальнейшие RxJ могут поддерживать объект в CombineLatest, чтобы мы могли напрямую использовать функциональные компоненты React для создания более крупных компонентов.

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

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

Привет,
Причина, по которой мы не используем Cycle или любой другой фреймворк, заключается в том, что я предпочитаю библиотеки фреймворкам (больше контроля для разработчика), а также я хочу пользоваться силой сообщества, которую имеет React.
Сейчас для React разработано так много движков рендеринга, что жаль не использовать его (ReactNative, ReactDom, ReactThree и т. д.). Довольно просто использовать только Rxjs и реагировать, как в примере, который я показал выше.

Было бы проще думать, если бы реагирующие компоненты могли принимать pojos так же долго, как наблюдаемые объекты, как реквизиты. На данный момент это невозможно, поэтому то, что я показал выше, - это путь, который мы выбрали.

Кстати, то, что вы сделали с MyFancyReactComponent, возможно, и мы действительно сделали это в некоторых случаях, хотя вы также можете написать jsx напрямую.

Что касается тем - я думаю, что это правильный способ, потому что в конечном итоге в компоненте React я просто использую функцию-обработчик, которая может быть чем угодно. Я решил реализовать его с субъектом внутри, который получает события, но кто-то другой может выбрать иное - это гибко.

Было бы проще думать, если бы реагирующие компоненты могли принимать pojos так же долго, как наблюдаемые объекты, как реквизиты. На данный момент это невозможно, поэтому то, что я показал выше, - это путь, который мы выбрали.

наблюдаемые реквизиты не имеют смысла в долгосрочной перспективе. На самом деле, в этом контексте это вообще не имеет смысла.

Мне кажется, что мы получили другую модель с Suspense (кеш + контекст). Сам кеш может получить поддержку подписок. Вы можете отслеживать оставшуюся работу для Suspense на странице https://github.com/facebook/react/issues/13206.

Мы также предоставляем пакет подписки для более единичных случаев.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги