vue总结
源码实现
是vue的一个中全局状态管理的工具,是一个插件,只能在vue中使用,因为依赖于vue的数据绑定。
数据持久化
利用插件 vuex-persist:
import VuexPersistedstate from 'vuex-persistedstate'
const vuexLocal = new VuexPersistedstate({
// storage: window.localStorage,//默认
storage: window.sessionStorage
})
const store = new Vuex.Store({
state: { ... },
mutations: { ... },
actions: { ... },
plugins: [vuexLocal.plugin]
})
严格模式
不使用 mutation 改变状态,会抛出异常。
const store = new Vuex.Store({
strict:true,
})
官方Vue Router
hash模式
hash表示的是地址栏URL中#符号(也称作为锚点)后的部分, hash虽然会出现在URL中, 但是不会被包含在Http请求中, 因此hash值改变不会重新加载页面,但是会触发 hashchange事件, 浏览器的前进后退也能被触发, 所以在HTML5之前, 基本都是使用hash来实现前端路由。
history模式
利用了HTML5新增的**pushState()和replaceState()**两个api, 通过这两个api完成URL跳转不会重新加载页面。
hash和history 实现vue-router 区别:
api | hash | history |
---|---|---|
push | window.location.assign | window.history.pushState |
replace | window.location.replace | window.history.replaceState |
go | window.history.go | window.history.go |
back | window.history.go(-1) | window.history.go(-1) |
forward | window.history.go(1) | window.history.go(1) |
router-link、router-view 这两个组件哪来的?
<router-link to="/home">Home</router-link>
<router-link to="/login">Login</router-link>
<router-view></router-view>
是在 Vue.use(VueRouter)注册路由 的时候创建的。
Vue.use(VueRouter)
源码做了什么呢?
执行 install 方法,在全局注册了组件。
注意:
在注册两个组件之前,还做了其他事情:
就是定义两个属性 $router、 $route
$router、 $route
源码中可以看出,定义这两个属性是用mixin方法混入某个组件,执行钩子 beforeCreate 中处理的。
$router的处理逻辑简单, $route 就相对复杂些,在路由切换时响应式的变化。
路由懒加载
减少首次加载资源,节约时间,提高效率。
使用 import 导入组件,可定义chunkName,webpack会单独打包为一个js,在进入当前这个路由时才会加载。
核心是使用 component 标签和 is 属性。
1、AST解析
标签上 is 属性的存在,会在 抽象语法树上打上 component属性标记,值为 is 属性上绑定的变量。
2、render
根据 ast 树生成render函数,有component属性则会执行动态组件分支。
和普通组件区别是 _c 函数第一参数是一个变量。
作用
style标签内的样式只在当前模板输出的HTML标签上生效
原理
1、每个Vue文件都将对应一个唯一的id,该id可以根据文件路径名和内容hash生成
2、编译template标签时为每个标签添加了当前组件的id
3、编译style标签时,会根据当前组件的id通过属性选择器和组合选择器输出样式
参考文章
初始化所有状态时,也初始化了计算属性,将每个计算属性作为Watcher函数参数实例化,把实例对象保存在一个computedWatchers中,挂在vue实例上。这个watcher构造函数内部定义了获取计算属性的方法和计算结果,并提供一个标识表示是否计算过了。
初始化计算属性时对每个计算属性做了响应式化,定义了一个高阶函数作为get方法,内部就是获取vue实例上的computedWatchers中的每个watcher实例,通过标识判断取值。
本身是一个抽象组件。不会渲染一个dom,也不会出现在父组件链中。
使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不销毁它们。
1、在动态组件中的应用
2、在vue-router中的应用
缓存原理
在 created 函数调用时将需要缓存的 VNode 节点保存在 cache 中,在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 cache 中取出之前缓存的 VNode 实例进行渲染
源码分析
解决什么问题?
vue创建实例完成,新添加的属性不能响应式化。也就是修改数据视图不会更新。
使用和原理
$set方法是在vue实例化时挂在vue原型上的(stateMixin)。
this.$set(target, key, val)
1、target 是 null 或 undefined 抛错
if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target))) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
2、target 是数组,利用 splice方法
if (Array.isArray(target) && isValidArrayIndex(key)) {
//首先需要处理数组length,保证索引>length,避免splice报错
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
3、target 是对象
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
4、给Vue 实例对象添加属性 或 为根数据对象(vm.$data)添加属性
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
5、target 创建全新属性,target本身不是响应式数据, 直接赋值,否则 调用 内部响应式方法
const ob = (target: any).__ob__
if (!ob) {
target[key] = val
return val
}
// 进行响应式处理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
native
让自定义组件可以响应到自身绑定的事件
sync
允许props数据的双向数据绑定。
// 子组件
this.$emit('update:val', newVal)
//父组件
<v-child :val.sync="val"></v-child>
Vue其实底层是继承了eventBus ,它提供了两个api
this.$emit可以触发事件
$event可以获取$emit的参数
stop
阻止事件冒泡,相当于 e.stopPropagation()
present
阻止事件默认行为,相当于 e.preventDefault()
self
只有点击元素本身才会触发事,相当于变相的阻止事件冒泡
trim
输入框过滤首尾的空格
lazy
input框光标离开才更新数据
once
只能用一次,无论点击几次,执行一次之后就不会再执行
capture
冒泡反转
passive
提升移动端的性能, 在每次滚动中都会有一个默认事件会触发,加上这个,则是告诉浏览器, 不需要查询, 不需要触发这个默认事件
为什么需要 $nextick?
因为vue的 dom更新是异步的 ,dom更新完成会触发$nextTick回调函数,通知dom更新完成。
vue的异步dom更新借助事件循环,根据执行环境优先选择Promise.then, 不支持则最终会调用setTimeout(fn, 0)
参考
定义为函数,每个组件实例都会创建一个私有数据空间,各个组件维护自己的数据。如果是个对象,所有组件实例都会共用一个数据(data)。
这根vue本身的设计无关,是js特性决定的。
我们在面向对象编程时,是基于原型链和构造函数的,原型链上添加的一般都是函数。
vue中创建一个组件,相当于创建了一个组件的构造器,使用的时候才实例化。
组件中的data是挂在原型上的,这个组件所有的实例data都指向原型中的data。
所以,data是对象会导致组件的所有实例都共用一个data,这自然不是我们预期的样子。
参考例子
那为什么是函数就不会共用一个data了呢?
实例化的时候会调用这个函数,返回新的data,函数也是有作用域的概念的,会有一个私有的作用域。
主要为了高效的更新虚拟dom,vue的diff算法需要用到key。
vue会基于key重新排列元素顺序,key不存在的元素会被移除
v-for 会优先于 v-if 执行,这样就浪费了资源,我们可以进行嵌套
初始化生命周期的状态
初始化事件容器
初始化创建元素方法
创建了Dep,用于watcher发布订阅模式,依赖收集
beforeCreate ,初始化接下来开始。。。
初始化vue组件内的属性
created ,现在初始化已经完成,vue内属性全部可以访问
判断有无 el,有,继续执行。无,执行结束,等待手动调用 $mount 方法:
new Vue({
...
}).$mount('#app') //这里的$mount 就是手动挂载
准备 render 方法,判断有无 render 方法:
beforeMount ,这时候内存中保存了要渲染的 render 函数
将vue的 渲染方法 render 添加到 Watcher 中
开始挂载,render函数内部:
mounted ,挂载完成
页面dom可见
数据发生改变,触发update
beforeUpdate
updated
当Vue选项对象中有render渲染函数时,Vue构造函数将直接使用渲染函数渲染DOM树,当选项对象中没有render渲染函数时,Vue构造函数首先通过将template模板编译生成渲染函数,然后再渲染DOM树,而当Vue选项对象中既没有render渲染函数,也没有template模板时,会通过el属性获取挂载元素的outerHTML来作为模板,并编译生成渲染函数。
换言之,在进行DOM树的渲染时,render渲染函数的优先级最高,template次之且需编译成渲染函数,而挂载点el属性对应的元素若存在,则在前两者均不存在时,其outerHTML才会用于编译与渲染。
p beforeCreate -> p created -> p beforeMount ->
c beforeCreate -> c created -> c beforeMount ->
c mounted -> p mounted
vue 使用步骤