React Router

作者:renzp94
时间:2021-08-07 04:11:35

React应用中常用的路由是React RouterReact Router分两个库,用在网站中的库是react-router-dom,在React Native中用的库是react-router-native.还有一个是react-router-config,此库是用于通过配置生成路由

主要的组件

BrowserRouter

使用history模式的路由器,即路由地址不带#

  • basename:基础路径,所有路径前缀都默认加上此路径
  • getUserConfirmation:确认导航
  • forceRefresh:强制刷新浏览器,类型:Boolean

HashRouer

使用hash模式的路由器,路由地址上带#

属性同BrowserRouter

Route

路由匹配器,通过指定path属性指定路由,通过子组件来指定路由显示的内容

路由渲染方式有三种:

  • <Route component>:通过component属性指定渲染的组件
  • <Route render>:通过render属性指定渲染的内容,接收一个函数,函数返回值为渲染内容
  • Route children>:通过children属性或者用Route包裹组件指定渲染的内容,chilren属性接收一个函数,函数返回值为渲染内容

Route其他属性:

  • path:指定路由路径,动态属性可通过:来指定,如:/user/:id
  • exact:精确匹配
  • strict:严格匹配
  • sensitive:区分大小写

Switch

匹配唯一路由,如果不使用Switch则会匹配所有满足的路由,当使用Switch时则从上到下一次匹配,匹配到则不继续匹配

导航,可通过to属性指定跳转地址,to属性可以是string(路由),或者为一个对象或者函数(函数需要返回一个对象),对象属性如下:

  • pathname:跳转地址
  • search:链接后的search字符串,类型是string
  • hash:链接后的hash字符串,类型是string,需要手动加#
  • state:状态

Link属性:

  • replace:替换路由,即指定的路由替换掉当前路由
  • innerRef:a 标签的ref
  • component:指定路由显示的组件内容

导航,可通过to属性指定跳转地址,与Link不同的是当地址匹配到指定路由时,则会激活当前NavLink,通过activeClassName指定激活样式

  • activeClassName:路由激活时的class
  • activeStyle:路由激活时的style
  • exact:是否精准匹配
  • strict:是否严格匹配,即:路由字符串完全一致,如果不严格匹配,则当前后有/时一样可以匹配
  • isActive:确定链接是否处于激活状态的额外逻辑操作函数

Redirect

重定向导航,通过 to属性制定重定向的地址,可为string或对象或函数(返回一个对象),对象属性同Linkto属性指定

Redirect属性:

  • push:向历史记录中推入一条记录,而不是替换,类型:Boolean
  • from:精准匹配,同Rtoue.exact,当from是当前路由时才可重定向
  • strict:严格匹配,用于指定from属性的严格匹配
  • sensitive:区分大小写,用于指定from属性区分大小写
import React from 'react';
import { BrowserRouter, Link, NavLink, Redirect, Route, Switch } from 'react-router-dom';
import classes from './app.module.css';

const Home = () => {
    return <h1>Home</h1>;
};

const About = () => {
    return <h1>About</h1>;
};

const App = () => {
    return (
        <BrowserRouter>
            <nav>
                <ul>
                    <li>
                        <Link to="/">Home</Link>
                    </li>
                    <li>
                        <Link to="/about">About</Link>
                    </li>
                    <li>
                        <NavLink to="/" exact activeClassName={classes.active}>
                            NavLink-Home
                        </NavLink>
                    </li>
                    <li>
                        <NavLink to="/about" activeClassName={classes.active}>
                            NavLink-About
                        </NavLink>
                    </li>
                </ul>
            </nav>
            <Switch>
                <Route exact path="/">
                    <Home />
                </Route>
                <Route path="/home">
                    <Redirect to="/" />
                </Route>
                <Route path="/about">
                    <About />
                </Route>
            </Switch>
        </BrowserRouter>
    );
};

export default App;

app.module.css

.active {
    color: orange;
    font-weight: 700;
}

withRouter

通过withRouter将路由相关数据传递给组件,withRouter是一个高阶组件,传入的组件内部可通过props访问matchlocationhistory对象

import React from 'react';
import {
    BrowserRouter,
    Link,
    NavLink,
    Redirect,
    Route,
    Switch,
    withRouter
} from 'react-router-dom';
import classes from './app.module.css';

const Home = () => {
    return <h1>Home</h1>;
};

const About = withRouter((props: any) => {
    const { match, location, history } = props;

    console.log(match, location, history);
    return <h1>About</h1>;
});

const App = () => {
    return (
        <BrowserRouter>
            <nav>
                <ul>
                    <li>
                        <Link to="/">Home</Link>
                    </li>
                    <li>
                        <Link to="/about">About</Link>
                    </li>
                </ul>
            </nav>
            <Switch>
                <Route exact path="/">
                    <Home />
                </Route>
                <Route path="/home">
                    <Redirect to="/" />
                </Route>
                <Route path="/about">
                    <About />
                </Route>
            </Switch>
        </BrowserRouter>
    );
};

export default App;

Hooks

react-router-dom也提供了一些Hooks用于函数组件中

useHistory

获取history对象,一般用于编程式导航

useLocation

获取当前URL对象

useParams

获取URL参数的键/值对对象

useRouteMatch

用于匹配路由,匹配成功之后返回匹配到的路由信息,否则返回null

import React, { useEffect } from 'react';
import {
    BrowserRouter,
    Redirect,
    Route,
    Switch,
    useHistory,
    useLocation,
    useParams,
    useRouteMatch
} from 'react-router-dom';

const Home = () => {
    return <h1>Home</h1>;
};

const About = () => {
    return <h1>About</h1>;
};

const UserDetails = () => {
    const params = useParams<{ id: string }>();
    return <h1>用户详情:{params.id}</h1>;
};

const Laytout = () => {
    const history = useHistory();
    const location = useLocation();
    const match = useRouteMatch('/user/:id');

    useEffect(() => {
        console.log(location);
        console.log(match);
    }, [location]);

    return (
        <>
            <nav>
                <ul>
                    <li>
                        <button onClick={() => history.push('/')}>Home</button>
                    </li>
                    <li>
                        <button onClick={() => history.push('/about')}>About</button>
                    </li>
                    <li>
                        <button onClick={() => history.push('/user/1')}>用户1</button>
                    </li>
                    <li>
                        <button onClick={() => history.push('/user/2')}>用户2</button>
                    </li>
                </ul>
            </nav>
            <Switch>
                <Route exact path="/" component={Home} />
                <Route path="/about" component={About} />
                <Route path="/user/:id" component={UserDetails} />
            </Switch>
        </>
    );
};

const App = () => {
    return (
        <BrowserRouter>
            <Laytout />
        </BrowserRouter>
    );
};

export default App;

react-router-config

react-router-configreact-router的辅助库,用于集中式配置路由,通过配置文件生成路由的一个库。提供两个方法:

  • renderRoutes:接收一个路由配置的数组,返回渲染的路由
  • matchRoutes:匹配路由,第一个参数传入路由配置数组,第二个参数传入要匹配的路由,返回匹配到的路由信息

App.tsx

import React, { Suspense } from 'react';
import { BrowserRouter, Link, Switch } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
import routes from './routes';

const App = () => {
    return (
        <Suspense fallback={<div>loading...</div>}>
            <BrowserRouter>
                <nav>
                    <ul>
                        <li>
                            <Link to="/">Home</Link>
                        </li>
                        <li>
                            <Link to="/login">Login</Link>
                        </li>
                        <li>
                            <Link to="/user">User</Link>
                        </li>
                    </ul>
                </nav>
                {renderRoutes(routes)}
            </BrowserRouter>
        </Suspense>
    );
};

export default App;

因为需要将路由做懒加载处理,所以用了Suspense边界处理,懒加载路由可以使用React.lazy

routes/index.ts

import { lazy } from 'react';
import { RouteConfig } from 'react-router-config';

const routes: Array<RouteConfig> = [
    {
        path: '/',
        name: 'Home',
        exact: true,
        component: lazy(() => import('../pages/home'))
    },
    {
        path: '/login',
        name: 'Login',
        component: lazy(() => import('../pages/login'))
    },
    {
        path: '/user',
        component: lazy(() => import('../pages/user')),
        routes: [
            {
                path: '/user/:id',
                component: lazy(() => import('../pages/user/user-details'))
            }
        ]
    }
];

export default routes;

page/home.tsx

import React from 'react';

const Home = () => {
    return <h1>Home</h1>;
};

export default Home;

page/login.tsx

import React from 'react';

const Login = () => {
    return <h1>login</h1>;
};

export default Login;

page/user/index.tsx

import React, { useState, useEffect } from 'react';
import { renderRoutes } from 'react-router-config';
import { Link } from 'react-router-dom';

interface User {
    id: number;
    name: string;
}

const userList: Array<User> = [
    {
        id: 1,
        name: '用户001'
    },
    {
        id: 2,
        name: '用户002'
    },
    {
        id: 3,
        name: '用户003'
    }
];

const User = (props: any) => {
    const [list, setList] = useState<Array<User>>([]);
    const getList = async () => {
        const data = await new Promise((resolve) => setTimeout(() => resolve(userList), 1000));
        setList(data as Array<User>);
    };

    useEffect(() => {
        getList();
    }, []);

    return (
        <>
            <div>列表:</div>
            <ul>
                {list.map((item) => (
                    <li key={item.id}>
                        <Link to={`/user/${item.id}`}>{item.name}</Link>
                    </li>
                ))}
            </ul>
            {renderRoutes(props.route.routes)}
        </>
    );
};

export default User;

用户详情使用了嵌套路由,所以在此处需要再次使用renderRoutes(props.route.routes)来渲染子路由

page/user/user-details.tsx

import React from 'react';

const UserDetails = (props: any) => {
    return <div>用户详情:{props.match.params.id}</div>;
};

export default UserDetails;

从上述列子可以总结出:首先需要一个Router(BrowserRouter/HashRouter)以及根据需求使用Switch来包裹路由,然后将所有的路由信息放到一个文件进行配置,并通过renderRoutes来生成并渲染Route。如果需要使用嵌套则需要在嵌套路由的父路由中使用renderRoutes生成渲染出子路由。若需要路由懒加载处理,则需要通过SuspenceReact.lazy结合使用。

路由配置属性:

  • path:路由路径
  • exact:是否精准匹配
  • strict:是否严格匹配
  • component:路由组件
  • render:路由渲染函数

路由鉴权

其实,react-router-config的代码量并不大,其renderRoutes的原理就是解析配置文件并渲染成Route组件,但是不支持路由鉴权,如果需要路由鉴权的话,可以通过withRouter封装一个鉴权组件,然后用其包裹

import React from 'react';
import { withRouter } from 'react-router';
import { Redirect } from 'react-router-dom';

const AuthRoute = withRouter((props: any) => {
    const { children, history } = props;

    const token = localStorage.getItem('token');

    if (history.location.pathname !== '/login' && !token) {
        return <Redirect to="/login" />;
    }

    return children;
});

export default AuthRoute;

调整App.tsx文件,用AuthRoute组件包裹renderRoutes

import React, { Suspense } from 'react';
import { BrowserRouter, Link, Switch } from 'react-router-dom';
import { renderRoutes } from 'react-router-config';
import routes from './routes';
+import AuthRoute from './components/AuthRoute';

const App = () => {
    return (
        <Suspense fallback={<div>loading...</div>}>
            <BrowserRouter>
                <nav>
                    <ul>
                        <li>
                            <Link to="/">Home</Link>
                        </li>
                        <li>
                            <Link to="/login">Login</Link>
                        </li>
                        <li>
                            <Link to="/user">User</Link>
                        </li>
                    </ul>
                </nav>
                <Switch>
-                    {renderRoutes(routes)}
+                    <AuthRoute>{renderRoutes(routes)}</AuthRoute>
                </Switch>
            </BrowserRouter>
        </Suspense>
    );
};

export default App;