Due to the need of work, we have recently made a series of visualization projects of background data reporting. Antd + TS + Ehcarts + MOBx are used in technology selection, and create-React-APP is used in scaffolding. In the process of integration, we find that some documents about this are not very clear. In the idle down, especially sorted out some key steps, for their own is also a review process, but also hope that this article to some meng forced brothers and sisters as a reference, do not panic, dry over.

Project configuration file package.json

{"name": "my-app", "version": "0.1.0", "private": true, "dependencies": {"@testing-library/jest-dom": "^ 4.2.4," "@ testing - the library/react" : "^ 9.3.2", "@ testing - the library/user - the event" : "^ 7.1.2", "@ types/jest" : "^ 24.0.0 @ types/"," node ":" ^ 12.0.0 ", "@ types/react" : "^ 16.9.0", "@ types/react - dom" : "^ 16.9.0", "antd" : "^ 4.6.4 lodash", ""," ^ 4.17.20 ", "mobx" : "^ 5.15.6", "mobx - react" : "^ 6.3.0", "react" : "^ 16.13.1", "the react - document - the title" : "^ 2.0.3", "the react - dom" : "^ 16.13.1", "the react - the router - dom" : "^ 5.2.0", "the react - scripts" : "rule 3.4.3", "typescript" : "~3.7.2"}, "scripts": {"start": "react-app_env" --REACT_APP_ENV=local", "build:prod": "react-app-rewired build --REACT_APP_ENV=prod", "build:test": "react-app-rewired build --REACT_APP_ENV=test", "build:dev": "react-app-rewired build --REACT_APP_ENV=dev", "test": "react-app-rewired test --env=jsdom" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [" > 0.2% ", "not dead", "not op_mini all"], "development" : [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@types/lodash": "^ 4.14.161", "@ types/react - document - the title" : "^ 2.0.4", "@ types/react - the router - dom" : "^ 5.1.5", "Babel - plugin - import" : "^ 1.13.0", "customize - the cra" : "^ 1.0.0", "less" : "^ 3.12.2", "less - loader" : "^ 7.0.1", "progress - bar - webpack - plugin" : "^2.1.0", "react-app-rewired": "^2.1.6"}Copy the code

I prefer to use YARN, so all the following operations are performed using the YARN command. You can use the NPM command if you want.

Integration steps

I. Stage 1: Complete project initialization and integrate TS

  • Project initialization

Find a directory where you want to place the project and execute the following command:

Yarn create react-app my-app --template typescript yarn create react-app my-app --template typescriptCopy the code

After the above command is executed, the project named my-app is generated. The tsconfig.json configuration file of the TS project is automatically generated in the root directory of the project:As an added bonus, if you get an error when introducing third-party dependencies in a TS project, something like this:This is because there is no type declaration file of TS, so you can go to the official website of the corresponding library to see if it is provided, which is generally the one suggested in the picture. If it is provided on the official website, you can install it. If it is not provided on the official website, then you can get an XXX.d. TS file under the project and declare it. Relevant information can be Google.

  • Add antd
yarn add antd
Copy the code

Finish the above two steps, the first stage is over, so easy, mom no longer need to worry about the bug I wrote…

You can also use antD reference documentation: ant.design/docs/react/…

Phase 2: React-app-rewire configuration file and ANTD integration

If you are doing this integration for the first time, if you look at the final release of the first phase of the document, you will probably find that the first phase is fine, but the second phase is more and more confusing, and the last phase is confusing, and nothing seems to work… That’s how I feel, anyway. However, it can be ignored, according to the following steps to continue to go, to ensure that you can go through.

  • Integrate the React -app-rewired configuration

If you don’t understand why react-app-rewire should be integrated, Google it first.

The integration steps are as follows:

1) In package.json configuration file, the script configuration is replaced as follows:

// Automatically generate the original configuration "scripts": {"start": "react-scripts start", "build": "react-scripts build", "test": "React-scripts test", "eject": "react-scripts eject"} // New configuration "scripts": {"start": "React-app-rewired start --REACT_APP_ENV=local", // "build:prod": "React-app-rewired build --REACT_APP_ENV=prod", "build:test": "React-app-rewired build --REACT_APP_ENV=test", "build:dev": "React-app-rewired build --REACT_APP_ENV=dev", "test": "React-app-rewired test --env=jsdom"} react-app-rewired test --env=jsdom It is set here first, and will be processed and used in config-overrides laterCopy the code

2) Generate the react-app-rewire configuration (which is used to modify the webpack configuration)

Install the react-app-rewired dependency: yarn add react-app-rewired -d

Create a new file under the root directory of my-app: config-overrides. Js, without further discussion, directly release the complete file and detailed configuration content as follows:

const { override, fixBabelImports, addLessLoader, useEslintRc, disableEsLint, addDecoratorsLegacy, overrideDevServer, addWebpackAlias, } = require('customize-cra'); REACT_APP_ENV for (let I = 0; i < process.argv.length; i++) { if (process.argv[i].indexOf('--') === 0) { let item = process.argv[i] .substring('--'.length, process.argv[i].length) .split('='); process.env[item[0]] = item[1]; GENERATE_SOURCEMAP = process.env.react_app_env! == 'local' ? 'false' : 'true'; //3, progress bar const chalk = require('chalk'); const progressBarPlugin = require('progress-bar-webpack-plugin')({ width: 60, format: `${chalk.green('build')} [ ${chalk.cyan(':bar')} ]` + ` ${chalk.cyan(':msg')} ${chalk.red('(:percent)')}`, clear: true, }); // const path = require('path'); const paths = require('react-scripts/config/paths'); const staticFile = 'my-app'; Path.appbuild = path.join(path.dirname(path.appbuild), staticFile); Const devServerConfig = () => (config) => {return {... Config, / / service open gzip compress: true, proxy: {'/' : {target: 'http://192.168.100.118:8808/api', changeOrigin: true, pathRewrite: { '^/': '/', }, }, }, }; }; module.exports = { webpack: FixBabelImports ('import', {libraryName: 'antd', style: AddLessLoader ({//localIdentName: '[name]__[local]--[hash:base64:5]', lessOptions: {javascriptEnabled: true, cssModules: {// Style modular configuration (but this does not seem to be a requirement, as the current scaffolding is supported by default) localIdentName: '[path][name]__[local]--[hash:base64:5]',}, // The following line is special, this is the key to change the theme, here I only changed the primary color, of course can also change other modifyVars: {' @primary_color ': '#317CC8', '@modal-confirm-body-padding': '8px', '@table-padding-horizontal': '10px', '@table-padding-vertical': '8px', '@layout-header-background': '#317CC8', '@layout-body-background': '#fafafa', // '@table-header-color': '#F4F4F4',},},}), disableEsLint(), // Ignore esLint warnings // AddWebpackAlias ({['@']: path.join(__dirname, '/ SRC '), '@util': path.join(__dirname, '/src/utils'), '@static': path.join(__dirname, '/src/static'), '@store': path.join(__dirname, '/src/store'), '@common': Path.join (__dirname, '/ SRC /common'),}), // addDecoratorsLegacy(),// addDecoratorsLegacy(),// addDecoratorsLegacy support // If (process.env.react_app_env!) if (process.env.react_app_env! == 'local') { config.output.path = path.join( path.dirname(config.output.path || '/'), staticFile ); } config.plugins.push(progressBarPlugin); return config; }, // 10, devServer: overrideDevServer(devServerConfig()),};Copy the code

The above configuration file is the complete configuration developed by the project, which can be directly consumed at ease. Read the code in the order of the comments, the key points are explained, it is easy to understand.

Once you’ve reached this stage, you can start the project by executing the following command:

yarn start
Copy the code

However, during the startup process, some NPM packages may be reported as not installed, etc., and then what is missing is installed, until the normal start is good, no further details. (No one can’t install NPM dependency package)

// Dependencies dependencies dependencies dependencies dependencies dependencies dependencies dependencies dependencies dependencies dependenciesCopy the code

The diagram below:Seeing this, I know that the customize-cra dependency is not installed, so I need to execute:

yarn add customize-cra -D
Copy the code

And then start again, and so on, and so on.

< antD > < antD > < antD > < antD > < antD > < antD > < antD > < antD > < antD > < antD >So you can put less:

yarn add less -D
Copy the code

3) Verify whether ANTD integration is successful

After following the above steps, normally there should be no problem starting the project. Run the following command to start the project:

yarn start
Copy the code

Startup success:Open in browser:http://localhost:3000/, you can see the following interface:

To be in this step, then all our project configuration to ok, at this point if the antd whether integration success is not assured, can verify, my authentication method is directly in the APP. The TSX entry files into a antd components (such as: the Button), and see whether can normal display, if you can, is proved that integration is successful, the following figure:The following page is displayed in the browser:Here, verify that the integration of the ANTD framework is OK…

Phase 3: Configure the React -router

For a SPA application of react stack, the React-Router route management scheme is basically used. About the react – the use of the router can probably at the official document: reactrouter.com/web/example… But to be honest, if this is not much, to really integrate in the real project use, or need to combine various web articles to look at the comparison. In particular, versions after React – Router5.0 may be used differently from those before. For details, please refer to the documentation for details.

In short, it is OK to meet the needs of your own project, so I think there is no best way to achieve this. In the real world, the routing scenarios we encounter can be summed up in the following core questions: Which version of the dependency is installed? (version selection problem), how to write in the routing file in the project? (routing configuration problem), what about user rights control? (Authentication). Here’s my micro for this question.

  • Install the react – the router – the dom

React-router-dom and react-router-dom can be seen in this article: juejin.cn/post/684490…

In general, install react-router-dom:

Yarn add react-router-dom yarn add @types/react-router-dom -d // TS Project requiredCopy the code

To verify the installation, start with two pages: login. TSX and home.tsxThe code is as follows:

//Login.tsx import { Button, Checkbox, Form, Input } from 'antd'; import React, { ReactElement } from 'react'; import { RouteComponentProps } from 'react-router-dom'; interface Props extends RouteComponentProps {} const layout = { labelCol: { span: 8 }, wrapperCol: { span: 16 }, }; const tailLayout = { wrapperCol: { offset: 8, span: 16 }, }; Export default function Login({history}: Props): ReactElement {const onFinish = (values: any) => { history.push('/'); }; const onFinishFailed = (errorInfo: any) => { console.log('Failed:', errorInfo); }; return ( <div style={{ width: 300, margin: '100px auto' }}> <Form {... layout} name="basic" initialValues={{ remember: True}} onFinish={onFinish} onFinishFailed={onFinishFailed} > < form. Item label=" username" name="username" rules={[{ required: true, message: 'Please input your username!', }, ]} > <Input /> </ form. Item> < form. Item label=" password" name="password" rules={[{required: true, message: 'Please input your password!', }, ]} > <Input.Password /> </Form.Item> <Form.Item {... TailLayout} name="remember" valuePropName="checked" > <Checkbox> </Button> </ form. Item> </Form> </div>); }Copy the code
//Home.tsx import { Button } from 'antd'; import React, { ReactElement } from 'react'; import { RouteComponentProps } from 'react-router-dom'; Interface extends RouteComponentProps {} Export default function Home({history}: Props): ReactElement { return ( <> <div style={{ textAlign: 'right' }}> <Button onClick={() => { history.push('/login'); </Button> </div> <div style={{textAlign: 'center', paddingTop: 30, fontSize: 24}}> }Copy the code

Modify the app.tsx file:

import React from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';
import './App.css';
import Home from './page/home';
import Login from './page/login';

function App() {
    return (
        <HashRouter>
            <Switch>
                <Route exact={true} path={'/'} component={Home} />
                <Route path={'/login'} component={Login} />
            </Switch>
        </HashRouter>
    );
}

export default App;

Copy the code

When you start the project, you should see the home page:Click the exit button in the upper right corner to exit to the login page:The React route is integrated into a project, but there are other things you need to consider in a real project (e.g., login to the home page, 404 page, maintainability, etc.).

  • Routing configuration during formal development

In the above verification, we found that our page configuration is written in the app.tsx file, and every time we add or subtract pages, we have to modify this file, which is not very easy to maintain. In the formal development, a better way is to extract a configuration file based on the route management, app.tsx file as the route configuration file written after it does not need to change it frequently, here I through the following several micro to achieve it:

1) Create a router folder and create an index.ts file in the folder.

import Home from '.. /page/home'; import Login from '.. /page/login'; export interface IRouterProps { name? : string; // Name (actually useless) path: string; // Path component: any; // The corresponding component needLogin? : boolean; [] = [{name: 'home ', path: '/', Component: Home, needLogin: true}, {name: 'login', path: '/login', Component: login, needLogin: false},]; export default routers;Copy the code

2) Generate the public layout page of the page after entering the system

General system page layout is fixed, like the following:In this case, we can pull out a common layer to do this. We create a create Layout directory under SRC and put a basicLayout.tsx file in it:The code is as follows:

import { Button, Layout, Menu } from 'antd'; import React, { Component } from 'react'; import DocumentTitle from 'react-document-title'; import { RouteComponentProps } from 'react-router-dom'; import styles from './BasicStyle.module.less'; interface IBasicLayoutProps extends RouteComponentProps { title? : string; loading? : boolean; } interface IBasicLayoutState {} const { Header, Content, Footer } = Layout; const { SubMenu } = Menu; / / export default Class BasicLayout extends Component< IBasicLayoutProps, IBasicLayoutState > { constructor(props: IBasicLayoutProps) { super(props); } componentDidMount() {} render() {let layout = (<DocumentTitle title={' system '}> < layout > <Header ClassName ={styles.header}> <div> menu (the actual case may be returned by the back end, </div> <Button onClick={() => {this.props.history.push('/login'); }} > Exit </Button> </Header> <Content className={styles.main} id="mainContainer"> {this.props. Children} </Content> </Layout> </DocumentTitle> ); return layout; }}Copy the code

3) Modify app.tsx configuration file:

import React, { FC } from 'react'; import { HashRouter, Redirect, Route, Switch } from 'react-router-dom'; import './App.less'; Import BasicLayout from './layout/BasicLayout'; import NoMatchPage from './page/404'; import routers, { IRouterProps } from './router'; Const App: FC = () => {// This variable is used to indicate whether a user has logged in. Console. log(' Login ==>' + isLogin); return ( <HashRouter> <Switch> {routers.map((item: IRouterProps, key: number) => { return ( <Route key={key} exact path={item.path} render={(props: any) => { if (! Item. NeedLogin | | isLogin) {if (item. NeedLogin) {/ / need to log in to the page using BasicLayout layout return (< BasicLayout {... props}> <item.component {... props} /> </BasicLayout> ); } else {// This is the layout of the page that does not require login return < item.component{... props} />; } } else { return <Redirect exact to={'/login'} />; }}} / >); })} {/* <Route exact={true} path={'/'} component={Home}/> <Route path={'/login'} component={Login}/>*/} <Route component={NoMatchPage} /> </Switch> </HashRouter>); }; export default App;Copy the code

After the above transformation, the transformation of routing configuration in a practical project can be realized basically. Now launch the project, browser accesshttp://localhost:3000/#/, will automatically jump to the login page, manually change the isLogin variable in app.tsx, is true, in the access, you can jump to the home page. So, as long as we use code control, such as: login successful change isLogin to true, otherwise false, can simply implement whether to log in to control the login route.

In the actual development, if you want to control different users to see different menus according to the role of the login user, then look at the menu area in BasicLayout. TSX. Only the data in this area is generated by rendering the menu data returned from the back end after login. Then the different menu corresponds to a front end page in the router.ts configuration:

import Home from '.. /page/home'; import Login from '.. /page/login'; export interface IRouterProps { name? : string; // Name (actually useless) path: string; // Path component: any; // The corresponding component needLogin? : boolean; [] = [{name: 'home ', path: '/', Component: Home, needLogin: true}, {name: 'login', path: '/login', Component: login, needLogin: False}, // demo add a page whose path corresponds to the menu returned from the back end {name:' new page ',path:'/ XXXX ', Component: XXXX, needLogin: true}]; export default routers;Copy the code

The implementation details of this can be handled flexibly and will not be described here.

Stage 4: Figure out mobx state management

After phase 3, it is possible to develop a real project. This is a non-essential integration, because there are dVA,hooks, page state, etc., to choose from. Because there are some states in the project that need to be managed globally, I have integrated the MOBx solution here, which is briefly explained as follows:

  • Install mobx
yarn add mobx mobx-react
Copy the code
  • Create a Store entry profile

Create a store directory under the root directory of the project and create an index. TSX file in the store as the entry file for the store configuration:The code is as follows:

import { useLocalStore } from 'mobx-react'; import 'mobx-react-lite/batchingForReactDom'; import React, { createContext, FC, useContext } from 'react'; import AppStore from './AppStore'; import UserStore from './UserStore'; Const createStores = () => {return {appStore: new appStore (), userStore: new UserStore(), }; }; // Store const stores = createStores(); Const StoresContext = createContext(stores); const StoresContext = createContext(stores); / / hooks to use notes to see here - > https://github.com/olivewind/blog/issues/1 const useStores = () = > useContext (StoresContext); // Store Type TTodoStore = ReturnType<typeof createStores>; const TodoStoreContext = createContext<TTodoStore | null>(null); // Create a Provider and inject const TodoStoreProvider via React.Context: FC = ({ children }) => { const hookStores = useLocalStore(createStores); / / function components in the use of hooks store return (< TodoStoreContext. The Provider value = {hookStores} > {children} < / TodoStoreContext Provider >); }; export { stores, TodoStoreProvider, useStores };Copy the code

The react function and class components can be configured in the same way as the react function and class components.

  • Modify project index.tsx entry file

Configure store integration:

import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'mobx-react'; import { stores, TodoStoreProvider } from './store'; import './index.css'; import App from './App'; import zhCN from 'antd/es/locale/zh_CN'; Import * as serviceWorker from './serviceWorker'; import { ConfigProvider, message } from 'antd'; import 'moment/locale/zh-cn'; Message. config({duration: 2, maxCount: 1,}); Const appStart = () => {return reactdom.render (<ConfigProvider locale={zhCN}> {/* Integrate store available in function and class components */} <Provider {... stores}> <TodoStoreProvider> <App /> </TodoStoreProvider> </Provider> </ConfigProvider>, document.getElementById('root') ); }; appStart(); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();Copy the code
  • The use of the store

Finally, I will do a wave of use of Store to see whether the integration is successful and how to use it. I will use the function of login and logout to do experiments. Generate userstore.ts in store directory:

import { action, observable } from 'mobx'; export default class UserStore { @observable public loading: boolean = false; @observable public loginSuccess: boolean = false; // log @action public loginAction() {this.loading = true; setTimeout(() => { this.loginSuccess = true; this.loading = false; sessionStorage.setItem('token', 'logingedddd'); }, 1000); } @action public loginOutAction() {this.loginSuccess = false; sessionStorage.setItem('token', ''); }}Copy the code

Transform the Login. TSX:

import { Button, Checkbox, Form, Input } from 'antd'; import { observer } from 'mobx-react'; import React, { ReactElement } from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import { useStores } from '.. /.. /store'; interface Props extends RouteComponentProps {} const layout = { labelCol: { span: 8 }, wrapperCol: { span: 16 }, }; const tailLayout = { wrapperCol: { offset: 8, span: 16 }, }; Function Login({history}: Props): ReactElement {const {userStore} = useStores(); const { loading, loginSuccess } = userStore; const onFinish = (values: any) => { userStore.loginAction(); }; const onFinishFailed = (errorInfo: any) => { console.log('Failed:', errorInfo); }; / / to jump to the home page after login the const token: string | null = sessionStorage. The getItem (" token "); if (loginSuccess || (token && token.length > 0)) { return <Redirect to={'/'} />; } return ( <div style={{ width: 300, margin: '100px auto' }}> <Form {... layout} name="basic" initialValues={{ remember: True}} onFinish={onFinish} onFinishFailed={onFinishFailed} > < form. Item label=" username" name="username" rules={[{ required: true, message: 'Please input your username!', }, ]} > <Input /> </ form. Item> < form. Item label=" password" name="password" rules={[{required: true, message: 'Please input your password!', }, ]} > <Input.Password /> </Form.Item> <Form.Item {... TailLayout} name="remember" valuePropName="checked" > <Checkbox> HtmlType ="submit" loading={loading}> </Button> </ form. Item> </Form> </div>; } export default observer(Login);Copy the code

Transform the App. TSX:

import { observer } from 'mobx-react'; import React, { FC } from 'react'; import { HashRouter, Redirect, Route, Switch } from 'react-router-dom'; import './App.less'; Import BasicLayout from './layout/BasicLayout'; import NoMatchPage from './page/404'; import routers, { IRouterProps } from './router'; import { useStores } from './store'; const App: FC = () => { const { userStore } = useStores(); const { loginSuccess } = userStore; / / if the variable is used to represent the login, in the actual project according to the user login after the cache or token judging whether there is a const isLogin = loginSuccess | | sessionStorage. The getItem (" token "); Console. log(' Login ==>' + isLogin); return ( <HashRouter> <Switch> {routers.map((item: IRouterProps, key: number) => { return ( <Route key={key} exact path={item.path} render={(props: any) => { if (! Item. NeedLogin | | isLogin) {if (item. NeedLogin) {/ / need to log in to the page layout of the return (< BasicLayout {... props}> <item.component {... props} /> </BasicLayout> ); } else {// This is the layout of the page that does not require login return < item.component{... props} />; } } else { return <Redirect exact to={'/login'} />; }}} / >); })} {/* <Route exact={true} path={'/'} component={Home}/> <Route path={'/login'} component={Login}/>*/} <Route component={NoMatchPage} /> </Switch> </HashRouter> ); }; export default observer(App);Copy the code

Transform BasicLayout. TSX:

import { Button, Layout, Menu } from 'antd'; import { inject, observer } from 'mobx-react'; import React, { Component } from 'react'; import DocumentTitle from 'react-document-title'; import { RouteComponentProps } from 'react-router-dom'; import AppStore from '.. /store/AppStore'; import UserStore from '.. /store/UserStore'; import styles from './BasicStyle.module.less'; interface IBasicLayoutProps extends RouteComponentProps { title? : string; loading? : boolean; appStore? : AppStore; userStore? : UserStore; } interface IBasicLayoutState {} const { Header, Content, Footer } = Layout; const { SubMenu } = Menu; /** * base layout component */ @inject('appStore', 'userStore') @observer export default class BasicLayout extends Component< IBasicLayoutProps, IBasicLayoutState > { constructor(props: IBasicLayoutProps) { super(props); } componentDidMount() {} render() { const { userStore } = this.props; Let Layout = (<DocumentTitle title={' system '}> < layout > <Header className={styles. Header}> <div> menu </div> <Button onClick={() => { //this.props.history.push('/login'); userStore? .loginOutAction(); }} > Exit </Button> </Header> <Content className={styles.main} id="mainContainer"> {this.props. Children} </Content> </Layout> </DocumentTitle> ); return layout; }}Copy the code

Ok, after the above changes, restart the project again, you can log in and log out, mobX integration is successful.

The files above are class components and function components. Please read the code for details on how to use MOBx in different components.

Fifth stage: summary

This operation basically covers building a popular react development technology architecture from scratch. It can be used as a reference for beginners to learn or build actual project frameworks. And finally, what if you think I should start at zero if it hurts so much? Is there a framework to take it off and start doing it? That is also some, such as UMI, it is good to pull down and write directly, but sometimes mature solutions are used too much, but some basic things will be lost, from this point of view, sometimes starting from 0 will give us a more emotional understanding, which may also be a kind of growth. Well, finally done, the project source code address: github.com/phonet/antd…

😫😫😫😫 disk 😫😫