Giter Club home page Giter Club logo

articles's People

Contributors

forl avatar

Watchers

 avatar  avatar

articles's Issues

如何取消 fetch

关于如何取消 fetch,曾有过许多讨论,也有很多的方案被提出来。

  1. 最初的讨论:whatwg/fetch#27
  2. Cancelable promise 的方案被否了:tc39/proposal-cancelable-promises#4
  3. 其它方案继续讨论:whatwg/fetch#447
  4. 讨论出的提案:whatwg/fetch#447 (comment)
  5. whatwg 最终给出的标准:https://dom.spec.whatwg.org/#dom-abortcontroller-abortcontroller
  6. MDN 使用文档:https://developer.mozilla.org/en-US/docs/Web/API/AbortController/AbortController

history 以及 React-router

history 对象

首先,一切的基础是window.history,它是一个只读属性,能够返回一个 History 对象。这个对象提供了接口用于操作浏览器回话历史(即 session history)。这一特性是 HTML5 引入的,叫做 history API。
我们首先需要关注 histroy 的两个属性和 5 个方法,通过这些属性和方法,我们得以用代码获取 history 状态或操作 history:

  • length,整个 session history 记录(entry)数量,包括当前加载的页面;
  • state,history 栈顶的状态,用于直接获取当前 session 的状态值;
  • back(),进入上一页;
  • forward(),进入下一页;
  • go(),进入指定页,go(1) 等效于 forward(),go(-1) 等效于 back();
  • pushState(),添加 history 记录,接受三个参数:state 对象、title(目前浏览器会忽略该参数)、URL(可选);
  • replaceState(),替换当前的 history 记录,参数与 pushState() 一样。

接下来还要关注一个事件:popstate。当 hostory 的当前活动记录发生变化时,popstate 事件会被派发给 window。如果被激活的 history 记录是通过 pushState 创建或者被 replaceSate 方法修改过,则事件有一个 state 字段,其值是对应 history 记录的 state 拷贝。但是需要注意,只有调用back()、forward()、go()方法或者用户点击浏览器的“后退”、“前进”按钮才会产生 popstate 事件,通过 pushState 和 replaceSate 函数调用对 history 的操作并不会产生 popstate 事件。

location

另一的基础是 location,它是一个对象,包含当前页面 URL 的相关信息,并且提供了一些方法来修改 URL。通过 window.loaction 和 document.location 都可以访问该对象(window.loaction === window.loaction)。

location 对象拥有以下属性和方法(TODO:简要说明以下属性和方法):

  • href
  • protocal
  • host
  • hostname
  • port
  • pathname
  • search
  • hash
  • username
  • password
  • origin
  • assign()
  • reload()
  • relace()
  • toString()

history 库

history 是一个用于管理 session history 的 JS 库,目前由 ReactTraining 维护。在继续阅读本文之前,最好先去扫一眼 history 库的文档。

history 库的目标是要让你可以在任何能够运行 JS 的地方轻松地管理 session history。那么这里的“任何能够运行 JS 的地方”一般就是指浏览器、Node.js 以及向 React-native 这样的非 DOM 环境。注意:这些环境中只有现代浏览器才支持 HTML5 history API。

它管理 session history 的方法与 HTML5 的 history API 类似,也是提供一个 history 对象。为了在不同的环境中提供相对统一的操作方式,history 库提供了三种创建 history 对象的方法:

  • createBrowserHistory,用于支持 HTML5 的现代浏览器环境;
  • createMemoryHistory,用于非 DOM 环境;
  • createHashHistory,用于老式浏览器。

history 对象与 HTML5 的 history API 很相似,但功能有所加强,具体区别请看它提供的属性和方法:

属性:

  • history.length,history 栈中的记录数量
  • history.location,当前的 location
  • history.action,当前的导航动作

导航方法(请注意方法名与 HTML5 history API 的区别):

  • history.push(path, [state])
  • history.replace(path, [state])
  • history.go(n)
  • history.goBack()
  • history.goForward()
  • history.canGo(n) (仅用于 createMemoryHistory)

监听(这是 HTML5 history API 不提供的):

通过 history.listen 方法可以监听 history.location 的变化,使用方式如下:

history.listen((location, action) => {
  console.log(
    `The current URL is ${location.pathname}${location.search}${location.hash}`
  );
  console.log(`The last navigation action was ${action}`);
});

通过 listen 方法注册的 listener 函数,无论是用户点击“前进”、“后退”按钮或者代码调用导航方法时,还是代码调用push()、replace()方法时,都会被触发。在前面讲 HTML5 的 history API 时提到,只有调用back()、forward()、go()方法或者用户点击“前进”、“后退”按钮时才会产生 popstate 事件,代码中调用 pushState() 和 replaceState() 方法并不会产生 popstate 事件。所以要实现监听,history 不仅要利用 popstate 事件来监听用户点击“前进”、“后退”按钮的行为,还要自己实现一套监听机制来监听代码中对导航方法的调用。

从 history 源码中发现,它实现了一个 TransitionManager 对象,用于管理监听器以及 prompt(在此可以不用关心 prompt 的管理)。去掉 prompt 管理功能之后的代码如下:

// createTransitionManager.js
const createTransitionManager = () => {
  let listeners = [];

  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);
    };
  };

  const notifyListeners = (...args) => {
    listeners.forEach(listener => listener(...args));
  };

  return {
    appendListener,
    notifyListeners
  };
};

export default createTransitionManager;

然后再来看 history.listen() 方法的实现:

const listen = listener => {
    const unlisten = transitionManager.appendListener(listener);
    checkDOMListeners(1);

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

对这段代码代码做一个简要解释:

首先将 listener 添加到 transitionManager 中去。另外还有对 checkDOMListeners 的函数的调用,这个函数实现的逻辑有点令人不解,而且做法不太科学,存在潜在的问题,在此就不对它进行分析了,只说它的作用。history 代码中实现了一个名为 handlePopState 的函数,用于监听最开始提到的 popstate 事件,checkDOMListeners(1)是为了确保当 transitionManager 对象中注册的 listener 数量不为 0 时该函数被注册 listener 了,checkDOMListeners(-1) 的作用是确保当 transitionManager 对象中注册的 listener 数量为 0 时该函数从事件监听上移除。

再来看 handlePopState 函数:

const handlePopState = event => {
  // Ignore extraneous popstate events in WebKit.
  if (isExtraneousPopstateEvent(event)) return;

  handlePop(getDOMLocation(event.state));
};

该函数最后调用了名为 handlePop 的函数,再继续看这个函数:

  const 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);
          }
        }
      );
    }
  };

先分析这个函数的作用:经过 confirmTransitionTo 抉择(这属于支线剧情,暂时不关心)之后,最终调用 setState 函数,setState 函数的实现如下:

  const setState = nextState => {
    Object.assign(history, nextState);

    history.length = globalHistory.length;

    transitionManager.notifyListeners(history.location, history.action);
  };

在这个函数里,我们通过 history.listen 注册的所有监听器终于被触发了。这就说明,popstate 事件的监听函数,最终会调用我们通过 history.listen 注册的所有监听器,也就是说 history.listen() 达到了监听 popstate 事件的效果。

至此,已经清楚 history 如何利用 popstate 事件来监听用户点击“前进”、“后退”按钮或者代码里的导航动作,接下来继续探究它如何监听用户代码对 push 和 replace 的调用。

通过寻找 setState 函数被调用的地方发现,history 对象的 push 和 replace 函数体中都调用了 setState,那这就很明了了:调用 push 和 replace 也能触发通过 history.listen 注册的所有监听器。
接下来分析其中的细节。

loaction:

// createBrowserHistory.js
// ...
const createBrowserHistory = (props = {}) => {
  const history = {
    length: globalHistory.length,
    action: "POP",
    location: initialLocation,
    createHref,
    push,
    replace,
    go,
    goBack,
    goForward,
    block,
    listen
  };

  return history;
};

从 createBrowserHistory() 函数返回的 history 对象中,location 属性包含了最多的信息,现在来看看 location 对象里有些什么数据。文档中已经有说明:

  • location 对象实现 window.loaction 接口的子集,包括这些属性:
  • location.pathname,URL 的 path 部分
  • location.search,Query string
  • location.hash,URL的 hash 段
  • location.state, 一些无法在 URL 中体现的额外信息(只支持 createBrowserHistory 和
    createMemoryHistory)
  • location.key, 代表该 location 的唯一字符串 (只支持 createBrowserHistory 和 createMemoryHistory)

如果是在支持 HTML5 story API 的现代浏览器环境下,location 中的字段其实是将 window.history.state 和 window.location 这两个对象的数据合并之后的结果。

TODO:待续

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.