umi project

The project address

File directory structure

├─ ├─ SRC ├─@types Typescript ├─ Components ├─ Core Tools ├─ Public Method ├─ Global Layout file ├─ Models DVA ├─ Exercises, Effects ├─ Pages │ ├─ Exercises │ ├─ Services API │ ├─staticStatic file ├ ─ CSSCopy the code

Project Preparation

Mobile adaptation.umirc.ts

extraPostCSSPlugins: [
  require('postcss-px-to-viewport') ({unitToConvert: 'px'.// The unit to convert, default is "px"
    viewportWidth: 750.// The width of the window corresponds to the width of our design
    viewportHeight: 1334.// The window height can be specified according to the width of the 375 device
    unitPrecision: 3.// Specify the decimal number to convert 'px' to the window unit value (often not divisible)
    propList: [The '*'].// List of attributes that can be converted to vw
    viewportUnit: 'vw'.// Specify the window unit to convert to. Vw is recommended
    fontViewportUnit: 'vw'.// The viewport unit used by the font
    selectorBlackList: ['.ignore-'.'.hairlines'.'am-'.'px-'].// 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
    replace: true.// Whether to replace the attribute value directly without adding the standby attribute
    exclude: [/\/Stores\/.*.less/./global.css/./node_modules/].// Ignore files under certain folders or specific files, such as files under 'node_modules'
    landscape: false.// Whether to add media query criteria based on landscapeWidth @media (Orientation: landscape)
    landscapeUnit: 'vw'.// The unit used in landscape
    landscapeWidth: 1134.// Width of viewport for landscape})]Copy the code

mock api

Define information about the API, with ‘GET/API /getUserInfo’ as a GET request and the interface address as/API /getUserInfo. The returned data is returned by the current interface to the client

import { Request, Response } from 'express';

export default {
  'GET /api/getUserInfo': {
    status: 1.name: 'Caramelized melon seeds'.icon: 'https://tva1.sinaimg.cn/large/00831rSTly1gdm7eok2oij301s01sgli.jpg'.userId: '001',},'POST /api/login': (req: Request, res: Response) = > {
    const { password, name } = req.body;
    if (password === '123456' && name === 'admin') {
      res.send({
        status: 200.name: 'Caramelized melon seeds'.icon: 'https://tva1.sinaimg.cn/large/00831rSTly1gdm7eok2oij301s01sgli.jpg'.userId: '001'}); }else {
      res.send({
        status: 0.msg: 'Wrong account or password! '}); }}};Copy the code

The request to encapsulate

Request Indicates whether the server fails according to the state returned by the backend. If you are involved in a project where the front and back ends are separated, follow the conventions of the front and back ends. For example, if the back end returns show: true,type: ‘error’, all interfaces must display error messages of the back end. Tokens must be added to interfaces on the front-end. Then all of them can be encapsulated for unified processing.

/ * * * * more detailed request network request tool API documentation: https://github.com/umijs/umi-request * /
import { extend } from 'umi-request';

const codeMessage = {
  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. '};/** * exception handler */
const errorHandler = (error: { response: Response }): Response= > {
  const { response } = error;
  if (response && response.status) {
    const errorText = codeMessage[response.status] || response.statusText;
    const { status, url } = response;

    console.error({
      message: 'Request error${status}: ${url}`.description: errorText,
    });
  } else if(! response) {console.error({
      description: 'Your network is abnormal and you cannot connect to the server'.message: 'Network exception'}); }return response;
};

/** * Configures the default parameter */ for the request
const request = extend({
  errorHandler, // Default error handling
  credentials: 'include'.// Whether cookies are included in the request by default
});
Copy the code

services

Request the interface and return the return information of the interface

import request from '@/utils/request';

// Get the current user
export async function getUserInfo() :Promise<any> {
  return request('/api/getUserInfo');
}
Copy the code

@types defines the type

// user.ts
import { Effect, Reducer } from 'umi';

export interface  UserModelType {
  namespace: String.state: {
		userInfo: Object
	},
	effects: {
		getUserInfo: Effect
	},
	reducers: {
		saveUser: Reducer
	}
}

Copy the code

models

Each model module will be merged into CONNECT, and multiple Models, like multiple reducers, perform combineReducer operations and pass mapStateToProps to connect as parameters. For example, if there are user.ts and vaccine. Ts in models, the state objects in these two models are used as parameters of mapStateToProps, corresponding to the namespace of their model respectively. Use: connect(({user, vaccine}) => ({user, vaccine}))(WrappedComponent)

// user.ts
import { UserModelType } from '@types/user';
import { getUserInfo } from '@/services/user';

const userModel: UserModelType = {
  namespace: 'user'.state: {
		userInfo: {}},effects: {*getUserInfo(action, { call, put }) {
			const userInfo = yield call(getUserInfo);
			yield put({
				type: 'saveUser'.payload: { userInfo }
			})
		}
	},
	reducers: {
		saveUser(state, action) {
			return{... state, ... action.payload }; }}};export default userModel;
Copy the code

Get routing table – custom navTitle

Extract routes as a public file and match the current match route according to pathName. Get a custom route. The route information is displayed according to the title parameter in route. If some components need to customize navBar, add hiddenNav

// util.js
export const getRouteByPath = (
  routes: RoutesState[],
  pathname: string | undefined,
): RoutesState | void= > {
  let resultRoute;
  if(! routes || routes.length ===0) return;
  for (let route of routes) {
    // Terminates if the match is currently hit
    if (route.path === pathname) {
      resultRoute = route;
      break;
    }

    // Deep recursionresultRoute = route.routes && getRouteByPath(route? .routes, pathname); }return resultRoute;
};

// BasicLayout/index.tsx
constpathname = location? .pathname;const route = getRouteByPath(routes, pathname);
return (
  <div className="container">{! route? .hiddenNav && (<NavBar
        mode="light"
        leftContent={<Icon type="left" />} onLeftClick={handleLeftClick} > {route? .title}</NavBar>
    )}
    <section>{children}</section>
    <footer>{pathname ! == '/login' &&<BottomNav pathname={pathname} />}
    </footer>
  </div>
);

Copy the code

Key benefits

CSS card

The effect of drop-shadow and radial gradient of filter will be better than that of pseudo-class +box-shadow. The shadow of the former can also appear at two arcs, while the effect of the latter can be achieved only by using other means

div {
    margin: 20px 30px;
    border-radius: 10px;
    filter: drop-shadow(0 3px 3px #cfcfcf);
    background-image: 
      radial-gradient(circle at calc(100% + 5px) 90px, transparent 0, transparent 20px.#fff 21px),
      radial-gradient(circle at -5px 90px, transparent 0, transparent 20px.#fff 21px);
    background-repeat: no-repeat;
    background-size: 50% 100%;
    background-position: right bottom, left top;
}
Copy the code

Record on pit

Sass global style

Error: Use the appropriate loader to chuli current file when using sass as precompiled CSS and setting global. SCSS global style to define uniform color specification

Solution: Install the corresponding loader, NPM install sass-loader node-sass, NPM install @umijs/ plugin-sass-s, and configure it in. Umirc. ts. If plugin-sass is not installed, Invalid config key: sass is displayed. Finally, run NPM audit fix

export default defineConfig({
  ...
  sass: {} // Add the current option
});
Copy the code

CSS alias paths introduce tread pits

Global. Global introduced iconfont in SCSS. CSS, but in the other file to introduce global. The SCSS error, when they can’t find it. @ static/CSS/iconfont/iconfont CSS.

Solution: Add ~ before alias path, this means the current path is alias path, when webpack to parse will actively find the alias path for concatenation

/* Error import mode */
@import '@/static/css/iconfont/iconfont.css';
@import '@/global.scss';

/* The correct way to import */
@import '~@/static/css/iconfont/iconfont.css';
@import '~@/global.scss';
Copy the code

The project uses colorful iconfont

  • Ali iconfont, select Symbol to generate a link
  • in.umirc.tsSet properties inheadScripts
  headScripts: [{ src: 'https://at.alicdn.com/t/font_2928601_j8grnplg68j.js'}].Copy the code
  • Create a global SvgIcon component that displays color IconFont
// Core code
<svg className="icon iconfont" style={{ width: `${size}px`.height: `${size}px` }} aria-hidden="true">
  <use xlinkHref={` #icon-The ${iconName} `} ></use>
</svg>
Copy the code