Based on mobile configuration.

Vite website

Advantages:

  1. The speed of light to start
  2. Hot Module Replacement
  3. According to the need to compile

Scaffolding function

  1. Antd-mobile Mobile component
  2. Axios network data interaction
  3. Hox status management
  4. React -router-dom Route management
  5. Postcss-px-to-viewport Move px to VW /vh
  6. Less precompiled
  7. Autoprefixer Automatic completion
  8. Typescript grammar
  9. 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:

  1. Project name: indicates the Project name, which is also the folder name
  2. Select a framework. Select vue and React
  3. 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:

  1. Add a new.env.test file with the same level as/SRC and add the following

    NODE_ENV=test
    Copy the code
  2. 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
  3. 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:

  1. 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.

  2. 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.tsandrouterConfig.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
  1. Advantages of query parameter transmission: Parameters can be transmitted to objects. Disadvantages: Refresh the address bar, parameter loss
  2. Advantages of state parameter transmission: Parameters can be transmitted objects; Disadvantages: Refresh the address bar, parameter loss
  3. 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)
  4. 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
  1. Message: string/function, function returns false by default to continue on the current page.
  2. When: Boolean, true to prompt, false not to prompt

Something like this:

Axios communication

The installation

npm install axios

use

New file:

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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:

  1. willindex.cssTo:index.module.css: any in.module.cssA CSS file with a suffix is considered oneCSS modules file. Importing such a file returns a corresponding module object:The official
  2. 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

  1. 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
  2. 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

  1. Utils /regexp.ts: Several regular expressions are commonly used;
  2. 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