個人サイトをjQueryからReactに書き換え

[uedayou.net](https://uedayou.net/)で個人的に作成したWebアプリやら技術系同人誌を紹介するサイトを公開しています。
サイトというか、ほとんど1ページしかないのですが、だいぶ古いコードで、アプリが増えてきて更新し難くなってきたので、作り直すことにしました。

# 従来の構成

当時、あまりサイト更新に時間をかけたくなく、1ページにまとまって見れるリンク集に毛が生えた程度のものを作りたかったので、スタイルは[Bootstrap](https://getbootstrap.com/)、PC、タブレット、スマートフォン、どの環境でもそれなりに見れるようにするため[Masonry](https://masonry.desandro.com/)と[jQuery](https://jquery.com/)を使って、作品紹介の要素がきれいに並び変わるようにしていました。

| 使用モジュール |
| ---- |
| [jQuery](https://jquery.com/) |
| [Masonry](https://masonry.desandro.com/) |
| [Bootstrap](https://getbootstrap.com/) |

![uedayou.net](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62575/f581898e-9ed3-c7d9-2826-5945d6e6935b.jpeg)



## 問題点

以下のような要素を作品分、1つのHTMLファイル内に記述する形なので、数が少ないときはよかったのですが、数が多くなるにつれて、コードの可読性が悪くなり、作品の順番を入れ替えるのも大変になってきました。

```html

<div class="app-section col-lg-4 col-sm-6">
  <h4>鉄道駅LOD</h4>
  <div class="well">
    <div class="image">
      <img src="app_images/jrslod-image.jpg" class="img-responsive">
    </div>
    <div class="description">
      <p>
        「<a href="https://uedayou.net/jrslod/" target="_blank">鉄道駅LOD</a>」は日本の鉄道に関するオープンデータを収集して提供するサイトです。
      </p>
      ...
      <a class="btn btn-primary" href="https://uedayou.net/jrslod/" target="_blank">サイトを開く</a>
      <a class="btn btn-primary" href="https://qiita.com/uedayou/items/b5131b5ca930fe0bef69" target="_blank">解説記事</a>
    </div>
  </div>
</div>
```

# 新しい構成

昨今だと、[Gatsby](https://www.gatsbyjs.org/)、[VuePress](https://vuepress.vuejs.org/)、[Hexo](https://hexo.io/)など静的サイトジェネレータを使うのが一般的かもしれませんが、上記のような構成だと静的サイトジェネレータ使うにはちょっと重いかなと思いました。

なので、[React](https://ja.reactjs.org/)で既存のページを再現して、作品紹介の部分のみ、編集しやすくするようにしました。
スタイルはBootstrap から [Material-UI](https://material-ui.com/)、MasonryはReact用に[react-masonry-css](https://github.com/paulcollett/react-masonry-css)というのがあったのでこちらに変更しました。
また、作品紹介部分はMarkdownで書けると楽なので [react-markdown](https://github.com/rexxars/react-markdown) を使うことにしました。

| 使用モジュール |
| ---- |
| [React](https://ja.reactjs.org/) |
| [react-masonry-css](https://github.com/paulcollett/react-masonry-css) |
| [Material-UI](https://material-ui.com/) |
| [react-markdown](https://github.com/rexxars/react-markdown) |

## インストール

React は CLI を使ってセットアップしました。

```shell
npx create-react-app uedayou.net
cd uedayou.net
```

以下のモジュールをインストールしました。

```shell
npm install react-masonry-css
npm install @material-ui/core
npm install react-markdown
```

## 作品紹介コンポーネント

作品紹介部分はほぼ共通化されているので、作品紹介用の共用コンポーネントを作成しました。

```jsx:Product.jsx
import React from 'react'
import ReactMarkdown from 'react-markdown/with-html';
import { makeStyles } from '@material-ui/core/styles';

import Card from '@material-ui/core/Card';
import CardHeader from '@material-ui/core/CardHeader';
import CardMedia from '@material-ui/core/CardMedia';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';

const useStyles = makeStyles((theme) => ({
  media: {
    height: 0,
    paddingTop: '56.25%',
    backgroundPositionY: 0,
  },
}));

function Product(props) {
  const classes = useStyles();
  return (
<Card>
  <CardHeader
    title={props.title}
  />
  <CardMedia
    className={classes.media}
    image={props.image}
    title={props.title}
  />
  <CardContent>
    <Typography variant="body2" component="div">
      <ReactMarkdown
        source={props.description || ""}
        escapeHtml={false} />
    </Typography>
  </CardContent>
  <CardActions>
    {(props.links && props.links.length>0) 
      && props.links.map(v=>(
      <Button variant="outlined" color="primary" href={v.link} key={v.link} target="_blank">
        {v.label}
      </Button>
    ))}
  </CardActions>
</Card>
  )
}

export default Product;
```

これで、たとえば

```jsx:JrsLod.jsx
import React from 'react'
import Product from './Product';

function JrsLod() {
  return (
<Product 
  title="鉄道駅LOD"
  image="app_images/jrslod-image.jpg"
  description={`
[鉄道駅LOD](https://uedayou.net/jrslod/)は日本の鉄道に関するオープンデータを収集して提供するサイトです。

鉄道会社、鉄道路線、鉄道駅のデータを閲覧、ダウンロードできます。鉄道会社のデータには、全路線のポリラインデータ、路線データには路線のポリラインデータ、駅データには、駅のポイントデータをダウンロードできます。
JSONデータでダウンロードでき、CORSにも対応しています。Webアプリから直接利用することができます。

[鉄道駅LOD](https://uedayou.net/jrslod/)のデータを使ったWebアプリ
[鉄道駅LOD GeoJSONダウンローダー](https://uedayou.net/jrslod-geojson-downloader/)
の
[ソースコード](https://github.com/uedayou/jrslod-geojson-downloader)
を公開しています。
「鉄道駅LOD」のデータを使ったアプリを作るときに参考にしてみてください。
`}
  links={[
    {
      label:"サイトを開く",
      link:"https://uedayou.net/jrslod/"
    },
    {
      label:"解説記事",
      link:"https://qiita.com/uedayou/items/b5131b5ca930fe0bef69"
    },
  ]}
/>
  )
}

export default JrsLod;
```

のように必要な部分だけをコンポーネントにかけるようになりました。
description部分は Markdown で書けるようにしています。
改善の余地はあると思いますが、個人でやってるサイトであれば必要十分かと思います。

## ページへの登録

上記の作品紹介コンポーネントは、以下のように登録しています。

```jsx
import React from 'react'
import Masonry from 'react-masonry-css'
import Container from '@material-ui/core/Container';
import { makeStyles } from '@material-ui/core/styles';

import Rosenoh from './products/Rosenoh';
import JrsLod from './products/JrsLod';
import LoajBook from './products/LoajBook';
...

const useStyles = makeStyles((theme) => ({
  myMasonryGrid: {
    display: "flex",
    marginLeft: "-30px",
    width: "auto",
  },
  myMasonryGridColumn: {
    paddingLeft: "30px",
    "& .MuiCard-root": { marginBottom: "30px" }
  }
}));

function Contents() {
  const classes = useStyles();
  const breakpointColumnsObj = {
    default: 3,
    1100: 2,
    700: 1
  };

  return (
<Container maxWidth="lg">
  <Masonry
    breakpointCols={breakpointColumnsObj}
    className={classes.myMasonryGrid}
    columnClassName={classes.myMasonryGridColumn}>
    <Rosenoh />
    <JrsLod />
    <LoajBook />
    ...
  </Masonry>
</Container>
  )
}

export default Contents;
```

`<Masonry>...</Masonry>` の間に作品紹介コンポーネントを記述します。
これで以下のように表示されます。

コンポーネントの順番を変えれば、ページでの表示順も変わりますし、削除も簡単です。
これで従来の構成よりもだいぶ編集が楽になりそうです。

![新ページ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62575/be3400eb-e433-c93a-052a-3ac3e0a01282.jpeg)