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):
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 學習實踐