2
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

Reactで実現するUniversal JavaScript

はじめに

この記事はReact#2 Advent Calendar 2019 10日目の記事です。

先日某勉強会で以下のようなLTをさせていただきました。

はじめてのUniversal JavaScript

当記事ではこのLTを元に

・Universal JavaScriptについて
・LT内でも言及されているReact Routerを用いたSSRについて

深ぼっていこうと思います。

なぜUniversal JavaScriptか

まずはじめに、少しReactAdventCalendarという趣旨から少しはずれるかもしれませんが、Universal JavaScriptという言葉について触れておこようと思います。

Universal JavaScriptとは、
・サーバーサイドやクライアントのコードをJavaScriptのコードで共通化させていこうという設計論
・Isomorphic JavaScriptやSSRと同義語として扱われることも多い。
SSR(サーバーサイドレンダリング)という言葉の方が皆さんにとっても馴染みが深いと思います(ていうか私もです。Universal JacaScriptという用語は最近知りました。)

このUniversal JavaScriptという用語は2015年、こちらのUniversal JavaScriptという記事で言及させたのがはじまりだと言われています。

当記事でも

Because good names are important. A good name teaches about purpose and responsibility, so you have to spend some time thinking about it.

と言われている通り、物事を伝えるためには言葉選びというのは大切です。
私も教育に携わっている身なので、そのことはよくわかります。
私は最近プログラミングをはじめて、SPA+SSRというような感じでサーバーサイドレンダリングはSEOのため、ページ表示を早くするためプラスアルファでやるものという認識でした。(私の勉強不足というのもあるかもしれませんが)
ただこの用語に出会ってからはSSRは実装方法によっては、JavaScriptのクライアント・サーバー(Node.js)どちらも実装することが可能であるという利点を生かした設計ができるのではという考えをもつことができました。
(これはSSRへの理解にも繋がると考えています。)

なので、あえてタイトルは現在ではあまり目にすることはないUniversal JavaScriptというタイトルを使わせていただきました(決して興味を持たせようという釣りではありません。)

実装例

冒頭のLTの資料でも紹介させていただきましたが、こちらのUniversal JavaScriptの考えを理解するのに最適なのがNode.jsデザインパターンです.

こちらの例を少し紹介したいと思います。

環境
フロントエンド:React
サーバーサイド:Node(Express)

server.js
 app.get('*', (req, res) => { 

   Router.match( 
     {routes: routesConfig, location: req.url}, 
     (error, redirectLocation, renderProps) => { 
       if (error) { 
         res.status(500).send(error.message) 
       } else if (redirectLocation) { 
         res.redirect(302, redirectLocation.pathname + redirectLocation.search) 
       } else if (renderProps) { 
         let markup = ReactDom.renderToString(<Router.RouterContext {...renderProps} />); 
         res.render('index', {markup}); 
       } else { 
         res.status(404).send('Not found') 
       } 
     } 
   ); 

Expressでのルーティングのコードです。
Router.matchとはフロントエンドのReact.Routerのルーティングとマッチさせます。つまりはExpressのルーティングにReactRouterのルーティングを合わせ、ReactDOM.renderToStringによって受け取ったReactDOMをHTMLにして、返すことによってSSRを実現しています。
このようにフロント/サーバーのコードを同じ考えのもと共通かしていくのがUniversal JavaScriptの考えです。

現在のSSR

とはいえ、先ほどの例のコードは現在のReactRouterのバージョンに対応していないので、どのようなコードを書いて、実装していったら良いか考えてみましょう。

先ほどの例では、

 {routes: routesConfig, location: req.url}, 

というようにフロントエンドのルーティングを一つのroutesConfigというモジュールにまとめることによって、ルーティングの共通化を実装しやすくしています。

これを実装するにはreact-routerの
react-router-config
が便利です。

こちらは、Routeを以下のようにまとめ、

route.js
const routes = [
  {
    component: Root,
    routes: [
      {
        path: "/",
        exact: true,
        component: Home
      },
      {
        path: "/child/:id",
        component: Child,
        routes: [
          {
            path: "/child/:id/grand-child",
            component: GrandChild
          }
        ]
      }
    ]
  }
];

このようにすることでルーティングを実装することができます。

import { BrowserRouter } from "react-routerdom";
import { renderRoutes } from "react-router-config";
import routes from "./client/routes";

<BrowserRouter>
    {renderRoutes(routes)}
</BrowserRouter>;

以下のようにしてルーティングを実装します。サーバーサイドのルーティングにはStaticRouterを使います。

import http from "http";
import React from "react";
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom";
import { renderRoutes } from "react-router-config"
import routes from "./client/routes"

http
  .createServer((req, res) => {
    const context = {};

    const html = ReactDOMServer.renderToString(
      <StaticRouter location={req.url} context={context}>
        {renderRoutes(routes)}
      </StaticRouter>
    );

    if (context.url) {
      res.writeHead(301, {
        Location: context.url
      });
      res.end();
    } else {
      res.write(`
      <!doctype html>
      <div id="app">${html}</div>
    `);
      res.end();
    }
  })
  .listen(3000);

※サンプルはReact Routerのドキュメントから引用

まとめ

いかがでしょうか?
SSRという用語だとなかなか気が進まない作業でもUniversal JavaScriptだとなんかかっこいい感じがします(笑)
というのは冗談で、UniversalJavaScriptという考えを知った上でSSRに取り組むと理解が進むのではないでしょうか?という初心者目線の投稿でした。

読んでくださり、ありがとうございました。

なんかReact要素少なかったような。。。すいません。

参考

ReactRouter
react-router-config

UniversalJavaScript

以下はIsomorphicJavaScriptの記事ですが、歴史がしれてとても興味深かったです。
https://speakerdeck.com/koichik/isomorphic-survival-guide)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
2
Help us understand the problem. What are the problem?