One, foreword

Three articles are planned for this series:

  • 【React series 】 Hand in Hand with the backend System (Architecture)
  • 【React series 】 Redux and Route Authentication
  • 【React series 】 Give you a hand with the backend system (componentized)

Project address: github React-admin

System preview: React-admin system

In the last article we introduced system architecture, and this one will continue:

  • The application of story
  • Login authorization
  • Routing authentication

Redux applications

Sidebar we have implemented rendering menu items according to configuration, now we need to improve its features: navigation highlighting and authentication. We manage our Sidebar state through Redux. To make Redux’s store available, we must use its and connect() :

// Page.js
import { createStore } from  'redux'
import store from 'store' // Create a store directory
ReactDOM.render(<Provider store={ createStore(store) }>
  <Page />
</Provider>.document.getElementById('root'))
Copy the code

is used to make stores available throughout the App, and connect() is used to connect stores to Components.

// Sidebar.js
import { connect } from 'react-redux'
import { updateSidebarState } from 'store/action'
class Sidebar extends Component {
    // omit code...
}
const mapStateToProps = (state, owns) = > ({
    sidebar: prop('sidebarInfo', state), // Retrieve the sidebarInfo data from store. owns })const mapDispatchToProps = dispatch= > ({
    dispatchSideBar: sidebar= > dispatch(updateSidebarState(sidebar)) // Update the data status
})
export default connect(
    mapStateToProps,
    mapDispatchToProps
)(SideBar)
Copy the code

2.1 Initialize the Sidebar data

Initializing the Sidebar data mainly does two things:

  • Default route configuration data does not identify the highlighted status, initialization process increasedactiveMark;
  • Adds the parent navigation itemkeyValue flag bit, used to detect the closing state;
// store/reducer.js
function initSidebar(arr) {
  return map(each= > {
      if(each.routes) {
          each.active = false
          each.key = generateKey(each.routes) // Produces a unique key, which is associated with the route path
          each.routes = initSidebar(each.routes)
      }
      return each
  }, arr)
}

// Update sidebar status
function updateSidebar(arr, key=' ') {
  return map(each= > {
      if(key === each.key) { each.active = !!! each.active }else if(each.routes) {
          each.routes = updateSidebar(each.routes, key)
      }
      return each
  }, arr)
}

export const sidebarInfo = (state=initSidebar(routes), action) = > {
  switch (action.type) {
      case 'UPDATE':
          return updateSidebar(state, action.key)
      default:
          return state
  }
}
Copy the code

Active and key attributes are added to the processed route configuration data:

2.2 Detection highlighting

The process of rendering the side navigation bar needs to detect the highlighting state: based on the current route path compared to the key of the navigation item:

// Sidebar.js
class Sidebar extends Component {
  constructor(props) {
    // ...
    this.state = {
      routeName: path(['locaotion'.'pathname'].this.props), // Obtain the current routing information
      routes: compose(this.checkActive.bind(this), prop('sidebar'(a))this.props) // Return the detected route data}}// omit code...
  checkActive (arr, routeName=' ') {
        const rName = routeName || path(['location'.'pathname'].this.props)
        if(! rName)return arr
        return map((each) = > {
            const reg = new RegExp(rName)
            if(reg.test(each.key)) {
                each.active = true
            } else if (each.routes) {
                each.routes = this.checkActive(each.routes, rName)
            }
            return each
        }, arr)
    }
}

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(withRouter(SideBar))
Copy the code

Special note: The Sidebar component needs to be wrapped by withRouter to get routing information within the component.

3. Login authorization

The scenario is as follows: user login data is valid for the current session (sessionStorage stores user information) and user information is available globally (Redux management). Suppose we store user data with:

{
    username: ' './ / account
    permission: [],  // List of user permissions
    isAdmin: false  // Administrator id
}
Copy the code

3.1 Initializing User Information

// store/reducer.js
const userInfo = getSessionStore('user') | | {// First get data from sessionStorage
    username: ' '.permission: [].isAdmin: false
}
export const user = (state=userInfo, action) = > {
    switch (action.type) {
        case 'LOGIN': 
            return pick(keys(state), action.user)
        default:
            return state
    }
}
Copy the code

3.2 Login

First inject the store state and action into the login component:

import { connect } from 'react-redux'
import { doLogin } from 'store/action'
import Login from './login'
const mapStateToProps = (state, owns) = > ({
    user: state, ... owns })const mapDispatchToProps = dispatch= > ({
    dispatchLogin: user= > dispatch(doLogin(user))
})
export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Login)
Copy the code

Then implement login logic in login.js:

class Login extends Component {
  login () {
      const { dispatchLogin, history } = this.props
      const { form } = this.state
      const user = {
          username: 'Ann song'.permission: ['add'].isAdmin: false
      }
      dispatchLogin(user) // Update the user data stored in the store
      setSessionStore('user', user) // Store user data in sessionStorage
      // login success
      history.push('/front/approval/undo') // Login redirection}}Copy the code

4. Route authentication

We implemented page-level routing in the previous article. Now we need to register application-level routing based on the routing configuration file. Review our routing configuration:

export default[{title: 'My affairs'.// Page title & level 1 NAV title
        icon: 'icon-home'.routes: [{
            name: 'Pending approval'.path: '/front/approval/undo'.component: 'ApprovalUndo'
        }, {
            name: 'Processed'.path: '/front/approval/done'.auth: 'add'.component: 'ApprovalDone'}}]]Copy the code

We register routes based on path and Component information, and authenticate routes based on Auth information. Let’s see how we implement the components for each route.

views
index.js

// views/index.js
import AsyncComponent from 'components/AsyncComponent'
const ApprovalUndo = AsyncComponent((a)= > import(/* webpackChunkName: "approvalundo" */ 'views/approval/undo'))
const ApprovalDone = AsyncComponent((a)= > import(/* webpackChunkName: "approvaldone" */ 'views/approval/done'))
export default {
    ApprovalUndo, ApprovalDone
}
Copy the code

Note: AsyncComponent was explained in the previous article.

4.1 Registering a Route

// router/index.js 
import routesConf from './config'
import views from 'views'
class CRouter extends Component {
    render () {
        return( <Switch> { pipe(map(each => { const routes = each.routes return this.generateRoute(routes, each.title) }), flatten)(routesConf) } <Route render={ () => <Redirect to="/front/404" /> } /> </Switch> ) } generateRoute (routes=[], Title ='React Admin') {return map(each => each.component? (<Route key={each. . Each path} render = {props = > {? Const reg = / \ \ S * g/const queryParams = window. The location. The hash. The match (reg) / / routing parameters matching const {params} = props. Match object.keys (params).foreach (key => {// delete? Parameter params[key] = params[key] && params[key].replace(reg, '') }) props.match.params = { ... params } const merge = { ... props, query: queryParams ? queryString.parse(queryParams[0]) : {}} const View = views[each.component.ponent] const wrapperView = (DocumentTitle title={title}> <View {... merge } /> </DocumentTitle> ) return wrapperView } } /> ) : this.generateRoute(each.routes, title), routes) } } const mapStateToProps = (state, owns) => ({ user: prop('user', state), ... owns }) export default connect( mapStateToProps )(CRouter)Copy the code

Our routing configuration file supports multi-level nesting, and the Route routes returned by recursive annotations are also nested arrays. Finally, flatten is needed to flatten the entire Route array.

4.2 Rights Management

Permission management includes login verification and permission verification. By default, all application routes require login verification.

class CRouter extends Component {
  generateRoute (routes=[], title='React Admin') {
    // ...
    // Return wrapperView directly in the previous version, which wrapped a layer of login verification
    return this.requireLogin(wrapperView, each.auth)
  }
  requireLogin (component, permission) {
    const { user } = this.props
    const isLogin = user.username || false  // Login id, fetched from redux
    if(! isLogin) {// Determine whether to log in
        return<Redirect to={'/front/login'} />} // If the current route has permission requirements, enter full permission verification return Permission? this.requirePermission(component, permission) : component } requirePermission (component, permission) { const permissions = path(['user', 'permission'], This.props) // if(! permissions || ! this.checkPermission(permission, permissions)) return <Redirect to="/front/autherror" /> return component } checkPermission (requirePers, UserPers) {const isAdmin = path(['user', 'isAdmin'], this.props) // // Return true if(typeof userPers === 'undefined') return false if(array.isarray (requirePers)) {// Return requirePers. Every (each => userpers.includes (each))} else if(requirePers instanceof RegExp) {// Return userpers.some (each => requirepers.test (each))} return userpers.includes (requirePers) // Route permission set to string}}Copy the code

The checkPermission function implements string, array, and re type checks. Therefore, the auth in our routing configuration file can be set in string, array, and re types.

In combination with redux, we can easily implement configurable and highly reusable route authentication. The system verifies the Permission list of the current user (generally returned from the back end through an interface) against the Permission requirements defined in the configuration file. If no Permission exists, the user is redirected to the Permission Error page.

Finally, this series of articles:

  • 【React series 】 Hand in Hand with the backend System (Architecture)
  • 【React series 】 Redux and Route Authentication
  • 【React series 】 Give you a hand with the backend system (componentized)

Project address: github React-admin

System preview: React-admin system