四、React全家桶(二)
react-router
react-router包含3个库,react-router、react-router-dom和react-router-native。react-router提供最基本的路由功能,实际使用的时候我们不会直接安装react-router,而是根据应用运行的环境选择安装react-router-dom(在浏览器中使用)或react-router-native(在rn中使用)。react-router-dom和react-router-native都依赖react-router,所以在安装时,react-router也会自动安装,创建web应用
安装:
npm install react-router-dom --save
基本使用:
react-router中奉行一切皆组件的思想,路由器-Router、链接-Link、路由-Route、独占-Switch、重定向-Redirect 都以组件形式存在
Route渲染优先级:children > component > render
创建RouterPage.js
import React, {Component} from 'react' import {BrowserRouter, Link, Route} from 'react-router-dom'; import HomePage from './HomePage'; import UserPage from './UserPage'; export default class RouterPage extends Component { render() { return ( <div> <h1>RouterPage</h1> <BrowserRouter> <nav> <Link to='/'>首页</Link> <Link to='/user'>用户中心</Link> </nav> {/* 跟路由需要添加 exact ,实现精确匹配 */} <Route exact path='/' component={HomePage}/> <Route path='/user' component={UserPage}/> </BrowserRouter> </div> ) } }
404页面---独占路由 Switch
设定一个没有path的路由在路由列表最后面,表示一定匹配
<BrowserRouter> <nav> <Link to='/'>首页</Link> <Link to='/user'>用户中心</Link> </nav> {/* 添加Switch表示仅匹配一个 */} <Switch> {/* 跟路由需要添加 exact ,实现精确匹配 */} <Route exact path='/' component={HomePage}/> <Route path='/user' component={UserPage}/> <Route component={()=><div>404</div>}/> </Switch> </BrowserRouter>
动态路由
使用 :id 的形式定义动态路由
定义路由:
<Route path='/search/:id' component={Search}/>
添加导航链接:
<Link to={'/search/' + searchId}>搜索</Link>
创建Search并获取参数:
import React, {Component} from 'react' import {BrowserRouter, Link, Route, Switch} from 'react-router-dom'; function Search(props){ const {id} = props.match.params return <div> <h1>Search:{id}</h1> </div> } export default class RouterPage extends Component { render() { const searchId = 123 return ( <div> <h1>RouterPage</h1> <BrowserRouter> <nav> <Link to={'/search/' + searchId}>搜索</Link> </nav> <Switch> <Route path='/search/:id' component={Search}/> </Switch> </BrowserRouter> </div> ) } }
路由嵌套
Route组件嵌套在其他页面组件中就产生了嵌套关系
修改Search,添加新增和详情
function Search(props){ const {id} = props.match.params return <div> <h1>Search:{id}</h1> <nav> <Link to="/search/add">新增</Link> <Link to={"/search/detail/" + id}>详情</Link> </nav> <Route path='/search/add' component={()=><div>add</div>}/> <Route path={"/search/detail/:" + id} component={Detail}/> </div> } function Detail(){ return <div> <h1>Detail</h1> </div> }
路由守卫
思路:创建高阶组件包装Route使其具有权限判断功能
对 UserPage 做路由守卫,在RouterPage.js 中
<PrivateRoute path='/user' component={UserPage}/> <Route path='/login' component={LoginPage}/>
PrivateRoute.js
import React, { Component } from 'react' import {Route, Redirect} from 'react-router-dom'; import UserPage from './UserPage'; export default class PrivateRoute extends Component { render() { const {isLogin = false, location} = this.props const redirect = location.pathname console.log(this.props); return isLogin ? ( <Route path='/user' component={UserPage}/> ) : ( <Redirect to="/login"/> ) } }
与HashRouter对比:
1、HashRouter最简单,不需要服务器渲染,靠浏览器的#来区分path就可以,BrowserRouter需要服务器端对不同的URL返回不同的HTML
后端配置可参考 https://react-guide.github.io/react-router-cn/docs/guides/basics/Histories.html
2、BrowserRouter使用HTML5历史API(pushState,replaceState 和 propstate 事件),让页面的UI同步与URL
3、HashRouter 不支持 location.key 和location.state,动态路由跳转需要通过?传递参数
4、Hash不需要服务器任何配置就可以运行,如果你刚刚入门,那就使用它吧。但是我们不推荐在实际线上环境中用到它,因为每一个web应用都应该渴望使用 browserHistory
实现BrowserRouter
历史管理对象history初始化及向下传递,location变更监听
MyRouterPage.js
import React, { Component } from 'react' // import { BrowserRouter, Link, Route, Switch } from 'react-router-dom'; import { BrowserRouter, Link, Route } from '../kReact-router-dom'; import HomePage from './HomePage'; import UserPage from './UserPage'; export default class MyRouterPage extends Component { render() { return ( <div> <h1>MyRouterPage</h1> <BrowserRouter> <nav> <Link to="/">首页</Link> <Link to="/user">用户中心</Link> </nav> <Route exact path="/" component={HomePage}/> <Route path="/user" component={UserPage}/> </BrowserRouter> </div> ) } }
kReact-router-dom.js
import React, { Component, useContext } from 'react' import { createBrowserHistory } from 'history'; const RouterContext = React.createContext() const RouterProvider = RouterContext.Provider const RouterConsumer = RouterContext.Consumer export class BrowserRouter extends Component { constructor(props) { super(props) this.history = createBrowserHistory() this.state = { location: this.history.location } this.unlisten = this.history.listen(location => { this.setState({ location }) }) console.log(this.history); } componentWillUnmount() { if (this.unlisten) { this.unlisten() } } render() { const { children } = this.props return <RouterProvider value={{ history: this.history, location: this.state.location }}> {children} </RouterProvider> } } export function Route(props) { const ctx = useContext(RouterContext) const { location } = ctx const { path, component } = props const matchCurrent = path === location.pathname return matchCurrent ? React.createElement(component) : null } export class Link extends Component { handlerClick = (event, history) => { const { to } = this.props event.preventDefault() history.push(to) } render() { const { children, to } = this.props return <RouterConsumer> { ctx => <a href={to} onClick={event => this.handlerClick(event, ctx.history)}> {children} </a> } </RouterConsumer> } }