React Router
路由原理
前端路由的本质是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新页面,核心是改变视图的同时不会向后端发出请求。目前前端使用的路由就只有两种实现方式:
Hash 模式和 History 模式
Hash 模式
Vue-router 默认是 hash 模式
- www.test.com/#/ 就是 Hash URL(有 /#/ 就是),当 # 后面的哈希值发生变化时
- hash 的修改不会导致浏览器刷新,因为 window.location 处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录中
- 所以我们可以通过 hashchange 事件来监听到 URL 的变化,写一些逻辑进行组件替换实现更新页面的效果;同时浏览器监听到 hash 变化,会把更新历史记录,并且按后退键能回到上个位置
- *无论哈希值如何变化,服务端接收到的 URL 请求永远是 www.test.com
1 | window.addEventListener('hashchange', () => { |
Hash 模式优点:
- 简单,兼容性也更好(ie8)
- 不需要服务器端进行任何设置和开发
- 除了资源加载和ajax请求以外,不会发起其他请求
缺点:
- 不太美观
- 对于部分需要重定向的操作,后端无法获取hash部分内容,导致后台无法取得url中的数据,典型的例子就是微信公众号的oauth验证
- 服务器端无法准确跟踪前端路由信息
- 对于需要锚点功能的需求会与目前路由机制冲突
History 模式
History 模式是 HTML5 新推出的功能,主要使用 history.pushState 和 history.replaceState 改变 URL。
通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录
1 | // 新增历史记录 |
当用户做出浏览器动作时,比如点击后退按钮时会触发 popState 事件
1 | window.addEventListener('popstate', e => { |
两种模式对比
history 的优点:
- pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
- pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
- pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
- pushState() 可额外设置 title 属性供后续使用
history 的缺点:一是兼容性,二是需要后端的支持,当真正想输入 url 发起 http 请求的时候,eg. 用户手动输入 URL 后回车
- hash 模式下,仅 # 之前的内容会被包含在请求中,所以对后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误
- history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”
功能
感觉 React 的路由比 Vue 的路由更复杂些
exect 的区别<Route>
,<Router>
,<Route>
, https://juejin.im/post/5d53e885f265da03bc1270ee 。应该确实是组件被
props.history.push()
Redux
工作流程
- Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个 Store。
- State:Store 对象包含所有数据,如果想得到某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State。
- Action:State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
- Action Creator:View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦,所以我们定义一个函数来生成 Action,这个函数就叫 Action Creator。
- Reducer:Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
- dispatch:是 View 发出 Action 的唯一方法。
然后我们过下整个工作流程:
- 首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法。
- 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State
- State 一旦有变化,Store 就会调用监听函数,来更新 View。
到这儿为止,一次用户交互流程结束。可以看到,在整个流程中数据都是单向流动的,这种方式保证了流程的清晰。
react-redux 工作流程
- Provider: Provider 的作用是从最外部封装了整个应用,并向 connect 模块传递 store
- connect: 负责连接 React 和 Redux
- 获取 state: connect 通过 context 获取 Provider 中的 store,通过 store.getState()获取整个 store tree 上所有 state
- 包装原组件: 将 state 和 action 通过 props 的方式传入到原组件内部 wrapWithConnect 返回一个 ReactComponent 对象 Connect,Connect 重新 render 外部传入的原组件 WrappedComponent,并把 connect 中传入的 mapStateToProps, mapDispatchToProps 与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent
- 监听 store tree 变化: connect 缓存了 store tree 中 state 的状态,通过当前 state 状态和变更前 state 状态进行比较,从而确定是否调用 this.setState()方法触发 Connect 及其子组件的重新渲染
Redux VS Mobx
两者对比:
- redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中
- redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作
- redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改
- mobx 相对来说比较简单,在其中有很多的抽象,mobx 更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
- mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易
场景辨析:
- mobx 更适合数据不复杂的应用: mobx 难以调试,很多状态无法回溯,面对复杂度高的应用时,往往力不从心.mobx 适合短平快的项目: mobx 上手简单,样板代码少,可以很大程度上提高开发效率.
- redux 适合有回溯需求的应用: 比如一个画板应用、一个表格应用,很多时候需要撤销、重做等操作,由于 redux 不可变的特性,天然支持这些操作.
参考资料
高频 React 面试题及详解