首页 > web前端 > js教程 > 处理服务器渲染的反应中的异步API

处理服务器渲染的反应中的异步API

William Shakespeare
发布: 2025-02-16 11:52:10
原创
745 人浏览过

Dealing with Asynchronous APIs in Server-rendered React

要点总结

  • React 代码的服务端渲染有助于缩短加载时间并提高 SEO 灵活性,但由于需要在知道所需数据之前渲染应用程序,因此处理异步 API 可能会面临挑战。
  • 现有的解决方案,例如 Next.js、Redux Connect 和 react-frontload,在处理服务端渲染的 React 代码中的异步 API 时各有优缺点。
  • 可以通过执行两次服务端渲染来实现自定义解决方案:第一次处理 API 调用和异步操作,第二次使用获取的数据进行最终页面渲染。
  • 自定义解决方案需要仔细处理组件中的不同状态,包括预取、后取、预渲染和后端渲染。这可以通过组件代码中的复杂 if 语句来实现。
  • 自定义解决方案还需要更改 index.html 文件,以便将预取数据作为页面请求的一部分发送,并将其添加到搜索和替换中。如果使用脚本标签,则需要进行 base-64 编码。

如果您曾经制作过基本的 React 应用页面,它可能会存在 SEO 差和性能问题,尤其是在较慢的设备上。您可以添加传统的网页服务端渲染(通常使用 NodeJS),但这并非一个简单的过程,尤其是在处理异步 API 时。

服务端渲染代码的两个主要好处是:

  • 加快加载速度
  • 提高 SEO 灵活性

请记住,Google 会等待您的 JavaScript 加载,因此标题内容等简单内容会无问题地更改。(不过,我无法说明其他搜索引擎的情况,或者这有多可靠。)

在这篇文章中,我将讨论在使用服务端渲染的 React 代码时如何从异步 API 获取数据。React 代码具有内置于 JavaScript 中的整个应用程序结构。这意味着,与具有控制器的传统 MVC 模式不同,您在应用程序渲染之前不知道需要什么数据。使用像 Create React App 这样的框架,您可以快速创建高质量的工作应用程序,但它要求您仅在客户端处理渲染。这存在性能问题,以及 SEO/数据问题,您可以在其中根据需要更改头部。

问题

React 主要同步渲染,因此如果您没有数据,则会渲染加载屏幕并等待数据到来。这在服务器端效果不佳,因为您在渲染之前不知道需要什么,或者您知道需要什么,但您已经渲染了。

查看此标准渲染方法:

ReactDOM.render(
  <provider> store={store}></provider>
    <browserrouter></browserrouter>
      <app></app>
    >
  >
, document.getElementById('root')
)
登录后复制
登录后复制
登录后复制
登录后复制

问题:

  1. 这是一个寻找根元素的 DOM 渲染。这在我的服务器上不存在,因此我们必须将其分开。
  2. 我们无法访问主根元素之外的任何内容。我们无法设置 Facebook 标签、标题、描述、各种 SEO 标签,并且我们无法控制元素外部的其余 DOM,尤其是头部。
  3. 我们提供了一些状态,但服务器和客户端具有不同的状态。我们需要考虑如何处理该状态(在本例中为 Redux)。

因此,我在这里使用了两个库,它们非常流行,因此希望它可以应用于您使用的其他库。

Redux:存储服务器和客户端同步的状态是一个噩梦般的问题。它非常昂贵,并且通常会导致复杂的错误。在服务器端,理想情况下,除了足以使事情正常工作并正确渲染之外,您不想使用 Redux 做任何事情。(您仍然可以照常使用它;只需设置足够的状态使其看起来像客户端。)如果您想尝试,请查看各种分布式系统指南作为起点。

React-Router:仅供参考,这是 v4 版本,这是默认安装的版本,但如果您有较旧的现有项目,则会有很大不同。您需要确保在服务器端和客户端处理路由,并且使用 v4——它在这方面非常出色。

毕竟,如果您需要进行数据库调用怎么办?这突然成为一个大问题,因为它是非同步的,并且位于您的组件内部。当然,这不是一个新问题:在官方 React 存储库中查看它。

您必须进行渲染才能确定需要哪些依赖项——这些依赖项需要在运行时确定——并在提供给客户端之前获取这些依赖项。

现有解决方案

下面,我将回顾当前提供的用于解决此问题的解决方案。

Next.js

在我们开始之前,如果您想要生产环境的服务端渲染的 React 代码或通用应用程序,Next.js 是您的理想选择。它有效、简洁,并且有 Zeit 支持。

但是,它是有主见的,您必须使用他们的工具链,并且他们处理异步数据加载的方式不一定那么灵活。

查看 Next.js 存储库文档中的这段直接复制内容:

ReactDOM.render(
  <provider> store={store}></provider>
    <browserrouter></browserrouter>
      <app></app>
    >
  >
, document.getElementById('root')
)
登录后复制
登录后复制
登录后复制
登录后复制

getInitialProps 是关键,它返回一个 promise,该 promise 解析为一个填充 props 的对象,并且仅在页面上。最棒的是,这只是内置到他们的工具链中:添加它即可工作,无需任何工作!

那么如何获取数据库数据呢?您进行 API 调用。您不想?好吧,太糟糕了。(好的,您可以添加自定义内容,但您必须自己完全实现它。)但是,如果您考虑一下,这是一个非常合理且通常来说是良好的实践,因为否则,您的客户端仍然会进行相同的 API 调用,并且服务器上的延迟几乎可以忽略不计。

您还可以访问的内容受到限制——几乎只是请求对象;同样,这似乎是良好的实践,因为您无法访问您的状态,而您的状态在服务器和客户端上本来就不同。哦,如果您之前没有注意到,它只适用于顶级页面组件。

Redux Connect

Redux Connect 是一个非常有主见的服务器端渲染器,具有不错的理念,但是如果您不使用他们描述的所有工具,这可能不适合您。此包有很多内容,但它非常复杂,尚未升级到 React Router v4。有很多设置,但让我们来看最重要的部分,只是为了学习一些经验教训:

ReactDOM.render(
  <provider> store={store}></provider>
    <browserrouter></browserrouter>
      <app></app>
    >
  >
, document.getElementById('root')
)
登录后复制
登录后复制
登录后复制
登录后复制

装饰器在 JavaScript 中不是标准的。在撰写本文时,它们处于第 2 阶段,因此请谨慎使用。这只是添加高阶组件的另一种方式。这个想法很简单:密钥是传递给您的 props 的内容,然后您有一系列 promise,它们会解析并传入。这看起来不错。也许另一种选择就是这个:

import React from 'react'
export default class extends React.Component {
  static async getInitialProps ({ req }) {
    return req
      ? { userAgent: req.headers['user-agent'] }
      : { userAgent: navigator.userAgent }
  }
  render () {
    return <div>
      Hello World {this.props.userAgent}
    </div>
  }
}
登录后复制
登录后复制

使用 JavaScript 可以做到这一点,而且不会出现太多问题。

react-frontload

react-frontload 存储库没有很多文档或解释,但我所能获得的最佳理解可能来自测试(例如这个测试)和阅读源代码。当某些内容被挂载时,它会被添加到 promise 队列中,当该队列解析时,它会被提供服务。它所做的事情非常好,尽管很难推荐一些没有良好文档、维护或使用的内容:

// 1. 连接您的数据,类似于 react-redux @connect
@asyncConnect([{
  key: 'lunch',
  promise: ({ params, helpers }) => Promise.resolve({ id: 1, name: 'Borsch' })
}])
class App extends React.Component {
  render() {
    // 2. 将数据作为 props 访问
    const lunch = this.props.lunch
    return (
      <div>{lunch.name}</div>
    )
  }
}
登录后复制
登录后复制

寻找更好的解决方案

以上解决方案都没有真正符合我对库的灵活性和简单性的期望,因此我现在将介绍我自己的实现。目标不是编写包,而是让您了解如何根据您的用例编写自己的包。

此示例解决方案的存储库位于此处。

理论

其背后的想法相对简单,尽管最终会产生相当多的代码。这是为了概述我们正在讨论的想法。

服务器必须渲染 React 代码两次,我们只会为此使用 renderToString。我们希望在第一次和第二次渲染之间保持上下文。在我们的第一次渲染中,我们试图消除任何 API 调用、promise 和异步操作。在我们的第二次渲染中,我们希望获取我们获得的所有数据并将其放回我们的上下文中,从而渲染我们的工作页面以进行分发。这也意味着应用程序代码需要根据上下文执行操作(或不执行操作),例如是否在服务器上或浏览器上,以及在任一情况下是否正在获取数据。

此外,我们可以根据需要自定义它。在本例中,我们根据上下文更改状态代码和头部。

第一次渲染

在您的代码中,您需要知道您是在服务器上还是在浏览器上工作,理想情况下,您希望对它进行复杂控制。使用 React Router,您可以获得一个静态上下文 prop,这很棒,所以我们将使用它。目前,我们只是添加了一个数据对象和请求数据,正如我们从 Next.js中学到的那样。我们的 API 在服务器和客户端之间有所不同,因此您需要提供一个服务器 API,最好与您的客户端 API 具有相似的接口:

ReactDOM.render(
  <provider> store={store}></provider>
    <browserrouter></browserrouter>
      <app></app>
    >
  >
, document.getElementById('root')
)
登录后复制
登录后复制
登录后复制
登录后复制

第二次渲染

在第一次渲染之后,我们将获取那些挂起的 promise 并等待这些 promise 完成,然后重新渲染,更新上下文:

import React from 'react'
export default class extends React.Component {
  static async getInitialProps ({ req }) {
    return req
      ? { userAgent: req.headers['user-agent'] }
      : { userAgent: navigator.userAgent }
  }
  render () {
    return <div>
      Hello World {this.props.userAgent}
    </div>
  }
}
登录后复制
登录后复制

App

快速从我们的服务器跳转到应用程序代码:在我们任何具有路由器连接的组件中,我们现在都可以获得它:

// 1. 连接您的数据,类似于 react-redux @connect
@asyncConnect([{
  key: 'lunch',
  promise: ({ params, helpers }) => Promise.resolve({ id: 1, name: 'Borsch' })
}])
class App extends React.Component {
  render() {
    // 2. 将数据作为 props 访问
    const lunch = this.props.lunch
    return (
      <div>{lunch.name}</div>
    )
  }
}
登录后复制
登录后复制

哇,这有很多复杂的代码。在这个阶段,您可能想要采用更具中继的方法,在该方法中,您将数据获取代码分离到另一个组件中。

此组件由您可能熟悉的组件构成——渲染步骤和 componentWillMount 步骤。四阶段 if 语句处理不同的状态——预取、后取、预渲染和后端渲染。我们还在数据加载后添加到头部。

最后,还有一个获取数据步骤。理想情况下,您的 API 和数据库具有相同的 API,这使得执行相同。您可能希望将这些放入 Thunk 或 Saga 中的操作中,以使其更具可扩展性。

查看文章“服务端 React 渲染”和存储库 React 服务端渲染以了解更多信息。请记住,您仍然需要处理数据未加载的状态!您只会在第一次加载时进行服务器端渲染,因此您将在后续页面上显示加载屏幕。

更改 index.html 以添加数据

我们需要将任何预取数据作为页面请求的一部分发送,因此我们将添加一个脚本标签:

@asyncConnect([{
  lunch: ({ params, helpers }) => Promise.resolve({ id: 1, name: 'Borsch' })
}])
登录后复制

服务

然后我们需要将其添加到我们的搜索和替换中。但是,HTML 使用非常基本的脚本标签查找器,因此如果您有脚本标签,则需要对其进行 base-64 编码。此外,不要忘记我们的头部标签!

const App = () => (
  <frontload>isServer</frontload>
    <component1> entityId='1' store={store}></component1>
  >
)

return frontloadServerRender(() => (
  render(<app></app>)
)).then((serverRenderedMarkup) => {
  console.log(serverRenderedMarkup)
})
登录后复制

我们还处理状态代码更改——例如,对于 404——因此,如果您有 404 页面,您可以这样做:

const context = {data: {}, head: [], req, api}
const store = configureStore()
renderToString(
  <provider> store={store}></provider>
    <staticrouter> location={req.url} context={context}>
      <app></app>
    >
  >
)
登录后复制

总结

如果您不确定自己在做什么,只需使用 Next.js。它专为服务端渲染和通用应用程序而设计,或者如果您希望手动执行所有操作的灵活性,则可以按照您想要的方式进行。一个例子可能包括您在子组件中而不是在页面级别进行数据获取。

希望本文能帮助您入门!不要忘记查看 GitHub 存储库以获取可行的实现。

关于异步 API 和服务端渲染 React 的常见问题解答 (FAQ)

服务端渲染和客户端渲染在 React 中有什么区别?

服务端渲染 (SSR) 和客户端渲染 (CSR) 是渲染网页的两种不同方法。在 SSR 中,服务器会响应请求生成页面的完整 HTML,然后将其发送到客户端。这会导致更快的初始页面加载时间,并且有利于 SEO。但是,这可能会导致页面转换速度变慢,因为每次请求都需要渲染整个页面。另一方面,CSR 意味着渲染是在浏览器中使用 JavaScript 进行的。这会导致初始页面加载时间变慢,但页面转换速度更快,因为只需要重新渲染必要的组件。

如何在我的客户端渲染的 React 应用程序中发出服务器端请求?

要在客户端渲染的 React 应用程序中发出服务器端请求,您可以使用 fetch API 或 axios 等库。您可以在 componentDidMount 生命周期方法中或在使用函数组件时在 useEffect 挂钩内发出请求。然后可以将响应设置为状态并在您的组件中使用。

为什么我的全局变量在 React 中执行两次?

这可能是由于 React 批处理状态更新的方式造成的。如果您在 React 组件内更新全局变量,则由于 setState 的异步性质,它可能会更新两次。为避免这种情况,您可以使用 setState 的函数形式,这可以确保状态更新基于先前状态,而不是当前状态。

如何在服务端渲染的 React 中使用异步 API?

要在服务端渲染的 React 中使用异步 API,您可以在服务器端代码中使用 async/await 语法。这允许您在渲染页面之前等待 API 响应。您可以使用 axios 等库来发出 API 请求。

服务端渲染在 React 中有哪些好处?

服务端渲染在 React 中有很多好处。它提高了初始页面加载时间,这可以带来更好的用户体验。它还提高了 SEO,因为搜索引擎爬虫可以更容易地索引服务端渲染的内容。此外,它允许更一致的初始状态,因为相同的代码在服务器和客户端上运行。

如何在使用服务端渲染的 React 中的异步 API 时处理错误?

您可以使用 try/catch 块在异步函数中处理错误。这允许您捕获在发出 API 请求时发生的任何错误并适当地处理它们,例如通过渲染错误消息。

我可以在服务端渲染的 React 中使用钩子吗?

是的,您可以在服务端渲染的 React 中使用钩子。但是,请记住,钩子只能在函数组件中使用,而不能在类组件中使用。此外,某些钩子(例如 useEffect)不会在服务器上运行,因此您需要确保您的代码可以处理这种情况。

如何提高服务端渲染的 React 应用程序的性能?

提高服务端渲染的 React 应用程序性能的方法有很多。您可以使用代码分割,只为每个页面加载必要的代码。您还可以使用缓存来避免重新渲染未更改的页面。此外,优化服务器端代码可以帮助提高性能。

如何测试我的服务端渲染的 React 应用程序?

您可以使用 Jest 和 React Testing Library 等测试库来测试您的服务端渲染的 React 应用程序。这些库允许您隔离测试组件并确保它们正确渲染。

我可以将服务端渲染与 Next.js 一起使用吗?

是的,Next.js 是一个用于 React 的框架,它开箱即用地支持服务端渲染。它提供了一个简单的服务端渲染 API,还支持静态站点生成和客户端渲染。

以上是处理服务器渲染的反应中的异步API的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板