createBrowserHistory.js

function createBrowserHistory(props = {}) {
    invariant(canUseDOM, "Browser history needs a DOM");
    // 获得全局的history
    const globalHistory = window.history;
    const canUseHistory = supportsHistory();
    const needsHashChangeListener = !supportsPopStateOnHashChange();

    const {
        forceRefresh = false,
        getUserConfirmation = getConfirmation,
        keyLength = 6
    } = props;
    const basename = props.basename
        ? stripTrailingSlash(addLeadingSlash(props.basename))
        : "";

    //  获取location对象
    function getDOMLocation(historyState) {
        const { key, state } = historyState || {};
        const { pathname, search, hash } = window.location;

        let path = pathname + search + hash;

        warning(
            !basename || hasBasename(path, basename),
            "You are attempting to use a basename on a page whose URL path does not begin " +
                'with the basename. Expected path "' +
                path +
                '" to begin with "' +
                basename +
                '".'
        );

        if (basename) path = stripBasename(path, basename);

        return createLocation(path, state, key);
    }

    function createKey() {
        return Math.random()
            .toString(36)
            .substr(2, keyLength);
    }

    const transitionManager = createTransitionManager();

    function setState(nextState) {
        Object.assign(history, nextState);
        history.length = globalHistory.length;
        transitionManager.notifyListeners(history.location, history.action);
    }

    function handlePopState(event) {
        // Ignore extraneous popstate events in WebKit.
        if (isExtraneousPopstateEvent(event)) return;
        handlePop(getDOMLocation(event.state));
    }

    function handleHashChange() {
        handlePop(getDOMLocation(getHistoryState()));
    }

    let forceNextPop = false;

    function handlePop(location) {
        if (forceNextPop) {
            forceNextPop = false;
            setState();
        } else {
            const action = "POP";

            transitionManager.confirmTransitionTo(
                location,
                action,
                getUserConfirmation,
                ok => {
                    if (ok) {
                        setState({ action, location });
                    } else {
                        revertPop(location);
                    }
                }
            );
        }
    }

    function revertPop(fromLocation) {
        const toLocation = history.location;

        // TODO: We could probably make this more reliable by
        // keeping a list of keys we've seen in sessionStorage.
        // Instead, we just default to 0 for keys we don't know.

        let toIndex = allKeys.indexOf(toLocation.key);

        if (toIndex === -1) toIndex = 0;

        let fromIndex = allKeys.indexOf(fromLocation.key);

        if (fromIndex === -1) fromIndex = 0;

        const delta = toIndex - fromIndex;

        if (delta) {
            forceNextPop = true;
            go(delta);
        }
    }

    const initialLocation = getDOMLocation(getHistoryState());
    let allKeys = [initialLocation.key];

    // Public interface

    function createHref(location) {
        return basename + createPath(location);
    }

    function push(path, state) {
        warning(
            !(
                typeof path === "object" &&
                path.state !== undefined &&
                state !== undefined
            ),
            "You should avoid providing a 2nd state argument to push when the 1st " +
                "argument is a location-like object that already has state; it is ignored"
        );

        const action = "PUSH";
        const location = createLocation(
            path,
            state,
            createKey(),
            history.location
        );

        transitionManager.confirmTransitionTo(
            location,
            action,
            getUserConfirmation,
            ok => {
                if (!ok) return;

                const href = createHref(location);
                const { key, state } = location;

                if (canUseHistory) {
                    globalHistory.pushState({ key, state }, null, href);

                    if (forceRefresh) {
                        window.location.href = href;
                    } else {
                        const prevIndex = allKeys.indexOf(history.location.key);
                        const nextKeys = allKeys.slice(0, prevIndex + 1);

                        nextKeys.push(location.key);
                        allKeys = nextKeys;

                        setState({ action, location });
                    }
                } else {
                    warning(
                        state === undefined,
                        "Browser history cannot push state in browsers that do not support HTML5 history"
                    );

                    window.location.href = href;
                }
            }
        );
    }

    function replace(path, state) {
        warning(
            !(
                typeof path === "object" &&
                path.state !== undefined &&
                state !== undefined
            ),
            "You should avoid providing a 2nd state argument to replace when the 1st " +
                "argument is a location-like object that already has state; it is ignored"
        );

        const action = "REPLACE";
        const location = createLocation(
            path,
            state,
            createKey(),
            history.location
        );

        transitionManager.confirmTransitionTo(
            location,
            action,
            getUserConfirmation,
            ok => {
                if (!ok) return;

                const href = createHref(location);
                const { key, state } = location;

                if (canUseHistory) {
                    globalHistory.replaceState({ key, state }, null, href);

                    if (forceRefresh) {
                        window.location.replace(href);
                    } else {
                        const prevIndex = allKeys.indexOf(history.location.key);

                        if (prevIndex !== -1) allKeys[prevIndex] = location.key;

                        setState({ action, location });
                    }
                } else {
                    warning(
                        state === undefined,
                        "Browser history cannot replace state in browsers that do not support HTML5 history"
                    );

                    window.location.replace(href);
                }
            }
        );
    }

    function go(n) {
        globalHistory.go(n);
    }

    function goBack() {
        go(-1);
    }

    function goForward() {
        go(1);
    }

    let listenerCount = 0;

    function checkDOMListeners(delta) {
        listenerCount += delta;

        if (listenerCount === 1 && delta === 1) {
            window.addEventListener(PopStateEvent, handlePopState);

            if (needsHashChangeListener)
                window.addEventListener(HashChangeEvent, handleHashChange);
        } else if (listenerCount === 0) {
            window.removeEventListener(PopStateEvent, handlePopState);

            if (needsHashChangeListener)
                window.removeEventListener(HashChangeEvent, handleHashChange);
        }
    }

    let isBlocked = false;

    function block(prompt = false) {
        const unblock = transitionManager.setPrompt(prompt);

        if (!isBlocked) {
            checkDOMListeners(1);
            isBlocked = true;
        }

        return () => {
            if (isBlocked) {
                isBlocked = false;
                checkDOMListeners(-1);
            }

            return unblock();
        };
    }

    function listen(listener) {
        // 监听location的变化,并且返回一个取消监听的函数
        const unlisten = transitionManager.appendListener(listener);
        checkDOMListeners(1);

        return () => {
            checkDOMListeners(-1);
            unlisten();
        };
    }

    const history = {
        length: globalHistory.length,
        action: "POP",
        location: initialLocation,
        createHref,
        push,
        replace,
        go,
        goBack,
        goForward,
        block,
        listen
    };

    return history;
}

返回一个 history 对象,包含一系列用来操控浏览器会话历史的方法,

results matching ""

    No results matching ""