Code Repository gitee
Creating a Project First, ensure Node.js is installed, then create a project using Vite.vite
1 2 3 npm create vite react-learn cd react-learn npm i
Directory Structure A complete front-end project requires:
State Management Maintain shared state (data) globally to enable data sharing between page components. We use Pinia.
Routing Routing allows navigation between pages. We use Vue Router.
Styling Styling makes the pages more visually appealing. We use TailwindCSS.
Network Requests The front end needs to interact with the back end via network requests to implement functionality. We use 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/ # Development environment build output directory ├── node_modules/ # Node.js dependencies directory ├── public/ # Static resources directory, not processed by build tools ├── src/ ├── admin/ # Contains admin pages ├── api/ # API request logic ├── assets/ # Static resources (images, fonts, etc.) ├── components/ # Common components ├── includes/ # Include files, external libraries ├── lib/ # Project-specific common resources ├── locales/ # Internationalization language packs ├── mocks/ # Mock data ├── pages/ # Contains regular page components ├── router/ # Routing configuration ├── store/ # State management (Pinia) ├── styles/ # Global styles or CSS files ├── utils/ # Utility functions ├── App.jsx # Root component ├── main.js # Project entry file ├── middleware.js # Middleware logic (e.g., route guards) └── settings.js # Project settings or configuration files ├── .gitignore # Git ignore file configuration ├── index.html # Project entry HTML file ├── package.json # Project configuration and dependencies ├── postcss.config.js # PostCSS configuration file ├── README.md # Project documentation ├── tailwind.config.js # Tailwind CSS configuration file ├── vite.config.js # Vite build tool configuration file
Configuration Path Aliases Configure path aliases to use @/
to represent the src/
directory.
1 2 # Node type declarations to prevent errors when using Node dependencies pnpm i @types/node --save-dev
1 2 3 4 5 6 7 8 9 10 11 12 13 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 Configuration 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 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' ), } } })
Project Settings 1 2 3 4 5 6 export default { routeMode : 'history' , BaseURL : 'http://localhost:4000' , timeout : 5000 , }
Tailwind Use Tailwind 3 (if you want to use version 4, there are some installation differences).
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-2 xl; } 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; } }
Remember to import in 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 > ,)
Routing (Router) Use react-router-dom
for routing and implement file-based routing, automatically scanning .jsx
files in the directory and registering them as routes. There are two page directories: admin
for admin pages and pages
for regular page components.
1 2 # v7 pnpm i react-router-dom@7
File Routing Native Approach File routing automatically scans and registers routes based on the directory structure without manual declarations. The key is:
This is provided by Vite to scan and retrieve files. Webpack has a similar method:
Since we are using Vite, we can use import.meta.glob
. Now, let’s implement the file scanning functionality.
Plugin Approach Use vite-plugin-pages
to achieve file-based route registration.
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 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 > ,)
State Management (Store) When learning React, you may find that passing values between components, especially siblings or across levels, can be cumbersome. We can use a global state management solution to address this. Here, we use 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 }), }))
Test in /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 ;
Network Requests (API) Use Axios for network requests. Typically, front-end and back-end development is done by different teams. The front-end request URLs are provided by the back-end. If your back-end is not ready yet, you can use mock data or json-server
for testing.
Wrapper
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
Test
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(); // Prevent default submission behavior const res = await auth.login({username, password}) localStorage.setItem('token', res.token) navigate('/') }} className={`flex flex-col gap-y-2`} > Account <input className ={ `px-2 py-1 border rounded-md `} type ="text" value ={username} onChange ={(e) => setUsername(e.target.value)}/> Password <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 `} > Login </button > </form > </div > ); }; export default Login ;
You can refer to my previous backend web monolithic teaching code templates:rust-web-starter go-web-starter
Next Steps
Simulate interfaces with json-server
Code generator, batch generate repetitive pages and code
You can contact me on these platforms: