美文网首页
React 路由使用

React 路由使用

作者: 沐灵洛 | 来源:发表于2022-06-06 21:22 被阅读0次

    Router

    react-router-dom是一个处理页面跳转的三方库,在使用之前需要先安装到我们的项目中:

    # npm
    npm install react-router-dom@6
    #yarn
    yarn add react-router-dom@6
    

    简单路由

    使用路由时需要为组件指定一个路由的path,最终会以path为基础,进行页面的跳转。具体使用先看个简单示例,该示例比较简单就是两个Tab页面的来回切换。

    ///导入路由
    import {Link} from 'react-router-dom'
    function App() {
      return (
        <div>
          <h1>路由练习</h1>
          <nav>
            {/* link 页面展示时,是个a标签 */}
            <Link className ='link' to='/Tab1'> Tab1</Link> ///覆盖:渲染tab1组件
            <Link className = 'link' to='/Tab2'> Tab2 </Link> ///覆盖:渲染tab2组件
          </nav>
        </div>
        );
    }
    ///路由页面1
    export default function Tab1(params) {
        return (
            // 文档中,<main> 元素是唯一的,所以不能出现一个以上的 <main> 元素
            <main style={{ padding: "1rem 0" }}>
              <h2>我是Tab1</h2>
            </main>
          );
    }
    ///路由页面2
    export default function Tab2(params) {
        return (
            <main style={{ padding: "1rem 0" }}>
              <h2>我是Tab2</h2>
            </main>
          );
    }
    ///在index.js中配置路由
    import {BrowserRouter,Routes,Route} from 'react-router-dom'
    import Tab1 from './pages/Tab1.jsx'
    import Tab2 from './pages/Tab2.jsx'
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <BrowserRouter>
         <Routes>
           <Route path = '/' element = {<App/>} /> ///兄弟路由
           <Route path = '/Tab1' element = {<Tab1/>} />///兄弟路由
           <Route path = '/Tab2' element = {<Tab2/>} />///兄弟路由
         </Routes>
        </BrowserRouter>
      </React.StrictMode>
    );
    

    最终交互时,上述路由配置会出现彼此覆盖的情况,如下图:

    [图片上传失败...(image-e7e5d4-1654521601103)]

    为了保证App组件,不会在Tab1Tab2切换时被覆盖需要使用嵌套路由。

    嵌套路由

    嵌套路由,可以保证子路由共享父路由的界面而不会覆盖。为此React提供了Outlet组件,将其用于父组件中可以为子路由的元素占位,并最终渲染子路由的元素。

    Outlet渲染一个子路由的元素

    import {Link,Outlet} from 'react-router-dom'
    function App() {
      return (
        <div>
          <h1>路由练习</h1>
          <nav>
            {/* link 页面展示时,是个a标签 */}
            <Link className ='link' to='/Tab1'> Tab1</Link> 
            <Link className ='link' to='/Tab2'> Tab2 </Link>
          </nav>
          {/* 此时尚不能实现共享APP UI的同时渲染出 Tab1 和 Tab2,还需要使用 <Outlet/>
            保证父路由,在子路由交换时,仍然存在
          */}
          <Outlet/>
        </div>
        );
    }
    
    ///index.js
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <BrowserRouter>
          <Routes>
           <Route path='/' element={<App />} >
              {/* 孩子路由,url为:  / + 孩子的path */}
              <Route path='Tab1' element={<Tab1 />} /> 
              <Route path='Tab2' element={<Tab2 />} />
            </Route>
          </Routes>
        </BrowserRouter>
      </React.StrictMode>
    );
    

    最终效果如下图:

    [图片上传失败...(image-491108-1654521601104)]

    未匹配路由

    通过path='*',实现没有其他路由匹配时,对其进行匹配。

    root.render(
      <React.StrictMode>
        <BrowserRouter>
          <Routes>
          <Route path='/' element={<App />} >
              {/* 孩子路由,url为:  / + 孩子的path */}
              <Route path='Tab1' element={<Tab1 />} /> 
              <Route path='Tab2' element={<Tab2 />} />
              <Route path = '*' element={<p>
                未匹配到路由时,会跳转此处。
              </p>} />
            </Route>
          </Routes>
        </BrowserRouter>
      </React.StrictMode>
    );
    

    效果如图:

    [图片上传失败...(image-c2afa2-1654521601104)]

    路由传参数

    通过路由传递参数到组件中

    ///模拟数据
    const dataList = [
        {
            id:20220101,
            content:'笔记1'
        },
        {
            id:20220102,
            content:'笔记2'
        },
        {
            id:20220103,
            content:'笔记3'
        },
    ]
    export default function getTodoList(params) {
        return dataList
    }
    
    export function findTodoItem(params) {
        return dataList.find((value)=>value.id === params)
    }
    ///组件Tab2中定义列表
    export default function Tab2(params) {
        let list = getTodoList()
        return (
            <div>
                <ul>
                    {
                        list.map((item) => (
                            <li key={item.id}>
                               {/*子路由形如:'/Tab2/20220103' */}
                                <Link to={`/Tab2/${item.id}`}>{item.content}</Link>
                            </li>
                        ))
                    }
                </ul>
               {/*渲染一个子路由的元素*/}
                <Outlet />
            </div>
        );
    }
    
    ///注册列表项的子路由
    root.render(
      <React.StrictMode>
        <BrowserRouter>
          <Routes>
          <Route path='/' element={<App />} >
              {/* 孩子路由,url为:  / + 孩子的path */}
              <Route path='Tab1' element={<Tab1 />} /> 
              <Route path='Tab2' element={<Tab2 />} >
                <Route path=':itemId' element={<ItemDetail/>}/>
              </Route>
              <Route path = '*' element={<p>未匹配到该路由请先设置路由页面 </p>} />
            </Route>
          </Routes>
        </BrowserRouter>
      </React.StrictMode>
    );
    
    ///定义Tab2子组件 ItemDetail
    import { useParams } from 'react-router-dom'
    export function ItemDetail() {
        //点击每一项的链接,注意:URL 发生了变化,但新的组件尚未显示
        ///需要父组件中添加<Outlet>
        ///HOOK 获取路由中的参数,形如{itemId:'20220102'}
        let params = useParams()
        let content = findTodoItem(parseInt(params.itemId)).content
        return (
            <div>
                <h2>笔记详情</h2>
                <p>这是我于{params.itemId},记录的笔记他的内容为{content}</p>
            </div>
        )
    }
    

    索引路由

    当我们切换至Tab1再切回Tab2后,笔记详情页面将空白。

    可以通过索引路由填补空白,具体只需:

    root.render(
      <React.StrictMode>
        <BrowserRouter>
          <Routes>
          <Route path='/' element={<App />} >
              {/* 孩子路由,url为:  / + 孩子的path */}
              <Route path='Tab1' element={<Tab1 />} /> 
    
              <Route path='Tab2' element={<Tab2 />} >
                {/*索引路由 有index 无path*/}
                <Route index element={<p>请选择一个笔记查看它的详情 </p>}/>
                <Route path=':itemId' element={<ItemDetail/>}/>
              </Route>
              
              <Route path = '*' element={<p>未匹配到该路由请先设置路由页面 </p>} />
    
            </Route>
          </Routes>
        </BrowserRouter>
      </React.StrictMode>
    );
    

    当父路由匹配,但其他子路由都不匹配时,由索引路由匹配。索引路由是父路由的默认子路由。
    当用户尚未单击导航列表中的一项时,会呈现索引路由。

    活动链接

    Link功能一致,差异是可以设置点击后的颜色

    export default function Tab2(params) {
        let list = getTodoList()
        return (
            <div>
                <ul>
                    {
                        list.map((item) => (
                            <li key={item.id}>
                                {/* <Link to={`/Tab2/${item.id}`}>{item.content}</Link> */}
                                <NavLink style = { ({isActive})=> ({ color : isActive ? "red" : "" }) } to={`/Tab2/${item.id}`}> {item.content} </NavLink>
                            </li>
                        ))
                    }
                </ul>
                <Outlet />
            </div>
        );
    }
    

    搜索参数

    搜索参数类似于 URL 参数,形如/login?success=1

    export default function Tab2(params) {
        let list = getTodoList()
        ///和React.useState很像
        let [searchParams, setSearchParams] = useSearchParams();
        return (
            <div>
                {/* 搜索框: 随着输入设置搜索参数 */}
                <input type="text" onChange = { (event)=>{
                    let text = event.target.value
                    if (text) {
                        setSearchParams({text})
                    } else {
                        setSearchParams({})
                    }
                } } />
    
                <ul>
                    { list.filter((item)=>{
                        let txt = searchParams.get('text')
                        if (!txt) return true
                        return item.content.startsWith(txt)
                    })
                        .map((item) => (
                            <li key={item.id}>
                                {/* <Link to={`/Tab2/${item.id}`}>{item.content}</Link> */}
                                <NavLink style = { ({isActive})=> ({ color : isActive ? "red" : "" }) } to={`/Tab2/${item.id}`}> {item.content} </NavLink>
                            </li>
                        ))
                    }
                </ul>
    
                <Outlet />
            </div>
        );
    }
    

    随着我们输入apple, 路由的地址将变为/Tab2?text=apple触发路由重新呈现。

    当我们在输入框输入字符时,便会触发列表的过滤显示。

    自定义行为

    上述UI在交互过程中,当我们点击Tab1Tab2进行切换时,或者点击appleappet时,会出现输入框被清空,且列表不再被过滤的问题。

    react-router提供了useLocation方法,它返回浏览器显示的url信息。通过它可以获取浏览器url中的搜索参数,从而进行暂存,在具体组件内,可以通过useSearchParams获取到暂存的值。具体方式,通过自定义组件包装NavLinkLink来实现。

    ///1.自定义`QueryLink`组件
    import React, { Component } from 'react';
    import { useLocation,NavLink } from "react-router-dom";
    export default function QueryLink({to,...props}) {
      ///代表当前浏览器显示的url
      // location内容:{pathname: '/Tab2', search: '?text=ad', hash: '', state: null, key: 'dhvg8xme'}
      let location = useLocation()
      // to = '/Tab2?text=ad'
      return <NavLink to={to+location.search} {...props} />
    }
    //2. 替换 `tab1`、`tab2`的link or Navlink
    <QueryLink className ='link' to='/Tab1'> Tab1</QueryLink> 
    <QueryLink className ='link' to='/Tab2'> Tab2 </QueryLink>
    //3. 替换列表项的link or Navlink
     <li key={item.id}>
       <QueryLink style = { ({isActive})=> ({ color : isActive ? "red" : "" }) } to={`/Tab2/${item.id}`}> {item.content} </QueryLink>
     </li>
    

    useNavigate

    上述示例中,路由的切换采用Link或者NavLink,但当我们的页面元素不使用Link时,比如使用Button,此时便需要使用采用useNavigate。同上可以配合useLocation保存搜索字段。

    export function ItemDetail() {
        //点击每一项的链接,注意:URL 发生了变化,但新的组件尚未显示
        ///需要父组件中添加<Outlet>
        let params = useParams()
        let content = findTodoItem(parseInt(params.itemId)).content
        ///返回函数
        let navigate = useNavigate()
        ///获取搜索字段
        let location = useLocation()
        return (
            <div>
                <h2>笔记详情</h2>
                <p>这是我于{params.itemId}记录的笔记,内容为{content}</p>
                <button onClick = {
                    (e)=>{
                        deleteTodoItem(params.itemId)
                        navigate('/Tab2/'+location.search)
                    }
                }>
                    删除笔记
                </button>
            </div>
        )
    }
    // src/data.jsx
    export function deleteTodoItem(params) {
        dataList = dataList.filter((value)=>value.id !== parseInt(params)) 
    }
    

    参考资料

    https://reactrouter.com/docs/en/v6/getting-started/tutorial

    相关文章

      网友评论

          本文标题:React 路由使用

          本文链接:https://www.haomeiwen.com/subject/zkxsmrtx.html