vue中常用的api($set,$delete,$nextTick..)

一 Vue 中的 $set 方法详解

$set 是 Vue.js 提供的一个重要 API,用于解决 Vue 响应式系统的限制问题。下面我将详细介绍它的用法、原理和实际应用场景。

1. 基本介绍

$set 是 Vue 实例的一个方法,用于向响应式对象添加一个新的 property,并确保这个新 property 同样是响应式的,且触发视图更新。

语法

javascript 复制代码
vm.$set(target, propertyName/index, value)

参数

  • target:要修改的目标对象(Object 或 Array)
  • propertyName/index:要添加或修改的属性名(对象)或索引(数组)
  • value:要设置的值

返回值

设置的值

2. 为什么需要 $set

Vue 2.x 使用 Object.defineProperty 实现响应式,它有一些限制:

  1. 无法检测对象属性的添加或删除

    javascript 复制代码
    data() {
      return {
        user: {
          name: '张三'
        }
      }
    }
    // 这样添加的属性不是响应式的
    this.user.age = 25
  2. 无法检测数组索引直接设置项

    javascript 复制代码
    data() {
      return {
        items: ['a', 'b', 'c']
      }
    }
    // 这样修改数组项不是响应式的
    this.items[1] = 'x'

$set 就是用来解决这些问题的。

3. 使用示例

对象属性添加

javascript 复制代码
export default {
  data() {
    return {
      user: {
        name: '张三',
        age: 20
      }
    }
  },
  methods: {
    addProperty() {
      // 错误方式 - 不是响应式的
      // this.user.gender = '男'
      
      // 正确方式
      this.$set(this.user, 'gender', '男')
    }
  }
}

数组项修改

javascript 复制代码
export default {
  data() {
    return {
      items: ['苹果', '香蕉', '橙子']
    }
  },
  methods: {
    updateItem() {
      // 错误方式 - 不是响应式的
      // this.items[1] = '西瓜'
      
      // 正确方式
      this.$set(this.items, 1, '西瓜')
    }
  }
}

动态添加嵌套属性

javascript 复制代码
export default {
  data() {
    return {
      formData: {}
    }
  },
  methods: {
    initForm() {
      // 动态添加嵌套的响应式属性
      this.$set(this.formData, 'userInfo', {})
      this.$set(this.formData.userInfo, 'name', '李四')
    }
  }
}

4. 与 Vue.set 的关系

$set 是 Vue 实例方法,而 Vue.set 是全局 API,两者功能完全相同:

javascript 复制代码
// 在组件内部
this.$set(target, key, value)

// 在任何地方
Vue.set(target, key, value)

5. 原理分析

$set 的实现主要做了以下几件事:

  1. 如果目标是数组,使用 splice 方法(Vue 重写了数组的变异方法)
  2. 如果目标是对象,且属性已存在,直接赋值
  3. 如果目标是对象,且属性不存在:
    • 将属性添加到对象
    • 使用 defineReactive 方法使其成为响应式
    • 触发依赖通知

6. 替代方案

在某些情况下,可以使用以下替代方案:

对象替代方案

javascript 复制代码
// 使用 Object.assign 创建新对象
this.user = Object.assign({}, this.user, { gender: '男' })

// 使用扩展运算符
this.user = { ...this.user, gender: '男' }

数组替代方案

javascript 复制代码
// 使用数组变异方法
this.items.splice(1, 1, '西瓜')

7. 注意事项

  1. 不要滥用 $set :对于已知的属性,应该在 data 中预先声明
  2. 性能考虑 :频繁使用 $set 会影响性能,应考虑数据结构设计
  3. Vue 3 的变化 :Vue 3 使用 Proxy 实现响应式,不再需要 $set

8. 实际应用场景

场景1:动态表单字段

javascript 复制代码
export default {
  data() {
    return {
      form: {
        basicInfo: {
          name: '',
          age: ''
        }
      }
    }
  },
  methods: {
    addCustomField(fieldName) {
      if (!this.form.basicInfo.hasOwnProperty(fieldName)) {
        this.$set(this.form.basicInfo, fieldName, '')
      }
    }
  }
}

场景2:表格行编辑

javascript 复制代码
export default {
  data() {
    return {
      tableData: [
        { id: 1, name: '产品A', price: 100 },
        { id: 2, name: '产品B', price: 200 }
      ]
    }
  },
  methods: {
    updatePrice(index, newPrice) {
      this.$set(this.tableData[index], 'price', newPrice)
    }
  }
}

场景3:树形结构操作

javascript 复制代码
export default {
  data() {
    return {
      treeData: {
        id: 1,
        label: '根节点',
        children: []
      }
    }
  },
  methods: {
    addChildNode(parentNode, newNode) {
      if (!parentNode.children) {
        this.$set(parentNode, 'children', [])
      }
      parentNode.children.push(newNode)
    }
  }
}

9. 常见问题

Q: 为什么直接赋值不生效?

A: Vue 2.x 的响应式系统无法检测属性的添加或删除,必须使用 $set 或预先声明所有属性。

Q: $setVue.set 有什么区别?

A: 功能完全相同,只是调用方式不同。$set 是实例方法,Vue.set 是全局方法。

Q: Vue 3 还需要 $set 吗?

A: Vue 3 使用 Proxy 实现响应式,可以检测属性添加/删除,大多数情况下不再需要 $set

总结

$set 是 Vue 2.x 中解决响应式限制的重要工具,合理使用可以确保数据变化的响应性。但在 Vue 3 中,由于响应式系统的改进,$set 的使用场景大大减少。在开发中,应根据 Vue 版本和具体场景选择合适的数据操作方式。

二 Vue 中的 $nextTick 方法详解

$nextTick 是 Vue.js 中一个非常重要的 API,它允许你在 DOM 更新完成后执行延迟回调。下面我将全面介绍它的工作原理、使用场景和最佳实践。

1. 核心概念

什么是 $nextTick

$nextTick 是一个异步方法,它将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,可以获取更新后的 DOM。

基本语法

javascript 复制代码
// 作为实例方法
this.$nextTick(() => {
  // DOM 更新完成后执行的代码
})

// 作为全局 API
Vue.nextTick(() => {
  // DOM 更新完成后执行的代码
})

// 也可以返回 Promise
this.$nextTick().then(() => {
  // DOM 更新完成后执行的代码
})

2. 为什么需要 $nextTick

Vue 的 DOM 更新是异步的,当数据变化时:

  1. Vue 开启一个队列
  2. 缓冲在同一事件循环中发生的所有数据变更
  3. 在下一个事件循环"tick"中刷新队列

这种机制导致:

javascript 复制代码
this.message = '更新后的消息'
console.log(this.$el.textContent) // 这里获取的还是旧DOM
this.$nextTick(() => {
  console.log(this.$el.textContent) // 这里获取的是更新后的DOM
})

3. 典型使用场景

场景1:获取更新后的 DOM

javascript 复制代码
methods: {
  updateMessage() {
    this.message = '新消息'
    this.$nextTick(() => {
      // 现在可以获取更新后的DOM
      const height = this.$refs.messageBox.offsetHeight
      console.log('更新后的高度:', height)
    })
  }
}

场景2:与第三方库集成

javascript 复制代码
methods: {
  initEditor() {
    this.content = '<p>初始内容</p>'
    this.$nextTick(() => {
      // 确保DOM更新后再初始化编辑器
      this.editor = new Editor(this.$refs.editor)
    })
  }
}

场景3:滚动到最新项

javascript 复制代码
methods: {
  addItem() {
    this.items.push(newItem)
    this.$nextTick(() => {
      // 滚动到最新添加的元素
      const lastItem = this.$refs.items[this.items.length - 1]
      lastItem.scrollIntoView()
    })
  }
}

4. 工作原理

Vue 的异步更新队列机制:

  1. 数据变更:当你修改响应式数据时
  2. 虚拟DOM:Vue 开始重新渲染虚拟DOM
  3. 队列:将 DOM 更新操作放入队列
  4. 事件循环
    • 当前调用栈执行完毕
    • 开始处理微任务队列
    • 执行 DOM 更新
  5. nextTick回调 :此时执行 $nextTick 的回调

5. 与 Promise 的关系

$nextTick 返回一个 Promise,所以可以这样使用:

javascript 复制代码
async updateData() {
  this.data = await fetchData()
  await this.$nextTick()
  console.log('DOM已更新')
  // 继续其他操作
}

6. 常见问题解答

Q: $nextTicksetTimeout(fn, 0) 有什么区别?

A:

  • $nextTick 优先级更高,在微任务阶段执行
  • setTimeout 是宏任务,执行时机更晚
  • $nextTick 能确保在 Vue 的 DOM 更新后立即执行

Q: 为什么有时候不用 $nextTick 也能获取更新后的DOM?

A:

  • 在某些同步代码块中,浏览器可能会在同一个tick中完成渲染
  • 但这不可靠,依赖这种行为会导致难以追踪的bug

Q: $nextTick 会阻塞渲染吗?

A:

  • 不会,它只是将回调推迟到DOM更新之后
  • 回调执行时界面已经更新完成

7. 最佳实践

  1. 避免嵌套 :不要在一个 $nextTick 回调中再嵌套 $nextTick

  2. 合理使用 :不是所有DOM操作都需要 $nextTick,只有依赖更新后DOM时才需要

  3. 错误处理

javascript 复制代码
this.$nextTick()
  .then(() => {
    // 成功回调
  })
  .catch(err => {
    console.error('nextTick出错:', err)
  })
  1. 组件销毁处理
javascript 复制代码
mounted() {
  this.$nextTick(() => {
    if (!this._isDestroyed) {
      // 安全操作
    }
  })
}

8. Vue 3 中的变化

Vue 3 中 nextTick 的行为基本保持一致,但有一些小变化:

  1. Vue.nextTick 改为直接导入:

    javascript 复制代码
    import { nextTick } from 'vue'
    nextTick(() => {...})
  2. 在组合式API中使用:

    javascript 复制代码
    setup() {
      const count = ref(0)
      
      async function increment() {
        count.value++
        await nextTick()
        console.log('DOM已更新')
      }
      
      return { count, increment }
    }

9. 性能考虑

虽然 $nextTick 很有用,但过度使用会影响性能:

  • 不要滥用:只在真正需要时使用
  • 批量操作:多个DOM操作尽量放在同一个回调中
  • 替代方案 :考虑使用 watchwatchEffect 监听变化

10. 实际案例

案例1:自动聚焦输入框

javascript 复制代码
methods: {
  showInput() {
    this.isShow = true
    this.$nextTick(() => {
      this.$refs.input.focus()
    })
  }
}

案例2:测量元素尺寸

javascript 复制代码
methods: {
  updateLayout() {
    this.layoutChanged = true
    this.$nextTick(() => {
      const width = this.$refs.container.offsetWidth
      // 根据新宽度调整布局
    })
  }
}

案例3:集成非响应式插件

javascript 复制代码
methods: {
  initPlugin() {
    this.dataLoaded = true
    this.$nextTick(() => {
      // 确保DOM渲染完成后再初始化插件
      this.plugin = new ThirdPartyPlugin(this.$el)
    })
  }
}

总结

$nextTick 是 Vue 响应式系统的关键部分,它解决了以下问题:

  • 在数据变化后安全地操作DOM
  • 确保获取到最新的DOM状态
  • 与第三方库正确集成

记住它的核心原则:将回调延迟到下次DOM更新循环之后执行。合理使用这个API可以避免许多常见的异步更新问题。

三 Vue 中的 $refs 详解

$refs 是 Vue 提供的一个重要特性,用于直接访问 DOM 元素或子组件实例。下面我将全面介绍它的用法、注意事项和最佳实践。

1. 基本概念

什么是 $refs

$refs 是一个对象,持有注册过 ref 特性的所有 DOM 元素和子组件实例。

核心特点:

  • 引用类型:可以是 DOM 元素或组件实例
  • 响应式:不是响应式的,只作为直接访问子组件的"逃生舱"
  • 生命周期:在组件挂载完成后填充,不是响应式的

2. 基本用法

引用 DOM 元素

html 复制代码
<template>
  <input ref="inputRef" type="text">
  <button @click="focusInput">聚焦输入框</button>
</template>

<script>
export default {
  methods: {
    focusInput() {
      this.$refs.inputRef.focus()
    }
  }
}
</script>

引用子组件

html 复制代码
<template>
  <child-component ref="childRef"></child-component>
  <button @click="callChildMethod">调用子组件方法</button>
</template>

<script>
export default {
  methods: {
    callChildMethod() {
      this.$refs.childRef.someMethod()
    }
  }
}
</script>

3. 使用场景

场景1:表单操作

javascript 复制代码
methods: {
  validateForm() {
    this.$refs.formRef.validate(valid => {
      if (valid) {
        // 提交表单
      }
    })
  }
}

场景2:与第三方库集成

javascript 复制代码
mounted() {
  this.$nextTick(() => {
    this.chart = new Chart(this.$refs.chartCanvas, {
      // 图表配置
    })
  })
}

场景3:父组件调用子组件方法

javascript 复制代码
// 父组件
methods: {
  refreshData() {
    this.$refs.childComponent.loadData()
  }
}

// 子组件
methods: {
  loadData() {
    // 获取数据逻辑
  }
}

4. 生命周期与 $refs

$refs 在不同生命周期的状态:

生命周期钩子 $refs 状态
beforeCreate {}
created {}
beforeMount {}
mounted 已填充所有ref
beforeUpdate 包含当前refs
updated 包含更新后的refs
beforeDestroy 仍然可以访问
destroyed undefined

5. 注意事项

  1. 不是响应式的

    javascript 复制代码
    // 错误用法 - 不会自动更新
    this.$refs.someRef = newValue
  2. v-for 中的 ref

    • 使用 v-for 时,$refs 会是一个数组
    html 复制代码
    <div v-for="item in list" :ref="setItemRef"></div>
    javascript 复制代码
    data() {
      return {
        itemRefs: []
      }
    },
    methods: {
      setItemRef(el) {
        if (el) {
          this.itemRefs.push(el)
        }
      }
    }
  3. 动态 ref

    html 复制代码
    <component :is="currentComponent" :ref="dynamicRef"></component>
  4. 避免过度使用

    • 优先使用 props 和 events 进行组件通信
    • 仅在需要直接访问 DOM 或子组件方法时使用

6. Vue 3 中的变化

在 Vue 3 中:

  1. 组合式 API 中使用 ref

    html 复制代码
    <template>
      <div ref="root"></div>
    </template>
    
    <script>
    import { ref, onMounted } from 'vue'
    
    export default {
      setup() {
        const root = ref(null)
        
        onMounted(() => {
          console.log(root.value) // <div></div>
        })
        
        return { root }
      }
    }
    </script>
  2. v-for 中的 ref不再自动创建数组,需要使用函数ref:

    javascript 复制代码
    const itemRefs = ref([])
    
    const setItemRef = el => {
      if (el) {
        itemRefs.value.push(el)
      }
    }

7. 最佳实践

  1. 命名规范

    • 使用有意义的ref名称
    • 推荐后缀:Ref (如 inputRef, formRef)
  2. 安全访问

    javascript 复制代码
    if (this.$refs.myRef) {
      // 安全操作
    }
  3. 配合 $nextTick

    javascript 复制代码
    this.showComponent = true
    this.$nextTick(() => {
      this.$refs.myComponent.doSomething()
    })
  4. 避免滥用

    • 优先使用 props/events
    • 避免用refs修改子组件状态

8. 常见问题解答

Q: 为什么我的 $refs 是空的?

A: 可能原因:

  • mounted 之前访问
  • ref所在的元素有 v-if 且条件为false
  • 组件未正确挂载

Q: 如何在父组件访问孙组件?

A: 不推荐直接访问,应该:

  1. 通过子组件暴露方法
  2. 使用 provide/inject
  3. 使用事件总线或状态管理

Q: $refs$el 有什么区别?

A:

  • $el 是组件自身的根元素
  • $refs 是通过ref属性注册的任意元素或组件

9. 实际案例

案例1:图片预览组件

javascript 复制代码
methods: {
  zoomIn() {
    this.$refs.image.style.transform = 'scale(1.2)'
  },
  resetZoom() {
    this.$refs.image.style.transform = 'scale(1)'
  }
}

案例2:表单组件集成

javascript 复制代码
submitForm() {
  this.$refs.form.validate().then(() => {
    // 验证通过
  }).catch(() => {
    // 验证失败
    this.$refs.firstErrorField.focus()
  })
}

案例3:视频播放控制

javascript 复制代码
playVideo() {
  this.$refs.videoPlayer.play()
},
pauseVideo() {
  this.$refs.videoPlayer.pause()
}

10. 替代方案

在某些情况下,可以考虑替代方案:

  1. DOM 事件:使用原生事件代替直接DOM操作
  2. 自定义事件 :通过 $emit 实现组件通信
  3. 作用域插槽:通过插槽prop暴露数据和方法

总结

$refs 是 Vue 中一个强大的特性,但需要谨慎使用:

适用场景

  • 集成第三方库需要DOM元素
  • 触发子组件方法
  • 访问DOM属性/方法

避免场景

  • 组件间常规通信
  • 频繁修改子组件状态
  • 替代Vue的数据驱动方式

合理使用 $refs 可以解决特定问题,但过度使用会导致代码难以维护。在大多数情况下,优先考虑使用 Vue 的声明式数据流和组件通信机制。

相关推荐
Pedantic1 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘1 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆2 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师3 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆3 小时前
VSCode自动格式化三要素
前端
爱勇宝3 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen4 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user20585561518136 小时前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端
LiaCode6 小时前
Redis 在生产项目的使用
前端·后端