This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together.

To help you quickly understand Ant Design Pro V5 configuration details for your own projects, is really delicious!

The last article covered the basics of Ant Design Por V5, and the next part of this article will cover the real stuff, from internationalization, useModel, routing (menu), network request, and mock.

I hope this article can help you a little bit. If you have any questions, or say something wrong, please feel free to discuss 👏🏻👏🏻 college

Ant Design Pro V5 has a favorite point under Star support ~

De-internationalization

Internationalization is a very powerful feature of Ant Design Pro, but it is not required for domestic projects. Therefore, when our own projects do not need this feature, we can consider removing it

We only need to execute the command NPM run i18n-remove, but when we delete local, we will still find a large number of errors because only umi useIntl method is used in the code, so now we just need to delete all the code in the file

A small problem

We still run into some minor problems when we remove internationalization

1. The translation function of the browser

This is because lang in the SRC /page/document.ejs file is en

We need to change it to zh-CN

2. Some Ant Design components display English (e.g. date components)

The default locale in config/config.js is’ zh-cn ‘

Loading and handling of data –useModel

In the previous article, this newbie said that V5’s useModel is equivalent to a fool operation, and it has to be said that ants are cows!

First, V5 comes with an initialState, which is officially called:

InitialState replaced the original built-in model in V5. Global,login, and setting were all incorporated into initialState. . We need to delete the SRC/models/global ts, SRC/models/login ts, SRC/models/setting. The ts, and will ask the user information and log in to intercept in SRC/app. The TSX

Understand the official line:

  • First, it is an official model built in and can be used as the outermost model
  • Secondly, we can put the global state in, for individual files to call, such as: user information, permission level, etc. ~

Shall we see how it works first

import { useModel } from 'umi';

export default () => {
  const { initialState, loading, error, refresh, setInitialState } = useModel('@@initialState');
  return <>{initialState}</>
};
Copy the code

It is very convenient to use, first introduce the use of all parameters

InitialState: Returns the global state, which is the return value of getInitialState

SetInitialState: (state:any) => Manually set the value of initialState. After manual setting, loading will be set to false.

Loading: Whether getInitialState is in loading state. Rendering of other parts of the page will be blocked until the initial state is obtained for the first time. Loading can be used to determine whether refresh is in progress.

Error: The error is stored in error when getInitialState throws error in the runtime configuration.

Refresh: () => void re-executes the getInitialState method and retrieves new data.

How do I create a Model alone

Now that we’re done with the official initialState, how do we create our own model

There’s not much to explain here, so let’s go straight to the code and use the simplest timer

First we need to create our own module in SRC/Model

File location SRC/models/test/modelTest ts

import { useState, useCallback } from 'react';

interfaceProps { count? :number
}

const initInfoValue: Props = {
  count: 1,}export default function modelTest() {

  const [init, setInitValue] = useState(initInfoValue);
  const [loading, setLoading] = useState(false);

  const waitTime = (time: number = 2000) = > {
    return new Promise((resolve) = > {
      setTimeout(() = > {
        resolve(true);
      }, time);
    });
  };

  const setInit = useCallback(async(res:any) => {
    setLoading(true)
    await waitTime()
    setLoading(false)
    setInitValue({count: res})
  }, [init])

  const setAdd= useCallback((res:any) = > {
    setInitValue({ count: res +1})
  }, [init])

  return {
    loading,
    init,
    setAdd,
    setInit
  };
}
Copy the code

Then you can get it directly from useModel on the desired page

import React from 'react';
import { useModel } from 'umi';
import { Button } from 'antd';

export const MockModel: React.FC<any> = () = > {
  const { init, setInit, setAdd, loading } = useModel('test.modelTest');

  return <div>
    <div style={{ marginBottom: 14}} >Count = {init.count}</div>
    <Button loading={loading} style={{ marginBottom: 18 }} type='primary' onClick={()= >SetInit (5)} > Set count to 5</Button>
    <br />
    <Button type='primary' onClick={()= >SetAdd (init.count)} > Add 1</Button>
  </div>
}
Copy the code

In general, as long as the corresponding method, value all return, and then in the call on THE OK, is not very simple ~

Performance optimization

As we have more and more data in the model, we need to use the second optional parameter of useModel for performance optimization, consuming only some of the parameters in the model and not others, and returning the final value of useModel

Let’s take the example above

import React from 'react';
import { useModel } from 'umi';
import { Button } from 'antd';

export const MockModelRet: React.FC<any> = () = > {
  const { init, setAdds } = useModel('test.modelTest'.(ret) = > {
    return {
      init: ret.init,
      setAdds: ret.setAdd
    }
  });

  return <div>
    <div style={{ marginBottom: 14}} >Count = {init.count}</div>
    <Button type='primary' onClick={()= >SetAdds (init.count)} > adds 1</Button>
  </div>
}
Copy the code

Route Details (Dynamic Menu)

Route Configuration

First of all, all routes are in config/routes. We configure routes in this file

Let’s start with a level 1 directory

export default[{path: '/test'.name: 'Primary directory'.icon: 'smile'.component: './Welcome'}]Copy the code

Let’s see what happens here

For multi-level directories, you only need to use the routes parameter. Other configurations are the same

We are creating a secondary directory here, we are creating a sub-directory of a secondary directory, we want to jump into the sub-directory from the secondary directory, but it is not shown in the menu, at this time we just need to use hideInMenu to hide in the menu, but we will find that it becomes like this

We found two problems: one is that the menu bar on the left is not highlighted, and the other is that the breadcrumbs are displayed incorrectly. This is because we wrote the path incorrectly. We need to mount this sub-page under the secondary directory to solve the problem perfectly

export default[{path: '/test'.name: 'Primary directory'.icon: 'smile'.routes: [{path: '/test'.redirect: '/test/twotest'}, {path: '/test/twotest'.name: 'Secondary directory'.component: './Welcome'}, {path: '/test/twotest/threetest'.name: 'Sub-pages of the secondary directory'.component: './Welcome'.hideInMenu: true}}]]Copy the code

Effect:

Let’s summarize the parameters of common routes (see the official website for other parameters) :

  • Path: indicates the access path of the address bar

  • Name: name of the

  • Icon: Small front icon

  • Component: Corresponding folder directory

  • Redirect: Indicates the redirected address

  • Authority: indicates the permission. Do not use the dynamic menu for large projects

  • HideInMenu: Whether to hide the menu bar

  • Routes: indicates the corresponding child route

Dynamic menu

In V5, two types are provided, one is permission, and the other is dynamic menu. Dynamic menu is recommended here, so only the usage of dynamic menu is introduced.

In my case, I mock out the data and put it into utils/initData. Okay

If you are interested, you can download it on GitHub. Here are the problems I found in using it:

  1. The address in the dynamic route can only be packaged according to the configured original route. If there is no such problem, (that is, the corresponding name must be written in the original path regardless of whether there is a dynamic route).
  2. The configured Component is useless, and the redirect is useless.
  3. The icon of the dynamic route is not displayed
  4. Dynamic routing Even if the corresponding path is hidden, you can enter an address in the address bar to access the page
  5. The redirect does not work, even if it is set to default to the redirect definition, so clicking on the header redirects to the original page
  6. During login, if the redirection page does not have a redirection address, the page is redirected to /. Because the redirection in the dynamic route does not work, the page is redirected to the original/page

If there is a wrong message please point out ~

Solutions:

For problem 1 and problem 2, we must negotiate with the backend. The route must correspond to the field returned by the interface, and only the corresponding name, icon and path need to be returned

Problem 3, we need to write a separate method to fit it

Use: menuData: formatter(menudata.data)const formatter = (data: any[]) = > {
  data.forEach((item) = > {
    if (item.icon) {
      const { icon } = item;
      const v4IconName = toHump(icon.replace(icon[0], icon[0].toUpperCase()));
      const NewIcon = allIcons[icon] || allIcons[' '.concat(v4IconName, 'Outlined')];

      if (NewIcon) {
        try {
          // eslint-disable-next-line no-param-reassign
          item.icon = React.createElement(NewIcon);
        } catch (error) {
          console.log(error); }}}if (item.routes || item.children) {
      const children = formatter(item.routes || item.children); // Reduce memory usageitem.children = children; }});return data;
};

const toHump = (name: string) = > name.replace(/-(\w)/g.(all: string, letter: any) = > letter.toUpperCase());
Copy the code

For Questions 5 and 6, we give a detailed description:

For example, my original page is configured with page A (the first page), but I do not want to display page A under its permission. When it is not displayed, this problem will occur

The solution

The click-header method and login method (excluding redirection) jump to the first one to get the route, and the redirection of the original route will be cancelled. Set it uniformly on getInitialState, and if the path is/it automatically gets the path of the first parameter

Bridge of Links – Network requests

Network requests are a bridge between the front end and the back end, and we need this bridge to bind the back end together

For a system, the request method and the accepted parameters are unified, so we need to centrally configure our request module, to adapt to their own system.

Set the requested module in V5 in SRC /app.tsx

In V5 we needed to introduce umi, and compared to V4, V5 extended a configuration skipErrorHandler, which skips the default error handling for specific interfaces

import { request } from 'umi';

request('/api/user', {
  params: {
    name: 1,},skipErrorHandler: true});Copy the code

Unified address

In the previous article, the nature of modular packaging is to type different packages through different commands. The requested interface needs to be configured through prefix here

export const request: RequestConfig = {
  prefix: process.env.NODE_ENV === "production" ? host : '/api/'};Copy the code

The interceptor

Each system has a different backend request, so we should do some special processing before and after the request to help us develop quickly, such as: in the request header token

Therefore, V5 provides two approaches, one is a middleware (middlewares) and the other is an interceptor

Both of these methods can elegantly do enhanced processing before and after network requests, but middleware is more complex to use, so I’ll just cover interceptors here

RequestInterceptors -requestInterceptors

First, what does request interception need to be configured for

  • The format and mode of sending network on the backend are different, for example, content-Type is configured
  • In addition, most projects now have a token for judging

Note that tokens usually need to be stored locally because they will be used every time a project is started. Of course, caching should be used as little as possible and data flow should be used as much as possible.

So we need to set up a variable to store the token

In addition, we need to note that there is no token when not logged in, and after logging out, we need to clear the cache

Nonsense a bit much ~ directly look at the code ~

/** Request to intercept */
export const requestInterceptors: any = (url: string, options: RequestInit) = > {
  if (storageSy.token) {
    const token = `Bearer ` + localStorage.getItem(storageSy.token);// The token storedoptions.headers = { ... options.headers,"Authorization": token,
      'Content-Type': 'application/json',}}return { url, options };
}
Copy the code

responseInterceptors

As with request interception, let’s talk about what response interception does first, okay

  • Unified error handling, such as: in the case of bad network, can not request data, then we can give a unified prompt to tell the user
  • If the return value is not 200 (success), it will be set to false, so it is not neededcatchTo capture it.
  • User login time, in a system, we want the user to log in and this is time-sensitive, this is the interface will return a specific status code to tell us that the user information does not match, or the login time is up, and then we need to give a corresponding prompt in the response interception, and clear the cache and exit the system

There’s actually a little bit of a problem with the second one, which is that it doesn’t return anything, but the status code is 200, so how do you determine success on a particular page, where you just have to determine that the type of return is not equal to a Boolean

// Response interception
export const responseInterceptors:any = async (response: Response) => {
  if(! response) { notification.error({description: 'Your network is abnormal and you cannot connect to the server'.message: 'Network exception'});return;
  }
  const data = await response.clone().json();
  if ([10001.10008].includes(data.resultCode)) {
    message.error(data.message);
    localStorage.clear();
    return false;
  }
  if(data.code ! = =200) {
    message.error(data.message);
    return false;
  }
  return data.data;
}
Copy the code

Data emulation – Mock

What is a mock? It is to simulate the data requested by the interface, and can randomly generate test data. When the backend is not good, we can communicate the name of the variable with the backend, and then mock the data to achieve development. When the backend is good, we can directly replace the interface

Please refer to the mock website for documentation

Mock services

Mock is the interface to mock, so mock data is not available after formal packaging. What if mock data is not used during development?

The command NPM run start:no-mock will do

A separate mock service

We know that mock data can sometimes coexist with real Api requests

But mocks are not available after packaging, so can you start a mock service? And then through the nginx proxy to the mock service?

The official solution is umi-serve

Install the yarn add global umi-serve command

For convenience’s sake

We can add “serve” to the script in package.json :”umi-serve”

To start the UMI-serve service next time, enter NPM Run Serve on the console.

GET and POST requests

This part of the content is relatively simple, there is no need to say, directly provide two request methods ok

  'GET /api/form/queryDetail': async (req: Request, res: Response) => {
    const { detail } = req.query;
    if (detail === 'introduce') {
      res.send(
        resData({
          list: introduce,
          anchorList: introduceAnchorList
        }
      ))
    } 
    
    res.send({
      code: 400,
      detail,
      message: 'Please enter parameters'})}'POST /api/domesy/queryDetail': async (req: Request, res: Response) => {
    const { detail } = req.query
    if(detail === 'welcome') {
      res.send(
        resData({
          list: welcome,
          anchorList: welcomeAnchorList
        }
      ))
      return
    }

    res.send({
      code: 400,
      detail,
      message: 'Please enter parameters'})},Copy the code

Here, is the end, I hope this article to you have a little help ~~~