lokalguiden / react-renderer
客户端和服务器端React渲染
Requires
- php: >=7.2.0
- ext-json: *
- nacmartin/phpexecjs: ^3.0
- twig/twig: ^2.4|^3.0
Requires (Dev)
This package is auto-updated.
Last update: 2024-09-23 15:13:58 UTC
README
ReactRenderer 允许你在PHP项目中实现React.js客户端和服务器端渲染,从而允许开发通用(同构)应用程序。
它之前是 ReactBundle 的一部分,但现在可以独立使用。
如果你希望与Silex一起使用它,请查看 @teameh Silex React Renderer Service Provider。
功能包括
- 预渲染服务器端React组件以进行SEO、更快地加载页面、对禁用JavaScript的用户或对渐进式Web应用程序。
- 集成Twig。
- 客户端渲染将获取服务器端渲染的DOM,识别它,并在需要之前不再重新渲染组件。
- 错误和调试服务器和客户端代码。
- 简单集成Webpack。
完整示例
要查看一个完整的实时示例,包括合理的Webpack设置、一个用于开始的示例应用程序以及在一个Symfony项目中集成,请查看 Symfony React Sandbox。
安装
ReactRenderer 使用Composer,如果您对此有疑问,请访问 composer网站。
此命令将在您的项目中安装 ReactRenderer。
$ composer require limenius/react-renderer
ReactRenderer 使用其类的PSR-4命名约定,因此您可以将其与自动加载器集成。
用法
JavaScript和Webpack设置
为了使用React组件,您需要在JavaScript中注册它们。此包使用React On Rails npm包来渲染React组件(别担心,您不需要编写任何Ruby代码!;))。
暴露React组件的代码将如下所示
import ReactOnRails from "react-on-rails";
import RecipesApp from "./RecipesAppServer";
ReactOnRails.register({ RecipesApp });
其中RecipesApp是我们想要在本例中注册的组件。
请注意,您很可能需要为服务器端和客户端组件分别设置入口点,以处理路由等问题。这是任何通用(同构)应用程序的常见问题。再次,请参阅沙盒以了解如何处理此问题。
如果您使用服务器端渲染,则还预期您将有一个Webpack包用于它,包含React、React on Rails以及用于评估您的组件的JavaScript代码。
请参阅 symfony-react-sandbox中的Webpack配置 了解更多信息。
启用Twig扩展
首先,您需要配置和启用Twig扩展。
use Limenius\ReactRenderer\Renderer\PhpExecJsReactRenderer;
use Limenius\ReactRenderer\Twig\ReactRenderExtension;
use Limenius\ReactRenderer\Context\SymfonyContextProvider;
// SymfonyContextProvider provides information about the current request, such as hostname and path
// We need an instance of Symfony\Component\HttpFoundation\RequestStack to use it
$contextProvider = new SymfonyContextProvider($requestStack);
$renderer = new PhpExecJsReactRenderer(__DIR__.'/client/build/server-bundle.js', false, $contextProvider);
$ext = new ReactRenderExtension($renderer, $contextProvider, 'both');
$twig->addExtension(new Twig_Extension_StringLoader());
$twig->addExtension($ext);
ReactRenderExtension需要一个renderer和一个字符串来定义我们是否在渲染React组件,是client_side、render_side还是both。
renderer是继承自AbstractReactRenderer的渲染之一。
此库目前提供两种renderer
PhpExecJsReactRenderer:它使用内部phpexecjs来自动检测最佳javascript运行时。ExternalServerReactRenderer:它依赖于外部nodeJs服务器。
现在您可以使用以下方式在您的Twig模板中插入React组件
{{ react_component('RecipesApp', {'props': props}) }}
在这里,RecipesApp 是我们组件的名称,而 props 是您组件的属性。属性可以是 JSON 编码的字符串或数组。
例如,一个会产生有效属性的控制器动作可以是
/**
* @Route("/recipes", name="recipes")
*/
public function homeAction(Request $request)
{
$serializer = $this->get('serializer');
return $this->render('recipe/home.html.twig', [
'props' => $serializer->serialize(
['recipes' => $this->get('recipe.manager')->findAll()->recipes], 'json')
]);
}
服务器端、客户端或两者都?
您可以选择您的 React 组件只渲染客户端、只渲染服务器端或两者都,要么如上所述在配置中,要么按 Twig 标签逐个设置。
如果您设置了 Twig 调用的 rendering 选项,您就可以覆盖配置(默认是同时渲染服务器端和客户端)。
{{ react_component('RecipesApp', {'props': props, 'rendering': 'client_side'}) }}
只会在客户端渲染组件,而下面的代码
{{ react_component('RecipesApp', {'props': props, 'rendering': 'server_side'}) }}
... 只会在服务器端渲染组件(因此动态组件将不起作用)。
或者两者都(默认)
{{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}) }}
您可以通过查看生成的 HTML 代码来探索这些选项。
调试
在 PHP 中运行服务器端 JavaScript 代码时,一个重要点是管理由 console.log 抛出的调试消息。受 React on Rails 启发的 ReactRenderer 有方法将 console.log 消息回放到您浏览器的 JavaScript 控制台中。
要启用跟踪,您可以根据上述说明设置配置参数,或者您可以以这种方式在模板中设置它
{{ react_component('RecipesApp', {'props': props, 'trace': true}) }}
请注意,在这种情况下,您可能会看到类似以下警告的 React 警告
“警告:render():目标节点具有由 React 渲染的标记,但还有其他无关的节点。这种情况通常是由围绕服务器端渲染的标记插入的空白引起的。”
这个警告是无害的,在生产中禁用跟踪后会消失。它意味着当在客户端渲染组件并与服务器端等效组件比较时,React 发现了额外的字符。这些字符是您的调试消息,因此请不用担心。
上下文
此库将为 React 组件提供关于当前请求的上下文信息。您的组件在实例化时将接收两个参数
const App = (initialProps, context) => {};
Symfony 上下文提供者有此实现
public function getContext($serverSide)
{
$request = $this->requestStack->getCurrentRequest();
return [
'serverSide' => $serverSide,
'href' => $request->getSchemeAndHttpHost().$request->getRequestUri(),
'location' => $request->getRequestUri(),
'scheme' => $request->getScheme(),
'host' => $request->getHost(),
'port' => $request->getPort(),
'base' => $request->getBaseUrl(),
'pathname' => $request->getPathInfo(),
'search' => $request->getQueryString(),
];
}
因此,您可以在 React 组件中访问这些属性,获取有关请求的信息以及它是否已经渲染到服务器端或客户端。
服务器端模式
此库支持两种服务器端渲染模式
使用 PhpExecJs 自动检测 JavaScript 环境(通过终端命令调用 node.js 或使用 V8Js PHP)并通过它运行 JavaScript 代码。
使用外部 node.js 服务器(示例)。它将使用一个哑服务器,该服务器对您的逻辑一无所知,为您渲染 React。引入了更多的操作复杂性(您必须保持节点服务器运行,但这并不是什么大问题)。
目前,在生产和环境中,使用外部服务器是最佳选择,因为要编译 V8js 是相当困难的。然而,如果您能够编译它或您的发行版/操作系统有很好的包,如果您启用缓存,那么这是一个非常好的选择,我们将在下一节中看到。
Redux
如果您正在使用 Redux,您可以使用此库来刷新您的 store
在渲染依赖于您的 store 的组件之前,在您的 Twig 文件中使用 redux_store
{{ redux_store('MySharedReduxStore', initialState ) }}
{{ react_component('RecipesApp') }}
在这里,MySharedReduxStore 是您在 JavaScript 中使用的标识符来获取 store。`initialState` 可以是一个 JSON 编码的字符串或一个数组。
然后,就像您暴露组件一样,在您的包中公开您的 store
import ReactOnRails from "react-on-rails";
import RecipesApp from "./RecipesAppServer";
import configureStore from "./store/configureStore";
ReactOnRails.registerStore({ configureStore });
ReactOnRails.register({ RecipesApp });
最终使用 ReactOnRails.getStore,你原本会在 registerStore 中传入的对象。
// Get hydrated store
const store = ReactOnRails.getStore("MySharedReduxStore");
return (
<Provider store={store}>
<Scorecard />
</Provider>
);
确保在这里使用相同的标识符(MySharedReduxStore),正如你在 Twig 文件中设置 store 时使用的那样。
在 沙箱 中有一个例子。
生成器函数
你可能会选择从 JavaScript 代码中返回一个对象,而不是返回一个组件。
这种用法的一个例子是在服务器端渲染中使用 React Helmet 渲染标题或其他元标签。你可能想返回组件生成的 HTML 以及标题。
export default (initialProps, context) => {
const renderedHtml = {
componentHtml: renderToString(<MyApp />),
title: Helmet.renderStatic().title.toString()
};
return { renderedHtml };
};
在这些情况下,将要渲染的主要 HTML 代码必须在 componentHtml 键中。你可以在 Twig 中访问生成的数组。
{% set recipes = react_component_array('RecipesApp', {'props': props}) %}
{% block title %}
{{ recipes.title is defined ? recipes.title | raw : '' }}
{% endblock title %}
{% block body %}
{{ recipes.componentHtml | raw }}
{% endblock %}
沙箱中有一个这样的例子。
缓冲
如果你将 buffered: true 设置为 react_component 的选项,那么上下文和 props 不会立即包含在模板中。所有这些数据都会被缓冲,并且可以在 react_flush_buffer 的帮助下直接在关闭 body 标签之前插入。
{{ react_component('RecipesApp', {'props': props, 'buffered': true}) }}
{{ react_flush_buffer() }}
如果你有很多 props 并且不想将它们包含在 HTML 响应的第一部分,这会很有用。见
https://developers.google.com/speed/docs/insights/PrioritizeVisibleContent
(在 4.0 之前,这个功能是一个标志而不是一个命名选项)。
缓存
有两种不同性质的缓存
- 你可以缓存单个组件。
- 如果你使用的是 V8 php 扩展,你可以缓存 V8 虚拟机的快照。
组件缓存
有时你可能想要缓存一个组件,并且不再次通过服务器端渲染过程。在这种情况下,你可以将选项 cached 设置为 true
{{ react_component('RecipesApp', {'props': props, 'cached': true}) }}
你也可以设置一个缓存键。这个键可以是实体的 id。好吧,由你来决定。如果你不设置 id,id 将基于组件的名称,所以无论你传递什么 props,组件都会被缓存并以相同的表示渲染。
{{ react_component('RecipesApp', {'props': props, 'cached': true, 'cache_key': "hi_there"}) }}
要全局启用/禁用你的应用程序的缓存,你需要写入此配置。默认值是禁用,所以如果你打算使用它,请启用此功能。
limenius_react:
serverside_rendering:
cache:
enabled: true
(在 4.0 之前,此功能被称为静态渲染)。
V8 缓存
如果你的 config.prod.yaml 或 config/packages/prod/limenius_react.yaml 中添加了以下配置,并且你已经安装了 V8js,这个包将会更快。
limenius_react:
serverside_rendering:
cache:
enabled: true
# name of your app, it is the key of the cache where the v8 snapshot will be stored.
key: "recipes_app"
在第一次页面渲染后,这将存储 JS 虚拟机 V8js 的快照到缓存中,所以在随后的访问中,你的整个 JavaScript 应用程序不需要再次处理,只需要渲染你想要渲染的特定组件。
启用缓存后,如果你更改了 JS 应用的代码,你需要清除缓存。
许可
这个库在 MIT 许可下。请参阅包中的完整许可。
LICENSE.md
鸣谢
ReactRenderer 严重受到伟大的 React On Rails 的启发,并使用其 npm 包来渲染 React 组件。