vue原理
如何追踪变化
模型层(model)只是普通 JavaScript 对象,修改它则更新视图(view)。把一个普通 Javascript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。通过它们(getter/setter)可以让 Vue 在访问属性时进行依赖追踪,以及修改属性时进行变更通知。
每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
变化检测问题
Vue 不允许在已经创建的实例上动态添加新的根级响应式属性。添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性:
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
声明响应式属性
data 对象就像组件状态的概要,提前声明所有的响应式属性。消除了在依赖项跟踪系统中的一类边界情况;Vue 实例在类型检查系统的帮助下运行的更高效;让组件代码在以后阅读时更易于被理解。
异步更新队列
Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际(已去重的)工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在DOM 更新完成后就会调用。
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el
属性目前不可见。
在挂载开始之前被调用:相关的 render
函数首次被调用。
el
被新创建的 vm.$el
替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted
被调用时 vm.$el
也在文档内。
注意 mounted
不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick 替换掉 mounted
:
mounted: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been rendered }) } |
该钩子在服务器端渲染期间不被调用。
beforeUpdate:
数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。
updated:
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
注意 updated
不会承诺所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以用 vm.$nextTick 替换掉 updated
:
updated: function () { this.$nextTick(function () { // Code that will run only after the // entire view has been re-rendered }) } |
该钩子在服务器端渲染期间不被调用
activated:
keep-alive 组件激活时调用。
该钩子在服务器端渲染期间不被调用。
deactivated:
keep-alive 组件停用时调用。
该钩子在服务器端渲染期间不被调用。
beforeDestroy:
实例销毁之前调用。在这一步,实例仍然完全可用。
该钩子在服务器端渲染期间不被调用。
destroyed:
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
2.5.0+ 新增,当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false
以阻止该错误继续向上传播。
#v-cloak
这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。简单的就是说防止页面刚载入时出现{{msg}}等信息。
#keep-alive
当引入keep-alive
的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated。
#key
key
的特殊属性主要用在 Vue的虚拟DOM算法,在新旧nodes对比时辨识VNodes。如果不使用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用key,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。
当你遇到如下场景时它可能会很有用:
- 完整地触发组件的生命周期钩子
- 触发过渡
#实现tab页面切换
1.子路由:利用router-link和router-view切换tab。
路由中写法,需要子路由形式,不然会跳转页面,不会在router-view中渲染页面。
{
path: '/tabs/list',
name: 'tabs_list',
component: resolve => require.async('/static/tabs/list.vue', proxy(resolve)),
children:[
{
path: '/tabs/first',
name: 'tab_first',
component: resolve => require.async('/static/tabs/first.vue', proxy(resolve))
},
{
path: '/tabs/second',
name: 'tab_second',
component: resolve => require.async('/static/tabs/second.vue', proxy(resolve))
}
]
},
<template>
<vm-page>
<div id="account">
<p class="tab">
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/tuopan/first">选项一</router-link>
<!-- 注意这里的路径,first和second是tuopan的子路由 -->
<router-link to="/tuopan/second">选项二</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
</vm-page>
</template>
2.动态组件:利用#is和#component切换页面,利用#keep-alive缓存,保留切换前的状态,避免重新渲染。
<template>
<vm-page>
<vm-topbar slot="header">
<div class="group">
<span v-for="(tab,index) in tabs" :class="{cur:iscur==index}" @click="onSwitch(index)">{{tab.name}}</span>
</div>
</vm-topbar>
<keep-alive>
<component :is="currentView"></component>
</keep-alive>
</vm-page>
</template>
<script>
import first from './first.vue';
import second from './second.vue';
export default {
data() {
return {
iscur: 0,
currentView: 'first',
tabs: [{name: "选项一"}, {name: "选项二"} ]
}
},
activated() {
},
components: {
first,
second
},
methods: {
onSwitch(index) {
switch(index){
case 1:
this.iscur = 1;
this.currentView = 'second';
break;
default:
this.iscur = 0;
this.currentView = 'first';
break;
}
}
}
}
</script>
<style scoped lang="less">
.group{font-size:0;height: 26px;line-height:26px;}
.group>span{cursor:pointer;display:inline-block;font-size:16px;text-align:center;width:100px;}
.cur{color:#fff;background-color: #20a0ff;}
</style>
#React 和 Vue
React 和 Vue 有许多相似之处,它们都有:
- 使用 Virtual DOM
- 提供了响应式(reactive)和可组合的视图组件(composable view component)。
- 将注意力集中保持在核心库,同时也关注路由和负责处理全局状态管理的辅助库。