冬空の中でReactの世界観をちょっとだけ広げたかった

それはただ美しかった。

コンポーネントという呪縛がある一方で、開発者の自由さを守りながら宣言的UIによる美しい構成が可能となった。

数千行に渡るDOMイベント管理の苦行から開放された。

見上げればそこにはReactがあった。

まるでオリオン座のように。

## だがしかしBUT

仮想DOMというバーチャルな世界を実現するために、「Hello World」を表示するには現実的にWebpackやRollup等のバンドルが必要になった。

これを私は「コンポーネントの呪縛」と勝手に思っている。（悪気はない。）

初学者だった頃のjQuery使いの私にはしばらく難しかった。

とりあえず[これ](https://github.com/saltyshiomix/webpack-typescript-react-starter)のようなminimalなスターターを書いて、そっと[公式ドキュメント](https://reactjs.org)を閉じた。

## 数年の月日を経て

複雑なステート管理にはReduxが有名に、事実上のデファクトとなった。

やがてHooksが誕生した。

Hooksの登場により再度[公式ドキュメント](https://reactjs.org)を開き、そしてそれをそっと閉じた。

## どうしてもReduxのような管理方法が必要になった

JSerとしての経験値が上がるにつれて、jQuery思想の自分にはやがて限界が訪れた。

コンポーネントである以上基本は親が子を教育するのである。

したがってグローバルなstoreへどのコンポーネントからもアクセスできる機構が必要になったのだ。

dispatchめ。（悪気はない。）

## おや...

結局グローバルstoreは親から子へ移譲しているだけではないか。

## 親から子へデータを渡している？

それは究極の発想ではview template engineに近しいのではなかろうか。

JSならejsやhandlebars、PHPあたりではbladeとかそういった、あの{{ data }}ではなかろうか。

あの古き良き、サーバーから受け取ったデータをビューへ渡す方法ではなかろうか。

## Reactを view template engine にできないだろうか

調べたらあった。

https://github.com/reactjs/express-react-views

サーバーからデータを渡す：

```js
// views/index.jsxをレンダリング
res.render('index', { message: 'Hello World' });
```

すると`views/index.jsx`のpropsにデータが伝搬される：

```jsx
var React = require('react');

function IndexPage(props) {
  return <div>{props.message}</div>;
}

module.exports = IndexPage;
```

素晴らしいではないか。

## と思ったら

> It renders static markup and does not support mounting those views on the client.

静的マークアップをレンダリングするだけでReactはマウントされないだと。。。

Reactにする意味はあるのか？（ビューの数千行が数百行くらいにはなるかもしれないが。。。）

JSのイベントはどうする？

CSS-in-JSはどうなる？

## どうしてもReactをマウントしたかった

https://github.com/saltyshiomix/react-ssr

```zsh
$ npm install --save @react-ssr/express
```

```json:package.json
{
  "scripts": {
    "start": "node server.js"
  }
}
```

```js:server.js
const express = require('express');
const register = require('@react-ssr/express/register');

const app = express();

(async () => {
  // Reactビューエンジンとして`.jsx`を登録
  await register(app);

  app.get('/', (req, res) => {
    const message = 'Hello World!';
    res.render('index', { message });
  });

  app.listen(3000, () => {
    console.log('> Ready on http://localhost:3000');
  });
})();
```

```jsx:views/index.jsx
export default function Index({ message }) {
  return <p>{message}</p>;
}
```

## そう、Reactをそのまま利用できるビューエンジンにしたかった

```jsx:views/index.jsx
import React from 'react';

const IndexPage = (props) => {
  const [message, setMessage] = React.useState('waiting...');

  const onClick = () => setMessage('This is a react-ssr!');

  return (
    <React.Fragment>
      <button onClick={onClick}>Click Me</button>
      <p>Message from state: {message}</p>
    </React.Fragment>
  );
};

export default IndexPage;
```

<img width="220" alt="スクリーンショット 2019-11-30 22.12.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32464/44ab5698-9b32-94b1-ddce-c6d9a66a4ac7.png">

↓↓↓

<img width="285" alt="スクリーンショット 2019-11-30 22.13.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32464/2e483e27-6871-d7f6-4112-c4c190601781.png">

## 想像してみてほしい

```js:server.js
const posts = [
  { id: 1, body: 'This is a first post.' },
  { id: 2, body: 'This is a second post.' },
  { id: 3, body: 'This is a last post.' },
];

app.get('/', (req, res) => {
  res.render('index', { posts });
});

app.get('/posts/:postId', (req, res) => {
  const { postId } = req.params;
  const post = findById(postId);
  res.render('post', { post });
});
```

この古き良きデータ流しを、

```jsx:views/index.jsx
import React from 'react';

const IndexPage = ({ posts }) => {
  return (
    <React.Fragment>
      {posts.map((post, index) => {
        return (
          <p key={index}>
            <a href={'/posts/' + post.id}>{post.body}</a>
          </p>
        );
      })}
    </React.Fragment>
  );
};

export default IndexPage;
```

↓↓↓

`http://localhost:3000`

<img width="168" alt="スクリーンショット 2019-11-30 22.23.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32464/5b1b588a-31ef-8beb-ec9f-b55caf1bf4dc.png">

```jsx:views/post.jsx
import React from 'react';

const PostPage = ({ post }) => {
  return (
    <React.Fragment>
      <p>{post.body}</p>
    </React.Fragment>
  );
};

export default PostPage;
```

↓↓↓

`http://localhost:3000/posts/1`

<img width="143" alt="スクリーンショット 2019-11-30 22.23.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32464/96739dc3-9217-f453-801f-90ec22b21187.png">

Reactで扱える喜びを...！！！

## NEXT.jsとの比較

### データの非同期取得

クライアント側のデータ非同期取得は、どちらも`React.useEffect()`で非同期関数を処理する必要がある。

#### NEXT.jsの場合

```jsx:pages/index.jsx
import React, { useState, useEffect } from 'react';

const HomePage = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    // componentDidMount like
    fn() {
      const res = await fetch('http://localhost:4000/api/posts');
      const posts = await res.json();
      setPosts(posts);
    }
    fn();

    return () => {
      // componentWillUnmount like
    };
  }, []);

  return (
    <React.Fragment>
      {posts.map((post, index) => {
        return (
          <p key={index}>
            <a href={'/posts/' + post.id}>{post.body}</a>
          </p>
        );
      })}
    </React.Fragment>
  );
};

export default HomePage;
```

しかしこれではSSRされないのでSEO対策にならない。

仕方なく以下のようなNEXT.js wayに従って書き直す。

```jsx:pages/index.jsx
import React from 'react';

const HomePage = ({ posts }) => {
  return (
    <React.Fragment>
      {posts.map((post, index) => {
        return (
          <p key={index}>
            <a href={'/posts/' + post.id}>{post.body}</a>
          </p>
        );
      })}
    </React.Fragment>
  );
};

HomePage.getInitialProps = async () => {
  const res = await fetch('http://localhost:4000/api/posts');
  const posts = await res.json();
  return { posts };
};

export default HomePage;
```

だいぶすっきりしたものの、非同期データをSSRさせたい場合、この`getInitialProps`という魔法の呪文を唱える必要がある。

#### react-ssrの場合

すでに紹介したように、view template engineとして機能するため、APIサーバーから取得したデータをそのままビューへ渡せば良い。

そもそも同一サーバーならAPIサーバーを建てる必要もなく、直接DBから取得したデータをビューへ渡せばpropsから取得データへアクセスできる。

```js:server.js
(async () => {
  const posts = await fetchFromDbOrAPIServer();

  app.get('/posts', (req, res) => {
    res.render('posts', { posts });
  });
})();
```

```jsx:views/index.jsx
import React from 'react';

const HomePage = ({ posts }) => {
  return (
    <React.Fragment>
      {posts.map((post, index) => {
        return (
          <p key={index}>
            <a href={'/posts/' + post.id}>{post.body}</a>
          </p>
        );
      })}
    </React.Fragment>
  );
};

export default HomePage;
```

### ユーザー認証

#### NEXT.jsの場合

[iaincollins/next-auth](https://github.com/iaincollins/next-auth)というライブラリがあるが、これもクライアント側では`getInitialProps`を利用する。

しかしサーバー側の使い方が、NEXT.js wayなサーバーの書き方をする必要があったり、そもそもドキュメントが充分とは思えないのであまり使う気にはなれなかった。

```js:server.js
const next = require('next')
const nextAuth = require('next-auth')
const nextAuthConfig = require('./next-auth.config')

require('dotenv').load()

const nextApp = next({
  dir: '.',
  dev: (process.env.NODE_ENV === 'development')
})

nextApp.prepare()
.then(async () => {
  const nextAuthOptions = await nextAuthConfig()
  const nextAuthApp = await nextAuth(nextApp, nextAuthOptions)  
  console.log(`Ready on http://localhost:${process.env.PORT || 3000}`)
})
.catch(err => {
  console.log('An error occurred, unable to start the server')
  console.log(err)
})
```

↑を読んでこれから何が起こるのかわかるエスパーはいるのだろうか。

#### react-ssrの場合

react-ssrはExpressのview template engineとして振る舞うので、古き良きExpress wayなユーザー認証を利用できる。

例えばメールアドレス・パスワードによる認証であれば[passport](https://www.npmjs.com/package/passport)と[passport-local](https://www.npmjs.com/package/passport-local)を利用する。

ソースコードは長くなるため割愛するが、その分サーバー側で柔軟な調整ができる。

- ユーザーデータのシリアライズ・デシリアライズ方法の調整
- sessionの保存方法の調整（たとえばDBにsession情報を保存する等）
- リダイレクトURLの柔軟な調整（条件分岐等）

サーバー側のバグはクリティカルであることが多い。
「なんでも屋」すぎるライブラリには頼らず、小さくて優秀なライブラリを複数利用しつつ細かい調整は自前で実装するのがベストプラクティスだと筆者は考える。

## react-ssrの実装サンプル

[examples/with-jsx-emotion](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-emotion)

<img width="807" alt="スクリーンショット 2019-12-01 5.25.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32464/19fdeb58-9dfe-dc42-8ca0-4167523883b6.png">

[examples/with-jsx-material-ui](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-material-ui)

<img width="620" alt="スクリーンショット 2019-12-01 5.27.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32464/226fb1c3-c48d-374e-c7fd-46b5acc132fa.png">

[examples/with-jsx-semantic-ui](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-semantic-ui)

<img width="719" alt="スクリーンショット 2019-12-01 5.29.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32464/337a91b8-e9c4-1325-7c30-6f7c1aecc558.png">

[examples/styled-components](https://github.com/saltyshiomix/react-ssr/tree/master/examples/with-jsx-styled-components)

<img width="390" alt="スクリーンショット 2019-12-01 5.31.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32464/7d769b39-b8dd-dedb-ca0a-3d6f627b5aa2.png">

## さいごに

最終的に[react-ssr](https://github.com/saltyshiomix/react-ssr)の宣伝っぽくなってしまってすみません。

ですが古き良きビューエンジンの形でReactを利用したいと思ったことはありませんか？

小規模開発でもわざわざAPIサーバーとクライアントを分けなければならない煩雑さを感じたことはありませんか？

そういった疑問を感じたことがある方にとって、少しでも可能性が広がる記事になれば幸いです。

筆者の稚拙な技術記事であるため、間違い等はコメントにてご指摘ください。

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

ちなみに私はベテルギウスが好きです。（一番高いところにあるから。）