如何在 Vue.js 和 Vuetify 中延迟大量渲染

How to delay heavy rendering in Vue.js and Vuetify

我正在使用 Vue.js 2 和 Vuetify 2.6,并且要在扩展面板中进行一些繁重的表单渲染。单击面板的 header 时,面板部分打开之前会有一小段延迟,因为我猜这是第一次渲染。延迟并没有给我带来太大的困扰,但我想在初始渲染发生时立即显示进度指示器。问题是,进度指示器仅在渲染完成后出现。不过,点击事件会立即发生,如以下代码段的 js 控制台所示。

有没有办法延迟面板部分的渲染以首先显示进度指示器?令人惊讶的是 UI 在渲染发生时没有冻结,所以进度指示器动画有效。

我无法使用扩展面板“eager”选项,因为我有很多面板,一次加载所有内容会花费太多时间。

这段代码片段用一个夸张的例子说明了这个问题。

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data: () => ({
    loading: false
  }),
  methods: {
    click() {
      console.log('click')
      this.loading = true
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.6.1/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify@2.4.4/dist/vuetify.min.css">


<v-app id="app">
  <template>
    <v-expansion-panels>
      <v-expansion-panel @click="click">
        <v-expansion-panel-header>
          <template v-slot:default="{ open }">
            <span>
              <v-progress-circular
                v-if="loading"
                indeterminate
                size="14"
                width="2"
                color="blue"
                class="d-inline-block"
              />
            </span>
            <span>Click me! opened: {{ open }}</span>
          </template>
        </v-expansion-panel-header>
        <v-expansion-panel-content>
          <v-text-field v-for="(item, i) in 1500" :key="i" />
        </v-expansion-panel-content>
      </v-expansion-panel>
    </v-expansion-panels>
  </template>
</v-app>

问题是 loading 标志更改(包括呈现微调器和面板内容)的效果发生在同一个宏标记中。

一种解决方法是将效果分成两组:(1) 切换 loading 以呈现微调器并打开面板,以及 (2) 切换一个新标志以呈现面板内容。这允许将第二个效果组移动到下一个宏标记:

  1. 添加布尔标志作为数据 属性,并有条件地使用该标志呈现面板内容。

  2. click 处理程序中,等待宏滴答(即等待 setTimeout() 没有超时)。

  3. 切换第 1 步中的标志以呈现面板内容。

<script>
new Vue({
  data: () => ({
    1️⃣
    loadTextfields: false,
  }),
  methods: {
    async click() {
      this.loading = true
      2️⃣
      await new Promise(r => setTimeout(r))
      3️⃣
      this.loadTextfields = true
    }
  }
})
</script>
<v-expansion-panels>
  <v-expansion-panel @click="click">
    ⋮
    <v-expansion-panel-content>
                           1️⃣
      <template v-if="loadTextfields">
        <v-text-field v-for="(item, i) in 1500" :key="i"/>
      </template>
    </v-expansion-panel-content>
  </v-expansion-panel>
</v-expansion-panels>

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data: () => ({
    loading: false,
    loadTextfields: false,
  }),
  methods: {
    async click() {
      console.log('click')
      this.loading = true
      await new Promise(r => setTimeout(r))
      this.loadTextfields = true
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.6.1/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify@2.4.4/dist/vuetify.min.css">


<v-app id="app">
  <template>
    <v-expansion-panels>
      <v-expansion-panel @click="click">
        <v-expansion-panel-header>
          <template v-slot:default="{ open }">
            <span>
              <v-progress-circular
                v-if="loading"
                indeterminate
                size="14"
                width="2"
                color="blue"
                class="d-inline-block"
              />
            </span>
            <span>Click me! opened: {{ open }}</span>
          </template>
        </v-expansion-panel-header>
        <v-expansion-panel-content>
          <template v-if="loadTextfields">
            <v-text-field v-for="(item, i) in 1500" :key="i"/>
          </template>
        </v-expansion-panel-content>
      </v-expansion-panel>
    </v-expansion-panels>
  </template>
</v-app>