JavaScriptの中のhistoryのソースコードのhashhistoryの実現を探求します.

9396 ワード

抜粋自:https://segmentfault.com/a/1190000012656017
history概要
通常は2種類のhistoryがあります.hashhistoryとbrowserHistoryです.ここではゼロからhashhistoryを実現します.
hashHistory:'#/home'
browserHistory: '/home'
下の実装プログラムは、公式のhistoryソースコードに基づいて分析され、hashHistoryソースコードは、本論文の学習に結合されます.
方案を実現する
1、createhashhistory関数を作成する
const createHashHistory = () => {
  const history = {};
  return history;
};
export default createHashHistory;
2、まずhistoryの対象の長さはどうですか?そして、私達は一つ一つそれを実現します.
history = {
  length: 1,  //  Number
  action: "POP",  //  String
  loaction: {}, //  Object
  createHref, //   
  push, //    
  replace,  //    
  go, //    
  goBack, //    
  goForward,  //    
  listen  //    
}
3、lengthを実現するには、windowの下にhistoryオブジェクトがあり、lengthを取得することができます.
const globalHistory = window.history;
history = {
  length: globalHistory.length
};
4、actionのデフォルトはPOPで、それはまたPUSHあるいはREPLACEかもしれません.私たちはこのステップでそれを実現しません.下でpushとreplaceを実現する時にまた実現します.5、location locationオブジェクトを実現するには以下のいくつかのkeyが含まれています.ここで使えるのはpathnameです.history.locationとwindow.locationは違います.history.locationはwindow.locationの簡潔版です.ブラウザコンソールでウインドウ.locationを印刷して、完全なlocationオブジェクトを見ることができます.
location = {
  hash: "",
  pathname: "/",
  search: "",
  state: undefined
}
パッケージ後のlocationを取得するためのgetDOM Location関数を定義します.
const decodePath = path => 
  path.charAt(0) === "/" ? path : "/" + path

const getHashPath = () => {
  //    url  #,   #,    
  //    :"http://localhost:8080/#/",  '/'
  const href = window.location.href;
  const hashIndex = href.indexOf("#");
  return hashIndex === -1 ? "" : href.substring(hashIndex + 1);
}

const getDOMLocation = () => {
  //  getHashPath  url   ,    #,   #
  let path = decodePath(getHashPath());
  //    location
  return createLocation(path);
}
この一歩の核心はcreateLocationの実現です.しかし、複雑ではありません.コードが長いだけです.理解するには、ソースコードをご覧ください.
import resolvePathname from "resolve-pathname"
import valueEqual from "value-equal"
import { parsePath } from "./PathUtils"

export const createLocation = (path, state, key, currentLocation) => {
  let location
  if (typeof path === "string") {
    // Two-arg form: push(path, state)
    location = parsePath(path)
    location.state = state
  } else {
    // One-arg form: push(location)
    location = { ...path }

    if (location.pathname === undefined) location.pathname = ""

    if (location.search) {
      if (location.search.charAt(0) !== "?")
        location.search = "?" + location.search
    } else {
      location.search = ""
    }

    if (location.hash) {
      if (location.hash.charAt(0) !== "#") location.hash = "#" + location.hash
    } else {
      location.hash = ""
    }

    if (state !== undefined && location.state === undefined)
      location.state = state
  }

  try {
    location.pathname = decodeURI(location.pathname)
  } catch (e) {
    if (e instanceof URIError) {
      throw new URIError(
        'Pathname "' +
          location.pathname +
          '" could not be decoded. ' +
          "This is likely caused by an invalid percent-encoding."
      )
    } else {
      throw e
    }
  }

  if (key) location.key = key

  if (currentLocation) {
    // Resolve incomplete/relative pathname relative to current location.
    if (!location.pathname) {
      location.pathname = currentLocation.pathname
    } else if (location.pathname.charAt(0) !== "/") {
      location.pathname = resolvePathname(
        location.pathname,
        currentLocation.pathname
      )
    }
  } else {
    // When there is no prior location and pathname is empty, set it to /
    if (!location.pathname) {
      location.pathname = "/"
    }
  }

  return location
}

export const locationsAreEqual = (a, b) =>
  a.pathname === b.pathname &&
  a.search === b.search &&
  a.hash === b.hash &&
  a.key === b.key &&
  valueEqual(a.state, b.state)
6、createHrefを実現するには、history.reate Href()を使ったことがないかもしれません.hashルートを作るために使われています.
const createPath = location => {
  const { pathname, search, hash } = location;
  let path = pathname || "/";
  if (search && search !== "?")
    path += search.charAt(0) === "?" ? search : `?${search}`;
  if (hash && hash !== "#") path += hash.charAt(0) === "#" ? hash : `#${hash}`;
  return path;
}

const createHref = location =>
  "#" + encodePath(createPath(location));
7、プッシュ方法を実現するために、私達はプッシュを使う時、通常はヒットマン・ピュー('/home')という形式で、自分で加えないでください.pushによって実現される原理:pushによってもたらされたルートは現在のurlのルートと同じかどうかを判断し、同じであれば、ルーティングを更新しない.そうでなければ、ルーティングを更新する.
//    history    ,length、location action
const setState = nextState => {
  Object.assign(history, nextState);
  history.length = globalHistory.length;
  transitionManager.notifyListeners(history.loation, history.action);
}
//  notifyListeners      history   
const notifyListeners = (...args) => {
  listeners.forEach(listener => listener(...args));
}
//      
const pushHashPath = path => (windows.location.hash = path);
//  push    
const push = (path, state) => {
  //    action 'PUSH'
  const action = "PUSH";
  //    location  
  const location = createLocation(
    path,
    undefined,
    undefined,
    history.location
  );
  //            ,confirmTransitionTo                 ,  ok,         ,      。
  transitionManager.confirmTransitionTo(
    location,
    action,
    getUserConfirmation,
    ok => {
      //             ,        
      if (!ok) return;
      //    location    pathname,  '/home'
      const path = createPath(location);
      const encodePath = encodePath(path);
      //       url     push           ,
      //      hashChanged true。
      const hashChanged = getHashPath() !== encodePath;
      if (hashChanged) {
        //        
        ignorePath = path;
        //      
        pushHashPath(encodePath);
        const prevIndex = allPaths.lastIndexOf(createPath(history.location));
        const nextPaths = allPaths.slice(0, prevIndex === -1 ? 0 : prevIndex + 1);
        nextPaths.push(path);
        allPaths = nextPaths;
        //  setState  history  。
        setState({ action, location });
      } else {
        //  push          ,       
        //  "Hash history cannot PUSH the same path; a new entry will not be added to the history stack"
        setState();
      }
    }
  )
}
8、replace replaceとpushはいずれもルートを更新することができますが、replaceは現在のルートを更新するので、pushは歴史記録を追加します.
//      
const replaceHashPath = path => {
  const hashIndex = window.location.href.indexOf("#");
  window.location.replace(
    window.location.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + "#" + path;
  );
}
//  replace    
const replace = (path, state) => {
  const action = "REPLACE";
  const location = createLocation(
    path,
    undefined,
    undefined,
    history.location
  );
  transitionManager.confirmTransitionTo(
    location,
    action,
    getUserConfirmation,
    ok => {
      if (!ok) return;
      const path = createPath(location);
      const encodePath = encodePath(path);
      const hashChanged = getHashPath() !== encodePath;
      //       ,      push         

      if (hashChanged) {
        ignorePath = path;
        //      
        replaceHashPath(encodePath);
      }
      const prevIndex = allPaths.indexof(createPath(history.location));
      if (prevIndex !== -1) allPaths[prevIndex] = path;
      setState({ action, location });
    }
  )
}
9、goを実現する
goメソッドの使用時には、history.go(-1)という形式があります.
//  globalHistory window.history
const go = n => globalHistory.go(n);
10、goBackを実現する
これは一目で分かるはずです.
const goBack = () => go(-1);
11、goForwardを実現する
これも一目で分かるはずです.
const goForward = () => go(1);
12、listenを実現する
const listen = listener => {
  const unlisten = transitionManager.appendListener(listener);
  checkDOMListeners(1);
  return () => {
    checkDOMListeners(-1);
    unlisten();
  }
}

//    hashchange   ,handleHashChange                ,replace、push   hash          ,     。
const checkDOMListeners = delta => {
  listenerCount += delta;
  if (listenerCount === 1) {
    //        
    window.addEventListener('hashchange', handleHashChange);
  } else if (listenerCount === 0) {
    //        
    window.removeEvenListener('hashchange', handleHashChange);
  }
}

//  appendListener    
ley listener = [];
const appendListener = fn => {
  let isActive = true;
  const listener = (...args) => {
    if (isActive) fn(...args);
  }
  listeners.push(listener);
  return () => {
    isActive = false;
    listeners = listeners.filter(item => item !== listener);
  }
};
締め括りをつける
historyオブジェクトのすべての属性と方法を一度に実現し、react-routerでは、historyオブジェクトをRouter、Routeなどのコンポーネントにカプセル化して、reactコンポーネントでthis.props.historyを通して読み取ることができます.ソースを見たら、historyの実現は本当に複雑ではないことが分かります.正確な考え方を探して、関数ごとに実現して、互換性を考えたら完璧です.他のブログで自分のルートプラグインを作ったと宣伝されました.