Skip to main content
Version: 7.x

服务端渲染

本指南将介绍如何使用 React Native for Web 和 React Navigation 在服务器上渲染 React Native 应用。我们将涵盖以下情况:

¥This guide will cover how to server render your React Native app using React Native for Web and React Navigation. We'll cover the following cases:

  1. 根据请求 URL 渲染正确的布局

    ¥Rendering the correct layout depending on the request URL

  2. 根据焦点屏幕设置适当的页面元数据

    ¥Setting appropriate page metadata based on the focused screen

::: warning 警告

服务器渲染支持目前有限。由于缺少媒体查询等 API,无法提供无缝的 SSR 体验。此外,许多第三方库通常与服务器渲染配合不佳。

¥Server rendering support is currently limited. It's not possible to provide a seamless SSR experience due to a lack of APIs such as media queries. In addition, many third-party libraries often don't work well with server rendering.

:::

先决条件

¥Pre-requisites

在按照指南进行操作之前,请确保你的应用已在服务器上正常渲染。为此,你需要确保以下几点:

¥Before you follow the guide, make sure that your app already renders fine on server. To do that, you will need to ensure the following:

渲染应用

¥Rendering the app

首先,让我们看一个在不涉及 React Navigation 的情况下如何执行 使用 React Native Web 进行服务器渲染 的示例:

¥First, let's take a look at an example of how you'd do server rendering with React Native Web without involving React Navigation:

import { AppRegistry } from 'react-native-web';
import ReactDOMServer from 'react-dom/server';
import App from './src/App';

const { element, getStyleElement } = AppRegistry.getApplication('App');

const html = ReactDOMServer.renderToString(element);
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());

const document = `
<!DOCTYPE html>
<html style="height: 100%">
<meta charset="utf-8">
<meta httpEquiv="X-UA-Compatible" content="IE=edge">
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
>
${css}
<body style="min-height: 100%">
<div id="root" style="display: flex; min-height: 100vh">
${html}
</div>
`;

这里,./src/AppAppRegistry.registerComponent('App', () => App) 所在的文件。

¥Here, ./src/App is the file where you have AppRegistry.registerComponent('App', () => App).

如果你在应用中使用 React Navigation,这将渲染由你的主页渲染的屏幕。但是,如果你的应用中有 配置链接,你需要在服务器上为请求 URL 渲染正确的屏幕,以便它与客户端上渲染的内容相匹配。

¥If you're using React Navigation in your app, this will render the screens rendered by your home page. However, if you have configured links in your app, you'd want to render the correct screens for the request URL on server so that it matches what'll be rendered on the client.

我们可以通过在 location 属性中传递此信息来使用 ServerContainer 来做到这一点。例如,对于 Koa,你可以使用上下文参数中的 pathsearch 属性:

¥We can use the ServerContainer to do that by passing this info in the location prop. For example, with Koa, you can use the path and search properties from the context argument:

app.use(async (ctx) => {
const location = new URL(ctx.url, 'https://example.org/');

const { element, getStyleElement } = AppRegistry.getApplication('App');

const html = ReactDOMServer.renderToString(
<ServerContainer location={location}>{element}</ServerContainer>
);

const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());

const document = `
<!DOCTYPE html>
<html style="height: 100%">
<meta charset="utf-8">
<meta httpEquiv="X-UA-Compatible" content="IE=edge">
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
>
${css}
<body style="min-height: 100%">
<div id="root" style="display: flex; min-height: 100vh">
${html}
</div>
`;

ctx.body = document;
});

你可能还想为搜索引擎、打开图表等设置正确的文档标题和描述。为此,你可以将 ref 传递给容器,该容器将为你提供当前屏幕的选项。

¥You may also want to set the correct document title and descriptions for search engines, open graph etc. To do that, you can pass a ref to the container which will give you the current screen's options.

app.use(async (ctx) => {
const location = new URL(ctx.url, 'https://example.org/');

const { element, getStyleElement } = AppRegistry.getApplication('App');

const ref = React.createRef<ServerContainerRef>();

const html = ReactDOMServer.renderToString(
<ServerContainer
ref={ref}
location={location}
>
{element}
</ServerContainer>
);

const css = ReactDOMServer.renderToStaticMarkup(getStyleElement());

const options = ref.current?.getCurrentOptions();

const document = `
<!DOCTYPE html>
<html style="height: 100%">
<meta charset="utf-8">
<meta httpEquiv="X-UA-Compatible" content="IE=edge">
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
>
${css}
<title>${options.title}</title>
<body style="min-height: 100%">
<div id="root" style="display: flex; min-height: 100vh">
${html}
</div>
`;

ctx.body = document;
});

确保你已为屏幕指定了 title 选项:

¥Make sure that you have specified a title option for your screens:

const Stack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeScreen,
options: {
title: 'My App',
},
},
Profile: {
screen: ProfileScreen,
options: ({ route }) => ({
title: `${route.params.name}'s Profile`,
}),
},
},
});

处理 404 或其他状态代码

¥Handling 404 or other status codes

渲染无效 URL 的屏幕 时,我们还应该从服务器返回一个 404 状态码。

¥When rendering a screen for an invalid URL, we should also return a 404 status code from the server.

首先,我们需要创建一个上下文,我们将在其中附加状态代码。为此,请将以下代码放入一个单独的文件中,我们将在服务器和客户端上导入该文件:

¥First, we need to create a context where we'll attach the status code. To do this, place the following code in a separate file that we will be importing on both the server and client:

import * as React from 'react';

const StatusCodeContext = React.createContext();

export default StatusCodeContext;

然后,我们需要在 NotFound 屏幕中使用上下文。在这里,我们添加一个值为 404code 属性来表示未找到屏幕:

¥Then, we need to use the context in our NotFound screen. Here, we add a code property with the value of 404 to signal that the screen was not found:

function NotFound() {
const status = React.useContext(StatusCodeContext);

if (status) {
staus.code = 404;
}

return (
<View>
<Text>Oops! This URL doesn't exist.</Text>
</View>
);
}

如果需要,你还可以在此对象中附加其他信息。

¥You could also attach additional information in this object if you need to.

接下来,我们需要创建一个状态对象来传递服务器上的上下文。默认情况下,我们将 code 设置为 200。然后将对象传递到 StatusCodeContext.Provider 中,该对象应该用 ServerContainer 封装元素:

¥Next, we need to create a status object to pass in the context on our server. By default, we'll set the code to 200. Then pass the object in StatusCodeContext.Provider which should wrap the element with ServerContainer:

// Create a status object
const status = { code: 200 };

const html = ReactDOMServer.renderToString(
// Pass the status object via context
<StatusCodeContext.Provider value={status}>
<ServerContainer ref={ref} location={location}>
{element}
</ServerContainer>
</StatusCodeContext.Provider>
);

// After rendering, get the status code and use it for server's response
ctx.status = status.code;

当我们使用 ReactDOMServer.renderToString 渲染应用后,如果渲染 NotFound 屏幕,则 status 对象的 code 属性将更新为 404

¥After we render the app with ReactDOMServer.renderToString, the code property of the status object will be updated to be 404 if the NotFound screen was rendered.

你也可以对其他状态代码采用类似的方法,例如 401 表示未经授权等。

¥You can follow a similar approach for other status codes too, for example, 401 for unauthorized etc.

概括

¥Summary

  • 使用 ServerContainer 上的 location 属性根据传入请求渲染正确的屏幕。

    ¥Use the location prop on ServerContainer to render correct screens based on the incoming request.

  • ref 附加到 ServerContainer 获取当前屏幕的选项。

    ¥Attach a ref to the ServerContainer get options for the current screen.

  • 使用上下文附加更多信息,例如状态代码。

    ¥Use context to attach more information such as status code.