这次的版本是 6.0.2

这里只讲 react-router-dom 提供的 API, 像是 Routes, Router 这些都是 react-router 提供的

BrowserRouter, HashRouter

BrowserRouter 和 hashRouter 的次要区别就在于应用的路由 API

简略解释

BrowserRouter

它应用了 history 库 的API,也就是说,浏览器(IE 9和更低版本以及同时代的浏览器)是不可用的。
客户端React应用程序可能保护洁净的路由,如 example.com/react/route ,但须要失去Web服务器的反对。
这须要Web服务器应该被配置为单页应用程序,即为/react/route门路或服务器上的任何其余路由提供雷同的index.html。

HashRouter

它应用URL哈希,对反对的浏览器或网络服务器没有限度, 如 example.com/#/react/route.

成果是所有后续的URL门路内容在服务器申请中被疏忽(即你发送 "www.mywebsite.com/#/person/john",服务器失去 "www.mywebsite.com"。
因而,服务器将返回前#URL响应,而后后#门路将由你的客户端反馈程序进行解析解决。

代码解析

先说 hashRouter , 他的依赖度是最低的, 代码也很简略:

import {createHashHistory} from "history";function HashRouter({basename, children, window}: HashRouterProps) {    let historyRef = React.useRef<HashHistory>(); // 用来存储 createHashHistory 后果     if (historyRef.current == null) {        historyRef.current = createHashHistory({window});    }    let history = historyRef.current;    let [state, setState] = React.useState({        action: history.action,        location: history.location    });    React.useLayoutEffect(() => history.listen(setState), [history]);    return (        <Router            basename={basename}            children={children}            location={state.location}            navigationType={state.action}            navigator={history}        />    );}

这里须要理解的一个 API 是 createHashHistory, 他来自于 history 仓库, 这里咱们须要解析一下这个办法:

/** * 此办法里并不是全副的源码, 省略了局部不太 core 的代码 */function createHashHistory(    options: HashHistoryOptions = {}): HashHistory {    let {window = document.defaultView!} = options; // window 是传递的参数    let globalHistory = window.history; // 全局的 history 对象    // 获取以后 state.idx 和 location 对象    function getIndexAndLocation(): [number, Location] {        let {            pathname = '/',            search = '',            hash = ''        } = parsePath(window.location.hash.substr(1)); // 解析 hash        let state = globalHistory.state || {};        return [            state.idx,            readOnly<Location>({                pathname,                search,                hash,                state: state.usr || null,                key: state.key || 'default'            })        ];    }    let blockedPopTx: Transition | null = null;    // pop 的操作 最终调用的是 go() 函数    function handlePop() {        // 省略    }    // popstate 事件监听    window.addEventListener(PopStateEventType, handlePop);    // hashchange 事件监听  ie11 中存在问题    window.addEventListener(HashChangeEventType, () => {        let [, nextLocation] = getIndexAndLocation();        // 疏忽内部的 hashchange 事件  createPath = pathname + search + hash         if (createPath(nextLocation) !== createPath(location)) {            handlePop();        }    });    // Action 是一个 枚举, Pop = 'POP'    let action = Action.Pop;    let [index, location] = getIndexAndLocation();    /**     *  createEvents 办法     *  一个闭包办法, 保护一个数组,相似观察者模式, 返回 push, call 两个办法     */    let listeners = createEvents<Listener>();    let blockers = createEvents<Blocker>();        // 罕用的 push 办法    function push(to: To, state?: any) {        let nextAction = Action.Push; // 枚举 Action.Push = 'PUSH'        let nextLocation = getNextLocation(to, state); // 生成一个新的 location 对象        function retry() {            push(to, state);        }        // blockers 为空的时候        if (allowTx(nextAction, nextLocation, retry)) {            // 依据 location 生成须要的对象, 只是数据格式更改了下            /* historyState = {                usr: nextLocation.state,                key: nextLocation.key,                idx: index            }*/            let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);            try {                // 调用原生 API, history.pushState                globalHistory.pushState(historyState, '', url);            } catch (error) {                // 不兼容就应用这个                window.location.assign(url);            }            applyTx(nextAction); // listeners 中增加回调 nextAction        }    }    function replace(to: To, state?: any) {        // 同 push, 只不过调用的原生改成了这个  globalHistory.replaceState(historyState, '', url);    }    function go(delta: number) { // 原生 go 办法        globalHistory.go(delta);    }    let history: HashHistory = { // 定义的部分 history 对象, 最初要返回的        get action() {            return action;        },        get location() {            return location;        },        createHref,        push,        replace,        go,        back() {            go(-1);        },        forward() {            go(1);        },        listen(listener) {            return listeners.push(listener);        },        block(blocker) {            let unblock = blockers.push(blocker);            if (blockers.length === 1) {                window.addEventListener(BeforeUnloadEventType, promptBeforeUnload);            }            return function () {                // 在页面 UnMount 的时候调用                unblock();                if (!blockers.length) {                    window.removeEventListener(BeforeUnloadEventType, promptBeforeUnload);                }            };        }    };    return history;}

Link

一个常常用到的小组件, 罕用来做跳转

const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(    function LinkWithRef(        { onClick, reloadDocument, replace = false, state, target, to, ...rest },        ref    ) {        // useHref 来自于 react-router 中, 用来 parse URL        let href = useHref(to);                // 实在点击跳转调用的函数, 具体源码在上面给出        let internalOnClick = useLinkClickHandler(to, { replace, state, target });                // 点击 a 标签的句柄, 如果有 onClick 事件 则优先        function handleClick(            event: React.MouseEvent<HTMLAnchorElement, MouseEvent>        ) {            if (onClick) onClick(event);            if (!event.defaultPrevented && !reloadDocument) {                internalOnClick(event);            }        }        return (            <a                {...rest}                href={href}                onClick={handleClick}                ref={ref}                target={target}            />        );    });

useLinkClickHandler

来看看这个 hooks 的具体形成

export function useLinkClickHandler<E extends Element = HTMLAnchorElement>(    to: To,    {        target,        replace: replaceProp,        state    }: {        target?: React.HTMLAttributeAnchorTarget;        replace?: boolean;        state?: any;    } = {}): (event: React.MouseEvent<E, MouseEvent>) => void {    // 来源于 react-router, 获取 navigate 函数, 能够用来跳转    let navigate = useNavigate();    // 获取以后的 location 对象(非 window.location)    let location = useLocation();    // 同样来源于react-router,   解析 to, 获取 path    let path = useResolvedPath(to);    return React.useCallback(        (event: React.MouseEvent<E, MouseEvent>) => {            if (                event.button === 0 && // 疏忽除了左键点击以外的, 参考: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button                (!target || target === "_self") &&                 !isModifiedEvent(event) // 疏忽各类键盘按钮, 参考 https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent                //  isModifiedEvent = !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);            ) {                event.preventDefault();                // 比照是否有变动                let replace =                    !!replaceProp || createPath(location) === createPath(path);                navigate(to, { replace, state });            }        },        [location, navigate, path, replaceProp, state, target, to]    );}

NavLink

等于是对 Link 组件的包装

const NavLink = React.forwardRef<HTMLAnchorElement, NavLinkProps>(    function NavLinkWithRef(        {            "aria-current": ariaCurrentProp = "page",            caseSensitive = false,            className: classNameProp = "",            end = false,            style: styleProp,            to,            ...rest        },        ref    ) {        // 这两个 hooks 上述曾经说过        let location = useLocation();        let path = useResolvedPath(to);        let locationPathname = location.pathname;        let toPathname = path.pathname;        if (!caseSensitive) { // 反对字符串大小写不敏感            locationPathname = locationPathname.toLowerCase();            toPathname = toPathname.toLowerCase();        }        // 是否 active        // /user =>  /user/name         let isActive =            locationPathname === toPathname ||            (!end &&                locationPathname.startsWith(toPathname) &&                locationPathname.charAt(toPathname.length) === "/");                // aria 是帮忙残障人士辅助浏览的        let ariaCurrent = isActive ? ariaCurrentProp : undefined;        // class 款式计算        let className: string;        if (typeof classNameProp === "function") {            className = classNameProp({ isActive });        } else {            className = [classNameProp, isActive ? "active" : null]                .filter(Boolean)                .join(" ");        }        let style =            typeof styleProp === "function" ? styleProp({ isActive }) : styleProp;        return (            <Link                {...rest}                aria-current={ariaCurrent}                className={className}                ref={ref}                style={style}                to={to}            />        );    });

useSearchParams

用来获取/设置 query 的 hooks

export function useSearchParams(defaultInit?: URLSearchParamsInit) {    // createSearchParams 的源码上面会讲, 大体是包装了 URLSearchParams     // 相干知识点: https://developer.mozilla.org/zh-CN/docs/Web/API/URLSearchParams    let defaultSearchParamsRef = React.useRef(createSearchParams(defaultInit));    // 获取 location     let location = useLocation();        // 解析, 通过比照 更新    let searchParams = React.useMemo(() => {        let searchParams = createSearchParams(location.search);        for (let key of defaultSearchParamsRef.current.keys()) {            if (!searchParams.has(key)) {                defaultSearchParamsRef.current.getAll(key).forEach(value => {                    searchParams.append(key, value);                });            }        }        return searchParams;    }, [location.search]);    let navigate = useNavigate();    // 通过 navigate 办法 实现 location.search 的变更    let setSearchParams = React.useCallback(        (            nextInit: URLSearchParamsInit,            navigateOptions?: { replace?: boolean; state?: any }        ) => {            // URLSearchParams toString 就成了 query 格局            navigate("?" + createSearchParams(nextInit), navigateOptions);        },        [navigate]    );    return [searchParams, setSearchParams] as const;}

createSearchParams

function createSearchParams(    init: URLSearchParamsInit = ""): URLSearchParams {    // 通过原生 api 创立, 数组的话 就相似于 tuple    return new URLSearchParams(        typeof init === "string" ||        Array.isArray(init) ||        init instanceof URLSearchParams            ? init            : Object.keys(init).reduce((memo, key) => {                let value = init[key];                return memo.concat(                    Array.isArray(value) ? value.map(v => [key, v]) : [[key, value]]                );            }, [] as ParamKeyValuePair[])    );}

总结

react-router-dom 其实就像 react-router 的再度包装, 给开发提供了良好的基础设施

援用

  • https://stackoverflow.com/que...

相干知识点汇总

  • https://developer.mozilla.org...
  • https://developer.mozilla.org...
  • https://developer.mozilla.org...