react-router v4 学习实践

Posted caihg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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。

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

以上是关于react-router v4 学习实践的主要内容,如果未能解决你的问题,请参考以下文章

刷新浏览器时如何使react-router v4:id-path工作?

在 react-router v4 中使用 React IndexRoute

在 react-router v4 的嵌套路由中看到空白页

react-router v4 - browserHistory 未定义

从 v3 迁移到 v4 react-router 时出现错误

如何在 react-router v4 中获取查询参数