前端笔记-Vue3(中)

学习参考视频:尚硅谷Vue3入门到实战,最新版vue3+TypeScript前端开发教程_哔哩哔哩_bilibili

vue3学习目标:

VUE 3 1、Vue3架构与设计理念
2、组合式API(Composition API)
3、常用API:ref、reactive、watch、computed
4、Vue3的生命周期
5、组件间通信(props、emit、defineModel)
6、了解插槽

 


Computed 计算属性

这里根据一个例子,开展本章节的知识点 

无 computed 的例子(手动计算)​


问题​:

  • 需要手动同步​ fullName,容易遗漏更新。
  • 逻辑分散,维护成本高

使用 computed 的例子(自动计算)​


优势​:

  • 自动追踪依赖​(firstNamelastName),变化时自动重新计算
  • 代码更简洁,逻辑集中。
特性 ​无 computed ​有 computed
计算逻辑 需手动维护(如 fullName = firstName + lastName 自动计算(基于函数返回值)
响应式更新 需手动触发更新 依赖变化时自动更新
代码组织 逻辑分散,易出错 逻辑集中,易维护
性能优化 无缓存,每次访问重新计算 有缓存,依赖未变化时直接返回缓存值
适用场景 简单且无需频繁更新的场景 依赖其他数据的复杂计算频繁访问

computed 核心要点

  1. 缓存机制

    • 只有依赖的响应式数据变化时才会重新计算,否则返回缓存值。
  2. 只读性

    • 默认是只读的,若需可写,需提供 getset
      // 计算属性——既读取又修改
        let fullName = computed({
          // 读取
          get(){
            return firstName.value + '-' + lastName.value
          },
          // 修改
          set(val){
            console.log('有人修改了fullName',val)
            firstName.value = val.split('-')[0]
            lastName.value = val.split('-')[1]
          }
        })
  3. 模板中透明使用

    • 像普通属性一样使用,无需调用(如 {{ fullName }})。

何时使用 computed?​

✅ ​适合​:

  • 需要基于其他数据动态计算的属性(如过滤列表、格式化数据)。
  • 需要缓存优化的场景(避免重复计算)。

❌ ​不适合​:

  • 需要执行副作用​(如异步请求、DOM 操作)→ 改用 watch
  • 计算逻辑极其简单且只使用一次 → 可直接内联在模板中。

watch

1. watch 核心作用

监视数据变化,执行副作用(如异步请求、DOM 操作、状态更新等)。

2. Vue3 中 watch 的监视目标

只能监视以下 ​4 种数据​:

  1. ref 定义的响应式数据​
    const count = ref(0)
    watch(count, (newVal, oldVal) => { /* ... */ })
  2. reactive 定义的响应式对象​
    const user = reactive({ name: '张三', age: 18 })
    watch(() => user.name, (newVal, oldVal) => { /* ... */ }) // 监听单个属性
  3. getter 函数(返回一个值)
    watch(
      () => user.age + 1, // 计算属性
      (newVal, oldVal) => { /* ... */ }
    )
  4. 包含上述内容的数组(监听多个数据源)​
    watch([count, () => user.age], ([newCount, newAge], [oldCount, oldAge]) => { /* ... */ })

3. watch 的 5 种常见使用场景

​✅ 场景 1:监听 ref 数据
const count = ref(0)
watch(count, (newVal, oldVal) => {
  console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})

这里有一个小问题

前端笔记-Vue3(中)_第1张图片

监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。

注意:

  • 若修改的是ref定义的对象中的属性,newValue 和 oldValue 都是新值,因为它们是同一个对象。

  • 若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。

​✅ 场景 2:监听 ref或reactive对象的某个属性

当使用 watch 监视 ref 或 reactive 定义的对象类型数据中的某个属性时,需要注意以下规则:

  1. 若属性值不是对象类型​:

    • 必须写成函数形式返回该属性值
    • 示例:
      const state = reactive({ count: 0, name: 'John' })
      
      // 监视非对象属性 count
      watch(() => state.count, (newVal, oldVal) => {
        console.log('count changed:', newVal, oldVal)
      })
  2. 若属性值仍然是对象类型​:

    • 可以直接写该属性(Vue 会自动处理)
    • 但建议统一写成函数形式(更明确且一致)
    • 示例:
      const state = reactive({ 
        user: { name: 'John', age: 30 },
        settings: { theme: 'dark' }
      })
      
      // 监视对象属性 user - 两种写法都有效
      watch(state.user, (newVal, oldVal) => { /* ... */ }) // 直接写法
      watch(() => state.user, (newVal, oldVal) => { /* ... */ }) // 推荐函数写法

重要注意事项

  1. 对象监视的是地址值​:

    • 当监视整个对象时,watch 实际上监视的是对象的引用(内存地址)
    • 如果只是修改对象内部的属性而引用不变,默认不会触发回调
  2. 需要手动开启深度监视​:

    • 若要监视对象内部属性的变化,必须设置 { deep: true }
    • 示例:
      watch(() => state.user, (newVal, oldVal) => {
        console.log('user changed:', newVal)
      }, { deep: true }) // 深度监视
​✅ 场景 3:监听 reactive 对象的所有属性(深度监听)
const user = reactive({ name: '张三', age: 18 })
watch(
  () => user, // 直接监听整个对象
  (newVal, oldVal) => {
    console.log('user 变化了', newVal)
  },
  { deep: true } // 必须开启 deep
)
情况 写法 是否需要深度监视
监视对象中的非对象属性 必须函数形式 () => obj.prop 不需要
监视对象中的对象属性 推荐函数形式 () => obj.prop 需要监视内部变化时开启
监视整个对象 可直接写或函数形式 需要监视内部变化时开启

核心原则​:当需要监视对象内部属性变化时,无论哪种情况,都应开启深度监视 { deep: true }

​✅ 场景 4:监听多个数据源(数组方式)​
const count = ref(0)
const user = reactive({ name: '张三' })

watch(
  [count, () => user.name], // 同时监听 count 和 user.name
  ([newCount, newName], [oldCount, oldName]) => {
    console.log(`count=${newCount}, name=${newName}`)
  }
)
​✅ 场景 5:立即执行 + 防抖(immediate + debounce
const searchQuery = ref('')

watch(
  searchQuery,
  (newQuery) => {
    fetchResults(newQuery) // 搜索请求
  },
  { 
    immediate: true, // 首次自动执行
    debounce: 300    // 防抖 300ms
  }
)

实战演练一下

前端实战-Vue3-watch的五种监听情形-CSDN博客 


4. watch vs watchEffect 对比

特性 watch watchEffect
监听方式 需明确指定监听目标 自动追踪回调内的响应式依赖
首次执行 默认不执行,需 immediate: true 默认立即执行
新旧值 提供 newValoldVal 不提供旧值
适用场景 精确控制监听目标 依赖变化时自动执行逻辑

具体代码示例对比

1. 基本用法差异
// watch 示例
const count = ref(0)
watch(count, (newVal, oldVal) => {
  console.log(`count从${oldVal}变为${newVal}`)
})

// watchEffect 示例
const count = ref(0)
watchEffect(() => {
  console.log(`当前count值: ${count.value}`) // 自动追踪count
})

2. 多个依赖处理

// watch 处理多个依赖
const state = reactive({ a: 1, b: 2 })
watch(
  [() => state.a, () => state.b],
  ([newA, newB], [oldA, oldB]) => {
    console.log(`a: ${oldA}→${newA}, b: ${oldB}→${newB}`)
  }
)

// watchEffect 处理多个依赖
const state = reactive({ a: 1, b: 2 })
watchEffect(() => {
  console.log(`a=${state.a}, b=${state.b}`) // 自动追踪state.a和state.b
})

3. 副作用清理

// watch 清理副作用
const id = ref(1)
const stop = watch(id, async (newId) => {
  const response = await fetch(`/api/data/${newId}`)
  // ...处理数据
  
  return () => {
    // 清理逻辑(如取消请求)
  }
})

// watchEffect 清理副作用
const id = ref(1)
watchEffect((onCleanup) => {
  const timer = setTimeout(() => {
    console.log(`Fetching data for id: ${id.value}`)
  }, 1000)
  
  onCleanup(() => {
    clearTimeout(timer)
  })
})

何时选择哪个?

使用 watch 当:
  1. 需要知道值变化前后的差异

    watch(price, (newVal, oldVal) => {
      console.log(`价格变化: ${oldVal} → ${newVal}`)
    })
  2. 需要惰性执行(不立即执行)

    watch(userId, fetchUserData) // 只在userId变化时执行
  3. 需要精确控制监视目标

    watch(
      () => user.value.age,
      (newAge) => { /* 只监视age */ }
    )

使用 watchEffect 当:

  1. 需要自动收集多个依赖

    watchEffect(() => {
      // 自动追踪所有使用的响应式数据
      console.log(`用户: ${user.name}, 年龄: ${user.age}`)
    })
  2. 需要立即执行一次

    watchEffect(initializeComponent) // 组件初始化时立即执行
  3. 需要更简洁的代码结构

    watchEffect(() => {
      // 所有相关逻辑在一个函数中
      if (isActive.value) {
        startTimer()
      } else {
        stopTimer()
      }
    })


5. 进阶技巧

停止监听
const stop = watch(count, (newVal) => { /* ... */ })
stop() // 手动停止监听
监听 props 的变化
const props = defineProps(['userId'])
watch(() => props.userId, (newId) => {
  fetchUser(newId)
})
结合 async/await 处理异步
watch(
  userId,
  async (newId) => {
    const data = await fetchUser(newId)
    console.log(data)
  }
)

6. 总结

  • watch 用于精确监听数据变化,支持 refreactivegetter 和数组。
  • deep: true可深度监听对象/数组。
  • immediate: true让回调首次立即执行。
  • watchEffect 更适合自动依赖追踪,但 watch 更可控。


补充 TypeScript核心概念

第一部分:TypeScript核心概念

1.自定义类型

// 自定义一个用户类型
type User = {
  id: number
  name: string
  age?: number // 可选属性
}

// 使用自定义类型
const user: User = {
  id: 1,
  name: '张三'
}

2.接口

// 定义一个商品接口
interface Product {
  id: number
  name: string
  price: number
  inStock?: boolean // 可选属性
}

// 接口继承
interface DiscountedProduct extends Product {
  discount: number
}

// 使用接口
const laptop: Product = {
  id: 101,
  name: '笔记本电脑',
  price: 5999
}

3.泛型

// 通用响应类型
interface ApiResponse {
  code: number
  message: string
  data: T
}

// 使用泛型
const userResponse: ApiResponse = {
  code: 200,
  message: '成功',
  data: { id: 1, name: '张三' }
}

const productResponse: ApiResponse = {
  code: 200,
  message: '成功',
  data: { id: 101, name: '笔记本电脑', price: 5999 }
}

 泛型像"类型参数",让组件/函数能处理多种类型,保持类型安全。


TypeScript 中 Props 的三种定义与使用方式

在 Vue 组件通信中,props 是父组件向子组件传递数据的主要方式,结合 TypeScript 定义组件 Props 可以让组件更加健壮和可维护。下面我将详细介绍三种渐进式的 Props 定义方法,并解释每种方式的适用场景。

1. 基础用法:只接收值

// 子组件 ChildComponent.vue


特点​:

  • 最简单的 props 接收方式
  • 没有类型检查
  • 适合快速原型开发
  • 不推荐在生产代码中使用(缺乏类型安全)

2. 类型安全:接收并限制类型

// 子组件 ChildComponent.vue


特点​:

  • 完整的 TypeScript 类型支持
  • 编辑器智能提示
  • 编译时类型检查
  • 可选属性使用 ? 标记
  • 无法设置默认值

3. 完整方案:接收+类型+默认值

// 子组件 ChildComponent.vue


特点​:

  • 完整类型安全
  • 可设置默认值
  • 复杂默认值使用工厂函数(如数组/对象)
  • 最完整的解决方案
  • 需要额外导入 withDefaults

三种方式对比

特性 只接收值 接收并限制类型 接收+类型+默认值
类型检查 ❌ 无 ✅ 完整 ✅ 完整
默认值支持 ❌ 无 ❌ 无 ✅ 支持
可选参数 ❌ 隐式 ✅ 显式(?) ✅ 显式(?)
代码复杂度 ⭐ 最简单 ⭐⭐ 中等 ⭐⭐⭐ 最完整
适用场景 快速原型 必须参数 完整生产环境组件

最佳实践建议

  1. 优先使用第三种方式​(withDefaults),特别是生产环境代码
  2. 为对象/数组默认值使用工厂函数​:
    withDefaults(defineProps(), {
      list: () => [], // 数组
      config: () => ({ enabled: false }) // 对象
    })
  3. 为复杂数据结构定义接口​:
    interface User {
      id: number
      name: string
    }
    
    interface Props {
      users: User[]
    }
  4. 在父组件中也使用相同类型​:
    // 父组件
    
    
    

你可能感兴趣的:(前端,笔记)