第4课:组合式API详解

【腾讯云】语音识别准确率高,支持多语种,多场景,限时特惠,最低14.9元起

推广

【腾讯云】语音识别准确率高,支持多语种,多场景,限时特惠,最低14.9元起

组合式API详解

setup函数

setup基础

<template>
  <div>
    <h2>{{ title }}</h2>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
    <p>双倍: {{ doubleCount }}</p>
  </div>
</template>

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

export default {
  name: 'SetupExample',
  // setup函数在组件创建之前执行
  setup(props, context) {
    console.log('setup执行了')
    console.log('props:', props)
    console.log('context:', context)
    
    // 响应式数据
    const title = ref('Composition API示例')
    const count = ref(0)
    
    // 计算属性
    const doubleCount = computed(() => count.value * 2)
    
    // 方法
    const increment = () => {
      count.value++
    }
    
    // 返回模板需要的数据和方法
    return {
      title,
      count,
      doubleCount,
      increment
    }
  }
}
</script>

setup语法糖

<template>
  <div>
    <h2>{{ title }}</h2>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
    <p>双倍: {{ doubleCount }}</p>
  </div>
</template>

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

// 使用<script setup>语法糖,无需手动返回
const title = ref('Setup语法糖示例')
const count = ref(0)

const doubleCount = computed(() => count.value * 2)

const increment = () => {
  count.value++
}
</script>

props和emit

<!-- 子组件 ChildComponent.vue -->
<template>
  <div>
    <h3>{{ title }}</h3>
    <p>接收到的消息: {{ message }}</p>
    <p>计数: {{ count }}</p>
    <button @click="handleIncrement">增加并通知父组件</button>
    <button @click="sendMessage">发送消息给父组件</button>
  </div>
</template>

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

// 定义props
const props = defineProps({
  message: {
    type: String,
    required: true
  },
  initialCount: {
    type: Number,
    default: 0
  }
})

// 定义emits
const emit = defineEmits(['update:count', 'send-message'])

// 响应式数据
const count = ref(props.initialCount)

// 计算属性
const title = computed(() => `子组件 - ${props.message}`)

// 方法
const handleIncrement = () => {
  count.value++
  emit('update:count', count.value)
}

const sendMessage = () => {
  emit('send-message', `来自子组件的消息: ${Date.now()}`)
}
</script>
<!-- 父组件 -->
<template>
  <div>
    <h2>父组件</h2>
    <p>父组件计数: {{ parentCount }}</p>
    <p>收到的消息: {{ receivedMessage }}</p>
    
    <ChildComponent
      :message="parentMessage"
      :initial-count="parentCount"
      @update:count="handleCountUpdate"
      @send-message="handleMessage"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const parentCount = ref(10)
const parentMessage = ref('Hello from parent')
const receivedMessage = ref('')

const handleCountUpdate = (newCount) => {
  parentCount.value = newCount
}

const handleMessage = (message) => {
  receivedMessage.value = message
}
</script>

响应式API详解

ref深入

<template>
  <div>
    <h2>ref详解</h2>
    
    <!-- 基本类型 -->
    <div>
      <p>字符串: {{ str }}</p>
      <p>数字: {{ num }}</p>
      <p>布尔值: {{ bool }}</p>
      <button @click="updateBasicTypes">更新基本类型</button>
    </div>
    
    <!-- 对象类型 -->
    <div>
      <p>用户: {{ user.name }} - {{ user.age }}</p>
      <button @click="updateUser">更新用户</button>
    </div>
    
    <!-- 数组类型 -->
    <div>
      <p>列表: {{ list.join(', ') }}</p>
      <button @click="updateList">更新列表</button>
    </div>
    
    <!-- DOM引用 -->
    <div>
      <input ref="inputRef" placeholder="输入内容">
      <button @click="focusInput">聚焦输入框</button>
    </div>
  </div>
</template>

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

// 基本类型的ref
const str = ref('Hello')
const num = ref(42)
const bool = ref(true)

// 对象类型的ref
const user = ref({
  name: '张三',
  age: 25
})

// 数组类型的ref
const list = ref([1, 2, 3])

// DOM引用
const inputRef = ref(null)

// 更新基本类型
const updateBasicTypes = () => {
  str.value = 'Hello Vue3!'
  num.value = Math.random() * 100
  bool.value = !bool.value
}

// 更新对象
const updateUser = () => {
  // 直接替换整个对象
  user.value = {
    name: '李四',
    age: 30
  }
  
  // 或者修改对象属性
  // user.value.name = '李四'
  // user.value.age = 30
}

// 更新数组
const updateList = () => {
  list.value.push(list.value.length + 1)
}

// 聚焦输入框
const focusInput = async () => {
  await nextTick()
  inputRef.value?.focus()
}
</script>

reactive深入

<template>
  <div>
    <h2>reactive详解</h2>
    
    <!-- 对象响应式 -->
    <div>
      <h3>用户信息</h3>
      <p>姓名: {{ state.user.name }}</p>
      <p>年龄: {{ state.user.age }}</p>
      <p>邮箱: {{ state.user.email }}</p>
      <button @click="updateUser">更新用户</button>
    </div>
    
    <!-- 数组响应式 -->
    <div>
      <h3>任务列表</h3>
      <ul>
        <li v-for="task in state.tasks" :key="task.id">
          {{ task.title }} - {{ task.completed ? '已完成' : '未完成' }}
          <button @click="toggleTask(task.id)">切换状态</button>
        </li>
      </ul>
      <button @click="addTask">添加任务</button>
    </div>
    
    <!-- 嵌套对象 -->
    <div>
      <h3>设置</h3>
      <p>主题: {{ state.settings.theme }}</p>
      <p>语言: {{ state.settings.language }}</p>
      <p>通知: {{ state.settings.notifications.email ? '开启' : '关闭' }}</p>
      <button @click="updateSettings">更新设置</button>
    </div>
  </div>
</template>

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

// 创建响应式对象
const state = reactive({
  user: {
    name: '张三',
    age: 25,
    email: 'zhangsan@example.com'
  },
  tasks: [
    { id: 1, title: '学习Vue3', completed: false },
    { id: 2, title: '写代码', completed: true }
  ],
  settings: {
    theme: 'dark',
    language: 'zh-CN',
    notifications: {
      email: true,
      push: false
    }
  }
})

// 更新用户信息
const updateUser = () => {
  state.user.name = '李四'
  state.user.age = 30
  state.user.email = 'lisi@example.com'
}

// 切换任务状态
const toggleTask = (id) => {
  const task = state.tasks.find(t => t.id === id)
  if (task) {
    task.completed = !task.completed
  }
}

// 添加任务
const addTask = () => {
  const newId = state.tasks.length + 1
  state.tasks.push({
    id: newId,
    title: `新任务${newId}`,
    completed: false
  })
}

// 更新设置
const updateSettings = () => {
  state.settings.theme = state.settings.theme === 'dark' ? 'light' : 'dark'
  state.settings.language = state.settings.language === 'zh-CN' ? 'en-US' : 'zh-CN'
  state.settings.notifications.email = !state.settings.notifications.email
}
</script>

toRef和toRefs

<template>
  <div>
    <h2>toRef和toRefs示例</h2>
    
    <div>
      <h3>原始对象</h3>
      <p>姓名: {{ user.name }}</p>
      <p>年龄: {{ user.age }}</p>
      <p>城市: {{ user.city }}</p>
    </div>
    
    <div>
      <h3>使用toRef</h3>
      <p>姓名: {{ userName }}</p>
      <button @click="updateUserName">更新姓名</button>
    </div>
    
    <div>
      <h3>使用toRefs</h3>
      <p>年龄: {{ age }}</p>
      <p>城市: {{ city }}</p>
      <button @click="updateAgeAndCity">更新年龄和城市</button>
    </div>
  </div>
</template>

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

// 响应式对象
const user = reactive({
  name: '张三',
  age: 25,
  city: '北京'
})

// 使用toRef创建单个属性的ref
const userName = toRef(user, 'name')

// 使用toRefs解构响应式对象
const { age, city } = toRefs(user)

// 更新姓名
const updateUserName = () => {
  userName.value = '李四'
  // 这会同时更新user.name
}

// 更新年龄和城市
const updateAgeAndCity = () => {
  age.value = 30
  city.value = '上海'
  // 这会同时更新user.age和user.city
}
</script>

生命周期钩子

生命周期对比

<template>
  <div>
    <h2>生命周期示例</h2>
    <p>计数: {{ count }}</p>
    <button @click="increment">增加</button>
    <p>消息: {{ message }}</p>
  </div>
</template>

<script setup>
import { 
  ref, 
  onBeforeMount, 
  onMounted, 
  onBeforeUpdate, 
  onUpdated, 
  onBeforeUnmount, 
  onUnmounted 
} from 'vue'

const count = ref(0)
const message = ref('组件已创建')

// 组件挂载前
onBeforeMount(() => {
  console.log('onBeforeMount: 组件挂载前')
  message.value = '组件即将挂载'
})

// 组件挂载后
onMounted(() => {
  console.log('onMounted: 组件挂载后')
  message.value = '组件已挂载'
  
  // 可以访问DOM
  console.log('DOM元素:', document.querySelector('h2'))
})

// 组件更新前
onBeforeUpdate(() => {
  console.log('onBeforeUpdate: 组件更新前')
  console.log('更新前的count:', count.value)
})

// 组件更新后
onUpdated(() => {
  console.log('onUpdated: 组件更新后')
  console.log('更新后的count:', count.value)
})

// 组件卸载前
onBeforeUnmount(() => {
  console.log('onBeforeUnmount: 组件卸载前')
  // 清理工作,如取消定时器、移除事件监听器等
})

// 组件卸载后
onUnmounted(() => {
  console.log('onUnmounted: 组件卸载后')
})

const increment = () => {
  count.value++
}

// setup函数相当于beforeCreate和created的组合
console.log('setup: 组件创建时')
</script>

生命周期实际应用

<template>
  <div>
    <h2>生命周期实际应用</h2>
    
    <div>
      <h3>定时器示例</h3>
      <p>时间: {{ currentTime }}</p>
      <button @click="toggleTimer">{{ timerRunning ? '停止' : '开始' }}定时器</button>
    </div>
    
    <div>
      <h3>窗口大小监听</h3>
      <p>窗口大小: {{ windowSize.width }} x {{ windowSize.height }}</p>
    </div>
    
    <div>
      <h3>数据获取</h3>
      <div v-if="loading">加载中...</div>
      <div v-else-if="error">错误: {{ error }}</div>
      <div v-else>
        <p>用户数量: {{ userData.length }}</p>
        <ul>
          <li v-for="user in userData" :key="user.id">
            {{ user.name }}
          </li>
        </ul>
      </div>
      <button @click="fetchData">重新获取数据</button>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'

// 定时器相关
const currentTime = ref(new Date().toLocaleTimeString())
const timerRunning = ref(false)
let timer = null

// 窗口大小监听
const windowSize = reactive({
  width: window.innerWidth,
  height: window.innerHeight
})

// 数据获取相关
const userData = ref([])
const loading = ref(false)
const error = ref(null)

// 定时器控制
const toggleTimer = () => {
  if (timerRunning.value) {
    clearInterval(timer)
    timer = null
    timerRunning.value = false
  } else {
    timer = setInterval(() => {
      currentTime.value = new Date().toLocaleTimeString()
    }, 1000)
    timerRunning.value = true
  }
}

// 窗口大小变化处理
const handleResize = () => {
  windowSize.width = window.innerWidth
  windowSize.height = window.innerHeight
}

// 模拟数据获取
const fetchData = async () => {
  loading.value = true
  error.value = null
  
  try {
    // 模拟API调用
    await new Promise(resolve => setTimeout(resolve, 1000))
    
    // 模拟数据
    userData.value = [
      { id: 1, name: '张三' },
      { id: 2, name: '李四' },
      { id: 3, name: '王五' }
    ]
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

// 组件挂载后
onMounted(() => {
  // 添加窗口大小监听
  window.addEventListener('resize', handleResize)
  
  // 获取初始数据
  fetchData()
  
  // 启动定时器
  toggleTimer()
})

// 组件卸载前清理
onUnmounted(() => {
  // 清理定时器
  if (timer) {
    clearInterval(timer)
  }
  
  // 移除事件监听器
  window.removeEventListener('resize', handleResize)
})
</script>

watch和watchEffect

watch监听器

<template>
  <div>
    <h2>watch监听器示例</h2>
    
    <div>
      <h3>基本监听</h3>
      <input v-model="searchText" placeholder="输入搜索内容">
      <p>搜索结果数量: {{ searchResults.length }}</p>
    </div>
    
    <div>
      <h3>对象监听</h3>
      <input v-model="user.name" placeholder="姓名">
      <input v-model.number="user.age" placeholder="年龄" type="number">
      <p>用户信息变化次数: {{ userChangeCount }}</p>
    </div>
    
    <div>
      <h3>多个值监听</h3>
      <input v-model="firstName" placeholder="名">
      <input v-model="lastName" placeholder="姓">
      <p>全名: {{ fullName }}</p>
    </div>
  </div>
</template>

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

// 基本监听
const searchText = ref('')
const searchResults = ref([])

// 监听搜索文本变化
watch(searchText, async (newValue, oldValue) => {
  console.log(`搜索文本从 "${oldValue}" 变为 "${newValue}"`)
  
  if (newValue) {
    // 模拟搜索API调用
    await new Promise(resolve => setTimeout(resolve, 300))
    searchResults.value = Array.from({ length: newValue.length }, (_, i) => ({
      id: i + 1,
      title: `结果 ${i + 1} - ${newValue}`
    }))
  } else {
    searchResults.value = []
  }
}, {
  // 立即执行
  immediate: true
})

// 对象监听
const user = reactive({
  name: '张三',
  age: 25
})
const userChangeCount = ref(0)

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

// 监听对象的特定属性
watch(() => user.name, (newName, oldName) => {
  console.log(`用户名从 "${oldName}" 变为 "${newName}"`)
})

// 多个值监听
const firstName = ref('三')
const lastName = ref('张')
const fullName = ref('')

// 监听多个响应式引用
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log('姓名变化:', { newFirst, newLast }, { oldFirst, oldLast })
  fullName.value = `${newLast}${newFirst}`
}, {
  immediate: true
})
</script>

watchEffect

<template>
  <div>
    <h2>watchEffect示例</h2>
    
    <div>
      <h3>自动依赖追踪</h3>
      <input v-model="keyword" placeholder="关键词">
      <select v-model="category">
        <option value="">所有分类</option>
        <option value="tech">技术</option>
        <option value="life">生活</option>
      </select>
      <p>过滤后的文章数量: {{ filteredArticles.length }}</p>
    </div>
    
    <div>
      <h3>副作用清理</h3>
      <p>鼠标位置: {{ mousePosition.x }}, {{ mousePosition.y }}</p>
      <button @click="toggleMouseTracking">
        {{ trackingMouse ? '停止' : '开始' }}追踪鼠标
      </button>
    </div>
  </div>
</template>

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

// 自动依赖追踪示例
const keyword = ref('')
const category = ref('')
const filteredArticles = ref([])

const articles = [
  { id: 1, title: 'Vue3教程', category: 'tech' },
  { id: 2, title: '生活小贴士', category: 'life' },
  { id: 3, title: 'JavaScript进阶', category: 'tech' },
  { id: 4, title: '健康饮食', category: 'life' }
]

// watchEffect会自动追踪依赖
watchEffect(() => {
  console.log('过滤条件变化,重新过滤文章')
  
  filteredArticles.value = articles.filter(article => {
    const matchKeyword = !keyword.value || 
      article.title.toLowerCase().includes(keyword.value.toLowerCase())
    const matchCategory = !category.value || article.category === category.value
    
    return matchKeyword && matchCategory
  })
})

// 副作用清理示例
const mousePosition = reactive({ x: 0, y: 0 })
const trackingMouse = ref(false)

const handleMouseMove = (event) => {
  mousePosition.x = event.clientX
  mousePosition.y = event.clientY
}

// 带清理的watchEffect
watchEffect((onInvalidate) => {
  if (trackingMouse.value) {
    console.log('开始追踪鼠标')
    window.addEventListener('mousemove', handleMouseMove)
    
    // 清理函数
    onInvalidate(() => {
      console.log('停止追踪鼠标')
      window.removeEventListener('mousemove', handleMouseMove)
    })
  }
})

const toggleMouseTracking = () => {
  trackingMouse.value = !trackingMouse.value
}
</script>

逻辑复用

组合函数(Composables)

// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  const isEven = computed(() => count.value % 2 === 0)
  const isPositive = computed(() => count.value > 0)
  
  return {
    count,
    increment,
    decrement,
    reset,
    isEven,
    isPositive
  }
}
// composables/useFetch.js
import { ref, watchEffect } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  
  const fetchData = async () => {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(url.value || url)
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      data.value = await response.json()
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }
  
  // 如果url是响应式的,监听变化
  if (typeof url === 'object' && url.value !== undefined) {
    watchEffect(fetchData)
  } else {
    fetchData()
  }
  
  return {
    data,
    loading,
    error,
    refetch: fetchData
  }
}
<!-- 使用组合函数 -->
<template>
  <div>
    <h2>组合函数示例</h2>
    
    <div>
      <h3>计数器组合函数</h3>
      <p>计数: {{ count }}</p>
      <p>是偶数: {{ isEven ? '是' : '否' }}</p>
      <p>是正数: {{ isPositive ? '是' : '否' }}</p>
      <button @click="increment">增加</button>
      <button @click="decrement">减少</button>
      <button @click="reset">重置</button>
    </div>
    
    <div>
      <h3>数据获取组合函数</h3>
      <div v-if="loading">加载中...</div>
      <div v-else-if="error">错误: {{ error }}</div>
      <div v-else-if="data">
        <p>获取到 {{ data.length }} 条数据</p>
        <button @click="refetch">重新获取</button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { useCounter } from '@/composables/useCounter'
import { useFetch } from '@/composables/useFetch'

// 使用计数器组合函数
const {
  count,
  increment,
  decrement,
  reset,
  isEven,
  isPositive
} = useCounter(10)

// 使用数据获取组合函数
const {
  data,
  loading,
  error,
  refetch
} = useFetch('https://jsonplaceholder.typicode.com/posts')
</script>

总结

本课我们深入学习了Vue3的Composition API:

  1. setup函数:组件的入口点,替代Options API
  2. 响应式API:ref、reactive、toRef、toRefs的使用
  3. 生命周期钩子:在Composition API中的使用方式
  4. watch和watchEffect:监听响应式数据变化
  5. 逻辑复用:通过组合函数实现代码复用

下一课预告

在下一课中,我们将学习Vue3的组件开发,包括:

  • 组件通信
  • 插槽系统
  • 动态组件
  • 异步组件

💡 小贴士:Composition API的核心思想是按逻辑功能组织代码,而不是按选项类型。这使得代码更容易理解和维护,特别是在大型组件中。

Vue3 + TypeScript 企业级项目实战

课程推荐

Vue3 + TypeScript 企业级项目实战
Python 全栈开发工程师培训

热门课程

Python 全栈开发工程师培训

📚 文章对你有帮助?请关注我的公众号,万分感谢!

获取更多优质技术文章,第一时间掌握最新技术动态

关注公众号

关注公众号

第一时间获取最新技术文章

添加微信

添加微信

技术交流 · 问题答疑 · 学习指导

评论讨论

欢迎留下你的想法和建议