even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1、router的安装

在react中,router的使用需要依赖react-router-dom

npm i react-router-dom

 2、router的使用

路由有分两种模式 

  • hash 模式: HashRouter
  • history 模式: BrowserRouter

入门案例

使用router时,在根目录需要用HashRouter或者是BrowserRouter包一层

import App from './App';
import ReactDOM from 'react-dom/client';
import { HashRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement,
);

root.render(
  <HashRouter>
    <App />
  </HashRouter>,
);

使用例子

import { FC, ReactElement } from 'react';
import { Routes, Route, NavLink } from 'react-router-dom';

const Home: FC = (): ReactElement => {
  return <div>this is Home</div>;
};

const About: FC = (): ReactElement => {
  return <div>this is About</div>;
};

const App: FC = (): ReactElement => {
  return (
    <div>
      <div className="header">
        <h1>this is App</h1>
        <div>
          {/*
           *  Link的其他属性
           *  to: 表示需要跳转到的目标路径
           *  state: any  表示在history中可以传入的state
           *  replace: boolean 表示是否替换,非push
           *  reloadDocument: boolean 表示是否需要重载页面
           */}
          {/* <Link to="/home">首页</Link>
          <Link to="/about">关于</Link>
          <Link to="/test">测试</Link> */}

          {/* 通常会使用NavLink, 因为当被选中时,会在对应的地方添加active的类, 也可以使用style,如下 */}
          <NavLink
            to="/home"
            style={({ isActive }) => ({ color: isActive ? 'red' : '' })}
          >
            首页
          </NavLink>
          {/* 也可以自定义className */}
          <NavLink
            to="/about"
            className={({ isActive }) => (isActive ? 'clsName' : '')}
          >
            关于
          </NavLink>
          <NavLink to="/test">测试</NavLink>
        </div>
      </div>
      <div className="content">
        <Routes>
          {/* 重定向到首页 */}
          <Route path="/" element={<Navigate to="/home"></Navigate>}></Route>
          <Route path="/home" element={<Home />}></Route>
          <Route path="/about" element={<About />}></Route>
          <Route path="/test" element={<div>this is test</div>}></Route>
          {/* 匹配到没有设置的路由 */}
          <Route path="*" element={<div>NotFound</div>}></Route>
        </Routes>
      </div>
    </div>
  );
};

export default App;

router的早期版本的写法如下

import React from 'react';
import ReactDom from 'react-dom'
import {HashRouter, Link, Route, Redirect} from "react-router-dom"
import Home from './components/Home'
import User from './components/User'

/**
 * HashRouter表示使用的是HashRouter即Hash模式, history模式使用的是BrowserRouter
 * Route相当于vueRouter里的routerView, 注意这里用的是route并非router
 * Link相当于vue里的routerLink,相当于点击跳转的标签
 * Redirect重定向组件,path表示匹配的路径,to表示重定向需要跳转的路径
 *
 */
const App = () => {
    return <div>
        <h1>this is App</h1>
        <Link to='/home'>首页</Link>
        <Link to='/user'>用户</Link>
        {/*相当于当路径匹配到地址/home的时候,那么就展示home这个组件,匹配到/user的时候就展示 User组件*/}
        {/*注意:当组件被route使用后,则会在该组件的props注入一些路由的属性,如果location, history等等*/}
        {/*<Redirect path='/' to='/home'></Redirect>重定向的方法*/}
        <Route path='/' exact render={() => <div>this is main</div>}/>{/*注意:这里的exact是表示精确匹配*/}
        <Route path='/home' component={Home} />
        {/*<Route path='/user' component={User}/>*/}
        {/*<Route path='/user' render={() => <div>this is user, render</div>}/>*/}
        <Route path='/user' render={() => <User/>}/>
        {/*Route在使用的过程中,如果需要有逻辑的判断,可以用另外一种写法*/}
    </div>
}

//注意:在使用路由的时候,根组件需要用HashRouter或者BrowserRouter进行包裹,这样子组件内就可以愉快的使用路由了
ReactDom.render(<HashRouter><App/></HashRouter>, window.root)
// ReactDom.render(<BrowserRouter><App/></BrowserRouter>, window.root)

注意: 在react的机制中,如果路由匹配上多个,那么就会所有匹配上的全部显示出来,这点区别于vue,当vue匹配上一个路由后则不会往下匹配,这个时候react就需要switch组件来做处理

import React from 'react';
import ReactDom from 'react-dom'
import {HashRouter, Link, Route, Switch} from "react-router-dom"
import Home from './components/Home'
import User from './components/User'

const App = () => {
    return <div>
        <h1>this is App</h1>
        <Link to='/home'>首页</Link>
        <Link to='/user'>用户</Link>
        <Switch>
            <Route path='/' render={() => <div>this is main</div>}/>
            <Route path='/home' component={Home} />
            <Route path='/home' render={() => <div>hello home</div>} />
            <Route path='/user' render={() => <User/>}/>
        </Switch>
    </div>
}

ReactDom.render(<HashRouter><App/></HashRouter>, window.root)

注意:Switch组件中间只能放Route组件,不能放其他组件,配置上Switch组件后匹配上一个路由后,就不会再匹配其他路由了

import React from 'react';
import ReactDom from 'react-dom'
import {HashRouter, Link, Route, Switch, Redirect} from "react-router-dom"
import Home from './components/Home'
import User from './components/User'

const App = () => {
    return <div>
        <h1>this is App</h1>
        <Link to='/home'>首页</Link>
        <Link to='/user'>用户</Link>
        <Switch>
            {/*如果这种重定向放在switch会把所有路由拦截,因为/可以匹配所有,所以要加上exact*/}
            <Redirect path='/' exact to='/home'/>
            <Route path='/home' component={Home} />
            <Route path='/user' render={() => <User/>}/>
            {/*匹配未能匹配到的路由则用以下方法,但是注意这个页面通常是配置在路由的最后,也可以不配置path*/}
            <Route path='/*' render={() => <div>NOT FOUND</div>} />
        </Switch>
    </div>
}

ReactDom.render(<HashRouter><App/></HashRouter>, window.root)

 NavLink组件的使用

为了实现标签的高亮显示, 这个时候可以使用NavLink来替代Link实现高亮显示,在路径匹配上后,也就是激活后,所对应的标签会被默认的加上active类,但是可以使用activeStyle或activeClassName进行定义样式或类名

<NavLink to='/home'>首页</NavLink>
<NavLink to='/user' activeClassName='yfbill'>用户</NavLink>

 3、嵌套子路由的实现

子路由的实现

import { FC, ReactElement } from 'react';
import { Routes, Route, Link, Navigate, Outlet } from 'react-router-dom';

const Home: FC = (): ReactElement => {
  return (
    <div>
      <h1>this is Home</h1>
      <div>
        <Link to="/home/first">first</Link>
        <Link to="/home/second">second</Link>
      </div>
      <div>
        {/* 这个是占位组件,表示当前的子组件的内容展示的位置 */}
        <Outlet />
      </div>
    </div>
  );
};

const About: FC = (): ReactElement => {
  return <div>this is About</div>;
};

const App: FC = (): ReactElement => {
  return (
    <div>
      <div className="header">
        <h1>this is App</h1>
        <div>
          <Link to="/home">首页</Link>
          <Link to="/about">关于</Link>
        </div>
      </div>
      <div className="content">
        <Routes>
          {/* 重定向到首页 */}
          <Route path="/" element={<Navigate to="/home"></Navigate>}></Route>
          <Route path="/home" element={<Home />}>
            <Route
              path="/home/first"
              element={<div>this is home first</div>}
            ></Route>
            <Route
              path="/home/second"
              element={<div>this is home second</div>}
            ></Route>
          </Route>
          <Route path="/about" element={<About />}></Route>
        </Routes>
      </div>
    </div>
  );
};

export default App;

早期版本的子路由的实现,承接上面的例子

import {NavLink, Route, Switch} from 'react-router-dom'
import Redirect from "react-router-dom/es/Redirect";
const User = () => {
    return <div>
        <h3>this is user</h3>
        <NavLink to='/user/list'>用户列表</NavLink>
        <NavLink to='/user/edit'>用户编辑</NavLink>
        <Switch>
            <Redirect path='/user' to='/user/list' exact />{/*表示当用户访问/user的时候,自动跳转到/user/list页面*/}
            <Route path='/user/list' render={() => <div>这个是用户列表页面</div>}/>
            <Route path='/user/edit' render={() => <div>这个是用户编辑页面</div>}/>
        </Switch>
    </div>
}

export default User

 4、手动路由的跳转

在常规开发过程中,经常会使用一个行为跳转到指定的路由中,那么可以使用以下的方法进行手动的跳转

import { FC, ReactElement } from 'react';
import { Routes, Route, Navigate, useNavigate } from 'react-router-dom';

const Home: FC = (): ReactElement => {
  return <div>this is Home</div>;
};

const About: FC = (): ReactElement => {
  return <div>this is About</div>;
};

const App: FC = (): ReactElement => {
  const navigate = useNavigate();
  return (
    <div>
      <div className="header">
        <h1>this is App</h1>
        <div>
          <button onClick={() => navigate('/home')}>首页</button>
          <button onClick={() => navigate('/about')}>关于</button>
        </div>
      </div>
      <div className="content">
        <Routes>
          {/* 重定向到首页 */}
          <Route path="/" element={<Navigate to="/home"></Navigate>}></Route>
          <Route path="/home" element={<Home />}></Route>
          <Route path="/about" element={<About />}></Route>
        </Routes>
      </div>
    </div>
  );
};

export default App;

但是useNavigate是一个hook在类组件中不能使用,这个时候就需要封装高阶组件进行操作(以下封装了useNavigate, useParams, useSearchParams)等方法

import { FC, ReactElement, PureComponent, ComponentType } from 'react';
import {
  Routes,
  Route,
  Navigate,
  useNavigate,
  useParams,
  useLocation,
  NavigateFunction,
  Params,
  useSearchParams,
} from 'react-router-dom';

interface IRouter {
  router: {
    navigate: NavigateFunction;
    params: Params<string>;
    query: Record<string, string>;
  };
}
const withRouter = <T,>(
  WrapComponent: ComponentType<T & IRouter>,
): ComponentType<T> => {
  return (props: T): ReactElement => {
    const navigate = useNavigate();

    // 获取路由上的params, 如/home/:id,那么就可以获取id的值
    const params = useParams<Params<string>>();

    // 获取路由上的query,如/home?name=aaa&age=12, 但是这个方法是没有解构query里的参数, 可以使用该方法获取当前的路径等信息
    const locations = useLocation();
    console.log(locations);

    // 获取路由上的query,如/home?name=aaa&age=12, 获取name与age的值
    const [searchPrams] = useSearchParams();
    const query = Object.fromEntries(searchPrams);

    const router = { navigate, params, query };
    return <WrapComponent {...props} router={router} />;
  };
};

const Home = withRouter(
  class HomeInner extends PureComponent<IRouter> {
    private routerTo(path: string): void {
      const { navigate } = this.props.router;
      navigate(path);
    }
    public render(): ReactElement {
      return (
        <div>
          <h3>this is home</h3>
          <button onClick={() => this.routerTo('/about')}>跳转到about</button>
        </div>
      );
    }
  },
);

const About: FC = (): ReactElement => {
  return <div>this is About</div>;
};

const App: FC = (): ReactElement => {
  const navigate = useNavigate();
  return (
    <div>
      <div className="header">
        <h1>this is App</h1>
        <div>
          <button onClick={() => navigate('/home')}>首页</button>
          <button onClick={() => navigate('/about')}>关于</button>
        </div>
      </div>
      <div className="content">
        <Routes>
          {/* 重定向到首页 */}
          <Route path="/" element={<Navigate to="/home"></Navigate>}></Route>
          <Route path="/home" element={<Home />}></Route>
          <Route path="/about" element={<About />}></Route>
        </Routes>
      </div>
    </div>
  );
};

export default App;

 5、组件中监听路由的变化

import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function App() {
  const location = useLocation();

  useEffect(() => {
    console.log('Path changed:', location.pathname);
    // 在这里可以执行路径变化时的操作
  }, [location]);

  return (
    <div>
      {/* 页面内容 */}
    </div>
  );
}

export default App;

 6、实现最新版本的路由可配置化

 配置如下, 注意:这个文件应该为index.tsx

import React from 'react';
import { RouteObject, Navigate } from 'react-router-dom';
import Home from '@app/views/home';
import About from '@app/views/about';

const routes: RouteObject[] = [
  {
    path: '/',
    element: <Navigate to="/home" />,
  },
  {
    path: '/home',
    element: <Home />,
    children: [
      { path: '/home/first', element: <div>this is home first</div> },
      { path: '/home/second', element: <div>this is home second</div> },
    ],
  },
  {
    path: '/about',
    element: <About />,
  },
];

export default routes;

那么在调用routes的地方代码如下

import { FC, ReactElement, ComponentType, useEffect } from 'react';
import { Link, useRoutes } from 'react-router-dom';
import routes from './routers';

// 自定义的附加属性类型
interface CustomProps {
  customData?: {
    title: string;
  };
}

const App: FC = (): ReactElement => {
  return (
    <div>
      <div className="header">
        <h1>this is App</h1>
        <div>
          <Link to="/home">首页</Link>
          <Link to="/about">关于</Link>
          <Link to="/home/first">first</Link>
          <Link to="/home/second">second</Link>
        </div>
      </div>
      <div className="content">{useRoutes(routes)}</div>
    </div>
  );
};

export default App;

如果需要使用路由的懒加载,那么使用如下

import React from 'react';
import { RouteObject, Navigate } from 'react-router-dom';
import Home from '@app/views/home';

const About = React.lazy(() => import('@app/views/about'));

const routes: RouteObject[] = [
  {
    path: '/',
    element: <Navigate to="/home" />,
  },
  {
    path: '/home',
    element: <Home />,
    children: [
      { path: '/home/first', element: <div>this is home first</div> },
      { path: '/home/second', element: <div>this is home second</div> },
    ],
  },
  {
    path: '/about',
    element: <About />,
  },
];

export default routes;

这个时候就需要在外围index.tx入口处使用组件 Suspense组件,如下

import App from './App';
import ReactDOM from 'react-dom/client';
import { HashRouter } from 'react-router-dom';
import { Suspense } from 'react';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement,
);

root.render(
  <HashRouter>
    <Suspense fallback={<h2>正在加载中。。。。</h2>}>
      <App />
    </Suspense>
  </HashRouter>,
);

这个时候组件第一次加载时就会显示正在加载中。。。

7、手动实现5.x版本的路由的可配置化

在router5进行使用的时候,可以使用依赖 react-router-config进行配置,也可以使用以下方法进行封装

router
 ├── config
 │   └── mainMenu.ts              //具体的路由配置
 ├── IRouter.ts                   //路由配置的声明
 ├── pageComps.tsx                //页面组件管理文件
 ├── RouterComponent.tsx          //主体的路由生成器
 └── RouterWrapper.tsx            //动态路由转发

 RouterComponent文件

import {Switch, Route, Redirect} from 'react-router-dom'
import {IRouter} from "./IRouter";
import AllComponents from './pageComps'
import React, {ReactElement} from "react";
import RouterWrapper from './RouterWrapper';

const routerComponent: (routerList: Array<IRouter>) => React.FC = (routerList: Array<IRouter>) => {

    const createRouters = (router: IRouter): Array<React.ReactElement> => {
        let routerArr: Array<React.ReactElement> = []

        if(router.redirect) {   //如果有进行重新定位,那么主使用redirect
            routerArr.push(<Redirect key={router.path} exact path={router.path} to={router.redirect}/>)
        } else {
            routerArr.push( router.component && AllComponents[router.component]?
                <Route path={router.path}
                       key={router.path}
                       exact
                       render={props => { //根据登录情况,实现动态判断登录
                           return <RouterWrapper {...{...props, Comp: router.component && AllComponents[router.component], router}}/>
                }} />:
                <Redirect key={router.path} path={router.path} to='/404' />)   //如果找不到页面, 那么跳转到not found页面
        }

        if(router.children) { //如果有子元素,那么用递归实现子路由
            routerArr = routerArr.concat(...router.children.map((val: IRouter) => createRouters(val)))
        }

        return routerArr
    }

    const transformRouters = () => {
        return routerList.reduce((total: Array<ReactElement>, per: IRouter) => {
            let items: Array<React.ReactElement> = createRouters(per)
            return total.concat(items)
        }, [])
    }

    //注意:如果在最根目录使用该方法,那么需要在要根目录使用HashRouter或BrowserRouter进行包裹, 否则会报错
    return () => {
        return <Switch>
                {transformRouters()}
            </Switch>
    }
}

export default routerComponent

RouterWrapper文件

import React, {PropsWithChildren} from "react";
import DocumentTitle from "../components/DocumentTitle"; //自己封装的改变document.title的组件
import queryString from 'query-string'


const RouterWrapper: React.FC<any> = (props: PropsWithChildren<any>) => {
    let { Comp, router, ...others } = props;

    const queryReg = /\?\S*/g;

    const matchQuery = (reg: RegExp) => {
        const queryParams = others.location.search.match(reg);
        return queryParams ? queryParams[0] : '{}';
    };

    others.match.query = queryString.parse(matchQuery(queryReg));  //扩展query方法

    return <DocumentTitle title={router.meta && router.meta.title}>
        <Comp {...others}/>
    </DocumentTitle>
}


export default RouterWrapper

DocumentTitle组件

import {PropsWithChildren, useEffect} from "react";
import config from '../config'

export interface IDocumentTitle {
    title?: string
}

const DocumentTitle: React.FC<IDocumentTitle> = (props: PropsWithChildren<IDocumentTitle>) => {
    let {title} = props
    useEffect(() => {
        document.title = config.defaultTitle + (title? `-${title}`: '')
    }, [title])
    return <>{props.children}</>
}

export default DocumentTitle

PageComps文件

import Login from '../pages/Login'
import Home from '../pages/home/Home'
import InfoList from "../pages/info/InfoList";
import InfoEdit from "../pages/info/InfoEdit";
import UserEdit from "../pages/user/UserEdit";
import UserList from "../pages/user/UserList";
import NotFound from "../pages/NotFound";

const routerComp: {[key: string]: React.FC|React.ComponentType} = {
    Login, Home, InfoEdit, InfoList, UserList, UserEdit, NotFound
}

export default routerComp

IRouter文件

export interface IMeta {
    title: string,
    icon?: string
}

export interface IRouter{
    path: string,
    component?: string,
    redirect?: string,
    hidden?: boolean,
    alwaysShow?: boolean,
    children?: Array<IRouter>,
    meta?: IMeta
}

mainMenu文件

import {IRouter} from "../IRouter";

const mainMenu: Array<IRouter> = [
    {
      path: '/login',
      component: 'Login',
      meta: {
          title: '登录页'
      }
    },
    {
        path: '/',
        component: 'Home',
        meta: {
            title: '首页'
        }
    },
    {
        path: '/info',
        redirect: '/info/list',
        children: [
            {
                path: '/info/list',
                component: 'InfoList',
                meta: {
                    title: '信息列表'
                }
            },
            {
                path: '/info/edit',
                component: 'InfoEdit',
                meta: {
                    title: '信息列表编辑'
                }
            }
        ]
    },
    {
        path: '/user',
        redirect: '/user/list',
        children: [
            {
                path: '/user/list',
                component: 'UserList',
                meta: {
                    title: '用户列表'
                }
            },
            {
                path: '/user/edit',
                component: 'UserEdit',
                meta: {
                    title: '用户列表编辑'
                }
            }
        ]
    },
    {
        path: '/*',
        component: 'NotFound',
        meta: {
            title: '页面未找到'
        }
    }
]

export default mainMenu

在app中调用

import React from 'react';
import routerComponent from "./router/RouterComponent";
import mainMenu from "./router/config/mainMenu";
import {HashRouter} from 'react-router-dom'
import './App.css';

const App: React.FC = () => {
  let RouterComp = routerComponent(mainMenu)
  return <HashRouter><RouterComp/></HashRouter>
}

export default App;

8、手动封装组件动态加载方法

组件的动态加载,可以使用react-loadable,这里实现手动编写原理

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

const Loading = () => { //顶替的组件
    return <div>现在正在加载。。。</div>
}

const Loadable = (config) => {  //方法主体
    const {loader, Loading} = config;
    return class LoadableComp extends Component {
        state = { flag: false, Comp: null }
        componentDidMount() {
            loader().then(res => {
                console.log('ok')
                this.setState({
                    Comp: res.default,
                    flag: true
                })
            })
        }
        render() {
            let {Comp, flag} = this.state;
            return <>{flag? <Comp {...this.props}/>: <Loading/>}</>
        }
    }
}

const UserList = Loadable({
    loader: () => import('./UserList'),     //需要预加载的组件
    Loading  //在预加载是顶替的组件
})


const User = () => {
    return <div>
        <h3>this is user</h3>
        <NavLink to='/user/list'>用户列表</NavLink>
        <NavLink to='/user/edit'>用户编辑</NavLink>
        <Switch>
            <Redirect path='/user' to='/user/list' exact />{/*表示当用户访问/user的时候,自动跳转到/user/list页面*/}
            <Route path='/user/list' component={UserList}/>
            <Route path='/user/edit' render={() => <div>这个是用户编辑页面</div>}/>
        </Switch>
    </div>
}

export default User

 注意: 在react中使用高阶函数,并且返回一个函数式组件,注意方法要使用小写的,否则会被识别为函数式组件,容易报错

 

posted on 2021-04-09 00:02  even_blogs  阅读(225)  评论(0编辑  收藏  举报