# 使用Devtools调优按键响应性能

# 插件安装

参考下方链接的"(2) 安装Vue.js Devtools (for JsView)插件"安装vue.js插件
链接:[Vue Devtools for JsView插件]

# 使用指南

打开vue插件界面,点到timeline标签,每次响应式变量调整触发界面刷新时,都可以看到如下图所示的时间占比线段图:

图中所见的阶段类型分为:

  • reactive: 响应式变量触发的组件刷新
  • render: 在reactive阶段内, 刷新组件的所有内容, 启动:xxx="{...}"形式的内容会重算, 并生成新的object
  • patch: 在reactive阶段内, render之后, 检测组件刷新后哪些props变化, 并触发这些组件的reactive
  • mount(react-mount): 在patch阶段内, 标识子组件新创

按键响应流程也如下图所示

从经验上来说, 尽量减少一次按键所引起的 reactive 组件的个数是提升性能的关键。

# 样例分析

下面是最容易发生的低性能的代码样例:

  • 【样例1】

v-for中, 焦点处理不要讲index和焦点index的判断写在template中 样例代码(可通过npm instal jsview后, 将下面代码拷贝并覆盖 src/App.vue 可进行实机尝试)
Item1.vue中直接将是否为焦点的判断放到template中, 当焦点index变化时会触发所有item的渲染(可观察vue.js工具的timeline), 性能远比Item2.vue放入watchEffect的触发的渲染项目多很多, 导致按键响应很慢。

App.vue部分:

<template>
  <!-- 用jsv-focus-block接受按键, 触发节点变化 -->
  <jsv-focus-block autoFocus :onKeyDown="processKeyDown">
    <!-- 这是用v-for做一段内容排布并把启动一个元素高亮显示 -->
    <div
      v-for="(item, index) of itemArray"
      :key="'idx' + index"
      :style="{ left: index * 120 }"
    >
      <Item :idx="index" :displayText="item" />
    </div>
  </jsv-focus-block>
</template>

<script setup>
import { onMounted, shallowRef, provide } from "vue";
import { jJsvRuntimeBridge, DefaultKeyCodeMap } from "jsview";
// import Item from "./Item1.vue";  // reactive触发过多, 低性能的样例
import Item from "./Item2.vue"; // reactive的触发个数被控制, 高性能的样例

let itemArray = [];
const MAX_ITEM_COUNT = 20;
for (let i = 0; i < MAX_ITEM_COUNT; i++) {
  itemArray[i] = "t" + i;
}

// 将焦点信息通知给子节点
let focusIndex = shallowRef(0);
provide("globalFocus", focusIndex);

const processKeyDown = (ev) => {
  if (ev.keyCode == DefaultKeyCodeMap.Ok) {
    // 收到[确定键OK]后, index循环++
    focusIndex.value = (focusIndex.value + 1) % MAX_ITEM_COUNT;
  } else if (
    ev.keyCode == 10000 /* 盒子runtime的返回键值 */ ||
    ev.keyCode == 8 /* PC浏览器的返回键 */
  ) {
    // JsView体系下,和runtime端约定,如果js的根节点处理未处理返回键,或者处理了但是返回false
    // 则视作js出现了异常,此时runtime将自我退出,
    // 以规避用户无法通过返回键退出异常小程序的问题

    // 如有需要进行主动退出时, 处理如下:
    // jJsvRuntimeBridge.closePage();

    console.log("consume back key...");
    return true; // consume the key
  }
};

onMounted(() => {
  /* apk到收到此调用后才会结束启动过程,去掉启动图(否则会显示加载超时的界面) */
  jJsvRuntimeBridge.notifyPageLoaded();
});
</script>

<style scoped></style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

Item1.vue部分:

<template>
  <!-- 用jsv-focus-block接受按键, 触发节点变化 -->
  <div
    class="itemSize"
    :class="globalFocus == props.idx ? 'focusItem' : 'normalItem'"
  >
    {{ props.displayText }}
  </div>
</template>

<script setup>
import { inject, watchEffect, shallowRef } from "vue";

let props = defineProps({
  idx: Number,
  displayText: String,
});

// globalFocus状态直接影响template会直接触发渲染
let globalFocus = inject("globalFocus");
</script>

<style scoped>
.focusItem {
  background-color: #c8bd19;
}
.normalItem {
  background-color: #6a9eda;
}
.itemSize {
  font-size: 60;
  text-align: center;
  line-height: 75;
  width: 110;
  height: 75;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

Item2.vue部分:

<template>
  <!-- 用jsv-focus-block接受按键, 触发节点变化 -->
  <div class="itemSize" :class="focused ? 'focusItem' : 'normalItem'">
    {{ props.displayText }}
  </div>
</template>

<script setup>
import { inject, watchEffect, shallowRef } from "vue";

let props = defineProps({
  idx: Number,
  displayText: String,
});

// 通过检测整体焦点状态(或者通过onFocus/onBlur回调), 影响本vue的响应式变量focused, 来规避渲染树其他节点触发渲染
// watchEffect改变单一项目的值,可规避触发所有项目的渲染动作(项目渲染消耗远高于watchEffect)
let globalFocus = inject("globalFocus");
let focused = shallowRef(false);
watchEffect(() => {
  focused.value = globalFocus.value == props.idx;
});
</script>

<style scoped>
.focusItem {
  background-color: #c8bd19;
}
.normalItem {
  background-color: #6a9eda;
}
.itemSize {
  font-size: 60;
  text-align: center;
  line-height: 75;
  width: 110;
  height: 75;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  • 【样例2】

含有v-for的template中, 避免有响应式变量(v-for的子组件组件内部的响应式变量可以写, 组件是隔离的), 例如如下代码, 其中globalFocus为响应式变量, 当globalFocus改变时, 会触发v-for的父节点 jsv-focus-block 进行reactive阶段处理, 此阶段的render阶段, 会重算所有的:set并生成新的Object引用, 从而触发每个item的 props.set 的响应式激发Item的reactive阶段处理, 进而导致性能过低。

<template>
  <jsv-focus-block autoFocus :onKeyDown="processKeyDown">
    <div
      v-for="(item, index) of itemArray"
      :key="'idx' + index"
    >
      <Item 
        :focused="globalFocus == index" 
        :set="{
          set1:someObject1, 
          set2:someObject2}" />
    </div>
  </jsv-focus-block>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Last Updated: 5/11/2024, 6:29:37 AM