computed

准备写一个关于汽车销量的无序数组,并返回展示销量排名前3的。

<template>
  销量总览:
  <ul>
    <li 
    	v-for="(car, idx) in carList" 
    	:key='idx'
     >
      {{ car.name + ' : '+ car.count}}
    </li>
  </ul>
  前三销量:
  <ul>
    <li 
    	v-for="(car, idx) in topThreeList" 
    	:key='idx'
     >
      {{ car.name + ' : '+ car.count}}
    </li>
  </ul>
</template>

<script lang="ts">
import { defineComponent, reactive, computed, toRefs} from "vue";
export default defineComponent({
  setup() {
   const data = reactive({
     carList: [
       {
         name: 'golf',
         count: 200
       },
       {
         name: 'mg6',
         count: 680
       },
       {
         name: 'foucs',
         count: 903
       },
       {
         name: '影豹',
         count: 1000
       },
       {
         name: 'uni-v',
         count: 666
       }
     ]
   })
   const topThreeList = computed(() => {
     let list = JSON.parse(JSON.stringify(data.carList)).sort((a, b) => {
       return b.count - a.count
     })
     return list.slice(0, 3)
   })
    return {
      ...toRefs(data),
      topThreeList
    };
  },
});
</script>

接下来看一下效果

alt

关于computed的一些特殊之处,官方文档是这样说的,例如

1. computed与方法的区别

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>

<template>
  <p>Has published books:</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

效果如下

alt

在表达式中像这样调用一个函数也会获得和计算属性相同的结果:

// 模板中
<p>{{ calculateBooksMessage() }}</p>

// 组件中
function calculateBooksMessage() {
  return author.books.length > 0 ? 'Yes' : 'No'
}

若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果,而不用重复执行 getter 函数。

这也解释了为什么下面的计算属性永远不会更新,因为 Date.now() 并不是一个响应式依赖:

const now = computed(() => Date.now())

相比之下,方法调用总是会在重渲染发生时再次执行函数。那么,需要缓存机制的原因,当然就是性能

2. 不要在计算属性中做异步请求或者更改dom,另外避免直接修改计算属性值


watch 侦听器

在Vue3中,watch有了更强大的功能

1. 基本用法

<template>
  <input type='text' v-model='name' />
  <p>{{ msg }}</p>
</template>

<script lang="ts">
import { defineComponent, reactive, ref, watch} from "vue";
export default defineComponent({
  setup() {
    const msg = ref('hello ');
    const name = ref('');
    watch(name, (v) => {
      msg.value = 'hello ' + v
    })
    return {
      msg,
      name
    }
  },
});
</script>

alt

2. 侦听数据源类型

watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:

const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

注意,你不能直接侦听响应式对象的属性值,例如:

const obj = reactive({ count: 0 })

// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
  console.log(`count is: ${count}`)
})

这里需要用一个返回该属性的 getter 函数:

// 提供一个 getter 函数
watch(
  () => obj.count,
  (count) => {
    console.log(`count is: ${count}`)
  }
)

3. 深层侦听器 (谨慎使用)

你也可以给上面这个例子显式地加上 deep 选项,强制转成深层侦听器:

watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 注意:`newValue` 此处和 `oldValue` 是相等的
    // *除非* state.someObject 被整个替换了
  },
  { deep: true }
)

4. watchEffect()

watch() 是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。

watchEffect(async () => {
  const response = await fetch(url.value)
  data.value = await response.json()
})

大家可以参考这个例子,它用了 watchEffect,还展示了如何做响应式的数据请求。

5. 停止侦听器

要手动停止一个侦听器,请调用 watch 或 watchEffect 返回的函数:

const unwatch = watchEffect(() => {})

// ...当该侦听器不再需要时
unwatch()