Open8

React+TypeScrript+vite+Material UIを始める

horie-thorie-t

プロジェクトの作成

ViteでReact+Typescriptでプロジェクト作成

$ npm create vite@latest frontend -- --template react-ts
Need to install the following packages:
  create-vite@4.1.0
Ok to proceed? (y) y

Scaffolding project in /home/tetsuya/repo/tiny-pmt/frontend...

Done. Now run:

  cd frontend
  npm install
  npm run dev
horie-thorie-t

プロジェクト作成時のメッセージの通りに、実行してみる。

$ cd frontend/
$ npm install

added 83 packages, and audited 84 packages in 12s

8 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
$ npm run dev

> frontend@0.0.0 dev
> vite


  VITE v4.1.4  ready in 329 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

http://localhost:5173/をブラウザで開くと以下の通り。

horie-thorie-t

frontend/src/index.cssfrontend/src/App.cssは中身は全部削除

App.tsxを以下のように変更。

frontend/src/App.tsx
import * as React from 'react';
import Button from '@mui/material/Button';

export default function App() {
  return (
    <div>
      <Button variant="contained">Hello World</Button>
    </div>
  );
}

horie-thorie-t

メニューバーを表示してみる。

App.css, index.cssを空のファイルにする。

frontend/src/App.tsx
import * as React from 'react';
import ButtonAppBar from './ButtonAppBar';

export default function App() {
  return (
    <div>
      <ButtonAppBar></ButtonAppBar>
    </div>
  );
}
frontend/src/ButtonAppBar.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 default function ButtonAppBar() {
  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 }}>
            TinyPMT
          </Typography>
          <Button color="inherit">Login</Button>
        </Toolbar>
      </AppBar>
    </Box>
  );
}
frontend/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TinyPMT</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

horie-thorie-t

Listを表示してみる。

frontend/src/App.tsx
import * as React from 'react';
import ButtonAppBar from './components/ButtonAppBar';
import TicketsComponent from './components/TicketsComponent';

const tickets = [
  {title: "最初のチケット"},
  {title: "2番目のチケット"},
  {title: "3番目のチケット"},
];

export default function App() {
  return (
    <div>
      <ButtonAppBar></ButtonAppBar>
      <TicketsComponent tickets={tickets} />
    </div>
  );
}
frontend/src/components/TicketsComponent.tsx
import React from "react";
import { List } from "@mui/material";
import TicketComponent from "./TicketComponent";

type TicketsComponentProps = {
  tickets: {title: string}[];
}

const TicketsComponent = ({tickets} : TicketsComponentProps) => {
  return (
    <List dense={false}>
      {tickets.map((ticket, i) => <TicketComponent ticket={ticket} key={i}/>)}
    </List>
  );
}

export default TicketsComponent;
frontend/src/components/TicketComponent.tsx
import React from "react";
import { ListItem, ListItemText } from "@mui/material";

type TicketComponentProps = {
  ticket: {title: string}
}

const TicketComponent = ({ ticket }: TicketComponentProps) => {
  return (
    <ListItem>
      <ListItemText primary={ticket.title} />
    </ListItem>
  );
}

export default TicketComponent;

horie-thorie-t

バックエンドからデータ取得

npm install axios
frontend/src/App.tsx
import * as React from 'react';
import ButtonAppBar from './components/ButtonAppBar';
import TicketsComponent from './components/TicketsComponent';
import axios from "axios";

export default function App() {
  const [tickets, setTickets] = React.useState(null);

  React.useEffect(() => {
    axios.get("http://localhost:8080/tickets").then((response) => {
      setTickets(response.data.results);
    });
  }, []);

  return (
    <div>
      <ButtonAppBar></ButtonAppBar>
      {tickets != null ? <TicketsComponent tickets={tickets} /> : ""}
    </div>
  );
}

バックエンドのコントローラのCORSを設定

backend/src/main/java/com/tehorie/tinypmt/presentation/TicketController.java
@RestController
@Tag(name = "Ticket", description = "The ticket API")
@CrossOrigin("http://localhost:5173")
public class TicketController {