当Vue的响应式系统不能发挥作用时,您可以试试重新渲染组件。又或者您只是想把当前的DOM卸载掉,并插入一个新的DOM。那么如何正确的将一个组件重新渲染呢?强制Vue来重新渲染一个组件最好的方式就是在组件上添加一个:key
属性。即当你需要这个组件被重新渲染时,只需要改变key的值就可以。
我不禁会赞叹这种解决方式的简洁!
当然,还有一些其他方式来解决这个问题:
- 重新加载整个页面
- 使用v-if
- 使用Vue内置的forceUpdate方法
- 通过组件上key属性的变化
以上便是这篇文章将要给您介绍的。
刷新页面,或者强制进行更新,显然不是最好的方法。
请您思考一下下面这些概念:
- Vue的响应式系统是万能的吗
- 组件上的数据什么时候可以被计算
- 组件上的参数什么时候可以被监听
- 在使用v-for时,可以不加
:key
吗
我这里将会列举一些强制更新的案例,它们中大多数将通过改变key属性的方式来解决,稍后您将在文末看到。
重新加载整个页面
这种方式无异于当您想关闭一个应用要通过重启电脑的方式一样。当然这种方法有时会奏效,但这种解决方式是很糟糕的。这种方法也就说这么多了,我劝您别这样做,让我们接下来找一个更好的方式吧。
使用V-IF作为权益之计
Vue中v-if
指令会在值为true
的时候渲染组件,值为false
时这个组件将不会在DOM中存在。
v-if
的使用方式是这样的:在template标签里,加入v-if
指令
<template>
<my-component v-if="renderComponent" />
</template>
在script标签内部的相应函数里,加上nextTick
<script>
export default {
data() {
return {
renderComponent: true,
};
},
methods: {
forceRerender() {
// Remove my-component from the DOM
this.renderComponent = false;
this.$nextTick(() => {
// Add the component back in
this.renderComponent = true;
});
}
}
};
</script>
这几句话的意思是:
- 首先,变量renderComponent被设置为true,my-component组件被渲染
- 当调用forceRerender时,马上就会把renderComponent置为false
- 由于v-if的值为false,my-component组件不会被渲染
- 在下一个任务队列里,renderComponent被重新设为true
- 现在v-if等于true,my-component将再一次被渲染
以下两点有助于您理解上面代码是如何工作的。
第一,必须要等到下一个任务队列,否则看不到任何变化。在Vue中,一个任务队列是一个单独的DOM更新周期。Vue将会收集同一个任务队列中产生的所有更新项,在更新队列结束时将会根据这些更新项在DOM中渲染应该渲染的部分。如果我们不等到下一个任务队列,这些更新项将会被取消,页面上什么都不会发生。
第二,当再次渲染时,Vue将会创建一个全新的组件,即Vue将会销毁之前的,创建一个新的。这意味着这个新的my-component将会照例经历它的生命周期,created,mounted等等。
顺便说一句,如果您愿意,你可以用promises的方式使用nextTick。
forceRerender() {
// Remove my-component from the DOM
this.renderComponent = false;
// If you like promises better you can
// also use nextTick this way
this.$nextTick().then(() => {
// Add the component back in
this.renderComponent = true;
});
}
即便如此,这也不是一个好的解决方法。我将它称之为权宜之计是因为这并不是使用Vue的最佳实践。
那么下面,我们看看Vue里面的最佳实践是什么。
使用VUE内置的FORCEUPDATE方法
这种方式是解决此问题最好的两种方法之一,它们都是被Vue官方支持的。
通常情况下,Vue会响应处于依赖数组中的变化,并更新视图。然而当调用forceUpdate时,即使依赖数组中没有变化,视图也可以强制地更新。
下面我来讲讲大多数人对forceUpdate的误解。那就是,如果Vue在变量改变时能自动更新,我们为什么要强制更新呢?
原因是有时Vue的响应式系统会感到困惑,我们会认为Vue将会对指定的属性或者变量的改变做出响应,但是这并没有发生。另外的情况是,Vue的响应式系统根本就没有检测到任何变化。因此如果您需要Vue的响应式系统来重新渲染您的组件,这个方法是个更好的选择。
调用forceUpdate有两种方式,即在组件内部调用,以及全局调用。
// Globally
import Vue from 'vue';
Vue.forceUpdate();
// Using the component instance
export default {
methods: {
methodThatForcesUpdate() {
// ...
this.$forceUpdate(); // Notice we have to use a $ here
// ...
}
}
}
重要提示:这个方法不会更新已有的计算属性,调用forceUpdate只会强制视图再次更新。
通过KEY的变化
大多数情况都需要你重新渲染一个组件。让我们假设提供一个属性key
,此时Vue就会知道有一个特定的组件跟特定的数据进行关联。如果key不变,这个组件不会跟着改变。反之如果key变化了,Vue就会知道,此时需要抛弃旧组件,创建一个新的组件。
这就是我们想要的机制!
在开始介绍这个方法之前,我们需要再简单理解下我们为什么在Vue中使用key。
在Vue中,我们为什么需要key呢?
一旦你理解了这个,对于理解如何用合适的方式强制重新渲染有一定帮助。
假设你将渲染一个组件列表,这个列表有下列一个或多个特点:
- 组件内维护自己的数据
- 组件内有一定的初始化的流程,尤其在created或mounted生命周期中
- 通过jQuery等api,操作非响应式的DOM
如果您对列表排序,或者以其他方式更新,您可能只希望重新渲染部分列表。不是列表中的每一项,即只重新渲染被改变的那些。
为了帮助Vue追踪哪些变化,哪些没有,我们会提供一个属性key
。数组元素的索引不能在此处使用,因为这个索引并没有绑定列表中的特定物体。
下面是一个列表的例子:
const people = [
{ name: 'Evan', age: 34 },
{ name: 'Sarah', age: 98 },
{ name: 'James', age: 45 },
];
如果用索引的话,我们这样写:
<ul>
<li v-for="(person, index) in people" :key="index">
{{ person.name }} - {{ index }}
</li>
</ul>
// Outputs
Evan - 0
Sarah - 1
James - 2
如果移除Sarah,我们将得到:
Evan - 0
James - 1
与James关联的索引改变了,即使James仍然是James。虽然您不愿意,James还是被重新渲染了。
因此,我们想要使用独一无二的id,生成id的方式并不做限制。
const people = [
{ id: 'this-is-an-id', name: 'Evan', age: 34 },
{ id: 'unique-id', name: 'Sarah', age: 98 },
{ id: 'another-unique-id', name: 'James', age: 45 },
];
<ul>
<li v-for="person in people" :key="person.id">
{{ person.name }} - {{ person.id }}
</li>
</ul>
在此之前,如果我们将Sarah从列表中删除,Vue将会删除与Sarah和James相关的组件,并为James创建新的组件。现在,Vue能够知道可以将Evan和James相关的组件保留,只需要删除Sarah即可。
如果我们将一个person添加进列表中,Vue仍然能够知道可以保留当前存在的所有组件,并单独创建一个新的组件,将它插入到正确的位置。这在我们有更复杂的组件列表时尤其有用,这些组件会有自己的state,或者有初始化的逻辑,或者任意与DOM相关的操作。
也许上面介绍的内容已经足够多了,但是还是很有必要解释keys是如何在Vue中工作的。
下面,让我们继续讲讲强制重新渲染的最好的方式吧!
通过改变key来对一个组件进行强制刷新
到此为止,以下内容就是(在我看来)强制Vue重新渲染组件的最好的方式。
如果您采取将keys赋值给子组件,那么无论何时想要重新渲染一个组件,只需要更新这个key就可以。
下面是一个非常基础的方式:
<template>
<component-to-re-render :key="componentKey" />
</template>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
每次这个forceRerender被调用,属性componentKey都会改变。一旦这个改变发生,Vue将会知道必须销毁这个组件,并且创建一个新的。
这样,您就会得到一个,将会再一次被初始化的,state被重置的子组件。
这既简单又不失优雅的解决了我们的问题!
请您记住,如果您发现需要强制Vue重新渲染一个组件了,很有可能您的代码有优化的空间。
但是,一旦您需要做重新渲染的操作,最好要选择上面的改变组件上的key属性的方式,而不是其他的方式。