Vue的简介
官网文档
Vue开发前的准备
node -v要大于15.0的版本
在指定目录下的命令行创建vue项目
npm init vue@latest
填写项目名称,然后全部回车即可,最后运行下面三行命令进行初始化
开发环境
推荐的IDE配置是VScode + Volar扩展插件
Vue项目目录结构
1 .vscode --- VSCode工具配置文件夹
2 node_modules --- Vue项目运行依赖文件夹
3 public --- 资源文件夹(浏览器图标)
4 src --- 源码文件夹
5 .gitignore --- git忽略文件
6 index.html --- 入口的HTML文件
7 package.json --- 信息描述文件
8 README.md --- 注释文件
9 vite.config.js --- Vue的配置文件
模板语法
先删除components和assets中所有文件,再将App.vue文件几乎全删,只剩一对</template>和</script>。
文本插值
最基本的数据绑定形式是文本插值,它使用的是“双大括号”语法:
<template>
<h3>模板语法</h3>
<p>{{ msg }}</p>
<p>{{ hello }}</p>
</template>
<script>
export default{
data(){
return{
msg:"神奇的语法",
hello:"Hello World!"
}
}
}
</script>
使用JS表达式
每绑定仅支持单一表达式,也就是一段能够被求值的js代码,一个简单的合法判断语句是可以在写在return后面的:
<template>
<p>{{ number+1 }}</p>
<p>{{ ok? "Yes" : "No" }}</p>
<p>{{ msg.split("").reverse().join("") }}</p>
</template>
<script>
export default{
data(){
return{
number:10,
ok:true,
msg:"大家好"
}
}
}
</script>
无效
<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}
<!-- 条件控制不支持,请使用三元表达式 -->
{{ if(ok){return msg} }}
原始HTML
双大括号将会将数据插值为纯文本,而不是HTML,若想插入HTML,你需要使用v-html指令:’
<template>
<p>{{ rawHtml }}</p>
<p v-html="rawHtml"></p>
</template>
<script>
export default{
data(){
return{
rawHtml:"<a href='http://itbaizhan.com'>百战程序员</a>"
}
}
}
</script>
属性绑定
双大括号不能在HTML属性中使用,想要变量绑定一个属性的属性值,应该使用v-bind指令
<template>
<div>{{ dynamicId }}</div>
<div v-bind:class="dynamicClass"></div>
</template>
<script>
export default{
data(){
return{
dynamicId:"appid",
dynamicClass:"appclass"
}
}
}
</script>
此时第二个div标签中class属性值为appclass
如果绑定的属性值为null,就等于没有添加该属性
简写
省去v-bind,直接:属性='变量'
<div :class="dynamicClass"></div>
布尔类型
<template>
<button :disabled="isButtonDisabled">Button</button>
</template>
<script>
export default{
data(){
return{
isButtonDisabled:false
}
}
}
</script>
disabled属性默认是true不能点击的,为false为可点击的。
动态绑定多个值
如果你有像这样的一个包含多个属性的js对象
<template>
<div v-bind="objectOfAttrs"></div>
</template>
<script>
export default{
data(){
return{
objectOfAttrs:{
id:"appId",
class:"appClass"
}
}
}
}
</script>
1、可以用js对象存储标签中要添加的多个属性及属性值,然后直接动态绑定到标签中
2、js对象中属性名要与标签中的属性名保持一致
3、格式为v-bind='变量名'
条件渲染
在Vue中,提供了条件渲染,这类似于js中的条件语句
v-if
v-else
v-else-if
v-show
v-if
v-if指令用于条件性地渲染一块内容,这块内容只会在指令的表达式返回真值时才被渲染
v-开头的指令可以直接变量绑定
<template>
<h3>条件渲染</h3>
<div v-if="flag">你能看见我吗?</div>
</template>
<script>
export default{
data(){
return{
flag:false
}
}
}
</script>
v-else
你也可以使用v-else为v-if添加一个‘else区块’
<template>
<div v-if="flag">你能看见我吗</div>
<div v-else>你还是看看我把</div>
</template>
<script>
export default{
data(){
return{
flag:true
}
}
}
</script>
v-else-if
顾名思义,v-else-if提供的时相应于v-if的''else if区块',它可以连续多次重复使用
<template>
<div v-if="type == 'A'">A</div>
<div v-else-if="type == 'B'">B</div>
<div v-else-if="type == 'C'">C</div>
<div v-else>NOT A/B/C</div>
</template>
<script>
export default{
data(){
return{
type:'E'
}
}
}
</script>
v-show
另一个可以用来按条件显示一个元素的指令是v-show,其用法基本一致
<template>
<div v-show="flag">你能看见我吗?</div>
</template>
<script>
export default{
data(){
return{
flag:false
}
}
}
</script>
v-if VS v-show
v-if 是真实的按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建
v-if 是惰性的,如果在初次渲染时条件值为 false,则不会做任何事,条件区块只有当条件首次变为 true 时才被渲染
相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好,如果在运行时绑定条件很少改变,则 v-if 会更合适
列表渲染
我们可以使用 v-for 指令基于一个数组来渲染一个列表,v-for 指令基于一个数组来渲染一个列表,v-for 指令的值需要使用 item in items 形式的特殊语法。其中 items 是源数据的数组,而 item 是迭代项的别名。
<template>
<p v-for="item in names">{{ item }}</p>
</template>
<script>
export default {
data(){
return{
names:["百战程序员","尚学堂","IT"]
}
}
}
</script>
复杂数据
大多数情况,我们渲染的数据来源于网络请求,也就是JSON格式
<template>
<div v-for="item in result">
<p>{{ item.id }}:{{ item.title }}</p>
</div>
</template>
<script>
export default {
data(){
return{
result:[{
"id":1,
"title":"aaa"
},{
"id":2,
"title":"bbb"
},{
"id":3,
"title":"ccc"
}]
}
}
}
</script>
v-for 也支持使用可选的第二个参数表示当前项的位置索引 (item,index) in items
<template>
<div v-for="(item,index) in names">{{ index+1 }}:{{ item }}</div>
</template>
<script>
export default {
data(){
return{
names:["百战程序员","黑马程序员","尚学堂"]
}
}
}
</script>
v-for 与对象
你也可以使用 v-for 来遍历一个对象的所有属性 (value,key,index) in userInfo
<template>
<div v-for="(value,key,index) in userInfo">{{ index }},{{ key }},{{ value }}</div>
</template>
<script>
export default {
data(){
return{
userInfo:{
name:"itheima",
age:20,
sex:"男",
phone:111111
}
}
}
}
</script>
通过key管理状态
在 v-for 中,key 是每一个元素唯一的索引,是不会改变的。
<template>
<h3>key属性添加到v-for中</h3>
<p v-for="(item,index) in names" :key="index">{{ item }}</p>
</template>
<script>
export default{
data(){
return{
names:["百战程序员","尚学堂","IT"]
}
}
}
</script>
key的来源
请不要使用 index 作为 key 的值,我们要确保每一条数据的唯一索引不会发生变化
<template>
<h3>key属性添加到v-for中</h3>
<div v-for="(item) in userInfo" :key="item.id">
<p>{{ item.id }},{{ item.name }},{{ item.age }}</p>
</div>
</template>
<script>
export default{
data(){
return{
userInfo:[{
"id":1,
"name":"aaa",
"age":20
},{
"id":2,
"name":"bbb",
"age":22
},{
"id":3,
"name":"ccc",
"age":18
}]
}
}
}
</script>
事件处理
我们可以使用 v-on 指令(简写@)来监听DOM事件,并在事件触发时执行对应 js ,用法:v-on:click="methodName" 或 @click="handler"
事件处理器的值可以是
内联事件处理器:事件被触发时执行的内联 js 语句(与onclick类似)
方法事件处理器:一个指向组件上定义的方法的属性名或是路径
内联事件处理器
内联事件处理器通常用于简单场景
<template>
<button @click="count++">点击加1</button>
<p>count:{{ count }}</p>
</template>
<script>
export default {
data(){
return{
count:0
}
}
}
</script>
方法事件处理器
<template>
<button @click="addCount">add</button>
<p>{{ count }}</p>
</template>
<script>
export default{
data(){
return{
count : 0
}
},
// 日后所有的方法都放在这里
methods:{
addCount(){
this.count++
}
}
}
</script>
事件传参
事件参数可以获取 event 对象和通过事件传递数据
获取 event 对象
<template>
<button @click="addCount">add</button>
<p>{{ count }}</p>
</template>
<script>
export default{
data(){
return{
count : 0
}
},
// 日后所有的方法都放在这里
methods:{
addCount(e){
// Vue中的event对象,就是原生js的Event对象,e.target就是标签button对象
console.log(e.target.innerHTML = "Add" + this.count)
this.count++
}
}
}
</script>
传递参数
<template>
<h3>事件传参</h3>
<div v-for="(item,index) in names" :key="index">
<button @click="getNameHandler(item)">{{ item }}</button>
</div>
</template>
<script>
export default{
data(){
return{
names:["iwen","ime","frank"]
}
},
// 日后所有的方法都放在这里
methods:{
getNameHandler(name){
console.log(name)
}
}
}
</script>
传递参数过程中获取 event 对象,在传入参数时加一个 $event 就可以了
<template>
<h3>事件传参</h3>
<div v-for="(item,index) in names" :key="index">
<button @click="getNameHandler(item,$event)">{{ item }}</button>
</div>
</template>
<script>
export default{
data(){
return{
names:["iwen","ime","frank"]
}
},
// 日后所有的方法都放在这里
methods:{
getNameHandler(name,e){
console.log(name)
console.log(e.target.innerHTML)
}
}
}
</script>
事件修饰符
在处理事件和调用 event.preventDefault() 或 event.stopPropagation() 是很常见的。尽管我们可以在直接在方法内调用,但如果方法能专注于数据逻辑而不用区处理DOM事件的细节会更好
为解决这一问题,Vue为 v-on 提供了事件修饰符,常用有以下几个:
.stop
.prevent
.once
.enter
...
具体参考
地址:https://cn.vuejs.org/guide/essentials/event-handling.html#event-modifiers
阻止默认事件和冒泡事件
<template>
<h3>事件修饰符</h3>
<!-- 阻止跳转默认事件 -->
<a @click.prevent="clickHandle" href="https://itbaizhan.com">百战程序员</a>
<!-- 默认点击触发子元素事件,然后冒泡到父元素事件 -->
<!-- 可以在子元素@click加.stop,能够阻止事件冒泡到父元素 -->
<div @click="clickDiv">
<p @click.stop="clickP">
测试冒泡
</p>
</div>
</template>
<script>
export default {
data(){
return{
}
},
methods:{
clickHandle(e){
// 阻止默认事件
// e.preventDefault();
console.log("点击了")
},
clickDiv(){
console.log("Div")
},
clickP(e){
// 阻止事件冒泡到父元素
// e.stopPropagation();
console.log("P")
}
}
}
</script>
数组变化侦测
原数组变更
Vue 能够侦听响应式数组的变更方法,并在题目被调用时触发相关的更新。这些变更方法包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
<template>
<h3>数组变化侦听</h3>
<button @click="addListHandle">添加数据</button>
<ul>
<li v-for="(item,index) in names" :key="index">{{ item }}</li>
</ul>
</template>
<script>
export default {
data() {
return{
names:["iven","ime","frank"]
}
},
methods:{
addListHandle(e){
// 往原数组加入元素,原数组改变,所以渲染出来也要变
this.names.push("Alice")
}
}
}
</script>
替换一个数组
变更方法,顾名思义,就是会对调用它们的原数组进行变更。相对地,也有一一些不可变方法,例如 filter(),concat() 和 slice(),这些都不会更改原数组,而总是返回一个新数组。当遇到的是非变更方法时,我们需要将旧的数组替换为新的、
<template>
<h3>数组变化侦听</h3>
<button @click="addListHandle">添加数据</button>
<ul>
<li v-for="(item,index) in names" :key="index">{{ item }}</li>
</ul>
</template>
<script>
export default {
data() {
return{
names:["iven","ime","frank"]
}
},
methods:{
addListHandle(e){
// 在原数组后面拼接一个数组并返回一个新的数组,赋值给原数组,从而更新了原数组
this.names = this.names.concat(["Alice"])
}
}
}
</script>
计算属性
<template>
<h3>{{ itbaizhan.name }}</h3>
<!-- <p>{{ itbaizhan.content.length > 0 ? "YES":"NO" }}</p> -->
<p>{{ itbaizhanContent }}</p>
</template>
<script>
export default {
data(){
return{
itbaizhan:{
name:"百战程序员",
content:["前端","Java","python"]
}
}
},
// 计算属性
computed:{
itbaizhanContent(){
return this.itbaizhan.content.length > 0 ? "YES":"NO"
}
}
}
</script>
计算属性缓存 vs 方法(未写)
Class绑定
绑定对象
<template>
<!-- 一个标签可以绑定多个class属性值 -->
<!-- 当isActive为true才绑定active属性值,当hasError为true才绑定text-danger属性值 -->
<p :class="{'active':isActive,'text-danger':hasError}">Class文本绑定</p>
</template>
<script>
export default{
data(){
return{
isActive:true,
hasError:true
}
}
}
</script>
<style>
.active{
color:red
}
.text-danger{
font-size: 25px;
}
</style>
多个对象绑定
<template>
<p :class="classObject">Class文本绑定2</p>
</template>
<script>
export default{
data(){
return{
classObject:{
"active":true,
"text-danger":true
}
}
}
}
</script>
<style>
.active{
color:red
}
.text-danger{
font-size: 25px;
}
</style>
绑定数组
<template>
<p :class="[arrayActive,arrayError]">Class文本绑定3</p>
</template>
<script>
export default{
data(){
return{
arrayActive:'active',
arrayError:'text-danger'
}
}
}
</script>
<style>
.active{
color:red
}
.text-danger{
font-size: 25px;
}
</style>
如果你也想在数组中有条件地渲染某个class,你可以使用三元表达式
<template>
<p :class="[isActive?arrayActive:'',hasError?arrayError:'']">Class文本绑定4</p>
</template>
<script>
export default{
data(){
return{
isActive:true,
hasError:true,
arrayActive:'active',
arrayError:'text-danger'
}
}
}
</script>
<style>
.active{
color:red
}
.text-danger{
font-size: 25px;
}
</style>
数组里面嵌入对象
<template>
<p :class="[isActive?arrayActive:'',{'text-danger':hasError}]">Class文本绑定5</p>
</template>
<script>
export default{
data(){
return{
isActive:true,
hasError:true,
arrayActive:'active',
arrayError:'text-danger'
}
}
}
</script>
<style>
.active{
color:red
}
.text-danger{
font-size: 25px;
}
</style>
Style绑定
绑定对象
<template>
<p :style="{color:activeColor,fontSize:activeFontSize}">Style样式1</p>
<p :style="styleObject">Style样式2</p>
</template>
<script>
export default{
data(){
return{
activeColor:'green',
activeFontSize:'30px',
styleObject:{
color:'red',
fontSize:'50px'
}
}
}
}
</script>
绑定数组
<template>
<p :style="[styleObject]">Style样式3</p>
</template>
<script>
export default{
data(){
return{
styleObject:{
color:'red',
fontSize:'50px'
}
}
}
}
</script>
侦听器
我们可以使用 watch 选项在每次响应式属性发生变化时触发一个函数
<template>
<h3>侦听器</h3>
<p>{{ msg }}</p>
<button @click="updateHandle">修改数据</button>
</template>
<script>
export default {
data() {
return{
msg:'hello'
}
},
methods:{
updateHandle(){
this.msg = 'world'
}
},
watch:{
// 函数名要与变量名一致
// newValue 为修改后的值
// oldValue 为修改前的值
msg(newValue,oldValue){
// 数据发生变化时,自动执行的逻辑
console.log(newValue,oldValue)
}
}
}
</script>
注意:函数名要与侦听变量对象名保持一致。
表单输入绑定
在前端处理表单时,我们常常需要将表单输入框的内容同步给 js 中响应的值,手动连接值绑定和更改监听器可能会很麻烦,就是说,表单输入的数据能够实时获取到,而不仅仅是提交时才获取,v-model 指令帮我们简化了这一步骤, v-model 能将数据实时赋值给变量
输入框
<template>
<h3>表单输入绑定</h3>
<form>
<input type="text" v-model="msg">
<p>{{ msg }}</p>
</form>
</template>
<script>
export default{
data(){
return{
msg:""
}
}
}
</script>
复选框
单一的复选框,绑定布尔类型值
<template>
<h3>表单输入绑定</h3>
<form>
<input type="text" v-model="msg">
<p>{{ msg }}</p>
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
</form>
</template>
<script>
export default{
data(){
return{
msg:"",
checked:null
}
}
}
</script>
修饰符
v-model 也提供了修饰符:.lazy、.number、.trim
.lazy
默认情况下,v-model 会在每次 input 事件后更新数据。你可以添加 .lazy 修饰符来改为在每次 change 事件后更新数据
<template>
<h3>表单输入绑定</h3>
<form>
<input type="text" v-model.lazy="msg">
<p>{{ msg }}</p>
</form>
</template>
<script>
export default{
data(){
return{
msg:""
}
}
}
</script>
回车后才获取数据
模板引用
虽然 Vue 的声明性渲染模型为你抽象了大部分对DOM的直接操作,但在某些情况下,我们需仍然需要直接访问底层DOM元素。要实现这一点,我们可以使用特殊的 ref 属性
挂载结束后引用都会被披露在 this.$refs 之上
<template>
<div ref="container" class="container">容器</div>
<input type="text" ref="username"><br>
<button @click="getElementHandle">获取元素</button>
</template>
<script>
export default {
data() {
return{
content:'内容'
}
},
methods:{
getElementHandle(){
console.log(this.$refs.container)
console.log(this.$refs.username)
this.$refs.container.innerHTML = "容器66"
this.$refs.username.value = '666'
}
}
}
</script>
ref 与 event 区别
event 是操作本身事件触发的DOM(标签)元素
ref 是操作其他的DOM(标签)元素
如果没有特别需求,最好不要操作DOM元素
组件组成
组件最大优势是可复用性
当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件(简称SFC)
组件引入
方式一:
- 引入组件
- 注入组件
- 使用组件
<template>
<!-- 第三步:使用组件 -->
<!-- 两种标签显示方法都可以 -->
<MyComponent />
<my-component />
</template>
<script>
// 第一步:引入组件
import MyComponent from './components/MyComponent.vue';
export default{
// 第二步:注入组件
components:{
MyComponent:MyComponent
}
}
</script>
<style scoped>
</style>
方式二:(更简化,推荐)
- <script>标签添加setup属性
- 引入组件
- 使用组件
<template>
<!-- 使用组件:两种使用方法都可以 -->
<MyComponent />
<my-component />
</template>
<script setup>
import MyComponent from './components/MyComponent.vue';
</script>
<style scoped>
</style>
注意添加setup属性后不能再写export default{},但可以再写一个<script>,再另外一个<script>标签中写export default{}
<style>标签中scoped表示只在当前组件中生效,不加scoped表示在全局中生效
组件嵌套
组件允许我们将 UI 划分为独立的,可重用的部分,并且可以将每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构
创建组件及引用关系
Header.vue
<template>
<h3>header</h3>
</template>
<style scoped>
h3{
width: 100%;
height: 100px;
border: 5px solid #999;
text-align: center;
line-height: 100px;
box-sizing: border-box;
}
</style>
Main.vue
<template>
<div class="main">
<h3>Main</h3>
<Article />
<Article />
</div>
</template>
<script setup>
import Article from './Article.vue';
</script>
<style scoped>
.main{
float: left;
width: 70%;
height: 600px;
border: 5px solid #999;
box-sizing: border-box;
}
</style>
Aside.vue
<template>
<div class="aside">
<h3>Aside</h3>
<Item />
<Item />
<Item />
</div>
</template>
<script setup>
import Item from './Item.vue';
</script>
<style scoped>
.aside{
float: right;
width: 30%;
height: 600px;
border: 5px solid #999;
box-sizing: border-box;
}
</style>
Article.vue
<template>
<h3>Article</h3>
</template>
<style scoped>
h3{
width: 80%;
margin: 0 auto;
text-align: center;
line-height: 100px;
box-sizing: border-box;
margin-top: 50px;
background: #999;
}
</style>
Item.vue
<template>
<h3>Item</h3>
</template>
<style scoped>
h3{
width: 80%;
margin: 0 auto;
text-align: center;
line-height: 100px;
box-sizing: border-box;
margin-top: 10px;
background: #999;
}
</style>
App.vue
<template>
<Header />
<Main />
<Aside />
</template>
<script setup>
import Aside from "./components/Aside.vue";
import Header from "./components/Header.vue";
import Main from "./components/Main.vue";
</script>
组件注册方式
一个Vue组件在使用前需要先被“注册”,这样Vue才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册
全局注册
全局注册后,所有地方都可以引入该组件
- 在main.js文件中注册
- 引入组件
- 注册组件
import { createApp } from 'vue'
import App from './App.vue'
// 第一步:先引入组件
import Header from './components/Header.vue'
const app = createApp(App)
// 第二步:在这中间写组件的注册
// 左边是使用该组件的标签名,右边是import后面引入名
app.component("MyHeader",Header)
app.mount('#app')
局部注册
全局注册虽然很方便,但有以下几个问题:
- 全局注册,但并没有被使用的组件无法在生产打包时被自动移除,如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的JS文件中
- 全局注册在大型项目中使用的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性
局部注册需要使用components选项
<template>
<!-- 第三步:使用组件 -->
<!-- 两种标签显示方法都可以 -->
<MyComponent />
<my-component />
</template>
<script>
// 第一步:引入组件
import MyComponent from './components/MyComponent.vue';
export default{
// 第二步:注入组件
components:{
MyComponent:MyComponent
}
}
</script>
之前写的三步就是局部注册,建议用setup属性来简化注册过程。
组件传递数据_props
组件与组件之间不是完全独立的,而是有交集的,那就是组件与组件之间是可以传递数据的
传递数据的解决方案就是 props
使用组件时,可以添加属性和属性值来传递数据,属性名为变量名,属性值为变量值
<template>
<h3>Parent</h3>
<ChildVue data="Parent数据" />
</template>
<script setup>
import ChildVue from "./Child.vue";
</script>
被引用的组件可以通过props来获取传递数据,数组里面的字符串为传过来的变量名,需要保持一致
<template>
<h3>Child</h3>
<p>{{ data }}</p>
</template>
<script>
export default{
data(){
return{
}
},
props:["data"]
}
</script>
传递的数据是没有数量限制的!
动态数据的传递 v-bind
Parent.vue
<template>
<h3>Parent</h3>
<ChildVue :data="msg"/>
<p>Parent:{{ msg }}</p>
</template>
<script>
import ChildVue from "./Child.vue";
export default{
components:{
ChildVue
},
data(){
return{
msg:"hello world !"
}
}
}
</script>
Child.vue
<template>
<h3>Child</h3>
<p>Child:{{ data }}</p>
</template>
<script>
export default{
props:["data"]
}
</script>
注意事项:
props 传递数据,只能从父级传递到子级,不能反其道而行!
组件传递多种数据类型
不同类型数据的传递
Parent.vue
<template>
<h3>Parent</h3>
<ChildVue :title="msg" :age="age" :names="names" :userInfo="userInfo"/>
</template>
<script>
import ChildVue from "./Child.vue";
export default{
components:{
ChildVue
},
data(){
return{
// 字符串
msg:"hello world !",
// 数字
age:20,
// 数组
names:["iwen","ime","frank"],
// 对象
userInfo:{
name:'wl',
age:21
}
}
}
}
</script>
Child.vue
<template>
<h3>Child</h3>
<p>{{ title }}</p>
<p>{{ age }}</p>
<p v-for="(item,index) in names" :key="index">{{ item }}</p>
<p>{{ userInfo.name }} : {{ userInfo.age }}</p>
</template>
<script>
export default{
props:["title","age","names","userInfo"]
}
</script>
组件传递Props校验
Vue 组件可以更细致地声明对传入的 props 的校验要求
props除了以数组表示,还可以以类表示
<template>
<h3>ComponentB</h3>
<p>{{ title }}</p>
<p>{{ age }}</p>
<P v-for="(item,index) in names" :key="index">{{ item }}</P>
</template>
<script>
export default{
props:{
title:{
type:String,
required:true
},
age:{
type:Number,
default:0
},
// 数组和字符串可以直接default,但是如果是数组和对象,必须通过工厂函数返回默认值
names:{
type:Array,
default(){
return ["空"]
}
}
}
}
</script>
- title、age、names为传递过来的参数,通过参数名匹配来获取
- type属性为传递参数的类型,若传递的参数类型与指定类型不一致,控制台会提醒
- default为默认值,若没有传入该参数,这该参数为默认值。但数组和对象的默认值必须是通过工厂函数返回默认值
- required为该参数的必要性,若值为true说明该参数必须传递
- 传递的参数只能读取,不能进行修改,修改时会报错的
组件事件
在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件
触发自定义事件的目的是组件之间传递数据
用子传父即被引用的组件向引用的组件传递数据
Child.vue
<template>
<h3>Child</h3>
<button @click="clickEventHandle">传递数据</button>
</template>
<script>
export default{
data(){
return{
msg:"Child传递的数据"
}
},
methods:{
clickEventHandle(){
// 自定义事件
this.$emit("someEvent",this.msg)
}
}
}
</script>
- 点击按钮触发元素事件
- 元素事件中通过this.$emit()自定义事件,第一个参数为自定义事件的事件名,第二个参数为传入的数据
Parent.vue
<template>
<h3>组件事件</h3>
<Child @someEvent="getHandle" />
<p>{{ msg }}</p>
</template>
<script>
import Child from './Child.vue'
export default{
data(){
return{
msg:""
}
},
components:{
Child
},
methods:{
getHandle(data){
console.log("触发了",data)
this.msg = data
}
}
}
</script>
- 通过子元素自定义事件someEvent触发了getHandle方法
- getHandle方法中data为传入的参数,通过this.msg = data来获取到传入的数据
- {{ msg }} 显示传入的数据
温馨提示
组件之间传递数据的方案:
- 父传子:props
- 子传父:自定义事件(this.$emit)
组件事件配合v-model使用
作用:被引用页面实时输入的内容,引用页面能够实时获取
SearchComponent.vue(被引用页面)
<template>
搜索:<input type="text" v-model="search">
<p>子:{{ search }}</p>
</template>
<script>
export default{
data(){
return{
search:''
}
},
// 监听器
watch:{
search(newValue,oldValue){
this.$emit('searchEvent',newValue)
}
}
}
</script>
Main.vue(引用页面)
<template>
<h3>Main</h3>
<search-component @searchEvent="getSearch" />
<p>父:{{ search }}</p>
</template>
<script>
import SearchComponent from "./SearchComponent.vue";
export default{
data(){
return{
search:''
}
},
components:{
SearchComponent
},
methods:{
getSearch(data){
this.search = data
}
}
}
</script>
<style scoped>
</style>
组件数据传递
我们之前讲解过了组件之间的数据传递,props 和 自定义事件 两种方式
- props:父传子
- 自定义事件:子传父
除了上述的方案,props 也可以实现子传父
- 父组件向子组件传递函数
- 子组件中调用函数时回传数据
- 父组件中执行函数获取数据
注意:由子组件调用函数,但函数的执行是在父组件中,所以父组件能获取传递的数据
ComponentA.vue(父组件)
<template>
<h3>ComponentA</h3>
<ComponentB :dataA="dataA" :onEvent="dataFn"/>
<p>{{ dataB }}</p>
</template>
<script>
import ComponentB from './ComponentB.vue';
export default{
data(){
return{
dataA:'父传子的数据',
// 获取子传父的数据
dataB:''
}
},
components:{
ComponentB
},
methods:{
dataFn(data){
this.dataB = data
}
}
}
</script>
ComponentB.vue(子组件)
<template>
<h3>ComponentB</h3>
<p>{{ dataA }}</p>
<p>{{ onEvent(dataB) }}</p>
</template>
<script>
export default{
data(){
return{
dataB:'子传父的数据'
}
},
props:{
dataA:{
type:String
},
onEvent:{
type:Function
}
}
}
</script>
透传 Attributes
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为props或emits的attribute或者v-on事件监听器。最常见的例子就是class,style和id
当一个组件以单个元素为根作渲染时,透传的attribute会自动被添加到根元素上
引用页面
<template>
<AttrComponent class="attr-container" />
</template>
<script>
import AttrComponent from "./components/AttrComponent.vue";
export default{
components:{
AttrComponent
}
}
</script>
<style scoped>
.attr-container{
color: red;
}
</style>
传递了class属性,属性值为attr-container
被引用页面
<template>
<h3>透传属性</h3>
</template>
<script>
export default{
inheritAttrs:false
}
</script>
class属性及属性值添加到根节点<h3>,若没有根节点,则都不添加传递过来的class属性及属性值
inheritAttrs:false可以拒绝透明传递
插槽Slot
我们可以在引用组件时往组件里面添加一些HTML片段,通过<slot>实现
引用组件
<template>
<SlotsBase>
<div>
<h3>插槽标题</h3>
<p>插槽内容</p>
</div>
</SlotsBase>
</template>
<script setup>
import SlotsBase from "./components/SlotsBase.vue";
</script>
往里组件里面添加了<div>、<h3>、<p>标签的HTML片段
被引用组件
<template>
<h3>插槽基础知识</h3>
<slot></slot>
</template>
通过<slot>标签来显示添加的HTML片段,还可以通过改变<slot>位置来改变显示的位置。
渲染作用域
添加的HTML动态绑定是在父元素中绑定的
<template>
<SlotsBase>
<div>
<h3>{{ title }}</h3>
<p>{{ content }}</p>
</div>
</SlotsBase>
</template>
<script>
import SlotsBase from "./components/SlotsBase.vue";
export default{
data(){
return{
title:'插槽标题',
content:'插槽内容'
}
},
components:{
SlotsBase
}
}
</script>
默认内容
在外部没有提供任何内容情况下,可以为插槽指定默认内容
<template>
<h3>插槽基础知识</h3>
<slot>默认内容</slot>
</template>
具名插槽
我们可以通过<template>标签添加多个HTML片段,每个片段通过v-slot设置一个名字,被引用组件可以通过<slot>标签,name属性指定片段名进行渲染。
引用组件
<template>
<SlotsBase>
<template v-slot:header>
<div>
<h3>{{ title }}</h3>
</div>
</template>
<template v-slot:main>
<div>
<p>{{ content }}</p>
</div>
</template>
</SlotsBase>
</template>
<script>
import SlotsBase from "./components/SlotsBase.vue";
export default{
data(){
return{
title:'插槽标题',
content:'插槽内容'
}
},
components:{
SlotsBase
}
}
</script>
添加了两个HTML片段,片段名分别为header和main
v-slot有对应的简写#,因此<template v-slot:header>可以简写为<template #header>
被引用组件
<template>
<h3>插槽基础知识</h3>
<slot name="header"></slot>
<slot name="main"></slot>
</template>
通过name属性指定片段进行渲染
插槽数据传递
子元素显示插槽时可以向父元素插槽HTML片段传递数据
<template>
<h3>Slots再续集</h3>
<slot :childMsg="childMsg"></slot>
</template>
<script>
export default{
data(){
return{
childMsg:'子元素数据'
}
}
}
</script>
父元素可以从v-slot="slotProps"获取slotProps对象,从slotProps对象中获取传递的数据
<template>
<SlotsA v-slot="slotProps">
<h3>{{ content }}-{{ slotProps.childMsg }}</h3>
</SlotsA>
</template>
<script>
import SlotsA from './components/SlotsA.vue';
export default{
data(){
return{
content:'测试内容'
}
},
components:{
SlotsA
}
}
</script>
具名插槽传递数据
子元素添加name属性
<template>
<h3>Slots再续集</h3>
<slot name="header" :childMsg="childMsg"></slot>
<slot name="main" :job="jobMsg"></slot>
</template>
<script>
export default{
data(){
return{
childMsg:'子元素数据',
jobMsg:'itbaizhan'
}
}
}
</script>
父元素要改变slotProps对象的获取方式
<template>
<SlotsA>
<template #header="slotProps">
<h3>{{ content }}-{{ slotProps.childMsg }}</h3>
</template>
<template #main="slotProps">
<p>{{ slotProps.job }}</p>
</template>
</SlotsA>
</template>
<script>
import SlotsA from './components/SlotsA.vue';
export default{
data(){
return{
content:'测试内容'
}
},
components:{
SlotsA
}
}
</script>
组件生命周期
生命周期函数
- 创建期:beforeCreate created
- 挂载期:beforeMounte mounted
- 更新期:beforeUpdate updated
- 销毁期:beforeUnmount unmounted
<template>
<h3>组件生命周期</h3>
<p>{{ msg }}</p>
<!-- 点击后,发生组件更新前,更新后 -->
<button @click="updateHandle">更新数据</button>
</template>
<script>
export default{
data(){
return{
msg:'更新之前'
}
},
methods:{
updateHandle(){
this.msg = '更新之后'
}
},
beforeCreate(){
console.log('组件创建之前')
},
created(){
console.log('组件创建之后')
},
beforeMount(){
console.log('组件渲染之前')
},
mounted(){
console.log('组件渲染之后')
},
beforeUpdate(){
console.log('组件更新之前')
},
updated(){
console.log('组件更新之后')
},
beforeUnmount(){
console.log('组件销毁之前')
},
unmounted(){
console.log('组件销毁之后')
}
}
</script>
生命周期应用
通过ref获取元素DOM结构
<template>
<h3>组件生命周期函数应用</h3>
<p ref="name">百战程序员</p>
</template>
<script>
export default{
beforeMount(){
console.log(this.$refs.name) //undefined
},
mounted(){
console.log(this.$refs.name) // 读到了
}
}
</script>
在生命周期mounted渲染后才能获取
模拟网络请求渲染数据
<template>
<h3>组件生命周期函数应用</h3>
<p ref="name">百战程序员</p>
<ul>
<li v-for="(item,index) in banner" :key="index">
<h3>{{ item.title }}</h3>
<p>{{ item.content }}</p>
</li>
</ul>
</template>
<script>
export default{
data(){
return{
// 存储网络请求的数据
banner:[]
}
},
created(){
// 获取网络请求的数据
this.banner = [{
'title':'itheima',
'content':'aaaa'
},{
'title':'itbaizhan',
'content':'bbb'
},{
'title':'itniuba',
'content':'ccc'
}]
}
}
</script>
也可以使用mounted生命周期函数
动态组件
有些场景会需要在两个组件来回切换,比如Tab界面
A、B两个组件
<template>
<h3>ComponentA</h3>
</template>
<template>
<h3>ComponentB</h3>
</template>
<template>
<component :is="tabComponent"></component>
<button @click="changeHandle">切换组件</button>
</template>
<script>
import ComponentB from "./components/ComponentB.vue";
import ComponentA from "./components/ComponentA.vue";
export default{
data(){
return{
tabComponent:'ComponentA'
}
},
components:{
ComponentA,
ComponentB
},
methods:{
changeHandle(){
this.tabComponent = this.tabComponent == 'ComponentA' ? 'ComponentB' : 'ComponentA'
}
}
}
</script>
通过<template>标签的is属性绑定显示的组件名,通过点击修改组件名来切换组件,默认ComponentA组件,点击后改变组件名来切换组件
组件保持存活
当使用<component is='...'>来在多个组件间切换时,被切换掉的组件会被卸载。我们可以通过<keep-alive>组件强制被切换掉的组件仍然保持“存活”状态
<template>
<h3>ComponentA</h3>
<p>{{ msg }}</p>
<button @click="updateHandle">更新数据</button>
</template>
<script>
export default{
data(){
return{
msg:'老数据'
}
},
beforeUnmount(){
console.log('组件A被卸载之前')
},
unmounted(){
console.log('组件A被卸载之后')
},
methods:{
updateHandle(){
this.msg = '新数据'
}
}
}
</script>
<template>
<keep-alive>
<component :is="tabComponent"></component>
</keep-alive>
<button @click="changeHandle">切换组件</button>
</template>
<script>
import ComponentB from "./components/ComponentB.vue";
import ComponentA from "./components/ComponentA.vue";
export default{
data(){
return{
tabComponent:'ComponentA'
}
},
components:{
ComponentA,
ComponentB
},
methods:{
changeHandle(){
this.tabComponent = this.tabComponent == 'ComponentA' ? 'ComponentB' : 'ComponentA'
}
}
}
</script>
不加<keep-alive>,当点击更新数据后,页面显示新数据,再次切换到该页面时,仍然显示老数据,这是因为切换时,之前的组件被删除了。加了<keep-alive>后,则显示新数据,因为之前组件未删除
异步组件
在大型项目中,我们可能需要拆分应用未更小的块,并仅在需要时再从服务器加载相关组件。Vue提供了defineAsynoComponent方法来实现此功能
<template>
<keep-alive>
<component :is="tabComponent"></component>
</keep-alive>
<button @click="changeHandle">切换组件</button>
</template>
<script>
// 引入defineAsyncComponent
import { defineAsyncComponent } from "vue";
import ComponentA from "./components/ComponentA.vue";
// 异步加载组件B,这点格式要注意
const ComponentB = defineAsyncComponent(()=>
import("./components/ComponentB.vue")
)
export default{
data(){
return{
tabComponent:'ComponentA'
}
},
components:{
ComponentA,
ComponentB
},
methods:{
changeHandle(){
this.tabComponent = this.tabComponent == 'ComponentA' ? 'ComponentB' : 'ComponentA'
}
}
}
</script>
在引入的地方需要进行相应改动
Vue引入第三方
Swiper 开源、免费、强大的触摸滑动插件
安装指定版本:npm install --save swiper@8.1.6
简单轮播图(手动滑动)
<template>
<swiper>
<swiper-slide>
<img src="./assets/1.png">
</swiper-slide>
<swiper-slide>
<img src="./assets/1.png">
</swiper-slide>
<swiper-slide>
<img src="./assets/1.png">
</swiper-slide>
</swiper>
</template>
<script>
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from 'swiper/vue';
// Import Swiper styles
import 'swiper/css';
export default {
components: {
Swiper,
SwiperSlide,
}
}
</script>
<style>
img{
width: 100%;
}
</style>
添加指示器
- 引入指示器
- 引入指示器样式
- data()的return那固定写法
- :modules="modules" :pagination="{clickable:true}"
<template>
<swiper :modules="modules" :pagination="{clickable:true}">
<swiper-slide>
<img src="./assets/1.png">
</swiper-slide>
<swiper-slide>
<img src="./assets/1.png">
</swiper-slide>
<swiper-slide>
<img src="./assets/1.png">
</swiper-slide>
</swiper>
</template>
<script>
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from 'swiper/vue';
// 引入指示器
import { Pagination } from 'swiper';
// Import Swiper styles
import 'swiper/css';
// 引入指示器CSS
import 'swiper/css/pagination'
export default {
components: {
Swiper,
SwiperSlide,
},
data(){
return{ // 指示器的固定写法
modules:[ Pagination ]
}
}
}
</script>
<style>
img{
width: 100%;
}
</style>
更多参考官方文档
Axios网络请求
Axios是一个基于promise的网络请求库
安装
Axios的应用需要单独安装的npm install --save axios
引入
组件中引入:import axios from 'axios'
全局引用:
import axios from 'axios'
const app = createApp(App)
app.config.globalProperties.$axios = axios
app.mount('#app')
// 在组件中调用
this.$axios
在传递参数时,需要使用querystring插件进行格式转换
- 安装依赖:
- 引入querystirng组件
- 转换参数格式:querystring.stringify({})
get请求方式
<script>
import axios from 'axios';
import qs from 'querystring';
export default{
mounted(){
axios({
method:'get',
// 指定请求路径并传递参数
url:'/api/index?' + qs.stringify({
// 左边是传递的参数名,右边是参数值
userId:Cookies.get("userId")
})
}).then(res=>{
// 成功后初始化数据
this.user = res.data.data
})
}
}
</script>
post请求方式
<script>
import axios from 'axios';
import querystring from 'querystring'
export default{
mounted(){
axios({
method:'post',
url:'http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php',
data:querystring.stringify({
// 左边是传递的参数名,右边是参数值
user_id:'iwen@qq.com',
password:'iwen123',
verification:'crfvw'
})
}).then(res=>{
console.log(res.data);
})
}
}
</script>
快捷方案
get请求方式
import axios from 'axios';
import querystring from 'querystring'
export default{
mounted(){
axios.get('http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php')
.then(res =>{
console.log(res.data)
})
}
}
</script>
post请求方式
<script>
import axios from 'axios';
import querystring from 'querystring';
mounted(){
axios.post('http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php',querystring.stringify({
user_id:'iwen@qq.com',
password:'iwen123',
verification:'crfvw'
})).then(res=>{
console.log(res.data)
})
}
</script>
Axios网络请求的封装
在src目录下创建文件夹utils,并创建文件request.js,用来存储网络请求对象axios
request.js
import axios from "axios";
import { error } from "console";
import { config } from "process";
import querystirng from 'querystring'
const errorHandle = (status,info) =>{
switch(status){
case 400:
console.log('语法错误');
break;
case 401:
console.log('服务器认证失败')
break;
default:
console.log(info)
break;
}
}
const instance = axios.create({
// 网络请求的公共配置
timeout:5000
})
// 拦截器
// 发送数据前
instance.interceptors.request.use(
// 成功函()
config =>{
if(config.methods === 'post'){
config.data = querystirng.stringify(config.data)
}
// config:包含着网络请求的所有信息
return config
},
error =>{
return Promise.reject(error)
}
)
// 获取数据之前
instance.interceptors.response.use(
// 成功函()
response =>{
return response.status == 200 ? Promise.resolve(response) : Promise.reject(response)
},
error =>{
const { response } = error
// 错误的处理才是我们需要最关注的
errorHandle(response.status,response.info)
}
)
export default instance;
创建api文件夹,并创建index.js文件和path.js文件
path.js文件中存放公共的请求路径
const base = {
baseUrl: 'aaaa',
chengpin: 'bbbb'
}
export default base
index.js文件中存放完整请求路径的函数
import axios from "axios";
import path from "./path";
const api = {
// 诚品详情地址
getChengpin(){
return axios.get(path.baseUrl + path.chengpin)
}
}
export default api
页面中引入并调用函数
import axios from "axios";
import path from "./path";
const api = {
// 诚品详情地址
getChengpin(){
return axios.get(path.baseUrl + path.chengpin)
}
}
export default api
Vue引入路由配置
在Vue中,我们可以通过vue-router路由管理页面之间的关系
在Vue中引入路由
第一步:安装路由
npm install --save vue-router
第二步:配置独立的路由文件
先创建router文件夹,再创建index.js文件
import {createRouter,createWebHashHistory} from "vue-router"
import HomeView from "@/views/HomeView.vue"
import AboutView from "@/views/AboutView.vue"
// 配置信息中需要页面的相关配置
const routes = [
{
// 指定HomeView页面的路径为/,即首页
path:"/",
component:HomeView
},
{
// 指定HomeView页面的路径为/about
path:"/about",
// 异步引入,更快
component:()=> import('../views/AboutView.vue')
}
]
const router = createRouter({
history:createWebHashHistory(),
routes
})
export default router;
createWebHashHistory()属性值l对应地址:localhost:5173/#/
createWebHistory()属性值对应地址:localhost:5173/
第三步:main.js中引入router
// 引入router
import router from './router'
// 使用router
app.use(router)
其中 const app = createApp(App)
第四步:App.vue进行演示
<template>
<router-view></router-view>
</template>
<script>
</script>
<router-view>相当于引入首页 / 组件,作为程序主入口
<router-link>路由跳转
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
该标签是页面公共的,常用作页面顶部导航栏
路由传递参数
第一步:在路由配置中指定参数的key
{
path:"/news/:name",
component:()=> import('../views/NewView.vue')
}
第二步:在跳转过程中携带参数
<router-link class="box" to="/news/百度">News</router-link>
第三步:在详情页面读取路由携带的参数
<p> {{ $route.params.name }}</p>