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 的声明式数据流和组件通信机制。

相关推荐
UI设计和前端开发从业者12 分钟前
从UI前端到数字孪生:构建数据驱动的智能生态系统
前端·ui
Junerver1 小时前
Kotlin 2.1.0的新改进带来哪些改变
前端·kotlin
千百元2 小时前
jenkins打包问题jar问题
前端
喝拿铁写前端2 小时前
前端批量校验还能这么写?函数式校验器组合太香了!
前端·javascript·架构
巴巴_羊2 小时前
6-16阿里前端面试记录
前端·面试·职场和发展
我是若尘2 小时前
前端遇到接口批量异常导致 Toast 弹窗轰炸该如何处理?
前端
该用户已不存在2 小时前
8个Docker的最佳替代方案,重塑你的开发工作流
前端·后端·docker
然我2 小时前
面试官最爱的 “考试思维”:用闭包秒杀递归难题 🚀
前端·javascript·面试
明月与玄武2 小时前
HTML知识全解析:从入门到精通的前端指南(上)
前端·html
teeeeeeemo3 小时前
CSS place-items: center; 详解与用法
前端·css·笔记