自己实现一个window.location

770 阅读2分钟

最近需要将项目中的browserHistory路由改成memoryHistory路由,(不懂的看这里:github.com/ReactTraini…),但是项目中有很多根据window.location来判断当前路由的代码,不好一一修改,所以决定自己实现一个location,目的就是对接到history上,根据业务实现的并不完整,但也差不多,直接看代码:

import resolvePathname from 'resolve-pathname';
import history from 'path/to/history'; // createMemoryHistory

export enum ILocationProtocol {
  HTTP = 'http:',
  HTTPS = 'https:',
}

export interface IHistoryLocationProps {
  pathname: string;
  search: string;
  hash: string;
}

export interface ILocationProps {
  protocol?: ILocationProtocol;
  hostname?: string;
  port?: string;
  reload?: (props?: IHistoryLocationProps) => void;
}

const defaultProps = {
  protocol: ILocationProtocol.HTTPS,
  hostname: 'bytedance.feishu.cn',
  port: '',
};

export default class Location {
  private _protocol: ILocationProtocol;
  private _hostname: string;
  private _port: string;
  private _host: string;
  private _origin: string;
  private _reload?: (props?: IHistoryLocationProps) => void;

  constructor(props: ILocationProps = {}) {
    this._protocol = props.protocol || defaultProps.protocol;
    this._hostname = props.hostname || defaultProps.hostname;
    this._port = props.port || defaultProps.port;
    this._host = this._port === '80' || this._port === ''
      ? this.hostname
      : `${this._hostname}:${this._port}`;
    this._origin = `${this._protocol}//${this._host}`;
    this._reload = props.reload;
  }

  get protocol(): ILocationProtocol {
    return this._protocol;
  }

  set protocol(value: ILocationProtocol) {
    throw new Error('Set protocol is not allowed');
  }

  get hostname() {
    return this._hostname;
  }

  set hostname(value: string) {
    throw new Error('Set hostname is not allowed');
  }

  get port() {
    return this._port;
  }

  set port(value: string) {
    throw new Error('Set port is not allowed');
  }

  get host() {
    return this._host;
  }

  set host(value: string) {
    throw new Error('Set host is not allowed');
  }

  get origin(): string {
    return this._origin;
  }

  set origin(value: string) {
    throw new Error('Set origin is not allowed');
  }

  get pathname() {
    return history.location.pathname;
  }

  set pathname(value: string) {
    const pathname = value;
    this.assign(pathname);
  }

  get search() {
    return history.location.search;
  }

  set search(value: string) {
    let search = value;
    if (!search.startsWith('?')) search = `?${search}`;
    this.assign(search);
  }

  get hash(): string {
    return history.location.hash;
  }

  set hash(value: string) {
    let hash = value;
    if (!hash.startsWith('#')) hash = `#${hash}`;
    this.assign(hash);
  }

  get href() {
    return `${this._origin}${this.pathname}${this.search}${this.hash}`;
  }

  set href(value: string) {
    const href = value;
    this.assign(href);
  }

  public assign(url: string) {
    this.replace(url);
  }

  public reload() {
    const props = {
      pathname: this.pathname,
      search: this.search,
      hash: this.hash,
    };
    this._reload && this._reload(props);
  }

  public replace(url: string) {
    let props: IHistoryLocationProps;
    const _url = encodeURI(url);
    if (_url.startsWith('#')) {
      history.push(_url);
      return;
    }
    if (_url.startsWith('?')) {
      props = {
        pathname: this.pathname,
        search: _url,
        hash: '',
      };
    } else if (/^https?:\/\//.test(_url)) {
      const [ , , pathname = '/', search = '', hash = '' ] =
        _url.match(/^[a-z]+:\/\/[^\/:]+(:\d+)?(\/[^\?]+)?(\?[^#]+)?(#.+)?$/) as any[];
      props = { pathname, search, hash };
    } else {
      props = {
        pathname: resolvePathname(_url, this.pathname),
        search: '',
        hash: '',
      };
    }
    this._reload && this._reload(props);
  }

  public toString(): string {
    return this.href;
  }

  public valueOf(): Location {
    return this;
  }
}

具体就不细说了,只允许修改pathname/search/hash以及调用接口,所以说实现不完整;protocol/hostname/port字段和reload接口由外部定义; 可是还有一个问题,就是代码中一般是以locationwindow.location来调用,如何让这些指向我实现的location呢?这里用了一个黑科技,就是webpackDefinePlugin(不懂的看这里:webpack.js.org/plugins/def…),如下:

module.exports = {
    // ...
    plugins: [
        // ...
        new webpack.DefinePlugin({
            'location': '(window.__MY__WINDOW__ || window).location',
            'window.location': '(window.__MY__WINDOW__ || window).location',
        })
    ],
    // ...
}

也不细说,大家自行感受。


插播一条广告

字节跳动招聘 - EE内推专场,广州、深圳、上海,欢迎狂砸简历:

戳我进入

or 扫码 ⤵️