Как выполнить рендеринг на стороне сервера в React с возможностью загрузки и извлечения данных для компонентов (например Next.js )?

#javascript #reactjs #react-router #server-side-rendering #react-loadable

#javascript #reactjs #react-маршрутизатор #Рендеринг на стороне сервера #react-загружаемый

Вопрос:

Как я могу иметь Next.js Например, выборка данных (getInitialProps) с помощью маршрутизатора React и возможность загрузки React с помощью Razzle. У меня была выборка данных без react-loadable, но без разделения кода пакет приложений слишком большой, и загрузка страницы для клиентов занимает слишком много времени.

этот код работает, но я просто не понимаю, что я делал около года назад (он немного отличается от предыдущего примера)

     const promises = routes
        .map((route) => {
            const match = matchPath(req.url, route)
            if (match amp;amp; route.component) {
                let promise

                if (typeof route.component.preload === "function") {
                    promise = route.component.preload()
                } else {
                    promise = Promise.resolve(route.component)
                }

                return promise
                    .then((res) => {
                        return res amp;amp; res.__esModule ? res.default : res
                    })
                    .then((component) => {
                        let promises = []

                        // STATIC INTI ACTION
                        promises.push(store.dispatch(getToken()))

                        if (component.initialAction) {
                            let results = component.initialAction({ match })

                            results = Array.isArray(results)
                                ? results
                                : [results]
                            results.forEach((result) => {
                                promises.push(store.dispatch(result))
                            })
                        }
                        return promises
                    })
            }

            return null
        })
        .filter((el) => el !== null)

    // page not found
    if (promises.length === 0) {
        renderTree(req, res, store)
    } else {
        Promise.all(promises.map((data) => data.then((moreData) => moreData)))
            .then((data) => {
                Promise.all(data[0]).then(() => renderTree(req, res, store))
            })
  

Server.js

 const promises = []

routes.some((route) => {
    const match = matchPath(req.url, route);
    if (match) {
        // route.component is React Loadable Component so getInitialData is undefined
        if (route.component.getInitialData) {
            promises.push(route.component.getInitialData({ match, req, res }))
        }
        return true;
    }
    return false;
});

Promise.all(promises)
    .then(renderReact)
    .catch(handleError)

// and at the end i will call
Loadable.preloadAll()
  .then(sendResponseToUser)
  

маршруты:

 [
    {
        path: "/",
        exact: true,
        component: Loadable({
            loader: () => import("@views/Home"),
            loading: Loading,
        }),
    },
    {
        path: "/shop",
        exact: true,
        component: Loadable({
            loader: () => import("@views/Shop"),
            loading: Loading,
        }),
    },
]
  

Мои компоненты такие:

 class Home extends React.Component {
  // This works similarly to Next.js's `getInitialProps`
  static getInitialData({ match, req, res }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({
          text: `
This text is server rendered if and only if it's the initial render.
Go to another route.
          `,
          currentRoute: match.pathname,
        });
      }, 500);
    });
  }

  render() {
    return <p>this is just a test</p>
  }

}
  

Загружаемый компонент React имеет метод preload (), который может загружать компонент, поэтому я попробовал:
route.component.preload() но это не работает.

Я попробовал loadable-components, и с этим тоже та же проблема, но я могу заменить react-loadable на loadable-components (моя предпочтительная библиотека — loadable-components, потому что с StrictMode все в порядке).

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

Ответ №1:

Вот как я это сделал.

Он работает с обоими. загружаемые компоненты и обычные компоненты.

Внутри matchRoutes(). проверьте наличие component.preload.

Если это true, то разрешите ему загрузить component.preload() .

Это вернет обещание, и после разрешения обещания мы получим наш компонент. Затем проверьте наличие loadData / getInitialProps / fetchData, какое бы имя статического метода мы ни использовали.

return res.default.loadData ? res.default.loadData(store) : null;

вы можете вызвать запрос на выборку на стороне сервера .. я называю это loadData ()

Всегда помните о возврате обещания, потому что это цепочка обещаний.

Как и в ваших действиях redux, всегда не забывайте возвращать dispatch() .

 app.get('*', (req, res) => {
  // store instance for every incoming request
  const store = createStore(req);

  

  // loadData from api... before rendering
  const promises = matchRoutes(Routes, req.path)
    .map((comp) => {
      console.log(comp, comp.route.component.loadData)
      if (comp.route.component.preload){
        console.log("LoadableComponent")
        comp.route.component.preload().then(res => {
          return res.default.loadData ? res.default.loadData(store) : null;
        })
      }
      return comp.route.component.loadData ? comp.route.component.loadData(store) : null;
    })
    .map(promise => {
      if (promise) {
        return new Promise((resolve, reject) => {
          promise.then(resolve).catch(resolve);
        });
      }
    });


  Promise.all(promises).then(() => {
    console.log(store.getState())
    const context = {};
    const content = renderer(req, store, context);

    if (context.url) {
      return res.redirect(301, context.url);
    }
    if (context.notFound) {
      res.status(404);
    }
    

    Loadable.preloadAll().then(() => {
      res.send(content);
    });
  });
});  

Комментарии:

1. В настоящее время я пытаюсь использовать этот подход, но вызов метода предварительной загрузки возвращает undefined в цепочку обещаний. Можете ли вы просветить меня о том, что может быть не так? в операторе if comp.route.component.preload определено и действительно является функцией, но затем я получаю ошибку ‘TypeError: не удается прочитать свойство’, а затем ‘неопределенной’