コードリポジトリ

gitee

プロジェクトの作成

まずNode.jsがインストールされていることを確認し、次にViteを使用してプロジェクトを作成します。
vite

1
2
3
npm create vite react-learn
cd react-learn
npm i

ディレクトリ構造

完全なフロントエンドプロジェクトには以下が必要です:

  • ステート管理
    グローバルで共有される状態(データ)を維持し、ページコンポーネント間でデータを共有するためにPiniaを使用します。
  • ルーティング
    ルーティングによりページ間でジャンプが可能になり、Vue Routerを使用します。
  • スタイル
    スタイルによりページがより美しくなり、TailwindCSSを使用します。
  • ネットワークリクエスト
    フロントエンドはネットワークリクエストを通じてバックエンドとデータをやり取りし、機能を実現するためにAxiosを使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
├── dev-dist/               # 開発環境ビルド出力ディレクトリ
├── node_modules/ # Node.js依存パッケージディレクトリ
├── public/ # 静的リソースディレクトリ、ビルドツールによって処理されない
├── src/
├── admin/ # バックエンド管理ページを格納
├── api/ # APIリクエストロジック
├── assets/ # 静的リソース(画像、フォントなど)
├── components/ # 共通コンポーネント
├── includes/ # 外部ライブラリを含むファイル
├── lib/ # プロジェクト内の共通リソースを格納
├── locales/ # 国際化言語パッケージ
├── mocks/ # モックデータ
├── pages/ # 普通のページコンポーネントを格納
├── router/ # ルーティング設定
├── store/ # ステート管理(Pinia)
├── styles/ # グローバルスタイルやCSSファイル
├── utils/ # ユーティリティ関数
├── App.jsx # ルートコンポーネント
├── main.js # プロジェクトエントリファイル
├── middleware.js # ミドルウェアロジック(例:ルーティングガード)
└── settings.js # プロジェクト設定や設定ファイル
├── .gitignore # Git無視ファイル設定
├── index.html # プロジェクトエントリHTMLファイル
├── package.json # プロジェクト設定及び依存関係の宣言
├── postcss.config.js # PostCSS設定ファイル
├── README.md # プロジェクト説明ドキュメント
├── tailwind.config.js # Tailwind CSS設定ファイル
├── vite.config.js # Viteビルドツール設定ファイル

設定

パスエイリアス

パスエイリアスを設定し、@/src/ディレクトリを表します。

1
2
# Nodeの型宣言、Nodeの依存関係を使用後にエラーを防ぐ
pnpm i @types/node --save-dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {defineConfig} from 'vite'
import {join} from 'path';
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
// パスエイリアス
resolve: {
alias: {
'@':
join(__dirname, 'src'),
}
}
})

PWA設定

1
2
# v1
pnpm i vite-plugin-pwa --save-dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import {defineConfig} from 'vite'
import {join} from 'path';
import react from '@vitejs/plugin-react'
import {VitePWA} from "vite-plugin-pwa";
// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
devOptions: {
// マニフェストファイルを生成
enabled: true
},
manifest: {
name: "vue-quick-start",
theme_color: '#ff5e3a',
icons: [
{
src: 'assets/logo.png',
size: '192x192',
type: 'image/png'
}
]
},
workbox: {
globPatterns: ['**/*.{js,css,html,png,jpg}']
}
})
],
resolve: {
alias: {
'@':
join(__dirname, 'src'),
}
}
})

プロジェクトの設定

1
2
3
4
5
6
// settings.js
export default {
routeMode: 'history', // ルーティングモード
BaseURL: 'http://localhost:4000', // バックエンドリクエストアドレス
timeout: 5000, // リクエストタイムアウト時間
}

Tailwind

Tailwind3を使用します(Tailwind4を使用することも可能ですが、インストール方法に若干の違いがあります)。

1
2
pnpm install -D tailwindcss@3 postcss autoprefixer
pnpm dlx tailwindcss@3 init -p

tailwind.config.js

1
2
3
4
5
6
7
8
9
10
11
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

styles/base.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@tailwind base;
@layer base {
h1 {
@apply text-2xl;
}

h2 {
@apply text-xl;
}

h3 {
@apply text-lg;
}

h4 {
@apply text-base;
}

h5 {
@apply text-sm;
}

h6 {
@apply text-xs;
}
}

@tailwind components;
@tailwind utilities;
body {
@apply h-full w-full p-0 m-0;
}

/* デフォルトの最大幅制限を上書き */
@media (min-width: 1536px) {
.container {
max-width: 100%;
}
}

/* ページの主内容 */
.container {
width: 100%;
height: 100%;
background-color: #f5f7fb;
padding: 20px;

.container-wrapper {
width: 100%;
height: 100%;
padding: 15px 30px 0;
background-color: #fff;
border-radius: 8px;
display: flex;
flex-direction: column;
overflow: hidden;
}
}

/* プリントコントロールのスタイル設定 */
.plugin-download {
width: 500px !important;

a:hover {
text-decoration: underline;
}
}

src/main.jsにインポートすることを忘れないでください。

1
2
3
4
5
6
7
8
9
10
import {StrictMode} from 'react'
import {createRoot} from 'react-dom/client'
import App from './App.jsx'
import '@/styles/base.css'

createRoot(document.getElementById('root')).render(
<StrictMode>
<App/>
</StrictMode>,
)

ルーティング router

React Router DOMを使用してルーティングジャンプを実装し、ファイルルーティングを実現します。ディレクトリ内のpage.jsx
ファイルを自動的にスキャンし、ルートとして登録します。2つのページディレクトリがあります:1つはadmin、もう1つはpagesです。
adminディレクトリはバックエンド管理ページを格納し、pagesディレクトリは通常のページコンポーネントを格納します。

1
2
# v7
pnpm i react-router-dom@7

ファイルルーティング

原生ソリューション

ファイルルーティングはディレクトリ構造に基づき、自動的にスキャンしルートを登録します。手動で個別に宣言・登録する必要はありません。実装の鍵は以下のメソッドです。

1
import.meta.glob("xxx")

これはViteが提供するメソッドで、ファイルをスキャンして取得します。Webpackにも同様のメソッドがあります。

1
require.context()

Viteを使用しているため、import.meta.globを使用します。ファイルスキャン機能を実装しましょう。

プラグインソリューション

vite-plugin-pagesを使用してもファイルルーティングの登録を実現できます。

1
2
npm install -D vite-plugin-pages
npm install react-router react-router-dom

vite.config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
import {VitePWA} from "vite-plugin-pwa";
import {join} from 'path';
import pages from 'vite-plugin-pages'
// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
///
/*pages({
// カスタム設定
// dir: 'src/pages', // ルートコンポーネントディレクトリ
extensions: ['jsx', 'tsx'], // サポートするファイル拡張子
// exclude: ['components'], // コンポーネントディレクトリを除外
base: process.env.VITE_APP_BASE_URL
}),*/
pages({
// 複数のディレクトリを登録し、異なるルートプレフィックスを設定
dirs: [
// 基本
{dir: 'src/pages', baseRoute: ''},
// カスタムファイルパターン付き
{dir: 'src/admin/', baseRoute: 'admin'},
],
}),
],
resolve: {
alias: {
'@':
join(__dirname, 'src'),
}
},
})

src/components/router-guard.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import {matchPath, Route, Routes, useLocation, useNavigate, useRoutes} from "react-router-dom";
import {Suspense, useEffect} from "react";
import routes from "~react-pages";
import Login from "../pages/login.jsx";
import NotFount from "../pages/not-fount.jsx";

const isAuthenticated = () => {
return localStorage.getItem("token") !== null; // サンプルロジック
};

// グローバルルートコンポーネント
function RouterGuard() {
const navigate = useNavigate();
const location = useLocation();
// `requiresAuth`属性を追加
const toNeedAuth = (r) => {
const route = {...r, requiresAuth: true};
if (route.children) {
route.children = route.children.map(c => toNeedAuth(c));
}
return route;
}
const authRoutes = routes.map(toNeedAuth)
useEffect(() => {
/*
// ネストされたルートをマッチングできない
const currentRoute = authRoutes.find(route =>
matchPath({path: route.path, end: true}, location.pathname)
);
console.log(currentRoute)
*/
console.log(location.pathname)
// グローバルナビゲーションガードロジック
/*
if (currentRoute?.requiresAuth && !isAuthenticated()) {
navigate("/login", {replace: true});
}
*/
// 後で認証不要なページを追加
if (location.pathname !== '/login' && !isAuthenticated()) {
navigate("/login", {replace: true});
}
}, [location, navigate]); // ルート変更を監視
return (
<Suspense fallback={<p>Loading...</p>}>
{useRoutes(
[
...authRoutes,
{
path: 'login',
element: <Login/>
},
{
path: "*",
element: <NotFount/>
}
]
)}
</Suspense>
);
}

export default RouterGuard;

app.jsx

1
2
3
4
5
6
7
8
9
import RouterGuard from "./components/router-guard.jsx";

function App() {
return (
<RouterGuard/>
)
}

export default App

main.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
import {StrictMode} from 'react'
import {createRoot} from 'react-dom/client'
import App from './App.jsx'
import '@/styles/base.css'
import {BrowserRouter} from "react-router-dom";

createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<App/>
</BrowserRouter>
</StrictMode>,
)

ステート管理 store

Reactを学んだ際に、元のコンポーネント間での値の伝達が非常に面倒な状況に遭遇することがあります。例えば、兄弟/階層間での値の伝達などです。これを解決するために、グローバルなステート管理のソリューションを使用できます。ここではZustandを使用します。

1
pnpm i zustand

src/store/count/index.js

1
2
3
4
5
6
7
import {create} from 'zustand'

export const useCountStore = create((set) => ({
count: 0,
increment: () => set((state) => ({count: state.count + 1})),
reset: () => set({count: 0}),
}))

テスト /pages/index.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';
import {useCountStore} from "../store/count/index.js";

const Index = () => {
const {count, increment, reset} = useCountStore()
localStorage.setItem("token", "123")
return (
<div className={`flex items-center flex-col`}>
<div className={``}>
{count}
</div>
<div className={`flex gap-x-4`}>
<button
className={`bg-blue-400 py-1 px-2 rounded-lg`}
onClick={() => increment()}>+1
</button>
<button
className={`bg-blue-400 py-1 px-2 rounded-lg`}
onClick={() => reset()}>reset
</button>
</div>
</div>
);
};
export default Index;

ネットワークリクエスト api

Axiosを使用してネットワークリクエストを行います。通常、フロントエンドとバックエンドは別々のチームで開発されるため、フロントエンドはバックエンドが提供するAPIエンドポイントを使用します。バックエンドのAPIがまだない場合は、モックデータやjson-serverを使用してテストを行うことができます。

1
2
# v1
pnpm i axios

ラップ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// request.js
import axios from 'axios'
import settings from "../settings.js";

const request = axios.create({
baseURL: settings.BaseURL, // ベースURLを設定
timeout: settings.timeout, // リクエストタイムアウト時間
})
// リクエストインターセプター
request.interceptors.request.use(
(config) => {
// 送信前に何かを行う、例えばトークンを追加
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
(error) => {
// リクエストエラーに対する処理
return Promise.reject(error)
}
)
// レスポンスインターセプター
request.interceptors.response.use(
(response) => {
// レスポンスデータに対する処理
return response.data
},
(error) => {
// レスポンスエラーに対する処理
return Promise.reject(error)
}
)
export default request

テスト

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
* @Author Malred · Wang
* @Date 2025-06-23 16:42:29
* @Description
* @Path src/pages/login.jsx
*/
import React, {useState} from 'react';
import auth from '@/api/auth/index.js'
import {useNavigate} from "react-router-dom";

const Login = () => {
const navigate = useNavigate();
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
return (
<div className={`flex items-center flex-col`}>
<h1>login</h1>
<form
onSubmit={async (e) => {
e.preventDefault(); // デフォルトの送信動作を阻止
const res = await auth.login({username, password})
localStorage.setItem('token', res.token)
navigate('/')
}}
className={`flex flex-col gap-y-2`}
>
アカウント
<input className={`px-2 py-1 border rounded-md`} type="text" value={username}
onChange={(e) => setUsername(e.target.value)}/>
パスワード
<input className={`px-2 py-1 border rounded-md`} type="password" value={password}
onChange={(e) => setPassword(e.target.value)}/>
<button
type={"submit"}
className={`text-white bg-blue-400 py-1 px-2 rounded-lg`}
>
ログイン
</button>
</form>
</div>
);
};
export default Login;

バックエンドサービスは、以前のバックエンドWebモノリシック教學コードテンプレートを参考にしてください:
rust-web-starter
go-web-starter

次のステップ

  • json-serverでAPIを模擬
  • コードジェネレーターで、繰り返しのページとコードを一括生成

コミュニティ

以下のプラットフォームで連絡できます:


本站总访问量