1

React 系列十二:React - Router

 2 years ago
source link: https://segmentfault.com/a/1190000040340668
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

快来加入我们吧!

"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( https://xhs-rookies.com/ ) 进行学习,及时获取最新文章。

"Code tailor" ,如果您对我们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与我们取的联系,您也可以在微信上观看我们的文章。每一个建议或是赞同都是对我们极大的鼓励

这节我们将介绍 Reactreact - router,路由跳转的配置以及使用

本文会向你介绍以下内容:

  • 认识 react-router
  • react-router 基本使用
  • react-router 高级使用
  • react-router-config

认识 react - router

如果你是第一次接触 router 这个名词,可以先去这里补充一下自己的路由知识再来往下阅读哦

注意:以下内容基于 react-router v5 版本,如果和读者当前使用的不符合,请以官网为主

安装 react-router

yarn add react-router-dom

npm install react-router-dom

什么是 react-router

React Router is a set of navigation components that are combined with your application in a declarative manner. Whether you want to provide bookmarkable URLs for your web applications or use composable navigation methods in React Native, React Router can be used anywhere React renders

react-router 是一组以声明方式与您的应用程序组合的导航组件,换句话,react-routerReact 体系中的路由库,它通过管理 URL,实现组件的切换和状态的变化

react-router 基本使用

React Router 中的组件主要分为三类:

路由器 BrowserRouterHashRouter

  • BrowserRouter 使用常规URL路径,创建一个像 example.com/some/path 这样真实的 URL ,但是他们要求正确的配置服务器
  • HashRouter 将当前位置存储在 URL 的哈希部分中,因此 URL 看起来类似于http://example.com/#/your/page ,由于哈希从不发送到服务器,因此这意味着不需要特殊的服务器配置

两者之间的主要区别是它们存储 URL 和与 Web 服务器通信的方式

路由匹配器,例如 Switch 和 Route:

当渲染 Switch 组件时,它将搜索它的 Route 子元素,以查找路径与当前 URL 匹配的元素,它将呈现找到的第一个 Route 并忽略所有的其他路由。这意味着您应该将包含更多特定路径的 Route 放在不那么特定的路由之前

Switch

我们来看下面的路由规则:

  • 当我们匹配到某一个路径时,我们会发现有一些问题;
  • 比如/about 路径匹配到的同时,/:userid也被匹配到了,并且最后的一个 NoMatch 组件总是被匹配到;
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/profile" component={Profile} />
<Route path="/:userid" component={User}/>
<Route component={NoMatch}/>

原因是什么呢?默认情况下,react-router 中只要是路径被匹配到的 Route 对应的组件都会被渲染;

但是实际开发中,我们往往希望有一种排他的思想:

  • 只要匹配到了第一个,那么后面的就不应该继续匹配了;
  • 这个时候我们可以使用 Switch 来将所有的 Route 进行包裹即可;
<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/about" component={About} />
  <Route path="/profile" component={Profile} />
  <Route path="/:userid" component={User} />
  <Route component={NoMatch} />
</Switch>

Route

它最基本的职责是在其路径与当前 URL 匹配时呈现一些 UI

Route 的渲染方法一共有三类

  • <Route component>
  • <Route render>
  • <Route children>

Route component

仅在位置匹配时根据 route props 渲染

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";

// All route props (match, location and history) are available to User
function User(props) {
  return <h1>Hello {props.match.params.username}!</h1>;
}

ReactDOM.render(
  <Router>
    <Route path="/user/:username" component={User} />
  </Router>,
  node
);

当你在使用此方法加载时,router 通常会根据给定的 component 去调用 React.createElement 创建一个新的 React 元素。这也就意味着如果你在此是用内联函数渲染的组件,那么它将会在每次渲染时都创建一个新的函数,即卸载组件安装新有组件,而不是更新。所以当你使用内联函数渲染组件时,请选择 renderchildren 方法

Route render

你可以传入一个在位置匹配时调用的函数,而不是使用组件 prop 为您创建一个新的 React 元素

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";

// convenient inline rendering
ReactDOM.render(
  <Router>
    <Route path="/home" render={() => <div>Home</div>} />
  </Router>,
  node
);

// wrapping/composing
// You can spread routeProps to make them available to your rendered Component
function FadingRoute({ component: Component, ...rest }) {
  return (
    <Route
      {...rest}
      render={routeProps => (
        <FadeIn>
          <Component {...routeProps} />
        </FadeIn>
      )}
    />
  );
}

ReactDOM.render(
  <Router>
    <FadingRoute path="/cool" component={Something} />
  </Router>,
  node
);

需要注意的是:<Route component> 的优先级比 <Route render> 高,所以不要在同一个 Route 中同时调用两种方法

Route children

工作原理同 <Route render> ,只是无论是否匹配都会调用

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

function ListItemLink({ to, ...rest }) {
  return (
    <Route
      path={to}
      children={({ match }) => (
        <li className={match ? "active" : ""}>
          <Link to={to} {...rest} />
        </li>
      )}
    />
  );
}

ReactDOM.render(
  <Router>
    <ul>
      <ListItemLink to="/somewhere" />
      <ListItemLink to="/somewhere-else" />
    </ul>
  </Router>,
  node
);

需要注意的是:<React children> 优先级比上面两个优先级都高

而这三种渲染方式都会传递相同的 route props

  • match :包含 <Route path> 如何匹配 URL 的信息
  • location :代表应用程序现在所在的位置
  • history : 用于在 JavaScript 中以各种方式管理会话历史记录

路由导航,例如:Link 、NavLink 、Redirect

  • <Link> 组件将在您的应用程序中创建链接。无论在何处呈现 <Link>,锚点都将呈现在 HTML 文档中,而其最终会被渲染成 a 元素
  • NavLinkLink 基础之上增加了一些样式属性,当其 prop 与当前位置匹配时,可以给它设置一个 activeClassName(被选中) 的样式。
  • 任何时候要强制导航,你都可以使用 <Redirect>,当呈现 <Redirect> 时,将根据 propto 值进行导航。
NavLink

路径选中时,对应的 a 元素变为红色

  • activeStyle:活跃时(匹配时)的样式;
  • activeClassName:活跃时添加的 class;
  • exact:是否精准匹配;
<NavLink to="/" activeStyle={{color: "red"}}>home</NavLink>
<NavLink to="/about" activeStyle={{color: "red"}}>about</NavLink>
<NavLink to="/profile" activeStyle={{color: "red"}}>profile</NavLink>

但是,我们会发现在选中 aboutprofile 时,第一个也会变成红色:

  • 原因是/路径也匹配到了 /about/profile
  • 这个时候,我们可以在第一个 NavLink 中添加上 exact 属性
<NavLink exact to="/" activeStyle={{ color: 'red' }}>
  home
</NavLink>

默认的 activeClassName

  • 事实上在默认匹配成功时,NavLink 就会添加上一个动态的 active class
  • 所以我们也可以直接编写样式
a.active {
  color: red;
}

当然,如果你担心这个 class 在其他地方被使用了,出现样式的层叠,也可以自定义 class

<NavLink exact to="/" activeClassName="link-active">home</NavLink>
<NavLink to="/about" activeClassName="link-active">about</NavLink>
<NavLink to="/profile" activeClassName="link-active">profile</NavLink>
Redirect

Redirect 用于路由的重定向,当这个组件出现时,就会执行跳转到对应的 to 路径中,而 Redirect 最常见的场景即鉴权

  • 用户浏览器输入 User 界面 url
  • 若用户未登录,需重定向到登录页面。若已登录,则可进入 User 界面

App.js 中提前定义好 Login 页面对应的 Route

<Switch>
  ...其他Route
  <Route path="/login" component={Login} />
  <Route component={NoMatch} />
</Switch>

User.js 中写上对应的逻辑代码:

import React, { PureComponent } from 'react'
import { Redirect } from 'react-router-dom'

export default class User extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      isLogin: false,
    }
  }

  render() {
    return this.state.isLogin ? (
      <div>
        <h2>微信公众号:小和山的菜鸟们</h2>
        <h2>用户名: Coder tailor</h2>
      </div>
    ) : (
      <Redirect to="/login" />
    )
  }
}

react-router 高级使用

此示例显示嵌套路由的工作原理。路由 /topics 加载 Topics 组件,该组件根据路径 :id 值有条件地呈现任何进一步的 <Route>

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

export default function App() {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
          <li>
            <Link to="/topics">Topics</Link>
          </li>
        </ul>

        <Switch>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/topics">
            <Topics />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

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

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

function Topics() {
  let match = useRouteMatch();

  return (
    <div>
      <h2>Topics</h2>

      <ul>
        <li>
          <Link to={`${match.url}/components`}>Components</Link>
        </li>
        <li>
          <Link to={`${match.url}/props-v-state`}>
            Props v. State
          </Link>
        </li>
      </ul>

      {/* Topics 页面有自己的 <Switch>,其中包含更多基于 /topics URL 路径
          的路由。您可以将此处的第二个 <Route> 视为所有主题的“索引”页面,或者
          未选择主题时显示的页面 */}
      
      <Switch>
        <Route path={`${match.path}/:topicId`}>
          <Topic />
        </Route>
        <Route path={match.path}>
          <h3>Please select a topic.</h3>
        </Route>
      </Switch>
    </div>
  );
}

function Topic() {
  let { topicId } = useParams();
  return <h3>Requested topic ID: {topicId}</h3>;
}

当我们讨论动态路由时,我们是指在您的应用渲染时发生的路由,而不是在运行中的应用之外配置或约定的。

这意味着几乎所有内容都是 React Router 中的一个组件。下面是对该 API 的回顾,以了解其工作原理:

首先,为您要定位的环境获取一个 Router 组件,并将其呈现在应用程序的顶部。

// react-dom (what we'll use here)
import { BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  el
);

const App = () => (
  <div>
    <nav>
      <Link to="/dashboard">Dashboard</Link>
    </nav>
    <div>
      <Route path="/dashboard" component={Dashboard} />
    </div>
  </div>
);

Route 将呈现 <Dashboard {... props} />,其中 props 是路由器特定的东西,比如 { match, location, history } 。如果用户不在 / dashboard 上,则 Route 将呈现 null

react-router-config

路由配置的方式多种多样,让我们看一下来自官网的最佳实践

// 路由配置只是数据
// React 非常擅长将数据映射到组件中,而 <Route> 是一个组件.

//我们的路由配置只是一个带有 path 和 component props 的逻辑"路由"数组
//排序方式与你在 `<Switch>` 中的顺序相同。

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

嵌入 App 中

export default function App() {
  ReactDOM.render(
    <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>
  ,document.getElementById('root')
  )
}

组件在这儿

function Sandwiches() {
  return <h2>Sandwiches</h2>;
}

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> 的特殊包装器,它知道如何通过将"子"路由
//传递到它呈现的组件的 `routes` 属性来处理它们。
function RouteWithSubRoutes(route) {
  return (
    <Route
      path={route.path}
      render={props => (
        // pass the sub-routes down to keep nesting
        <route.component {...props} routes={route.routes} />
      )}
    />
  );
}

本节我们学习了 React-Router 中高阶组件以及组件补充的内容,至此,React 相关知识我们已学习完毕,下一节我们将为之前的留言板加上登录功能!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK