React 学习笔记 4(React -router-dom 源码解析)

1.BrowserRouter与HashRouter对比:

  • HashRouter 最简单,不需要服务器端渲染,靠浏览器的 #
    的来区分 path 就可以, BrowserRouter 需要服务器端对不
    同的 URL 返回不同的 HTML
  • BrowserRouter 使⽤ HTML5 history API pushState
    replaceState popstate 事件),让⻚⾯的 UI URL
    步。
    • HashRouter不⽀持location.keylocation.state,动态路
    由跳转需要通过 ? 传递参数。
    • Hash history 不需要服务器任何配置就可以运⾏,如果你
      刚刚⼊⻔,那就使⽤它吧。但是我们不推荐在实际线上环
      境中⽤到它,因为每⼀个 web 应⽤都应该渴望使⽤
      browserHistory。

2.BrowserRouter.js

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

export default class BrowserRouter extends Component {
    static computeRootMatch(pathname) {
        return {
            path: "/",
            url: '/',
            params: {},
            isExact: pathname = "/"
        }
    }
    constructor(props) {
        super(props);
        this.history = createBrowserHistory();
        this.state = {
            location: this.history.location
        }
        this.unlisten = this.history.listen(location => {
            this.setState({ location })
        })
    }
    componentWillUnmount(){
        if(this.unlisten) {
            this.unlisten()
        }
    }
    render() {
        return (
            // 需要提供history location match三种属性
            <RouterContext.Provider
                value ={{
                    history: this.history,
                    location: this.state.location,
                    match: BrowserRouter.computeRootMatch(this.state.location.pathname)
                }}
            >
                {this.props.children}
            </RouterContext.Provider>
        )
    }
}

 3.Link.js

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

export default class Link extends Component {
    // 处理点击事件
    handleClick = (event, history) => {
        // 禁止默认行为
        event.preventDefault();
        history.push(this.props.to);
      };
    render() {
        const { to, children } = this.props;
        return (
            <RouterContext.Consumer>
                {
                    context => (
                        <a
                            href={to}
                            onClick={event => this.handleClick(event, context.history)}>
                            {children}
                        </a>
                    )
                }
            </RouterContext.Consumer>
        )
    }
}

4.Route.js

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

// 这里的children不管是否匹配match都可以存在,这里能不能直接返回,就不判断了
// match 匹配 children是function或者是节点
// 不match 不匹配  children是function

export default class Route extends Component {
    render() {
        return (
            <RouterContext.Consumer>
                {context => {
                    const { path, computedMatch, children, component, render } = this.props;
                    // 首先获取props中的location
                    const location = this.props.location || context.location;
                    const match = computedMatch
                        ? computedMatch
                        : path
                            ? matchPath(location.pathname, this.props)
                            : context.match;
                    const props = {
                        ...context,
                        location,
                        match
                    };
                    return (
                        <RouterContext.Provider value={props}>
                            {/* 首先匹配children>component>render
                                1. 匹配上是children>component>render
                                2. 不匹配是children或者null
                                3. children 可以是一个函数或者组件

                             */}
                            {match
                                ? children
                                    ? typeof children === "function"
                                        ? children(props)
                                        : children
                                    : component
                                        ? // ? React.cloneElement(element, props)
                                        React.createElement(component, props)
                                        : render
                                            ? render(props)
                                            : null
                                : typeof children === "function"
                                    ? children(props)
                                    : null}
                        </RouterContext.Provider>
                    );
                }}
            </RouterContext.Consumer>
        );
    }
}

5.Switch.js

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

export default class Switch extends Component {
    render() {
        return (
            <RouterContext.Consumer>
                {context => {
                    // 找出渲染的,第一个符合匹配的元素,存在element
                    // const {location} = context;
                    // 优先用props上的location
                    const location = this.props.location || context.location;
                    let element,
                        match = null;
                    let { children } = this.props;
                    React.Children.forEach(children, child => {
                        console.log('child', child)
                        if (match === null && React.isValidElement(child)) {
                            element = child;
                            const path = child.props.path;
                            match = path
                                ? matchPath(location.pathname, {
                                    ...child.props,
                                    path
                                })
                                : context.match;
                        }
                    });
                    return match
                        ? React.cloneElement(element, {
                            location,
                            computedMatch: match
                        })
                        : null;
                }}
            </RouterContext.Consumer>
        );
    }
}

6.Redirect.js

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

export default class Redirect extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          const {history} = context;
          const {to} = this.props;
          // history.push(to)
          return <LifeCycle onMount={() => history.push(to)} />;
        }}
      </RouterContext.Consumer>
    );
  }
}

class LifeCycle extends Component {
    // 组件挂载
  componentDidMount() {
    if (this.props.onMount) {
      this.props.onMount();
    }
  }
  render() {
    return null;
  }
}

7.RouterContext.js

import React from "react";

export const RouterContext = React.createContext();

8.matchPath.js

import pathToRegexp from "path-to-regexp";

const cache = {};
const cacheLimit = 10000;
let cacheCount = 0;

function compilePath(path, options) {
  const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
  const pathCache = cache[cacheKey] || (cache[cacheKey] = {});

  if (pathCache[path]) return pathCache[path];

  const keys = [];
  const regexp = pathToRegexp(path, keys, options);
  const result = { regexp, keys };

  if (cacheCount < cacheLimit) {
    pathCache[path] = result;
    cacheCount++;
  }

  return result;
}

/**
 * Public API for matching a URL pathname to a path.
 */
function matchPath(pathname, options = {}) {
  if (typeof options === "string" || Array.isArray(options)) {
    options = { path: options };
  }

  const { path, exact = false, strict = false, sensitive = false } = options;

  const paths = [].concat(path);

  return paths.reduce((matched, path) => {
    if (!path && path !== "") return null;
    if (matched) return matched;

    const { regexp, keys } = compilePath(path, {
      end: exact,
      strict,
      sensitive
    });
    const match = regexp.exec(pathname);

    if (!match) return null;

    const [url, ...values] = match;
    const isExact = pathname === url;

    if (exact && !isExact) return null;

    return {
      path, // the path used to match
      url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
      isExact, // whether or not we matched exactly
      params: keys.reduce((memo, key, index) => {
        memo[key.name] = values[index];
        return memo;
      }, {})
    };
  }, null);
}

export default matchPath;

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值