初识 Vue 3

Vue 3 简介

Vue 团队在 2020 年 9 月 18 日发布了 Vue 3.0 版本,代号海贼王,现在市面上一些主流的组件库都已经发布了支持 Vue 3,其他生态也在不断地完善中。

Vue 3 的优势:

  • 更快的渲染速度:Vue 3 重写了 diff 算法,使用了新的虚拟 DOM 实现,可以更快地渲染页面,减少页面的闪烁和卡顿。
  • 更好的 TypeScript 支持:Vue 3 引入了全面的 TypeScript 支持,包括类型检查和代码提示。
  • 更好的响应式系统:Vue 3 的响应式系统比 Vue 2 更加灵活和高效,可以更好地处理嵌套数据和响应式数组。
  • 更好的组件封装:Vue 3 引入了 Composition API(组合式 API),它可以更好地封装组件的逻辑和状态,并使代码更具可读性和可维护性。
  • 更少的包大小:Vue 3 的核心库比 Vue 2 小得多,这意味着更快的下载速度和更少的资源占用。
  • 更好的 Tree-Shaking:Vue 3 的模块系统使用 ES 模块,可以更好地支持 Tree-Shaking,当引入一个模块的时候,不引入这个模块的所有代码,只引入我需要的代码。
  • ......

Vue 3 脚手架

create-vue 是 Vue 官方新的脚手架工具,底层切换到了 vite,vite 在开发时具有更快的启动速度和重新加载速度。

在 Vue 2 中,官方提供的脚手架是 vue-cli,底层是 webpack。


示例:创建 Vue 3 项目

终端执行:

# 创建vue项目
npm init vue@latest

示例效果:

终端执行:

# 切换项目目录
cd vue3-project

# 安装依赖
npm install

# 运行项目
npm run dev

示例效果:


Vue 3 项目结构

使用 create-vue 创建的项目:

文件/文件夹结构:

名称描述
public静态资源文件夹
src/assets静态资源文件夹
src/componentsVue 普通组件文件夹
App.vue所有页面的根组件
main.js项目的入口 JavaScript 文件,全局的配置和初始化设置在这里执行
index.html项目的入口页面
Vue 2 的入口页面在 public/index.html 中
package.jsonnpm 配置文件
vite.config.js脚手架配置文件
Vue 2 中的脚手架配置文件是 vue.config.js

*.vue 文件:Vue 单文件组件。

<!--
    在Vue2中,单文件组件的代码顺序是<template>、<script>、<style>
    在Vue3中,单文件组件的代码顺序是<script>、<template>、<style>
-->

<!--
    setup:允许使用Vue3的组合式API
    在<script>中,可以使用绝大部分的Vue2语法(即选项式API)
-->
<script setup>

</script>

<template>
    <!--
        在Vue2中,组件模板有且只能有一个根标签
        在Vue3中,无此限制
    -->
</template>

<style scoped>

</style>

src/main.js:项目的入口 JavaScript 文件。

// 导入外部样式
import './assets/main.css'

// 使用对象解构的方式从Vue中获取createApp
import { createApp } from 'vue'
// 导入根组件
import App from './App.vue'

// createApp:相当于new Vue,创建Vue实例
// mount:将Vue实例挂载到index.html的#app上
createApp(App).mount('#app')

package.json:npm 配置文件。

{
    "name": "vue3-project",
    "version": "0.0.0",
    "private": true,
    "type": "module",
    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "preview": "vite preview"
    },
    "dependencies": {
        "vue": "^3.4.21"
    },
    "devDependencies": {
        "@vitejs/plugin-vue": "^5.0.4",
        "vite": "^5.1.6"
    }
}

Vue 3 初体验

通过在脚手架项目中,使用 Vue 2 和 Vue 3 语法实现 Counter,体验 Vue 3 的新特性。


示例:Vue 3 初体验

Vue2 根组件(src/App.vue):

<script>
export default {
    data() {
        return {
            count: 0
        }
    },
    methods: {
        addCount() {
            this.count++
        }
    }
}
</script>

<template>
    <button @click="addCount">count is {{count}}</button>
</template>

<style scoped>

</style>

Vue 3 根组件(src/App.vue):

<script setup>
// 从Vue中导入ref
import { ref } from 'vue'

// 定义响应式对象
const count = ref(0)

// 定义方法
const addCount = ()=> count.value++
</script>

<template>
    <button @click="addCount">count is {{count}}</button>
</template>

<style scoped>

</style>

示例效果:


Vue 3 语法实现 Counter,相比于 Vue 2 语法实现 Counter,代码量相对变少,分散式维护也变成了集中式维护。

组合式 API 语法

setup

setup 是一个新的组件选项,作为组件中使用组合 API 的起点。

  • 从组件生命周期来看,setup 的执行在组件实例创建之前 beforeCreate 执行。
  • 在 setup 函数中, this 不是指向组件实例,this 此时是 undefined。

setup 有两种使用方式:

  • 定义为 setup 函数,函数内返回数据和方法。
  • 在 script 标签上添加 setup 标记以使用语法糖。

示例:setup

根组件(src/App.vue):

import {ref} from "vue"

export default {
    setup() {
        const count = ref(0)
        const addCount = () => {
            // 访问数据时,不需要通过this访问,this==undefined
            // 通过数据名.value访问
            count.value++
        }

        // 优先于beforeCreate执行
        console.log('setup')

        // 返回数据和方法
        return {
            count,
            addCount
        }
    },
    beforeCreate() {
        console.log('beforeCreate')
    }
}

示例效果:


ref

ref 函数用于接收简单类型或者对象类型的数据传入并返回一个响应式的对象。

  • ref 函数内部的实现依赖于 reactive 函数。
  • 访问数据时,通过数据名.value 的方式访问数据。
  • 在 template 模板中使用 ref 声明的响应式数据,可以省略 .value。

示例:ref

根组件(src/App.vue):

<script setup>
// 导入ref函数
import {ref} from "vue"

// 执行ref函数,传入一个简单类型的参数
// 底层会把这个数据包装成一个对象,最后依赖于reactive函数
const count = ref(0)

// 定义自增方法
const addCount = () => {
    // 通过count.value访问数据
    count.value++
}

</script>

<template>
    <button @click="addCount">count is {{count}}</button>
</template>

reactive

reactive 函数用于接收对象类型的数据传入并返回一个响应式的对象。

  • reactive 不能处理简单类型的数据,只能处理对象类型的数据。
  • 访问数据时,通过对象名.属性名的方式访问数据。

示例:reactive

根组件(src/App.vue):

<script setup>
// 导入reactive函数
import {reactive} from "vue"

// 执行reactive函数,传入一个对象类型的参数
const state = reactive({
    count: 0
})

// 定义自增方法
const addCount = () => {
    state.count++
}

</script>

<template>
    <button @click="addCount">count is {{state.count}}</button>
</template>

computed

Vue 3 的计算属性基本思想和 Vue 2 完全一致,组合式 API 下的计算属性只是修改了写法。


示例:computed

根组件(src/App.vue):

<script setup>
// 导入computed函数
import {ref, computed} from "vue"

const price = ref(10)
const count = ref(0)

// 使用computed函数,计算总价
// 参数1:计算函数
const totalPrice = computed(() => {
    // 返回计算结果
    return price.value * count.value
})
</script>

<template>
    单价:{{price}}<br>
    数量:{{count}}<br>
    总价:{{totalPrice}}<br>
    <button @click="count++">+</button>
    <button @click="count--">-</button>
</template>

示例效果:


watch

watch 监听器用于监听数据的变化,数据变化时执行回调函数。


示例:watch 监听数据的变化

根组件(src/App.vue):

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

const name = ref('duozai')
const age = ref(26)

// 定义监听器
// 参数1:要监听的数据
// 参数2:监听回调函数,传入新值和旧值
watch(name, (newValue, oldValue) => {
    console.log(newValue, oldValue)
})
</script>

<template>
    姓名:{{name}}<br>
    年龄:{{age}}<br>
    <button @click="name='shaozai'">name change</button>
    <button @click="age++">age++</button>
</template>

示例效果:


watch 监听器还可以用于监听多个数据的变化。


示例:watch 监听多个数据的变化

根组件(src/App.vue):

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

const name = ref('duozai')
const age = ref(26)

// 定义监听器
// 参数1:要监听的数据数组
// 参数2:监听回调函数,传入新值数组和旧值数组
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
    console.log(newName, newAge, oldName, oldAge)
})
</script>

<template>
    姓名:{{name}}<br>
    年龄:{{age}}<br>
    <button @click="name='shaozai'">name change</button>
    <button @click="age++">age++</button>
</template>

示例效果:


在 watch 监听器中,有一个配置项 immediate,可以在监听器创建时立即出发回调,响应式数据变化之后继续执行回调。


示例:watch 监听器创建时立即回调

根组件(src/App.vue):

watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
    console.log(newName, newAge, oldName, oldAge)
}, {
    // 监听器创建时立即出发回调
    immediate: true
})

示例效果:


通过 watch 监听的 ref 对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启 deep 以实现深度监听。

深度监听对象类型的数据变化时,无法获取旧值,只能监听到数据的变化。


示例:watch 深度监听对象类型数据的变化

根组件(src/App.vue):

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

const user = ref({
    name: 'duozai',
    age: 26
})

// 定义监听器
// 参数1:要监听的对象
// 参数2:监听回调函数
// 参数3:监听器配置对象
watch(user, (newValue, oldValue) => {
    console.log(newValue, oldValue)
}, {
    // 开启深度监听
    deep: true
})
</script>

<template>
    姓名:{{user.name}}<br>
    年龄:{{user.age}}<br>
    <button @click="user.name='shaozai'">name change</button>
    <button @click="user.age++">age++</button>
</template>

示例效果:


使用 watch 监听对象类型的数据变化时,可以精确监听某个对象属性的变化。


示例:watch 精确监听对象属性的变化

根组件(src/App.vue):

// 定义监听器
// 参数1:函数,指向要监听的对象属性
// 参数2:监听回调函数
watch(
    () => user.value.age,
    () => {
        console.log('age change')
    }
)

示例效果:


生命周期钩子函数

Vue 2 和 Vue 3 中的生命周期钩子函数:

Vue 2Vue 3 选项式 APIVue 3 组合式 API
beforeCreatebeforeCreatesetup
createdcreatedsetup
beforeMountbeforeMountonBeforeMount
mountedmountedonMounted
beforeUpdatebeforeUpdateonBeforeUpdate
updatedupdatedonUpdated
beforeDestroybeforeUnmountonBeforeUnmount
destroyedunmountedonUnmounted

在 Vue 3 中,生命周期函数可以执行多次,按照顺序依次执行。


示例:生命周期

根组件(src/App.vue):

// 导入生命周期钩子函数
import {onMounted} from "vue";

// 执行生命周期函数
onMounted(() => {
  console.log("onMounted 1");
})

onMounted(() => {
    console.log("onMounted 2");
})

示例效果:


父子组件通信

在 Vue2 中,父组件向子组件传递数据的基本思想:

  • 父组件中给子组件绑定属性。
  • 子组件内部通过 props 选项接收数据。

在 Vue 3 中,父组件向子组件传递数据的基本思想:

  • 父组件中给子组件绑定属性。
  • 子组件内部通过 defineProps 选项接收数据,defineProps 中可以传入数组或对象。

示例:父组件向子组件传递数据

根组件(src/App.vue):

<script setup>
// 导入子组件,setup语法糖会自动注册子组件
import Child from "@/Child.vue"
</script>

<template>
    <div>
        <h3>父组件</h3>
        <!-- 父组件调用子组件,并传递数据 -->
        <Child message="hello, Vue 3"></Child>
    </div>
</template>

子组件(src/components/Child.vue):

<script setup>
// 在子组件中使用defineProps接收参数

// 方式1:传入参数数组
// defineProps(['message'])

// 方式2:传入参数对象,可以限定参数类型
defineProps({
    message: String
})
</script>

<template>
    <div>
        <h4>子组件</h4>
        <p>父组件传递给子组件的参数:{{message}}</p>
    </div>
</template>

示例效果:


子父组件通信

在 Vue2 中,子组件向父组件传递数据的基本思想:

  • 父组件中给子组件标签通过 @ 绑定自定义事件。
  • 子组件内部通过 emit 方法触发事件。

在 Vue 3 中,子组件向父组件传递数据的基本思想:

  • 父组件中给子组件标签通过 @ 绑定自定义事件。
  • 子组件中,通过 defineEmits 生成 emit 方法。
  • 子组件内部触发 emit 自定义事件,并传递参数。

示例:子组件向父组件传递数据

根组件(src/App.vue):

<script setup>
import Child from "@/Child.vue"
import {ref} from "vue";

const message = ref('')

// getMessage传入一个参数
// 该参数即子组件传递过来的数据
const getMessage = (value) => {
    message.value = value
}
</script>

<template>
    <div>
        <h3>父组件</h3>
        <!-- 父组件中给子组件绑定自定义事件 -->
        <Child @get-message="getMessage"></Child>
        <p>子组件传递给父组件的数据:{{message}}</p>
    </div>
</template>

子组件(src/components/Child.vue):

<script setup>
import {ref} from "vue"

const message = ref('')

// 通过defineEmits生成emit方法
// defineEmits传入要触发的事件的名称数组
const emit = defineEmits(['get-message'])

const send = () => {
    // 触发emit自定义事件,并传递参数
    // 参数1:要触发的事件名称
    // 参数2:要传递的数据
    emit('get-message', message)
}
</script>

<template>
    <div>
        <h4>子组件</h4>
        <input v-model="message">
        <button @click="send">Send</button>
    </div>
</template>

示例效果:


跨层组件通信

provide 函数和 inject 函数用于顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信。


示例:跨层组件通信

根组件(src/App.vue):

<script setup>
import Child from "@/Child.vue"
import {provide, ref} from "vue"

const count = ref(0)
const addCount = () => {
  count.value++
}

// 顶层通过provide提供数据
// 参数1:数据名称
// 参数2:数据对象/数据的值/方法
provide('count', count)

// 顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据
provide('addCount', addCount)
</script>

<template>
    <div>
        <h3>父组件</h3>
        <!-- 父组件调用子组件 -->
        <Child></Child>
    </div>
</template>

子组件(src/components/Child.vue):

<script setup>
import GradeSon from "@/GradeSon.vue"
</script>

<template>
    <div>
        <h4>子组件</h4>
        <!-- 子组件调用孙组件 -->
        <GradeSon></GradeSon>
    </div>
</template>

孙组件(src/components/GradeSon.vue):

<script setup>
import {inject} from "vue";

// 底层通过inject接收数据
const count = inject('count')
const addCount = inject('addCount')
</script>

<template>
    <div>
        <h5>孙组件</h5>
        <p>顶层传递给底层的数据:{{count}}</p>
        <button @click="addCount">修改顶层的count</button>
    </div>
</template>

示例效果:


Pinia

Pinia 简介

Pinia 是 Vue 的专属状态管理库,可实现跨组件或页面共享状态,是 Vuex 状态管理工具的替代品。

Pinia 文档:https://pinia.vuejs.org/zh/

Pinia 的优势:

  • 提供更加简单的 API,去掉了 mutations。
  • 提供符合组合式 API 风格的 API,和 Vue3 新语法统一。
  • 去掉了 modules 的概念,每一个 store 都是一个独立的模块。
  • 搭配 TypeScript 一起使用提供可靠的类型推断。
  • ......

Pinia 初体验

Pinia 支持组合式 API,和 Vue3 新语法相似。


示例:Pinia 初体验

终端执行:

npm install pinia

项目入口配置文件(src/main.js):

// 导入createPinia
import { createPinia } from 'pinia'

createApp(App)
    // 注册Pinia
    .use(createPinia())
    .mount('#app')

CountStore(src/store/counter.js):

// 导入defineStore
import { defineStore } from 'pinia'
import {computed, ref} from "vue";

// defineStore:定义一个store
// 参数1:store的名称,一般叫做useXxxStore
// 参数2:Store的配置函数
export const useCounterStore = defineStore('useCounterStore', () => {
    // 定义store中的数据
    const count = ref(0)

    // 定义store中的计算属性
    const doubleCount = computed(() => count.value * 2)

    // 定义store中修改数据的方法
    function increment() {
        count.value++
    }

    // 返回数据和方法
    return { count, doubleCount, increment }
})

根组件(App.vue):

<script setup>
// 导入use方法
import { useCounterStore } from '@/store/counter'
import {storeToRefs} from "pinia";
// 执行方法得到store里的数据和方法
const counterStore = useCounterStore()
// 使用storeToRefs获取响应式数据
const { count } = storeToRefs(counterStore)
</script>

<template>
    <!-- 还可以使用count++,此时count已经是响应式数据了 -->
    <button @click="counterStore.increment">
        <!-- {{ counterStore.count }} -->
        {{count}}
    </button>
</template>

示例效果: