react-router小记

一、前端路由原理

前端路由是如何做到URL和内容进行映射呢?监听URL的改变。
URL的hash
URL的hash也就是锚点(#), 本质上是改变window.location的href属性;
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新;

<div id="app">
  <a href="#/home">home</a>
  <a href="#/about">about</a>
  <div class="router-view"></div>
</div>

<script>
  // 1.获取router-view
  const routerViewEl = document.querySelector(".router-view");

  // 2.监听hashchange
  window.addEventListener("hashchange", () => {
    switch(location.hash) {
      case "#/home":
        routerViewEl.innerHTML = "home";
        break;
      case "#/about":
        routerViewEl.innerHTML = "about";
        break;
      default:
        routerViewEl.innerHTML = "default";
    }
  })
</script>

hash的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#,显得不像一个真实的路径。
HTML5的History
history接口是HTML5新增的, 它有l六种模式改变URL而不刷新页面:

  1. replaceState:替换原来的路径;
  2. pushState:使用新的路径;
  3. popState:路径的回退;
  4. go:向前或向后改变路径;
  5. forword:向前改变路径;
  6. back:向后改变路径;

我们这里来简单演示几个方法:

<div id="app">
  <a href="/home">home</a>
  <a href="/about">about</a>
  <div class="router-view"></div>
</div>

<script>
  // 1.获取router-view
  const routerViewEl = document.querySelector(".router-view");

  // 2.监听所有的a元素
  const aEls = document.getElementsByTagName("a");
  for (let aEl of aEls) {
    aEl.addEventListener("click", (e) => {
      e.preventDefault();
      const href = aEl.getAttribute("href");
      console.log(href);
      history.pushState({}, "", href);
      historyChange();
    })
  }

  // 3.监听popstate和go操作
  window.addEventListener("popstate", historyChange);
  window.addEventListener("go", historyChange);

  // 4.执行设置页面操作
  function historyChange() {
    switch(location.pathname) {
      case "/home":
        routerViewEl.innerHTML = "home";
        break;
      case "/about":
        routerViewEl.innerHTML = "about";
        break;
      default:
        routerViewEl.innerHTML = "default";
    }
  }

</script>

二. react-router

React Router的版本4开始,路由不再集中在一个包中进行管理了:
react-router是router的核心部分代码;
react-router-dom是用于浏览器的;
react-router-native是用于原生应用的;
目前我们使用最新的React Router版本是v5的版本:
实际上v4的版本和v5的版本差异并不大;
安装react-router:
安装react-router-dom会自动帮助我们安装react-router的依赖;

yarn add react-router-dom

三. react-router基本使用

3.1. Router基本使用

react-router最主要的API是给我们提供的一些组件:

1. BrowserRouter或HashRouter 2. Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件; 3. BrowserRouter使用history模式; 4. HashRouter使用hash模式;

Link和NavLink:
通常路径的跳转是使用Link组件,最终会被渲染成a元素;

NavLink是在Link基础之上增加了一些样式属性;
to属性:Link中最重要的属性,用于设置跳转到的路径;
Route:
Route用于路径的匹配;
path属性:用于设置匹配到的路径;
component属性:设置匹配到路径后,渲染的组件;
exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件;

eg:

import React, { PureComponent } from 'react';

import { BrowserRouter, Route, Link } from 'react-router-dom';

import Home from './pages/home';
import About from './pages/about';
import Profile from './pages/profile';

export default class App extends PureComponent {
  render() {
    return (
      <BrowserRouter>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
        <Link to="/profile">我的</Link>

        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/profile" component={Profile} />
      </BrowserRouter>
    )
  }
}

3.2. NavLink的使用

路径选中时,对应的a元素变为红色
这个时候,我们要使用NavLink组件来替代Link组件:

activeStyle:活跃时(匹配时)的样式;
activeClassName:活跃时添加的class;
exact:是否精准匹配;

先演示activeStyle:

<NavLink to="/" activeStyle={{color: "red"}}>首页</NavLink>
<NavLink to="/about" activeStyle={{color: "red"}}>关于</NavLink>
<NavLink to="/profile" activeStyle={{color: "red"}}>我的</NavLink>

但是,我们会发现在选中about或profile时,第一个也会变成红色:
原因是/路径也匹配到了/about或/profile;
这个时候,我们可以在第一个NavLink中添加上exact属性;

<NavLink exact to="/" activeStyle={{color: "red"}}>首页</NavLink>

默认的activeClassName:
事实上在默认匹配成功时,NavLink就会添加上一个动态的active class;

当然,如果你担心这个class在其他地方被使用了,出现样式的层叠,也可以自定义class

<NavLink exact to="/" activeClassName="link-active">首页</NavLink>
<NavLink to="/about" activeClassName="link-active">关于</NavLink>
<NavLink to="/profile" activeClassName="link-active">我的</NavLink>

3.3. Switch的作用

我们来看下面的路由规则:
当我们匹配到某一个路径时,我们会发现有一些问题;
比如/about路径匹配到的同时,/:userid也被匹配到了,并且最后的一个NoMatch组件总是被匹配到;

<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/profile" component={Profile} />
<Route path="/:userid" component={User}/>
<Route component={NoMatch}/>

默认情况下,react-router中只要是路径被匹配到的Route对应的组件都会被渲染;
但是实际开发中,我们往往希望有一种排他的思想:
只要匹配到了第一个,那么后面的就不应该继续匹配了;
这个时候我们可以使用Switch来将所有的Route进行包裹即可;

<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/about" component={About} />
  <Route path="/profile" component={Profile} />
  <Route path="/:userid" component={User} />
  <Route component={NoMatch} />
</Switch>

3.4. Redirect的使用

Redirect用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中:
我们这里使用这个的一个案例:
用户跳转到User界面;
但是在User界面有一个isLogin用于记录用户是否登录:
true:那么显示用户的名称;
false:直接重定向到登录界面;
App.js中提前定义好Login页面对应的Route:

<Switch>
  <Route path="/login" component={Login} />
  <Route component={NoMatch} />
</Switch>

在User.js中写上对应的逻辑代码:

import React, { PureComponent } from 'react'
import { Redirect } from 'react-router-dom';

export default class User extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      isLogin: false
    }
  }

  render() {
    return this.state.isLogin ? (
      <div>
        <h2>User</h2>
        <h2>用户名: coderwhy</h2>
      </div>
    ): <Redirect to="/login"/>
  }
}

四. react-router高级使用

4.1. 路由嵌套

在开发中,路由之间是存在嵌套关系的。
eg:

import React, { Component } from "react";
import { NavLink, Route, Switch } from "react-router-dom";

//renderRoutes路由统一配置
import { renderRoutes } from "react-router-config";

import routes from "../router";

export function AboutHistory(props) {
  return <h2>企业成立于2000年,拥有悠久的历史文化</h2>;
}
export function AboutCulture(props) {
  return <h2>创新/发展</h2>;
}
export function AboutContact(props) {
  return <h2>联系电话:020-888888</h2>;
}

export default class About extends Component {
  render() {
    console.log(this.props.route);
    return (
      <div>
        <NavLink exact to="/about" activeClassName="about-active">
          企业历史
        </NavLink>
        <NavLink exact to="/about/culture" activeClassName="about-active">
          企业文化
        </NavLink>
        <NavLink exact to="/about/contact" activeClassName="about-active">
          联系我们
        </NavLink>

        <Switch>
          <Route exact path="/about" component={AboutHistory}></Route>
          <Route path="/about/culture" component={AboutCulture}></Route>
          <Route path="/about/contact" component={AboutContact}></Route>
          <Route path="/about/join" component={AboutJoinus}></Route>
        </Switch>
       
      </div>
    );
  }
 
}

4.2. 手动跳转

目前我们实现的跳转主要是通过Link或者NavLink进行跳转的,实际上我们也可以通过JavaScript代码进行跳转。
但是通过JavaScript代码进行跳转有一个前提:必须获取到history对象。
如何可以获取到history的对象呢?两种方式
方式一:如果该组件是通过路由直接跳转过来的,那么可以直接获取history、location、match对象;
方式二:如果该组件是一个普通渲染的组件,那么不可以直接获取history、location、match对象;
如果我们希望在一个普通渲染的组件App组件中获取到history对象,必须满足以下两个条件:
App组件必须包裹在Router组件之内;
App组件使用withRouter高阶组件包裹;
index.js代码修改如下:

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

App.js代码修改如下:

import { Route, Switch, NavLink, withRouter } from 'react-router-dom';

class App extends PureComponent {
  render() {
    console.log(this.props.history);

    return (
      <div>
        <button onClick={e => this.pushToProfile()}>我的</button>

        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/profile" component={Profile} />
          <Route path="/:userid" component={User} />
          <Route component={NoMatch} />
        </Switch>
      </div>
    )
  }

  pushToProfile() {
    this.props.history.push("/profile");
  }
}

export default withRouter(App);

withRouter的高阶函数来自react-router-dom:
使用withRouter函数,就可以获取history、location、match对象;

五、 传递参数

传递参数有三种方式:

  1. 动态路由的方式;
  2. search传递参数;
  3. to传入对象;

动态路由的方式
动态路由的概念指的是路由中的路径并不会固定:
比如/detail的path对应一个组件Detail;
如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route,并且进行显示;
这个匹配规则,我们就称之为动态路由;
通常情况下,使用动态路由可以为路由传递参数。

<div>
  <NavLink to="/detail/abc123">详情</NavLink>

  <Switch>
    <Route path="/detail/:id" component={Detail}/>
    <Route component={NoMatch} />
  </Switch>
</div>

detail.js的代码如下:
我们可以直接通过match对象中获取id;
这里我们没有使用withRouter,原因是因为Detail本身就是通过路由进行的跳转;

import React, { PureComponent } from 'react'

export default class Detail extends PureComponent {
  render() {
    console.log(this.props.match.params.id);

    return (
      <div>
        <h2>Detail: {this.props.match.params.id}</h2>
      </div>
    )
  }
}

search传递参数
NavLink写法:
我们在跳转的路径中添加了一些query参数;

<NavLink to="/detail2?name=lisa&age=18">详情2</NavLink>

<Switch>
  <Route path="/detail2" component={Detail2}/>
</Switch>

Detail2中如何获取呢?
Detail2中是需要在location中获取search的;
注意:这个search没有被解析,需要我们自己来解析;

import React, { PureComponent } from 'react'

export default class Detail2 extends PureComponent {
  render() {
    console.log(this.props.location.search); // ?name=why&age=18

    return (
      <div>
        <h2>Detail2:{this.props.location.search}</h2>
      </div>
    )
  }
}

to传入对象
to可以直接传入一个对象

<NavLink
          to={{ pathname: "/detail3", search: "?name=abc", state: info }}
          activeClassName="lick-active"
        >
          详情3
        </NavLink>

详情3

import React, { Component } from "react";

export default class Detail3 extends Component {
  render() {
    const location = this.props.location;
    console.log(location);
    return (
      <div>
        <h2>Detail3 : {location.state.name}</h2>
      </div>
    );
  }
}

六. react-router-config

目前我们所有的路由定义都是直接使用Route组件,并且添加属性来完成的。
但是这样的方式会让路由变得非常混乱,我们希望将所有的路由配置放到一个地方进行集中管理:
这个时候可以使用react-router-config来完成;
安装react-router-config:

yarn add react-router-config

常见router/index.js文件:

import Home from "../pages/Home";
import About, {
  AboutHistory,
  AboutCulture,
  AboutContact,
  AboutJoinus,
} from "../pages/About";
import Profile from "../pages/Profile";
import User from "../pages/User";
import Product from "../pages/Product";

const routes = [
  {
    path: "/",
    exact: true,
    component: Home,
  },
  {
    path: "/about",
    component: About,
    //子路由
    routes: [
      {
        path: "/about",
        exact: true,
        component: AboutHistory,
      },
      {
        path: "/about/culture",
        component: AboutCulture,
      },
      {
        path: "/about/contact",
        component: AboutContact,
      },
      {
        path: "/about/join",
        component: AboutJoinus,
      },
    ],
  },
  {
    path: "/profile",
    component: Profile,
  },
  {
    path: "/user",
    component: User,
  },
  {
    path: "/product",
    component: Product,
  },
];

export default routes;

将之前的Switch配置,换成react-router-config中提供的renderRoutes函数:

 {/* <Switch>
          <Route exact path="/" component={Home}></Route>
          <Route path="/about" component={About}></Route>
          <Route path="/profile" component={Profile}></Route>
          <Route path="/user" component={User}></Route>
          <Route path="/login" component={Login}></Route>
          <Route path="/product" component={Product}></Route>
          <Route path="/detail/:id" component={Detail}></Route>
          <Route path="/detail2" component={Detail2}></Route>
          <Route path="/detail3" component={Detail3}></Route>
          <Route component={noMatch}></Route>
        </Switch> */}

        {renderRoutes(routes)}

如果是子组件中,需要路由跳转,那么需要在子组件中使用renderRoutes函数:
在跳转到的路由组件中会多一个 this.props.route 属性
该route属性代表当前跳转到的路由对象,可以通过该属性获取到 routes;

export default class About extends PureComponent {
  render() {
    return (
     <div>
        <NavLink exact to="/about" activeClassName="about-active">
          企业历史
        </NavLink>
        <NavLink exact to="/about/culture" activeClassName="about-active">
          企业文化
        </NavLink>
      
        {/* <Switch>
          <Route exact path="/about" component={AboutHistory}></Route>
          <Route path="/about/culture" component={AboutCulture}></Route>
        </Switch> */}
        {/* {renderRoutes(routes[1].routes)} */}
        {renderRoutes(this.props.route.routes)}
      </div>
    )
  }
}

实际上react-router-config中还提供了一个matchRoutes辅助函数:
matchRoutes(routes, pathname)传入一个路由对象数组,获取所有匹配的路径;

const routes = matchRoutes(this.props.route.routes, "/about");
console.log(routes);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值