react全家桶之router(三)拓展:BrowserRouter、Route、Link实现源码

react-router 秉承一切皆组件,因此实现的核心就是BrowserRouter、Route、Link
一、实现BrowserRouter、Route、Link
BrowserRouter: 历史纪录管理对象history初始化及向下传递,location变更监听
1、创建:my-react-router-dom.js

import React, { Component, createContext, useContext } from 'react'
import { createBrowserHistory } from "history"
import matchPath from './matchpath'

const RouterContext = createContext();
const RouterProvider = RouterContext.Provider;
const RouterConsumer = RouterContext.Consumer;

//实现BroserRouter
export class BrowserRouter extends Component {
    constructor(props) {
        super(props)
        //往下传递,在Link 中使用history
        this.history = createBrowserHistory(this.props);
        //location 获取最顶层的路径,保留下来往下传递,方便判断路由对应要显示的页面
        this.state = {
            location: this.history.location
        }
        //history.listen  因为location会变化,所以监听更新它的值
        this.unlisten = this.history.listen(location => {
            this.setState({ location })
        })
    }
    //卸载监听
    componentWillUnmount() {
        if (this.unlisten) {
            this.unlisten();
        }
    }
    render() {
        return (
            <div>
                <RouterProvider children={this.props.children || null}
                    value={{
                        history: this.history,
                        location: this.state.location
                    }}
                />
            </div>
        )
    }
}

// 实现Route
export function Route(props) {
    //useContext function组件获取上层组件传过来的值
    const ctx = useContext(RouterContext);
    //获取到 BrowserRouter 传来的location
    const { location } = ctx;
    const { path, component, children, render } = props;
    const match = matchPath(location.pathname, props);
    const matchCurrent = match && match.isExact;
    console.log('matchCurrent', matchCurrent);
    const cmpProps = { ...ctx, match };

    if (matchCurrent && typeof children === "function") {
        return children(cmpProps);
    }
    return (
        <>
            {typeof children === 'function' && children(cmpProps)}
            {
                matchCurrent && component
                    ? React.createElement(component, cmpProps)
                    : null
            }
            {matchCurrent && !component && render && render(cmpProps)}
        </>
    )
}

//实现Link  跳转链接,处理点击事件
export class Link extends Component {
    handleClick = (e, history) => {
        e.preventDefault();
        history.push(this.props.to)
    }
    render() {
        const { to, children } = this.props;

        return (
            <RouterConsumer>
                {ctx => (
                    <a href={to} onClick={e => this.handleClick(e, ctx.history)}>
                        {children}
                    </a>
                )}
            </RouterConsumer>
        )
    }
}


2、matchpath实现
Router组件依赖于matchpath
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") options = { path: options };

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

    const paths = [].concat(path);

    return paths.reduce((matched, path) => {
        if (!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;

3、测试
常见MyRouterPage.js

import React, { Component } from 'react'
import { BrowserRouter, Link, Route } from "../my-react-router-dom"

export function HomPage() {
    return <div>HomPage</div>
}
export function UserPage() {
    return <div>UserPage</div>
}


export default class MyRouterPage extends Component {
    render() {
        return (
            <div>
                <h1>MyRouterPage</h1>
                {/*
                 //BrowserRouter 可以往下传递一些值:如 history,location... 
                 BrowserRouter对数据做统一的管理,如果哪些数据改变了,就在顶层做数据的改变
                 ,对它下面所有的组件进行派发
                 */}
                <BrowserRouter>
                    <nav>
                        <Link to="/">首页</Link>
                        <Link to="/user">用户中心</Link>
                    </nav>
                    <Route exact path="/" component={HomPage} children={() => <div>children page</div>} />
                    <Route path="/user"
                        // component={UserPage}
                        render={() => <div>render page</div>} />
                </BrowserRouter>
            </div>
        )
    }
}

完毕!

发布了18 篇原创文章 · 获赞 0 · 访问量 363

猜你喜欢

转载自blog.csdn.net/weixin_39788999/article/details/104145996
今日推荐