2021-02-04 React-router的使用及源码API实现

2021-02-04 React-router的使用及源码API实现

react-router简介
react-router包含三个库,react-router、react-router-dom和react-router-native。react-router提供最基本的路由功能,实际使用的时候根据应用运行的环境选择安装react-router-dom(在浏览器中使用)或react-router-native(在react-native中使用)

yarn add react-router-dom 或
yarn add react-router-native

基本使用
react-router中奉行一切皆组件的思想,路由器-Router、链接-Link、路由Route、独占Switch、重定向-Redirect都已组件的形式存在

import React from "react";
import { BrowserRouter as Router, Link, Route, Switch } from "react-router-dom";
import HomePage from "./HomePage";
import LoginPage from "./LoginPage";
import UserPage from "./UserPage";

function RouterPage() {
  return (
    <div>
      <h3>RouterPage</h3>
      <Router>
        <nav>
          <Link to="/">首页</Link> <Link to="/user">用户中心</Link>{" "}
          <Link to="/login">登录</Link> <Link to="/product/123">商品</Link>
        </nav>
        <Switch>
          <Route path="/" exact component={HomePage} />
          <Route path="/user" component={UserPage} />
          <Route path="/login" component={LoginPage} />
          <Route render={() => <h3>404 not found</h3>} />
        </Switch>
      </Router>
    </div>
  );
}
export default RouterPage;

Route渲染内容的三种方式
Route渲染优先级:children>component>render
三者都能收到同样的[route props],包括match,location,history,但是当不匹配的时候的children的match为null
这三种方式互斥,只能选择其中一种

children:func
有时候,不管location是否匹配,你都要渲染一些内容,这时候可以用children,除了不管location是否都匹配都会被渲染之外,其他工作方式与render完全一样,另外用了Switch独占组件还是选择条件选择匹配

render:func
当你用render的时候,你调用的是只是个函数,但是它和component一样,能访问到所有的[route props]

component:component
只有当location匹配的时候才渲染

注意:当用component的时候,Route会用你指定的组件和React.createElement创建一个新的[React element]。这意味着你提供的是一个内联函数的时候,每次render都会创建一个新的组件。这会导致不再更新已有组件,而是直接卸载然后挂载一个新的组件。因此,当用到内联函数的内联渲染时,请使用render或者children

摘自源码片段:
route
BrowserRouter与HashRouter对比
1、HashRouter最简单,不需要服务器渲染,靠浏览器的#来区分path就可以,BrowserRouter需要服务器端对不同的URL返回不同的HTML,后端需要配置
2、BrowserRouter使用HTML5 history API( pushState,replaceState和popstate事件),让页面的UI同步与URL
3、HashRouter不支持location.key和location.state,动态路由跳转需要通过?传递参数
4、Hash history不需要服务器任何配置就可以运行

API源码实现

Router
所有Router组件的通用低阶接口。通常情况下,会使用以下其中一个高阶Router:

  • BrowserRouter
  • HashRouter
  • MemoryRouter
  • NativeRouter
  • StaticRouter
import React, { Component } from "react";
import RouterContext from "./RouterContext";

class Router extends Component {
  static computeRootMatch(pathname) {
    return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
  }
  constructor(props) {
    super(props);
    this.state = {
      location: props.history.location
    };

    // 监听location变化
    this.unlisten = props.history.listen(location => {
      this.setState({ location });
    });
  }

  componentWillUnmount() {
    // 取消监听
    if (this.unlisten) {
      this.unlisten();
    }
  }
  render() {
    const { history, children } = this.props;
    return (
      <RouterContext.Provider
        value={{
          history,
          location: this.state.location,
          match: Router.computeRootMatch(this.state.location.pathname)
        }}
      >
        {children}
      </RouterContext.Provider>
    );
  }
}
export default Router;

BrowserRouter
<BrowserRouter>使用HTML5提供的history API(pushState,replaceStatepopState事件)来保持UI和URL的同步

HashRouter
<HashRouter>使用URL的hash部分(即window.location.hash)来保持UI和URL的同步

MemoryRouter
把URL的历史记录保存在内存中的<Router>(不读取、不写入地址栏)。在测试和非浏览器环境中很有用,如React Native

实现BrowserRouter
BrowserRouter:历史记录管理对象history初始化以及向下传递,location变更监听

import { createBrowserHistory } from "history";
import { Component } from "react";
import Router from "./Router";

class BrowserRouter extends Component {
  constructor(props) {
    super(props);
    this.history = createBrowserHistory();
  }
  render() {
    return <Router children={this.props.children} history={this.history} />;
  }
}

export default BrowserRouter;


Route
<Route>是React Router中最重要的组件,其最基本的职责是在其path属性与某个location匹配时呈现一些UI
使用<Route>渲染方式有以下三种:

  • component
  • render: func
  • children: func

在不同情况下使用不同的方式,在指定的<Route>中应该只使用其中一种

路由配置,匹配检测,内容渲染
match按照互斥规则,优先渲染顺序为children>component>render>null,child如果是function执行function,是节点直接渲染
不match children或者null(只渲染function)

import React, { Component } from "react";
import matchPath from "./matchPath";
import RouterContext from "./RouterContext";

class Route extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          const { location } = context;

          const {
            children,
            component,
            render,
            path,
            computedMatch
          } = this.props;
          const match = computedMatch
            ? computedMatch
            : path
            ? matchPath(location.pathname, this.props)
            : context.match; // location.pathname === path

          const props = { ...context, match };

          // match
          // 匹配 children, component, render, null
          // 不匹配 children(function)
          // return match?React.createElement(component):null

          return (
            <RouterContext.Provider value={props}>
              {match
                ? children
                  ? typeof children === "function"
                    ? children(props)
                    : children
                  : component
                  ? React.createElement(component, props)
                  : render
                  ? render(props)
                  : null
                : typeof children === "function"
                ? children(props)
                : null}
            </RouterContext.Provider>
          );
        }}
      </RouterContext.Consumer>
    );
  }
}
export default Route;

Link
<Link>跳转链接,处理点击事件
参数
to: string | object一个字符串或object对象形式的链接地址,通过pathname,search,hash,state等创建
replace: bool当设置为true时,点击链接后将替换历史堆栈中的当前条目,而不是添加新条目。默认为false
others属性如title,id,className等

import { useContext } from "react";
import RouterContext from "./RouterContext";

function Link({ children, to, ...restProps }) {
  const { history } = useContext(RouterContext);
  const handleClick = e => {
    e.preventDefault(); // 阻止默认点击行为
    // 命令式跳转
    history.push(to);
  };
  return (
    <a href={to} {...restProps} onClick={handleClick}>
      {children}
    </a>
  );
}
export default Link;

Switch
用于渲染与路径匹配的第一个子<Route><Redirect>
<Switch>只会渲染一个路由

import React, { Component } from "react";
import matchPath from "./matchPath";
import RouterContext from "./RouterContext";

class Switch extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          const { location } = context;
          let match; // 标记匹配
          let element; // 标记匹配的元素

          // todo 遍历子元素
          // children

          React.Children.forEach(this.props.children, child => {
            if (match == null && React.isValidElement(child)) {
              element = child;
              match = child.props.path
                ? matchPath(location.pathname, child.props)
                : context.match;
            }
          });

          return match
            ? React.cloneElement(element, { computedMatch: match })
            : null;
        }}
      </RouterContext.Consumer>
    );
  }
}
export default Switch;

hooks实现

import { useContext } from "react";
import RouterContext from "./RouterContext";

// 自定义hooks
export function useHistory() {
  return useContext(RouterContext).history;
}

export function useLocation() {
  return useContext(RouterContext).location;
}

export function useRouteMatch() {
  return useContext(RouterContext).match;
}

export function useParams() {
  const match = useContext(RouterContext).match;
  return match ? match.params : {};
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React Router是一个用于构建单页面应用程序的库。它提供了一种在React应用程序中管理路由的方式。React Router DOM是React Router的一个扩展,用于在Web应用程序中进行路由。 React Router DOM 5和React Router DOM 6之间有几个重要的区别: 1. 安装方式:React Router DOM 5使用npm包管理器进行安装,命令为`npm install react-router-dom`。而React Router DOM 6使用yarn进行安装,命令为`yarn add react-router-dom@next`。 2. 路由组件:在React Router DOM 5中,使用`<Route>`组件来定义路由。而在React Router DOM 6中,使用`<Route>`组件的替代方案`<Routes>`来定义路由。 3. 路由匹配方式:React Router DOM 5使用基于路径的匹配方式来确定哪个路由应该被渲染。而React Router DOM 6引入了新的匹配方式,称为元素匹配(element matching),它可以根据组件的类型来匹配路由。 4. 嵌套路由:在React Router DOM 5中,嵌套路由需要使用嵌套的`<Route>`组件来定义。而在React Router DOM 6中,可以使用嵌套的`<Routes>`组件来定义嵌套路由。 5. 动态路由:在React Router DOM 5中,可以通过在路径中使用参数来定义动态路由。而在React Router DOM 6中,可以使用`<Route>`组件的新属性`element`来定义动态路由。 6. 错误处理:React Router DOM 5使用`<Switch>`组件来处理路由匹配错误。而React Router DOM 6使用`<Routes>`组件的新属性`fallback`来处理路由匹配错误。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值