俺専用フロントエンドアプリひな型

## はじめに
[WCDI Advent Calendar](https://adventar.org/calendars/3284#list-2018-12-01) 2日目
[前の人の記事](http://hnron.hatenablog.com/entry/2018/12/01/232942)
[次の人の記事](http://site0801.hatenablog.com/entry/2018/12/03/000102)


ネタがないので俺専用React starterの俺専用解説を書く
https://github.com/NozomiSugiyama/react-material-starter

最適化しきってない(する予定もない)ので、参考にできるところがあったら参考にして、参考にならないところは自分で書き換えて使ってほしい

今回作成したのは
React + TypeScriptでMaterialデザインのWebApplicationのひな型である


## 使ってるもの
1. React
2. styled-components
2. TypeScript
3. Material-UI
4. webpack-serve
5. etc...

## 解説

### ディレクトリ構成
- assets(静的コンテンツ置き場)
- src(JavaScriptのソース置き場)
    - api(RESTful APIの呼び出し関数などを配置することを想定)
    - components(Reactのコンポーネント置き場)
        - commons(依存性の低い汎用的に使うコンポーネント置き場)
        - pages(ページ用コンポーネント置き場)
        - wrappers(すべてのページコンポーネントにpropsを渡すためのwrapperコンポーネント置き場)
    - util(汎用的なコード置き場(通称なんでも置き場))

### セットアップ
```sh
$ git clone git@github.com:NozomiSugiyama/react-material-starter.git
$ yarn
```

### コマンド解説

```
$ yarn start
```
`webpack-serve` を使ってブラウザデバッグをサポート
ホットリロードしてくれる

```
$ yarn build:prod
```
Production用にビルドを行う

```
$ yarn lint
```
`/src` 下を対象にtslintを行う

※他のコマンド類は `package.json` を見るべし。サイズアナライズ機能とかDocumentジェネレートとかも一応用意してある

### ページ追加

1. `/src/components/pages/` 以下にファイルを作成 (`HogePage.tsx`)

```HogePage.tsx
import React from "react";
import { PageComponentProps } from "../../App";
import Page                   from "../commons/Page";

export default class WorkListPage extends React.Component<PageComponentProps> {
    render() {
        return (
            <Page>
                HogePage
            </Page>
        );
    }
}
```

2. `/src/components/NavigationBar.tsx` を変更

```NavigationBar.tsx
import React from "react";
import {
    Divider,
    List,
    ListItem,
    ListItemText,
    Typography,
    withTheme
} from "@material-ui/core";
import { TypographyProps } from "@material-ui/core/Typography";
import Link from "./commons/Link";
import styled from "styled-components";

export default (props: React.HTMLAttributes<HTMLElement>) => (
    <nav {...props}>
        <Toolbar>
            <Title>ServiceName</Title>
        </Toolbar>
        <Divider />
        <List>
            <Link
                to="/top"
            >
                <ListItem
                    button
                    selected={location.pathname === "/top" || location.pathname === "/"}
                >
                    <ListItemText
                        primary={"Top Page"}
                    />
                </ListItem>
            </Link>
            <Link
                to="/dashboard"
            >
                <ListItem
                    button
                    selected={location.pathname === "/dashboard"}
                >
                    <ListItemText
                        primary={"Dashboard"}
                    />
                </ListItem>
            </Link>
            <!-- 追加　ここから --> 
            <Link
                to="/hoge"
            >
                <ListItem
                    button
                    selected={location.pathname === "/hoge"}
                >
                    <ListItemText
                        primary={"Hoge"}
                    />
                </ListItem>
            </Link>
            <!-- 追加 ここまで --> 
        </List>
    </nav>
);
```

3. `/src/Routes.tsx` に追加

```Routes.tsx
export { default as DashboardPage } from "./components/pages/DashboardPage";
export { default as TopPage      } from "./components/pages/TopPage";
// 追加
export { default as HogePage      } from "./components/pages/HogePage";
```

4. `/src/App.tsx` に追加

```App.tsx
import React from "react";
import { createMuiTheme }   from "@material-ui/core/styles";
import { MuiThemeProvider } from "@material-ui/core";
import {
    BrowserRouter as Router,
    RouteComponentProps,
    match
} from "react-router-dom";
import ComposingRoute                from "./components/commons/ComposingRoute";
import ComposingSwitch               from "./components/commons/ComposingSwitch";
import { MainLayoutEventProps }      from "./components/wrappers/MainLayout";
import { NotificationListenerProps } from "./components/wrappers/NotificationListener";
import {
    DashboardPage,
    TopPage,
    // 追加
    HogePage
} from "./Routes";
import Root from "./Root";

export default () => (
    <MuiThemeProvider theme={theme}>
        <Router>
            <Root
                key="root"
            >
                <ComposingSwitch>
                    <ComposingRoute
                        path="/"
                        component={TopPage}
                        exact={true}
                    />
                    <ComposingRoute
                        path="/top"
                        component={TopPage}
                    />
                    <ComposingRoute
                        path="/dashboard"
                        component={DashboardPage}
                        exact={true}
                    />
                    <!-- 追加 -->
                    <ComposingRoute
                        path="/hoge"
                        component={DashboardPage}
                        exact={true}
                    />
                    <!-- 追加 -->
                </ComposingSwitch>
            </Root>
        </Router>
    </MuiThemeProvider>
);
```

### Snackbarを用いた通知
```Hoge.tsx
import React from "react";
import { PageComponentProps } from "../../App";
import Page                   from "../commons/Page";

export default class WorkListPage extends React.Component<PageComponentProps> {
    _notification() {
        this.props.notificationListener.notification("info", "通知内容");
    }

    render() {
        return (
            <Page>
                HogePage
                <button
                    onClick={this._notification}
                />
                    通知ボタン
                </button>
            </Page>
        );
    }
}
```

## いいところ
オレオレを使うとやっぱり楽。作ってない人はまじで作るべき

## つらいところ
styled-componentsのTypeScriptとReactのTypeScriptで喧嘩が激しい
バージョン上げると大体そこの修正が必要