ReactでSPAなポートフォリオサイトを作ってみた

ReactでSPAなポートフォリオを作ってみたので、紹介とざっくり解説です。
備忘録なのでこれからReactを始める際の参考にでもしていただけると幸いです。
[ソースコード：GitHub](https://github.com/Shin-Taro/Portfolio)
[ポートフォリオ](https://shin-taro.github.io/Portfolio/)

# 環境
`npx create-react-app --template typescript`で環境構築しています。
バージョンはざっくり以下の通り。

```
//Node.js//
node -v
v16.9.1

//npm//
npm -v
7.21.1
```

```json:package.json
{
  "react": "^17.0.2",
  "react-dom": "^17.0.2",
  "react-scripts": "4.0.3",
  "typescript": "^4.4.4",
  "react-router-dom": "^5.3.0",
  "styled-components": "^5.3.3",
  "styled-media-query": "^2.1.2",
  "styled-reset": "^4.3.4",
  "gh-pages": "^3.2.3"
}
```

# Routing
react-router-domで各ページのルーティングをしています。

Pagesコンポーネントで各ページのコンポーネントを設定。

```tsx:Pages.tsx
const Pages = () => {
  return(
    <Switch>
      <Route exact path="/" component={Home} />
      <Route exact path="/about" component={About} />
      <Route exact path="/skill" component={Skill} />
      <Route exact path="/works" component={Works} />
    </Switch>
  );
};

export default Pages;
```

App.tsxで`<BrowserRouter>`を設定。

ちなみにHeaderに`<Link />`を使用しているのですが、うっかり`<BrowserRouter>`の外で使うと怒られるので注意。

```tsx:App.tsx
const App = () => {
  return (
    <>
      <BrowserRouter basename={process.env.PUBLIC_URL}>
          <Header/>
          <Pages/>
          <Footer/>
      </BrowserRouter>
    </>
  );
};

export default App;
```

# styled-components
今回勉強も兼ねてstyled-componentsを使用しました。

Sassでこう書いていたものが、

```tsx:Hoge.tsx
const Hoge = () => {
  return(
    <div className="hoge">
      <h1 className="hoge__title">title</h1>
    </div>
  );
}
```

```scss:hoge.scss
.hoge{
  background-color: black;

  &__title{
    font-size: 20px;
  }
}
```

styled-componentsを使うとこう書けます。

```tsx:Hoge.tsx
import styled from "styled-components";

const Hoge = () => {
  return(
    <Wrapper>
      <Title>title</Title>
    </Wrapper>
  );
};

const Wrapper = styled.div`
  background-color: black;
`;

const Title = styled.h1`
  font-size: 20px;
`;
```

コンポーネント内でスコープが閉じるため命名規則で悩まなくて良くなります。
また、すべてjs(またはts)で書けるため値の共有が容易になります。
今回はシンプルなサイトのためそれほど大きな恩恵は有りませんが、全体的にスタイルの管理がしやすくなるという印象でした。

## media query
styled-componentsでmedia queryを設定する方法はいくつかありますが、
今回は[styled-media-query](https://github.com/morajabi/styled-media-query)というパッケージを使用しました。

styled-media-queryを使った書き方はこのようになります。

```tsx:Hoge.tsx
import styled from "styled-components";
import media from "styled-media-query";

const Hoge = () => {
  return(
    <Wrapper>
      <Title>title</Title>
    </Wrapper>
  );
};

const Wrapper = styled.div`
  background-color: black;

  ${media.lessThan("medium")`
    background-color: white;
  `};
`;

const Title = styled.h1`
  font-size: 20px;

  ${media.lessThan("medium")`
    font-size: 14px;
  `};
`;
```

syled-componentsのテンプレートリテラル内で``${media.メソッド名(ブレイクポイント)`スタイル`;};``
のようにすることで実装出来ます。
メソッドには`lessThan` `greaterThan` `between`があり、ブレイクポイントには事前に定義されている`"medium"`等の値か任意の数値を渡します。
それほど多くの機能はないので、[GitHub](https://github.com/morajabi/styled-media-query)を見ればすぐに使えます。
個人的には、Sassで`@mixin`で定義して`@include mq(u-sp){}`みたいにするのと同じ感覚で使えるので非常に使いやすかったです。

## Reset CSS
styled-componentsでもreset CSSを適用できます。
色々とやり方は有りそうですが、今回は[styled-reset](https://github.com/zacanger/styled-reset)を使用しました。

styled-componentsの`createGlobalStyle`を利用すれば、独自のリセット用スタイルを追加することも出来ます。
ルート付近に設置すれば全体にスタイルが流れてくれます。

```tsx:App.tsx
import { createGlobalStyle } from 'styled-components'
import reset from 'styled-reset'

const GlobalStyle = createGlobalStyle`
  ${reset}
  *, *::after, *::before {
    box-sizing: border-box;
  }
`

const App = () => {
  return (
    <>
      <ResetStyle/>
      <BrowserRouter basename={process.env.PUBLIC_URL}>
          <Header/>
          <Pages/>
          <Footer/>
      </BrowserRouter>
    </>
  );
};
```

## Themeの設定
styled-componentsにはReactのContext機能を基にしたThemeProviderがあります。

まずはthemeを設定します。

```ts:theme.ts
export const theme = {
  color:{
    dark: "#222",
    gray: "#333",
    white: "#fff"
  },
  font:{
    primary: 18px,
    scondary: 12px
  }
//other themes//
}
```

themeを使用したいコンポーネントを`<ThemeProvider>`でwrapします。(大抵はルート付近)
`<ThemeProvider>`のthemeプロパティに先程作成した`theme`を渡すと配下のコンポーネントから参照できます。

```tsx:App.tsx
import { ThemeProvider } from "styled-components";
import { theme } from './styles/theme';

const App = () => {
  return (
    <>
      <ResetStyle/>
      <BrowserRouter basename={process.env.PUBLIC_URL}>
        <ThemeProvider theme={theme}>
          <Header/>
          <Pages/>
          <Footer/>
        </ThemeProvider>
      </BrowserRouter>
    </>
  );
};

```

themeを使う側ではこのようになります。

```tsx:Hoge.tsx
import styled from "styled-components";

const Hoge = () => {
  return(
    <Wrapper>
      <Title>title</Title>
    </Wrapper>
  );
};

const Wrapper = styled.div`
  background-color: ${props => props.theme.color.dark};
`;

const Title = styled.h1`
  font-size: ${props => props.theme.font.primary};
`;
```

`<ThemeProvider theme={theme}>`で設定したthemeは配下のコンポーネントに自動的にpropsとして渡されるため、`props.theme`で参照できます。

## アニメーション
styled-componentsでも`@keyframes`が使えます。

```tsx:Hoge.tsx
import styled, { keyframes } from "styled-components";

const Hoge = () => {
  return(
    <Wrapper>
      <Title>title</Title>
    </Wrapper>
  );
};
//keyframes定義//
const fadeUp = keyframes`
  from{
    opacity: 0;
    transform: translateY(30px);
  }
  to{
    opacity: 1;
    transform: translateY(0);
  }
`;
//animationプロパティで適用//
const Title = styled.h1`
  animation: ${fadeUp} 0.4s linear;
`;
```

## スタイルの共通化
タイトルやセクション単位でレイアウトを調整するWrapper等、多くのコンポーネントでスタイルが共通する要素をutilityとして切り出しました。

```tsx:utility.tsx
...
export const Title = styled.h1`
  color: ${props => props.theme.color.white};
  font-size: ${props => props.theme.font.large};
  ${media.lessThan("medium")`
    font-size: ${props => props.theme.font.small};
  `}
`;
...
```

```tsx:Hoge.tsx
import { Title } from "../styles/utility"

const Hoge = () => {
  return(
    <Wrapper>
      <Title>title</Title>
    </Wrapper>
  );
};

const Wrapper = styled.div`
  background-color: ${props => props.theme.color.dark};
`;
```

この要領で汎用的なスタイルを切り出していけるのですが、個人的に課題があります。

1. ユーティリティの`Title`では無くコンポーネント独自のタイトルを定義したいとなったときや、  
`Title`を拡張して使いたいとなった場合、ある程度命名規則がないと可読性が下がる。
* そもそもコンポーネント毎に閉じたスコープでスタイルを管理する事が出来るstyled-componentsの良さが薄まるのではないか。

当然開発方針によって運用は変わると思いますが、プロダクトに採用している現場ではどのようにしているか気になりました。

#GitHub Pagesで公開
このポートフォリオはGitHub Pagesを利用して公開しています。
いくつか手順がありますが、今回は[gh-pages](https://github.com/tschaub/gh-pages)を使用しました。

package.jsonにscriptを定義して、

```json:package.json
"scripts": {
  "build": ...,
  "deploy": "gh-pages -d dist"
}
```

`dist`の部分はbundleしたファイルが出力されるディレクトリ名です。`build`とか`public`とか。
そしたら、コマンドを叩くだけでデプロイできます。

```
//buildして//
npm run build
//deployする//
npm run deploy
```

仕組み的にはgh-pagesブランチを切るやり方と同じです。
deployコマンドを叩くとGitHubで自動的にgh-pagesのブランチが作られ、distの中身だけがpushされた状態になっています。

# まとめ
半分くらいstyled-componentsの話になってしました。
単純なサイトでもReactを使うと効率よく開発することができました。
また、単純なサイトだからこそあまり恩恵がない部分なども見えてきたので良い勉強になりました。