pwt777/react-renderer

客户端和服务器端React渲染

v5.1.0 2021-12-23 10:05 UTC

README

ReactRenderer 允许你在PHP项目中实现React.js的客户端和服务器端渲染,从而开发出通用(同构)应用程序。

它曾是 ReactBundle 的一部分,但现在可以独立使用。

如果你希望与Silex一起使用,请查看 @teameh 的 Silex React Renderer Service Provider

功能包括

  • 为SEO预渲染服务器端React组件,加快页面加载速度,针对禁用JavaScript的用户,或用于渐进式Web应用程序。
  • 集成Twig。
  • 客户端渲染将获取服务器端渲染的DOM,识别它,并在需要时才重新渲染组件,从而接管控制权。
  • 服务器和客户端代码的错误和调试管理。
  • 简单集成Webpack。

Build Status Latest Stable Version Latest Unstable Version License

完整示例

为了查看一个完整的实时示例,包括合理的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_siderender_sideboth的字符串。

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抛出的调试信息。ReactRenderer受React on Rails的启发,有重放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。引入了更多的操作复杂性(您必须保持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 });

最后,在您本应使用传递给registerStore的对象的地方使用ReactOnRails.getStore

// Get hydrated store
const store = ReactOnRails.getStore("MySharedReduxStore");

return (
  <Provider store={store}>
    <Scorecard />
  </Provider>
);

请确保在这里使用相同的标识符(MySharedReduxStore),您在Twig文件中用于设置store。

Sandbox中有示例。

生成器函数

您可以选择从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虚拟机的一个快照。

组件缓存

有时您想缓存一个组件并再次不通过服务器端渲染过程。在这种情况下,您可以设置选项cachedtrue

{{ 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组件。