react-router-dom 官方示例解读(一)

593 阅读8分钟

官方文档



首先我们创建一个 react 的项目. 这里使用 create-react-app 来快速创建

npx create-react-app react-router-dom-demo

然后我们需要下载安装 react-router-dom

cd react-router-dom-demo
yarn add react-router-dom

启动项目

yarn start

Demo Github地址



BaseRoute -- 基本路由

import React from "react";
import {
    //as的作用为将HashRouter重命名为Router,这样的好处是在反复测试HashRouter和BrowserRouter时,可以免去组件修改
    BrowserRouter as Router,
    Switch,
    Route,
    Link
} from "react-router-dom";

export default function BasicExample() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/dashboard">Dashboard</Link>
          </li>
        </ul>

        <hr />
        
        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

function Home() {
  return (
    <div>
      <h2>Home</h2>
    </div>
  );
}

function About() {
  return (
    <div>
      <h2>About</h2>
    </div>
  );
}

function Dashboard() {
  return (
    <div>
      <h2>Dashboard</h2>
    </div>
  );
}

UrlParameters -- 动态路由

获取路由参数可以使用hooks形式 :param,也可以用原始的props.match.params.xxx

注意 : <Route></Route>标签的子元素如果使用 children={}的话 原始方法 props.match.params.xxx就不适用只能使用useParams()的方式. 关于 Route 标签的属性后续进行说明

import React, { Component } from 'react';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    useParams
  } from "react-router-dom";

export default class UrlParameters extends Component {
    render() {
        return (
            <Router>
                <div>
                    <h2>Accounts</h2>

                    <ul>
                    <li>
                        <Link to="/netflix">Netflix</Link>
                    </li>
                    <li>
                        <Link to="/zillow-group">Zillow Group</Link>
                    </li>
                    <li>
                        <Link to="/yahoo">Yahoo</Link>
                    </li>
                    <li>
                        <Link to="/modus-create">Modus Create</Link>
                    </li>
                    </ul>

                    <Switch>
                        <Route path="/:id" component={Child} />
                    </Switch>
                </div>
            </Router>
        )
    }
}
function Child(props) {
    // We can use the `useParams` hook here to access
    // the dynamic pieces of the URL.
    let { id } = useParams();
    let idd = props.match.params.id;
    return (
      <div>
        <h3>ID: {id} ,other:{idd}</h3>
      </div>
    );
}

NestingRouter -- 嵌套路由

嵌套路由适用于有明显层级划分的情况. 简单来说就是路由下面还有子路由的情况.

案例中用到的新的 hooks: useRouteMatch

import React, { Component } from 'react';
import {
	BrowserRouter as Router,
	Switch,
	Route,
	Link,
	useParams,
	useRouteMatch
} from "react-router-dom";

export default class NestingRouter extends Component {
    render() {
        return (
			<Router>
				<div>
					<ul>
						<li>
							<Link to="/">Home</Link>
						</li>
						<li>
							<Link to="/topics">Topics</Link>
						</li>
					</ul>

					<hr />

					<Switch>
						<Route exact path="/">
							<Home />
						</Route>
						<Route path="/topics">
							<Topics />
						</Route>
					</Switch>
				</div>
			</Router>
        )
    }
}

function Home(){
	return(
		<div>
			<h2>Home</h2>
		</div>
	)
}

function Topics(){
	// The `path` lets us build <Route> paths that are
	// relative to the parent route, while the `url` lets
	// us build relative links.


	let { path, url } = useRouteMatch()

	return (
		<div>
			<h2>Topics</h2>
			<ul>
				<li>
					<Link to={`${url}/rendering`}>Rendering with React</Link>
				</li>
				<li>
					<Link to={`${url}/components`}>Components</Link>
				</li>
				<li>
					<Link to={`${url}/props-v-state`}>Props v. State</Link>
				</li>
			</ul>

			<Switch>
				<Route exact path={path}>
					<h3>Please select a topic.</h3>
				</Route>
				<Route path={`${path}/:topicId`}>
					<Topic />
				</Route>
			</Switch>
		</div>
	)
}

function Topic(){
	let { topicId } = useParams();

	return (
		<div>
			<h3>{topicId}</h3>
		</div>
	);
}

我们在 Topics 组件中打印一下 useRouteMatch

结果如下

path 和 url 的值是一样的, 官方的说明则是 : path使我们能够建立相对于父路线的 <Route>路径,而url使我们能够建立相对的链接。这段话翻译过来大概意思就是: path 是用来获取父路径的. url 是用于 Link 组件的跳转的.

RedirectRoute(Auth) -- 重定向路由(鉴权)

首先分析一下页面逻辑:
有三个页面 一个公共页面PublicPage; 一个登陆页面LoginPage; 还有一个受保护页面ProtectedPage
点击查看受保护页面 必须登录过后才能看
接下来在代码里面一步一步分析

我将官方给的案例稍微改动了一下 因为AuthButton 这个组件 重新登录什么的没有重新渲染到 所以这个组件就永远不会变 我将它放入 PrivateRoute 内 进行展示就可以 (分析在代码的注释内)

import React, { Component } from 'react';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    Redirect,
    useHistory,
    useLocation
} from "react-router-dom";

//Redirect 会导航到新的位置,重定向
//useHistory 该useHistory挂钩使您可以访问history可用于导航的实例
//useLocation钩子返回表示当前URL的location对象。您可以将其看作是useState,它在URL出现时返回一个新位置改变。这个可能非常有用,例如,在您希望在每次加载新页面时使用web分析工具触发新的“页面视图”事件的情况下


export default class RedirectRouter extends Component {
    render() {
        return (
            <Router>
                <div>
                    

                    <ul>
                    <li>
                        <Link to="/public">Public Page</Link>
                    </li>
                    <li>
                        <Link to="/protected">Protected Page</Link>
                    </li>
                    </ul>

                    <Switch>
                        <Route path="/public">
                            <PublicPage />
                        </Route>
                        <Route path="/login">
                            <LoginPage />
                        </Route>
                        <PrivateRoute path="/protected">
                            <AuthButton />
                            <ProtectedPage />
                        </PrivateRoute>
                    </Switch>
                </div>
            </Router>
        )
    }
}

//定义变量以及登录退出方法
const fakeAuth = {
    isAuthenticated: false, //是否登录字段
    authenticate(cb) {      //登录方法
        fakeAuth.isAuthenticated = true;
        setTimeout(cb, 100); // fake async
    },
    signout(cb) {           //退出登录方法
        fakeAuth.isAuthenticated = false;
        setTimeout(cb, 100);
    }
}

//登录控制按钮组件
function AuthButton() {
    let history = useHistory();
    return fakeAuth.isAuthenticated?(
        <p>
            Welcome!
            <button
            onClick={() => {
                // fakeAuth.signout(() => history.push("/protected"));
                fakeAuth.signout(() => history.push("/protected"));  //如果已经登录成功就显示 sign out 按钮 点击调用退出登录方法 isAuthenticated 置为 false, 异步回调 history.push 进行页面跳转
            }}
            >
                Sign out
            </button>
        </p>
    ):(
        //如果登录失败 显示你没有登录
        <p>You are not logged in.</p>
    )
}

//自定义高阶路由组件 {children, ...rest} children 表示父组件下面的所有子组件 此处是指ProtectedPage组件  ...rest 是父组件上的所有属性 此处是指  path="/protected" 
function PrivateRoute( {children,...rest} ) { 
    // let history = useHistory();
    // console.log("history:",history)
    return (
        <Route
          {...rest}
          render={({ location }) =>
            fakeAuth.isAuthenticated ? (
              children
            ) : (              // 这里 我们可以将退出登录的地方AuthButton组件的 onClick 事件修改一下 push 到 /protected 这样我们就能看到这个组件的切换
              <Redirect
                to={{
                  pathname: "/login",
                  state: { from: location }
                }}
              />
            )
          }
        />
    );
}
//公共页面组件
function PublicPage() {
    return <h3>Public</h3>;
}
//受保护页面组件
function ProtectedPage() {
    return <h3>Protected</h3>;
}
//登录页面组件
function LoginPage() {
    let history = useHistory();
    let location = useLocation();
    // console.log("history:",history)
    // console.log("location:",location)

    //有 state 属性的话就将 location.state 的 form 赋值给 from,  没有的话就默认 form 是{pathname:"/"}
    let { from } = location.state || { from: { pathname: "/" } };
    //登录方法 调用定义的fakeAuth的登录方法 回调改变路由地址
    //“history.replace 跟 history.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录
    let login = () => {
        fakeAuth.authenticate(() => {
            history.replace(from);
            // history.push(from.pathname)
            
        });
    };

    return (
        <div>
        <p>You must log in to view the page at {from.pathname}</p>
            <button onClick={login}>Log in</button>
        </div>
    );
}

其中我们在受保护组件里面打印一下 historylocation 的值如下

可以看出 location 就是当前URL的location对象 包括路由的属性

CustomRouter(Auth) -- 自定义路由

自定义路由其实最多应用的场景是 应用底部的 tabbar 了吧. 可以根据是否选中改变样式

import React, { Component } from 'react';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    useRouteMatch
  } from "react-router-dom";

export default class CustomLink extends Component {
    render() {
        return (
            <Router>
                <div>
                    <OldSchoolMenuLink
                        activeOnlyWhenExact={true}
                        to="/"
                        label="Home"
                    />
                    <OldSchoolMenuLink to="/about" label="About" />
            
                    <hr />
            
                    <Switch>
                        <Route exact path="/">
                            <Home />
                        </Route>
                        <Route path="/about">
                            <About />
                        </Route>
                    </Switch>
                </div>
            </Router>
        )
    }
}
function OldSchoolMenuLink({ label, to, activeOnlyWhenExact }) {
    let match = useRouteMatch({
        path: to,
        exact: activeOnlyWhenExact
    });
  
    return (
        <div className={match ? "active" : ""}>
            {match && "> "}
            <Link to={to}>{label}</Link>
        </div>
    );
}
  
function Home() {
    return (
        <div>
            <h2>Home</h2>
        </div>
    );
}
  
function About() {
    return (
        <div>
            <h2>About</h2>
        </div>
    );
}

PreventRouter -- 阻止路由

该组件的使用情景: 输入表单的时候 点击跳转页面后再回来 数据会丢失. 该组件就是用来阻断跳转页面的. 如果点击了取消, 就会还在原页面, 表单数据不会丢失. 如果点击了确定, 会跳转页面. 数据丢失. 其实就是一个二次确认弹窗组件

import React, { Component, useState } from 'react';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    Prompt
  } from "react-router-dom";

export default class PreventRouter extends Component {
    render() {
        return (
            <Router>
                <ul>
                    <li>
                        <Link to="/">From</Link>
                    </li>
                    <li>
                        <Link to="/one">One</Link>
                    </li>
                    <li>
                        <Link to="/two">Two</Link>
                    </li>
                </ul>
                <Switch>
                    <Route path="/" exact children={ <BlockingForm /> } />
                    <Route path="/one" children={<h3>One</h3>} />
                    <Route path="/two" children={<h3>Two</h3>} />
                </Switch>
            </Router>
        )
    }
}

function BlockingForm() {

    //定义一个锁 变量 isBlocking  修改的方法是 setIsBlocking 默认值是 false
    let [isBlocking, setIsBlocking] = useState(false);

    return(
        //提交表单 event.preventDefault()默认阻止默认提交事件;
        //event.target.reset() 重置表单内容
        //setIsBlocking(false) 将 isBlocking 设为 false;

        //Prompt 组件阻止过渡 when={isBlocking} 如字面意思 表示当isBlocking为 true 的时候
        // message 内容则是 弹窗显示的内容
        <form
            onSubmit={event => {
                event.preventDefault();
                event.target.reset();
                setIsBlocking(false);
            }}
        >

            <Prompt
                when={isBlocking}
                message={location =>
                `Are you sure you want to go to ${location.pathname}`
                }
            />
            <p>
                Blocking?{" "}
                {isBlocking ? "Yes, click a link or the back button" : "Nope"}
            </p>
            <p>
                <input
                size="50"
                placeholder="type something to block transitions"
                onChange={event => {
                    setIsBlocking(event.target.value.length > 0);
                }}
                />
            </p>

            <p>
                <button>Submit to stop blocking</button>
            </p>

        </form>
    )
}

  • Prompt组件的 message属性还可以接收一个函数,该函数可以判断需要跳转的路由,返回true不提示,反之,弹出提示;
<Prompt
    when={isBlocking}
    message={location =>
      location.pathname=="/one"
      ? true
      :
      `Are you sure you want to go to ${location.pathname}`
    }
/>

这里就是表单数据填好之后点击 Two 弹出提示; 点击 One 则不提示直接跳转

NoMatchRouter(404) -- 未匹配路由

当路由选择到一个未匹配项的时候 会默认选择去 Switch 中的最后一个路由项内. 匹配规则为 *

import React, { Component } from 'react';
import {
    BrowserRouter as Router,
    Route,
    Link,
    Switch,
    Redirect,
    useLocation
  } from "react-router-dom";

export default class NomatchRouter extends Component {
    render() {
        return (
            <Router>
                <div>
                    <ul>
                        <li>
                            <Link to="/">Home</Link>
                        </li>
                        <li>
                            <Link to="/old-match">Old Match, to be redirected</Link>
                        </li>
                        <li>
                            <Link to="/will-match">Will Match</Link>
                        </li>
                        <li>
                            <Link to="/will-not-match">Will Not Match</Link>
                        </li>
                        <li>
                            <Link to="/also/will/not/match">Also Will Not Match</Link>
                        </li>
                    </ul>
                    <Switch>
                        <Route exact path="/">
                            <Home />
                        </Route>
                        <Route path="/old-match">
                            <Redirect to="/will-match" />
                        </Route>
                        <Route path="/will-match">
                            <WillMatch />
                        </Route>
                        <Route path="*">
                            <NoMatch />
                        </Route>
                    </Switch>
                </div> 
            </Router>
        )
    }
}

function Home(){
    return <h3>Home</h3>
}

function WillMatch(){
    return <h3>Matched!!</h3>
}

function NoMatch(){
    let location = useLocation();

    return (
        <div>
            <h3>
                No match for {location.pathname}
            </h3>
        </div>
    )
}

通常在实际业务中 未匹配项都会跳转到统一一个 404 error 的路由页面. 这里我们将最后一个 Route 换成重定向 Redirect 即可

<Switch>
    <Route exact path="/">
        <Home />
    </Route>
    <Route path="/old-match">
        <Redirect to="/will-match" />
    </Route>
    <Route path="/will-match">
        <WillMatch />
    </Route>
    <Route path="/error">
        <Error />
    </Route>
    <Redirect from="/*" to="/error" />
</Switch>

RecursiveRouter -- 递归路由

递归路由 简单来讲类似路由嵌套路由. 像一颗递归树 父路由的子路由又有子路由... 逐渐循环下去. 官方给的 demo 是一个 0,1,2,3 的数组对象 每个又有一个friends的子数组,子数组 0,1,2,3 又对应父数组对象的每个对象

import React, { Component } from 'react';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    Redirect,
    useParams,
    useRouteMatch
  } from "react-router-dom";

export default class RecursiveRouter extends Component {
    render() {
        return (
            <Router>
                <Switch>
                    <Route path="/:id">
                        <Person />
                    </Route>
                    <Route path="/">
                        <Redirect to="/0" />
                    </Route>
                </Switch>
            </Router>
        )
    }
}

function Person(){
    let { url } = useRouteMatch();
    let { id } = useParams();
    let person = find(parseInt(id));

    return(
        <div>
            <h3>{person.name}'s Friends</h3>

            <ul>
                {
                    person.friends.map(
                        id=> 
                            (
                                <li key={id}>
                                    <Link to={`${url}/${id}`}>{find(id).name}</Link>
                                </li>
                            )
                        
                    )
                }
            </ul>
            <Switch>
                <Route path={`${url}/:id`}>
                    <Person />
                </Route>
            </Switch>
        </div>
    )
}

const PEEPS = [
    { id: 0, name: "Michelle", friends: [1, 2, 3] },
    { id: 1, name: "Sean", friends: [0, 3] },
    { id: 2, name: "Kim", friends: [0, 1, 3] },
    { id: 3, name: "David", friends: [1, 2] }
];

function find(id){
    return PEEPS.find(p=>p.id===id)
}

写代码的时候写错了一个小 bug ; 就是 这里在写箭头函数的时候 => 后面习惯性加了 {} 导致页面报错: 箭头函数 加括号的函数体返回对象字面量表达式~ 所以这里需要用()包含住

SidebarRouter -- 侧边栏路由

侧边栏是很常见的一种页面展示, 我在这里将官方案列稍微改动了一下 应用了之前的 CustomRouter 的自定义link 方法做了一个小小的点击背景色功能;

import React, { Component } from 'react';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    useRouteMatch
} from "react-router-dom";
import "../App.css"

const routes = [
    {
        path:"/home",
        exact:true,
        sidebar: ()=><div>home?!</div>,
        main: ()=><h2>Home</h2>
    },
    {
        path:"/bubblegum",
        sidebar: ()=><div>bubblegum?!</div>,
        main: ()=><h2>Bubblegum</h2>
    },
    {
        path:"/shoelaces",
        sidebar: ()=><div>shoelaces?!</div>,
        main: ()=><h2>Shoelaces</h2>
    },

]

export default class SidebarRouter extends Component {
    
    render() {
        return (
            <Router>
                <div style={{display:"flex"}}>
                    <div style={{padding:"10px",width:"40%",background: "#f0f0f0"}}>
                       
                        <ActiveLink  
                            activeOnlyWhenExact={false}
                            to="/home"
                            label="Home"
                        />
                        <ActiveLink  
                            
                            to="/bubblegum"
                            label="Bubblegum"
                        />
                        <ActiveLink  
                            
                            to="/shoelaces"
                            label="Shoelaces"
                        />
                        
                        <Switch>
                            {
                                routes.map((route,index)=>(
                                    <Route
                                        key={index}
                                        path={route.path}
                                        exact={route.exact}
                                        children={<route.sidebar />}
                                    />
                                ))
                            }
                        </Switch>
                    </div>
                    <div style={{ flex: 1, padding: "10px" }}>
                        <Switch>
                            {
                                routes.map((route,index)=>(
                                    <Route 
                                        key={index}
                                        path={route.path}
                                        exact={route.exact}
                                        children={<route.main />}
                                    />
                                ))
                            }
                        </Switch>
                    </div>
                </div>
            </Router>
        )
    }
}
function ActiveLink({ label, to, activeOnlyWhenExact }){
    let match = useRouteMatch({
        path: to,
        exact: activeOnlyWhenExact
    });

    return(
        <div className={match ? "bg_color_blue" : ""}>
            {match && "!!"}
            <Link to={to}>{label}</Link>
        </div>
    )
}

css 样式直接引用 App.css 了 里面写了一个

.bg_color_blue{
  background-color: skyblue;
}

ConfigRouter -- 配置路由

多数实际应用中我们都是将路由进行集中配置的

import React, { Component } from 'react';
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link
} from "react-router-dom";

//定义路由配置
const routes = [
    {
        path:"/sandwiches",
        component: Sandwiches
    },
    {
        path: "/tacos",
        component: Tacos,
        routes: [
            {
                path: "/tacos/bus",
                component: Bus
            },
            {
                path: "/tacos/cart",
                component: Cart
            }
        ]
    }
]

export default class ConfigRouter extends Component {
    render() {
        return (
            <Router>
                <div>
                    <ul>
                        <li>
                            <Link to="/tacos">Tacos</Link>
                        </li>
                        <li>
                            <Link to="/sandwiches">Sandwiches</Link>
                        </li>
                    </ul>

                    <Switch>
                        {
                            routes.map((route,i)=>(
                                <RouteWithSubRoutes key={i} {...route} />
                            ))
                        }
                    </Switch>
                </div>
            </Router>
        )
    }
}

function RouteWithSubRoutes(route){
    return (
        <Route
            path={route.path}
            render={props=>(
                
                <route.component {...props} routes={route.routes} />
                
            )}
        />
    )
}
function Sandwiches() {
    return <h2>Sandwiches</h2>;
}

// 父组件的 routes 属性
function Tacos({ routes }) {
    return (
      <div>
        <h2>Tacos</h2>
        <ul>
          <li>
            <Link to="/tacos/bus">Bus</Link>
          </li>
          <li>
            <Link to="/tacos/cart">Cart</Link>
          </li>
        </ul>
  
        <Switch>
          {routes.map((route, i) => (
            <RouteWithSubRoutes key={i} {...route} />
          ))}
        </Switch>
      </div>
    );
}
  
function Bus() {
    return <h3>Bus</h3>;
}
  
function Cart() {
    return <h3>Cart</h3>;
}

Route 有一个render和children
render是一个函数,语法:render={()=>{return <div></div>}},只要你的路由匹配了,这个函数才会执行
children也是一个函数,不管匹配不匹配,这个函数都会执行 他们两个有个优先级关系,render的优先级总是高于children,是会覆盖children的