路由

安装
  • 先用create-react-app生成一个react项目
  • 进入该项目
  • 安装包 react-router-dom
  • 分为2种模式:hash(哈希),browser(浏览器的那种,H5提供的API)
认识路由

路由模块,需要一个路由容器,标识着使用了那种模式的路由

import {容器} from "react-router-dom";
// 容器中存放的就是路由:HashRouter 或者 BrowserRouter
// 这2种都可以,并且都是组件,为了我们方便来回使用方便一般都会给他们起个别名
//⚠️ 起完别名之后,则BrowserRouter这个名字就作废了。
import {BrowserRouter as Router} from "react-router-dom";

在以下案例中均已,Router 为BrowserRouter。
Route

  • 路由一条一条的路由,也是个组件
import {BrowserRouter as Router,Route} from "react-router-dom";
渲染路由

⚠️ Router 路由盒子,只能有一个根节点

  • 先渲染Router
  • 在渲染Route

修改前

import React,{Component} from "react";
import ReactDOM from "react-dom";
import {BrowserRouter as Router,Route} from "react-router-dom";

render(<Router>
<Route></Route>//直接Router 放在第一个根节点会报错,Error:  Router只能有一个child
<Route></Route> //所以要加一个 <></>或者 div 包起来
</Router>,wondow.root)

修改后

import React,{Component} from "react";
import ReactDOM from "react-dom";
import {BrowserRouter as Router,Route} from "react-router-dom";
render(<Router>
	<>
	<Route></Route>
	<Route></Route>
	</>
</Router>,wondow.root)

每条路由都和对应的组件创建链接
默认路由都是从上到下匹配,如果匹配成功就会渲染对应的组件

  • path 路由地址
  • component 对应的组件
  • exact 是否严格匹配,默认为不严格匹配(false)。
import React,{Component} from "react";
import ReactDOM from "react-dom";
import {BrowserRouter as Router,Route} from "react-router-dom";
import Home from "./pages/Home";
import Proile from "./pages/Proile";
render(<Router>
	<>
	<Route path="/home" component={Home}></Route>
	<Route path="/prfile" component={User}></Route>
	</>
</Router>,wondow.root)

路由匹配的问题

  • 路由只匹配开头
  • path="/home" 和 path="/home/user" 都有/home 会渲染2个组件
  • home 一般用/路径表示,在浏览器中输入/会显示home,输入/user或者其他的/… 也会显示home页面
    此时,只需要加入是否严格匹配 exact
render(<Router>
	<>
	<Route path="/home" exact={true} component={Home}></Route>
	<Route path="/prfile" component={User}></Route>
	</>
</Router>,wondow.root)

解决404问题,在后端处理,找不到的话就返回首页,首页会根据当前的路径再次渲染路由。

  • Vue 中有专门说根据那种方式来处理的
    https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations
  • react-router-dom的解决方案
    • 添加一个Redirect 组件用来解决找不到对应的页面的时候
      • to 找不到页面是定位到哪里页面的地址
    • 存在的问题当我们给home的地址设置为 / 时,这是Redirect的地址也是**/** ,页面会报错:Error 你尝试去定位一个相同的路由
    	import {BrowserRouter as Router,Route,Redirect} from "react-router-dom";
    	render(<Router>
    		<>
    		<Route path="/home" exact={true} component={Home}></Route>
    		<Route path="/prfile" component={User}></Route>
    		<Route to="/" />
    		</>
    	</Router>,wondow.root)
    
    • 解决方案
      希望路由匹配到一个只会,就不要向下匹配了。
    • 添加一个Switch 组件
    • Switch 里面只能包路由Route
    • 把包裹Route的元素换成Switch就好
    	import {BrowserRouter as Router,Route,Redirect} from "react-router-dom";
    	render(<Router>
    		<Switch>
    		<Route path="/home" exact={true} component={Home}></Route>
    		<Route path="/prfile" component={User}></Route>
    		<Route to="/" />
    		</Switch>
    	</Router>,wondow.root)
    

路由导航

  • Link
  • NavLink
  • 这2个都是组件,用法也都一样,唯一的就是:NavLink 可以给选中的按钮配置高亮样式
  • 默认都是a标签
  • 不支持指定标签名
    • to 路径
    • 使用NavLink 要注意当碰到首页是一个**/的时候加exact属性严格匹配,不然会出现多个元素都有高亮**类名
	import {BrowserRouter as Router,Route,Redirect,NavLink} from "react-router-dom";
	render(<Router>
			<>
				<div>
						// <NavLink to="/" exact={true}></NavLink>
						<NavLink to="/home"></NavLink>
						<NavLink to="/user"></NavLink>
				</div>
				<Switch>
				<Route path="/home" exact={true} component={Home}></Route>
				<Route path="/user" component={User}></Route>
				<Route to="/" />
				</Switch>
			</>
		</Router>,wondow.root)

优化

每个页面都各自处理自己的内容

布局:nav.js 存放导航,index.js 存放路由并渲染到页面,home.js /user.js 是导航对应的内容
需求:每个页面都各自存放自己的东西,采用另一个app.js 页面把路由和导航组合到一起,这样就不需要把导航文件引入到index.js路由页面了

  • 把导航nav.js 页面在app.js 页面引入
  • 把app.js 页面在 index.js 路由页面引入
  • 用引入的App组件包裹 ‘Switch’ 组件
  • 在app.js 里面使用 {this.props.children} 把路由在这里展示

index.js

import App from "./app";
render(<Router>
			<App>
				<Switch>
				<Route path="/home" exact={true} component={Home}></Route>
				<Route path="/user" component={User}></Route>
				<Route to="/" />
				</Switch>
			</App>
		</Router>,wondow.root)

App.js

import React,{Component} from "react";
import ReactDOM from "react-dom";
import Nav from "./nav";

export default class App extends Component{
constructor(){
	super();
}
	render(
		return(
			<div>
				<Nav></Nav>
				{this.props.children}
			</div>
		)
	)
}
添加二级导航
  • 在user.js为例

改写user.js

import React, { Component } from 'react';
import SliderBar from '../components/SliderBar';
import {Route,Switch} from 'react-router-dom';
import Add from './Add'
import UserDetail from './UserDetail'
import List from './List'
export default class User extends Component {
  state = {
  // 二级路由地址和标题
    sliderBarData: [
      { path: '/user/add', content: '用户添加' },
      { path: '/user/list', content: '用户list' },
    ]
  }
  render() {
    return (<div>
      <div className="col-md-3">
        <SliderBar sliderBarData={this.state.sliderBarData}></SliderBar>
      </div>
      <div className="col-md-9">
        <Switch>
          {/* 二级菜单 默认展示添加路由 */}
          <Route path='/user' exact={true} component={Add} /> //当点击user路径的时候默认展示Add组件的内容。这样不会当我们点击user路径的时候页面空着
          <Route path='/user/add' component={Add}/>
          <Route path='/user/list' component={List}/>
          <Route path='/user/detail/:uid' component={UserDetail}/>
          ///user/detail/:uid   路径参数
        </Switch>
      </div>
    </div>)
  }
}

SliderBar.js 二级导航

	import React, { Component } from 'react';
	import { Link} from  'react-router-dom'
	export default class SliderBar extends Component {
	  constructor() {
	    super();
	  }
	  render() {
	    return (<nav className="nav nav-stacked" >
	      {this.props.sliderBarData.map((slide,key)=>(
	        <li key={key}><Link to={slide.path}>{slide.content}</Link></li>
	      ))}
	    </nav>)
	  }
	}

Add.js

  • 使用了 Router 容器,路由容器上会挂在着一些属性,Provider(供应商的意思,在最外层组件提供一些属性,所有子孙都可以应用),histoty…
  • Route 组件中 可以获取到 父级提供的属性,Route来消费,并把这些属性传递给了渲染组件_
  • 在Add.js 中console.log(this.props);可以看到有3个属性分别是: history,location,match
    加粗为常用方法
    • history 类似浏览器的history,可以理解为主要是实现跳转
      history下的方法
      • go 去哪
      • goBack 后退
      • goForward 往前走
      • push 跳转页面
    • location 当前关于路径的一些信息。
      • hash
      • key
      • _pathname _
      • state 跳转页面时带的数据放在这里
    • match 匹配,当前路径是否和我的匹配
      • path
      • url
      • params:{} 当前路径的参数
      • isExact:false/true 是否严格匹配
	import React, { Component } from 'react';
	import ReactDOM from 'react-dom';
	export default class Add extends Component {
	  input = React.createRef();
	  constructor() {
	    super();
	  }
	  handleSubmit = (e) => {
	    e.preventDefault(); // 阻止默认行文
	    let username = this.input.current.value;
	    let lists = JSON.parse(localStorage.getItem('lists'))|| [];
	    lists.push({username,id: Math.random()});
	    localStorage.setItem('lists', JSON.stringify(lists));
	    // 使用路由容器后 路由容器上挂载着一些属性 Provider history..
	    // Route组件中可以获取到 父级提供的属性,Route来消费 并且把这些属性传递给了渲染的组件
	    this.props.history.push('/user/list'); //跳转到 list页面
	  }
	  render() {
	    return (<div>
	      <form onSubmit={this.handleSubmit}>
	        <input type="text" className="form-control" required ref={this.input}/>
	        <button className="btn btn-primary">添加</button>
	      </form>
	    </div>)
	  }
	}

List.js

	import React, { Component } from 'react';
	import ReactDOM from 'react-dom';
	import {Link} from "react-router-dom";
	export default class List extends Component {
	  state = {
	    users: JSON.parse(localStorage.getItem('lists')) || [] //取app.js 添加的数据
	  }
	  render() {
	    return (<table className="table table-borderd">
	      <thead>
	        <tr>
	          <th>id</th>
	          <th>name</th>
	        </tr>
	      </thead>
	      <tbody>
	          {this.state.users.map((user,index)=>{
	            return <tr key={index}>
	              <td><Link to={{
	                pathname: `/user/detail/${user.id}`,//点击的时候把列表的id作为参数带入
	                state: user.username // 这个状态只有点击的时候才有
	              }}>{user.id}</Link></td>
	              //如果不传state数据可以直接这样:
	             //  <td><Link to={`/user/detail/${user.id}`}>{user.id}</Link></td>
	              <td>{user.username}</td>
	            </tr>
	          })}
	      </tbody>
	    </table>)
	  }
	}

UserDetail.js 二级内容 当点击list的时候进入,展示id和内容

	import React,{Component} from 'react';
	import ReactDOM from 'react-dom';
	export default class UserDetail extends Component{
	    constructor(){
	        super();
	   }
	   render(){
	      return (<div>
	         UserDetail
	         //浏览器的地址		真实的				去到id
	         {/* /user/detail/1  /user/detail/:id  => id:1 */}
	         //拿到当前路径的参数
	         {this.props.match.params.uid}
	         {/* 如果没拿到状态 就在获取一遍 通过id*/}
	         {this.props.location.state} //拿到传入的数据
	     </div>)
	 }
	}
Route 嵌套组件

过程

  • 最开始渲染Route组件
  • Route 又帮我们渲染 传入的组件

方法1
修改App.js

import React,{Component} from "react";
import ReactDOM from "react-dom";
import {Route} from "react-router-dom";
import Nav from "./nav";
export default class App extends Component{
constructor(){
	super();
}
	render(
		return(
			<div>
				// <Nav></Nav>改为
				//这样可以使 Nav下面的所有导航都可以有history 
				//所有用Route 渲染的组件 都有history
				<Route path="/" component={Nav}>
				{this.props.children}
			</div>
		)
	)
}

方法2
把App.js 复原

import React,{Component} from "react";
import ReactDOM from "react-dom";
import Nav from "./nav";
export default class App extends Component{
constructor(){
	super();
}
	render(
		return(
			<div>
				<Nav></Nav>
				{this.props.children}
			</div>
		)
	)
}

Nav.js修改

import React, { Component } from 'react';
import { NavLink, Route, withRouter } from 'react-router-dom';
import MenuLink from './MenuLink';
//希望给当前组件带上一个Route,模拟的
// let withRouter = (Component) => {
//   return ()=>{
//     return <Route  component={Component}/>
//   }
// }
// 高阶组件 就是组件返回组件 ,可以把公共的功能放到父亲来做
// 封装公共方法的组件
class Nav extends Component {
  constructor() {
    super();
  }
  handleClick = () => {
    this.props.history.push('/')
  }
  render() {
    return (
      <nav className="navbar navbar-inverse">
        <div className="container-fluid">
          <div className="navbar-header">
            <a className="navbar-brand" onClick={this.handleClick}>
              路由系统
            </a>
          </div>
          <div className="navbar-nav nav">
            <MenuLink to="/" exact={true}>首页</MenuLink>
            <MenuLink to="/user">用户</MenuLink>
            <MenuLink to="/profile">个人中心</MenuLink>
            <MenuLink to="/login">登录</MenuLink>
          </div >
        </div >
      </nav >
    )
  }
}
// 如果某个组件 不是通过route来渲染的还想用里面的props 可以使用withRouter
export default withRouter(Nav); // 可以改写成 @withRouter的形式
权限校验

需求:判断用户是否登入,没有登入则跳转到登入页面,
login.js

	import React,{Component} from 'react';
	import ReactDOM from 'react-dom';
	export default class Login extends Component{
	   constructor(){
	        super();
	   }
	   render(){
	      return (<div>
	        <button onClick={()=>{
	          localStorage.setItem('login',true) //点击登入设置login,login用来判断用户是否登入
	        }}>登录</button>
	        <button onClick={()=>{
	          localStorage.clear();//点击清除所有的值
	        }}>取消登录</button>
	     </div>)
	 }
	}

给index.js 添加login

	import Login from './Login';
	<Route path="/login" component={Login}></Route>

给nav.js 添加login导航

<NavLink to="/login">登录</NavLink>
  • 这里拿user来做案例
  • 在进入user之前,在外面在包一层做校验
  • 叫做:高阶组件中判断用户是否校验,如果没做校验就跳转
    添加一个protected.js
	import React,{Component} from 'react';
	import { Route,Redirect} from 'react-router-dom';
	// 函数组件 参数是属性
	// 把component 拿出来 重新命名Component 组件名必须大写
	// props就是其他属性
	
	// route中可以放置 component
	// render render可以放一个函数 他会渲染这个函数的返回值
	let Protected = ({component:Component,...props})=>{
	  return <Route {...props} render={(props)=>{ // p=> history,match,lication
	    return localStorage.getItem('login') ? <Component {...props}/> : <Redirect to="/login"/>    
	  }}/>
	}
	export default Protected

修改index.js

	import Protected from './Protected'
	render(<Router>
	  <App>
	    {/* switch 会判断path */}
	    <Switch>
	      <Protected path="/" exact={true} component={Home}></Protected>
	      <Route path="/user" component={User}></Route>
	      {/* 高阶组件中判断用户是否登录过,如果没登陆跳转 Protected */}
	      <Protected path="/profile" component={Profile}></Protected>
	      <Route path="/login" component={Login}></Route>
	      <Redirect to="/"/>
	    </Switch>
	  </App>
	</Router>, window.root)
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐