Vue3知识点梳理

注:纯手打,如有错误欢迎评论区交流!
转载请注明出处:https://blog.csdn.net/testleaf/article/details/148056625
编写此文是为了更好地学习前端知识,如果损害了有关人的利益,请联系删除!
本文章将不定时更新,敬请期待!!!
欢迎点赞、收藏、转发、关注,多谢!!!

一、Vue2选项式API和Vue3组合式API

1、Vue 2 选项式 API(Options API)​

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="reverseMessage">Reverse</button>
  </div>
</template>

<script>
export default {
  data() {
    return { message: "Hello Vue 2!" };
  },
  methods: {
    reverseMessage() {
      this.message = this.message.split('').reverse().join('');
    }
  },
  mounted() {
    console.log("Component mounted!");
  }
};
</script>

2、Vue 3 组合式 API(Composition API)

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="reverseMessage">Reverse</button>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  setup() {
    const message = ref("Hello Vue 3!");

    const reverseMessage = () => {
      message.value = message.value.split('').reverse().join('');
    };

    onMounted(() => {
      console.log("Component mounted!");
    });

    return { message, reverseMessage };
  }
};
</script>

3、核心对比

​​对比维度​​​​Vue 2 选项式 API​​​​Vue 3 组合式 API​​
​​​​代码组织方式​​​​按选项(datamethods 等)分类​​按逻辑功能聚合(setup() 函数内)
​​​​逻辑复用​​​​依赖 mixins(易冲突)​​自定义组合函数(无冲突)
​​​​TypeScript 支持​​​​ 较弱(this 类型推断困难)​​优秀(ref、reactive 类型明确)
​​​​适用场景​​​​ 简单组件、小型项目​​复杂组件、大型项目、逻辑复用需求
​​​​响应式系统​​​​基于 Object.defineProperty(Vue 2)​​ 基于 Proxy(Vue 3,性能更好)

二、Vue3相比于Vue2的优势

1、更容易维护:组合式API、更好的TypeScript支持;
2、更快的速度:重写diff算法、模板编译优化、更高效的组件初始化;
3、更优的数据响应式:Proxy
4、更小的体积:良好的TreeShaking、按需引入;

三、使用create-vue搭建Vue3项目

1、认识create-vue
create-vue是Vue官方新的脚手架工具,底层切换到了 vite (下一代前端工具链),为开发提供极速响应。
vue-cli的底层是webpack
2、使用create-vue创建项目

npm init vue@latest

3、项目关键文件

  • vite.config.js - 项目的配置文件:基于vite的配置
  • package.json - 项目包文件:核心依赖项变成了 Vue3.xvite
  • main.js - 入口文件:createApp函数创建应用实例
  • app.vue - 根组件:SFC单文件组件 script - template - style
    • 变化一:脚本script和模板template顺序调整
    • 变化二:模板template不再要求唯一根元素
    • 变化三:脚本script添加setup标识支持组合式API
  • index.html - 单页入口:提供idapp的挂载点

四、组合式API - setup选项

1、写法

<script>
  export default {
    setup(){
      
    },
    beforeCreate(){
      
    }
  }
</script>

2、执行时机
beforeCreate钩子之前执行;
3、setup中写代码的特点
setup函数中写的数据和方法需要在末尾以对象的方式return,才能给模版使用;
4、<script setup>语法糖
script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句;

<script setup>
  const message = 'this is message'
  const logMessage = ()=>{
    console.log(message)
  }
</script>

五、组合式API - reactive和ref函数

1、reactive
接受对象类型数据的参数传入并返回一个响应式的对象;

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  message: 'Hello Vue!'
})

// 访问和修改
state.count++
console.log(state.message)

2、ref
接收简单类型或者对象类型的数据传入并返回一个响应式的对象;

import { ref } from 'vue'

const count = ref(0)
const message = ref('Hello Vue!')

// 访问和修改
count.value++ // 注意需要通过.value访问
console.log(message.value)

3、比较与选择

特性reactiveref
适用类型对象任意值
访问方式直接访问.value
模板中使用直接使用自动解包
解构响应性丢失保持

4、使用建议

  • 当需要管理一组相关状态时,使用reactive
  • 当需要管理单个原始值时,使用ref
  • 在组合式函数中返回状态时,通常使用ref以保持灵活性

六、组合式API - computed

计算属性基本思想和Vue2保持一致,组合式API下的计算属性只是修改了API写法;
基本用法:

import { ref, computed } from 'vue'

const count = ref(0)

// 创建一个计算属性
const doubleCount = computed(() => count.value * 2)

console.log(doubleCount.value) // 0
count.value++
console.log(doubleCount.value) // 2

可写计算属性:

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

fullName.value = 'Jane Smith' // 会自动更新firstName和lastName

七、组合式API - watch

1、基本用法

import { ref, watch } from 'vue'

const count = ref(0)

// 基本watch用法
watch(count, (newValue, oldValue) => {
  console.log(`count从${oldValue}变为${newValue}`)
})

2、观察多个源

const firstName = ref('')
const lastName = ref('')

// 观察多个ref
watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log(`名字从${oldFirst} ${oldLast}变为${newFirst} ${newLast}`)
})

3、观察响应式对象

const state = reactive({
  user: {
    name: 'Alice',
    age: 25
  }
})

// 深度观察对象
watch(
  () => state.user,
  (newUser, oldUser) => {
    console.log('用户信息变化:', newUser)
  },
  { deep: true }
)

4、立即执行

const data = ref(null)

// 立即执行回调
watch(
  data,
  (newValue) => {
    console.log('数据:', newValue)
  },
  { immediate: true }
)

5、观察getter函数

const state = reactive({
  items: [],
  total: 0
})

// 观察计算值
watch(
  () => state.items.length,
  (newLength) => {
    console.log(`项目数量变为: ${newLength}`)
  }
)

6、停止观察

const stop = watch(count, (newValue) => {
  if (newValue > 10) {
    stop() // 停止观察
    console.log('已达到最大值,停止观察')
  }
})

7、实际应用示例

<template>
  <div>
    <input v-model="searchQuery" placeholder="搜索...">
    <div v-if="isSearching">搜索中...</div>
    <ul v-else>
      <li v-for="result in searchResults" :key="result.id">
        {{ result.name }}
      </li>
    </ul>
  </div>
</template>

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

const searchQuery = ref('')
const searchResults = ref([])
const isSearching = ref(false)

// 防抖搜索
watch(searchQuery, async (newQuery) => {
  if (!newQuery.trim()) {
    searchResults.value = []
    return
  }
  
  isSearching.value = true
  try {
    // 模拟API调用
    await new Promise(resolve => setTimeout(resolve, 300))
    searchResults.value = await mockSearch(newQuery)
  } finally {
    isSearching.value = false
  }
})

async function mockSearch(query) {
  // 模拟API返回
  return [
    { id: 1, name: `${query} 结果1` },
    { id: 2, name: `${query} 结果2` }
  ]
}
</script>

8、watchEffect
与watch相关的还有watchEffect,它会立即执行一次,并自动追踪其依赖:

import { ref, watchEffect } from 'vue'

const count = ref(0)

watchEffect(() => {
  console.log(`count的值是: ${count.value}`)
})

9、比较watch和watchEffect

特性watchwatchEffect
惰性执行否(立即执行)
指定观察源需要自动追踪
访问旧值可以不可以
初始化执行需要配置immediate总是执行

10、最佳实践

  • ​​避免过度使用​​:优先使用计算属性,只在需要副作用时使用watch
  • ​​清理副作用​​:在回调中执行异步操作时,注意清理之前的操作;
  • ​​性能优化​​:对于复杂对象,考虑使用{ deep: true }或特定getter函数;
  • ​​组件卸载时​​:在setup()中创建的watch会自动停止,手动创建的记得清理;

八、组合式API - 生命周期函数

在Vue 3的组合式API中,生命周期钩子是通过特定的函数来访问的,而不是Options API中的选项形式。
1、主要生命周期函数
onBeforeMount 和 onMounted:

import { onBeforeMount, onMounted } from 'vue'

onBeforeMount(() => {
  console.log('组件即将挂载')
})

onMounted(() => {
  console.log('组件已挂载')
  // 可以访问DOM元素
})

onBeforeUpdate 和 onUpdated:

import { onBeforeUpdate, onUpdated } from 'vue'

onBeforeUpdate(() => {
  console.log('组件即将更新')
})

onUpdated(() => {
  console.log('组件已更新')
  // DOM已更新完成
})

onBeforeUnmount 和 onUnmounted:

import { onBeforeUnmount, onUnmounted } from 'vue'

onBeforeUnmount(() => {
  console.log('组件即将卸载')
})

onUnmounted(() => {
  console.log('组件已卸载')
  // 清理定时器、事件监听等
})

onErrorCaptured:

import { onErrorCaptured } from 'vue'

onErrorCaptured((err, instance, info) => {
  console.error('捕获到错误:', err)
  // 返回false阻止错误继续向上传播
  return false
})

2、新增的生命周期函数
Vue 3还引入了两个新的生命周期函数:
onRenderTracked (开发模式):

import { onRenderTracked } from 'vue'

onRenderTracked((event) => {
  console.log('跟踪到依赖:', event)
})

onRenderTriggered (开发模式):

import { onRenderTriggered } from 'vue'

onRenderTriggered((event) => {
  console.log('触发重新渲染:', event)
})

3、组合式API与Options API生命周期对应关系

组合式APIOptions API
onBeforeMountbeforeMount
onMountedmounted
onBeforeUpdatebeforeUpdate
onUpdatedupdated
onBeforeUnmountbeforeUnmount
onUnmountedunmounted
onErrorCapturederrorCaptured
onActivatedactivated
onDeactivateddeactivated

4、实际应用示例

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

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

const count = ref(0)

onBeforeMount(() => {
  console.log('1. 组件即将挂载 - count:', count.value)
})

onMounted(() => {
  console.log('2. 组件已挂载 - 可以访问DOM')
  // 这里可以执行需要DOM的操作
})

onBeforeUpdate(() => {
  console.log('3. 组件即将更新 - 旧count:', count.value)
})

onUpdated(() => {
  console.log('4. 组件已更新 - 新count:', count.value)
})

onBeforeUnmount(() => {
  console.log('5. 组件即将卸载')
})

onUnmounted(() => {
  console.log('6. 组件已卸载')
  // 清理工作
})
</script>

5、最佳实践

  • ​​逻辑组织​​:将相关生命周期逻辑放在一起,保持代码可读性;
  • ​​清理工作​​:在onUnmounted中清理定时器、事件监听等资源;
  • ​​避免滥用​​:不是所有逻辑都需要放在生命周期中,有些可以放在方法中按需调用;
  • ​​异步操作​​:在onMounted中进行DOM操作或数据获取;
  • 性能优化​​:使用onRenderTracked和onRenderTriggered调试性能问题;

九、组合式API - 父子通信

1、父传子 - Props
父组件传递props:

<!-- ParentComponent.vue -->
<template>
  <ChildComponent 
    :title="pageTitle" 
    :count="counter" 
    :user="userData"
  />
</template>

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

const pageTitle = ref('Vue 3父子通信')
const counter = ref(0)
const userData = reactive({
  name: '张三',
  age: 25
})
</script>

子组件接收props:

<!-- ChildComponent.vue -->
<template>
  <div>
    <h2>{{ title }}</h2>
    <p>计数: {{ count }}</p>
    <p>用户: {{ user.name }} - {{ user.age }}岁</p>
  </div>
</template>

<script setup>
// 定义props
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  },
  user: {
    type: Object,
    default: () => ({})
  }
})
</script>

2、子传父 - Emits
子组件触发事件:

<!-- ChildComponent.vue -->
<template>
  <button @click="increment">增加计数</button>
  <button @click="sendData">发送数据</button>
</template>

<script setup>
// 定义emits
const emit = defineEmits(['increment', 'submit-data'])

const increment = () => {
  emit('increment')
}

const sendData = () => {
  emit('submit-data', {
    message: '来自子组件的数据',
    timestamp: new Date()
  })
}
</script>

父组件监听事件:

<!-- ParentComponent.vue -->
<template>
  <ChildComponent 
    @increment="handleIncrement"
    @submit-data="handleData"
  />
</template>

<script setup>
import ChildComponent from './ChildComponent.vue'

const handleIncrement = () => {
  console.log('子组件触发了increment事件')
}

const handleData = (data) => {
  console.log('接收到子组件数据:', data)
}
</script>

3、使用v-model实现双向绑定
Vue 3支持多个v-model绑定:
子组件:

<!-- ChildComponent.vue -->
<template>
  <input 
    :value="modelValue" 
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

父组件:

<!-- ParentComponent.vue -->
<template>
  <ChildComponent v-model="message" />
  <!-- 相当于 -->
  <!-- <ChildComponent :modelValue="message" @update:modelValue="message" /> -->
  <p>父组件中的消息: {{ message }}</p>
</template>

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

const message = ref('')
</script>

4、使用插槽(Slot)通信
默认插槽:

<!-- ParentComponent.vue -->
<template>
  <ChildComponent>
    <p>这是插入到子组件的内容</p>
  </ChildComponent>
</template>

<!-- ChildComponent.vue -->
<template>
  <div>
    <h2>子组件标题</h2>
    <slot></slot> <!-- 插槽内容将在这里渲染 -->
  </div>
</template>

具名插槽:

<!-- ParentComponent.vue -->
<template>
  <ChildComponent>
    <template #header>
      <h1>自定义标题</h1>
    </template>
    <template #footer>
      <p>自定义页脚</p>
    </template>
  </ChildComponent>
</template>

<!-- ChildComponent.vue -->
<template>
  <div>
    <slot name="header"></slot>
    <div>子组件内容</div>
    <slot name="footer"></slot>
  </div>
</template>

作用域插槽:

<!-- ParentComponent.vue -->
<template>
  <ChildComponent v-slot="slotProps">
    <p>来自子组件的数据: {{ slotProps.userData }}</p>
  </ChildComponent>
</template>

<!-- ChildComponent.vue -->
<template>
  <div>
    <slot :userData="user"></slot>
  </div>
</template>

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

const user = reactive({
  name: '李四',
  age: 30
})
</script>

5、使用provide/inject跨层级通信
祖先组件提供数据:

<!-- AncestorComponent.vue -->
<template>
  <ParentComponent />
</template>

<script setup>
import { provide, ref } from 'vue'
import ParentComponent from './ParentComponent.vue'

const theme = ref('dark')

// 提供数据
provide('theme', theme)
provide('appName', 'Vue 3应用')
</script>

后代组件注入数据:

<!-- DescendantComponent.vue -->
<template>
  <div :class="theme">
    <h2>{{ appName }}</h2>
  </div>
</template>

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

// 注入数据
const theme = inject('theme', 'light') // 第二个参数是默认值
const appName = inject('appName')
</script>

还可以传递方法;
祖先组件提供方法:

<!-- AncestorComponent.vue -->
<template>
  <ParentComponent />
</template>

<script setup>
import { provide } from 'vue'
import ParentComponent from './ParentComponent.vue'

// 提供方法
const showNotification = (message) => {
  alert(`通知: ${message}`)
}

const updateTheme = (newTheme) => {
  console.log('主题更新为:', newTheme)
}

provide('showNotification', showNotification)
provide('updateTheme', updateTheme)
</script>

后代组件使用方法:

<!-- DescendantComponent.vue -->
<template>
  <button @click="notify">显示通知</button>
  <button @click="changeTheme">切换主题</button>
</template>

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

// 注入方法
const showNotification = inject('showNotification')
const updateTheme = inject('updateTheme')

const notify = () => {
  showNotification('这是来自子组件的消息')
}

const changeTheme = () => {
  updateTheme('dark')
}
</script>

6、使用ref访问子组件

<!-- ParentComponent.vue -->
<template>
  <ChildComponent ref="childRef" />
  <button @click="callChildMethod">调用子组件方法</button>
</template>

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

const childRef = ref(null)

const callChildMethod = () => {
  if (childRef.value) {
    childRef.value.sayHello()
  }
}
</script>

<!-- ChildComponent.vue -->
<script setup>
const sayHello = () => {
  console.log('Hello from child component!')
}

// 暴露方法给父组件
defineExpose({
  sayHello
})
</script>

defineExpose:

  • 默认情况下在 <script setup>语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法容许访问;

7、最佳实践

  • ​​props向下​​:数据流应该主要从父组件流向子组件;
  • ​​events向上​​:子组件应该通过事件通知父组件状态变化;
  • ​​避免直接修改props​​:子组件不应该直接修改props,应该通过emit事件让父组件修改;
  • ​​合理使用provide/inject​​:适用于跨多层级的通信,但不应该滥用;
  • 谨慎使用ref访问子组件​​:这会增加组件间的耦合度;
  • ​​类型安全​​:使用TypeScript时,为props和emits定义明确的类型;

十、Vue3.3 新特性

1、defineOptions
Vue 3.3 引入了 defineOptions 宏,这是一个在 <script setup> 中定义组件选项的新方式,解决了之前无法直接在 <script setup> 中声明组件选项(如 nameinheritAttrs 等)的问题。

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

defineOptions({
  name: 'MyComponent',
  inheritAttrs: false
})
</script>

2、defineModel
Vue 3.3 引入了 defineModel 宏,这是一个简化组件双向绑定的新特性,特别针对 v-model 的使用场景进行了优化。

<!-- ChildComponent.vue -->
<script setup>
const model = defineModel()
</script>

<template>
  <input v-model="model">
</template>

<!-- ParentComponent.vue -->
<template>
  <ChildComponent v-model="message" />
</template>

<script setup>
import { ref } from 'vue'
const message = ref('')
</script>

十一、Vue3 Pinia的使用

Pinia 是 Vue 3 的官方推荐状态管理库,相比 Vuex 更简单、更符合组合式 API 的设计理念。
1、安装 Pinia

npm install pinia
# 或
yarn add pinia

2、创建 Pinia 实例并注册

// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

3、定义 Store

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    name: 'Eduardo'
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
    doubleCountPlusOne() {
      return this.doubleCount + 1
    }
  },
  actions: {
    increment() {
      this.count++
    },
    async incrementAsync() {
      await new Promise(resolve => setTimeout(resolve, 1000))
      this.increment()
    }
  }
})

Pinia 也支持组合式 API 风格的 Store 定义:

// stores/counter.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  // state
  const count = ref(0)
  const name = ref('Eduardo')

  // getters (使用 computed)
  const doubleCount = computed(() => count.value * 2)
  const doubleCountPlusOne = computed(() => doubleCount.value + 1)

  // actions (普通函数)
  function increment() {
    count.value++
  }

  async function incrementAsync() {
    await new Promise(resolve => setTimeout(resolve, 1000))
    increment()
  }

  return {
    // state
    count,name,
    
    // getters
    doubleCount,doubleCountPlusOne,
    
    // actions
    increment,incrementAsync
  }
})

4、在组件中使用 Store

<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

// 直接访问 state
console.log(counter.count) // 0

// 调用 action
counter.increment()

// 使用 getter
console.log(counter.doubleCount) // 2
</script>

<template>
  <div>
    <h1>{{ counter.count }}</h1>
    <button @click="counter.increment()">Increment</button>
    <p>Double count: {{ counter.doubleCount }}</p>
  </div>
</template>

5、状态持久化
可以使用 pinia-plugin-persistedstate 插件实现状态持久化:

npm install pinia-plugin-persistedstate
// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

然后在 Store 中启用:

export const useCounterStore = defineStore('counter', {
  // ...其他配置
  persist: true
})

在选项式 API 中使用:

import { mapState, mapActions } from 'pinia'
import { useCounterStore } from '@/stores/counter'

export default {
  computed: {
    ...mapState(useCounterStore, ['count', 'doubleCount'])
  },
  methods: {
    ...mapActions(useCounterStore, ['increment'])
  }
}

在组合式API中使用:

<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()

// 直接访问 state 和 getters
const count = computed(() => counter.count)
const doubleCount = computed(() => counter.doubleCount)

// 直接调用 actions
function increment() {
  counter.increment()
}
</script>

<template>
  <!-- 模板中使用方式不变 -->
  <div>{{ count }}</div>
  <div>{{ doubleCount }}</div>
  <button @click="increment">Increment</button>
</template>

6、Pinia 的主要优势

  • 更简单的 API 设计;
  • 完美的 TypeScript 支持;
  • 支持组合式 API;
  • 模块化设计,无需嵌套模块;
  • 更轻量级;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

testleaf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值