React学习笔记(七)

183 阅读8分钟

一.React路由

1.SPA

单页Web应用(single page web application,SPA)。整个应用只有一个完整的页面。

点击页面中的链接不会刷新页面,只会做页面的局部更新。

数据都需要通过ajax请求获取,并在前端异步展现

2.什么是路由

一个路由其实就是一个映射关系(k:v)

key为路径,value可能是function 或者是 component

后端路由:

value是function,用来处理客户端提交的请求

注册路由:router.get(path,function(req,res))

工作过程:当node接收一个请求的时候,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应的数据

前端路由:

浏览器端路由,value是Component,用于展示页面内容

注册路由:< Route path="/test" component={Test}>

工作过程:当浏览器的path变为/test的时候,当前路由组件就会变成Test组件

前端路由的原理

这个主要是依靠于history,也就是浏览器的历史记录。

浏览器上的记录其实就是一个栈,前进一次就是入栈,后退一次就是出栈。

并且历史记录上有一个监听的方法,可以时时刻刻监听记录的变化。从而判断是否改变路径

History developer.mozilla.org/zh-CN/docs/…

3.react-router-dom

react的路由有三类:

web【主要适用于前端】,native【主要适用于本地】,anywhere【任何地方】

在这主要使用web也就是这个标题 react-router-dom

基本的使用:

导航中的a标签改写成Link标签

<Link to="/路径" >xxx< /Link>

展示区写Route标签进行路径的匹配

<Route path = '/路径' component={组件名称}>

< App>最外侧包裹了一个< BrowserRouter>或者< HashRouter>

<div className="list-group">
    <Link className="list-group-item"  to="/about">About</Link>
    <Link className="list-group-item"  to="/home">Home</Link>
</div>

<div className="panel-body">
    {/* 注册路由,也就是写对应的关系 */}
    <Route path="/about"component={About}/>
    <Route path="/home"component={Home}/>
</div>

index.js:
ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>
    ,document.getElementById("root"))

那么使用Link代替a标签之后,在页面上会是什么呢,我们发现其实页面上也是把link转化为了a标签

路由组件以及一般组件

  • 写法不一样

一般组件:

路由组件:< Route path="/demo" component ={Demo}/>

  • 存放的位置一般不同

一般组件:components

  • 接收的内容【props】

一般组件:写组件标签的时候传递什么,就能收到什么

路由组件:接收到三个固定的属性【history,location,match

history:
    go: ƒ go(n)
    goBack: ƒ goBack()
    goForward: ƒ goForward()
    push: ƒ push(path, state)
    replace: ƒ replace(path, state)
location:
    pathname: "/about"
    search: ""
    state: undefined

match:
    params: {}
    path: "/about"
    url: "/about

二.NavLink

因为Link不能够改变标签体,因此只适合用于一些写死的标签。而如果想要有一些点击的效果,使用NavLink.

如下代码,就写了ctiveClassName,当点击的时候就会触发这个class的样式

{/*NavLink在点击的时候就会去找activeClassName="ss"所指定的class的值,如果不添加默认是active
 这是因为Link相当于是把标签写死了,不能去改变什么。*/}

<NavLink  ctiveClassName="ss" className="list-group-item"  to="/about">About</NavLink>
<NavLink className="list-group-item"  to="/home">Home</NavLink> 

但是可能一个导航又很多标签,如果这样重复的写NavLink也会造成很多的重复性的代码问题。

因此可以自定义一个NavLink:

 // 通过{...对象}的形式解析对象,相当于将对象中的属性全部展开
 //<NavLink  to = {this.props.to} children = {this.props.children}/>
<NavLink className="list-group-item" {...this.props}/>

在使用的时候:直接写每个标签中不一样的部分就行,比如路径和名称

{/*将NavLink进行封装,成为MyNavLink,通过props进行传参数,标签体内容props是特殊的一个属性,叫做children */}
<MyNavLink to = "/about" >About</MyNavLink>

1.解决多级路径刷新页面样式丢失的问题

拿上面的案例来说:

这里面会有一个样式:

1612316916900.png

此时,加载该样式的路径为:

1612317786643.png

但是在写路由的时候,有的时候就会出现多级目录,

<MyNavLink to = "/cyk/about" >About</MyNavLink>

<Route path="/cyk/about"component={About}/>

这个时候就在刷新页面,就会出现问题:

样式因为路径问题加载失败,此时页面返回public下面的index.html

1612317880916.png 解决这个问题,有三个方法:

1.样式加载使用绝对位置

 <link href="/css/bootstrap.css" rel="stylesheet"> 

2.使用 %PUBLIC_URL%

 <link href="%PUBLIC_URL%/css/bootstrap.css" rel="stylesheet">

3.使用HashRouter

因为HashRouter会添加#,默认不会处理#后面的路径,所以也是可以解决的

2.模糊匹配和精准匹配

react默认是开启模糊匹配的。

比如:

<MyNavLink to = "/home/a/b" >Home</MyNavLink>

此时该标签匹配的路由,分为三个部分 home a b;将会根据这个先后顺序匹配路由。

如下就可以匹配到相应的路由:

<Route path="/home"component={Home}/>

但是如果是下面这个就会失败,也就是说他是根据路径一级一级查询的,可以包含前面那一部分,但并不是只包含部分就可以。

<Route path="/a" component={Home}/>

当然也可以使用这个精确的匹配 exact={true}

如以下:这样就精确的匹配/home,则上面的/home/a/b就不行了

<Route exact={true}  path="/home" component={Home}/>
或者
<Route exact path="/home" component={Home}/>

3.初始化路由

在配置好路由,最开始打开页面的时候,应该是不会匹配到任意一个组件。这个时候页面就显得极其不合适,此时应该默认的匹配到一个组件。

RouterDef.gif 此时就需要使用Redirect进行默认匹配了。如下的代码就是默认匹配/home路径所到的组件

三.Switch

<Switch>
    <Route path="/about"component={About}/>
    {/* exact={true}:开启严格匹配的模式,路径必须一致 */}
    <Route   path="/home" component={Home}/>
    {/* Redirect:如果上面的都没有匹配到,就匹配到这个路径下面 */}
    <Redirect  to = "/home"/>
</Switch>

就可以做到如下的效果:

1.嵌套路由

简单来说就是在一个路由组件中又使用了一个路由,就形成了嵌套路由。

举个例子来说:

我们在home这个路由组件中又添加两个组件:

  • App.jsx:
<Route   path="/home" component={Home}/>
  • Home.jsx:
<div>
    <ul className="nav nav-tabs">
    <li>
    	<MyNavLink to = "/home/news">News</MyNavLink>
    </li>
    <li>
    	<MyNavLink  to = "/home/message">Message</MyNavLink>
    </li>
    </ul>
    
    <Switch>
        <Route path = "/home/news" component={News} />
        <Route path = "/home/message" component={Message} />
        <Redirect to="/home/message"/>
    </Switch>
</div>

react中路由的注册是有顺序的,因此在匹配的时候也是按照这个顺序进行的,因此会先匹配父组件中的路由

比如上面的 /home/news的路由处理过程:

  1. 因为父组件home的路由是先注册的,因此在匹配的时候先去找home的路由,也就是根据/home/news先模糊匹配到/home

  2. 在去Home组件里面去匹配相应的路由,从而找到了/home/news进行匹配,因此找到了News组件。

但是如果开启精确匹配,就会在第一步的时候卡住,这个时候就走不下去了。因此不要轻易的使用精确匹配

四.向路由组件传递参数数据

4.1 效果

在这里插入图片描述

4.2 具体方法

  • 方法1. params参数
  1. 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
  2. 注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
  3. 接收参数:this.props.match.params
  • Message/index.jsx
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom';
import Detail from './Detail';

export default class Message extends Component {
  state = {
    messageArr: [
      { id: '01', title: '消息1' },
      { id: '02', title: '消息2' },
      { id: '03', title: '消息3' },
    ]
  }
  render() {
    const { messageArr } = this.state;
    return (
      <div>
        <ul>
          {
            messageArr.map((msgObj) => {
              return (
                <li key={msgObj.id}>
                  {/* 向路由组件传递params参数 */}
                  <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
                </li>
              )
            })
          }
        </ul>
        <hr />
        {/* 声明接收params参数 */}
        <Route path="/home/message/detail/:id/:title" component={Detail}/>
      </div>
    )
  }
}
  • Detail/index.jsx

在这里插入图片描述

import React, { Component } from 'react'

export default class Detail extends Component {
  state = {
    detailData : [
      { id: '01', content: '你好啊' },
      { id: '02', content: '还不错鸭' },
      { id: '03', content: '显示我吧' }
    ]
  }
  render() {
    console.log(this.props)
    // 接收params参数
    const { id, title } = this.props.match.params
    const findResult= this.state.detailData.find((dataObj) => {
      return dataObj.id === id
    })
    return (
      <div>
        <ul>
          <li>ID: {id }</li>
          <li>Title: {title }</li>
          <li>Content: { findResult.content}</li>
        </ul>
      </div>
    )
  }
}

方法2. search参数

  1. 路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
  2. 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
  3. 接收参数:this.props.location.search
  4. 备注:获取到的searchurlencoded编码字符串,需要借助querystring解析
import qs from 'querystring'
let obj = {name:'tom', age:18}
console.log(qs.stringify(obj)) // name=tom&age=18
let str = 'carName=Benz&price=199'
console.log(qs.parse(str)) // {carName: 'Benz', price: 199}
复制代码

方法3. state参数

  1. 路由链接(携带参数):<Link to={{ pathname:'/demo/test', state:{name:'tom',age:18} }}>详情</Link>
  2. 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
  3. 接收参数:this.props.location.state
  4. 备注:刷新也可以保留住参数【history对象记录着在】

代码

  • Message/index.jsx
export default class Message extends Component {
  render() {
    const {messageArr} = this.state
    return (
      <div>
        <ul>
          {
            messageArr.map((msgObj)=>{
              return (
                <li key={msgObj.id}>

                  {/* 向路由组件传递params参数 */}
                  {/* <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> */}

                  {/* 向路由组件传递search参数 */}
                  {/* <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> */}

                  {/* 向路由组件传递state参数 */}
                  <Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>

                </li>
              )
            })
          }
        </ul>
        <hr/>
        {/* 声明接收params参数 */}
        {/* <Route path="/home/message/detail/:id/:title" component={Detail}/> */}

        {/* search参数无需声明接收,正常注册路由即可 */}
        {/* <Route path="/home/message/detail" component={Detail}/> */}

        {/* state参数无需声明接收,正常注册路由即可 */}
        <Route path="/home/message/detail" component={Detail}/>
      </div>
    )
  }
}
  • Detail/index.jsx
import React, { Component } from 'react'
// import qs from 'querystring'

export default class Detail extends Component {
  render() {
    console.log(this.props);

    // 接收params参数
    // const {id,title} = this.props.match.params 

    // 接收search参数
    // const {search} = this.props.location
    // const {id,title} = qs.parse(search.slice(1))

    // 接收state参数
    const {id,title} = this.props.location.state || {}

    const findResult = DetailData.find((detailObj)=>{
      return detailObj.id === id
    }) || {}
    return (
      <ul>
        <li>ID:{id}</li>
        <li>TITLE:{title}</li>
        <li>CONTENT:{findResult.content}</li>
      </ul>
    )
  }
}
复制代码

6. 多种路由跳转方式

6.1 编程式路由导航

借助this.prosp.history对象上的API对操作路由跳转、前进、后退 - this.prosp.history.push() - this.prosp.history.replace() - this.prosp.history.goBack() - this.prosp.history.goForward() - this.prosp.history.go()

6.2 withRouter的使用

export default withRouter(Header)

withRouter可以加工一般组件,让一般组件具备路由组件所特有的API withRouter的返回值是一个新组件

7. 注意

1.BrowserRouter与HashRouter的区别

  1. 底层原理不一样: BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。 HashRouter使用的是URL的哈希值。
  2. path表现形式不一样 BrowserRouter的路径中没有#,例如:localhost:3000/demo/test HashRouter的路径包含#,例如:localhost:3000/#/demo/test
  3. 刷新后对路由state参数的影响 (1) BrowserRouter没有任何影响,因为state保存在history对象中。 (2) HashRouter刷新后会导致路由state参数的丢失!!!
  4. 备注:HashRouter可以用于解决一些路径错误相关的问题。