自己實現一個window.location

Anho發表於2019-03-20

最近需要將專案中的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 掃碼 ⤵️

自己實現一個window.location

相關文章