Vue3简单快速基础入门(三)
Vue3简单快速基础入门(三)
Vue3快速入门3 -组件篇
1. 基于Vite创建Vue3项目
Vite是一个快速的前端开发环境,它使用原生ES模块,并提供丰富的内置功能,如零配置的HMR(Hot Module Replacement)、ESLint、TypeScript支持、CSS预处理等。
在合适的目录下,打开终端,输入以下命令:
npm create vite@latest
成功后会出现以下提示:
# 项目名称
✔ Project name: demo
# 选择项目框架
✔ Select a framework: › Vue
# 选择项目语言
✔ Select a variant: › JavaScript
然后回车,等待项目创建完成。
创建完成后,按照提示输出
cd demo
npm install
npm run dev
npm install 安装依赖包,执行完成后可以在项目目录看到
node_modules文件夹以及package-lock.json文件。
npm run dev 启动项目
这样就成功创建了Vue3项目,打开浏览器访问http://localhost:5173即可看到项目运行效果。
2. 新建一个vue文件
<script setup>
import {reactive} from 'vue'
const web = reactive({
show:true
})
const toggle = ()=>{
web.show =!web.show
}
</script>
<template>
<h1>{{ web.show }}</h1>
<p v-show="web.show">十一月的早晨</p>
<button @click="toggle">切换显示</button>
</template>
<style scoped>
</style>
以上代码是一个简单的Vue3组件,它定义了一个名为
web的reactive对象,并定义了两个方法toggle和show,其中toggle方法用来切换show属性的值,show属性用来控制p标签的显示与否。
3. 组件化开发
组件化开发是Vue3的核心理念之一,它可以将复杂的页面拆分成多个小组件,每个组件只负责一部分功能,更加方便维护和复用。
3.1 定义组件
在components文件夹下新建一个名为
header.vue的文件,内容如下:
<script setup>
</script>
<template>
<h1> 十一月的早晨 </h1>
</template>
<style scoped>
</style>
同样方式,创建一个名为
footer.vue的文件,内容如下:
<script setup>
</script>
<template>
<h1> 吹进卧室的风 </h1>
</template>
<style scoped>
</style>
3.2 导入并使用组件
在
App.vue文件中导入并使用组件,内容如下:
<script setup>
// 导入组件 注意这里 组件名首字母必须大写
import Header from './components/header.vue'
import Footer from './components/footer.vue'
import {reactive} from 'vue'
const web = reactive({
show:true
})
const toggle = ()=>{
web.show =!web.show
}
</script>
<template>
<!-- 使用组件 -->
<Header/>
<h1>{{ web.show }}</h1>
<button @click="toggle">切换显示</button>
<!-- 使用组件 -->
<Footer/>
</template>
<style scoped>
</style>
导入组件后,此时设计一个概念,父子组件,父组件可以包含多个子组件,子组件只能有一个父组件。在这里,
App.vue是父组件,header.vue和footer.vue则是子组件。
3.3 父子组件通信
父子组件通信是Vue3的重要特性之一,它可以让父组件向子组件传递数据,也可以让子组件向父组件传递数据。
3.3.1 父向子传递数据
可以直接在子组件上设置属性,子组件中用
definProps接收属性,并在模板中使用。如例:
<!-- 首先在父组件中为子组件设置属性 -->
<script setup>
// 导入组件
import Header from './components/header.vue'
import Footer from './components/footer.vue'
import {reactive} from 'vue'
// 使用reactive创建响应式数据
const web = reactive({
user:"旺旺",
age:25
})
const addAge = () =>{
web.age++
}
</script>
<template>
<!-- 通过设置属性传递数据 -->
<Header propsName="十一月" propsUrl="https://blog.xy21lin.cn" />
<h1>学习Vue3 父子组件传值 </h1>
<button @click="addAge">增加年龄</button>
<!-- 使用v-bind绑定属性传递数据 -->
<Footer :="web" />
</template>
<!-- 子组件使用defineProps接收父组件传递的数据 -->
<!-- defineProps可以用两种方式来接收,一种是对象,另一种是数组。 -->
<!-- Header子组件接收 -->
<script setup>
//使用数组接收传递的数据
const prop = defineProps(["propsName","propsUrl"])
</script>
<template>
<h1> 十一月的早晨 </h1>
<h2>数组接收父组件传来的值 | propsName:{{propsName}} | propsUrl:{{propsUrl}}</h2>
</template>
<!-- Footer子组件接收 -->
<script setup>
//使用对象接收传递的数据
const prop = defineProps({
user:String,
age:Number,
//可以在这里进行更细致的设置 必填 默认值等
sex:{
type:String,
// required:true,
default:"男"
}
})
</script>
<template>
<h1> 吹进卧室的风 , 名字{{prop.user}} , 年龄{{prop.age}} , 性别{{prop.sex}}</h1>
</template>
3.3.2 子向父传递数据
子组件使用
defineEmits来定义自己可以触发的事件,父组件中用v-on监听事件并调用相应的方法。如例:
<script setup>
// 导入组件
import Header from './components/header.vue'
import {reactive} from 'vue'
const web = reactive({
url:"www.baidu.com",
name:"无名",
age:10
})
//监听 子组件定义的自定义事件getWeb并绑定 emitsWebUrl方法上 来处理
const emitsWebUrl = (data) =>{
console.log("接收到子组件传来的值",data)
web.url = data.url
}
//监听 子组件定义的自定义事件addUser 并绑定到 emitsAddUser 方法上来处理
const emitsAddUser = (data) =>{
console.log("接收到子组件传来的值",data)
web.age += data
}
</script>
<template>
<Header @getWeb="emitsWebUrl" @addUser="emitsAddUser" />
<h2>{{web.url}} | {{web.name}} | {{web.age}}</h2>
</template>
<!-- 子组件使用defineEmits定义自定义事件 -->
<script setup>
// 使用defineEmits 定义了名为 getWeb 和 addUser 的自定义事件
const emit = defineEmits(['getWeb','addUser'])
// 调用自定义事件,并传入对象参数
emit("getWeb", {name:"十一月",url:"https://blog.xy21lin.cn"})
// 定义 addUser 方法,调用自定义事件 addUser,传入参数值20
const addUser = () =>{
emit("addUser",20)
}
</script>
<template>
<h1> 十一月的早晨 </h1>
<button @click="addUser">添加用户</button>
</template>
4. 跨组件通信
跨组件通信是Vue3的重要特性之一,它可以让不同组件之间通信,实现功能的解耦。
父组件可以传递给子组件之下的子组件,但是子组件不能直接向父组件传递数据。
在这里,使用的是
provide和inject来实现跨组件通信。
provide可以向下传递数据,也就是可以把父组件里的数据传递给子组件及之下的组件。
inject则是用于接收provide传递的数据。
使用App.vue作为父组件,header.vue作为子组件,nav.vue作为子组件之下的子组件。(没有文件就创建)
<!-- App.vue 父组件使用provide传递数据 -->
<script setup>
// 导入组件
import Header from './components/header.vue'
import {reactive,ref,provide} from 'vue'
const web = reactive({
name:"十一月",
url:"https://blog.xy21lin.cn/"
})
// 传递web对象 参数是key和valu 也就是键值对 建议键名带上provide前缀 方便后期维护
provide("provideWeb",web)
const user = ref(0)
provide('provideUser',user)
const addUser = ()=>{
user.value++
}
// 传递函数
provide('provideFuncAddUser',addUser)
</script>
<template>
<h1>App.vue | Top 组件 | user: {{user}}</h1>
<button @click="addUser">父组件 Top 增加</button>
<Header />
</template>
<!-- header.vue 子组件 使用inject接收 -->
<script setup>
import Nav from './nav.vue'
// 子组件需要使用 inject 接收父组件 provide 传递的数据
import {inject} from 'vue'
// 使用inject 通过 provide 设置的键名 来获取传递的数据
const web = inject('provideWeb')
console.log("provideWeb", web)
const user = inject('provideUser')
console.log("provideUser", user)
const addUser = inject('provideFuncAddUser')
console.log("provideFuncAddUser", addUser)
</script>
<template>
<h1> header.vue | Middle 组件</h1>
<button @click="addUser">子组件Middle添加</button>
<Nav />
</template>
<!-- nav.vue 子组件之下的子组件 使用inject接收 -->
<script setup>
import {inject} from 'vue'
const addUser = inject("provideFuncAddUser")
</script>
<template>
<h1> nav.vue | Bottom 组件</h1>
<button @click="addUser">子组件 Bottom 添加</button>
</template>
5. 插槽
插槽:是指可以在父组件内定义模板片段,在子组件中可以将定义的模板片段插入到子组件的特定位置。
插槽使用的是solt属性
5.1 匿名插槽
匿名插槽:是指在父组件中定义的插槽,没有指定名称,只能有一个默认插槽。
<!-- App.vue 父组件 -->
<script setup>
// 导入组件
import Header from './components/header.vue'
</script>
<template>
<Header>
<h1 style="color: pink">Header组件插槽,这里是匿名插槽</h1>
</Header>
</template>
<!-- header.vue 子组件使用匿名插槽 -->
<script setup>
</script>
<template>
<h1>Header组件</h1>
<!-- 调用匿名插槽模板 -->
<slot/>
</template>
5.2 具名插槽
具名插槽:是指在父组件中定义的插槽,指定了名称,可以有多个插槽。
<!-- App.vue 父组件 -->
<script setup>
// 导入组件
import Footer from './components/footer.vue'
</script>
<template>
<Footer>
<!-- 具名插槽,需要使用template标签,并在template标签上使用v-slot 指定插槽名称 这里指定模板名称为url -->
<!-- <template v-slot:url> -->
<!-- v-slot:url 可以简写为 #url -->
<template #url>
<h1 style="color:blue">Footer组件插槽,这里是具名插槽</h1>
</template>
</Footer>
</template>
<!-- footer.vue 子组件使用具名插槽 -->
<script setup>
</script>
<template>
<h1> Footer组件</h1>
<!-- 调用具名插槽模板 -->
<slot name="url"/>
</template>
5.3 作用域插槽
作用域插槽:子组件向父组件传递数据,并在父组件定义的模板中渲染
<!-- App.vue 父组件 -->
<script setup>
import Footer from './components/footer.vue'
</script>
<template>
<Footer>
<!-- 可以使用data对象 来接收子组件传递过来的数据 -->
<!-- <template #url="data">
<h1>这里是作用域插槽,数据是:{{ data.author }} | {{ data.url }}</h1>
</template> -->
<!-- 或者是直接用具体属性来接收 -->
<template #url="{author,url}">
<h1>这里是作用域插槽,数据是:{{ author }} | {{ url }}</h1>
</template>
</Footer>
</template>
<!-- footer.vue 子组件 -->
<script setup>
</script>
<template>
<h1> Footer组件</h1>
<!-- 调用具名插槽模板 并传递数据给父组件 -->
<slot name="url" author="十一月" url="https://blog.xy21lin.cn"/>
</template>
<style scoped>
</style>
6. 生命周期函数
Vue3的生命周期函数分为
beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed。
6.1 创建阶段钩子函数
beforeCreate:在实例初始化之后,数据观测(data observer)和事件配置之前被调用。
created:在实例创建完成后被调用,此时实例已经完成了数据观测(data observer)和事件配置。
目前已基本废弃,使用setup函数替代。
setup这是 Vue 3 新引入的钩子,它在组件实例创建之前调用,允许我们在组件实例创建之前进行复杂的初始化操作。使用场景:适合进行异步数据获取、状态管理、逻辑封装等。
6.2 挂载阶段钩子函数
beforeMount:在挂载开始之前被调用,相关的模板渲染函数都没有被调用。
mounted:在实例挂载之后被调用,相关的模板渲染函数被调用。
<script setup>
import {onBeforeMount,onMounted} from 'vue'
const web = {
name:"十一月",
url:"https://blog.xy21lin.cn/"
}
// 实例挂载之后被调用
onMounted(() => {
console.log("实例挂载之后被调用")
})
// 实例挂载之前被调用
onBeforeMount(() => {
console.log("实例挂载之前被调用")
})
</script>
<template>
<h1>App.vue | Top 组件</h1>
</template>
6.3 更新阶段钩子函数
beforeUpdate:在数据更新之前被调用,发生在虚拟 DOM 打补丁之前。
updated:在数据更新之后被调用,发生在虚拟 DOM 打补丁之后。
<script setup>
import {onBeforeUpdate,onUpdated} from 'vue'
const web = {
name:"十一月",
url:"https://blog.xy21lin.cn/"
}
// 实例数据更新之前被调用
onBeforeUpdate(() => {
console.log("实例数据更新之前被调用")
})
// 实例数据更新之后被调用
onUpdated(() => {
console.log("实例数据更新之后被调用")
})
</script>
<template>
<h1>App.vue | Top 组件</h1>
</template>
6.4 销毁阶段钩子函数
beforeDestroy:在实例销毁之前被调用。
destroyed:在实例销毁之后被调用。
<!-- 父组件App.vue -->
<script setup>
import {reactive} from 'vue'
import Header from './components/header.vue'
const web = reactive({
url: 'https://blog.xy21lin.cn',
count:0,
show:true
})
</script>
<template>
<Header v-if="web.show"/>
<button @click="web.show=!web.show">显示切换</button>
</template>
<!-- 子组件header.vue -->
<script setup>
import { onUnmounted,onBeforeUnmount} from 'vue';
onUnmounted(() => {
console.log('组件销毁');
});
onBeforeUnmount(() => {
console.log('组件即将销毁');
});
</script>
<template>
<h1>Header组件</h1>
</template>
7. toRef 和 toRefs
toRef:可以将响应式对象的某个属性转换为ref变量。
toRefs:可以将响应式对象的所有属性转换为ref对象。
<script setup>
import {toRef,toRefs,reactive} from 'vue'
const web = reactive({
url: 'https://blog.xy21lin.cn',
count:0
})
// toRef 第一个参数 是要转换的对象,第二个参数 是要转换的属性名
// let count = toRef(web, 'count')
// toRefs 参数 是要转换的对象,返回的是一个对象,对象的属性名和值都是响应式的 这里使用解构进行接收
let {url,count} = toRefs(web)
const addCount = () =>{
count.value++
}
</script>
<template>
<h1>count :{{ count }}</h1>
<h2>url :{{ url }}</h2>
<button @click="addCount">点赞</button>
</template>
8. Pinia
Pinia:是 Vue 3 官方推荐的状态管理库,它可以帮助我们管理应用的状态。
Pinia官方网址
可能还是有些不明白Pinia的作用,这里举个例子,以登陆为例:
- 使用Pinia创建一个userStore来集中管理用户的登陆状态和过期时间
- 用户登陆成功时,设置userStore中用户登陆状态为已登录,并设置过期时间
- 用户登出时,设置userStore中用户登陆状态为未登录,并清除过期时间
8.1 Pinia 和 组件通信 的区别
虽然Vue提供的父传子、子传父以及跨组件通信也可以用于状态共享,但在大型项目中,随着组件数量的增加,会导致以下问题:
- 组件之间传递大量的props,会使项目变得非常繁琐和难以维护
- 非父子组件间过度依赖provide/inject,使状态散落在各个组件之间
但是使用了Pinia 后可以解决的问题:
- 全局状态管理:所有组件都可以访问和修改状态,而不用在每个组件内部进行状态管理
- 简化组件之间的通信:避免在组件之间传递大量的props
- 状态持久化:可以将应用程序的状态保存到本地存储中,在应用程序重启后会保留状态,对于登录等场景非常有用
总的来说,Pinia可以处理大型项目中复杂的状态管理需求,而父传子、子传父以及跨组件通信,可以解决一些简单的状态传递问题,更适合小型项目
8.2 Pinia 和 localStorage的区别
虽然localStorage可以用于存储数据,但它只能存储字符串,不能存储复杂的数据结构,并且只能存储少量的数据,且有大小限制,通常为5MB左右
Pinia可以存储任意数据类型,包括对象、数组等,没有大小限制,可以存储大量的数据。并且可以持久化存储数据,可以用于存储登录状态等复杂数据。
总的来说,对于复杂的状态管理需求,使用Pinia是更好的选择,而对于简单的状态管理需求,使用localStorage是更简单的解决方案
8.3 安装并使用Pinia
项目目录下打开终端,输入以下命令:
npm install pinia
安装完成后打开
package.json文件,可以看到以下代码,可以看到pinia版本为3.0.1:
// ...
"dependencies": {
"pinia": "^3.0.1",
"vue": "^3.5.13"
},
// ...
在
main.js中导入createPinia方法用于创建Pinia实例:
import {createPinia} from 'pinia'
// 创建pinpa实例,在应用中集中管理状态
const pinia = createPinia()
// 创建vue实例,注册pinia实例到vue应用中,挂载到#app元素上
createApp(App).use(pinia).mount('#app')
接下来,在
src目录下创建文件夹stores,并在其中创建js文件web.js
import {reactive,ref} from 'vue'
import {defineStore} from 'pinia'
// 定义一个store,第一个参数是store的唯一标识,建议使用文件名,第二个参数使用setup()函数 或 option 对象,这里使用setup()函数
// 使用export const useXXXStore = defineStore('web',()=>{...}) 定义一个store,并导出一个useXXXStore函数,调用该函数可以获取到该store的实例
// useXXXStore 命名规范,在标识前加use,后加store
export const useWebStore = defineStore('web',()=>{
const users = ref(100) // 定义一个响应式变量 用户数量,初始值为100
const web = reactive({
title:"枫月Blog",
url:"https://blog.xy21lin.cn/"
})
// 定义增加用户的方法
const userAdd = () =>{
users.value++
}
//返回定义的数据
return{
users,
web,
userAdd
}
})
在
App.vue中导入useWebStore函数,并在template标签中使用{{ }}插值语法,将web数据渲染到页面上:
<script setup>
//导入useWebStore函数
import {useWebStore} from './stores/web.js'
// 获取webStore实例
const webStore = useWebStore()
console.log(webStore)
</script>
<template>
<h1>users :{{ webStore.users }}</h1>
<h2>url :{{ webStore.web.url }}</h2>
<button @click="webStore.userAdd">添加用户</button>
</template>
8.4 Pinia持久化存储插件
Pinia提供了持久化存储插件,可以将Pinia的状态存储到本地存储中,也就是说在应用程序重启后会保留状态。
Pinia持久化存储插件网址
首先,安装Pinia持久化存储插件,在项目目录终端下输入命令:
npm i pinia-plugin-persistedstate
同Pinia一样,安装完成后,在package.json文件中可以看到插件的版本号,这里是
"pinia-plugin-persistedstate": "^4.2.0"
然后,在main.js文件中导入createPersistedState方法,并注册到Pinia实例中:
import { createApp } from 'vue'
import {createPinia} from 'pinia'
//从 pinia-plugin-persistedstate 模块中导入 piniaPluginPersistedstate
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
// 创建pinpa实例,在应用中集中管理状态
const pinia = createPinia()
// 注册 pinia-plugin-persistedstate 插件到pinia实例上
pinia.use(piniaPluginPersistedstate)
// 创建vue实例,注册pinia实例到vue应用中,挂载到#app元素上
createApp(App).use(pinia).mount('#app')
最后,在
stores文件夹下的需要进行持久化存储的仓库中,进行配置:
import {reactive,ref} from 'vue'
import {defineStore} from 'pinia'
// 定义一个store,第一个参数是store的唯一标识,建议使用文件名,第二个参数使用setup()函数 或 option 对象,这里使用setup()函数
// 使用export const useXXXStore = defineStore('web',()=>{...}) 定义一个store,并导出一个useXXXStore函数,调用该函数可以获取到该store的实例
// useXXXStore 命名规范,在标识前加use,后加store
export const useWebStore = defineStore('web',()=>{
const users = ref(100) // 定义一个响应式变量 用户数量,初始值为100
const web = reactive({
title:"枫月Blog",
url:"https://blog.xy21lin.cn/"
})
// 定义增加用户的方法
const userAdd = () =>{
users.value++
}
//返回定义的数据
return{
users,
web,
userAdd
}
},
// 主要就是这一段 注意位置,是在defineStore第三个参数的位置
{
persist:true // 开启持久化
})
这样,在应用程序重启后,
web仓库的状态会被存储到本地存储中。可以在App.vue中进行测试,刷新页面后,可以看到web仓库的状态被保留。浏览器f12,点击应用,本地存储空间,可以看到web字段,里面存储了web仓库的状态。
注意:pinia持久化插件也是存储到localStorage中,为什么不直接使用localStorage?
- 自动状态同步:持久化插件自动将Pinia的状态存储到localStorage中,无需手动处理状态的读取和写入
- 易用性:无需手动处理localStorage的键值对存储、数据转换等繁琐过程
- 与Vue组件状态紧密集成,持久化插件与Vue组件的响应式数据完美结合,当状态改变时,依赖这些状态的组件会自动更新视图,与仅仅从localStorage中读取静态数据相比更加灵活和强大