服务端渲染
本指南将介绍如何使用 React Native for Web 和 React Navigation 对你的 React Native 应用进行服务端渲染。我们将涵盖以下情况:
- 根据请求 URL 渲染正确的布局
- 根据聚焦的屏幕设置适当的页面元数据
::: warning
目前服务端渲染支持有限。由于缺少诸如媒体查询之类的 API,因此无法提供无缝的 SSR 体验。此外,许多第三方库通常在服务端渲染中无法很好地工作。
:::
先决条件
在遵循本指南之前,请确保你的应用已经在服务端渲染中正常运行。为此,你需要确保以下几点:
- 你使用的所有依赖项在发布到 npm 之前都已编译,这样你就不会在 Node 上遇到语法错误。
- Node 配置为能够
require
资源文件,例如图像和字体。你可以尝试使用 webpack-isomorphic-tools 来做到这一点。 react-native
被别名为react-native-web
。你可以使用 babel-plugin-module-resolver 来实现。
渲染应用
首先,让我们看一个示例,了解如何在不涉及 React Navigation 的情况下使用 React Native Web 进行服务端渲染
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)
的文件。
如果你在应用中使用 React Navigation,这将渲染你的主页渲染的屏幕。但是,如果你的应用中配置了链接,你可能希望为服务器上的请求 URL 渲染正确的屏幕,以便它与客户端上渲染的内容匹配。
我们可以使用 ServerContainer
来做到这一点,方法是在 location
属性中传递此信息。例如,使用 Koa,你可以使用上下文参数中的 path
和 search
属性
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;
});
你可能还希望为搜索引擎、open graph 等设置正确的文档标题和描述。为此,你可以将 ref
传递给容器,这将为你提供当前屏幕的选项。
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
选项
- 静态
- 动态
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 或其他状态代码
当为无效 URL 渲染屏幕时,我们还应该从服务器返回 404
状态代码。
首先,我们需要创建一个上下文,在其中附加状态代码。为此,请将以下代码放在一个单独的文件中,我们将在服务器和客户端上导入该文件
import * as React from 'react';
const StatusCodeContext = React.createContext();
export default StatusCodeContext;
然后,我们需要在我们的 NotFound
屏幕中使用上下文。在这里,我们添加一个 code
属性,其值为 404
,以表示未找到屏幕
function NotFound() {
const status = React.useContext(StatusCodeContext);
if (status) {
staus.code = 404;
}
return (
<View>
<Text>Oops! This URL doesn't exist.</Text>
</View>
);
}
如果需要,你还可以在此对象中附加其他信息。
接下来,我们需要创建一个状态对象,以便在我们的服务器上的上下文中传递。默认情况下,我们将 code
设置为 200
。然后将对象传递到 StatusCodeContext.Provider
中,它应该用 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
。
你也可以对其他状态代码采用类似的方法,例如,401
表示未授权等。
总结
- 使用
ServerContainer
上的location
属性根据传入的请求渲染正确的屏幕。 - 将
ref
附加到ServerContainer
以获取当前屏幕的选项。 - 使用上下文附加更多信息,例如状态代码。