React + Redux + React-router-dom + ANTD-mobile

—- Body boundary —–

The installation

npx create-react-app react-juejin
cd react-juejin
yarn add antd-mobile rc-form react-loadable react-redux
yarn add -D @babel/plugin-proposal-decorators @rematch/core babel-plugin-import customize-cra less less-loader react-app-rewired
Copy the code

The basic structure

Directory splitting logic

In development, it may be common to encounter two pages or areas that present the same document structure and style, but with different data sources and interactions. How do you maximize functionality reuse in this case? — Separate the presentation section and the data and interaction sections

  • Assets: pictures/third-party style files, etc
  • components: Places public display components. Solely responsible for the presentation of data from Props
    • Component: We can place style scripts associated with component views in each Componet folder with the entry file index.js
  • Containers: container components that are responsible for data acquisition, business-related interactions, etc.
  • Layouts: Often reusable layouts in front pages, such as navigation, with fixed bottom menus. We can handle layout by writing higher-order components one at a time
  • Routes: Related to the route configuration
  • store: Global status management
    • Models: Define the state management for each module, including State, Reducers, and Effects
  • Utils: utility class
  • Services: Place apis related to data interaction

Rewrite the WebPack configuration

This time, we used react-app-rewired. The specific usage can be found on the official website. Below is a config-overrides

const {
  override,
  fixBabelImports,
  addWebpackAlias,
  addLessLoader,
  addDecoratorsLegacy
} = require('customize-cra')
const path = require('path')
const theme = require('./package.json').theme
module.exports = {
  webpack: override(
    addWebpackAlias({
      '@components': path.resolve(__dirname, 'src/components'),
      '@assets': path.resolve(__dirname, 'src/assets'),
      '@layouts': path.resolve(__dirname, 'src/layouts'),
      '@utils': path.resolve(__dirname, 'src/utils'),
      '@store': path.resolve(__dirname, 'src/store'),
      '@containers': path.resolve(__dirname, 'src/containers'),
      '@services': path.resolve(__dirname, 'src/services')
    }),
    fixBabelImports('import', {
      libraryName: 'antd-mobile'.libraryDirectory: 'lib'.style: true.legacy: true
    }),
    addLessLoader({
      javascriptEnabled: true.modifyVars: theme
    }),
    addDecoratorsLegacy({
      legacy: true}}))Copy the code

We can customize the theme color of our project in package.json and reconfigure the project launch command

  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
Copy the code

The routing configuration

We use Loadable to load routes dynamically

const Home = Loadable({
  loader: (a)= > import('@containers/Home'),
  loading: (a)= > <HomeLoading />
})
/ /...
// Configure routing information
const routes = [
  {path: '/'.exact: true.component: Home},
  {path: '/home'.exact: true.component: Home},
  {path: '/post/:id'.component: Post},
  {
      path: '/profile'.component: Profile,
      routes: [{path: '/profile/notification'.component: Notification}
        / /...]}/ /...
]
export default routes
Copy the code

Write nested renderable components from documentation

import React from "react"
import {Route} from "react-router-dom";
export function RouteWithSubRoutes(route) {
    return( <Route path={route.path} render={props => ( // pass the sub-routes down to keep nesting <route.component {... props} routes={route.routes} /> )} /> ); }Copy the code

Render route (index.js)

import React from 'react'
import ReactDOM from 'react-dom'
import routes from './routes'
import {BrowserRouter as Router, Switch} from 'react-router-dom'
import {RouteWithSubRoutes} from './routes/RouteWithSubRoutes'
const RouterConfig = (a)= > (
  <Router>
      <Switch>
        {routes.map((route, i) => (
          <RouteWithSubRoutes key={i} {. route} / >
        ))}
      </Switch>
  </Router>
)
ReactDOM.render(<RouterConfig />, document.getElementById('root'))
Copy the code

We can write simple page components in containers to test whether the routing configuration is successful (containers /home/index.js)

import React, {Component} from 'react'
class HomeContainer extends Component {
  render() {
    return (
      <div>
        HOME
      </div>)}}export default HomeContainer
Copy the code

Route configuration is complete, at this point you can access different pages

Write a layout component using HOC

This is a page displayed by me imitating the Nuggets APP, from which we can extract: ① The layout of the navigation bar is fixed at the top of the page; ② There is an arrow on the left to return to the original page. Then a simple layout is as follows:

import React, {Component} from 'react'
import {NavBar, Icon} from 'antd-mobile'
const withNavBarBasicLayout = title= > {
  return WrappedComponent= > {
    return class extends Component {
      render() {
        return( <div> <NavBar mode="dark" icon={<Icon type="left" />} onLeftClick={this.goBack}> {title} </NavBar> <WrappedComponent {... this.props} /> </div> ) } goBack = () => { this.props.history.goBack() } } } } export default withNavBarBasicLayoutCopy the code

We specify the layout using decorator syntax on the Container page where we need it

@withNavBarBasicLayout('Home Page Special Display')
class TabPicker extends Component {
/ /...
}
Copy the code

With such a simple layout now complete, we can write multiple layout styles, such as the common three-column layout, as long as we specify them on the page

Global state Management

This has taken a long time. The examples on the official website are generally written by splitting actions,reducers, Saga middleware, etc. With this configuration, writing a simple state change requires switching between multiple folders. Then we see @rematch/core, which is a magic tool that we can use to write a streamlined dVA-style state management. Manage State, Reducers, and Effects as a Model. Take the homepage display of nuggets APP as an example

There is a tablist on the home page to show our selected concerns. The tablist data is shared by multiple routing pages, so we considered using store management. Here we consider storing the label display in the local localStorage. Write a simple model(store/models/home.js)

export default {
  namespace: 'home'.state: {
    tabList: [{title: 'front end'.show: true},
      {title: 'design'.show: true},
      {title: 'back-end'.show: true},
      {title: Artificial intelligence.show: true},
      {title: 'operations'.show: true},
      {title: 'Android'.show: true},
      {title: 'iOS'.show: true},
      {title: 'products'.show: true},
      {title: 'Tool Resources'.show: true}},reducers: {
    //resetTabList
    resetTabList(state, {tabList}) {
      return {
        ...state,
        tabList: tabList || state.tabList
      }
    }
  },
  effects: {async getTabListAsync(playload, state) {
      let tabList = await loadData('tabList')
      this.resetTabList({tabList})
    },
    async resetTabListAsync(playload, state) {
      await saveData('tabList', playload.tabList)
      this.resetTabList(playload)
    }
  }
}
Copy the code

Configure the Models exit page (Models /index.js)

import home from './home'
export default {
  home
}
Copy the code

Registered store (store/index. Js)

import { init } from '@rematch/core';
import models from './models'
const store = init({
    models
})
export default store;
Copy the code

Provide a root Provider in the root directory of index.js to provide a store that all routing pages can access

/ / new
import store from './store'
import {Provider} from 'react-redux'
/ / modify
const RouterConfig = (a)= > (
  <Router>
    <Provider store={store}>
      <Switch>
        {routes.map((route, i) => (
          <RouteWithSubRoutes key={i} {. route} / >
        ))}
      </Switch>
    </Provider>
  </Router>
)
Copy the code

For each page, initialize dispatch on the home page using the CONNECT association

import {connect} from 'react-redux'
const mapState = state= > ({
  tabList: state.home.tabList
})
const mapDispatch = ({home: {resetTabListAsync}}) = > ({
  resetTabListAsync: (tabList) = > resetTabListAsync({tabList: tabList})
})
@connect(mapState,mapDispatch)
@withTabBarBasicLayout('home')
class HomeContainer extends Component {
  static propTypes = {
    tabList: PropTypes.array.isRequired
  }
  componentWillMount() {
    // Global data can be initialized here
    this.props.getTabListAsync()
  }
  / /...
}
Copy the code

We can also modify the data on the TAB management page so that the data on the two pages is consistent.

this.setState({tabList: items},() => {
    this.props.resetTabListAsync(this.state.tabList)
})
Copy the code

And you’re done!

The basic project framework is complete, but there are still login authentication and data processing parts to deal with. I’ll write it here for now, and put the project on Github


Supplement: Icon used in the project can be directly downloaded from the app store to extract apK and filter image format files. Some images can be introduced using iconfont of the Ali cloud