服务端渲染
本指南将介绍如何使用 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:
-
根据请求 URL 渲染正确的布局
¥Rendering the correct layout depending on the request URL
-
根据焦点屏幕设置适当的页面元数据
¥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:
-
你使用的所有依赖都是 发布前编译 到 npm,这样你就不会在 Node 上出现语法错误。
¥All of the dependencies that you use are compiled before publishing to npm, so that you don't get syntax errors on Node.
-
节点配置为能够处理
require
资源文件,例如图片和字体。你可以尝试 webpack-isomorphic-tools 来做到这一点。¥Node is configured to be able to
require
asset files such as images and fonts. You can try webpack-isomorphic-tools to do that. -
react-native
是react-native-web
的别名。你可以用 babel-plugin-module-resolver 来做。¥
react-native
is aliased toreact-native-web
. You can do it with babel-plugin-module-resolver.
渲染应用
¥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/App
是 AppRegistry.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,你可以使用上下文参数中的 path
和 search
属性:
¥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:
- Static
- Dynamic
const Stack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeScreen,
options: {
title: 'My App',
},
},
Profile: {
screen: ProfileScreen,
options: ({ route }) => ({
title: `${route.params.name}'s Profile`,
}),
},
},
});
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
title: 'My App',
}}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={({ route }) => ({
title: `${route.params.name}'s Profile`,
})}
/>
</Stack.Navigator>
处理 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
屏幕中使用上下文。在这里,我们添加一个值为 404
的 code
属性来表示未找到屏幕:
¥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 onServerContainer
to render correct screens based on the incoming request. -
将
ref
附加到ServerContainer
获取当前屏幕的选项。¥Attach a
ref
to theServerContainer
get options for the current screen. -
使用上下文附加更多信息,例如状态代码。
¥Use context to attach more information such as status code.