赞
踩
Vuex是一个专为Vue.js应用程序开发的状态管理模式,就是一个全局的状态管理工具,和全局变量没什么区别,唯一区别就是vuex是有规则的存储,获取,并且可以适配vue的响应式规则,并且提供可供调试的devtools
vuex的规则就是
优化首屏加载可以从这几个方面开始
创建Vue实例的,因此首先搜索Vue的定义
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
可以看到Vue构造函数的核心代码只有一行:this._init(options);因此搜索私有化_init方法。由于_init是作为this的一个方法,注意此处的this就是Vue。经过查找_init方法的定义如下:
Vue.prototype._init = function (options) { var vm = this; // a uid vm._uid = uid$3++; var startTag, endTag; /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = "vue-perf-start:" + (vm._uid); endTag = "vue-perf-end:" + (vm._uid); mark(startTag); } // a flag to avoid this being observed vm._isVue = true; // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options); } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm); } else { vm._renderProxy = vm; } // expose real self vm._self = vm; debugger initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, 'beforeCreate'); initInjections(vm); // resolve injections before data/props initState(vm); initProvide(vm); // resolve provide after data/props callHook(vm, 'created'); /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false); mark(endTag); measure(("vue " + (vm._name) + " init"), startTag, endTag); } if (vm.$options.el) { vm.$mount(vm.$options.el); } }; }
读源码需要注意的一点就是不相关的一定要忽略,一旦遵循深度遍历法则读下去,你是一定会失败的。如果方法不对,那还不如不读,睡会觉。可以将上面的代码简化为:
Vue.prototype._init = function (options) { var vm = this; ... vm.$options = mergeOptions(options || {}, vm); ... initState(vm); ... if (vm.$options.el) { vm.$mount(vm.$options.el); } ... }; } _init方法总体上做的事情其实并不多,第一项就是合并配置项。比如路由,状态管理,渲染函数。 new Vue({ store: store, router: router, render: h => h(App), }).$mount('#app') 然后初始化状态,initState的方法定义如下: function initState (vm) { vm._watchers = []; var opts = vm.$options; if (opts.props) { initProps(vm, opts.props); } if (opts.methods) { initMethods(vm, opts.methods); } if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } if (opts.computed) { initComputed(vm, opts.computed); } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
从源码可以看出,initState就是将vue实例中的data,method,computed,watch等数据项做进一步得处理,其实就是做代理以及转化成可观测对象。
数据处理完成之后就将数据挂载到指定的钩子上:vm.options.el);
另外需要注意的是,_init方法中有一下一段代码,在上面我为来突出主线而省略了,这就是
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
可以看到在initState(vm)执行之前,我们执行了beforeCreate方法,在initState(vm)执行之后,我们执行了created方法。因此在beforeCreate方法中,我们无法直接引用data,method,computed,watch等在initState(vm)中才开始存在的属性。
//单独设置每个路由的属性:
meta: { may: true }
router.beforeEach((to, from, next) => {
if (to.matched.some(item => item.meta.may)) {
let id = window.localStorage.getItem("id")
if (id) {
next()
} else {
next({ name: "login" })
}
} else {
next()
}
})
注意:next 方法必须要调用,否则钩子函数无法 resolved
后置钩子
router.afterEach((to,from) => {
if(to.meta && to.meta.title){
document.title = to.meta.title
}else{
document.title = "666"
}
})
{
path: '/home',
name: 'home',
component: Home,
beforeEnter(to, from, next) {
if (window.localStorage.getItem("id")) {
next()
} else {
next({ name: "login" })
}
}
}
beforeRouteEnter(to, from, next) {
// do someting
// 在渲染该组件的对应路由被 confirm 前调用
},
beforeRouteUpdate(to, from, next) {
// do someting
// 在当前路由改变,但是依然渲染该组件时调用
},
beforeRouteLeave(to, from ,next) {
// do someting
// 导航即将离开该组件的对应路由时被调用
}
全局解析守卫
router.beforeResolve 注册一个全局守卫,和 router.beforeEach 类似
(1)在路由中配置
{
path : '/home/:id',
name : 'Home',
component
}
然后写调用的时候
this.$router.push({path : `/describle/${id}`})
取值:
$route.parms.id
(2)通过params传参,通过name配置路由
路由配置:
{
path : '/home',
name : 'Home',
component : Home
}
this.$router.push({
name : 'Home',
params : {
id : id
}
})
获取
$route.params.id
(3)使用path来配置路由,通过query来传递参数,参数会在url后边的?id=?中显示
注意:path与params不能同时使用应用path+query
路由配置:
{
path : '/home',
name : 'Home',
component : Home
}
调用:
this.$router.push({
path : '/home',
query : {
id : id
}
})
获取
this.$route.query.id
写法:component:()=>import('@/components/Home');
原理核心就是 更新视图但不重新请求页面。
vue-router实现单页面路由跳转,提供了三种方式:hash模式、history模式、abstract模式,根据mode参数来决定采用哪一种方式。
hash: 使用 URL hash 值来作路由。默认模式。
history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式。
abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端
hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分(/#/…),浏览器只会加载相应位置的内容,不会重新加载网页,也就是说 #是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中不包括#;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置;所以说Hash模式通过锚点值的改变,根据不同的值,渲染指定DOM位置的不同数据。
HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面;
由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: ‘history’",这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
abstract模式是使用一个不依赖于浏览器的浏览历史虚拟管理后端。
根据平台差异可以看出,在 Weex 环境中只支持使用 abstract 模式。 不过,vue-router 自身会对环境做校验,如果发现没有浏览器的 API,vue-router 会自动强制进入 abstract 模式,所以 在使用 vue-router 时只要不写 mode 配置即可,默认会在浏览器环境中使用 hash 模式,在移动端原生环境中使用 abstract 模式。
参考地址:https://juejin.im/post/5bc6eb875188255c9c755df2
要讲清楚,computed原理,首先得讲vue响应式原理,因为computed的实现是基于Watcher对象的。那么vue的响应式原理是什么呢,众所周知,vue是基于Object.defineProperty实现监听的。在vue初始化数据data和computed数据过程中。会涉及到以下几个对象:
Observe对象是在data执行响应式时候调用,因为computed属性基于响应式属性,所以其不需要创建Observe对象。
Dep对象主要功能是做依赖收集,有个属性维护多个
Watch对象,当更新时候循环调用每个Watch执行更新。Watch对象主要是用于更新,而且是收集的重点对象。
这里谈到computed计算属性,首先要知道,其有两种定义方式,-种是方法,另-种是get, set属性。而且,其内部监听的对象必须是已经定义响应式的属性,比如data的属性vuex的属性。vue在创建computed属性时候,会循环所有计算属性,每一个计算属性会创建一个watch, 并且在通过defineProperty定义监听,在get中,计算属性工作是做依赖收集,在set中, 计算属性重要工作是重新执行计算方法,这里需要多补充- -句,因为computed是懒执行,也就是说第一-次初始化之后,怀会执行计算,下一次变更执行重新计算是在set中。
另一个补充点是依赖收集的时机,computed收集时机和data一样,是在组件挂载前,但是其收集对象是自己属性对应的watch,而data本身所有数据对应一个watch
vue-loader is a loader for Webpack that can transform Vue components written in the following
format into a plain JavaScript module
简单来说就是:将 *.vue 文件变成 *.bundle.js,然后放入浏览器运行。
vue-loader简介
vue-loader是一个webpack的loader;可以将vue文件转换为JS模块;
vue-loader特性
(1)ES2015默认支持
(2)允许对VUE组件的组成部分使用其他webpack loader;比如对< style >使用SASS(编译CSS语言),对< template >使用JADE(jade是一个高性能的模板引擎,用JS实现,也有其他语言的实现—php,scala,yuby,python,java,可以供给node使用)
(3).vue文件中允许自定义节点,然后使用自定义的loader处理他们
(4)对< style >< template >中的静态资源当做模块来对待,并且使用webpack loaders进行处理
(5)对每个组件模拟出CSS作用域
(6)支持开发期组件的热重载
在编写vue应用程序时,组合使用webpack跟vue-loader能带来一个现代。灵活并且非常强大的前端工作流程;
webpack简介
(1)webpack是一个模块打包工具,他可以将一堆文件中的每个文件都作为一个模块;找出他们的依赖关系,将他们打包为可部署的静态资源;
(2)使用webpack的loaders,我们可以配置webpack以任何方式去转换所有类型的文件;例如
A:转换ES2015,CoffeeScript或者TypeScript模块为普通的ES5 CommonJs模块;
B:可以选择在编译之前检验你的源代码;
C:将jade模板转换为纯HTML并且嵌入JS字符串中
D:将SASS文件转换为纯CSS,然后将其转换成JS片段,将生成的CSS作为< style >标签插入页面;
E:处理html或者CSS中引用的图片。移动到配置的路径中,并且使用MD5 hash重命名;
(3)当你理解webpack原理后会感觉到它是这么强大,可以大大的优化你的前端工作流程;缺点是配置比较复杂;
VUE组件细则
.vue文件是一个自定义的文件类型,用类HTML语法描述一个vue组件,每个.vue组件包含三种类型的顶级语言快< template>< script>< style>,还允许添加自定义的模块;
<template> <div class="example">{{ msg }}</div> </template> <script> export default { data () { return { msg: 'Hello world!' } } } </script> <style> .example { color: red; } </style> <custom1> This could be e.g. documentation for the component. </custom1>
vue-loader会解析文件,提取出每个语言块,如果有必要会通过其他loader处理,最后将他们组装成一个commonjs模块;module.exports出一个vue.js组件对象;
vue-loader支持使用非默认语言,比如CSS预处理器,预编译的HTML模板语言,通过设置语言块的lang属性;例如
<style lang='sass'>
/*sass*/
</style>
Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。
由于 Object.defineProperty 只能对属性进行劫持,需要遍历对象的每个属性。而 Proxy 可以直接代理对象。
Object.defineProperty对新增属性需要手动进行Observe。
由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。
也正是因为这个原因,使用vue给 data 中的数组或对象新增属性时,需要使用 vm.$set 才能保证新增的属性也是响应式的。
在vue的 set 方法中,对 target 是数组和对象做了分别的处理,
target 是数组时,会调用重写过的 splice 方法进行手动 Observe 。
对于对象,如果 key 本来就是对象的属性,则直接修改值触发更新,否则调用 defineReactive方法重新定义响应式对象。
(1)如果采用 proxy 实现,Proxy 通过 set(target, propKey, value, receiver) 拦截对象属性的设置,是可以拦截到对象的新增属性的。
(2)Proxy 对数组的方法也可以监测到,不需要像上面vue2.x源码中那样进行 hack。
(3) Proxy支持13种拦截操作,这是defineProperty所不具有的
get(target, propKey, receiver):拦截对象属性的读取,比如 proxy.foo 和 proxy[‘foo’]。
set(target, propKey, value, receiver):拦截对象属性的设置,比如 proxy.foo = v 或 proxy[‘foo’] = v ,返回一个布尔值。
has(target, propKey):拦截 propKey in proxy 的操作,返回一个布尔值。
deleteProperty(target, propKey):拦截 delete proxy[propKey] 的操作,返回一个布尔值。
ownKeys(target):拦截 Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 、Object.keys(proxy) 、for…in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。
getOwnPropertyDescriptor(target, propKey):拦截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象。
defineProperty(target, propKey, propDesc):拦截 Object.defineProperty(proxy, propKey, propDesc) 、Object.defineProperties(proxy, propDescs) ,返回一个布尔值。
preventExtensions(target):拦截 Object.preventExtensions(proxy) ,返回一个布尔值。
getPrototypeOf(target):拦截 Object.getPrototypeOf(proxy) ,返回一个对象。
isExtensible(target):拦截 Object.isExtensible(proxy) ,返回一个布尔值。
setPrototypeOf(target, proto):拦截 Object.setPrototypeOf(proxy, proto) ,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如 proxy(…args)、proxy.call(object, …args) 、proxy.apply(…) 。
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(…args) 。
(4)Proxy 作为新标准,长远来看,JS引擎会继续优化 Proxy ,但 getter 和 setter 基本不会再有针对性优化。
proxy缺点:
Proxy 对于IE浏览器来说简直是灾难。(不兼容)
并且目前并没有一个完整支持 Proxy 所有拦截方法的Polyfill方案,有一个google编写的 proxy-polyfill 也只支持了 get,set,apply,construct 四种拦截,可以支持到IE9+和Safari 6+。
总结:
Object.defineProperty 对数组和对象的表现一直,并非不能监控数组下标的变化,vue2.x中无法通过数组索引来实现响应式数据的自动更新是vue本身的设计导致的,不是 defineProperty 的锅。
Object.defineProperty 和 Proxy 本质差别是,defineProperty 只能对属性进行劫持,新增属性需要手动 Observe 的问题。
Proxy 作为新标准,浏览器厂商势必会对其进行持续优化,但它的兼容性也是块硬伤,并且目前还没有完整的polifill方案。
参考链接:https://blog.csdn.net/weixin_40687883/article/details/102565285
计算属性computed :
侦听属性watch:
监听的对象也可以写成字符串的形式
https://img2018.cnblogs.com/blog/1402448/201908/1402448-20190809160648619-505189772.png
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。这是和computed最大的区别
vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。
在DOM更新完毕之后执行一个回调
Vue.nextTick(function () {
// DOM 更新了
})
尽管MVVM框架并不推荐访问DOM,但有时候确实会有这样的需求,尤其是和第三方插件进行配合的时候,免不了要进行DOM操作。而nextTick就提供了一个桥梁,确保我们操作的是更新后的DOM。
vue就是这样的思路,并不是用MO进行DOM变动监听,而是用队列控制的方式达到目的。
vue的数据响应过程包含:数据更改->通知Watcher->更新DOM。而数据的更改不由我们控制,可能在任何时候发生。如果恰巧发生在repaint之前,就会发生多次渲染。这意味着性能浪费,是vue不愿意看到的。
还需了解event loop的另一个重要概念,microtask.我们可以把它称为微任务
每一次事件循环都包含一个microtask队列,在循环结束后会依次执行队列中的microtask并移除,然后再开始下一次事件循环。
在执行microtask的过程中后加入microtask队列的微任务,也会在下一次事件循环之前被执行。也就是说,macrotask总要等到microtask都执行完后才能执行,microtask有着更高的优先级。
microtask的这一特性,简直是做队列控制的最佳选择啊!vue进行DOM更新内部也是调用nextTick来做异步队列控制。而当我们自己调用nextTick的时候,它就在更新DOM的那个microtask后追加了我们自己的回调函数,从而确保我们的代码在DOM更新后执行,同时也避免了setTimeout可能存在的多次执行问题。
常见的microtask有:Promise、MutationObserver、Object.observe(废弃),以及nodejs中的process.nextTick.
队列控制的最佳选择是microtask,而microtask的最佳选择是Promise.但如果当前环境不支持Promise,vue就不得不降级为macrotask来做队列控制了。
在vue2.5的源码中,macrotask降级的方案依次是:setImmediate、MessageChannel、setTimeout.
setImmediate是最理想的方案了,可惜的是只有IE和nodejs支持。
MessageChannel的onmessage回调也是microtask,但也是个新API,面临兼容性的尴尬…
所以最后的兜底方案就是setTimeout了,尽管它有执行延迟,可能造成多次渲染,算是没有办法的办法了。
总结
以上就是vue的nextTick方法的实现原理了,总结一下就是:
vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
因为兼容性问题,vue不得不做了microtask向macrotask的降级方案
** 初始化阶段的4个钩子: beforeCreate、created、beforeMount、mounted;**
beforeCreate:
1、组件创建前触发,目的是为组件的 生命周期和组件中的事件做准备;
2、没有获得数据,真实dom也没有渲染出来
3、在此阶段内可以进行数据请求,提供一次修改数据的机会
4、此阶段执行一次
Created:
1、组件创建结束
2、数据可以拿到了
3、可以进行数据请求,提供一次修改数据的机会
4、该过程执行一次
beforeMount:
1、组件挂载前
2、该阶段任务:
判断el,判断template;
如果el没有,那么我们需要手动挂载,如果有,那麽判断template;
如果template有,那么进行render函数,如果template没有,那么通过outHTML手动书写模板;
3、数据可以获得,但是真实dom还没有渲染
4、可以进行数据请求,也提供了一次数据修改的机会
Mounted:
1、组件挂载结束
2、数据获得,真实dom也获得了
3、可以进行数据请求,也可以修改数据
4、执行一次
5、可以进行真实dom的操作了
总结:数据请求我们一般写在created钩子中,第三方库的实例化我们一般写在mounted钩子中;
运行中阶段有两个钩子:beforeUpdate、updated;
beforeUpdate:
① 更新前
② 重新渲染 VDOM , 然后通过diff算法比较两次vdom,生成patch 补丁对象,还未渲染到页面
③ 这个钩子函数更多的是内部进行一些操作
④ 可以触发多次,只要数据更新就会触发
Updated:
① 数据更新结束
② 真实dom得到了,数据也得到了( 更新后的 )
③ 可以用于动态数据获取( 第三方库实例化 )
④ 可以触发多次,只要数据更新就会触发
销毁阶段也是有两个钩子:beforeDestroy、destroyed.这两个钩子用法基本相同。
beforeDestroy
Destroyed
methods:{
//内部通过$destroy方法删除自身组件,触发destroy生命钩子函数
clear() {
this.$destroy();
}
},
beforeDestroy () {
console.log('beforeDestroy');
//组件已经销毁,但是渲染出的真实dom结构未被销毁,手动销毁
document.querySelector('#app').remove()
},
destroyed () {
console.log('destroyed')
}
一般源码中,都会用到 window.history 和 location.hash
history 实现
window.history 对象包含浏览器的历史,window.history 对象在编写时可不使用 window 这个前缀。history是实现SPA前端路由是一种主流方法,它有几个原始方法:
如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是静默失败
在HTML5,history对象提出了 pushState() 方法和 replaceState() 方法,这两个方法可以用来向历史栈中添加数据,就好像 url 变化了一样(过去只有 url 变化历史栈才会变化),这样就可以很好的模拟浏览历史和前进后退了,现在的前端路由也是基于这个原理实现的。
history.pushState
pushState(stateObj, title, url) 方法向历史栈中写入数据,其第一个参数是要写入的数据对象(不大于640kB),第二个参数是页面的 title, 第三个参数是 url (相对路径)。
stateObj :一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此* 处可以填null。
title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。
url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
关于pushState,有几个值得注意的地方:
pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应,只有当触发前进后退等事件(back()和forward()等)时浏览器才会刷新
这里的 url 是受到同源策略限制的,防止恶意脚本模仿其他网站 url 用来欺骗用户,所以当违背同源策略时将会报错
history.replaceState
replaceState(stateObj, title, url) 和pushState的区别就在于它不是写入而是替换修改浏览历史中当前纪录,其余和 pushState一模一样。
popstate事件
定义:每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。
注意:仅仅调用pushState方法或replaceState方法 ,并不会触发该事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用JavaScript调用back、forward、go方法时才会触发。另外,该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件也不会触发。
用法:使用的时候,可以为popstate事件指定回调函数。这个回调函数的参数是一个event事件对象,它的state属性指向pushState和replaceState方法为当前URL所提供的状态对象(即这两个方法的第一个参数)。
HISTORY实现SPA前端路由代码
<a class="spa">abc.html</a> <a class="spa">123.html</a> <a href="/rdhub" class="spa ">rdhub</a> // 注册路由 document.querySelectorAll('.spa').forEach(item => { item.addEventListener('click', e => { e.preventDefault(); let link = item.textContent; if (!!(window.history && history.pushState)) { // 支持History API window.history.pushState({name: 'history'}, link, link); } else { // 不支持,可使用一些Polyfill库来实现 } }, false) }); // 监听路由 window.addEventListener('popstate', e => { console.log({ location: location.href, state: e.state }) }, false) popstate监听函数里打印的e.state便是history.pushState()里传入的第一个参数,在这里即为{name: 'history'}
hash
hash基本介绍
url 中可以带有一个 hash http://localhost:9000/#/rdhub.html
window 对象中有一个事件是 onhashchange,以下几种情况都会触发这个事件:
直接更改浏览器地址,在最后面增加或改变#hash;
通过改变location.href或location.hash的值;
通过触发点击带锚点的链接;
浏览器前进后退可能导致hash的变化,前提是两个网页地址中的hash值不同。
hash实现SPA前端路由代码
<a href="/rdhub" class="spa">rdhub</a> <a href="/abc" class="spa">abc</a> <a href="/123" class="spa">123</a> <a href="/hash" class="spa">hash</a> document.querySelectorAll('.spa').forEach(item => { item.addEventListener('click', e => { e.preventDefault(); let link = item.textContent; location.hash = link; }, false) }); // 监听路由 window.addEventListener('hashchange', e => { console.log({ location: location.href, hash: location.hash })//欢迎加入全栈开发交流群一起学习交流:864305860 }, false)
hash模式与history模式,这两种模式都是通过浏览器接口实现的,除此之外vue-router还为非浏览器环境准备了一个abstract模式,其原理为用一个数组stack模拟出浏览器历史记录栈的功能。当然,以上只是一些核心逻辑,为保证系统的鲁棒性源码中还有大量的辅助逻辑,也很值得学习。
两种模式比较
pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL
pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中
pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串
pushState可额外设置title属性供后续使用
history模式的一个问题
我们知道对于单页应用来讲,理想的使用场景是仅在进入应用时加载index.html,后续在的网络操作通过Ajax完成,不会根据URL重新请求页面,但是难免遇到特殊情况,比如用户直接在地址栏中输入并回车,浏览器重启重新加载应用等。
hash模式仅改变hash部分的内容,而hash部分是不会包含在HTTP请求中的:
http://rdhub.cn/#/user/id // 如重新请求只会发送http://rdhub.cn/故在hash模式下遇到根据URL请求页面的情况不会有问题。而history模式则会将URL修改得就和正常请求后端的URL一样http://rdhub.cn/user/id
在此情况下重新向后端发送请求,如后端没有配置对应/user/id的路由处理,则会返回404错误。
官方推荐的解决办法是在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。同时这么做以后,服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。或者,如果是用 Node.js 作后台,可以使用服务端的路由来匹配 URL,当没有匹配到路由的时候返回 404,从而实现 fallback。
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,可以帮助我们管理共享状态。
如何在Vue中使用Vuex?
如下先来回顾一下使用Vuex的正确姿势:
// store.js
Vue.use(Vuex);
// store.js const store = new Vuex.Store({ state:{ count:0 }, mutations:{ increment(state){ state.count++; }, del(state){ state.count--; }, }, actions:{ asyncAdd({commit}){ setTimeout(() => { commit("increment"); }, 2000); } } })
new Vue({
store,
render: h => h(App),
}).$mount('#app')
<template> <div>计数器 <span>{{$store.state.count}}</span> <br/> <button @click="this.add">+</button> <button @click="this.del">-</button> <button @click="this.asyncAdd">异步+</button> </div> </template> <script> export default { methods:{ add(){ this.$store.commit('increment'); }, del(){ this.$store.commit('del'); }, asyncAdd(){ this.$store.dispatch('asyncAdd'); } } } </script>
Vuex的核心源码解析:
目标:
Vuex的核心源码简版:
let Vue; class Store { // 持有state,并使其响应化 constructor(options){ this.state = new Vue({ data:options.state }) this.mutations = options.mutations;// mutations 是对象 this.actions = options.actions;// mutations 是对象 // 绑定this this.commit=this.commit.bind(this); this.dispatch=this.dispatch.bind(this); } // 实现commit和dispatch方法 commit(type,arg){ this.mutations[type](this.state,arg); } dispatch(type,arg){ console.log(this.actions[type]) return this.actions[type](this,arg) } } function install(_vue){ Vue = _vue; Vue.mixin({// 为什么用混入?use是先执行,而this指向的是vue实例,是在main.js中后创建的,使用混入才能在vue实例的指定周期里拿到store实例并做些事情 beforeCreate(){ if (this.$options.store) { Vue.prototype.$store=this.$options.store; } } }) } export default { Store, install }
其实,Vuex.Store是个类,使用他的时候,你给他传入了参数(state,mutations,actions)并让他实例化。你把这个实例配置给了Vue,Vuex帮你把他给了Vue原型上的$store。
Vuex还送给你个commit和dispatch方法让你能有办法改 s t o r e . s t a t e , 当 然 你 也 能 通 过 store.state,当然你也能通过 store.state,当然你也能通过store.state方法到你要的状态。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。