devStandard/docs/learning/7-react/2-React Router(V5).md
2025-03-29 14:35:49 +08:00

9.2 KiB
Executable File

React Router(V5)

将url地址和组件进行映射

React Router: Declarative Routing for React.js

安装

# 安装v5
pnpm add react-router-dom@5

helloRouter

/**
 * react router 使用步骤
 * 1. 安装react-router-dom包
 * 2. 在main.js 中引入BrowserRouter组件
 * 3. 将BrowserRouter设置为根组件
 */
// @/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import {BrowserRouter as Router} from "react-router-dom";

ReactDOM.createRoot(document.getElementById('root')).render(
  <Router>
    <App />
  </Router>
)
  /**
   * 1. 引入Route组件
   * 2. 设置 path 和 component属性, 访问对应路径,就会挂载对应组件
   * 注意: 默认情况下Route并不是严格匹配
   * 举例: 访问'/about',除了About组件,还会挂载Home组件
   *       访问'/about/123',会挂载Home和About组件
   * 所以要设置exact, 严格匹配
   */
// @/App.jsx
import { Route } from 'react-router-dom/cjs/react-router-dom.min'
import Home from './components/Home'
import About from './components/About'

const App = ()=>{
  return (
    <div className="App">
      <Route exact path='/' component={Home}/>
      <Route exact path='/about' component={About}/>
    </div>
  )
}

export default App

使用react router时, 不能使用a标签超链接切换路径, 因为会重新加载页面, 向服务器重新发请求

使用 Link组件或者NavLink组件

//@/components/Menu.jsx
import { Link } from 'react-router-dom'

const Menu = () => {
  return (
    <ul>
      <li>
        <Link to="/">主页</Link>
      </li>
      <li>
        <Link to="/about">关于</Link>
      </li>
    </ul>
  )
}

export default Menu
/**
 * NavLink和Link类似,但可以指定激活路径的链接样式
 * 同样需要设置严格模式 exact
 * activeClassName 为激活的链接添加类名
 * activeStyle 为激活的标签添加样式
 */
//@/components/Menu.jsx
import { NavLink } from 'react-router-dom'
import classes from './Menu.module.css'

const Menu = () => {
  return (
    <ul>
      <li>
        <NavLink exact activeClassName={classes.active} to="/">主页</NavLink>
      </li>
      <li>
        <NavLink exact activeStyle={{color:'green'}} to="/about">关于</NavLink>

      </li>
    </ul>
  )
}

export default Menu

/* @/src/components/Menu.module.css */
a:link,
a:visited {
  color: black;
  text-decoration:none;
}

a:hover {
  color: skyblue;
}

a.active {
  color:green;
}

两种路由模式 BrowserRouter HashRouter

/**
* 1. 地址栏: hash多了个#号, http://127.0.0.1:5173/#/   http://127.0.0.1:5173/#/about
* 2. 开发过程: 没有区别
* 3. 部署过程: 
*    BrowserRouter是直接通过url地址进行跳转
*       直接通过链接进行跳转,没有经过服务器,没有问题
*       直接在地址栏输入或者刷新,会向服务器发送请求,404
*       解决方案: 1. 换成HashRouter 2.需要修改服务器,将所有请求都转发到index.html
*    HashRouter是url中的hash来对地址进行匹配, 服务器不会判断hash
*/
# 修改nginx配置
server {
  location / {
    root html;
    #index index.html;
    try_files $url /index.html;
  }
}

路由传参

component挂载组件

/**
 * component直接传组件的类,会自动创建组件并传递参数
 * match: 匹配的信息
 *    isExact 检查路径是否完全匹配
 *    params 请求的参数 <Route path='/student/:id' component={student} />
 * location: 地址信息
 *    pathname 当前路径
 *    search 搜索参数
 * history: 历史记录的信息, 主要用来控制页面的跳转
 *    goForward() 前进
 *    goBack() 后退
 *    go(n) 参数为数字
 *    push() 跳转,有历史记录, 需要一个location作为参数
 *    replace() 替换,没有历史记录
*/
//@/components/Student.jsx
const Student = (props)=>{

  console.log(props)

  const clickHandler = ()=>{
    props.history.push({
      pathname:'/student/2',
      state: {
        // 在props.location.state可以找到
        name: 'test'
      }
    })
  }
  const clickHandler2 = ()=>{
    props.history.replace({
      pathname:'/student/3'
    })
  }
  return (
    <div>
      <p>{JSON.stringify(props.match.params)}</p>
      <p>{JSON.stringify(props.location.state)}</p>
      <button onClick={clickHandler}>用push方法跳转学生2</button>
      <br/>
      <button onClick={clickHandler2}>用replace方法跳转学生3</button>
    </div>
  )
}
export default Student 
// src/App.jsx
import { Route } from 'react-router-dom/cjs/react-router-dom.min'
import Home from './components/Home'
import About from './components/About'
import Menu from './components/Menu'
import Student from './components/Student'

const App = ()=>{

  return (
    <div className="App">
      <Menu />
      <hr />
      <Route exact path='/' component={Home}/>
      <Route exact path='/about' component={About}/>
      <Route path='/student/:id' component={Student} />
    </div>
  )
}

export default App
// src/components/Menu.jsx
import { NavLink } from 'react-router-dom'
import classes from './Menu.module.css'

const Menu = () => {
  return (
    <ul>
      <li>
        <NavLink exact activeClassName={classes.active} to="/">主页</NavLink>
      </li>
      <li>
        <NavLink exact activeStyle={{color:'green'}} to="/student/1">学生1</NavLink>
      </li>
      <li>
        <NavLink exact activeStyle={{color:'green'}} to="/about">关于</NavLink>
      </li>
    </ul>
  )
}

export default Menu

render挂载组件

// 这样挂载组件有缺点, 传入的是类,没办法自定义属性或者传递参数
// 解决方法 用render或者children
<Route path='/student/:id' component={Student} />
<Route exact path='/student/:id' render={(routerProps)=>{
  console.log(routerProps)
  return <Student {...routerProps} />
}}/>

children挂载组件

// src/App.jsx
<Route path='/student/:id' children={<Student />} />
// 或者 <Route path='/student/:id' ><Student /></Route>
// src/components/Student.jsx
import { useHistory, useRouteMatch,useLocation,useParams } from 'react-router-dom'

const Student = ()=>{
  /**
   * 用钩子函数获取路由
  */

  const match = useRouteMatch()
  const location = useLocation()
  const history = useHistory()
  const params = useParams()
  
  const clickHandler = ()=>{
    history.push({
      pathname:'/student/2',
      state: {
        // 在props.location.state可以找到
        name: 'test'
      }
    })
  }
  const clickHandler2 = ()=>{
    history.replace({
      pathname:'/student/3'
    })
  }
  return (
    <div>
      <p>{JSON.stringify(params)}</p>
      <p>{JSON.stringify(location.state)}</p>
      <button onClick={clickHandler}>用push方法跳转学生2</button>
      <br/>
      <button onClick={clickHandler2}>用replace方法跳转学生3</button>
    </div>
  )
}
export default Student

路由嵌套

访问/about显示About组件, 访问/about/hello, 既显示About组件, 也显示hello组件

// src/App.jsx
// ...
// exact 去掉
  <Route path='/about'>
    <About />
    <Route path='/about/hello'>
      <Hello />
    </Route>
  </Route>
//...

或者在<About>组件中添加

// src/components/About.jsx
// ...
// exact 去掉
  <Route path='/about/hello'>
      <Hello />
  </Route>
// ...

为了避免要手动补全路径, 使用钩子获取当前组件path

// src/components/About.jsx
import {useRouteMatch,Route} from 'react-router-dom'
import Hello from './Hello'

const About = () => {
  const {path} = useRouteMatch()
  return (
    <div>
      <p>about页面</p>
      <Route path={`${path}/hello`} >
          <Hello />
      </Route>
    </div>
  )
}
export default About

prompt提示组件

离开当前路由时提示

import  { useState } from 'react'
import { Prompt } from 'react-router-dom'

const Form = ()=>{
  const [input, setInput] = useState('')
  const [showPrompt, setShowPrompt] = useState(false)
  const changeHandler = (e)=>{
    const format = e.target.value.trim()
    setInput(format)
    setShowPrompt(!!format)
  }

  return (
    <div>
      <h3>表单</h3>
      <Prompt
        when={showPrompt} {}
        message={'将要离开页面,数据不会保留!确定吗?'}
      />
      <input type="text" value={input} onChange={changeHandler}/>
    </div>
  )
}

export default Form

Redirect重定向组件

/**
 * Redirect 重定向组件
 *   to 跳转的url,可以是string,也可以是location
 *   默认是通过replace跳转
 *   添加push属性,改为push跳转
 */
<Route exact path="/">
  {loggedIn ? <Redirect to="/dashboard" /> : <PublicHomePage />}
</Route>
<Redirect push to="/somewhere/else" />

Switch组件

常用于权限判断路由权限

Redirect一起用, Redirect 要添加from

import { Redirect, Route, Switch } from "react-router";

let routes = (
  <Switch>
    <Route exact path="/">
      <Home />
    </Route>

    <Route path="/users">
      <Users />
    </Route>
    <Redirect from="/accounts" to="/users" />

    <Route>
      <NoMatch />
    </Route>
  </Switch>
);