前端路由

抛砖引玉

  1. window.location | window.history | history 库 | react-router | react-router-redux(connected-react-router)
  2. history.listen 什么时候触发?变化时触发?我调用history.replace(),但是 location 参数没变化会触发吗?
  3. dispatch(routerRedux.push(…)) 为什么要这样写?history.push(…)可以吗?routerRedux 我们用对了吗?
  4. 搞这么多 API 真复杂,使用浏览器原生 API 解析 url 和跳转,不香吗?香,但要避免踩坑(兼容:浏览器的兼容、hashHistory 和 browserHistory 的兼容)

浏览器 API

location

https://dev.to/samanthaming/window-location-cheatsheet-4edl

https://developer.mozilla.org/en-US/docs/Web/API/Location

小结:

  1. window.location 与 location 是一个东西,甚至与 window.document.location 和 document.location 都是一个东西

  2. 掌握读写操作,读 - 知道字段含义,写 - 知道方法区别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ancestorOrigins: DOMStringList {length: 0}
    assign: ƒ assign()
    fragmentDirective: FragmentDirective {}
    hash: "#components-table-demo-virtual-list"
    host: "ant.design"
    hostname: "ant.design"
    href: "https://ant.design/components/table-cn/?useless=1&test=2#components-table-demo-virtual-list"
    origin: "https://ant.design"
    pathname: "/components/table-cn/"
    port: ""
    protocol: "https:"
    reload: ƒ reload()
    replace: ƒ replace()
    search: "?useless=1&test=2"
    toString: ƒ toString()
    valueOf: ƒ valueOf()
    Symbol(Symbol.toPrimitive): undefined
    __proto__: Location
  3. location.origin 会有老版浏览器兼容问题

window.history

https://developer.mozilla.org/zh-CN/docs/Web/API/History

window.history、history 一个东西

为了与 history 库区分,以下都叫 window.history

小结:

  1. 出于隐私考虑,不会获取到详细的浏览历史

  2. window.history.pushStatewindow.history.replaceState:相同之处是两个 API 都会操作浏览器的历史记录,而不会引起页面的刷新;不同之处在于,pushState会增加一条新的历史记录,而replaceState则会替换当前的历史记录。

前端路由解决方案

什么叫前端路由:改变 URL,不会请求后端(不刷新),但是页面内容会发生变化

那么有哪些情况改变 URL,不会请求后端:

  1. hash 变化(无论使用js、还是在地址栏输入),不会请求后端
  2. window.history.pushState()

怎么让页面内容变化:

就是要让 js 知道 url 变化了,怎么搞?

  1. 对于方案 1 window.addEventListener('hashchange', ...)
  2. 对于方案 2 window.addEventListener('popstate', ...)

history 库

https://github.com/ReactTraining/history

它封装了上面两种方案,统一了浏览器差异

小结:

  1. 改变 URL,不会请求后端

    1
    2
    3
    4
    5
    history.push(path, [state]);
    history.replace(path, [state]);
    history.go(n);
    history.goBack();
    history.goForward();
  2. 让 js 知道 url 变化了

    history.listen((location, action) => ...),这里的 location 可不是 window.location,但是对字段使用了相同的命名 { pathname, search, hash, state },你一定记得 history@2.x 有个 query 参数,在 history@3.x 中可以通过使用 useQueries 参数,让 location 有 query 参数,但在 history@4.x 完全取消了 query,为的是保证 history.location 是 window.location 的严格子集

  3. 你可能不知道

    1
    2
    3
    const history = createHistory({
    basename: '/the/base'
    });
  4. 你可能不知道

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // Register a simple prompt message that will be shown the
    // user before they navigate away from the current page.
    const unblock = history.block('Are you sure you want to leave this page?');

    // Or use a function that returns the message when it's needed.
    history.block((location, action) => {
    // The location and action arguments indicate the location
    // we're transitioning to and how we're getting there.

    // A common use case is to prevent the user from leaving the
    // page if there's a form they haven't submitted yet.
    if (input.value !== '') return 'Are you sure you want to leave this page?';
    });

    // To stop blocking transitions, call the function returned from block().
    unblock();
  5. browserHistory 需要服务端做什么?把所有请求(或视情况而定)转发到 index.html
    举一个实际的例子,现在的前端 nginx,只做了域名和静态资源简单的映射
    如:只做了 https://zyh.group.com/ -> https://static.group.com/zyh/index.html

    于是你访问 https://zyh.group.com/#/list -> https://static.group.com/zyh/index.html#/list

    但你访问 https://zyh.group.com/table -> https://static.group.com/zyh/index.html/table 就出错了

    需要如下配置

    1
    2
    3
    location / {
    try_files $uri /index.html;
    }

    这样你访问 https://zyh.group.com/xxx/xxx 都会返回 https://static.group.com/zyh/index.html

    也可以 node 做,谁都可以做

react-router

https://github.com/ReactTraining/react-router

  1. 注意大版本升级的 breaking changes,印象中 @2.x 升 @3.x 和 @3.x 升 @4.x 时,网上都是一片哀嚎,据说 @4.x 升 @5.x 很平稳
  2. @4.x 是有一波大的重构,有了<Switch />,一开始还不明白这是干嘛的,其实就如同 js 中的 switch, 依次匹配,匹配成功则停止,是为了防止 url 匹配到多个符合条件的路由
  3. <Link /> 组件和 <a /> 标签一样吗?
  4. 官方说的 react-router-redux 已经不支持 react-router@4.x

react-router-redux

https://github.com/reactjs/react-router-redux

同步 router 和 redux

小结:

  1. 为什么不维护?很多依赖的 react api 即将废弃,需要一波重构,结果社区已经有人搞好了。用的时候也会看到很多 warning,并且放到 react-router 中维护了,但是只维护 @4.x,@5.x 以上可以看到都删掉了,之后得改用 connected-react-router,dva 最新的代码中都改了
    图1

  2. 都已经不维护了,才发现之前根本没用对

    https://github.com/ReactTraining/react-router/blob/v4.3.1/packages/react-router-redux/README.md
    https://github.com/dvajs/dva/blob/2.4/packages/dva/src/index.js

connected-react-router

https://github.com/supasate/connected-react-router

小结:

  1. 用 hooks api 做了重构,用法和 react-router-redux 差不多

回到开始的问题

  1. 看源码实现

  2. routerRedux.push(...) 返回的是 action,所以要 dispatchhistory.push(...) 其实可以满足相同的需求。
    区别是前者需要注入 dispatch,使用 connect 注入;后者需要注入 history,要么 react-router 已注入,要么使用 withRouter

    在 model 中跳转需使用前者

  3. 小心下面情况

    1
    2
    3
    4
    5
    6
    // "https://zyh.group.com/#/list?key=1&value=2"
    console.log(window.location)
    // ...
    hash: "#/list?key=1&value=2"
    search: ""
    // ...
    1
    2
    3
    4
    5
    // "https://zyh.group.com/list?key=1&value=2#hash"
    // ...
    hash: "#hash"
    search: "?key=1&value=2"
    // ...