v-for="user in users" v-if="user.isActive"
)。此时定义一个计算属性 (比如 activeUsers
),让其返回过滤后的列表即可。v-for="user in users" v-if="shouldShowUsers"
)。此时把 v-if
移动至容器元素上 (比如 ul
、ol
)即可。v-if
和 v-for
同时用在同一个元素上,显然这是一个重要的注意事项。源码中找答案 compiler/codegen/index.js
beforeCreate
钩子函数调用的时候,是获取不到 props
或者 data
中的数据的,因为这些数据的初始化都在 initState
中。created
钩子函数,在这一步的时候已经可以访问到之前不能访问到的数据,但是这时候组件还没被挂载,所以是看不到的。beforeMount
钩子函数,开始创建 VDOM,最后执行 mounted
钩子,并将 VDOM 渲染为真实 DOM 并且渲染数据。组件中如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。beforeUpdate
和 updated
,这两个钩子函数没什么好说的,就是分别在数据更新前和更新后会调用。keep-alive
独有的生命周期,分别为 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 actived 钩子函数。beforeDestroy
和 destroyed
。前者适合移除事件、定时器等等,否则可能会引起内存泄露的问题。然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed
钩子函数组件通信方式大体有 8 种:
根据组件之间的关系讨论使用通信的方式:
参考:Vue 8 中传参方式open in new window
由生命周期钩子函数可知,子组件是在父组件 mounted
之后才开始挂载的,所以顺序是:
父 beforeCreate
父 create
父 beforeMount
子 beforeCreate
子 create
子 beforeMount
孙 beforeCreate
孙 create
孙 beforeMount
孙 mounted
子 mounted
父 mounted
然后,mounted 生命周期被触发。
mounted 被调用完成,到此为止,组件的挂载就完成了,初始化的生命周期结束。
触发钩子的完整顺序: 将路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从 a 组件离开,第一次进入 b 组件:
beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。
beforeEnter: 路由独享守卫
beforeRouteEnter: 路由组件的组件进入路由前钩子。
beforeResolve:路由全局解析守卫
afterEach:路由全局后置钩子
beforeCreate:组件生命周期,不能访问this。
created:组件生命周期,可以访问this,不能访问dom。
beforeMount:组件生命周期
deactivated: 离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
mounted:访问/操作dom。
activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
执行beforeRouteEnter回调函数next。
参考:nextTick 原理
熟读文档可以得出,除数组侦听的那几个方法外,其他的方式进行修改是不具有相应式的(如 this.arr[10]=xx)
同样的,具有相应是的对象,也应该在初始化的时候在 data 中声明出来,这样才能让 watcher 在一开始就侦听它,如果是新增的属性,则需要使用this.$set()
方法了。
在最新的 Vue3.0 中,基于 Proxy 的响应式已经可以支持数组的所有方法了。 但是如果是改变对象的动态新增属性和数组中直接使用索引修改值、直接修改长度不可以被监测到,但是任然可以使用Vue.set()
方法解决
computed
是计算属性,依赖其他属性计算值,并且 computed
的值有缓存,只有当计算值变化才会返回内容。watch
监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。computed
,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用 watch
。源码中找答案:src\core\vdom\patch.js - sameVnode()
function sameVnode(a, b) {
return (
a.key === b.key &&
a.asyncFactory === b.asyncFactory &&
((a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)) ||
(isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error)))
);
}
Vue 是基于虚拟 DOM 做更新的,而 Diff 又是其核心部分。
答题思路:根据题目描述,这里主要探讨 Vue 代码层面的优化
路由懒加载
const router = new VueRouter({
routes: [{ path: "/foo", component: () => import("./Foo.vue") }],
});
keep-alive 缓存页面
<template>
<div id="app">
<keep-alive>
<router-view />
</keep-alive>
</div>
</template>
使用 v-show 复用 DOM
<template>
<div class="cell">
<!--这种情况用v-show复用DOM,比v-if效果好-->
<div v-show="value" class="on">
<Heavy :n="10000" />
</div>
<section v-show="!value" class="off">
<Heavy :n="10000" />
</section>
</div>
</template>
v-for 遍历避免同时使用 v-if
<template>
<ul>
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
<script>
export default {
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive;
});
},
},
};
</script>
长列表性能优化
如果列表是纯粹的数据展示,不会有任何改变,就不需要做响应化
export default {
data: () => ({
users: [],
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
},
};
如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
<recycle-scroller class="items" :items="items" :item-size="24">
<template v-slot="{ item }">
<FetchItemView :item="item" @vote="voteItem(item)" />
</template>
</recycle-scroller>
参考vue-virtual-scrolleropen in new window、vue-virtual-scroll-listopen in new window
事件的销毁
Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。
created() {
this.timer = setInterval(this.refresh, 2000)
},
beforeDestroy() {
clearInterval(this.timer)
}
图片懒加载
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。
<img v-lazy="/static/img/1.png" />
第三方插件按需引入
像 element-ui 这样的第三方组件库可以按需引入避免体积太大。
import Vue from "vue";
import { Button, Select } from "element-ui";
Vue.use(Button);
Vue.use(Select);
无状态的组件标记为函数式组件
<template functional>
<div class="cell">
<div v-if="props.value" class="on"></div>
<section v-else class="off"></section>
</div>
</template>
<script>
export default {
props: ["value"],
};
</script>
子组件分割
<template>
<div>
<ChildComp />
</div>
</template>
<script>
export default {
components: {
ChildComp: {
methods: {
heavy() {
/* 耗时任务 */
},
},
render(h) {
return h("div", this.heavy());
},
},
},
};
</script>
变量本地化
<template>
<div :style="{ opacity: start / 300 }">
{{ result }}
</div>
</template>
<script>
import { heavy } from "@/utils";
export default {
props: ["start"],
computed: {
base() {
return 42;
},
result() {
const base = this.base; // 不要频繁引用this.base
let result = this.start;
for (let i = 0; i < 1000; i++) {
result += heavy(base);
}
return result;
},
},
};
</script>