mini-router

58 阅读1分钟

Router

/**
 * 将history注入Context上下文
 * 路由监听,路由改变重置location对象,通知消费context的Route、Switch等组件的监听。 unListen 解绑路由器
 */
 
import React, { useCallback, useState, useEffect, createContext, useMemo } from 'react';
import { createBrowserHistory as createHistory, History } from 'history';

export const RouterContext = createContext(null);
export let rootHistory = null;

export default function Router(props) {
    const history: History = useMemo(() => {
        rootHistory = createHistory();
        return rootHistory;
    }, []);
    const [location, setLocation] = useState(history.location);
    useEffect(() => {
        const unListen = history.listen((location) => {
            setLocation(location);
        });
        return function () {
            unListen?.();
        };
    });

    return <RouterContext.Provider
        value={{
            location,
            history,
            match: {
                path: '/',
                url: '/',
                params: {},
                isExact: location.pathname === '/',
            }
        }}
    >
        { props.children }
    </RouterContext.Provider>;
}

Route

import React, { useContext } from 'react';
import { matchPath } from 'react-router';
import { RouterContext } from './Router';

export default function Route(props: any) {
    const context: any = useContext(RouterContext);
    const location = props.location || context.location;
    /* 是否匹配当前路由,如果父级有switch,就会传入computedMatch来精确匹配渲染此路由 */
    const match = props.computedMatch ? props.computedMatch : (props.path ? matchPath(location.pathname, props) : context.match);

    const newRouterProps = { ...context, location, match };
    let { children, component, render } = props;
    if (Array.isArray(children) && children.length === 0) {
        children = null;
    }
    let renderChildren = null;
    // 不同组件设置方式的渲染
    if (newRouterProps.match) {
        if (children) {
            renderChildren = typeof children === 'function' ? children(newRouterProps): children;
        } else if (component) {
            renderChildren = React.createElement(component, newRouterProps);
        } else if (render) {
            renderChildren = render(newRouterProps);
        }
    }

    return <RouterContext.Provider value={ newRouterProps }>
        { renderChildren }
    </RouterContext.Provider>;
}

Switch

import React, { useContext } from 'react';
import { matchPath } from 'react-router';
import {  RouterContext} from './Router';

export default function Switch(props) {
    const context: any = useContext(RouterContext);
    const location = props.location || context.location;
    let children, match;
    React.Children.forEach(props.children, child => {
        if (!match && React.isValidElement(child)) {
            const path = child.props.path;
            children = child;
            match = path ? matchPath(location.pathname, {...child.props}) : context.match;
        }
    });
    return match ? React.cloneElement(children, {
        location: location,
        computedMatch: match
    }) : null;
}

withRouter

import React , { useContext } from 'react'
import hoistStatics from 'hoist-non-react-statics';
import { RouterContext } from './Router';

export default function withRouter(Component) {
    const WrapComponent = (props) => {
        const { wrappedComponentRef, ...otherProps } = props;
        const context = useContext(RouterContext);
        return <Component
            { ...otherProps }
            ref={ wrappedComponentRef }
            { ...context }
        />
    };

    return hoistStatics(WrapComponent, Component); // 通过hoist-non-react-statics继承原始组件的静态属性。
}

hook

import { useContext } from 'react';
import { RouterContext } from './Router';

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