RubyLouvre/anu

View on GitHub
packages/router/history.js

Summary

Maintainability
C
1 day
Test Coverage
import { getWindow } from 'react-core/util';
//计划有history hash iframe三种模式
export var modeObject = {};
//伪造一个Location对象
function getLocation(source) {
    const location = {
        getPath() {
            return modeObject.value === 'hash' ? this.hash.slice(1) : this.pathname;
        },
        state: source.history.state,
        key: (source.history.state && source.history.state.key) || 'initial'
    };
    for (const key in source.location) {
        if (Object.prototype.hasOwnProperty.call(source.location, key)) {
            location[key] = source.location[key];
        }
    }
    return location;
}

//伪造一个History对象
function createHistory(source) {
    let listeners = [];

    let transitioning = false;
    let resolveTransition = () => {};
    let target = {
        //减少魔法,提高兼容性,将只读的访问器属性改成普通属性
        location: getLocation(source),

        transitioning,

        _onTransitionComplete() {
            target.transitioning = transitioning = false;
            resolveTransition();
        },

        listen(listener) {
            listeners.push(listener);

            let popstateListener = e => {
                target.location = getLocation(source);
                listener();
            };
            var event = modeObject.value === 'hash' ? 'hashchange' : 'popstate';
            addEvent(source, event, popstateListener);
            return () => {
                removeEvent(source, event, popstateListener);
                listeners = listeners.filter(fn => fn !== listener);
            };
        },

        navigate(to, { state, replace = false } = {}) {
            state = {
                ...state,
                key: Date.now() + ''
            };
            // try...catch iOS Safari limits to 100 pushState calls
            var slocation = source.location;
            if (modeObject.value === 'hash') {
                if (replace && slocation.hash !== newHash) {
                    history.back();
                }
                slocation.hash = to;
            } else {
                try {
                    if (transitioning || replace) {
                        source.history.replaceState(state, null, to);
                    } else {
                        source.history.pushState(state, null, to);
                    }
                } catch (e) {
                    slocation[replace ? 'replace' : 'assign'](to);
                }
            }

            target.location = getLocation(source);

            target.transitioning = transitioning = true;

            let transition = new Promise(res => (resolveTransition = res));
            listeners.forEach(fn => fn());
            return transition;
        }
    };
    return target;
}

function addEvent(dom, name, fn) {
    if (dom.addEventListener) {
        dom.addEventListener(name, fn);
    } else if (dom.attachEvent) {
        dom.attachEvent('on' + name, fn);
    }
}

function removeEvent(dom, name, fn) {
    if (dom.removeEventListener) {
        dom.removeEventListener(name, fn);
    } else if (dom.detachEvent) {
        dom.detachEvent('on' + name, fn);
    }
}
////////////////////////////////////////////////////////////////////////////////
// 伪造一个window对象
function createMemorySource(initialPathname = '/') {
    let index = 0;
    let states = [];
    let stack = [
        {
            pathname: initialPathname,
            search: ''
        }
    ];

    let target = {
        // location
        addEventListener(name, fn) {},
        removeEventListener(name, fn) {},
        history: {
            // index
            // entries,
            // state
            back() {},
            pushState(state, _, uri) {
                index++;
                stack.push(uri2obj(uri));
                states.push(state);
                sync(target);
            },
            replaceState(state, _, uri) {
                stack[index] = uri2obj(uri);
                states[index] = state;
                sync(target);
            }
        }
    };

    function sync(target) {
        var history = target.history;
        history.index = index;
        history.entries = stack;
        history.state = states[index];
        target.location = stack[index];
    }
    sync(target);
    return target;
}

function uri2obj(uri) {
    let arr = uri.split('?');
    let pathname = arr[0];
    let search = arr[1] || '';
    return {
        pathname,
        search
    };
}

////////////////////////////////////////////////////////////////////////////////
// global history - uses window.history as the source if available, otherwise a
// memory history
let win = getWindow();
let getSource = () => {
    return win.location ? win : createMemorySource();
};

let globalHistory = createHistory(getSource());
let { navigate } = globalHistory;

export { globalHistory, navigate, createHistory, createMemorySource };