学习:Vue (2)

Vue3 内置属性

Vue3 提供了许多内置属性和方法,这些是框架的核心功能,开发者可以在组件中直接使用它们。

DOM相关属性和方法

$refs
javascript 复制代码
<template>
  <div>
    <input ref="inputRef" type="text">
    <button ref="buttonRef" @click="focusInput">Focus Input</button>
  </div>
</template>

<script>
export default {
  methods: {
    focusInput() {
      // 访问DOM元素
      this.$refs.inputRef.focus()
    }
  }
}
</script>
$attrs$listeners (Vue3中 $listeners 被合并到 $attrs )
javascript 复制代码
// 父组件
<template>
  <ChildComponent
    class="parent-class"
    @custom-event="handleCustomEvent"
    v-bind="$attrs"
  />
</template>

// 子组件
<script>
export default {
  inheritAttrs: false,  // 不继承根元素的属性
  mounted() {
    // $attrs包含父组件传递但子组件未声明为props的属性和事件
    console.log(this.$attrs)  // { class: 'parent-class', onCustomEvent: function }
  }
}
</script>

全局属性

app.config.globalProperties
javascript 复制代码
// main.js
const app = createApp(App)
app.config.globalProperties.$http = axios

// 组件中
export default {
  mounted() {
    // 在选项式API中访问全局属性
    this.$http.get('/api/data')
  }
}

组合式API中的内置属性

在组合式API中,我们可以使用以下函数获取内置属性:

useAttrsuseSlots
javascript 复制代码
import { useAttrs, useSlots } from 'vue'

export default {
  setup(props, { emit, attrs, slots }) {
    // 或者使用hooks
    const attrs = useAttrs()
    const slots = useSlots()
    
    return { attrs, slots }
  }
}
useSlots
javascript 复制代码
import { useSlots } from 'vue'

export default {
  setup() {
    const slots = useSlots()
    
    // 检查是否存在特定插槽
    const hasDefaultSlot = !!slots.default
    
    // 渲染插槽
    const renderSlots = () => {
      return slots.default ? slots.default() : null
    }
    
    return { hasDefaultSlot, renderSlots }
  }
}

Vue3 内置组件

<component> 动态组件

html 复制代码
<template>
  <div>
    <component :is="currentComponent" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentComponent: 'ComponentA'
    }
  },
  components: {
    ComponentA,
    ComponentB
  }
}
</script>

<transition> 过渡组件

html 复制代码
<template>
  <div>
    <button @click="show = !show">Toggle</button>
    <transition name="fade">
      <p v-if="show">Hello</p>
    </transition>
  </div>
</template>

<style>
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>

<keep-alive> 缓存组件

html 复制代码
<template>
  <div>
    <keep-alive>
      <component :is="currentComponent" />
    </keep-alive>
  </div>
</template>

<slot> 插槽

html 复制代码
<!-- 父组件 -->
<template>
  <ChildComponent>
    <template v-slot:header>
      <h1>标题内容</h1>
    </template>
    <p>默认内容</p>
    <template v-slot:footer>
      <p>底部内容</p>
    </template>
  </ChildComponent>
</template>

<!-- 子组件 -->
<template>
  <div>
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<teleport> 传送组件

html 复制代码
<template>
  <div>
    <button @click="showModal = true">显示模态框</button>
    
    <teleport to="body">
      <div v-if="showModal" class="modal">
        <div class="modal-content">
          <p>这是一个模态框</p>
          <button @click="showModal = false">关闭</button>
        </div>
      </div>
    </teleport>
  </div>
</template>

Vue 组件实例

在组件中,我们可以通过 this 访问以下内置属性(选项式API中):

$data

javascript 复制代码
export default {
  data() {
    return {
      message: 'Hello Vue3',
      count: 0
    }
  },
  mounted() {
    // 访问组件的响应式数据
    console.log(this.$data)  // { message: 'Hello Vue3', count: 0 }
    console.log(this.$data.message)  // 'Hello Vue3'
    
    // 通常直接访问
    console.log(this.message)  // 'Hello Vue3'
  }
}

$props

javascript 复制代码
export default {
  props: {
    title: String,
    initialCount: {
      type: Number,
      default: 0
    }
  },
  mounted() {
    // 访问接收到的props
    console.log(this.$props)  // { title: '组件标题', initialCount: 5 }
    
    // 通常直接访问
    console.log(this.title)   // '组件标题'
    console.log(this.initialCount)  // 5
  }
}

$el

javascript 复制代码
export default {
  mounted() {
    // 访问组件的根DOM元素
    console.log(this.$el)  // 组件的根DOM元素
    
    // 可以直接操作DOM(不推荐,除非必要)
    this.$el.querySelector('button').focus()
  }
}

$options

javascript 复制代码
export default {
  name: 'MyComponent',
  data() {
    return { message: 'Hello' }
  },
  methods: {
    greet() {
      console.log(this.$options.name)  // 'MyComponent'
      console.log(this.$options.methods)  // 所有方法的对象
    }
  }
}

$parent$root

javascript 复制代码
export default {
  mounted() {
    // 访问父组件实例
    if (this.$parent) {
      console.log(this.$parent)
    }
    
    // 访问根组件实例
    console.log(this.$root)
  }
}

$children (Vue3中已废弃)

Vue3中不再推荐使用 $children ,应该使用 $refs 或其他方式访问子组件。

事件方法

$emit
javascript 复制代码
export default {
  methods: {
    notifyParent() {
      // 向父组件发射事件
      this.$emit('child-event', { data: 'from child' })
    }
  }
}
$on , $once , $off (Vue3中已被移除)

Vue3中移除了这些方法,推荐使用 mitt 或其他事件总线库。

组件实例方法

$mount

javascript 复制代码
// 手动挂载实例
const MyComponent = Vue.extend({
  template: '<div>Hello</div>'
})

const instance = new MyComponent()
instance.$mount('#app')  // 手动挂载到id为app的元素
// 或者
instance.$mount()  // 不挂载到DOM,只创建实例
document.body.appendChild(instance.$el)  // 手动插入DOM

$forceUpdate

javascript 复制代码
export default {
  data() {
    return { 
      items: [] 
    }
  },
  methods: {
    addItem() {
      // 直接修改数组索引,Vue可能无法检测到变化
      this.items[0] = 'new item'
      
      // 强制更新视图(不推荐,应该使用Vue.set或数组方法)
      this.$forceUpdate()
    }
  }
}

$nextTick

javascript 复制代码
export default {
  methods: {
    updateData() {
      this.message = 'Updated message'
      
      // DOM尚未更新
      console.log(this.$el.textContent)  // 旧内容
      
      // 等待下一次DOM更新
      this.$nextTick(() => {
        // DOM已更新
        console.log(this.$el.textContent)  // 'Updated message'
      })
    }
  }
}

Vue3 内置指令

Vue3提供了多个内置指令:

v-if , v-else-if , v-else

html 复制代码
<template>
  <div>
    <p v-if="isLoggedIn">欢迎回来</p>
    <p v-else>请登录</p>
  </div>
</template>

v-show

html 复制代码
<template>
  <div>
    <p v-show="isVisible">可见内容</p>
  </div>
</template>

v-for

html 复制代码
<template>
  <div>
    <li v-for="(item, index) in items" :key="item.id">
      {{ index }}: {{ item.name }}
    </li>
  </div>
</template>

v-model

html 复制代码
<template>
  <div>
    <input v-model="message">
    <p>{{ message }}</p>
    
    <!-- 自定义组件中使用v-model -->
    <CustomComponent v-model:title="pageTitle" />
  </div>
</template>

v-bind (简写 : )

html 复制代码
<template>
  <div>
    <img :src="imageUrl" :alt="imageAlt">
    
    <!-- 对象语法 -->
    <div v-bind="{ id: dynamicId, class: dynamicClass }"></div>
  </div>
</template>

v-on (简写 @ )

html 复制代码
<template>
  <div>
    <button @click="doSomething">点击</button>
    
    <!-- 事件修饰符 -->
    <form @submit.prevent="onSubmit">
      <input @keyup.enter="onEnter">
    </form>
  </div>
</template>

全局API

createApp

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

defineComponent

javascript 复制代码
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'MyComponent',
  props: {
    msg: String
  },
  setup(props) {
    return { props }
  }
})

ref , reactive , computed , watch

javascript 复制代码
import { ref, reactive, computed, watch } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const state = reactive({ name: 'Vue3' })
    
    const doubled = computed(() => count.value * 2)
    
    watch(count, (newVal, oldVal) => {
      console.log(`Count changed from ${oldVal} to ${newVal}`)
    })
    
    return { count, state, doubled }
  }
}

Vue3 生命周期钩子

1. Vue3 生命周期钩子概览

选项式API (Vue2/Vue3) 组合式API (Vue3) 说明
beforeCreate - 实例初始化后,数据观测和事件配置之前
created - 实例创建完成,数据观测、属性、方法等已配置
beforeMount onBeforeMount 挂载开始之前
mounted onMounted 挂载完成
beforeUpdate onBeforeUpdate 数据更新前
updated onUpdated 数据更新后
beforeUnmount onBeforeUnmount 卸载前
unmounted onUnmounted 卸载后
- onActivated 被缓存的组件激活时
- onDeactivated 被缓存的组件停用时
- onErrorCaptured 捕获后代组件的错误
- onRenderTracked 跟踪虚拟DOM重新渲染
- onRenderTriggered 虚拟DOM重新渲染被触发

2. 选项式API中的生命周期钩子

beforeCreatecreated
javascript 复制代码
export default {
  beforeCreate() {
    console.log('beforeCreate: 实例初始化完成')
    console.log('此时无法访问data、props、computed等')
    // this.message 是 undefined
  },
  
  created() {
    console.log('created: 实例创建完成')
    console.log('此时可以访问data、props、computed等')
    console.log('message:', this.message) // 可以访问
    
    // 适合进行数据请求
    this.fetchData()
  },
  
  data() {
    return {
      message: 'Hello Vue3',
      users: []
    }
  },
  
  methods: {
    fetchData() {
      // 获取初始数据
      console.log('获取数据')
    }
  }
}
beforeMountmounted
javascript 复制代码
export default {
  beforeMount() {
    console.log('beforeMount: 组件挂载前')
    console.log('DOM尚未创建,无法访问DOM元素')
    // this.$el 是 undefined
  },
  
  mounted() {
    console.log('mounted: 组件挂载完成')
    console.log('DOM已创建,可以访问DOM元素')
    console.log(this.$el) // 组件的根DOM元素
    
    // 适合:
    // - 访问DOM元素
    // - 启动定时器
    // - 添加事件监听器
    // - 初始化图表等第三方库
    
    // 初始化图表
    this.initChart()
    
    // 添加全局事件监听
    window.addEventListener('resize', this.handleResize)
  },
  
  methods: {
    initChart() {
      // 初始化图表代码
      console.log('初始化图表')
    },
    
    handleResize() {
      console.log('窗口大小改变')
    }
  },
  
  beforeUnmount() {
    // 清理工作
    window.removeEventListener('resize', this.handleResize)
  }
}
beforeUpdateupdated
javascript 复制代码
export default {
  data() {
    return {
      count: 0
    }
  },
  
  beforeUpdate() {
    console.log('beforeUpdate: 数据更新,DOM重新渲染前')
    // 可以获取更新前的DOM状态
    console.log('当前计数:', this.$el.querySelector('.count').textContent)
  },
  
  updated() {
    console.log('updated: 数据更新,DOM重新渲染完成')
    // 可以获取更新后的DOM状态
    console.log('更新后计数:', this.$el.querySelector('.count').textContent)
    
    // 注意:避免在这里修改状态,可能导致无限循环
    // this.count++ // ❌ 可能导致无限循环
  },
  
  methods: {
    increment() {
      this.count++
    }
  }
}
beforeUnmountunmounted
javascript 复制代码
export default {
  mounted() {
    // 创建定时器
    this.timer = setInterval(() => {
      console.log('定时器执行')
    }, 1000)
    
    // 创建事件监听
    window.addEventListener('scroll', this.handleScroll)
  },
  
  beforeUnmount() {
    console.log('beforeUnmount: 组件卸载前')
    console.log('组件仍然完全可用')
    
    // 适合:
    // - 清理定时器
    // - 移除事件监听
    // - 取消网络请求
    // - 清理第三方库实例
    
    // 清理定时器
    if (this.timer) {
      clearInterval(this.timer)
    }
  },
  
  unmounted() {
    console.log('unmounted: 组件卸载完成')
    console.log('组件实例已销毁,大部分属性不可用')
    
    // 清理事件监听
    window.removeEventListener('scroll', this.handleScroll)
  },
  
  methods: {
    handleScroll() {
      console.log('滚动事件')
    }
  }
}

3. 组合式API中的生命周期钩子

基本用法
javascript 复制代码
import { 
  onBeforeMount, 
  onMounted, 
  onBeforeUpdate, 
  onUpdated,
  onBeforeUnmount, 
  onUnmounted,
  ref
} from 'vue'

export default {
  setup() {
    const count = ref(0)
    const element = ref(null)
    
    // 相当于 beforeMount
    onBeforeMount(() => {
      console.log('onBeforeMount: 组件挂载前')
      console.log('DOM尚未创建')
      // element.value 是 null
    })
    
    // 相当于 mounted
    onMounted(() => {
      console.log('onMounted: 组件挂载完成')
      console.log('DOM已创建,可以访问DOM元素')
      console.log(element.value) // DOM元素
      
      // 访问DOM元素
      element.value.focus()
      
      // 启动定时器
      const timer = setInterval(() => {
        console.log('定时器执行')
      }, 1000)
      
      // 返回清理函数(可选)
      return () => {
        clearInterval(timer)
      }
    })
    
    // 相当于 beforeUpdate
    onBeforeUpdate(() => {
      console.log('onBeforeUpdate: 数据更新,DOM重新渲染前')
    })
    
    // 相当于 updated
    onUpdated(() => {
      console.log('onUpdated: 数据更新,DOM重新渲染完成')
      // 同样避免在这里修改状态
    })
    
    // 相当于 beforeUnmount
    onBeforeUnmount(() => {
      console.log('onBeforeUnmount: 组件卸载前')
    })
    
    // 相当于 unmounted
    onUnmounted(() => {
      console.log('onUnmounted: 组件卸载完成')
    })
    
    // 多次调用同一个钩子函数
    onMounted(() => {
      console.log('第二个mounted钩子')
    })
    
    return {
      count,
      element
    }
  }
}
调试钩子
javascript 复制代码
import { onRenderTracked, onRenderTriggered, ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    // 跟踪虚拟DOM重新渲染
    onRenderTracked((e) => {
      console.log('onRenderTracked:', e)
      // e包含:
      // - key: 正在跟踪的响应式属性
      // - target: 跟踪的对象
      // - type: 跟踪的类型 (get, set等)
    })
    
    // 虚拟DOM重新渲染被触发
    onRenderTriggered((e) => {
      console.log('onRenderTriggered:', e)
      // 当组件重新渲染时触发,可以帮助调试性能问题
    })
    
    return {
      count
    }
  }
}
错误处理钩子
javascript 复制代码
import { onErrorCaptured, ref } from 'vue'

export default {
  setup() {
    const error = ref(null)
    
    // 捕获子孙组件的错误
    onErrorCaptured((err, instance, info) => {
      console.error('捕获到错误:', err)
      console.log('错误组件实例:', instance)
      console.log('错误信息:', info)
      
      // 存储错误信息
      error.value = err.message
      
      // 返回false阻止错误继续向上传播
      return false
    })
    
    return {
      error
    }
  }
}

4. 生命周期钩子使用场景与最佳实践

数据获取
javascript 复制代码
export default {
  // 选项式API
  created() {
    // 适合获取初始数据
    this.fetchUserData()
  },
  
  methods: {
    async fetchUserData() {
      try {
        const response = await fetch('/api/user')
        this.user = await response.json()
      } catch (error) {
        console.error('获取用户数据失败:', error)
      }
    }
  }
}

// 组合式API
import { onMounted, ref } from 'vue'

export default {
  setup() {
    const user = ref(null)
    const loading = ref(false)
    const error = ref(null)
    
    // 在onMounted中获取数据
    onMounted(async () => {
      loading.value = true
      try {
        const response = await fetch('/api/user')
        user.value = await response.json()
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    })
    
    return { user, loading, error }
  }
}
DOM操作与第三方库初始化
javascript 复制代码
export default {
  // 选项式API
  mounted() {
    // 初始化图表库
    this.initChart()
    
    // 添加全局事件监听
    window.addEventListener('resize', this.handleResize)
  },
  
  beforeUnmount() {
    // 清理图表实例
    if (this.chart) {
      this.chart.dispose()
    }
    
    // 移除事件监听
    window.removeEventListener('resize', this.handleResize)
  },
  
  methods: {
    initChart() {
      // 使用echarts等库初始化图表
      this.chart = echarts.init(this.$refs.chartContainer)
      // ...
    },
    
    handleResize() {
      if (this.chart) {
        this.chart.resize()
      }
    }
  }
}

// 组合式API
import { onMounted, onBeforeUnmount, ref } from 'vue'
import * as echarts from 'echarts'

export default {
  setup() {
    const chartContainer = ref(null)
    let chart = null
    
    onMounted(() => {
      // 初始化图表
      chart = echarts.init(chartContainer.value)
      
      // 配置图表
      const option = {
        // 图表配置
      }
      chart.setOption(option)
      
      // 添加事件监听
      window.addEventListener('resize', handleResize)
    })
    
    onBeforeUnmount(() => {
      // 清理图表实例
      if (chart) {
        chart.dispose()
        chart = null
      }
      
      // 移除事件监听
      window.removeEventListener('resize', handleResize)
    })
    
    const handleResize = () => {
      if (chart) {
        chart.resize()
      }
    }
    
    return {
      chartContainer
    }
  }
}
订阅与清理
javascript 复制代码
export default {
  // 选项式API
  data() {
    return {
      subscription: null
    }
  },
  
  mounted() {
    // 订阅数据流
    this.subscription = someObservable.subscribe(data => {
      this.processData(data)
    })
  },
  
  beforeUnmount() {
    // 取消订阅
    if (this.subscription) {
      this.subscription.unsubscribe()
    }
  },
  
  methods: {
    processData(data) {
      // 处理数据
    }
  }
}

// 组合式API
import { onMounted, onBeforeUnmount } from 'vue'

export default {
  setup() {
    let subscription = null
    
    onMounted(() => {
      // 订阅数据流
      subscription = someObservable.subscribe(data => {
        processData(data)
      })
    })
    
    onBeforeUnmount(() => {
      // 取消订阅
      if (subscription) {
        subscription.unsubscribe()
      }
    })
    
    const processData = (data) => {
      // 处理数据
    }
  }
}

5. 生命周期钩子注意事项

  1. 避免在 updated 中修改状态

    javascript 复制代码
    // ❌ 错误:可能导致无限循环
    updated() {
      this.count++
    }
    
    // ✅ 正确:使用计算属性或watch
    computed: {
      doubleCount() {
        return this.count * 2
      }
    }
  2. 在组合式API中可以多次注册同类型钩子

    javascript 复制代码
    setup() {
      onMounted(() => {
        console.log('第一个mounted钩子')
      })
      
      onMounted(() => {
        console.log('第二个mounted钩子')
      })
      
      // 两个钩子都会执行
    }
  3. 父子组件生命周期顺序

    javascript 复制代码
    父beforeCreate → 父created → 父beforeMount → 子beforeCreate → 子created → 
    子beforeMount → 子mounted → 父mounted → 父beforeUpdate → 子beforeUpdate → 
    子updated → 父updated → 父beforeUnmount → 子beforeUnmount → 子unmounted → 父unmounted
  4. 异步操作和生命周期

    javascript 复制代码
    setup() {
      onMounted(async () => {
        // 可以使用async/await
        const data = await fetchData()
        console.log(data)
      })
    }
相关推荐
北辰alk6 小时前
Vue项目Axios封装全攻略:从零到一打造优雅的HTTP请求层
vue.js
YJlio6 小时前
桌面工具学习笔记(11.1):BgInfo——给服务器桌面“刻”上关键信息
服务器·笔记·学习
TL滕7 小时前
从0开始学算法——第十五天(滑动窗口)
笔记·学习·算法
失败才是人生常态7 小时前
并发编程场景题学习
学习
醇氧7 小时前
springAI学习 一
学习·spring·ai·ai编程
菜鸟‍7 小时前
【论文学习】Co-Seg:互提示引导的组织与细胞核分割协同学习
人工智能·学习·算法
老华带你飞7 小时前
出行旅游安排|基于springboot出行旅游安排系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring·旅游
YJlio7 小时前
Active Directory 工具学习笔记(10.14):第十章·实战脚本包——AdExplorer/AdInsight/AdRestore 一键化落地
服务器·笔记·学习
东华万里7 小时前
第十四篇 操作符详讲
c语言·学习·大学生专区