コードリポジトリ
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'
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";
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
| 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
| 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
|
ファイルルーティング
原生ソリューション
ファイルルーティングはディレクトリ構造に基づき、自動的にスキャンしルートを登録します。手動で個別に宣言・登録する必要はありません。実装の鍵は以下のメソッドです。
これはViteが提供するメソッドで、ファイルをスキャンして取得します。Webpackにも同様のメソッドがあります。
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'
export default defineConfig({ plugins: [ react(),
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(); 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(() => {
console.log(location.pathname)
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を使用します。
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 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
| import axios from 'axios' import settings from "../settings.js";
const request = axios.create({ baseURL: settings.BaseURL, 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
|
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を模擬
- コードジェネレーターで、繰り返しのページとコードを一括生成
コミュニティ
以下のプラットフォームで連絡できます: