React

路由导航

Router

  1. react-router
    • 核心模块
    • 包含React路由的大部分核心功能、路由匹配算法、核心组件、钩子
  2. react-router-dom
    • React应用中用于软件包
    • 包括react-router的全部内容
    • 并添加了一些特定于DOM的API
  3. react-rouer-native
    • 用于开发React-Native应用
    • 包括react-router的所有内容
    • 并添加了一些特定于React-Native的API

代码示例:

/* src/css/Tabber.css */

.myActive {
    color: #30363d;
}
// src/components/Redirect.jsx

// 自定义
import {useEffect} from "react";
import {useNavigate} from "react-router-dom";

export default function Redirect({to}) {  // 直接解构
    const navigate = useNavigate()

    useEffect(() => {
        navigate(to, {replace: true})  // replace:是否关掉上一个页面,重新打开指定页面
    })

    return null
}
// src/components/Tabber.jsx

// NavLink:具备Link的所有功能,相比Link增加了高亮显示
import React from "react";
import {NavLink} from "react-router-dom";
import '../css/Tabber.css'

export default function Tabber() {
    return (
        <footer>
            <ul>
                <li>  {/* 设置key与否取决于是否有虚拟DOM比对 */}
                    <NavLink to={'/films'} className={({isActive}) =>  // 直接解构
                        isActive ? 'myActive' : ''  // 取决于isActive是否为真(属性被添加到此DOM中)
                    }>首页
                    </NavLink>
                </li>
                <li>
                    <NavLink to={'/cinemas'} className={({isActive}) =>
                        isActive ? 'myActive' : ''
                    }>内容
                    </NavLink>
                </li>
                <li>
                    <NavLink to={'/centers'} className={({isActive}) =>
                        isActive ? 'myActive' : ''
                    }>我的
                    </NavLink>
                </li>
            </ul>
        </footer>
    )
}
// src/components/WithRouter.jsx

/*
import React from "react";
import {useNavigate, useParams, useLocation} from "react-router-dom";

// 自行封装WithRouter(应用于类组件兼容,高阶组件)
export default function WithRouter(Component) {
    return function (props) {
        const push = useNavigate()
        const match = useParams()
        const location = useLocation()
        return <Component {...props} history={{push, match, location}}/>
    }
}
*/
import React from "react";

export default function WithRouter() {
    return (
        <div>
            FilmItems_class
        </div>
    )
}
// src/router/index.jsx

import React from "react";
import {Route, Routes} from "react-router-dom";
// import Films from "../views/Films";
// import Centers from "../views/Centers";
// import Cinemas from "../views/Cinemas";
import Redirect from '../components/Redirect'
// import NotFound from '../views/NotFound'
// import Nowplaying from "../views/films/Nowplaying";
// import Comingsoon from "../views/films/Comingsoon";
// import Detail from "../views/Detail";
// import Login from "../views/Login";
import AuthComponent from "../views/AuthComponent";

const LazyLoad = (path) => {  // 自行封装路由懒加载(避免首次加载过慢)
    const Comp = React.lazy(() => import(`../${path}`))

    return (
        <React.Suspense fallback={<>加载ing...</>}>
            <Comp/>
        </React.Suspense>
    )
}

export default function MRouter() {
    // function isAuth() {
    //     return localStorage.getItem('token')  // 获取浏览器本地缓存的token键值(演示)
    // }

    return (
        <Routes>
            {/* <Route index element={<Films/>}/>  index:指定默认子路由(以父路径为准) */}
            <Route path={'/films'} element={LazyLoad('views/Films')}>
                {/* <Route index element={<Nowplaying/>}/>  默认子路由'/films/'(嵌套在父组件中,也等同于'') */}
                <Route path={''} element={<Redirect to={'/films/nowplaying'}/>}/>
                {/* 省略'/films/':相对于父路径 */}
                <Route path={'nowplaying'} element={LazyLoad('views/films/Nowplaying')}/>
                <Route path={'comingsoon'} element={LazyLoad('views/films/Comingsoon')}/>
            </Route>
            <Route path={'/centers'} element={<AuthComponent>{LazyLoad('views/Centers')}</AuthComponent>}/>
            <Route path={'/login'} element={LazyLoad('views/Login')}/>
            <Route path={'/detail/:id'} element={LazyLoad('views/Detail')}/>  {/* 动态路由 */}
            <Route path={'/cinemas'} element={LazyLoad('views/Cinemas')}/>
            <Route path={'/'} element={<Redirect to={'/films'}/>}/>
            <Route path={'*'} element={LazyLoad('views/NotFound')}/>
            {/* <Route path={'/'} element={<Navigate to={'/films'}/>}/>  匹配'/'时重定向到'/films' */}
            {/* <Route path={'*'} element={<Navigate to={'/films'}/>}/>  '*':万能匹配(如果前面的路由都没匹配上的话) */}
        </Routes>
    )
}
// src/router/index_useRoutes.jsx

import React from "react";
import {useRoutes} from "react-router-dom";
import Redirect from '../components/Redirect'
import AuthComponent from "../views/AuthComponent";

const LazyLoad = (path) => {
    const Comp = React.lazy(() => import(`../${path}`))

    return (
        <React.Suspense fallback={<>加载ing...</>}>
            <Comp/>
        </React.Suspense>
    )
}

export default function MRouter() {
    const element = useRoutes([
        {
            path: '/films',
            element: LazyLoad('views/Films'),
            children: [
                {
                    path: '',
                    element: <Redirect to={'/films/nowplaying'}/>
                },
                {
                    path: 'nowplaying',
                    element: LazyLoad('views/films/Nowplaying')
                },
                {
                    path: 'comingsoon',
                    element: LazyLoad('views/films/Comingsoon')
                }
            ]
        },
        {
            path: '/centers',
            element:
                <AuthComponent>
                    {LazyLoad('views/Centers')}
                </AuthComponent>
        },
        {
            path: '/login',
            element: LazyLoad('views/Login')
        },
        {
            path: '/cinemas',
            element: LazyLoad('views/Cinemas')
        },
        {
            path: '/detail/:id',
            element: LazyLoad('views/Detail')
        },
        {
            path: '/',
            element: <Redirect to={'/films'}/>
        },
        {
            path: '*',
            element: LazyLoad('views/NotFound')
        }
    ])

    return (
        element
    )
}
// src/views/films/Comingsoon.jsx

import React from "react";

export default function Comingsoon() {
    return (
        <div>
            Comingsoon
        </div>
    )
}
// src/films/FilmItems.jsx

import React from "react";
import {useNavigate} from "react-router-dom";

export default function FilmItems(item) {  // props
    const navigate = useNavigate()  // 可以在任何路由包裹的组件内部使用

    const handleChangePage = (Id) => {  // 编程式导航
        // 方式01:query(URLSearch)传参,/detail?id=1024
        // 方式02:路由传参(/detail/1024),navigate(`/detail?id=${Id}`)
        navigate(`/detail/${Id}`)
    }

    return (
        <li onClick={() => handleChangePage(item.filmId)}>
            {item.name}
        </li>
    )
}
// src/FilmItems_class.jsx

/*
import React, {Component} from "react";
import WithRouter from "../../components/WithRouter";

class FilmItems_class extends Component {
    handleClick(id) {
        console.log(this.props.history)
        this.props.history.push(`/detail/${id}`)  // 跳转页面
        this.props.history.match()  // 获取参数
        this.props.history.location()  // 获取当前路由
    }

    render() {
        return (
            <li onClick={() => this.handleClick(this.props.filmId)}>
                {
                    this.props.name
                }
            </li>
        )
    }
}

export default WithRouter(FilmItems_class)
*/
import React from "react";

export default function FilmItems_class() {
    return (
        <div>
            FilmItems_class
        </div>
    )
}
// src/views/films/Nowplaying.jsx

import {useEffect, useState} from "react";
import axios from "axios";
import FilmItems from "./FilmItems";

export default function Nowplaying() {
    const [list, setList] = useState([])

    useEffect(() => {
        axios.get(`/test.json`).then((res) => {
            console.log(res.data.films)
            setList(res.data.films)
        }).catch(err => console.log(err))
    }, [])

    return (
        <div>
            <ul>
                {
                    list.map(item =>
                        <FilmItems key={item.filmId} {...item}/>
                    )
                }
            </ul>
        </div>
    )
}
// src/views/AuthComponent.jsx

import React from "react";
import Redirect from "../components/Redirect";

// 自定义路由拦截组件
export default function AuthComponent({children}) {  // 接收的是子组件
    // 演示
    const isLogin = localStorage.getItem('token')

    return isLogin ? children : <Redirect to={'/login'}/>
}
// src/views/Centers.jsx

import React from "react";

export default function Centers() {
    return (
        <div>
            Center
        </div>
    )
}
// src/views/Cinemas.jsx

import React from "react";

export default function Cinemas() {
    return (
        <div>
            Cinema
        </div>
    )
}
// src/views/Detail.jsx

import React from "react";
import {useNavigate, useParams} from "react-router-dom";

export default function Detail() {
    const params = useParams()
    console.log(params)  // 拿到一个对象(键:设置的占位符对应的名称)
    const navigate = useNavigate()

    return (
        <div>
            <button onClick={() => {
                navigate(`/detail/${1024}`)  // 演示
            }}>随机页面</button>
        </div>
    )
}
// src/views/Detail_Search.jsx

// window.location.href(获取当前页面的完整路径)
import React from "react";
import {useSearchParams} from "react-router-dom";

export default function Detail_Search() {
    const [searchParams,setSearchParams] = useSearchParams()
    console.log(searchParams.get('id'))  // 获取参数键值
    /*
    console.log(searchParams.has('id'))  // 判断参数是否存在

    useEffect(() => {
        setSearchParams({'id': 1024})  // 改变路由
    },[setSearchParams])
    */

    return (
        <div>
            <button onClick={() => {
                setSearchParams({id: 1024})  // 演示
            }}>随机页面</button>
        </div>
    )
}
// src/views/Films.jsx

import React from "react";
import {Outlet} from "react-router-dom";

export default function Films() {
    return (
        <div>
            <div style={{height: '200px', background: 'gray'}}>轮播</div>
            <Outlet/>  {/* 嵌套路由容器 */}
        </div>
    )
}
// src/views/Login.jsx

import React, {useEffect} from "react";
import {useNavigate} from "react-router-dom";

export default function Login() {
    const navigate = useNavigate()

    useEffect(() => {
        localStorage.getItem('token') && navigate('/centers')
    }, [navigate])

    return (
        <div>
            {/* 演示 */}
            <input type="text"/>
            <input type="password"/>
            <button onClick={() => {
                localStorage.setItem('token', 'HQSY')
                navigate('/centers')
            }}>登录
            </button>
        </div>
    )
}
// src/views/NotFound.jsx

import React from "react";

export default function NotFound() {
    return (
        <div>
            <h1>404</h1>
        </div>
    )
}
// src/App.jsx

import React from "react";
import {BrowserRouter} from "react-router-dom";
import MRouter from "./router";
import Tabber from "./components/Tabber";

/*
1. HashRouter(带'#')
2. BrowserRouter(不带'#')
3. 区别
    · HashRouter不会与后端路由冲突(后端识别不了'#')
    · BrowserRouter会与后端路由冲突(因为会先请求后端的路由,如果后端处理不了对应的路径,则会返回404状态码)
4. 解决
    · 路由功能全部交给前端或后端其中一个
    · 后端处理不了的路径移交给前端路由
*/
export default function App() {
    return (
        <BrowserRouter>
            <MRouter/>
            <Tabber/>
        </BrowserRouter>
    )
}
// src/index.jsx

import React from "react";
import {createRoot} from "react-dom/client";
import App from "./App";

createRoot(document.getElementById('root')).render(<App/>)