[译]强制vue渲染组件的正确方式

1,450 阅读6分钟

原文:michaelnthiessen.com/force-re-re…

当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属性的方式,而不是其他的方式。