background

Background projects generally have a strong page structure or logical consistency, such as pages like search, tables, navigation menus, layout, logical aspects such as data flow, permissions. While webPack-based encapsulation of these features would have required a large amount of up-front work, Umi was based on routing and expanded its functionality to include micro front-end, component packaging, request library, hooks library, data flow, etc. Based on this, the practice of LANDING UMI in the company.

The directory structure

Based on the description of the overall directory structure of THE project based on UMI, I can have a general understanding of the project

├── exercises ├─ exercises ├─ exercises ├─ exercises ├─ exercises ├─ exercises ├─ exercises ├─ exercises ├─ exercises ├── pages ├─ index.less ├─ index.js ├─ services ├─ wrappers ├─global.js └ ─ ─ app. JsCopy the code
  • config.js– Routing configuration, plug-in configuration,webpackconfiguration
  • Layouts – Layouts
  • Locales – internationalization
  • modelsdvaData flow scenarios orplugin-model
  • Wrappers – Configure higher-order component encapsulation of routes, such as routing level permission verification
  • app.js– Runtime configuration. For example, routes need to be dynamically modified to override renderingrenderTo listen for route changes
  • global.js– Global execution entry, for example, can be placedsentry

routing

Routing is the cornerstone of a front-end project. Let’s talk about routing configuration

// config/route.js
export default [{
  path: '/merchant'.name: 'Merchant Management'.routes: [{path: '/merchant/list'.name: 'Merchant List'
      component: './list'
    },
    {
      path: '/merchant/detail'.name: 'Merchant Details'.hideInMenu: true.component: './detail'}}]]Copy the code

In addition to name and path, the Component can also be configured with umi plug-in configuration options, such as pro-Layout hideInMenu to hide the navigation menu items corresponding to the route

Routing component loading on demand can be enabled by configuration in config.js

// config/config.js
export default {
  dynamicImport: {}}Copy the code

Routing also supports hook hook operations, such as login and then access the login page to redirect to the home page

// config/route.js
{
  path: '/login'.wrappers: [
    '@/wrappers/checkLogin',].component: './Login'
}
Copy the code

For some items, the route may be configured by the database. In this case, dynamic routing is required to obtain data from the interface to create the route

// src/app.js
let extraRoutes;

export function patchRoutes({ routes }) {
  merge(routes, extraRoutes);
}

export function render() {
  fetch('/api/routes').then((res) = > { extraRoutes = res.routes })
}
Copy the code

Data flow scheme selection

  1. use@umijs/plugin-dva, in a similar wayredux
// config/config.js
export default {
  dva: {
    immer: true.hmr: false,}}Copy the code
  • The convention is to model the organizationNo manual registration is requiredmodel
  • The file name is the namespace.modelInside if there is no declarationnamespaceWill be used as the file namenamespace
  • Built-in dva – loadingConnect directly,loadingField.
  1. Using the @ umijs/plugin – model

A simple data management solution based on the HOOKS paradigm (which can replace DVA in some scenarios), usually used for globally shared data in midstation projects.

// src/models/useAuthModel.js
import { useState, useCallback } from 'react'

export default function useAuthModel() {
  const [user, setUser] = useState(null)
  const signin = useCallback((account, password) = > {
    // signin implementation
    // setUser(user from signin API)
  }, [])
  const signout = useCallback(() = > {
    // signout implementation
    // setUser(null)
  }, [])
  return {
    user,
    signin,
    signout
  }
}
Copy the code

Using the Model

import { useModel } from 'umi';

export default() = > {const { user, fetchUser } = useModel('user'.model= > ({ user: model.user, fetchUser: model.fetchUser }));
  return <>hello</>
};
Copy the code

From the perspective of experience, the middle platform project is basically forms and tables, and there are not many scenarios for sharing data across pages. Using DVA is a bit heavy, so the second kind of plugin-Model is recommended, which is lightweight

layout

The @umijs/plugin-layout plugin provides a more convenient layout

  • The default is Ant Design’s layout@ant-Design/pro-Layout, which supports all of its configuration items.
  • The sidebar menu data is automatically generated based on the configuration in the route.
  • By default, 403/404 handling of routes and Error Boundary are supported.
  • Use it together with the @umijs/plugin-access plug-in to control routing permissions.
// src/app.js
export const layout = {
  logout: () = > {}, // do something
  rightRender:(initInfo) = > { return 'hahah'; },// return string || ReactNode;
};
Copy the code

permissions

Umi uses @umijs/plugin-access to provide permission Settings

// src/access.js
export default function(initialState) {
  const { permissions } = initialState; // After the getInitialState method is executed

  return {
    canAccessMerchant: true. permissions } }Copy the code
  1. Added permission control on the route page to Route Configurationaccessattribute
// config/route.js
export default [{
  path: '/merchant'.name: 'Merchant Management'.routes: [{path: '/merchant/list'.name: 'Merchant List'
      component: './list'.access: 'canAccessMerchant'}}]]Copy the code
  1. It can also be used within a page or componentuseAccessThe permission information is obtained
import React from 'react'
import { useAccess } from 'umi'

const PageA = props= > {
  const { foo } = props;
  const access = useAccess();

  if (access.canReadFoo) {
    // If Foo can be read...
  }

  return <>TODO</>
}

export default PageA
Copy the code
  1. In actual business development, permissions need to be obtained dynamically from the interface, so @umijs/plugin-initial-state and @umijs/plugin-model need to be used
// src/app.js
/** getInitialState will be executed at the beginning of the entire application, and the returned value will be shared globally. The Layout plugin, the Access plugin, and the user can use the useModel('@@initialState') to get the data directly */
export async function getInitialState() {
  const permissions = await fetchUserPermissions()
  return { permissions }
}
Copy the code

internationalization

@umijs/plugin-locale internationalization plug-in, used to solve i18N problems

Use ANTD development, the default is English, display Chinese need to enable internationalization configuration

// config/config.js
export default {
  locale: {
    default: 'zh-CN'.antd: true.baseNavigator: true,}}Copy the code

The title or name in the route can be directly used by the internationalization key and automatically converted to the copy of the corresponding language

// src/locales/zh-CN.js
export default {
  'about.title': 'About - Title',}// src/locales/en-US.js
export default {
  'about.title': 'About - Title',}Copy the code

The project configuration is as follows

export default {
  routes: [{path: '/about'.component: 'About'.title: 'about.title',}}]Copy the code

Integrate the Redux plug-in

If DVA is enabled and redux is used to manage the data flow in a central way, then redux data can be persisted to localStorage using the Redux-Persist plug-in, as follows

// src/app.js
import { getDvaApp } from 'umi'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'
import createFilter from 'redux-persist-transform-filter'

export const dva = {
  config: {
    onError(e) {
      e.preventDefault()
    },
    onReducer(reducer) {
      const globalCollapsedFilter = createFilter('global'['collapsed'])
      const persistConfig = {
        key: 'root',
        storage,
        whitelist: ['global'].transforms: [globalCollapsedFilter],
        stateReconciler: autoMergeLevel2
      }
      return persistReducer(persistConfig, reducer)
    }
  }
}

window.addEventListener('DOMContentLoaded'.() = > {
  const app = getDvaApp()
  persistStore(app._store)
})
Copy the code

Plug-in development

Umi implemented the full life cycle and made it plug-in, thus providing users with access to extension. For example, setting the default configuration plug-in

export default api => {
  api.modifyDefaultConfig(config= > {
    return Object.assign({}, config, {
      title: false.history: {
        type: 'hash'
      },
      hash: true.antd: {},
      dva: {
        hmr: true
      },
      dynamicImport: {
        loading: '@/components/PageLoading'
      },
      targets: {
        ie: 10
      },
      runtimePublicPath: true.terserOptions: {
        compress: {
          drop_console: true}}}); }); }Copy the code

Advantages of upgrading Umi2 to Umi3

Before the upgrade, the e-commerce project in the group used ANTD-Design-Pro4 embedded with UMi2. Although it could meet the requirements of business development, there were still many parts of the template that did not meet the requirements of business, such as permission verification.

The release of Umi3 also leads to a better architecture and development experience

  • The configuration layer has been heavily streamlined
  • The latest Umi3 plug-in provides Layout, data flow, permissions and other new solutions
  • Finally, the permissions related code in the template is built in

Build scaffolding templates based on Umi

The following figure shows the scaffolding template for the internal middle platform based on Umi

Based on Umi this scaffolding template extends the following capabilities

  • Compile and package the dist directory that meets the company beetle (internal CI/CD platform) deployment specification
  • Customize the default configuration plug-in to reduce configuration items
  • eslintcheck
  • prettierFormatting code
  • Git Commit Specification
  • In combination withpro-layoutAchieve a more convenient layout
  • Take advantage of the runtime configurationapp.jsDynamically generate local and remote configuration navigation menus
  • In combination withplugin-accessPlug-ins and internal permission systems implement page – or button-level permission control

New project According to the scaffolding tools in the company to select the platform template can quickly create an initial project with permissions, layout, code specifications, common pages and other functions, can greatly avoid duplication of work.

conclusion

Umi offers out of the box capabilities. You don’t need to configure Webpack, Babel, etc. Best practice configurations are built in. Of course, you can also develop custom plug-in extensions. Umi has put a lot of effort into performance, which is invisible to developers.

However, Umi is less open to webpack-dev-server configuration. If there is a large configuration requirement for webpack-dev-server, you need to consider ~~

The welfare of

Next, we will continue to release around the micro front, iOS offline package and other infrastructure and mid-platform technology related practices and thinking, welcome to pay attention to, looking forward to communicate with you more. Article in the “big turn FE” public number will be sent, and the public number has a raffle, this article prize is around a commemorative T-shirt, welcome to pay attention to the ✿✿ angry (°▽°) Blue