i__dmitry
@i__dmitry
Weaving a web

Каковы best practice по использованию React вместе с PHP-шаблонизаторами (Blade, Twig)?

Всем привет!
Хотел бы получить мудрых советов касательно использования React внутри обычного PHP-шаблонизатора.
Классическая модель использования реакта предполагает, что он полностью реализует фронтенд, получая и передавая данные серверу по API. То есть, реализуя MVC-приложение на PHP, я могу полностью повесить весь "V" и даже частично "C" на Реакт. Но что, если необходимости в этом нет и я хочу использовать силы PHP-фреймворка для реализации роутинга и вывода HTML-контента, а реакт хочу использовать только для создания реактивности в отдельных разделах и блоках приложения? К примеру, каталог продукции с фильтрами и прочими элементами интерфейса. Достаточно ли просто создать div#catalog и рендерить в него компоненты реакта? Если да, то как это реализовать лучше всего? И как быть с SEO в таком случае?
Буду благодарен за советы и ссылки.
  • Вопрос задан
  • 981 просмотр
Решения вопроса 2
Есть опыт с Vue в похожей ситуации, суть та же самая.

Где-то в шаблонах формируется контейнер для компонента, в атрибутах которого передаются пропсы для компонента:

<div 
  data-component="ComponentName"
  data-component-props='{ "id": "123", "foo": "bar" }'
>
</div>


Где-то в Component.js происходит инициализация компонента:

// псевдо-код
const el = получили элемент с data-component="ComponentName" или с каким-то id и т.д.
const props = спарсили пропсы из атрибута 
const app = отрендерили React-компонент в этом жлементе передав ему пропсы


С Vue ранее еще пробовали инициализировать приложение прямо на body и использовать кастомные теги для компонентов, но выходило хуже, с пропсами было неудобно.

Из известных крупных сайтов Lamoda например делает что-то подобное. Поищите по исходному коду страницы товара <div class="vue-widget">

С SEO быть или никак, или заполняя будущий контейнер для компонента голым сеошным текстом и потом его замещая рендером компонента. Скорее всего, реакт вы будете использовать для динамических вещей - формы, конструкторы, корзина и т.д., которым сео не нужно.

По опыту использования такого подхода на нескольких средних проектах могу сказать, что подход рабочий. Писать динамические компоненты на Vue гораздо удобнее. Мы написали что-то вроде недо-микро-фреймворка для удобного биндинга vue и не-vue компонентов https://github.com/interfacesdev/nospa Внутри у себя используем, но рекомендовать к вашему продакшену не могу - документация не закончена, поддержки нет, на гитхаб пока просто выдернули актуальную версию с прошлого проекта. Может когда-то руки дойдут заняться плотнее.

А в целом значительно удобнее писать фронт полностью на Vue/React, используя, например, Next/Nuxt для рендеринга и все эти по сути костыли просто от безысходности :( Но это уже тема отдельной дискуссии.
Ответ написан
KulakovAngel
@KulakovAngel
Full Stack Developer (Node.JS)
Доброго дня! Давайте разберемся.
Достаточно ли просто создать div#catalog и рендерить в него компоненты реакта?

Ответ: достаточно просто (ссылка на песочницу). Допустим, имеется шаблон (я использовал HTML, но, думаю, с Blade проблем не возникнет):
<html>
  <head>
    <title>PHP React Test</title>

    <script src="https://unpkg.com/react@17/umd/react.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js" crossorigin></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
  </head>
  <body>
    <?php
      $INIT_PARAMS = ['user' => 'Vasilij']; // данные из PHP
      echo '<p>Hello React App</p>';
    ?>

    <div id='react-app'></div>

    // React-component. Конечно, лучше вынести в отдельный файл, если кода много.
    //  Еще лучше скомпилировать и подключить минифицированный бандл. Здесь - концептуальный пример.
    <script type="text/babel">
      'use strict';
      const {useState} = React;

      function App({user}) {
        const [liked, setLiked] = useState(false);
        return (
          liked ? (`You (${user}) liked this.`) : (
            <button onClick={() => setLiked(true)}>
              Like
            </button>
          )
        )
      }

      const domContainer = document.querySelector('#react-app');
      ReactDOM.render(<App user={'<?=$INIT_PARAMS['user']?>'}/>, domContainer);
    </script>
  </body>
</html>


И как быть с SEO в таком случае?

Правильный вопрос. То, что мы сделали выше - называется "клиентский рендеринг". Для оптимизации SEO он не очень подходит. Такой способ прост, но используется там, где SEO не нужно (например, приложение, в которое в принципе нельзя зайти, не пройдя процедуру авторизации). Для оптимизации SEO используется серверный рендеринг (SSR), а точнее изоморфная структура приложения (совмещаем SSR и CSR). Но поскольку PHP и JS - разные языки, нам потребуется "компилятор JS" прямо в PHP (мы же хотим скомпилировать JS на сервере и отправить клиенту готовый HTML). Для этого можно использовать V8Js. Почитать подробнее можно, погуглив, например.

Но, наверное, многие согласятся, что такое использование не типично и крайне усложняет и без того сложный процесс. Если Вы хотите попробовать из академического интереса - я кое-что объяснил. Если же мы говорим про best practice, то давайте разберемся, что Вы хотите:
  • Использовать PHP?
  • Или использовать React SSR?


  1. Если Вы хотите использовать PHP и добавить компонентам реактивности, нечто подобное уже реализовано в Laravel: присмотритесь к laravel-livewire или inertiajs. Я бы рекомендовал laravel-livewire. Инструкция по установке того и другого: jetstream Получите архитектуру, почти как в React, только на сервере. Все живое, стэйт обновляется (как? автоматически через скрытые ajax-запросы). Вот, нашел наспех что-то из своего проекта (public $adJob играет роль состояния):
    <?php
    
    namespace App\Http\Livewire;
    
    use Livewire\Component;
    use Illuminate\Support\Facades\Auth;
    
    class AdJob extends Component {
        
        public $adJob;
        
        public function mount($adJobId) {
            $this->adJob = \App\Models\AdJob::find( $adJobId );
        }
        
        public function setReleaserForAdJob($releaserId) {
            $this->adJob['releaser_id'] = $releaserId;
            $this->adJob['status'] = 'in_progress';
            $this->adJob->save();
        }
        
        public function setAdJobDone() {
            $this->adJob['status'] = 'done';
            $this->adJob->save();
        }
    	
        public function render() {
            return view('livewire.ad-job-with-reviews');
        }
    }

    И последнее. Если не знакомы с Laravel, но нравится PHP - самое время начать изучать.

  2. Ежели желаете использовать React - то много проще (чем с PHP) использовать React SSR на Node.js. Самые популярные способы:
    1. Nextjs, как заметил Андрей Хохлов. Здесь все просто: изучайте документацию по этой технологии.

    2. React SSR + Express. Здесь, по сути, все то же самое, просто настроить все нужно самому. Хотя, это и более интересно (мне так кажется). Ситуация такова:

      1. Создаем React-приложение на клиенте. Делаем бандл. В пункте 3 будет небольшое дополнение сюда.


      2. На нужных маршрутах на сервере используем
        res.send(ReactDOMServer.renderToString(myElement)) - получаем нечто вроде создания "шаблона" (res.send - API express, остальное - react)


      3. На клиенте вместо ReactDOM.render() вызываем ReactDOM.hydrate(element, container), это позволяет не создавать разметку заново, а "гитратировать" HTML, отрендеренный React-ом на сервере и полученный браузером.


      Есть также пакеты, позволяющие настроить использование React как шаблонизатора, например: @react-ssr/express. Под капотом он использует тот же renderToString, только с правильной архитектурой и дополнительными, уже настроенными возможностями. В 12-ой строке файла default.tsx из пакета можно увидеть:
      const html = ReactDOMServer.renderToString(app);





Таким образом, если интерес для Вас представляет именно PHP - рекомендую Laravel + livewire-компоненты. Если хотите именно React SSR - я бы предпочел Node-стэк (Next или Express+React, вначале сам попробовал бы, потом можно попробовать использовать готовый пакет). Вообще best practice - Next.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы