1. 程式人生 > >react-router v4 學習實踐

react-router v4 學習實踐

key 網上 .com 主域 left 登錄 返回 定向 .cn

最近學習了 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):

 1 import React, { Component } from ‘react‘
 2 
 3 export default class Login extends Component {
 4   constructor(props) {
 5     super(props)
 6     this.state = {
 7       loggedIn: localStorage.getItem(‘loggedIn‘),
 8       username: ‘anonymous‘,
 9       password: ‘123‘
10     }
11 
12     this.onInputChange = this.onInputChange.bind(this)
13     this.onSubmit = this.onSubmit.bind(this);
14   }
15 
16   onInputChange(event) {
17     const target = event.target
18     const name = target.name
19     const value = target.value
20 
21     this.setState({
22       [name]: value
23     })
24   }
25 
26   onSubmit(event) {
27     if (this.state.username && this.state.password) {
28       localStorage.setItem(‘loggedIn‘, true)
29       localStorage.setItem(‘username‘, this.state.username)
30       this.setState({loggedIn: true})
31       this.props.history.push(‘/home‘)
32     }
33   }
34 
35   render() {
36     return (
37       <div className=‘login-wrap‘>
38         <h2>登 錄</h2>
39         <div className=‘field-box‘>
40           <label className=‘control-label‘>用戶名:</label>
41           <input type=‘text‘ name=‘username‘ value={this.state.username} onChange={this.onInputChange} />
42         </div>
43         <div className=‘field-box‘>
44           <label className=‘control-label‘>密  碼:</label>
45           <input type=‘password‘ name=‘password‘ value={this.state.password} onChange={this.onInputChange} />
46         </div>
47         <div className=‘field-box‘>
48           <label className=‘control-label‘></label>
49           <button type=‘button‘ onClick={this.onSubmit}>登 錄</button>
50         </div>
51       </div>
52     )
53   }
54 }

將用戶名寫入 localStorage,再通過 this.props.history.push(‘/home‘) 跳轉到主頁。

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

 1 import React, { Component } from ‘react‘
 2 import { Redirect } from ‘react-router-dom‘
 3 
 4 import Header from ‘./Header‘
 5 
 6 class Container extends Component {
 7   constructor() {
 8     super()
 9     this.state = {
10       loggedIn: localStorage.getItem(‘loggedIn‘),
11       test: ‘it is a testing‘
12     }
13   }
14 
15   render() {
16     if (!this.state.loggedIn) {
17       return (
18         <Redirect to=‘/login‘ />
19       )
20     } else if (this.props.location.pathname === ‘/‘) {
21       return (
22         <Redirect to=‘/home‘ />
23       )
24     }
25 
26     return (
27       <div>
28         <Header {...this.state} />
29         <div className=‘main-layout‘>
30           {this.props.children}
31         </div>
32       </div>
33     )
34   }
35 }
36 
37 export default Container

判斷用戶是否登錄,再通過 Redirect 重定向到相應的路由。

this.props.children 用於獲取 Container 的子組件。

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

 1 import React, { Component } from ‘react‘
 2 import { NavLink, Redirect } from ‘react-router-dom‘
 3 
 4 export default class Header extends Component {
 5   constructor(props) {
 6     super(props)
 7     this.state = {
 8       loggedIn: localStorage.getItem(‘loggedIn‘)
 9     }
10   }
11 
12   onLogout = () => {
13     localStorage.setItem(‘loggedIn‘, ‘‘)
14     this.setState({loggedIn: false})
15   }
16 
17   render() {
18     if (!this.state.loggedIn) {
19       return (
20         <Redirect to=‘/login‘ />
21       )
22     }
23 
24     return (
25       <header className=‘fixed-top‘>
26         <div className=‘pull-left‘>
27           <h1>管理平臺</h1>
28           <NavLink to=‘/home‘ exact>主頁</NavLink>
29           <NavLink to=‘/goods‘>商品管理</NavLink>
30         </div>
31         <div className=‘pull-right‘>
32           <div className=‘header-info‘>
33             歡迎您,{localStorage.getItem(‘username‘)}
34             <span style={{marginLeft: 10}}>|</span>
35             <a className=‘logout‘ onClick={this.onLogout}>退出</a>
36           </div>
37         </div>
38       </header>
39     )
40   }
41 }

退出後,清空 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 學習實踐