Vue中computed于DOM渲染时可见?script setup洗牌prop数据为何可见?
问题描述
在Vue的Composition API的script setup中(相当于Vue2的created钩子),对prop传入的数据执行洗牌操作时,为什么能看到洗牌的过程,而不是在DOM渲染前就完成?
相关代码:
<script setup> const props = defineProps({ categories: Array, lexiconTerms: Array }) const allTerms = computed(() => { const array = [] if (props.categories) array.push(...props.categories) if (props.lexiconTerms) { const lexiconTermsShuffled = props.lexiconTerms.sort(() => Math.random() - 0.5) array.push(...lexiconTermsShuffled) } return array }) </script>
核心原因
1. computed的惰性求值机制
Vue的computed属性是延迟计算的,只有当它被DOM模板引用,或者被其他响应式依赖调用时,才会执行内部的计算逻辑。script setup的代码执行时机是组件实例创建阶段,但此时computed还没被触发,直到DOM开始渲染、需要用到allTerms数据时,才会执行洗牌逻辑——这时候DOM已经在构建中,洗牌后的数组更新会直接反映到页面上,所以你能看到洗牌过程。
2. 原地修改响应式数组触发重渲染
Array.sort()是原地修改原数组的方法,而你传入的lexiconTerms prop是响应式的。哪怕你把它赋值给lexiconTermsShuffled,本质上还是同一个数组的引用。当computed执行时,直接修改了原prop数组,这会立刻触发组件的响应式更新,导致DOM在渲染过程中出现内容变化,进一步放大了“看到洗牌过程”的现象。
解决方法
要避免页面上看到洗牌过程,需要做到两点:不修改原prop数组,确保洗牌逻辑在DOM渲染前完成(或者让computed只执行一次洗牌)。
方案1:在onMounted中提前处理数据
利用onMounted钩子在DOM渲染前完成洗牌,把结果存入ref变量:
<script setup> import { onMounted, ref } from 'vue' const props = defineProps({ categories: Array, lexiconTerms: Array }) const allTerms = ref([]) onMounted(() => { const tempArray = [] if (props.categories) tempArray.push(...props.categories) if (props.lexiconTerms) { // 创建原数组的副本,避免修改原prop const lexiconCopy = [...props.lexiconTerms] lexiconCopy.sort(() => Math.random() - 0.5) tempArray.push(...lexiconCopy) } allTerms.value = tempArray }) </script>
方案2:在computed中操作数组副本(适用于props不需要动态更新的场景)
如果props不会动态变化,可以在computed里先复制原数组再洗牌,避免修改原响应式数组:
<script setup> const props = defineProps({ categories: Array, lexiconTerms: Array }) const allTerms = computed(() => { const array = [] if (props.categories) array.push(...props.categories) if (props.lexiconTerms) { // 创建原数组的副本再洗牌 const lexiconCopy = [...props.lexiconTerms] lexiconCopy.sort(() => Math.random() - 0.5) array.push(...lexiconCopy) } return array }) </script>
注:如果props后续会更新,computed会重新执行洗牌逻辑,可能再次出现页面变化。
内容的提问来源于stack exchange,提问作者John the Painter

