react-router v4 学习实践

    最近学习了 react-router v4,根据官方 API 文档和网上资源做了一个简单的路由示例。

    先用官方的工具 create-react-app  初始化一个 react 项目模板,再根据自己的需要修改。

要实现的路由:

1. 登录页(/login)

2. 主页(/home):一级导航

3. 商品管理(/goods):一级导航

4. 商品列表(/goods/list):二级导航

5. 商品品牌(/goods/brand):二级导航

6. 路由重定向:

    (1)未登录时,地址栏输入主域名(localhost:3000),页面重定向到登录页;否则,重定向到主页。

    (2)点击一级导航“商品管理”时,重定向到其下的第一个子导航“商品列表”。

    (3)退出后,重定向到登录页。

项目结构:

├── app
│   ├── public
│   │   ├── favicon.ico
│   │   ├── index.html
│   │   └── manifest.json
│   ├── src
│   │   ├── assets
│   │   │   ├── app.css
│   │   │   └── logo.svg
│   │   ├── common
│   │   │   └── RouteWithSubRoutes.js
│   │   ├── modules
│   │   │   ├── asideContainer
│   │   │   │   └── Goods.js
│   │   │   ├── container
│   │   │   │   ├── Container.js
│   │   │   │   ├── Header.js
│   │   │   │   └── Home.js
│   │   │   ├── error
│   │   │   │   └── NotFound.js
│   │   │   ├── goods
│   │   │   │   ├── Brand.js
│   │   │   │   └── List.js
│   │   │   ├── login
│   │   │   │   └── Login.js
│   │   ├── index.js
│   │   ├── Routes.js
│   ├── .gitignore
│   ├── package-lock.json
│   ├── package.json
│   └── README.md

路由配置(src/Routes.js):

import React from 'react'
import {
  BrowserRouter as Router,
  Switch,
  Route
} from 'react-router-dom'

import RouteWithSubRoutes from './common/RouteWithSubRoutes.js'
import NotFound from './modules/error/NotFound.js'
import Login from './modules/login/Login.js'
import Container from './modules/container/Container.js'
import Home from './modules/container/Home.js'
import Goods from './modules/asideContainer/Goods.js'
import List from './modules/goods/List.js'
import Brand from './modules/goods/Brand.js'

const routes = [
  {
    path: '/home',
    component: Home
  },
  {
    path: '/goods',
    component: Goods,
    children: [
      {
        path: '/goods/list',
        component: List
      },
      {
        path: '/goods/brand',
        component: Brand
      }
    ]
  }
]

export default () => (
  <Router>
    <Switch>
      <Route path='/login' component={Login} />
      <Container>
        <Switch>
          {routes.map((route, i) => (
            <RouteWithSubRoutes key={i} {...route} />
          ))}
          <Route component={NotFound} />
        </Switch>
      </Container>
    </Switch>
  </Router>
)

    重定向需要用到 Redirect 组件,但是我的经验就是,Redirect 不要与 Route 作为同级兄弟一起使用,否则页面会保持在 Redirect 指定的路由,而不能跳到其它的路由:

this.props.history.push 指定的路由就会无效。

    RouteWithSubRoutes 参考的是官方的的示例。它是一个函数,接收一个对象作为参数,并返回一个(子)路由。在这里它用于渲染一级导航。

登录(src/modules/login/Login.js):

import React, { Component } from 'react'
import { Redirect } from 'react-router-dom'

export default class Login extends Component {
  constructor(props) {
    super(props)
    this.state = {
      loggedIn: localStorage.getItem('loggedIn'),
      username: 'anonymous',
      password: '123'
    }

    this.onInputChange = this.onInputChange.bind(this)
    this.onSubmit = this.onSubmit.bind(this);
  }

  onInputChange(event) {
    const target = event.target
    const name = target.name
    const value = target.value

    this.setState({
      [name]: value
    })
  }

  onSubmit(event) {
    if (this.state.username && this.state.password) {
      localStorage.setItem('loggedIn', true)
      localStorage.setItem('username', this.state.username)
      this.setState({loggedIn: true})
      this.props.history.push('/home')
    }
  }

  render() {
    if (this.state.loggedIn && this.props.location.pathname === '/login') {
      return (
        <Redirect to='/home' />
      )
    }

    return (
      <div className='login-wrap'>
        <h2>登 录</h2>
        <div className='field-box'>
          <label className='control-label'>用户名:</label>
          <input type='text' name='username' value={this.state.username} onChange={this.onInputChange} />
        </div>
        <div className='field-box'>
          <label className='control-label'>密  码:</label>
          <input type='password' name='password' value={this.state.password} onChange={this.onInputChange} />
        </div>
        <div className='field-box'>
          <label className='control-label'></label>
          <button type='button' onClick={this.onSubmit}>登 录</button>
        </div>
      </div>
    )
  }
}

将用户名写入 localStorage,再通过 this.props.history.push('/home') 跳转到主页。

Container组件(src/modules/container/Container.js):

import React, { Component } from 'react'
import { Redirect } from 'react-router-dom'

import Header from './Header'

class Container extends Component {
  constructor() {
    super()
    this.state = {
      loggedIn: localStorage.getItem('loggedIn'),
      test: 'it is a testing'
    }
  }

  render() {
    if (!this.state.loggedIn) {
      return (
        <Redirect to='/login' />
      )
    } else if (this.props.location.pathname === '/') {
      return (
        <Redirect to='/home' />
      )
    }

    return (
      <div>
        <Header {...this.state} />
        <div className='main-layout'>
          {this.props.children}
        </div>
      </div>
    )
  }
}

export default Container

判断用户是否登录,再通过 Redirect 重定向到相应的路由。

this.props.children 用于获取 Container 的子组件。

头部(src/modules/container/Header.js):

import React, { Component } from 'react'
import { NavLink, Redirect } from 'react-router-dom'

export default class Header extends Component {
  constructor(props) {
    super(props)
    this.state = {
      loggedIn: localStorage.getItem('loggedIn')
    }
  }

  onLogout = () => {
    localStorage.setItem('loggedIn', '')
    this.setState({loggedIn: false})
  }

  render() {
    if (!this.state.loggedIn) {
      return (
        <Redirect to='/login' />
      )
    }

    return (
      <header className='fixed-top'>
        <div className='pull-left'>
          <h1>管理平台</h1>
          <NavLink to='/home' exact>主页</NavLink>
          <NavLink to='/goods'>商品管理</NavLink>
        </div>
        <div className='pull-right'>
          <div className='header-info'>
            欢迎您,{localStorage.getItem('username')}
            <span style={{marginLeft: 10}}>|</span>
            <a className='logout' onClick={this.onLogout}>退出</a>
          </div>
        </div>
      </header>
    )
  }
}

退出后,清空 localStorage 中的 loggedIn,并重定向到登录页

<Redirect to='/login' />

商品管理(src/modules/asideContainer/Goods.js):

import React from 'react'
import { NavLink, Route, Redirect } from 'react-router-dom'

import RouteWithSubRoutes from '../../common/RouteWithSubRoutes.js'

export default ({ routes, path }) => (
  <div>
    <div className='aside-nav'>
      <NavLink to="/goods/list">商品列表</NavLink>
      <NavLink to="/goods/brand">商品品牌</NavLink>
    </div>

    {
      routes.map((route, i) => {
        return (
          <RouteWithSubRoutes key={i} {...route}/>
        )
      })
    }

    <Route exact path='/goods' render={() => (
      <Redirect to='goods/list' />
    )} />
  </div>
)

同样用到了 RouteWithSubRoutes, 在这里它用于渲染二级导航。

通过 Route 判断当前页是“商品管理”(exact 用于路由的严格匹配),再用 Redirect 重定向。

注意,当前路由处于 active 状态,用到的是 NavLink 组件;另一个类似功能的组件是 Link,但没有当前 active 状态。

回过头去看看 Header 组件:

<NavLink to='/home' exact>主页</NavLink>
<NavLink to='/goods'>商品管理</NavLink>

对于“主页”,添加了 exact 属性,但“商品管理”则没有,为什么?因为当路由跳转到“商品列表”(/goods/list)时,exact 严格匹配 /goods 的结果为 false,模糊匹配的结果才为 true。

 更多细节,详见项目内容。

原文地址:https://www.cnblogs.com/caihg/p/7111904.html