React: Поддержка асинхронного рендеринга сервера (ожидание данных перед рендерингом)

Созданный на 24 июн. 2014  ·  139Комментарии  ·  Источник: facebook/react

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

Component API Server Rendering Backlog Feature Request

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

Несколько месяцев назад на JSConf Iceland я выступил с докладом, в котором описал будущие функции асинхронного рендеринга в React (см. вторую часть): https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react. -16.html. Речь идет о выборке данных на стороне клиента.

Теперь @acdlite рассказал о том, как те же концепции могут применяться для включения асинхронного ожидания данных на сервере в компонентах React и постепенного сброса разметки по мере ее готовности: https://www.youtube.com/watch?v= z-6JC0_cOns

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

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

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

Есть 2 случая, которые мне трудно решить без этого:

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

react-async решает эти проблемы с волокнами и кешем. Это помогает, но, на мой взгляд, это просто _хакерские_ решения для решения проблемы, которую можно решить только в ядре.

Раскрасьте меня, не информированного по этому поводу, @fdecampredon говорит, что componentWillMount является асинхронным, и вы ничего не возвращаете немедленно, что должен отображать React, пока ничего не будет? Если да, то почему бы просто не вернуть ничего при рендеринге, если данных еще нет? (Да, я получаю серверную часть) Кроме того, что должно произойти, если реквизит изменится до того, как сработает componentWillMount ?

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

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

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

Раскрасьте меня, не информированного по этому поводу, @fdecampredon говорит, что componentWillMount является асинхронным, и вы ничего не возвращаете немедленно, что должен отображать React, пока не будет ничего? Если да, то почему бы просто не вернуть ничего при рендеринге, если данных еще нет? (Да, я получаю на стороне сервера) Кроме того, что должно произойти, если реквизиты изменятся до срабатывания componentWillMount?

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

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

Так или иначе, вы захотите, чтобы компонент «верхнего уровня» мог извлекать данные, как это сделано в примере Flux .
В этом примере все довольно просто, потому что получение списка задач является синхронной операцией, если бы это не было, и в случае предварительного рендеринга на сервере мы бы визуализировали первый раз без данных (и потеряли бы предварительно визуализированный разметка с сервера).

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

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

  • В каком-то примере мне кажется, что @petehunt пытался добиться чего-то подобного.
  • response-nested-router продвигает аналогичный механизм в willTransitionTo , и некоторые обсуждения заставляют меня чувствовать, что никто не нашел подходящего решения.
  • RRouter также предоставляет некоторый механизм для предварительной выборки данных во время рендеринга / монтирования компонента.
  • и, наконец, react-async, как я сказал ранее.

@fdecampredon Чтобы было ясно, цель willTransitionTo в react-nested-router — _не_ для загрузки данных, особенно потому, что возврат обещания из этого метода действительно заблокирует рендеринг нового пользовательского интерфейса, чего вы не хотите делать если вам это абсолютно необходимо.

@fdecampredon Все все еще пытаются во всем разобраться, поэтому меня не удивит, если ни у кого нет окончательного ответа. Но я предполагаю, что разработчики Facebook сами сталкивались с этим несколько раз.

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

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

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

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

Я не думаю, что этот подход означает рассмотрение компонента как представления и модели. Если вы посмотрите на архитектуру Flux, они думают о компонентах React как о _controller-views_, которые не только отображают данные (из хранилищ), но и вызывают действия на основе взаимодействия с пользователем. А действия то обновляют магазины(=модель). Для меня это звучит как очевидная архитектура MVC.

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

На данный момент мне кажется, что единственный путь — это React-async/Fibers + Flux. Преимущество Flux в том, что нам не нужен какой-то искусственный кеш для передачи состояний компонентов с сервера на клиент (как это сделано в исходном примере с асинхронной реакцией), мы можем просто инициализировать хранилища, а затем отправить их в клиент с разметкой html (см. этот пример ).

Но это решение действительно _хакерское_.

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

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

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

Моим идеальным решением для решения этой проблемы было бы разрешить «рендеринг», который просто строит дерево компонентов, тогда мы сможем пройти по дереву, чтобы найти компоненты, которым нужны дополнительные данные, что позволит нам асинхронно загружать больше данных и «повторно визуализировать». " это поддерево компонентов, а затем, когда мы будем готовы к очистке разметки, позвольте нам преобразовать это дерево в строку. — @мридгуэй

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

Я хочу сослаться на react-nexus как на пример того, что я хотел бы поддерживать в React. По сути, это переписывание того, как работает mountComponent за исключением того, что он строит дерево компонентов без фактического монтирования его в DOM или записи строки. Это позволяет вам перемещаться по дереву компонентов и запускать асинхронные методы во время построения дерева. Проблема с этой реализацией заключается в том, что она отбрасывает это первое дерево, а затем все равно вызывает React.renderToString , тогда как было бы неплохо взять это дерево предварительной визуализации и отобразить/смонтировать его.

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

@mridgway Если я не ошибаюсь, глобальные ReactContext могут быть совместимы и исчезнут в 0.14. Но я _могу_ ошибаться.

@gaearon Да, это то, что я понял из устаревания withContext.

:+1: Использование react-router для этого банкомата. Подход @mridgway звучит очень разумно.

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

Как обсуждалось в этом тикете - https://github.com/rackt/react-router/issues/1402 - проблема с поддержкой асинхронного рендеринга заключается в том, что первоначальный рендеринг кажется нулевым, поскольку соответствующие фрагменты еще не загружены в первом рендеринг проходит, что приводит к <noscript> в корне DOM и сбою контрольной суммы. После этого все быстро встает на свои места, но это приводит к мерцанию пользовательского интерфейса при локальной работе и еще хуже в полевых условиях, особенно если загружаемые фрагменты имеют разумный размер (скажем,> 30 КБ).

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

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

Не стесняйтесь применять альтернативные стратегии, которые могут подойти вам лучше, скажем, <WaitFor for={MyAsyncLoadedCompSignal} until={...} then={() => <MyAsyncLoadedComp ... />} /> . Но они по своей сути самоуверенные, а не то, что React должен или даже должен предложить, поэтому лучше оставить это сообществу.

Лучше оставить асинхронные вещи за рамками компонентов React, вот пример:

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

const routes = {

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

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

};

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

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

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

См. Разделение кода Webpack , React.js Routing from Scratch и -routing (скоро)

@syranide Я буду продолжать работать над этим, но я не думаю, что это так двоично, как вы сказали выше. Мы используем react-router, так что это может привести к некоторым проблемам, поскольку маршрутизатор является компонентом, а не находится вне среды React.

Если бы мы использовали подход <WaitFor ... /> , наверняка мы все равно получим другой DOM при первом рендеринге, который по-прежнему будет вызывать мерцание/исчезновение контента?

Если бы мы сделали подход, конечно, мы все равно получим другой DOM при первом рендеринге, который все равно будет вызывать мерцание/исчезновение контента?

Если вы не хотите мерцания (некоторые делают), это просто вопрос ожидания загрузки всех фрагментов, от которых вы зависите, перед рендерингом, webpack предоставляет это из коробки с помощью require.resolve .

PS. Да, react-router и все, что вы используете, безусловно, усложняет решение, но это все еще не проблема React, которую нужно решить.

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

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

Глядя на наш код еще раз, я думаю, что мы можем обойти проблему, заставив react-router думать, что он рендерится на сервере, но затем следуя стандартному подходу на стороне клиента:

const history = new BrowserHistory();

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

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

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

Извините, да, я имел в виду require.ensure . Обратный вызов выполняется только тогда, когда все зависимости удовлетворены, поэтому нужно просто поместить в него render/setState.

Хорошо, примерно так мы это и делали, спасибо за ваши ответы. Это похоже на то, что нужно решить в react-router, поэтому я продолжу обсуждение там - извините, если это было неподходящее место для этого разговора!

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

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

Я не очень хорошо знаком с реактивным маршрутизатором, но я все еще представляю, что это просто случай setRoute(...) => require.ensure([], function() { require(...); setRoute(...); }) что на самом деле нецелесообразно, поэтому вы вводите еще один уровень косвенности. Карта маршрутов и асинхронный загрузчик require.ensure для каждого. Напишите помощник function asyncSetRoute(...) { loadRoute(route, function() { setRoute(...); }} , теперь вы вызываете asyncSetRoute вместо этого, и он отложит обновление маршрутизатора, пока все не будет готово.

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

Итак, после нескольких часов исследований я только сейчас подтверждаю, что рендеринг на стороне сервера _невозможен_, если вы не загружаете все сверху вниз (?).

Возможные краткосрочные решения:
A. Предварительно заполните хранилище и синхронизируйте загрузку на стороне сервера.

B. Подавайте все сверху как один ввод данных после асинхронного получения вашего одного объекта данных.

ре: «А». Это не сработает, если вы уже не знаете, как будет выглядеть ваша структура рендеринга. Мне нужно отобразить его, чтобы узнать мои различные зависимости от коллекций/моделей/api. Кроме того, как сделать так, чтобы выборка была асинхронной на клиенте, но синхронизировалась на сервере без двух разных API?

ре: «Б». В основном та же проблема, что и выше. Должны ли люди создавать небольшие зависимости JSON для каждого из своих маршрутов?

Есть ли другие краткосрочные решения, пока мы ждем решения с поддержкой React? Любые пользовательские форки или плагины? https://www.npmjs.com/package/react-async ?

@НикСтефан

Я не понимаю проблемы. :-(

  1. Используйте Flux или похожий на Flux контейнер (Alt, Redux, Flummox и т. д.), где хранилища не являются синглтонами.
  2. Определите статические методы, такие как fetchData в обработчиках маршрутов, которые возвращают промисы.
  3. На сервере сопоставьте маршрут с компонентами, возьмите из них fetchData и дождитесь их завершения перед рендерингом. Это заполнит ваш экземпляр магазина Flux или Redux. Обратите внимание, что экземпляр хранилища не является одноэлементным — он привязан к конкретному запросу, поэтому запросы остаются изолированными.
  4. Когда промисы будут готовы, выполните рендеринг синхронно с экземпляром хранилища, который вы только что предварительно заполнили.

Вот хороший учебник по Redux, описывающий этот подход: https://medium.com/@bananaoomarang/handcrafting -an-isomorphic-redux-application-with-love-40ada4468af4

@gaearon Прошу прощения, если я вас смутил. Спасибо за ответ. Судя по вашему списку, я прав, предполагая, что решение проблемы зависимости данных сервера состоит в том, чтобы когда-либо определять потребности в данных только в вашем корневом компоненте (статические методы/статья, на которую вы ссылаетесь). Если ваши зависимости данных определены в корне, намного проще предварительно заполнить хранилища и т. д.

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

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

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

@NickStefan с -routing , например, асинхронная выборка данных выглядит так:

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

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

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

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

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

Я мечтаю, что однажды мы сможем написать полноценную CMS с использованием React, что-то вроде Wordpress, Drupal или Modx. Но вместо фрагментов HTML/PHP мы будем составлять веб-сайт из компонентов React.

@НикСтефан

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

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

Не совсем в корне — на уровне обработчика маршрута. В вашем приложении может быть много обработчиков маршрутов. Более того, такие библиотеки, как React Router, поддерживают обработчики вложенных маршрутов . Это означает, что ваш вложенный обработчик маршрута может определить зависимость, а сопоставление маршрутизатора будет содержать массив совпадающих иерархических обработчиков маршрутов, поэтому вы можете Promise.all их. Имеет ли это смысл?

Это не так детально, как «каждый компонент может объявить зависимость», но я обнаружил, что в большинстве случаев ограничение выборки данных обработчиками маршрутов (верхними и вложенными) — это все, что мне нужно. У меня есть чистые компоненты ниже, которые принимают данные через props поэтому они даже не знают, что они извлекаются. Логично, что они не могут его запросить.

Подход «каждый компонент может объявить зависимость» нецелесообразен, если у вас нет какого-либо решения для пакетной обработки запросов. Представьте, если <User> объявляет, что ему нужен вызов API, и теперь вы отображаете список из 100 <User> s — хотите ли вы ждать 100 запросов? Такая степень детализации требований к выборке данных работает, только если у вас есть решение для пакетной обработки запросов. Если вас это устраивает, Relay — это именно то, что вам нужно. Но для этого вам понадобится специальный бэкенд.

@NickStefan В настоящее время мы не поддерживаем асинхронную

@gaearon

Подход «каждый компонент может объявить зависимость» нецелесообразен, если у вас нет какого-либо решения для пакетной обработки запросов.

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

У меня изначально была мысль, что даже ваши 100например, подойдет командная дисциплина (т.е. просто сделайте <UsersContainer> который выполняет 1 запрос на все 100). Но это не «масштабируемость людей», чтобы просто иметь соглашение. Вероятно, лучше всего установить все зависимости от корня или маршрутизатора. Даже Relay вынуждает вас объявлять зависимости в корне с его тремя разными контейнерами (RelayContainer, RelayRootContainer, RelayRouter и т. д.). Это как бы доказывает, что единственный способ - по существу "поднять" ваши объявления зависимостей вверх по дереву.

Всем привет!
Есть ли обновления по этому поводу?

+1

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

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

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

Согласованный. Раньше я думал, что нам это нужно, но на самом деле было бы наоборот, если бы представление указывало, какие данные загружать. Представление является функцией состояния приложения, которое само по себе является функцией запроса (и, возможно, состояния пользователя, если есть сеанс). Вот что такое React; разрешение компонентам указывать, какие данные должны быть загружены, противоречит идее «одностороннего потока данных», IMO.

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

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

Несмотря на это, это обсуждение может стать гораздо более актуальным, если мы будем следовать идее запуска кода React в веб-воркере (#3092), где для связи через мост требуется асинхронность.

В примере с большим древовидным представлением, если бы я действительно хотел иметь возможность отображать его с путем, уже открытым на стороне сервера, я бы добавил этот путь в структуру URL. Если бы таких сложных компонентов было несколько, то я бы представлял их состояния с помощью GET-параметров. Таким образом, эти компоненты получают все преимущества SSR: их можно сканировать, по ним можно перемещаться с помощью истории, пользователи могут обмениваться ссылками на узел внутри них и т. д. Теперь у нас также есть способ определить, какие данные нужны серверу. для извлечения, чтобы отобразить ответ.

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

Всю ветку не читал, извиняюсь, если уже обсуждалось.

Одна вещь, которая действительно помогла бы, была бы, если бы существовал метод react-dom/server , который просто «запускал бы/создавал экземпляр» компонента и запускал бы методы жизненного цикла, но позволял разработчику решать, когда он готов отобразить компонент в строку html. . Например, разработчик может использовать setTimeout или что-то более надежное, чтобы дождаться завершения асинхронных методов.

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

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

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

Проблема с этим подходом заключается в том, что второй ReactDOM.renderToString снова запускает все методы componentWillMount что приводит к выборке ненужных данных.

Привет, ребята! это что-то в настоящее время работает? Я думаю, что эта проблема становится все более актуальной. Недавно я создал библиотеку, которая как бы расширяет реакцию редукции connect на dataConnect где вы также можете указать требования к данным контейнера. Затем эти требования к данным объединяются во время выполнения и запрашиваются с помощью GraphQL в одном запросе. Я думаю, что это будущее спецификаций данных, поскольку оно способствует компонуемости и изоляции, а также обеспечивает более производительную выборку. (все концепты видны на Relay)

Проблема с вышеизложенным - рендеринг на стороне сервера. Поскольку я не могу статически анализировать, какие требования к данным я получу, в настоящее время мне нужно будет выполнить рендеринг один раз, чтобы получить пакет для запроса из БД, скрыть хранилище избыточности, а затем повторно выполнить рендеринг, чтобы получить окончательную строку . Проблема похожа на проблему @olalonde .

Было бы здорово иметь возможность инициировать действия и обновления дерева элементов реакции и получать результат строки DOM по запросу. Вот как я это себе представляю:

const virtualTree = ReactDOM.renderVirtual(rootEle);

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

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

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

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

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

Другой вариант — просто позволить ему обновляться по желанию, как если бы это было на стороне клиента, имея все хуки, присутствующие и работающие, такие как didMount. Но вместо изменения DOM он просто изменит представление виртуального дома, а также отобразит в строку по запросу.

Что вы думаете, ребята? Что-то учитывать или я вижу это совершенно неправильно?

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

С тех пор я проникся гораздо более глубоким уважением к идеалам REST/HATEOS из-за масштабной простоты, которая возникает в системе приложений (браузеры, поисковые роботы, серверы приложений, прокси, CDN и т. д.), когда ее руководящие принципы последовал. Что касается этой проблемы, _URL-адрес должен быть единственным верным представлением состояния приложения_. Он и только он должен определять, какая информация требуется для обслуживания запроса. Уровень представления, React, не должен определять это; он должен быть построен на основе данных. Представление — это функция данных, а данные — функция URL-адреса .

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

  • Имеет ли ваше состояние независимо изменяющиеся параметры? Представьте их как отдельные параметры запроса.
  • Ваше состояние (или его часть) слишком велико, чтобы поместиться в URL? Назовите его и сохраните в неизменном хранилище данных. Обратитесь к нему по имени/идентификатору.
  • Ваше состояние меняется так быстро, что вы просто не можете сохранить все это навсегда? Поздравляем, у вас есть законные большие данные, используйте их для хранения и начните думать о том, как с ними поступить. Или вы можете уклониться и просто изменить свои данные с помощью запросов UPDATE и согласиться с тем, что вы не можете навсегда кэшировать все вещи позже (*).
  • Есть ли у вас представления, которые различаются для разных пользователей, но обслуживаются по одному и тому же URL-адресу, например персонализированная домашняя страница? Перейдите после идентификации пользователя к URL-адресу, который включает идентификатор пользователя.
  • Вы строите старое приложение, в котором у вас нет возможности сломать старую структуру URL? Это больно, я в той же лодке. Перенаправьте старую структуру URL-адреса на хорошо спроектированную структуру URL-адресов, переводя данные сеанса (или что-то еще) в сегменты и параметры пути URL-адреса.
  • У вас мало или совсем нет контроля над архитектурой вашего приложения? Это не тот случай, для которого был разработан React, и мы не хотим, чтобы React искажался, чтобы вписаться во что-то вроде взаимно совместимой архитектуры Wordpress / Magnolia / Umbraco.

Что вы получаете за всю эту работу, чего в противном случае у вас не было бы?

  • Возможность для одного пользователя привести другого пользователя в то же место в приложении, что и он, путем совместного использования URL-адреса.
  • Возможность навигации по приложению с использованием всех инструментов, которые предоставляет браузер. Стандартный клиент в своей стихии.
  • Возможность предложить сложную разбивку на страницы таким образом, чтобы клиенту было просто следовать: ссылка next в ответе. API графов FB — отличный пример этого.
  • Подробный график рабочих процессов вашего пользователя в Google Analytics.
  • Возможность построения указанного графа самостоятельно только из журналов запросов.
  • Выход из звездного пути хаоса: вместо того, чтобы сопоставлять все запросы как app.get("*", theOneTrueClientonaserverEntryPoint) , вы можете использовать свою структуру серверных приложений, как это было задумано. Вы можете возвращать правильные коды состояния HTTP из запросов вместо 200 OK\n\n{status: "error"} . Звездный путь заманчив, но ведет к безумию.

Итак, теперь, когда React, наш главный инструмент, не контролирует операцию, как мы можем получить наши данные? Откуда мы знаем, какие компоненты рендерить?

  1. Направить к функции доступа к данным и получить данные для соответствующих параметров запроса. Повторяйте, пока не проанализируете весь URL-адрес и не получите полный объект контекста.
  2. Преобразуйте контекст в максимально простой объект ответа, как если бы вы собирались ответить на запрос API. Вы обслуживаете запрос API? Тогда вы сделали и СУХОЙ.
  3. Передайте этот минимально сложный, но, возможно, большой объект компоненту верхнего уровня. Отсюда, это состав компонентов React до самого низа.
  4. Рендеринг в строку, ответ. Сообщите вашей CDN, что она может кэшировать это навсегда (* если только вы не пожертвовали этой опцией выше), потому что CDN идентифицируется по URL-адресам, и все ваши состояния имеют URL-адрес. Конечно, у CDN нет бесконечного хранилища, но они расставляют приоритеты.

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

Вот что делает React замечательным: это представление. Лучший вид. Только вид. λ

Придется с вами не согласиться @d4goxn. Я не пытаюсь сделать React чем-то большим, чем уровень представления, а маршрутизация важна, но явно недостаточна для указания всех требований к данным. Указав требования к данным на уровне компонента/контейнера, вы не сделаете React чем-то большим, чем уровень представления. Это просто обеспечивает вам гораздо лучшую изоляцию и рабочий процесс для вашего приложения. Это не просто упрощение работы, а потребность в большом приложении. Представьте, что у моего приложения 30 маршрутов, и я хочу добавить компонент профиля пользователя на каждый из них. Следуя альтернативному маршруту, где для каждого указаны все требования к данным, вам нужно будет пройти 30 маршрутов, чтобы добавить эту зависимость данных. Когда вы указываете свои потребности в данных в контейнере для этого компонента, вам просто нужно добавить компонент туда, куда вы хотите. Это подключи и играй. Не только это, но и запрос для всех зависимостей данных компонентов может быть оптимизирован. Relay — хороший пример этого, и разговоры о нем объясняют это гораздо лучше, чем я.

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

Мое предложение в основном состоит в том, чтобы не менять React, а иметь «только виртуальный» способ изменения дерева dom/components, в основном React, который можно «запускать» на стороне сервера, что, я думаю, довольно просто сделать (просто заблокируйте действия изменить DOM). Вы по-прежнему можете использовать кэширование и CDN. С растущей динамикой сайтов и приложений в настоящее время статическое кэширование будет иметь тенденцию к сокращению, но это уже другая тема 😄

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

В примере, где есть большое количество маршрутов, которым нужны одни и те же данные, я на самом деле не вижу в этом причины для исключения идентификатора этого источника данных из URL-адреса. Я бы смонтировал небольшой модуль промежуточного программного обеспечения для извлечения данных довольно близко к корню стека, который прикрепил бы этот пользовательский объект к контексту, а затем передал бы контекст другому промежуточному программному обеспечению на пути к обработчику конечного маршрута. Корневой компонент React может не заботиться об этой конкретной части контекста, но он передаст ее следующему уровню дочерних элементов, которые могут об этом заботиться, или иметь собственных потомков, которым это небезразлично. Если это приведет к необоснованно сложной или глубокой проводке, то может потребоваться что-то вроде хранилища Flux, но это отдельная большая тема.

Разделение и изоляция: я чувствую твою боль там. В моем предложении у нас есть две совершенно разные системы, преобразующие данные из формы, оптимизированной для хранения и извлечения, в форму, оптимизированную для абстрактной простоты. Затем мое представление преобразует это в форму, которая ему подходит, по мере того, как оно передается по иерархии компонентов. Сохранение слабой связи этих двух систем при добавлении функции, которая требует дополнений к обеим системам, — это не то, что я бы назвал трудным, но и не путь наименьшего сопротивления. Ментальное переключение между программированием хранилища данных и программированием динамического пользовательского интерфейса вполне реально. Раньше у нас были отдельные разработчики бэкенда и внешнего интерфейса, и HTTP служил интерфейсом между этими двумя парадигмами. Теперь я пытаюсь внедрить его в одно приложение, а вы пытаетесь делегировать его.

Я не уверен, что растущая сложность и активность внутри приложений не позволяют представить состояние клиента очень маленьким объектом, который ссылается на гораздо больший набор данных на сервере. Рассмотрим массовый многопользовательский шутер от первого лица: многое происходит быстро, и вы хотите передать минимальное количество информации, необходимой от клиента, к игровому хосту/серверу. Как мало вы могли бы получить это? Карта всех входных состояний; временная метка + диапазон неопределенности, ~110-битное поле для клавиатуры и несколько десятков байтов для ориентации мыши/джойстика и VR-шлема. Клиент будет прогнозировать, оптимистично отображать и отображать физические данные, а сервер будет извлекать огромное количество информации из небольшого состояния клиента и истории состояний и возвращать довольно большой объем данных для исправления и обновления всех клиентов ( все те же параметры для всех ближайших агентов, отправка активов), но этот поток клиентских запросов может по-прежнему удобно вписываться в запросы размером 2 КБ. И это может быть благом для архитектуры приложения.

Я не буду просить вас обучать меня Relay и GraphQL; Я только поверхностно изучил их, прежде чем их API достигли стабильной версии (сейчас они заблокированы?). Если вы все еще убеждены, что использование компонентов для выбора схем GraphQL, которые определяют зависимости данных, которые определяют, какие фактические данные необходимо получить, хороший план, тогда, может быть, пришло время взглянуть на них еще раз. У меня есть несколько трудных вопросов об этой архитектуре, но я собираюсь уйти от темы.

:пиво:

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

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

Я считаю, что этот уровень абстракции — правильный путь. Я использовал его в Relax CMS, и рабочий процесс и изоляция — это счастье для работы. Мало того, запрошенные данные не больше и не меньше того, что нужно пользовательскому интерфейсу, и это делается автоматически путем сбора данных, которые нужны каждому компоненту, и их слияния. Это делается Relate http://relax.github.io/relate/how-it-works.html, если вы заинтересованы в проверке. При этом я также могу включить любой компонент в любом месте своего приложения и быть уверенным, что он будет работать как независимый блок моего приложения.

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

@d4goxn Я тоже осмелюсь с вами поспорить :) В вашем исходном посте содержится утверждение, что представление — это функция данных, а данные — функция URL . Я бы не назвал это откровением. Это применимо практически к любому веб-сайту или любому веб-приложению, которое не является полностью случайным (есть вопрос, должно ли, например, состояние открытых диалоговых окон также быть частью URL-адреса). Поскольку у всех нас была математика в школе, мы знаем, что функцию можно составить. Таким образом, фактическое утверждение может заключаться в том, что то,

Для меня главный вопрос — как построить такую ​​функцию .

Предлагаемый вами подход очень похож на старые добрые серверные приложения MVC (например, Spring MVC является хорошим примером). Текущий URL-адрес активирует соответствующий метод контроллера, который _выполняет бизнес-логику_. Он извлекает все необходимые данные и передает их в представление. Моя проблема заключается в следующем: когда вы смотрите на сгенерированную веб-страницу, это своего рода иерархия компонентов (не обязательно компонентов React). Что вам нужно сделать, так это создать или сгенерировать иерархию из URL-адреса. Но это надо сделать дважды! Во-первых, вам нужно декодировать иерархию в контроллере, чтобы знать, какие данные вам нужно получить, а во-вторых, вам нужно декодировать иерархию представления, чтобы... ну... визуализировать фактическое представление. Я не думаю, что это очень _СУХОЙ_ подход.

Я не очень хорошо знаком с Spring MVC, но я очень часто использую другой фреймворк MVC (фреймворк PHP под названием Nette), который решает эту проблему очень похоже на то, как я хотел бы использовать React. Эта структура также поддерживает компоненты. Идея состоит в том, что для каждого компонента (например, формы) вы определяете в своем коде фабрику, которая отвечает за создание экземпляра компонента и особенно за _загрузку всех необходимых данных_. Затем такой компонент можно включить в любом месте представления. Поэтому, когда вы пишете HTML-код и создаете _представление_иерархии_, вы можете просто вставить компонент, а базовая фабрика позаботится о необходимой инициализации. Компонент ведет себя как небольшой независимый контроллер, у него есть собственный жизненный цикл, он обрабатывает свои зависимости, и его можно даже параметризовать из представления, что увеличивает его повторное использование.

Я также использовал этот подход с React на стороне клиента, и он кажется очень жизнеспособным. Компоненты React с самого начала обозначались как _controller-views_, и именно так я их и использую. У меня есть компоненты _controller_, которые отвечают за выборку данных, а затем у меня есть компоненты _view_, которые заботятся только о визуальных эффектах. Вся страница состоит из компонентов обоих типов. Они прекрасно разъединяются и легко используются повторно.

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

  • Основным инструментом является react-router . Он позволяет перехватывать асинхронный запрос данных по каждому URL-адресу и работает как на стороне клиента, так и на стороне сервера. Формально говоря, этот подход страдает от проблемы, о которой я упоминал ранее. Эти компоненты _controller_ невозможны. Но здесь важен дизайн react-router . Это позволяет определить как _иерархию_данных_, так и _иерархию представления/компонента_ в одном месте, а фактические определения маршрута также являются иерархическими. Это очень похоже на _React_.
  • Все состояние (данные) обрабатывается с помощью redux . Помимо всех других преимуществ, он сохраняет все состояние в одном объекте, что означает очень простое и естественное создание объекта на сервере и отправка его клиенту. Кроме того, благодаря подходу connect() нет необходимости передавать все данные с верхнего уровня вниз с помощью реквизитов (это было бы абсолютно безумием, учитывая текущий размер моего приложения).
  • Еще одна часть головоломки — повторный выбор, который позволяет нам сохранять состояние как можно меньше. А также значительно увеличивает коэффициент развязки.

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

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

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

@decodeman, если вам интересно, для Relate я сделал минимальный

@decodeman , FWIW, мы успешно использовали подход двойного рендеринга (например, то, на что вы намекаете) в большом производственном приложении. Он вдохновлен этим примером redux-saga , но общий подход должен хорошо работать с любым типом асинхронной загрузки. Вам просто нужно переместить загрузку в componentWillMount . Чтобы упростить управление, у нас также есть компоненты более высокого порядка, которые заботятся о распространенных сценариях загрузки (например, проверяют наличие свойства x и загружают, если nil.

Я также думаю, что было бы очень важно иметь возможность загружать данные внутри компонентов и иметь какой-то метод ленивого рендеринга. Возможно, чтобы обе стороны были довольны, создайте новый метод с именем asyncRenderToString и добавьте новое событие «жизненного цикла» с именем asyncOnLoad или что-то в этом роде. Это будет вызываться только в том случае, если вы вызываете метод asyncRender. Что касается дублирования кода между серверной и клиентской сторонами, это может быть исправлено разработчиками путем простого перемещения общего кода загрузки в отдельный метод и вызова этого метода из asyncOnLoad и componentMount . Обнаружение выполнения asyncOnLoad, вероятно, должно использовать возвращенное обещание (если возвращается undefined / метод не определен, он должен напрямую вызывать применимые события жизненного цикла, а затем отображать), в противном случае он ждет, пока обещание не будет разрешено.

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

Это было решено, так как он называется потоковой проверкой потока реакции.

В четверг, 3 ноября 2016, Флориан Krauthan [email protected]
написал:

Я также думаю, что было бы очень важно иметь возможность загружать данные в
компоненты и имеют какой-то ленивый метод рендеринга. Может быть, сделать оба
стороны счастливы создать новый метод с именем asyncRenderToString и добавить новый
Событие «жизненного цикла», называемое asyncOnLoad или что-то в этом роде. Это будет
вызываться только в том случае, если вы вызываете метод asyncRender. С точки зрения кода
дублирование между сервером и клиентом, это может быть исправлено разработчиками
просто перенести общий код загрузки в отдельный метод и вызвать его
метод из asyncOnLoad и componentMount. Обнаружение, если asyncOnLoad
done, вероятно, следует использовать возвращенный Promise (если undefined
возвращенный/метод не определен, он должен напрямую вызывать применимый
события жизненного цикла, а затем визуализировать), в противном случае он ждет, пока обещание
решает.

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


Вы получаете это, потому что подписаны на эту тему.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/facebook/react/issues/1739#issuecomment-258198533 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/ATnWLUIEJw4m1Y3A4oGDOBzP6_ajDcqIks5q6g4_gaJpZM4CHAWq
.

@ yamat124, если я ищу реагирующий поток, я нахожу только этот проект: https://github.com/aickin/react-dom-stream

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

Опять же, это все очень хакерские способы. Требуется, чтобы маршрутизация не зависела от дерева компонентов. И «Основной» компонент, который включает маршрут, должен знать все данные, которые необходимо загрузить (работает в 99% случаев, но не всегда). Оба были бы устаревшими, если бы у реакции был отложенный рендеринг компонента.

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

Next.js имеет async getInitialProps для рендеринга на стороне сервера, но, к сожалению, все еще нужно вызвать дочерний getInitialProps от родителей до самого корня. Клиент Apollo GraphQL также позволяет пройти по всему дереву, чтобы собрать требования к данным на стороне сервера, а затем отправить все обратно. Пример Next + Apollo: https://github.com/sedubois/realate.

Было бы здорово иметь асинхронные методы жизненного цикла компонентов React, чтобы объединить эти вещи вместе.

Только что узнал о SSR на этой неделе, надеюсь, то, что я пишу, имеет смысл 😄

/cc @nkzawa @stubailo

Кстати, componentWill/DidMount уже асинхронны, помогает ли это?

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

@sedubois React ждет разрешения обещания перед рендерингом? Иначе не поможет!

@olalonde кажется так: https://github.com/sedubois/react-async-poc/blob/1d41b6f77e789c4e0e9623ba1c54f5ed8d6b9912/src/App.js

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

@sedubois Обратите внимание, что в вашем примере componentWillMount

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

возвращает undefined и обещание, но на снимках экрана видно, что React не ждет (не может) обещания, поскольку отображает текст Loading... .

Изменить: исправлено.

@sedubois @polytypic Это также приведет к молчаливому игнорированию всех ошибок, возникающих в функции, поскольку Promise вообще не обрабатывается.

@polytypic @dantman Я не совсем понимаю, что подразумевается под «обещанием вообще не обрабатываться». Его ожидает оператор await . Вам нужно попытаться поймать ошибку, я обновил пример (используя сигнатуру загрузки/ошибки, аналогичную тому, что делает Apollo).

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

И визуализировать:

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

@sedubois Когда вы async выполняете функцию, она неявно возвращает обещание, любое throw в функции неявно превращается в отклонение этого обещания, а не в бросок вверх по стеку. Это означает, что componentWillMount теперь возвращает промис вызывающей стороне.

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

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

И даже попытка try..catch не защитит вас от этого полностью. Если setState выдаст ошибку из вашего улова или вы сделаете опечатку в улете, эта ошибка исчезнет, ​​и вы не будете знать, что что-то сломалось.

@sedubois На самом деле асинхронный метод всегда возвращает обещание в JavaScript (ну, в будущем JavaScript). Я был сбит с толку, думая о проблемах с асинхронным ожиданием в C#. _Извините за шум!_

Тем не менее, на скриншотах ясно видно, что React не ждет, пока обещание будет разрешено. Он вызывает render сразу после вызова componentWillMount и отображает текст Loading... . Затем он снова вызывает render после вызова setState . Если бы React дождался разрешения обещания, он вообще не отобразил бы текст Loading... . Вместо этого он будет ждать, пока промис разрешится, и только после этого вызовет render , что и является типом поведения, о котором идет речь в этой проблеме: иметь возможность выполнять асинхронный ввод-вывод во время рендеринга на стороне сервера.

@dantman @polytypic спасибо 👍 Да, конечно, внутри React нужны изменения, чтобы ждать ответа и корректно обрабатывать ошибки. Возможно, добавление await тут и там в его код могло бы помочь 😄

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

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

@sedubois Это должен быть другой стек методов; или критическое изменение. Текущие методы рендеринга React синхронизированы, поэтому нам понадобится другой метод рендеринга сервера, который возвращает обещание.

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

Мне нравится то, что предложил @fkrauthan . Метод жизненного цикла load который может возвращать обещание или undefined и дополнительную асинхронную функцию renderToStringAsync . Но load всегда следует вызывать как на клиенте, так и на сервере. Если мы используем render или renderToString возвращаемое обещание игнорируется, чтобы соответствовать сегодняшнему поведению. Если мы используем renderToStringAsync а load возвращает обещание, обещание должно быть разрешено перед рендерингом.

Почему бы, как Next.js , не добавить функцию жизненного цикла React async getInitialProps которая вызывается перед конструктором? Если такого метода нет, вызовите конструктор напрямую.

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

Похоже, что Next.js справляется со своей задачей, за исключением того, что его getInitialProps не подключены к жизненному циклу компонента React, поэтому его можно вызывать только для компонента верхнего уровня. Итак, идеи есть, и их можно просто объединить?

@sedubois Я не думаю, что getInitialProps - правильное место. Во-первых, люди, использующие ES6, не используют/не используют этот метод. Во-вторых, вы не хотите работать с initalProps (это просто значения по умолчанию), вы хотите работать поверх слияния initalProps + переданных реквизитов. Поэтому я предлагаю новое событие жизненного цикла, которое отправляется до componentWillMount .

Я думаю, что идея

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

люди, которые используют ES6, у них нет/используют этот метод

@fkrauthan, может быть, вы имеете в виду getInitialState ? Здесь я говорил о новом, getInitialProps (имя, используемое в Next.js).

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

@sedubois Даже тот. Например, я использую функцию es7 и определяю static props = {}; в своем теле класса. На мой взгляд, это делает код более приятным для чтения, и я уверен, что все больше и больше людей перейдут на него, как только es7 будет официально выпущен.

Примечание: свойства класса не являются функцией «ES7», потому что ES7 уже завершен, и единственной новой языковой функцией является оператор возведения в степень. То, что вы имеете в виду, - это предложение свойств класса этапа 2. Это еще не часть языка и может быть изменено или удалено.

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

Я упоминал ранее, но хорошим API будет что-то вроде следующего (с небольшим обновлением того, что я предложил ранее):

const virtualTree = ReactDOM.renderVirtual(rootEle);

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

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

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

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

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

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

@fkrauthan да, но виртуальные рендеры гораздо более эффективны, чем необходимость делать два рендеринга в строку, что, на мой взгляд, является основной проблемой здесь (производительность в SSR), это слабая часть React. Было проведено несколько экспериментов по улучшению реакции renderToString но команда реагирования не добилась реального прогресса в этом вопросе (не критикуя здесь, они делают потрясающую работу).

Я согласен с @bruno12mota , который также некоторое время

Это все сильно усложняет.

1.) С точки зрения кода (я просмотрел код, и сделать асинхронный рендеринг должно быть намного проще, чем создать рендерер, который просто использует VDom, а затем может быть сброшен в какой-то момент)
2.) Теперь вам также нужно пересмотреть события размонтирования. И это делает пользовательский код более сложным, чем необходимо. Например, загрузка данных может привести к отображению нового компонента, которому также необходимо загружать данные. Теперь вдруг библиотека должна выяснить, какой компонент в какой позиции уже загрузил данные и какой компонент содержит загрузку новых данных и прочее.

Я с @bruno12mota. VDom, возможно, является более «низкоуровневым» решением, но, вероятно, разработчикам React было бы проще реализовать его, у него не было бы проблем с обратной совместимостью, и он мог бы позволить сообществу экспериментировать с различными решениями более высокого уровня.

Например, одним из решений более высокого уровня может быть «компонент более высокого уровня», который оборачивает асинхронные методы componentWillMount , ожидает всех ожидающих обещаний и запускает VDom.renderToString() когда все разрешено. Я использую такую ​​стратегию для SSR в своем приложении, хотя, к сожалению, сейчас я делаю много избыточной выборки, потому что нет VDom.

Я думаю, что лучше всего было бы не менять какие-либо функции, которые у нас есть в настоящее время. Это означает, что getInitialState, componentWillMount или renderToString не будут вести себя по-разному.

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

Я думаю, мы могли бы добавить функцию «renderToStringAsync» в react-dom/server. Отличие от обычного renderToString будет в том, что он возвращает обещание.

Со стороны реализации единственное отличие будет заключаться в том, что асинхронная версия будет всякий раз, когда она создает экземпляр компонента, сначала вызывать и ждать "initialize()" для него, прежде чем он вызовет render(). Если компонент не имеет метода «инициализации», он может немедленно вызвать render().

Итак, если у нас есть следующий пример структуры приложения:

    ComponentB
    ComponentC
        ComponentD
        ComponentE

процесс будет:

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

Итак, в основном нам просто нужна новая функция «renderToStringAsync», которая использует новую необязательную функцию «инициализировать». Это было бы не так сложно.

У @VanCoding есть та же идея, о которой я думал некоторое время. Мне было интересно, может ли инициализация также вызываться автоматически в componentDidMount на клиенте?

Итак, на сервере это будет:

initialize()
render()

И на клиенте это будет:

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

У меня есть рабочая реализация рендеринга сервера реагирования с помощью react-router v4 и react-apollo с GraphQL.

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

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

Кстати, серверный рендеринг реализован с помощью Nashorn Script Engine (JVM).

Клиентский код рендеринга
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/main.js#L63 -L82

Код рендеринга сервера
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/main.js#L128 -L155

Есть одно важное место, чтобы отличить синхронизированный или асинхронный компонент в маршруте.
Синхронизированный компонент в маршруте
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/Route.js#L10

Асинхронный компонент в RouteAsync
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/src/routes/Counter/RouteAsync.js#L7 -L23

ПРИМЕЧАНИЕ: имя файла ДОЛЖНО быть Route.js или RouteAsync.js. Код всегда должен импортировать RouteAsync.js. Webpack заменит его на Route.js, если он создан для серверного рендеринга.
https://github.com/shendepu/react-ssr-starter-kit/blob/apollo/build/webpack.config.js#L72 -L80

Таким образом, будет две версии сборки dist и dist_ssr , dist для клиента, а dist_ssr для сервера, который имеет только два фрагмента: поставщик и приложение. . Состояние магазина экспортируется и встраивается в <script> файла index.html как __INITIAL_STATE__ . Причина наличия двух версий сборки в том, что ReactDomServer.renderToString() еще не поддерживает асинхронный компонент.

Единственная проблема с подходом сборки с двумя версиями заключается в том, что в режиме разработки появляется предупреждение:
React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:

Но поскольку единственная разница между версиями клиента и сервера заключается в Route.js и RouteAsync.js, предупреждение можно игнорировать, оно не отображается в рабочем режиме.

Для асинхронной выборки данных перед рендерингом компонента на стороне сервера:

  • Rest API: добавлен статический loadData при использовании компонента.
const { matchedRoutes, params } = matchRoutesToLocation(rootRoute.routes, 
                                                        location, [], {}, rootRoute.pattern)
matchedRoutes.filter(route => route.component.loadData).map(route =>
              route.component.loadData(store, params))

для проверки и выполнения loadData . matchedRoutes — это массив совпадающих маршрутов, родитель которых находится в индексе 0 . loadData могут выполняться последовательно или параллельно.

  • Запрос GraphQL: просто используйте getDataFromTree из react-apollo/server для выполнения всех запросов GraphQL.

Кстати: использование Nashorn Script Engine дает одну возможность оптимизации производительности: поскольку поставщик содержит библиотечный код и полифиллы, которые выполняются один раз и используются повторно, поэтому для последующих запросов выполняется только фрагмент приложения. Так как библиотеки перемещены в чанк поставщика, приложение содержит только код javascript в src, который имеет небольшой размер, поэтому это повышает производительность по сравнению с оценкой фрагмента поставщика каждый раз. (весь javascript компилируется один раз и кэшируется, выполняется для каждого запроса.)

Я солгал, что часть приложения содержит только код src. он также содержит babel-runtime и дублирует core-js/library ссылается babel-runtime . babel-runtime нельзя переместить в чанк поставщика, поскольку он не имеет index.js или основной записи, поэтому веб-пакет не может распознать его как модуль. но размер кода небольшой.

Давайте подробнее остановимся на примере @VanCoding: https://github.com/facebook/react/issues/1739#issuecomment -261577586

Возможно, инициализация не очень хорошее имя — имя должно как бы давать понять, что оно предназначено для использования только на сервере или в среде, где рендеринг откладывается до тех пор, пока эта функция не вернется — то есть не в клиенте. Идеи: getStaticProps , getServerProps , getInitialProps , getAsyncProps ...

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

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

Кроме того, я сделал для этого доказательство концепции примерно в 40 строках кода. Он не предназначен для промышленного использования, но мои тесты с различными компонентами прошли успешно.

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

В качестве альтернативы мы могли бы добавить опцию к renderToString, как в
renderToString(Component, {async: true})

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

@VanCoding у вас есть пример того, как использовать ваш код в качестве сторонней библиотеки?

@VanCoding круто! Но, честно говоря, я думаю, что будет сложно интегрировать это во внутренний алгоритм рендеринга React. Посетите https://github.com/facebook/react/blob/master/src/renderers/dom/stack/server/ReactServerRendering.js и заблудитесь во внутренностях реакции... IMO, нам действительно нужно мнение кого-то из основной.

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

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

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

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

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

@nmaro Ты прав. Это было бы полезно.

Каков статус по этому поводу?

@firasdib Думаю, мы все еще ждем комментариев разработчиков.

+1

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

Я думаю, что весь асинхронный рендеринг не нужен, если JavaScript обеспечивает правильный способ ожидания обещания. Затем мы можем передать параметр, указывающий погоду или нет, мы хотим, чтобы загрузка данных была синхронной (для SSR) или асинхронной (для реального веб-браузера). Логика загрузки данных может запустить промис в конструкторе любого компонента React, а ожидание можно выполнить в componentWillMount.

Однако, насколько я понимаю, разработчики React усомнились в полезности componentWillMount и рекомендовали, чтобы всю работу делал конструктор. В любом случае будет работать.

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

Суть проблемы в том, что функция рендеринга может иметь логику выбора компонентов для добавления в DOMTree. Вот что делает React таким мощным и плохо подходящим для рендеринга на стороне сервера.

ИМО, истинное решение состоит в том, чтобы иметь возможность регистрировать все выданные обещания загрузки данных с помощью реакции. Как только все обещания загрузки данных завершены, реакция перезванивает нам с результатом рендеринга. Это имеет ряд последствий:

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

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

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

Тем временем мы должны решить SSR в среде асинхронной загрузки данных.

  1. Вызовы загрузки данных должны быть получены из URL-адреса запроса (например, react-router). Соберите все промисы загрузки данных в единый список промисов.
  2. Используйте Promise.all(), который сам по себе является обещанием. Когда это обещание завершится, передайте загруженные данные в функцию рендеринга сервера.

Эта модель не привязана к определенной архитектуре дизайна. Кажется, что Flux/Redux может немного выиграть, но я не уверен, так как отказался от модели Redux в своем приложении.

Проблема в этой модели - пункт №1 выше. Простое приложение знает все вызовы загрузки данных. В сложном приложении с независимыми «модулями» эта модель требует некоторой репликации исходного дерева компонентов React и даже логики рендеринга.

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

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

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

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

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

Вот что я вижу.

Если бы я понял эти последствия SSR, я бы дважды подумал о полезности React для индексируемых приложений Google. Возможно, Flux — это решение, но Flux делает приложение на целый уровень сложнее, чем простое приложение React. Я был здесь. Вместо простой функции загрузки данных я должен преследовать свою логику в нескольких файлах. На теоретическом уровне это выглядело действительно очень хорошо, пока я не запустил проект. Новым людям было трудно начать. Нет такой проблемы после извлечения избыточности из кодовой базы. Я думал, что это ответ на все сложности дизайна пользовательского интерфейса :)

Начиная с конференции React Conf на этой неделе, после выхода React Fiber (React 16), они планируют работать над следующим (предположительно для React 17):

screen shot 2017-03-16 at 15 55 51

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

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

@VanCoding обновил мое сообщение выше, которое было немного неясным. Я имел в виду их планы на после того, как оптоволокно выйдет из строя.

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

https://github.com/siddharthkp/reaqt

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

@siddharthkp Я действительно не понимаю, что ваш компонент пытается решить? Если он поддерживает только asyncComponentWillMount для компонента React приложения верхнего уровня, зачем мне вообще использовать вашу библиотеку? Я мог бы просто разрешить это обещание снаружи, а затем вызвать render? Или я что-то пропустил?

@fkrauthan

Я мог бы просто разрешить это обещание снаружи, а затем вызвать render?

Вы правы, это именно то, что он делает.

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

он поддерживает только asyncComponentWillMount для компонента React приложения верхнего уровня.

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

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

@siddharthkp чем ваш репозиторий лучше Next.js (который также был представлен на React Conf BTW)?

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

В любом случае обсуждение сторонних библиотек ИМХО не по теме, нас интересует поддержка внутри самого React.

обсуждение сторонних библиотек ИМХО не по теме, нас интересует поддержка внутри самого React.

Я полностью согласен. Может обсуждать конкретные вопросы в ответных вопросах

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

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

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

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

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

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

Это действительно полезно для загрузки данных - в значительной степени вариант использования, для решения которого я его написал. Попробуйте! 😄

https://github.com/davnicwil/реакт-фронтлоад

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

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

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

Я придумал реализацию https://github.com/timurtu/react-render-async

Очень приятный интерфейс асинхронного компонента, @timurtu.

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

  • С одной стороны, учитывая SSR, я думаю, что выборка данных на уровне маршрута имеет наибольший смысл — вот почему:
    Извлечение данных Redux-First Router: решение 80% варианта использования асинхронного промежуточного программного обеспечения
  • рендеринг сервера с Redux теперь до глупости прост, вот пошаговое руководство с Redux-First Router о том, как сделать это так, как вы никогда раньше не видели: Server-Render как Pro /w Redux-First Router за 10 шагов
  • наконец, чтобы выполнить правильное разделение кода, вы также должны сделать и то, и другое серьезной проблемой , успешно достигнутой немногими, и никогда до сих пор не было общего пакета, чтобы помочь сообществу. React Universal Component + babel-plugin-universal-import + webpack-flush-chunksextract-css-chunks-webpack-plugin ) — это семейство пакетов, которые действительно решают этот вопрос. И впервые. По сути, он позволяет вам синхронно отображать компоненты на сервере, но, что более важно, передает точные фрагменты, визуализированные клиенту, для первоначального синхронного рендеринга на клиенте. Вы даже получаете таблицы стилей css chunk через плагин webpack. Это отличается от того, чем поделился main.js , синтаксического анализа, оценки, рендеринга и, наконец, через несколько секунд запроса динамического импорта. Это решения для решения проблем со вспышкой нестилизованного контента (FOUC), но не для встраивания на вашу страницу точных фрагментов, отображаемых на сервере. Универсальный — название моего семейства пакетов — позволяет вам делать это с гораздо меньшим временем до интерактивности (TTI) синхронным способом, который работает как традиционные веб-приложения. Вы можете узнать больше об этом в этой статье: React Universal Component 2.0 & babel-plugin-universal-import

Я знаю, что не рассматривал плюсы и минусы уровня маршрута и уровня компонентов , но первая ссылка в основном посвящена этому. В основном это сводится к тому, что уровень компонентов плох для SSR, если у вас есть вложенные компоненты, данные которых должны извлекаться последовательно (а не параллельно). И если у вас правильно есть только одна выборка для каждого маршрута, вам лучше формализовать контракт, прикрепив ваши зависимости данных к объекту маршрута, вместо того , Redux-First Router очень хорошо формализует это в виде «контрактов», которые делают работу с Redux такой приятной.

Redux-First Router делает многие старые парадигмы новыми, но удивительно подходит как недостающая часть экосистемы Redux. Если вы когда-либо хотели, чтобы маршрутизатор был родным для рабочего процесса Redux, попробуйте Redux-First Router. Он относительно новый, и я новичок в открытом исходном коде, но это то, над чем я работал в основном год, и за 2 месяца, когда оно было выпущено, оно набрало обороты. Люди любят это. Я очень надеюсь, что вы проверите это и дадите ему шанс :).

Вот также его стартовая статья, в которой объясняется, насколько это решение для Redux гораздо лучше, чем React Router, и как оно дает правильное видение, которое, к сожалению, упустил также превосходный Redux-Little Router:
Предварительная версия: Redux-First Router — шаг за пределы Redux-Little-Router

Основное различие между RLR и RFR заключается в том, что RFR отправляет другой тип действия для каждого маршрута (например, изменение URL), а не всегда просто LOCATION_CHANGED . Это позволяет вам переключать изменения URL-адресов как уникальные типы в ваших редьюсерах, как и любые другие действия. Это может показаться небольшим, но это имеет огромное значение. Из-за этого ему не требуются компоненты <Route /> , которые не нужны в мире Redux. Существует также огромный список функций, поддерживаемых Redux-First Router, от выборки данных в маршруте thunks до React Native, React Navigation, разделения кода, предварительной выборки и многого другого.

Хотелось бы услышать мысли людей.

@faceyspacey спасибо, я думаю, что это решает проблему, с которой я react-native-vector-icons может вернуть значок в качестве обещания. Это определенно другой взгляд на вещи, но он уменьшает componentWillMount или редукцию X_GET_START , X_GET_END и X_GET_ERROR шаблонных действий, которые связывают асинхронные компоненты с состоянием, когда вы действительно хотите сделать этот запрос только один раз, чтобы отобразить компонент. Если вы занимаетесь такими вещами, как опрос данных, то, вероятно, более целесообразно использовать состояние и использование избыточности.

@faceyspacey Я понимаю, что вы говорите об уровне маршрута, но не было бы лучше иметь более мелкие компоненты, которые полагаются только на выборку необходимых им данных при синхронном рендеринге всего остального вокруг них?

Лучше? Где? На React Native? В крупных организациях, таких как Facebook, в своих приложениях React Native?

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

Что касается React Native и SPA, конечно. Но тестировать компоненты со встроенными хранилищами данных очень сложно. У Apollo здесь есть кое-что, но в последний раз, когда я проверял, у них был длинный список задач, чтобы сделать тестирование компонентов с парными данными действительно правильным, то есть бесшовным. Я предпочитаю настройку, в которой у вас есть один магазин, который легко настроить и смоделировать. Вы можете повторно использовать то, как вы это делаете, во всех своих тестах. Затем тестирование компонентов представляет собой простой синхронный рендеринг с созданием моментальных снимков. Как только вы добавите данные в компоненты, тестирование React станет менее интуитивным. Но люди это делают и автоматизируют. Там меньше стандарта, и теперь он ближе к пользовательскому специальному коду. Более продуктивно иметь хранилище, которое можно заполнять асинхронно очень очевидным способом, а не иметь потенциально разные стратегии для каждого асинхронного компонента, который необходимо заполнить.

Я не против уровня компонентов, если нет SSR, и ваша стратегия тестирования безупречна и убрана. Если вы мегакорпорация, это имеет смысл, потому что тогда каждому разработчику не нужно касаться верхнего уровня маршрута и потенциально ломать его для других. Вот почему FB стал пионером этого маршрута с Relay и GraphQL. Дело в том, что только около 100 компаний в мире действительно находятся в этой лодке. Поэтому для большинства людей/приложений/компаний я рассматриваю колокейшн как рекламу, а не реальность. Как только вы разберетесь с уровнем маршрута и у вас будет пакет, который делает это действительно хорошо, как Redux-First Router , у вас появится гораздо менее запутанный и простой в разработке подход для команд, членам которых разрешено касаться уровня маршрута . Просто посмотрите демо на главной странице codeandbox:

https://www.codesandbox.io

В принципе, как только вы увидите, как это делается, я хотел бы услышать ваше мнение. Имейте в виду, что это СПА. Так что вам придется просмотреть демонстрационное репо или шаблон для примера SSR. Но это хорошее начало.

Я думаю, что самым простым способом будет поддержка асинхронного метода рендеринга (рендеринг возвращает обещание). Это позволит корневому компоненту ждать рендеринга дочерних компонентов, и, конечно, результат рендеринга будет обещанным. Я реализовал что-то подобное в preact здесь https://github.com/3axap4eHko/preact-async-example

@faceyspacey Спасибо за подробный ответ выше. Мне очень нравится идея Redux-First Router, он определенно решает все мои проблемы и делает вещи намного более прозрачными и чистыми, чем то, что было раньше.

Большое спасибо!

@raRaRa рада, что тебе понравилось! ... одна из самых больших проблем заключается в том, что до сих пор не было хорошей абстракции для выборки данных на уровне маршрута . React В React v3 были обратные вызовы, а в v4 отличный образец (особенно с пакетом react-router-config ), но он неочевиден и не первоклассен в v4. Это запоздалая мысль. Однако, что более важно, он не живет в Redux. Так что только сейчас у пользователей Redux впервые появилась полная абстракция «уровня маршрута».

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

Тем не менее, в конечном итоге RFR будет иметь интеграцию на уровне маршрута с Apollo. На уровне компонентов очень мало преимуществ, за исключением вышеупомянутых редких исключений. По крайней мере, в 80% случаев (и, возможно, в 100% в вашем приложении) вы можете определить все необходимые данные на основе маршрута. Если это невозможно, вероятно, потому, что вы получаете данные в своем «слое представления», которые очень хорошо можно переосмыслить, чтобы все они интерпретировались URL-адресом. Таким образом, даже с такими инструментами, как Apollo, вы начинаете заниматься проблемными анти-паттернами, которые, вероятно, в конечном итоге причинят вам боль, позволяя «состоянию» жить нигде, кроме слоя представления. Т.е. состояние, необходимое для выполнения этих выборок данных. Вам часто приходится уточнять точные параметры, используемые для получения данных. Таким образом, вы берете состояние и реквизиты и преобразуете 2, чтобы получить параметры непосредственно перед получением данных. Это то, что теперь находится на уровне представления и зависит от его механизмов повторного рендеринга (т. е. когда вызываются обработчики жизненного цикла). Каждое приложение в тот или иной момент заканчивает тем, что данные не извлекаются, когда они должны быть, или извлекаются в ответ на получение/изменение несвязанных реквизитов. И во всех приложениях вам нужно написать дополнительный код для защиты от несвязанных реквизитов, которые изменились. Т.е. реквизиты, которые не влияют на параметры, используемые для выборки данных.

Что касается React Native + Apollo, здесь проблема меньше, потому что у вас нет SSR и, следовательно, возможность последовательной выборки данных на сервере. Однако вам действительно нужна выборка данных на уровне компонентов, когда экраны такие маленькие. Обычно вы знаете, что нужно каждой «сцене» с точки зрения ее данных. Это не похоже на веб-панель рабочего стола с миллионом графиков, несколькими таблицами и т. д. Этот вариант использования, возможно, является самым большим основным местом, где уровень компонентов начинает иметь смысл. Но даже там вы можете указать все свои требования для всех этих виджетов в одном месте и получить их через Apollo. Я еще недостаточно обдумал это, но основная идея состоит в том, чтобы привязать ваши требования к данным GraphQL к маршруту, а затем в ваших компонентах подключиться к данным, как и к любым другим данным Redux. То есть, более конкретно, идея состоит в том, чтобы исключить как connect react-redux, так и эквивалент Apollo. Я просто хочу один, и мы хотим, чтобы он был более плоским. Использование Apollo требует деструктурирования более глубоко вложенных данных, чтобы получить доступ к тому, что вы действительно хотите в своих компонентах. Видение — это один из способов, который выглядит точно так же, как обычный Redux, плюс спецификации graphql на уровне маршрута.

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

Наконец, тестирование становится намного проще, если все ваши зависимости от данных и асинхронная работа отделены от компонентов. Разделение ответственности — это серьезный стимул для повышения производительности, когда вы можете тестировать свои компоненты, не беспокоясь о выборке данных. Конечно, в своих тестах вы должны заранее заполнить хранилище, выполнив эти асинхронные выборки, но, отделив их от ваших компонентов, вы можете легко формализовать такую ​​работу с помощью некоторых вспомогательных функций setup все ваши тесты используют для предварительной загрузки. заполните хранилище перед тестированием рендеринга компонентов :)

@faceyspacey Полностью согласен. Но я должен признать, что я не пробовал ни Apollo, ни GraphQL, я обязательно проверю их и посмотрю, как они подходят для моих вариантов использования.

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

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

Для меня Redux-First Router очень похож на часть контроллера в MVC, плюс маршруты. Что имеет большой смысл :)

Спасибо.

В конце концов, наш новый стек таков:

М: Редукс
В: реагировать
C: Redux-First Router (скоро будет переименован в «Rudy» )

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

Это может работать немного по-другому в новом стеке клиент-сначала по сравнению с традиционными MVC на стороне сервера, но в основном это то же самое разделение задач на 3.

Что касается разницы во взаимодействии/поведении между тремя, то в основном это то, что в прошлом контроллер получал модели на начальном этапе, а затем визуализировал с ними представления; и теперь это контроллер (RFR), немедленно отображающий представление (выбранное через «модель пользовательского интерфейса», то есть состояние Redux), а затем повторно отображающий представление во второй раз, как только контроллер находит модели предметной области (также хранящиеся в состоянии Redux).

Привет, ребята, посмотрите эту ссылку. вот пример рендеринга на стороне сервера данных https://github.com/bananaoomarang/isomorphic-redux

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

Любые изменения? Выпущено 16 реагирующих и большинство решений не работают

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

Мой случай использования

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

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

Почему бы не загрузить данные/контент вместо любопытного компонента?

Когда? У меня есть динамические страницы, и компонент может быть, а может и не быть.

Похоже, вам нужен условный рендеринг https://reactjs.org/docs/conditional-rendering.html

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

Использование @graphql от Apollo делает загрузку данных очень простой, а API-интерфейс на основе компонентов React-Router v4 обеспечивает простую компонуемую маршрутизацию. Оба ортогональны состоянию приложения в Redux.

Чтобы выполнить однопроходный потоковый рендеринг, ожидающий выборки данных, render() должна иметь возможность возвращать обещание (на клиенте вы делаете то же самое, что и сейчас, только на сервере вы возвращаете обещание).

Затем @graphql может просто вернуть обещание для render() после загрузки данных; все остальное — это просто React.

Несколько месяцев назад на JSConf Iceland я выступил с докладом, в котором описал будущие функции асинхронного рендеринга в React (см. вторую часть): https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react. -16.html. Речь идет о выборке данных на стороне клиента.

Теперь @acdlite рассказал о том, как те же концепции могут применяться для включения асинхронного ожидания данных на сервере в компонентах React и постепенного сброса разметки по мере ее готовности: https://www.youtube.com/watch?v= z-6JC0_cOns

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

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

Можно использовать ReactDOMServer.renderToStaticMarkup для обхода дерева, поместить закладку там, где оно достигает асинхронных зависимостей (обычно загружая данные из API), и продолжать обход дерева по мере разрешения этих зависимостей, пока все дерево не будет разрешено, а затем выполните окончательный ReactDOMServer.renderToString для фактического рендеринга в HTML, который будет синхронным, поскольку все зависимости теперь разрешены.

Я использовал этот подход в react-baconjs : render-to-html.js . Это было вдохновлено комментарием @gaearon, сделанным в другом выпуске, в котором рекомендовался такой подход.

@steve-taylor да, это работает. Это также обходной путь, который я использую в react-frontload, который является более универсальным решением, которое будет работать в любом приложении React.

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

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

@davnicwil хорошая работа! Я подумал об обобщении конкретного решения async SSR. Обрабатывает ли response-frontload неограниченную глубину асинхронности? Спрашивает, потому что вы сказали «дважды».

@ Стив-Тейлор, ура! Да, если под глубиной вы подразумеваете глубину компонента в дереве, она не ограничена. Он позволяет вам объявить функцию загрузки данных в самом Компоненте (с Компонентом более высокого порядка), а затем, когда это встречается при первом рендеринге, он запускается, и обещание собирается.

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

Тем не менее, проблема вложенности (на самом деле, возможно, вся эта проблема рендеринга асинхронного сервера) может быть решена с помощью материала Suspense, который появится в React в ближайшем будущем. 🤞

К вашему сведению, мы начали работу над этим.

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

Потрясающе @gaearon!

@davnicwil react-baconjs поддерживает неограниченную глубину.

@gaearon Интересно, сделает ли это возможной поддержку наблюдаемых в create-subscription, чтобы я мог преобразовать наблюдаемый запускающий JSX в компонент реакции?

Мне бы хотелось иметь эту функцию по двум причинам. Во-первых, в моем приложении состояние хранится в веб-воркере. Таким образом, хотя получение битов этого состояния, которые требуются компоненту, является асинхронной операцией, это занимает около 5 мс, и React не имеет смысла отображать что-либо в ожидании данных. Во-вторых, прямо сейчас нет универсального способа преобразовать наблюдаемое в компонент: если ваш наблюдаемый объект испускает первое значение синхронно, то вам нужно подписаться, чтобы получить это значение, затем отменить подписку, а затем снова подписаться, чтобы прослушивать изменения (это Replay предметный пример в документации create-observable). И если он генерирует первое значение асинхронно, тогда вы сначала визуализируете null и слушаете изменения.

@ steve-taylor react-frontload теперь также поддерживает неограниченную глубину вложения компонентов с выпуском 1.0.7 .

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

Мы сталкивались с этой проблемой раньше, когда наше приложение было создано с помощью React Hooks, а затем мы создали пакет react-use-api для ее решения, который представляет собой настраиваемый хук, который извлекает данные API и поддерживает SSR. Я надеюсь, что пакет поможет нуждающимся.

Тем не менее, мы все еще с нетерпением ждем официального решения. Как говорит react-frontload , нет встроенного способа ждать загрузки асинхронных данных после начала рендеринга.

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