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