limenius / 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-14 21:31:11 UTC
README
ReactRenderer 允许您在PHP项目中实现React.js客户端和服务器端渲染,从而允许开发通用(同构)应用。
它之前是ReactBundle的一部分,但现在可以作为独立使用。
如果您想与Silex一起使用,请查看@teameh Silex React Renderer Service Provider。
功能包括
- 为SEO、更快的页面加载、禁用JavaScript的用户或渐进式Web应用预先渲染服务器端React组件。
- 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
的渲染器之一。
此库目前提供以下两个渲染器
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}) }}
请注意,在这种情况下,您可能会看到类似以下警告:
"警告: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。引入了更多的操作复杂性(您需要保持node服务器运行,但这并不是什么大问题)。
目前,在生产环境中使用外部服务器是最佳选择,因为编译 V8js 相对困难。然而,如果您可以编译它或您的发行版/操作系统有良好的包,如果启用缓存,这是一个非常好的选择,我们将在下一节中看到。
Redux
如果您正在使用 Redux,您可以使用此库来填充您的store
在渲染您的组件之前,在您的Twig文件中使用 redux_store
,具体取决于您的store
{{ redux_store('MySharedReduxStore', initialState ) }} {{ react_component('RecipesApp') }}
这里的 MySharedReduxStore
是您在javascript中使用来获取store的标识符。"initialState" 可以是一个JSON编码的字符串或数组。
然后,在您的bundle中公开您的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 文件中设置存储时使用的那样。
在 沙盒 中有一个示例。
生成器函数
你可能会选择从 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 之前,这个特性是一个标志而不是一个命名选项)。
缓存
有两种类型的缓存,性质截然不同
- 你可以缓存单个组件。
- 如果你使用 php 扩展 V8,你可以缓存 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 组件。