← 返回主页

第3课: 计算属性与侦听器

计算属性(Computed)

计算属性是基于响应式依赖进行缓存的,只有依赖发生变化时才会重新计算。

Options API写法

<template>
  <div>
    <p>原始消息: {{ message }}</p>
    <p>反转消息: {{ reversedMessage }}</p>
    <p>全名: {{ fullName }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue3',
      firstName: '张',
      lastName: '三'
    }
  },
  computed: {
    // 只读计算属性
    reversedMessage() {
      return this.message.split('').reverse().join('')
    },
    // 可读写计算属性
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(newValue) {
        const names = newValue.split(' ')
        this.firstName = names[0]
        this.lastName = names[names.length - 1]
      }
    }
  }
}
</script>

Composition API写法

<template>
  <div>
    <p>原始消息: {{ message }}</p>
    <p>反转消息: {{ reversedMessage }}</p>
    <p>全名: {{ fullName }}</p>
  </div>
</template>

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

const message = ref('Hello Vue3')
const firstName = ref('张')
const lastName = ref('三')

// 只读计算属性
const reversedMessage = computed(() => {
  return message.value.split('').reverse().join('')
})

// 可读写计算属性
const fullName = computed({
  get() {
    return firstName.value + ' ' + lastName.value
  },
  set(newValue) {
    const names = newValue.split(' ')
    firstName.value = names[0]
    lastName.value = names[names.length - 1]
  }
})
</script>

计算属性 vs 方法

<template>
  <div>
    <!-- 计算属性:有缓存 -->
    <p>{{ reversedMessage }}</p>
    <p>{{ reversedMessage }}</p>

    <!-- 方法:每次都执行 -->
    <p>{{ reverseMessage() }}</p>
    <p>{{ reverseMessage() }}</p>
  </div>
</template>

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

const message = ref('Hello')

// 计算属性:只计算一次
const reversedMessage = computed(() => {
  console.log('计算属性执行')
  return message.value.split('').reverse().join('')
})

// 方法:每次调用都执行
function reverseMessage() {
  console.log('方法执行')
  return message.value.split('').reverse().join('')
}
</script>
提示:计算属性会缓存结果,只有依赖变化时才重新计算。方法每次调用都会执行。

侦听器(Watch)

侦听器用于观察和响应数据的变化,适合执行异步操作或开销较大的操作。

Options API写法

<template>
  <div>
    <input v-model="question" placeholder="输入问题">
    <p>{{ answer }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      question: '',
      answer: '请输入问题'
    }
  },
  watch: {
    // 简单侦听
    question(newValue, oldValue) {
      console.log(`问题从 "${oldValue}" 变为 "${newValue}"`)
      this.getAnswer()
    },
    // 深度侦听
    user: {
      handler(newValue, oldValue) {
        console.log('用户信息变化')
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    getAnswer() {
      if (this.question.includes('?')) {
        this.answer = '思考中...'
        setTimeout(() => {
          this.answer = '这是一个好问题!'
        }, 1000)
      }
    }
  }
}
</script>

Composition API写法

<template>
  <div>
    <input v-model="question" placeholder="输入问题">
    <p>{{ answer }}</p>
  </div>
</template>

<script setup>
import { ref, watch, watchEffect } from 'vue'

const question = ref('')
const answer = ref('请输入问题')

// watch: 明确指定侦听源
watch(question, (newValue, oldValue) => {
  console.log(`问题从 "${oldValue}" 变为 "${newValue}"`)
  if (newValue.includes('?')) {
    answer.value = '思考中...'
    setTimeout(() => {
      answer.value = '这是一个好问题!'
    }, 1000)
  }
})

// watchEffect: 自动追踪依赖
watchEffect(() => {
  console.log(`当前问题: ${question.value}`)
})

// 侦听多个源
const firstName = ref('')
const lastName = ref('')

watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log(`姓名变化: ${newFirst} ${newLast}`)
})
</script>

深度侦听

<script setup>
import { ref, watch } from 'vue'

const user = ref({
  name: '张三',
  age: 25,
  address: {
    city: '北京'
  }
})

// 深度侦听对象
watch(user, (newValue, oldValue) => {
  console.log('用户信息变化', newValue)
}, { deep: true })

// 侦听对象的某个属性
watch(() => user.value.name, (newValue, oldValue) => {
  console.log(`姓名从 ${oldValue} 变为 ${newValue}`)
})
</script>

立即执行侦听器

<script setup>
import { ref, watch } from 'vue'

const count = ref(0)

// immediate: 立即执行一次
watch(count, (newValue) => {
  console.log('count变化:', newValue)
}, { immediate: true })

// watchEffect: 默认立即执行
watchEffect(() => {
  console.log('count当前值:', count.value)
})
</script>

停止侦听器

<script setup>
import { ref, watch } from 'vue'

const count = ref(0)

// 侦听器返回停止函数
const stopWatch = watch(count, (newValue) => {
  console.log('count:', newValue)
  if (newValue >= 10) {
    stopWatch() // 停止侦听
  }
})
</script>

实战示例:搜索功能

<template>
  <div>
    <input v-model="searchText" placeholder="搜索...">
    <p>搜索结果数量: {{ filteredItems.length }}</p>
    <ul>
      <li v-for="item in filteredItems" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

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

const searchText = ref('')
const items = ref([
  { id: 1, name: 'Vue3教程' },
  { id: 2, name: 'React教程' },
  { id: 3, name: 'Vue Router' },
  { id: 4, name: 'Pinia状态管理' }
])

// 使用计算属性过滤
const filteredItems = computed(() => {
  return items.value.filter(item =>
    item.name.toLowerCase().includes(searchText.value.toLowerCase())
  )
})

// 使用侦听器记录搜索历史
watch(searchText, (newValue) => {
  if (newValue) {
    console.log('搜索:', newValue)
    // 可以在这里保存搜索历史
  }
})
</script>

练习

  1. 创建一个购物车,使用计算属性计算总价
  2. 实现一个表单验证,使用侦听器检查输入是否合法
  3. 创建一个实时搜索功能,使用计算属性过滤列表
  4. 使用watchEffect实现一个自动保存功能