从源码对react-router v5进行原理分析(一)

时间:2020-9-3 作者:admin

目录

  • react-router介绍
  • react-router路由跳转原理

    • 常见路由模式
    • 模拟react-router路由跳转
  • 总结

react-router介绍

Declarative routing for React

来自 react-router github 介绍

react-router仓库的简介是: 为React声明路由

Components are the heart of React’s powerful, declarative programming model. React Router is a collection of navigational components that compose declaratively with your application. Whether you want to have bookmarkable URLs for your web app or a composable way to navigate in React Native, React Router works wherever React is rendering–so take your pick!

来自 react-router官方文档

官方文档中对react-router的介绍是: 组件是React强大的声明式编程模型的核心. React Router是一组以声明方式与你的应用程序组合起来的导航组件集合. 不管你是否想要为你的web应用添加可书签的url, 或是在React Native中添加一个可组合的导航方式, React Router都能在React渲染的地方工作, 所以你可以选择!

简而言之, react-routerReact提供了路由能力, 不管是web应用或是React Native应用, 都可以使用react-router进行路由管理;

这里只对web应用的路由原理进行分析

react-router路由跳转原理

常见路由模式

SPA(单页面应用)的路由模式一般分为两种:

  1. hash模式
  2. history模式

源码分析react-router路由跳转

在引入了react-routerReact应用中, 我们通常使用react-router-dom提供的Link组件进行路由跳转; 在Link组件中, 路由跳转相关代码如下:

const method = replace ? history.replace : history.push;

method(location);

replace表示是否替换当前路由, location表示跳转的路由

可以看出, react-router实现路由跳转主要使用了history.replace以及history.push, 往上层探究后发现, 这里的historyreact-router开发者实现的一个库, 对window.history进行封装, 利用window.history.pushStatewindow.history.replaceState两个api, 实现url跳转而无须重新加载页面;

模拟react-router路由跳转

react-router中路由跳转之类的路由操作便是通过history库完成的, 下面使用create-react-app(react^16.13.1)写了一个小栗子🌰, 简单实现了一下history路由跳转的原理:

History.ts

interface Listener {
  (url: string): void
};

interface History {
  listeners: Array<Listener>,
  push: {
    (url: string, state?: {[propsName: string]: any} | null): void
  },
  listen: {
    (fn: Listener): {(): void}
  }
};
const createHistory = (): History => {
  const globalHistory = window.history;
  const _history: History = {
    listeners: [],
    listen(fn) {
      this.listeners.push(fn);
      return () => {
        let i: number = -1;
        this.listeners.find((listener, index) => {
          if (listener === fn) {
            i = index;
          }
          return listener === fn;
        });
        if (i !== -1) {
          this.listeners.splice(i, 1);
        }
      };
    },
    push(url, state) {
      globalHistory.pushState(state, '', url);
      this.listeners.forEach(listener => {
        listener(url);
      });
    }
  };
  return _history;
};

export default createHistory;

上面是一个简单实现的history库, 只实现了push的功能(未实现replace功能), 主要分为三个部分:

  1. listeners: 数组类型, 当history.push调用时, 依次执行listeners中的函数;
  2. listen: 函数类型, 接受一个函数listener作参数, 并将listener 加到listeners中, 等待history.push执行; 返回一个函数unlisten, 执行时将当前的listenerlisteners中移除;
  3. push: 函数类型, 接收一个url作为参数, 执行globalHistory.pushState(此处的globalHistorywindow.history), 并依次执行listeners中所有函数;

从上面代码可以看出, history主要运用了订阅-发布设计模式的思想;

App.ts

import React, {useEffect, useState} from 'react';
import createHistory from './history';
const history = createHistory();

const Page1: React.FC = props => {
  return <div>Page1</div>;
};

const Page2: React.FC = props => {
  return <div>Page2</div>;
};

const App: React.FC = props => {
  const [location, setLocation] = useState<string>(window.location.pathname);
  const pushHistory = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>, url: string): void => {
    event.preventDefault();
    history.push(url);
  };
  const renderComponent = (): ReactElement => {
    switch (location) {
      case '/page1': {
        return <Page1></Page1>;
      }
      case '/page2': {
        return <Page2></Page2>;
      }
      default: {
        return <Page1></Page1>;
      }
    }
  };

  useEffect(() => {
    // 页面首次渲染完成后执行
    history.listen((url) => {
      setLocation(url);
    });
  }, []);

  return (
    <div>
      <div className="nav">
        <a href="/page1" onClick={(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => pushHistory(event, '/page1')}>page1</a>
        <a href="/page2" onClick={(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => pushHistory(event, '/page2')}>page2</a>
      </div>
      <div>{renderComponent()}</div>
    </div>
  );
};

export default App;

上面的代码生成的页面结构分为:

  • 导航部分: 对超链接的默认事件进行阻止, 避免刷新页面, 并绑定新的点击事件, 触发history.push进行路由跳转;
  • 路由组件渲染部分: 通过location变量渲染对应的路由组件;

代码逻辑结构如下:

  1. 创建一个history示例;
  2. 执行renderComponent函数, 渲染出当前路由对应组件;
  3. App首次渲染完成时使用history.listen注册一个监听事件, 事件调用时使用setLocationlocation设置为url参数; 并将history.listen返回的函数赋值给变量unlisten;
  4. 点击超链接, 执行history.push跳转路由, 执行history.listen中的回调函数, 执行setLocation修改location变量的值, 导致组件重新渲染, renderComponent函数重新执行, 路由组件成功渲染;
  5. 退出页面时, 执行unlisten函数, 销毁当前监听事件;

总结

这篇文章主要是对react-router中路由跳转原理的分析, 并自行实现了一个简单的history库, 当然history库的逻辑更为复杂, 这里并不深究; 如果喜欢请点个赞吧, 下一篇文章将会接着对react-router的组件进行源码分析, 冲!
如果发现文章有错误可以在评论区里留言哦, 欢迎指正!

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。