Based on mobile configuration.
Vite website
Advantages:
- The speed of light to start
- Hot Module Replacement
- According to the need to compile
Scaffolding function
- Antd-mobile Mobile component
- Axios network data interaction
- Hox status management
- React -router-dom Route management
- Postcss-px-to-viewport Move px to VW /vh
- Less precompiled
- Autoprefixer Automatic completion
- Typescript grammar
- Window.$cancelRequest() cancels the request, and an error is reported after initialization (i.e., before axios requests data).
The installation
Install the Vite and React frameworks
npm init vite@latest
Options:
Installation completed:
- Project name: indicates the Project name, which is also the folder name
- Select a framework. Select vue and React
- Select a Variant: Select js/ TS version
Directory structure:
Vite. Config. ts: Configuration file, related parameters visit here
Without the public folder, index.html is the same as the SRC folder. You can customize a public folder
Installation-dependent dependencies
Go to the viet-react directory and install dependencies
npm install
Start the
npm run dev
Run as shown:
View the operating environment:
If you find the Process red wavy line, follow the instructions to install @types/ Node. This package contains the node.js type definition.
npm i --save-dev @types/node
Re-open vscode, the red wavy line disappears,
packaging
npm run build
Look at the directory structure:
Js, CSS, and images are all in an Assets folder.
Take a look at the HTML file:
Some of the related imports all start with /assets. If you don’t put them in the server root directory, you will see a blank page, so modify the configuration file vite.config.ts
export default defineConfig({
base:'/'
})
Copy the code
When packaging again, I found that the dist folder was not deleted and repackaged like VUe-CLI or umiJS, but was directly modified into a file, greatly increasing the packaging speed
After adding the Base configuration:
If some static files don’t want to be hashed and just want to use urls, you can customize a public folder
The environment variable
Let’s take a look at the package command in package.json:
NPM run dev (env=production), NPM run serve(env=production), NPM run build(env=production)
Now add a test environment:
-
Add a new.env.test file with the same level as/SRC and add the following
NODE_ENV=test Copy the code
-
Json file to add packaging commands
"scripts": { "dev": "vite"."build": "tsc && vite build"."test": "tsc && vite build --mode test".// This command "serve": "vite preview" }, Copy the code
-
Run the package command NPM run test, and the value of process.env.node_env is test
routing
The installation
npm install react-router-dom --save-dev
React-router-dom
use
Create a page
Start with a few page components:home/home.tsx
,about/about.tsx
,login/login.tsx
,user/user.tsx
,404/404.tsx
home.tsx
import { withRouter,Link } from 'react-router-dom'
function Home(props: any) {
return (
<div className="Home">
<p>home</p>
<Link to='/user'>user</Link><br/>
<Link to='/user? id=1111'>user(search)</Link><br/>/ / search parameter<Link to={{ pathname: '/user', search: 'id=123' }}>user(search)</Link><br/>/ / search parameter<Link to={{ pathname: '/user', state: { num: '002'}}} >user(state)</Link><br/>/ / state parameter<Link to={{pathname: '/user', query: {num: '003'}}} >user(query)</Link><br/>/ / query parameter</div>)}export default withRouter(Home)
Copy the code
login.tsx
import { withRouter } from 'react-router-dom'
function Login(props:any) {
const login = () = > {
let redirect = decodeURIComponent(props.location.search.split('redirect=') [1]).split('&')
let path = redirect[0] // Redirected route after successful login
const data = JSON.parse(redirect[1]) Routerconfig. TSX routerConfig. TSX routerConfig. TSX routerConfig. TSX routerConfig. TSX routerConfig. TSX routerConfig. TSX
sessionStorage.setItem('token'.'123')
props.history.replace({pathname:path,... data}) }return (
<div className="login">
login
<button onClick={()= >{login()}}> Login</button>
</div>)}export default withRouter(Login)
Copy the code
About.tsx and User.tsx and 404.tsx are about the same
import { withRouter } from 'react-router-dom'
function User(props:any) {
return (
<div className="User">
user
</div>)}export default withRouter(User)
Copy the code
Since we are using a functional component, if the history method is required in the component:
-
WithRouter is used in the high-order component of the react-router-DOM to wrap a component inside the Route. Then the three objects of the React-Router, history, location, and match, will be placed in the props property of the component.
-
React-router-dom: react-router-dom: react-router-dom: react-router-dom: react-router-dom: react-router-dom: react-router-dom: react-router-dom: react-router-dom: react-router-dom
import { useHistory } from "react-router-dom";
function HomeButton() {
let history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
Copy the code
Routing configuration file:router/router.ts
androuterConfig.tsx
Router. TSX: router-dom: router-dom: router-dom: router-dom: router-dom: router-dom: router-dom: router-dom: router-dom: router-dom: router-dom: router-dom: router-dom
Create a new typing.d.ts file in the viet-react root directory as a global declaration file and do the following:
declare module 'react-router-dom'
Copy the code
Then include the tsconfig.json file:
{ // Other configurations
"include": [
// Other configurations
"./typing.d.ts"]}Copy the code
Wavy lines disappear.
To facilitate the introduction of components, we configure an alias, like vue:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
const path = require("path");
// https://vitejs.dev/config/
export default defineConfig({
// ...
resolve: {
alias: {
The '@':path.resolve(__dirname, "src")}}})Copy the code
Introducing components to router.tsx:
React-router-dom is handled in typing. D. ts:
declare module 'react-router-dom'declare module '@ / *'
Copy the code
Router.ts is configured for the route
Suspense+lazy routing load is implemented in Suspense with router. Ts and React
import React from 'react'
/ / component
const Home = React.lazy(() = > import('@/pages/home/home')) // Routes are lazy to load, cooperate with ap.tsx Suspense
const About = React.lazy(() = > import('@/pages/about/about'))
const Login = React.lazy(() = > import('@/pages/login/login'))
const User = React.lazy(() = > import('@/pages/user/user'))
const Miss = React.lazy(() = > import('@/pages/404/404'))
const routerMap:any[] = [{path: '/'.redirect: '/home'.auth: false.footerShow: true
},
{
path: '/home'.component: Home,
auth: false.footerShow: true
},
{
path: '/about'.component: About,
auth: false.footerShow: true
},
{
path: '/login'.component: Login,
auth: false.footerShow: false
},
{
path: '/user'.component: User,
auth: true.footerShow: false
},
{
path: '/ 404'.component: Miss,
auth: false.footerShow: false},]export default routerMap
Copy the code
Verify the route permission. Configure RouterConfig. TSX
routerConfig.tsx
import { Route,Redirect,withRouter } from 'react-router-dom';
import routerMap from './router'
const BasicRoute = (props:any) = > {
const pathname = props.location.pathname
const targetRouter = routerMap.find((item: any) = > item.path === pathname);
const isLogin = sessionStorage.getItem('token')
if(! targetRouter) {// The page does not exist
return <Redirect to="/ 404" />
}
if (targetRouter && targetRouter.redirect) { / / redirection
return <Redirect to={targetRouter.redirect} />
}
if(targetRouter && ! targetRouter.auth) {// No login required
return <Route exact path={targetRouter.path} component={targetRouter.component}/>
}
if (targetRouter.auth) { // Login authorization is required
if (isLogin) {
return <Route exact path={targetRouter.path} component={targetRouter.component} />
} else {
let redirect = pathname
const query = JSON.stringify(props.location? .query)const state = JSON.stringify(props.location? .state)constsearch = props.location? .searchif (query) { / / query parameter
redirect += `&{"query":${query}} `
}
if (state) { / / state parameter
redirect += `&{"state":${state}} `
}
if (search) { / / search parameter
redirect += `&{"search":"${search}"} `
}
redirect = encodeURIComponent(redirect)
return <Redirect to={` /login?redirect=${redirect}`} / >}}};// exact: exact match
export default withRouter(BasicRoute);
Copy the code
Since there is no navigational guard like vue-Router, permission validation has to be configured, including 404 pages. Basic routing and permissions are almost complete.
App.tsx: It looks a little bit like Vue
import { Switch ,NavLink,withRouter} from 'react-router-dom';
import RouterView from '@/router/routerConfig'
import {useState,useEffect,Suspense} from 'react'
import routerMap from './router/router'
import './App.css'
function App(props:any) {
const [footerShow, setFooterShow] = useState(false)
const routerChange = () = > {
const targetRouter = routerMap.find((item: any) = >item.path === props.location.pathname); setFooterShow(targetRouter? .footerShow) } useEffect(() = > {
routerChange()
}, [props.location])
return (
<div className="page">
<div className="content">
<Suspense fallback={<div>Loading...</div>}> {/* With router.ts lazy load */}<Switch>
<RouterView />
</Switch>
</Suspense>
</div>
{footerShow ? <div className="footer">
<NavLink to="/home" className="item">Home page</NavLink>
<NavLink to="/about" className="item">about</NavLink>
</div> : ''}
</div>)}export default withRouter(App)
Copy the code
Main. TSX: BrowserRouter was added
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter} from 'react-router-dom';
import './index.css'
import App from './App'
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>.document.getElementById('root'))Copy the code
Route parameter transmission mode
import { withRouter,Link } from 'react-router-dom'
function Home(props: any) {
return (
<div className="Home">
<p>home</p>
<Link to='/user'>user</Link><br/>
<Link to='/user? id=1111'>user(search)</Link><br/>/ / search parameter<Link to={{ pathname: '/user', search: 'id=123' }}>user(search)</Link><br/>{/** /user? id=123 **/}<Link to={{ pathname: '/user', state: { num: '002'}}} >user(state)</Link><br/>/ / state parameter<Link to={{pathname: '/user', query: {num: '003'}}} >user(query)</Link><br/>// query pass parameter // function pass parameter<button onClick={()= >Push ({pathname:"/user",search:'123456'})}> Jump through function</button>{/** /user? 123456 * * /}<button onClick={()= >This.props.history.push({pathName :"/user",state: {num: '002'}})}> Jump to the detail component by function</button>
<button onClick={()= >Push ({pathname:"/user",query: {num: '002'}})}> Jump to the detail component by function</button>
</div>)}export default withRouter(Home)
Copy the code
- Advantages of query parameter transmission: Parameters can be transmitted to objects. Disadvantages: Refresh the address bar, parameter loss
- Advantages of state parameter transmission: Parameters can be transmitted objects; Disadvantages: Refresh the address bar, parameter loss
- Advantage of params passing parameters: Refresh the address bar, but parameters still have disadvantages: only strings can be passed, and if too many values are passed, the URL can become long and ugly. (Objects can be passed as strings)
- The search parameter pass advantage: Refresh the address bar. The parameter still has disadvantages: only strings can be passed, and if too many values are passed, the URL can become long and ugly. (Objects can be passed as strings)
Navigation guard. – Confirm before departure
import { useState } from 'react'
import { withRouter,Prompt } from 'react-router-dom'
function User(props: any) {
const [leave, setLeave] = useState(true)
return (
<div className="User">
user
<Prompt message={()= >{ if (! Leave) {return true} const r = confirm(' Leave? ') return r }} when={leave}></Prompt>
</div>)}export default withRouter(User)
Copy the code
- Message: string/function, function returns false by default to continue on the current page.
- When: Boolean, true to prompt, false not to prompt
Something like this:
Axios communication
The installation
npm install axios
use
New file:
-
ApiNames. Ts: interface name file
export default { entrustStorageExport: '/exportDetails'./ / download commonUpload: '/upload'.// Upload the image PageCustomService: '/support'.// Customer service message get loginUserResetPassword: '/resetPassword'.// Reset the password post } Copy the code
-
Axios. ts: Axios requests that the file be encapsulated
import request from './axiosConfig'; import { sessions } from '@/utils/utils' interface api { url: stringdata? :anyheader? :any } const httpConfig = (method:string,params? :any) = > { let token = sessions.get(`token`) let data: any = null if(method ! = ='FILE') { // Non-file upload if (method === 'POST' || method === 'PUT') { data = { data: params.data, } } else if (method === 'GET' || method === 'DELETE') { data = { params: params.data, } } return new Promise((resolve, reject) = >{ request(params.url, { method, ... data,headers: { 'Content-Type': 'application/json; charset=UTF-8'.'Authorization': token ? token : 'Basic aHc6aHc='. params.header } }).then((res:any) = > { resolve(res) }).catch((err:any) = >{ console.log(err,'abnormal')})})}else { // File upload return new Promise((resolve, reject) = > { request(params.url, { method: 'post'.data: params.data, requestType: 'form'.headers: { 'Authorization': token ? token : 'Basic aHc6aHc=' } }).then((res:any) = > { resolve(res) }).catch((err:any) = >{ console.log(err,'abnormal')})})}}export default { post: (params: api) = > { return httpConfig('POST', params) }, get: (params:api) = > { return httpConfig('GET', params) }, delete: (params:api) = > { return httpConfig('DELETE', params) }, put: (params:api) = > { return httpConfig('PUT', params) }, file: (params:api) = > { return httpConfig('FILE', params) }, } ` `` Copy the code
-
Axiosconfig. ts: indicates the AXIos configuration file
/** * Axios network request tool */ import axios from 'axios'; import api from './apiNames' import url from './url' // Server status code const codeMessage:any = { 200: 'The server successfully returned the requested data. '.201: 'Creating or modifying data succeeded. '.202: 'A request has been queued in the background (asynchronous task). '.204: 'Deleting data succeeded. '.400: The server did not create or modify data. '.401: 'User has no permissions (wrong token, username, password). '.403: 'The user is authorized, but access is forbidden. '.404: 'The request was made for a nonexistent record, and the server did not act on it. '.406: 'Requested format not available. '.410: 'The requested resource is permanently deleted and will not be retrieved. '.422: 'A validation error occurred while creating an object. '.500: 'Server error, please check server. '.502: 'Gateway error. '.503: 'Service unavailable, server temporarily overloaded or maintained. '.504: 'Gateway timed out. '};The interface returns a status code const apiCode: any = { toast: 101.// Error message, need toast prompt loginFail: 10086.// The login is invalid } let request = axios.create({ baseURL: url, timeout: 2e4.responseType: 'json'});/** * exception handler */ const errorHandler = (response: any) = > { if(response && response? .status) {const errorText = codeMessage[response.status] || response.statusText; const { status, url } = response; if(response? .status ===401) { // The login is invalid setTimeout(() = > { window.sessionStorage.clear(); window.location.href = `The ${window.location.origin}/login`; }, 1e3); } else { console.log('Request error${status}: ${url}.${errorText}`); }}return response; }; // Cancel the request const cancelAxios:any = []; request.interceptors.request.use((config:any) = > { const c = config; c.cancelToken = new axios.CancelToken((cancel:any) = > { cancelAxios.push(cancel); }); return c; }, () = > { // console.log(error); }); // Trigger axios cancel event to mount to window window.$cancelRequest = () = > { cancelAxios.forEach((element:any, index:number) = > { element('cancel'); delete cancelAxios[index]; }); }; // Filter export Excel error message, file stream download interface declaration list const list = [ { url: api.entrustStorageExport, type: 'export'.export: 1},];// Add request interceptor request.interceptors.request.use((config:any) = > { const finds = list.find(item= > config.url.includes(item.url)); const num = config[config.method.toUpperCase() === 'GET' ? 'params' : 'data']? .export ||0; if (finds && num === 1) { / / download config.responseType = 'blob' } return config }, (error: any) = > { console.log(error) }) // Add a response interceptor request.interceptors.response.use(async (response: any) = > {const options = response.config const finds = list.find(item= > options.url.includes(item.url)); const num = options[options.method.toUpperCase() === 'GET' ? 'params' : 'data']? .export ||0; if (finds && num === 1 && response.status === 200) { // To download file streams, the request must contain export: 1, fileName is optional, and the default time is const blob = new Blob([response.data], {type: 'application/vnd.ms-excel'}); let filename = options[options.method.toUpperCase() === 'GET' ? 'params' : 'data']? .fileName ||new Date().Format('YYYY-MM-DD hh:mm:ss'); if(window.navigator.msSaveOrOpenBlob) {/ / build is compatible navigator.msSaveBlob(blob,filename); } else { // Create a hyperlink, assign the file stream to it, and implement the hyperlink's click event const elink = document.createElement('a'); elink.download = filename; elink.style.display = 'none'; elink.href = URL.createObjectURL(blob); document.body.appendChild(elink); elink.click(); URL.revokeObjectURL(elink.href); // Release the URL object document.body.removeChild(elink); } return blob } if (response.status === 200) { // Generic interface request try { if (response.data.code) { if(response.data.code ! = =200) { if (response.data.code === apiCode.loginFail) { // The login is invalid setTimeout(() = > { window.sessionStorage.clear(); window.location.href = `The ${window.location.origin}/login`; }, 1e3); } if (response.data.code === apiCode.toast) { alert(response.data.errorMsg); } else { console.log(response.data.errorMsg); }}}}catch (err) { console.log('Interface request failed'); }}else { errorHandler(response) } return response.data; }); export default request Copy the code
-
Url. ts: indicates the interface path configuration file
let url = ' ' switch (process.env.NODE_ENV) { case 'development': url = "http://127.0.0.1:9999"; break; / / development case 'test': url = "http://127.0.0.1:9999"; break; / / test case 'production': url = "http://10.21.1.104:9999"; break; / / production default: url = "http://127.0.0.1:9999"; break;/ / other } export default url Copy the code
-
Create services.ts in the user folder
import request from '@/axios/axios' import api from '@/axios/apiNames' // Get service information export const getPageCustomService = () = > { let params = { url: api.PageCustomService } return request.get(params) } // Reset the password export const resetPassword = (name:string) = > { let params = { url: api.loginUserResetPassword, data: { name } } return request.post(params) } // Upload the image export const uploadImage = (formData:any) = > { let params = { url: api.commonUpload, data: formData } return request.file(params) } // Export the application list export const exportUrl = () = > { let params = { url: api.entrustStorageExport, data: { id: 4.fileName: 'Application Items.xls'.export: 1,}}return request.get(params) } Copy the code
-
Modify the user. The TSX
import { useState,useEffect } from 'react' import { withRouter, Prompt, useHistory } from 'react-router-dom' import { getPageCustomService, resetPassword, uploadImage, exportUrl } from './services' function User(props: any) { const [leave, setLeave] = useState(true) let history = useHistory() console.log(history) useEffect(() = > { getData() }, []) / / get request const getData = async() = > {let { data, code } = await getPageCustomService() console.log(data,code) } / / post request const postData = async() = > {let { code, data } = await resetPassword("gdtest002") console.log(code,data) } // Image upload const [file, setFile] = useState(null) as any const imageUpload = async() = > {if(! file) {return } if(file? .size >2 * 1024 * 1024) { alert('big') return } let formData = new FormData(); const fileName = props.name || 'file'; formData.append(fileName, file); let {code,data} = await uploadImage(formData) console.log(code,data) } // Export/download the file stream const exportData = async() = > {let data = await exportUrl() console.log(data) } return ( <div className="User"> user <p> <button onClick={()= >{postData()}}> Post request</button> </p> <p> <button onClick={()= >{history.replace('/about')}}> click I go about</button> </p> <p> <button onClick={()= >{exportData()}}> click I download</button> </p> <p> <input type="file" onChange={(e:any)= > {setFile(e.target.files[0])}}/> <button onClick={()= >{imageUpload()}}> click me upload</button> </p> <Prompt message={()= >{ if (! Leave) {return true} const r = confirm(' Leave? ') return r }} when={leave}></Prompt> </div>)}export default withRouter(User) Copy the code
The CSS and LESS
css
Create a new index.css in the About folder:
.App{ color: #ff0000; }.text{ font-size: 30px; }Copy the code
And introduce the about.tsx file:
import './index.css'
function About() {
return (
<div className="App">
<p className="text">about</p>
</div>)}export default About
Copy the code
The effect is as follows:
The problem is that if I have the same class name in other components, the styles will affect each other.
Improvement:
- will
index.css
To:index.module.css
: any in.module.css
A CSS file with a suffix is considered oneCSS modules file. Importing such a file returns a corresponding module object:The official- Use:
less
Install the less
npm install -D less
The official documentation
use
To use the less syntax, change the suffix of the above index.module. CSS to index.module.less
Configure the global less variable
Create a new global.less file in/SRC:
/** less */
@mainColor: #ff0000;
@textColor: #Awesome!;
Copy the code
In vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
const path = require("path");
export default defineConfig({
plugins: [react()],
/ /... Other configuration
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true.additionalData: `@import "${path.resolve(__dirname, 'src/global.less')}"; `}}}})Copy the code
Browser prefix
The installation
npm install autoprefixer postcss -D
use
In vite.config.ts:
export default defineConfig({
/ /... Other configuration
css: {
/ /... Other configuration
postcss: {
plugins: [
require("autoprefixer")]}}})Copy the code
Mobile terminal unit conversion
The plugin used here is postcss-px-to-viewPort
The installation
npm install postcss-px-to-viewport --save-dev
use
In vite.config.ts:
export default defineConfig({
/ /... Other configuration
css: {
/ /... Other configuration
postcss: {
plugins: [
require("autoprefixer"),
require("postcss-px-to-viewport") ({viewportWidth: 750.// The width of the window corresponds to the width of our design, which is usually 750
viewportHeight: 1334.// The window height can be specified according to the width of 750 devices
unitPrecision: 3.// Specify the decimal number to convert 'px' to the window unit value (often not divisible)
viewportUnit: 'vw'.// Specify the window unit to convert to. Vw is recommended
selectorBlackList: ['.ignore'.'.hairlines'].// Specify a class that is not converted to Windows units. It can be customized and added indefinitely. It is recommended to define one or two common class names
minPixelValue: 1.// less than or equal to '1px' does not convert to window units, you can also set to whatever value you want
mediaQuery: false // Allow conversion of 'px' in media queries})]}}})Copy the code
Ant Design Mobile
Website to install
npm install --save antd-mobile@next
use
import { Button,Input } from 'antd-mobile'
function About() {
return (
<div>
<Button color="primary">123</Button>
<Input placeholder='Please enter content'/>
</div>)}export default About
Copy the code
State management HOX
Ant Financial react status manager
- There’s only one API, it’s simple and efficient, and it costs almost nothing to learn
- Use Custom Hooks to define models, embracing React Hooks perfectly
- Perfect TypeScript support
- Support for multiple data sources, with access
The installation
npm install --save hox
use
-
Create a store/store.ts file:
import { useState } from "react"; import { createModel } from "hox"; function useCounter() { const [count, setCount] = useState(0); const decrement = (num? :number) = > setCount(typeofnum ! = ='number' ? count - 1 : count - num); const increment = (num? :number) = > setCount(typeofnum ! = ='number' ? count + 1 : count + num); return { count, decrement, increment }; } export default createModel(useCounter); Copy the code
-
home.tsx
import {useEffect} from 'react' import useCounterModel from '@/store/store' import { withRouter, Link } from 'react-router-dom' import { Button } from 'antd-mobile' function Home(props: any) { const model = useCounterModel() useEffect(() = > { console.log(The value has changed.) }, [model.count]) return ( <div className="Home"> {model.count} <Button color="danger" onClick={()= >{model.increment()}}> Increment</Button> <Button color="danger" onClick={()= >{model.decrement()}}> Minus</Button> <Button color="danger" onClick={()= >{model.increment(20)}}> increment 20</Button> </div>)}export default withRouter(Home) Copy the code
More references
typing.d.ts
Global declaration file
Format:
declare module '* * *'
Copy the code
vite.config.ts
The official documentation
other
- Utils /regexp.ts: Several regular expressions are commonly used;
- Utils /utils. Ts: sessionStorage encapsulation, data * replacement, three digits plus commas
Git address
Gitee.com/mosowe/reac…
npm
npm install -g yo
npm install -g generator-vite-react-app
yo vite-react-app