【React + Typescript】ダークモード対応画面作ってみた

# 1.前提条件
### 1-1.プロジェクト作成
開発環境としては、React + Typescriptで開発していきます。

尚、執筆時のバージョンは以下のようになっています。
```
$ node -v 
v16.17.1
```
実際に `create-react-app` でプロジェクトを作成します。
```
$ npx create-react-app my-project --template typescript
```
`my-project` の部分は自分の好きなプロジェクト名を設定して下さい。
上記のコマンドを確定させた後、`Happy hacking!` という文字が表示されれば成功です。

### 1-2.プロジェクト起動
上記で作成したプロジェクトのディレクトリに移動し、起動します。
```
$ cd {プロジェクト名}
$ yarn start
```
ブラウザが起動し、以下のような画面が表示されていれば成功です。
![スクリーンショット 2023-01-22 15.38.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115156/01c27f7d-c0ea-30f0-4a8f-c1df95550da4.png)

### 1-3.MUIのインストール
MUIと関連するモジュールをインストールします。
```
$ npm install @mui/material @mui/icons-material @emotion/react @emotion/styled
```
これでプロジェクト作成の準備は完了です。

# 2.画面の作成
### 2-1.トップ画面の作成
ダークモードが反映されているかを視覚的にわかりやすくするために、MUIのコンポーネントを使用したトップ画面を作成します。
MUIの `Basic App bar` を使用して作成します。（[公式サイト](https://mui.com/)）
以下がトップ画面のソースコードです。
```typescript:src/components/pages/top/top.tsx
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';

export const Top = () => {
  return (
    <Box sx={{ flexGrow: 1 }}>
      <AppBar position="static">
        <Toolbar>
          <IconButton
            size="large"
            edge="start"
            color="inherit"
            aria-label="menu"
            sx={{ mr: 2 }}
          >
            <MenuIcon />
          </IconButton>
          <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
            News
          </Typography>
          <Button color="inherit">Login</Button>
        </Toolbar>
      </AppBar>
    </Box>
  );
}
```
実際のWebアプリ制作に近い環境で作成したいため、 `components` ディレクトリを作成しました。
そして、作成したトップ画面を表示するために `App.tsx` 、`App.test.tsx` を以下のように編集します。
```typescript:App.tsx
import React from 'react';
import { Top } from './components/pages/top/top';

export const App = () => {
  return (
    <>
        <Top />
    </>
  );
}
```
```typescript:App.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import { App } from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});
```
上記のように編集できたら以下のような画面になります。
![スクリーンショット 2023-01-22 17.26.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115156/1b1a52ad-be19-0907-72c5-672c065c7972.png)

:::note info
関数を `export const 関数名 = () =>` にしている理由としては、import、exportで命名が統一できるからです。
そして、命名を統一することでスペルミスによるバグを未然に防ぐことができます。
また、import側で型定義ができるなど多くの利点があります。
このように、関数名を指定してexportすることを **named export** といいます。
:::

# 3.ダークモード対応化
### 3-1.MUIコンポーネントを使用したダークモード対応画面の作成
今回作成するダークモード対応画面はその画面を表示しているパソコンの外観モードに対応した画面となります。
使用するMUIコンポーネントは[こちら](https://mui.com/material-ui/customization/dark-mode/#system-preference)です。
上記コンポーネントを使用して、`App.tsx` を以下のように編集します。
```typescript:App.tsx
import * as React from 'react';
import useMediaQuery from '@mui/material/useMediaQuery';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { Top } from './components/pages/top/top'

export const App = () => {
  const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');

  const theme = React.useMemo(
    () =>
      createTheme({
        palette: {
          mode: prefersDarkMode ? 'dark' : 'light',
        },
      }),
    [prefersDarkMode],
  );

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Top />
    </ThemeProvider>
  );
}

```
:::note info
`useMediaQuery` はMUI独自のもので、`true` or `false` の結果を返します。
:::

### 3-2.結果
結果を画面で確認します。

システム設定がダークモードの場合
![スクリーンショット 2023-01-22 18.13.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115156/65000b0b-efe1-e5d5-9cf4-3fde3140cb74.png)
実際の画面
![スクリーンショット 2023-01-22 18.15.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115156/7cd8ff58-38e0-646f-ca8c-6833564add86.png)

システム設定がライトモードの場合
![スクリーンショット 2023-01-22 18.16.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115156/3d698851-12d4-109c-845d-f542ff2279d7.png)
実際の画面
![スクリーンショット 2023-01-22 18.17.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115156/b8ef88a0-1936-cf4d-c1cb-0aa78d575365.png)
これでダークモード対応画面が完成しました！