React Router 5 完整指南

发表于 3年以前  | 总阅读数:592 次

最近在搭建自己的网站时,以前一直被自己认为写起来很简单的路由狠狠地给了我一巴掌,我既然怎么也想不到该怎么去合理地设计路由,痛定思痛,阅读了很多文章及官方文档,过程中也读到了这一篇很好的基础性文章,想翻译下来向大家分享下!同时,文中一些没讲到点上的,我都会进行补充,欢迎大家阅读与留言!

另外,Twitter 已经私信给原作者,得到了翻译许可!

React Router 是 React 社区最受欢迎的路由库,当你需要在一个有多个页面的 React 应用程序中根据 URL 来导航到对应的页面时,就可以使用 React Router 来处理这个问题,它会使你的应用的 UI 和 URL 保持同步。

本教程将会向你介绍 React Router 5 以及你可以利用它而做到的一大堆事情。

介绍

我们都知道 React 是一个用于创建在客户端进行渲染单页应用(SPA)的流行库,在一个 SPA 中可能有多个视图(也可以叫页面),但是与传统的多页应用程序不同的是,浏览这些页面时不会导致整个页面被重新加载。我们希望的是这些页面能够在当前页面中进行内联渲染,当然了,如果我们习惯了多页应用程序,那么希望 SPA 中也要具有以下的功能:

  • 每个页面都应该有一个唯一指定该页面的 URL,这是为了能让用户可以将 URL 加入书签或直接输入浏览器而访问,比如 www.example.com/products
  • 点击浏览器的后腿和前进按钮都应该如其如期工作。
  • 动态生成的嵌套页面最好也有一个自己的 URL,比如 www.example.com/products/shoes/101 ,其中 101 是产品 ID。

路由是使浏览器的 URL 与页面上正在展示的页面保持同步的过程。React Router 让你以声明的方式处理路由,声明式路由方法允许你控制应用程序中的数据流,基本的使用方式就像下面一样简单:

<Route path="/about">
  <About />
</Route>
复制代码

这里简单提一下声明式路由函数式路由分别长啥样:

  • 声明式:<NavLink to='/products' />
  • 函数式:histor.push('/products')

你可以把 <Route> 组件放在任何你想渲染路由的地方,因为 <Route><Link> 以及其它 React Router 的 APIs 都只是组件而已,所以你可以很容易地在 React 中启动和运行路由。

⚠️ 注意:有一个普遍的误解,认为 React Router 是由 Facebook 开发的官方路由解决方案。实际上,它只是一个第三方库,但因其设计和简单性而广受欢迎。

概览

本教程将会分为几个小节,首先我们会使用 npm 来安装 React 和 React Router,接着就直接介绍 React Router 的基础知识。你会看到根据不同知识点而写的不同的代码演示,本教程中涉及的例子有:

  • 基本的导航路由
  • 嵌套路由
  • 带路径参数的嵌套路由
  • 权限路由

所有与构建这些路由有关的概念都将在此过程中讨论。另外,该项目的全部代码可在 GitHub repo 上找到。 现在就让我们搞起来吧!

安装 React Router

请保证你电脑上安装了 nodenpm ,然后利用 create-react-app 来创建一个新的 React 项目,我们直接使用 npx 来进行项目的新建:

npx create-react-app react-router-demo
复制代码

npx 可以使你不需要全局安装 create-react-app 就能创建 cra 项目。

接下来切换到该项目目录下:

cd react-router-demo
复制代码

React Router 库包含三个包:react-router、react-router-dom 和 react-router-native 。路由操作相关的核心包是 react-router,而其他两个是特定环境下使用的。如果你正在开发一个 web 应用,你应该使用 react-router-dom,如果你在使用 React Native 开发移动应用,则应该使用 react-router-native。 使用 npm 来安装 react-router-dom

npm install react-router-dom
复制代码

然后执行以下命令来启动本地服务:

npm run start
复制代码

好了,你现在已经有了一个安装了 React Router 的 React 应用,你可以在 http://localhost:3000/ 查看该应用的运行情况了。

React Router 基础知识

现在让我们熟悉一下 React Router 的基础知识,为了做到这一点,我们将制作一个有三个独立页面的应用程序:Home,Category 和 Products。

Router 组件

我们需要做的第一件事是将我们的 <App> 组件包裹在一个 <Router> 组件中(由 React Router 提供)。由于我们正在建立的是一个基于浏览器的 web 应用程序,我们可以使用 React Router API 中的两种类型的路由:

  • BrowserRouter
  • HashRouter

两者主要区别在于他们创建的 URL 上:

// <BrowserRouter>
http://example.com/about
// <HashRouter>
http://example.com/#/about
复制代码

<BrowserRouter> 在两者中会更受欢迎些,因为它使用的是 HTML5 History API 来保持应用的页面与 URL 同步,而 <HashRouter> 则使用的是 URL 的哈希部分(window.location.hash)。如果你的代码运行在不支持 History API 的传统浏览器上,你应该使用 <HashRouter> ,否则 <BrowserRouter> 对于大多数情况来说是更好的选择。

导入 BrowserRouter 组件并用其包裹 <App> 组件:

// src/index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";

ReactDOM.render(
  <Router>
    <App />
  </Router>,
  document.getElementById("root")
);
复制代码

在上面代码中,我们为整个 <App> 组件创建了一个 history 实例,等会向大家解释这意味着什么。

⚠️ 为了能让大家更加明白这两者有啥区别,我会在下面做一个简短的说明。

<BrowserRouter><HashRouter> 区别

BrowserRouter:

BrowserRouter 要求服务端对发送的不同的 URL 都要返回对应的 HTML,比如说现在有如下两个 URL 发送 GET 请求到服务端:

http://example.com/home http://example.com/about
复制代码

那么这个时候服务端拿到的是完整的 URL,这时候服务端就必须分别对 /home/about 做处理并返回相应的 HTML 来给到客户端渲染。这个带来的影响就是,如果你切换到某个服务端没有做相应处理的页面路由,比如:

http://example.com/article
复制代码

如果你在 SPA 中写了这部分路由要渲染的页面,在页面无刷新情况下跳转是没啥问题的。但是如果你直接在此路由下进行页面的刷新,就会得到一个 404。

HashRouter

HashRouter 在 URL 中使用哈希符号(#)来使服务端忽略 # 后面所有的 URL 内容,比如你在浏览器地址栏中直接输入以下 URL:

http://example.com/#/home http://example.com/#/about
复制代码

服务端拿到的只会是 http://example.com/ ,这样服务端只需要对这个路由做处理并返回 HTML,然后后面的路由 /home/about 将全部交给客户端(也就是我们的 SPA 应用)来处理并渲染对应的页面。所以你在任意的路由进行页面的刷新都不会是 404。

History 的小知识

history 这个库可以让你在 JavaScript 运行的任何地方都能轻松地管理回话历史,history 对象抽象化了各个环境中的差异,并提供了最简单易用的的 API 来给你管理历史堆栈、导航,并保持会话之间的持久化状态。— React Training 文档

每个 <Router> 组件都会创建一个 history 对象,它记录了当前的位置(history.location),还记录了堆栈中以前的位置。在当前位置发生变化时,页面会被重新渲染,于是你就有一种导航跳转的感觉。

那么如何改变当前的位置呢?也就是说如何做到导航跳转呢?这时候 history 的作用就来了,这个对象暴露了一些方法,比如 history.pushhistory.replace ,它们就可以拿来处理上面的问题。

当你点击一个 <Link> 组件时,history.push 就会被调用,而当你使用一个 <Redirect> 组件时,history.replace 就会被调用。其它的方法比如 history.goBackhistory.goForward 可以用来在历史堆栈中回溯或前进。

LinkRoute 组件

可以说 <Route> 组件是 React Router 中最重要的组件了,如果当前的位置与路由的路径匹配,就会渲染对应的 UI。理想情况下,<Route> 组件应该有一个名为 path 的属性,如果路径名称与当前位置匹配,它就会被渲染。

<Link> 组件被用来在页面之间进行导航,它其实就是 HTML 中的 <a> 标签的上层封装,不过在其源码中使用 event.preventDefault 禁止了其默认行为,然后使用 history API 自己实现了跳转。我们都知道,如果使用 <a> 标签去进行导航的话,整个页面都会被刷新,这是我们不希望看到的,当然,跳转到首页这种行为我倒是蛮喜欢用 <a> 标签的~

所以我们使用 <Link> 组件来导航到一个目标 URL,可以在不刷新页面的情况下重新渲染页面。 现在我们已经知道了所有要完成我们的 APP 所需要的知识,接着更新 src/App.js ,如下所示:

import React from "react";
import { Link, Route, Switch } from "react-router-dom";

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
);

const Category = () => (
  <div>
    <h2>Category</h2>
  </div>
);

const Products = () => (
  <div>
    <h2>Products</h2>
  </div>
);

export default function App() {
  return (
    <div>
      <nav className="navbar navbar-light">
        <ul className="nav navbar-nav">
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/category">Category</Link>
          </li>
          <li>
            <Link to="/products">Products</Link>
          </li>
        </ul>
      </nav>
      {/* 如果当前路径与 path 匹配就会渲染对应的组件 */}
      <Route path="/">
        <Home />
      </Route>
      <Route path="/category">
        <Category />
      </Route>
      <Route path="/products">
        <Products />
      </Route>
    </div>
  );
}
复制代码

在上面的 App.js 中我们定义了三个组件分别为 HomeCategoryProducts 。虽然现在这样做还算说得过去,但是当一个组件内的代码变得很多时,最好的方式是为每一个组件建立一个独立的文件。就我的经验来说,如果一个组件占用的代码超过 10 行,我就会为它创建一个新的文件。所以从第二个演示开始,我将会为那些代码过多而放在 App.js 中会显得特别臃肿的组件单独创建一个文件来存放。

App 组件中我们已经写好了路由的逻辑,<Route>path 如果与当前位置相匹配的话,对应的组件也会被渲染。在以前,要被渲染的组件应该作为 <Route> 组件的属性传入的,但是现在的版本只要作为 <Route> 的子组件就可以被正确渲染。

在上面的路由设计中,/ 将会匹配 //category 以及 /products ,这带来的结果是会同时在页面上渲染三个组件,即 HomeCategoryProducts ,这不是我们所希望看到的。因此,我们可以通过传入 exact 属性给 <Route> 组件来避免这个问题出现:

<Route exact path="/">
  <Home />
</Route>
复制代码

所以如果你期望的是根据一个安全匹配的 path 去渲染对应的组件,你就应该考虑使用属性 exact 了。

嵌套路由

如果想要使用嵌套路由,我们要更加深入地理解 <Route> 组件的工作方式,接下来我们一探究竟。 通过 React Router 官方文档 可知,使用 <Route> 渲染一个页面(或组件)的最佳方式是使用子元素方式,就像我们上面的演示一样。然而,还是有一些其它的方式,这些方式是为了兼容在没有引进 hooks 之前的早期版本的 React Router 构建的 APP:

  • component :当 URL 匹配时,React Router 会使用 React.createElement 从给定的组件创建一个 React 元素。
  • render :能使你便捷的渲染内联组件或是嵌套组件,你可以给这个属性传入一个函数,当路由的路径匹配时调用,返回一个元素。
  • children :与 render 属性有些类似,它也是接收一个函数,不同的是,无论现在 path 是否与当前位置匹配,这个函数都会被执行。

路径和匹配

属性 path 是用于识别路由应该被匹配到的 URL 部分,它使用 path-to-regexp 库将字符串形式的 path 转换为一个正则表达式,然后将它与当前的位置进行匹配。

如果路由的 path 与当前位置完全匹配时,一个 match 对象 就会被创建,这个对象中有关于 URL 和路径的更多信息,这些信息可以通过这个对象的属性来进行访问,下面为大家列出有哪些属性:

  • match.url :一个字符串(string),返回 URL 匹配的部分,这对于构建嵌套的 <Link> 组件特别有用。
  • match.path :一个字符串(string),返回路由的 path ,即 <Route path=""> ,我们将使用它来构建嵌套的 <Route> 组件。
  • match.isExact :一个布尔值(boolean),如果匹配时精确的,即没有任何尾部字符,则返回 true
  • match.params :一个对象(object),返回的是从 URL 中解析出来键值对。

属性的隐式传递

请注意,当使用 component 属性来渲染路由时,matchlocationhistory 这些路由属性是隐式地传给被渲染的组件的。但当使用比较新的路由渲染模式时,情况有所不同。

比如,以下面这个组件为例:

const Home = (props) => {
  console.log(props);

  return (
    <div>
      <h2>Home</h2>
    </div>
  );
};
复制代码

以这种方式渲染路由:

<Route exact path="/" component={Home} />
复制代码

控制台打印的日志:

{
  history: { ... }
  location: { ... }
  match: { ... }
}
复制代码

但是现在如果以这种方式渲染路由:

<Route exact path="/">
  <Home />
</Route>
复制代码

控制台打印的日志将会是这样:

{}
复制代码

可能你会觉得以这种方式来使用不太好,因为我们在渲染的组件中拿不到路由属性了。但是不用担心,React v5.1 引入了几个 hooks,通过在组件内部使用这些 hooks 可以助你访问到上面隐式传递的任何路由属性,这是一种新的管理路由状态的方法,并在一定程度上使我们的组件更加整洁。 我将在本教程中使用其中的一些 hooks,但是如果你想要更深入地了解,可以查看 React Router v5.1 的发布公告。请注意,hooks 是在 React 的 16.8 版本中引入的,所以你至少需要在这个版本以上才能使用它们。

Switch 组件

在开始代码演示之前,我想先向大家介绍一下 Switch 组件。当多个 <Route> 被一起使用时,所有匹配到的路由都会被渲染,大家看下下面的代码,我会向大家解释为什么 <Switch> 是有用的:

<Route exact path="/"><Home /></Route>
<Route path="/category"><Category /></Route>
<Route path="/products"><Products /></Route>
<Route path="/:id">
  <p>This text will render for any route other than '/'</p>
</Route>
复制代码

如果 URL 是 /products ,那么 path/products/:id 的路由会一起在页面渲染出来,这就是这样设计的。然而,这种行为基本不可能是我们所期待的,所以才要用到 <Switch> ,有了 <Switch> ,只有第一个与当前 URL 匹配到的子 <Route> 才会被渲染:

<Switch>
  <Route exact path="/">
    <Home />
  </Route>
  <Route path="/category">
    <Category />
  </Route>
  <Route path="/products">
    <Products />
  </Route>
  <Route path="/:id">
    <p>This text will render for any route other than those defined above</p>
  </Route>
</Switch>
复制代码

path:id 部分用于动态路由,它将匹配斜杠后面的任何东西,并且这个匹配到的值在被渲染的组件中是可以拿到的,我们会在下一节演示如何取这个值。

现在我们知道了关于 <Route><Switch> 组件的一切,让我们看看本节的主题嵌套路由的示例吧。

动态嵌套路由

在上面的示例中我们创建了 //category/products 路由,但是如果我们想要匹配一个 /category/shoes 的路由咋办呢?让我们更新一波 src/App.js 的代码:

import React from "react";
import { Link, Route, Switch } from "react-router-dom";
import Category from "./Category";

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
);

const Products = () => (
  <div>
    <h2>Products</h2>
  </div>
);

export default function App() {
  return (
    <div>
      <nav className="navbar navbar-light">
        <ul className="nav navbar-nav">
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/category">Category</Link>
          </li>
          <li>
            <Link to="/products">Products</Link>
          </li>
        </ul>
      </nav>

      <Switch>
        <Route path="/">
          <Home />
        </Route>
        <Route path="/category">
          <Category />
        </Route>
        <Route path="/products">
          <Products />
        </Route>
      </Switch>
    </div>
  );
}
复制代码

你应该注意到了,我已经把 Category 组件独立出来了,而我们的嵌套路由就在这个组件中去定义,那么现在就来创建 Category.js 吧!

// src/Category.js

import React from "react";
import { Link, Route, useParams, useRouteMatch } from "react-router-dom";

const Item = () => {
  const { name } = useParams();

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

const Category = () => {
  const { url, path } = useRouteMatch();

  return (
    <div>
      <ul>
        <li>
          <Link to={`${url}/shoes`}>Shoes</Link>
        </li>
        <li>
          <Link to={`${url}/boots`}>Boots</Link>
        </li>
        <li>
          <Link to={`${url}/footwear`}>Footwear</Link>
        </li>
      </ul>
      <Route path={`${path}/:name`}>
        <Item />
      </Route>
    </div>
  );
};

export default Category;
复制代码

在这里我们使用 useRouteMatch hook 来获取上面我们说过的 match 对象。如前所述,match.url 为 URL 匹配的部分,用于构建嵌套链接。match.path 为路由的 path ,用于构建嵌套路由。 如果你觉得在 match 对象中的属性有理解上的困难,没关系,console.log(useRouteMatch()) 打印在控制台仔细看看它的属性的值是什么,你就大概能知道啥意思了。

<Route path={`${path}/:name`}>
  <Item />
</Route>
复制代码

这就是我们对动态路由的第一次尝试,因为我们没有将路由写死,而是在属性 path 中使用了一个变量,:name 是一个路径参数,可以捕捉到 category/ 之后的所有内容,直到遇到另外一个正斜杠(/)。因此,像 category/running-shoes 这样的路径名称将会创建一个 params 对象,如下所示:

{
  name: "running-shoes";
}
复制代码

为了在 <Item> 组件中访问到这个值,我们使用 useParams hook ,它返回一个 URL 参数的键值对的对象。 你可以在控制台中打印下看看返回的到底是什么,那么现在 Category 应该就会有三个子路由了。

带路径参数的嵌套路由

我们把这个例子在复杂化一点,以便我们更好地去理解。在实际开发中,我们的路由必须具有处理数据并动态展示它们的功能。假设有一些 API 返回的产品数据,其格式如下:

const productData = [
  {
    id: 1,
    name: "NIKE Liteforce Blue Sneakers",
    description:
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin molestie.",
    status: "Available",
  },
  {
    id: 2,
    name: "Stylised Flip Flops and Slippers",
    description:
      "Mauris finibus, massa eu tempor volutpat, magna dolor euismod dolor.",
    status: "Out of Stock",
  },
  {
    id: 3,
    name: "ADIDAS Adispree Running Shoes",
    description:
      "Maecenas condimentum porttitor auctor. Maecenas viverra fringilla felis, eu pretium.",
    status: "Available",
  },
  {
    id: 4,
    name: "ADIDAS Mid Sneakers",
    description:
      "Ut hendrerit venenatis lacus, vel lacinia ipsum fermentum vel. Cras.",
    status: "Out of Stock",
  },
];
复制代码

假设我们还需要为以下的路径创建路由:

  • /products :这应该显示一个产品列表。
  • /products/:productId :如果匹配到 :productId 那么就应该显示这个产品的数据,如果没有就显示一个错误信息。

创建一个新文件 src/Products.js 文件,并添加以下代码:

import React from "react";
import { Link, Route, useRouteMatch } from "react-router-dom";
import Product from "./Product";

const Products = ({ match }) => {
  const productData = [ ... ];
  const { url } = useRouteMatch();

  /* Create an array of `<li>` items for each product */
  const linkList = productData.map((product) => {
    return (
      <li key={product.id}>
        <Link to={`${url}/${product.id}`}>{product.name}</Link>
      </li>
    );
  });

  return (
    <div>
      <div>
        <div>
          <h3>Products</h3>
          <ul>{linkList}</ul>
        </div>
      </div>

      <Route path={`${url}/:productId`}>
        <Product data={productData} />
      </Route>
      <Route exact path={url}>
        <p>Please select a product.</p>
      </Route>
    </div>
  );
};

export default Products;
复制代码

首先我们使用了 useRouteMatch 钩子,并从 match 对象中拿到 URL ,然欧根据每个产品的 id 属性来建立一个 <Link> 组件的列表,并将其返回存储到一个 linkList 变量中。

第一个路由使用 path 中的一个变量,它与产品 id 对应,当匹配成功时,我们就会渲染 <Product> 组件(我们马上进行定义),将我们的产品数据传递给它:

<Route path={`${url}/:productId`}>
  <Product data={productData} />
</Route>
复制代码

注意到第二个路由中有一个 exact 属性,只有当 URL 是 /products 且其后面没有任何路径参数时才会渲染。

OK,下面是 <Product> 组件的代码,你只需要在 src/Product.js 创建这个文件:

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

const Product = ({ data }) => {
  const { productId } = useParams();
  const product = data.find((p) => p.id === Number(productId));
  let productData;

  if (product) {
    productData = (
      <div>
        <h3> {product.name} </h3>
        <p>{product.description}</p>
        <hr />
        <h4>{product.status}</h4>
      </div>
    );
  } else {
    productData = <h2> Sorry. Product doesn't exist </h2>;
  }

  return (
    <div>
      <div>{productData}</div>
    </div>
  );
};

export default Product;
复制代码

find 方法用于在产品数组中搜索一个 id 属性与 match.params.productId 相同的对象。如果该产品存在,就会渲染对应的数据。如果不存在,就会显示 “产品不存在”的信息。

最后,更新你的 <App> 组件,如下所示:

import React from "react";
import { Link, Route, Switch } from "react-router-dom";
import Category from "./Category";
import Products from "./Products";

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
);

export default function App() {
  return (
    <div>
      <nav className="navbar navbar-light">
        <ul className="nav navbar-nav">
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/category">Category</Link>
          </li>
          <li>
            <Link to="/products">Products</Link>
          </li>
        </ul>
      </nav>

      <Switch>
        <Route exact path="/">
          <Home />
        </Route>
        <Route path="/category">
          <Category />
        </Route>
        <Route path="/products">
          <Products />
        </Route>
      </Switch>
    </div>
  );
}
复制代码

现在你就可以在浏览器中访问你写的这些路由了,如果你选择“Products”,你会看到一个子菜单,并且显示了产品的数据。 尝试着好好理解下这个演示中的代码,确保你要掌握这部分内容。

权限路由

在如今大多数网站应用中,只有登录了的用户才能访问网站的某些部分,比如掘金登录之后才会有进入到个人主页的入口。接下来这一节,我会告诉大家如何去实现一个权限路由,也就是说如果有人试图访问 /admin ,他将会首先被要求登录。

然而,我们需要先了解 React Router 的几个方面。

<Redirect> 组件

与服务端的重定向类似,React Router 的 Redirect component 将会用一个新的位置替换历史栈中的当前位置,新的位置是由 to 属性来指向的。那么接下来我就会向大家介绍如何使用 <Redirect>

<Redirect to={{ pathname: '/login', state: { from: location }}}
复制代码

如果有人试图在未登录状态下访问 /admin 路由,他就会被重定向到 /login 路由,关于当前位置的信息是由 state 属性进行传递的,这样做是为了在用户登录成功之后,用户又可以被重定向到他试图访问的路由页面。

自定义路由

如果我们需要决定一个路由是否应该被渲染,那么编写一个自定义路由是个好办法,接下来在 src 目录下创建一个新文件 PrivateRoute.js ,并写入以下代码:

import React from "react";
import { Redirect, Route, useLocation } from "react-router-dom";
import { fakeAuth } from "./Login";

const PrivateRoute = ({ component: Component, ...rest }) => {
  const location = useLocation();

  return (
    <Route {...rest}>
      {fakeAuth.isAuthenticated === true ? (
        <Component />
      ) : (
        <Redirect to={{ pathname: "/login", state: { from: location } }} />
      )}
    </Route>
  );
};

export default PrivateRoute;
复制代码

如你所见,在函数定义中,我们将接收到的 props 中拿到一个 Component 还有一个剩余属性 restComponent 将包含我们的 <PrivateRoute> 所保护的任何组件(在该例中为 Admin 组件),其余的属性将会通过 rest 传递给 <Route>

我们返回的是一个 <Route> 组件,该组件会根据用户是否登录来决定是否渲染受到保护的组件,如果没有登录将会重定向到 /login 路由。这是由 fakeAuth.isAuthenticated 属性决定的,这个属性从 <Login> 组件中导入。 这种封装的方法好处在于是声明式的,而且 <PrivateRoute> 可被重复使用。

实践权限路由

现在我们可以修改 src/App.js

import React from "react";
import { Link, Route, Switch } from "react-router-dom";
import Category from "./Category";
import Products from "./Products";
import Login from "./Login";
import PrivateRoute from "./PrivateRoute";

const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
);

const Admin = () => (
  <div>
    <h2>Welcome admin!</h2>
  </div>
);

export default function App() {
  return (
    <div>
      <nav className="navbar navbar-light">
        <ul className="nav navbar-nav">
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/category">Category</Link>
          </li>
          <li>
            <Link to="/products">Products</Link>
          </li>
          <li>
            <Link to="/admin">Admin area</Link>
          </li>
        </ul>
      </nav>

      <Switch>
        <Route exact path="/">
          <Home />
        </Route>
        <Route path="/category">
          <Category />
        </Route>
        <Route path="/products">
          <Products />
        </Route>
        <Route path="/login">
          <Login />
        </Route>
        <PrivateRoute path="/admin" component={Admin} />
      </Switch>
    </div>
  );
}
复制代码

正如你所见,我们在文件的顶部添加了一个 <Admin> 组件,并在 <Switch> 组件下添加了一个 <PrivateRoute> 组件。正如前面所说,如果用户已经登录的话,这个自定义路由将会渲染的是 <Admin> 组件,否则,用户会被重定向到 /login

最后,这里是 Login 组件代码:

import React, { useState } from "react";
import { Redirect, useLocation } from "react-router-dom";

export default function Login() {
  const { state } = useLocation();
  const { from } = state || { from: { pathname: "/" } };
  const [redirectToReferrer, setRedirectToReferrer] = useState(false);

  const login = () => {
    fakeAuth.authenticate(() => {
      setRedirectToReferrer(true);
    });
  };

  if (redirectToReferrer) {
    return <Redirect to={from} />;
  }

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

/* A fake authentication function */
export const fakeAuth = {
  isAuthenticated: false,
  authenticate(cb) {
    this.isAuthenticated = true;
    setTimeout(cb, 100);
  },
};
复制代码

我们使用 useLocation hook 来访问路由的 location 属性,也就是从 state 属性带过来的。然后我们使用对象的解构来获取用户在被要求登录之前试图访问的 URL,这个这个值不存在,我们就设为 { pathname: "/" }

然后我们使用 React 的 useState 钩子来初始化一个 redirectToReferrer 状态为 false ,根据这个值来决定用户是被重定向到他们想要访问的路径(也就是说用户已经登录了),还是向用户展示一个按钮让他们登录。

一旦按钮被点击,fakeAuth.authenticate 这个方法就会被执行,它将 fakeAuth.isAuthenticated 设为 true ,并(在一个回调函数中)将 redirectToReferrer 状态更新为 true ,这将导致组件重新渲染,用户将被重定向。

完整示例

以下就是我们使用学到的东西做出来的最终 demo:

Edit React Router Demo

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/GaWxOcGnyqU-BkcwCGgnIg

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录