Don’t use scaffolding structures, the React project: https://github.com/zhuyuanmin/react-0-1-build. Readers can build step by step according to the submitted branch order, all libraries are using the latest version, let us grow in the pit! The Typescript-React branch contains the full version of typescript.

I. Project launch

  1. Understand the background of requirements

  2. Understand the business process

Ii. Initialization of project construction

This example uses the scaffolding create-react-app to initialize the project. This scaffold has advantages and disadvantages, the project directory structure is simple, do not need to care too much about the troublesome configuration of Webpack; The downside is that scaffolding can be a bit bulky, taking about 4 minutes to build. You can choose, or you can build your own project.

  1. Set up taobao mirror warehouse

    $ yarn config set registry registry.npm.taobao.org/ -g

    $yarn config set sass_binary_site cdn.npm.taobao.org/dist/node-s… -g

  2. Project directory init

    $ create-react-app qpj-web-pc –typescript

    $ tree -I “node_modules”

    . |-- README.md |-- package.json |-- public | |-- favicon.ico | |-- index.html | |-- logo192.png | |-- logo512.png | |--  manifest.json | `-- robots.txt |-- src | |-- App.css | |-- App.test.tsx | |-- App.tsx | |-- index.css | |-- index.tsx |  |-- logo.svg | |-- react-app-env.d.ts | |-- reportWebVitals.ts | `-- setupTests.ts `-- tsconfig.jsonCopy the code
  3. Yarn build try

    $ yarn build & tree -I “node_modules”

    . | - README. | - build/md # transformation point (due to ` Jenkins ` pack build script may have written dead ` dist ` package name) | -- package. Json | - public | | -- the favicon. Ico | |-- index.html | |-- logo192.png | |-- logo512.png | |-- manifest.json | `-- robots.txt |-- src | |-- App.css | |-- App.test.tsx | |-- App.tsx | |-- index.css | |-- index.tsx | |-- logo.svg | |-- react-app-env.d.ts | |-- reportWebVitals.ts | `-- setupTests.ts `-- tsconfig.jsonCopy the code
  4. Connect to a Git remote repository

    $git remote add origin zhu%40wetax.com:[email protected]/front/qpj-w…

  5. Add the gitignore

    $ echo -e ” yarn.lock \n package-lock.json \n /dist \n .idea” >> .gitignore

  6. Add ESLint code and submit comments for validation

    $ yarn add husky lint-staged @commitlint/cli @commitlint/config-conventional -D

    $ npx husky install

    $ npx husky add .husky/pre-commit “npx lint-staged”

    $ npx husky add .husky/prepare-commit-msg “npx commitlint -e”

    • Create new commitlint.config.js in the project root directory

      // commitlint.config.js
      module.exports = {
          extends: ['@commitlint/config-conventional'].rules: {
              'type-enum': [
                  2.'always'['feat'.'fix'.'docs'.'style'.'refactor'.'test'.'chore'.'revert']],'subject-full-stop': [0.'never'].'subject-case': [0.'never',}}Copy the code
    • Search ESLint vscode extension and installation, project root directory. The new eslintrc. Js, content may refer to article configuration: zhuanlan.zhihu.com/p/84329603 the fifth

    • Commit Message format description

      <type>: <subject>

      • The type values are enumerated as follows:

        • Feat: Add a new feature
        • Fix: fix the bug
        • Docs: Only modified documents
        • Style: Just changed Spaces, formatting indentation, all good, etc., without changing the code logic
        • Refactor: Code refactored without adding new features or fixing bugs
        • Perf: Add code for performance testing
        • Test: Adds test cases
        • Chore: Change the build process, or add dependency libraries, tools, etc
        • Revert: Current COMMIT Used to undo a previous COMMIT
      • Subject is a short description of the commit purpose, no more than 50 characters long, and without a period (.).

    • Package. json adds the following configuration:

      {... ."lint-staged": {
              "src/**/*.{jsx,txs,ts,js,json,css,md}": [
                  "eslint --quiet"]}}Copy the code
    • NPX esLint [filePath] –fix NPX eslint [filePath] –fix NPX eslint [filePath] –fix

Iii. Project Configuration I (Function Configuration)

  1. Install the project’s common dependency libraries

    $YARN add ANTD axios dayjs QS -s # UI library

    $yarn add react-router-dom redux redux redux-logger redux-thunk-s # Route and state management

  2. Webpack configuration extensions are necessary

    • Root Directory Creationconfig-overrides.jsFor detailed use, please visit:React config-overrides file configuration
    • The installation
      • $ yarn add react-app-rewired customize-cra -D

    • Modify thepackage.jsonIn the startup
      // package.json
      "scripts": {
          "start": "react-app-rewired start"."build": "react-app-rewired build",}Copy the code
    • use
      // config-overrides.js
      const {
          override, / / the main function
          fixBabelImports, // The configuration is loaded on demand
          addWebpackExternals, // Do not configure packaging processing
          addWebpackAlias, // Configure the alias
          addLessLoader // lessLoader configuration, you can change the theme color, etc
      } = require('customize-cra')
      module.exports = override(/ *... * /.config= > config)
      Copy the code
  3. The configuration is loaded on demand

    // config-overrides.js.module.exports = override(
        fixBabelImports('import', {
            libraryName: 'antd'.libraryDirectory: 'es'./ / the library catalog
            style: true.// Automatically package related styles}),)Copy the code
  4. Change the theme color

    // config-overrides.js.module.exports = override(
        addLessLoader({
            lessOptions: {
                javascriptEnabled: true.modifyVars: {
                    '@primary-color': '#1890ff',},}}),)Copy the code
  5. Alias configuration (typescript projects have pits here)

    // config-overrides.js
    const path = require('path')...module.exports = override(
        addWebpackAlias({
            The '@': path.resolve(__dirname, 'src'),}),)Copy the code
  6. Remove comments, multi-process packaging compression

    // config-overrides.js
    const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
    const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')...module.exports = override(/ *... * /.config= > {
        config.plugins = [...config.plugins, {
            new UglifyJsPlugin({
                uglifyOptions: {
                    warnings: false.compress: {
                        drop_debugger: true.drop_console: true,}}}),new HardSourceWebpackPlugin()
        }]
        return config
    })
    Copy the code
  7. Solve the two holes

    • Change the name of the packaged folderdist
      // Change the package path as well as output
      const paths = require('react-scripts/config/paths')
      paths.appBuild = path.join(path.dirname(paths.appBuild), 'dist')
      
      module.exports = override(/ *... * /.config= > {
          config.output.path = path.resolve(__dirname, 'dist')
      
          return config
      })
      Copy the code
    • To solvetypescriptThe alias configuration
      • For relevant information, please visittsconfig.jsonTo add a configuration
        {..."extends": "./paths.json"
        }
        Copy the code
      • The new filepaths.json
        {
            "compilerOptions": {
                "baseUrl": "src"."paths": {
                    "@ / *": ["*"]}}}Copy the code
  8. Configure the decorator writing method

    {
        "compilerOptions": {
            "experimentalDecorators": true. }}Copy the code
  9. Configuring the development agent

    • insrcNew directorysetupProxy.js
      // src/setupProxy.js
      const proxy = require('http-proxy-middleware').createProxyMiddleware
      
      module.exports = function(app) {
          // App is an instance of Express where Mock data can be written
          app.use(
              proxy('/api',
              {
                  "target": "https://qpj-test.fapiaoer.cn"."changeOrigin": true."secure": false.// "pathRewrite": {
                  // "^/api": ""
                  // }}}))Copy the code
  10. Add polyfill and ANTD component internationalization processing

    // src/index.tsx
    import React from 'react'
    import ReactDOM from 'react-dom'
    / / into the store
    import { Provider } from 'react-redux'
    import store from '@/store/store'
    
    import { ConfigProvider, Empty } from 'antd'
    import App from './App'
    import zhCN from 'antd/es/locale/zh_CN'
    import 'moment/locale/zh-cn'
    
    // polyfill
    import 'core-js/stable'
    import 'regenerator-runtime/runtime'
    
    ReactDOM.render(
        <Provider store={store}>
            <ConfigProvider locale={zhCN} renderEmpty={Empty}>
            <App />
            </ConfigProvider>
        </Provider>.document.getElementById('root'))Copy the code
  11. CSS Modules

    The create – react – app bring support to XXX. The module. (c | le | sa) ss stylesheet file, use typescript on projects should pay attention to:

    const styles = require('./index.module.less')
    
    retrun (
        <div className={` ${styles.container} `} >
            <Table
                columns={columns}
                className={` ${styles['border-setting` ']}}dataSource={props.store.check.items}
                rowKey={record= > record.id}
                pagination={false}
            />
            <div className="type-check-box"></div>
        </div>
    )
    Copy the code
    // index.module.less
    .container {
        padding: 24px;
        background-color: #fff;
        height: 100%;
        overflow: auto;
        .border-setting {
            tr {
                td:nth-child(3) {
                    border-left: 1px solid #F0F0F0;
                    border-right: 1px solid #F0F0F0; }}td {
                text-align: left ! important; }} :global { // Descendant elements can write 'className' without using 'styles['type-check-box']'
            .type-check-box {
                .ant-checkbox-wrapper + .ant-checkbox-wrapper{
                    margin-left: 0; }}}}Copy the code
  12. React JSX {r-if, r-for, r-model, r-show}

    • Install dependencies

      $ yarn add babel-react-rif babel-react-rfor babel-react-rmodel babel-react-rshow -D

    • configuration.babelrc
      // .babelrc{... ."plugins": [
              "babel-react-rif"."babel-react-rfor"."babel-react-rmodel"."babel-react-rshow"]},Copy the code
    • Example:
      • r-if
        <div>
          <h1 r-if={height < 170} >good</h1>
          <h1 r-else-if={height > 180}>best</h1>
          <h1 r-else>other</h1>
        </div>
        Copy the code
      • r-for
        {/* eslint-disable-next-line no-undef */}
        <div r-for={(item, index) in [1.2.3.4} key={index}> contents {item +The '-' + index}
        </div>
        Copy the code
      • r-model
        <input onChange={this.callback} type="text" r-model={inputVale} />
        Copy the code
      • r-show
        <div r-show={true}> contents </div> # Note: This is`r-if`The effect does not render nodesCopy the code

Iv. Project Configuration II (Optimized Configuration)

  1. Implement lazy loading of componentsreact-loadable
    import Loadable from 'react-loadable'
    
    const Loading = (props: any) = > {
        if (props.error) {
            console.error(props.error)
            return <div>Error! <Button type="link" onClick={props.retry}>Retry</Button></div>
        } else if (props.timedOut) {
            return <div>Timeout! <Button onClick={props.retry}>Retry</Button></div>
        } else if (props.pastDelay) {
            return <div>Loading...</div>
        } else {
            return null}}const loadable = (path: any) = > {
        return Loadable({
            loader: () = > import(`@/pages${path}`),
            loading: Loading,
            delay: 200.timeout: 10000})},const Home = loadable('/homePage/Home')
    
    Copy the code
  2. To deal withaxiosIntercept the response
    const service = axios.create({
        baseURL: '/'.timeout: 15000,
    })
    
    service.interceptors.request.use(function (config) {
        return config
    })
    
    service.interceptors.response.use(function (config) {
        return config
    })
    Copy the code
  3. To deal withReact routerThe nested configuration of

    We know that React does not support routing tables like Vue Router. In React, everything is a component, and routing is also a component. When routing is needed, you need to add routing components temporarily.

    // router/router.config.ts
    const routes = [
        {
            path: '/home'.component: loadable('components/Index'),
            exact: true}, {path: '/new'.component: loadable('components/New'),
            redirect: '/new/list'.// exact: true,
            routes: [{path: '/new/list'.component: loadable('components/NewList'),
                    exact: true}, {path: '/new/content'.component: loadable('components/NewContent'),
                    exact: true,},],},]export default routes
    Copy the code
    // router/router.ts
    import React from 'react'
    import { Switch, BrowserRouter as Router, Route } from 'react-router-dom'
    import routes from './index'
    
    function mapRoutes(routes: any[], store: object) :any {
        return routes.map((item: any, index: number) = > {
            return (
                <Route exact={item.exact || false} path={item.path} key={index} render={props= >{ const NewComp = item.component Object.assign(props, { redirect: item.redirect || null, permission: item.permission || [], ... store }) if (item.routes) { return<NewComp {. props} >{ mapRoutes(item.routes, store) }</NewComp>
                    } else {
                        return <NewComp {. props} / >
                    }
                }} />)})}const Routes = (props: any) = > {
        return (
            <Router>
                <Switch>
                    { mapRoutes(routes, props.store) }
                    <Route component={()= > (<div>404 Page not Found!</div>)} / ></Switch>
            </Router>)}export default Routes
    Copy the code

    The child routing bearer page needs to add the following code:

    import { Redirect, Route, Switch } from 'react-router-dom'
    
    <Switch>
        {props.children}
        <Route component={() = > (<div>404 Page not Found!</div>)} />
        {props.redirect && <Redirect to={props.redirect} />}
    </Switch>
    Copy the code
  4. To deal withReact store
    // store/store.ts
    import { createStore, applyMiddleware } from 'redux'
    import thunk from 'redux-thunk'
    import logger from 'redux-logger'
    import reducers from './reducer'
    
    const store = process.env.NODE_ENV === 'development'
        ? createStore(reducers, applyMiddleware(thunk, logger))
        : createStore(reducers, applyMiddleware(thunk))
    
    export default store
    Copy the code

    For ease of use and avoiding the need for connect for each component, global injection of redux Store is implemented, but performance is compromised if the project is large.

    // App.tsx
    import { dispatchActions } from '@/store/reducer'
    
    export default connect((state: any) = > ({ store: state }), dispatchActions)(App)
    Copy the code

Five, the summary

The project setup is complete, and all that remains is the business code. Believe that you can get the following harvest: (1) the project construction in the macro has a great ability to improve; (2) Clear understanding of the overall function of the project; (3) troubleshooting problems do not panic; (4) Packaging capacity has been strengthened; ⑤ The business functions are clear.

Six, digression

Create a react project based on create-react-app. Create a react project based on create-react-app. Create a react project based on create-react app. Link: github.com/zhuyuanmin/…