1. vue3

1. vue3

明廷盛 嘻嘻😁

第一章 Hello Vue3

第一节 导入vue的两种方式

1.1.1 CDN导入

<body>
<div id="hh"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <!--通过这一行导入vue3-->
<script>
const app = Vue.createApp({
template: '<h1>hello vue</h1>'
})
app.mount("#hh")
</script>
</body>

1.1.2 本地导入

第二节 计数器的实现

1.2.1 传统计数器实现

<body>
<!--View-->
<h2 id="hh"></h2>
<button id="b1">+1</button>
<button id="b2">-1</button>

<!--Controller-->
<script>
let h2Element = document.querySelector("#hh");
h2Element.innerHTML = 100; // Module
let increase = document.querySelector("#b1");
let decrease = document.querySelector("#b2");
increase.addEventListener("click", () => {
h2Element.innerHTML++;
});
decrease.addEventListener("click", () => {
h2Element.innerHTML--;
});
</script>
</body>

1.2.2 Vue3实现计数器

  • MVVM设计模式
    image.png|625
<body>
<div id="hh"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
Vue.createApp({
// PART1: 模板
template: `
<h2 id="hh">{{message}}</h2>
<button id="b1" @click="increase">+1</button>
<button id="b2" @click="decrease">-1</button>
`,
// PART2: 数据
data: function () {
return {
message: "Hello Vue3 Counter",
counter: 100,
};
},
// PART3: 方法
methods: {
increase() {
this.message = this.counter++;
},
decrease() {
this.message = this.counter--;
},
},
}).mount("#hh");
</script>
</body>

第三节 分离template

1.3.1 使用HTML的template标签

  • 注意⚠️:一定要注意对应关系, ①template中的hh是指代原来该写在``中的内容 ②mount是Vue渲染到那个标签的内容;
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app"></div> <!--对应mount-->

<template id="hh"> <!--替代原来的template:``-->
<h2>{{counter}}</h2>
<button @click="increase">+1</button>
<button @click="decrease">-1</button>
</template>

<script>
Vue.createApp({
template: "#hh", // STEP1:谁是template?
data: function () {
return {counter: 100,};
},
methods: {
increase() {this.counter++;},
decrease() {this.counter--;},
},
}).mount("#app"); // STEP2:挂载到哪里
</script>
</body>

第四节 VS Code snippet代码片段配置

见VSCODE使用心得

第二章 基本语法

官网指令apilink

第一~四
image.png|525
第五~八
image.png|500

第一节 基础指令

2.1.1 Mustache语法

  • 基本用法:双大括号{{ }}用于显示数据
  • 示例:
<template id="hh">
<!-- 1.mustache基本使用 -->
<h1>{{message}}</h1>
<!-- 2.可以是一个表达式 -->
<p>{{100*20/2}}</p>
<!-- 3.可以调用函数 -->
<p>{{message.split(" ").reverse().join(" ")}}</p>
<p>{{resverMessage()}}</p>
<!-- 4.三元运算法 -->
<p>{{flag?"True Vue3":"Fack Vue3"}}</p>
<button @click="flag=!flag">tf</button>
</template>
<!---错误用法示例: 可以是上面的表达式, 但不能是语句, 赋值语句,判断语句,循环语句都不可以 -->

<!-- 错误1:赋值语句不允许 -->
<h2>{{ var name = "abc" }}</h2>

<!-- 错误2:判断语句不允许 -->
<h2>{{ if(isShow) { return "哈哈哈" } }}</h2>

<!-- 错误3:循环语句不允许 -->
<h2>{{ for(let i=0;i<10; i++) console.log(i) }}</h2>

<!-- 错误4:函数定义不允许 -->
<h2>{{ function test() { return "test" } }}</h2>

<!-- 错误5:try-catch语句不允许 -->
<h2>{{ try { riskyOperation() } catch(e) { console.log(e) } }}</h2>

2.1.2 v-once

  • 定义: 仅渲染元素和组件一次(包括该子结点),并跳过之后的更新。
  • 示例:
<template id="hh">
<h1>{{counter}}</h1> <!--101,2,3...-->

<!--v-once无法增加,只有页面开始渲染一次-->
<h1 v-once>
{{counter}} <!--100-->
<p>{{counter}}</p> <!--100-->
</h1>

<button @click="counter++">add</button>
</template>

2.1.3 v-text

  • 定义: 与双大括号语法效果相同
  • 示例:
<template id="hh">
<h1>{{message}}</h1> <!--Hello Vue3-->
<h1 v-text="message"></h1> <!--Hello Vue3-->
</template>

2.1.4 v-html

  • 定义: 默认情况下,如果我们展示的内容本身是html的,那么vue并不会对其进行特殊的解析。如果我们希望这个内容被Vue可以解析出来,那么可以使用v-html来展示;
  • 示例:
<template id="my-app">
<h1>{{msg}}</h1> <!--<span style="color:red; background:blue">哈哈嘿</span>-->
<h1 v-html="msg"></h1> <!--哈哈嘿-->
</template>
<!--msg: '<span style="color:red; background:blue">哈哈嘿</span>',-->

2.1.5 v-pre

  • 定义: 不解析{{}}
  • 示例:
<template id="my-app">
<h1>{{message}}</h1> <!--Hello Vue3-->
<h1 v-pre>{{message}}</h1> <!--{{message}}-->
</template>

第二节 v-bind指令

2.2.1 v-bind绑定HTML基本属性

  • 定义: 用于动态绑定HTML元素的属性值(比如img标签的src属性, a标签的href属性, 一般标签的class属性…..)
  • 语法: v-bind:属性名="表达式" 简写为 :属性名="表达式"
  • 示例: 点击按钮, 在百度链接+图片和b站链接+图片中切换
<!-- 小案例: 点击按钮, 在百度链接+图片和b站链接+图片中切换 -->
<div id="app"></div>
<template id="hh">
<a style="color: aliceblue" :href="links[index]">
<img style="height: 100px; width: 100px" :src="pic[index]" />
</a>
<button @click="index=(index+1)%2">{{text[index]}}</button>
</template>
<script>
const App = {
template: "#hh",
data() {
return {
pic: [
"https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png",
"https://i0.hdslb.com/bfs/archive/ac7edcb6a0cd83bc3a7a7a0a3730ba69c17f47ab.png",
],
links: ["https://www.baidu.com/", "https://www.bilibili.com/"],
text: ["百度", "bilbil"],
index: 1,
};
},
methods: {},
};
Vue.createApp(App).mount("#app");
</script>

2.2.2 v-bind绑定class属性

  • 定义: v-bind绑定HTML中的”class”属性
  • 语法:
    • ①基本用法: :class="{'className': 布尔值, 'className2': 布尔值}"
    • ②静态class共存: class="static-class" :class="{dynamic-class: isActive}"
    • ③可以等于”data中的对象”: class="对象属性"
    • ④可以等于”方法执行返回的对象”: class="方法()"
    • ⑤v-bind绑定数组class::class="['class1', 'class2', variable]"
  • 注意: 所有的类名都需要加上引号
  • 示例: v-bind绑定class案例: 按钮1切换一个h1标签的颜色(黑/红)
<style>
.active {
color: brown;
}
.title {
font-size: 50px;
}
.static-class {
background-color: cadetblue;
}
</style>
<template id="hh">
<!--1.v-bind绑定class案例: 按钮1切换一个h1标签的颜色(黑/红) -->
<h1 :class="{active:isActive, title:isTitle}">{{msg}}</h1>
<button @click="isActive=!isActive">acitve</button>
<button @click="isTitle=!isTitle">title</button>

<!--2.动态class和静态class结合 -->
<h1 class="static-class" :class="{active:isActive, title:isTitle}">{{msg}}</h1>

<!--3.将对象放到一个单独的属性中 -->
<h1 :class="classObj">{{msg}}</h1>

<!--4.将对象用一个方法返回 !注意这里是调用!有()()()的!!!-->
<h1 :class="getClassObj()">{{msg}}</h1>

<!--5.v-bind绑定class数组 -->
<h1 :class="['active',{title:isTitle},isStaticClass?'static-class':'']">绑定class数组</h1>
<!-- ↑静态class ↑嵌套对象语法 ↑允许三目操作符-->
<!-- ↑注意这里一定要'', 否者不生效 -->
</template>
<script>
const App = {
template: "#hh",
data() {
return {
msg: "Hello Vue",
//1.案例
isActive: true,
isTitle: false,
isStaticClass: true,
//3.将对象放到一个单独的属性中
classObj: {
active: false,
title: true,
},
};
},
methods: {
getClassObj() {
return this.classObj;
},
},
};
Vue.createApp(App).mount("#app");
</script>

2.2.3 v-bind绑定style属性

  • 定义: v-bind绑定HTML中的”style属性”
  • 语法:
    • ①基本用法:style="{ 'css属性名1': 属性值1, 'css属性名2': 属性值2 }" 其中属性值可以”写死字符串”, 也可以是data中”动态变量”
    • ②v-bind绑定数组style: :style="[styleObj1, styleObj2]"
  • 注意: css属性名 需要有''; 属性值不用!, 属性值是Vue去data里面找的
  • 示例: 用户输入(按下enter确认)颜色,字体大小,字体加粗,你需要按照用户指示进行展示
<template id="my-app">
<!--html的行内样式(style)原始写法,无法动态改-->
<h1 style="color: orange; font-size: 50px">Hello Vue3</h1>

<!--1. v-bind绑定style案例:用户输入(按下enter确认)颜色,字体大小,你需要展示 -->
<h1 :style="{'background-color':'skyblue', 'color':userColor, 'font-size':userSize+'px', }">Hello Vue3</h1>
<!-- ↑静态的,写死背景为天蓝色 ↑动态的,字体颜色 ↑动态的,字体大小 -->
<input type="text" placeholder="颜色" @keyup.enter="userColor=$event.target.value" /><br />
<input type="text" placeholder="字号" @keyup.enter="userSize=$event.target.value" /><br />

<!--2.v-bind和styld的数组语法 -->
<h1 :style="[styleObj1, styleObj2]">数组语法</h1>
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
// 1.案例
userColor: "red",
userSize: "20",
// 2.v-bind和styld的数组语法
styleObj1: {
'font-size': '20px',
'color': 'black',
},
styleObj2: {
'textDecoration': "underline",
}
};
},
methods: {},
};
Vue.createApp(App).mount("#app");
</script>

2.2.4 v-bind绑定自定义属性

  • 定义: 属性的名称不确定(绑定不是style, src, href, class…这样html的属性,而是自定义的属性(自定义组件))
  • 语法: 用v-bind:[属性名变量]="属性值变量"或简写:[属性名变量]="属性值变量"
  • 示例: 自定义属性名为abc, 值为hhh的属性
<template id="my-app">
<div :[attribute_name]="attribute_value">Hello Vue3!</div>
<!-- 效果→ <div abc="hhh">Hello Vue3!</div> -->
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
// 1.案例
attribute_name: "abc",
attribute_value: "hhh"
};
},
methods: {},
};
Vue.createApp(App).mount("#app");
</script>

2.2.5 v-bind绑定多对属性

  • 定义: 多对属性通过对象映射, 直接加到一个标签中
  • 语法: v-bind="对象"(不带任何属性名)
  • 示例:
<style>
.active {
color: red;
}
</style>

第三节 v-on指令

2.3.1 v-on基本使用

  • 语法: v-on:任意dom事件="触发后干的事情"@任意dom事件
  • 示例:
<template id="my-app">
<!-- 1. 鼠标事件 -->
<h1>1.鼠标事件</h1>
<button @click="console.log('点击事件')">点击</button>
<button @dblclick="console.log('双击事件')">双击</button>
<button @mousedown="console.log('鼠标按下')" @mouseup="console.log('鼠标抬起')">鼠标按下与抬起</button>
<button @mouseenter="console.log('鼠标进入')" @mouseleave="console.log('鼠标离开')">鼠标进入与离开</button>
<button @mousemove="console.log('鼠标移动',$event.clientX,$event.clientY)">鼠标移动</button>

<button @click.left="console.log('左键点击')">左键</button>
<button @click.middle="console.log('中键点击')">中键</button>
<button @click.right="console.log('右键点击')">右键</button>
<!-- 只触发一次 -->
<button @click.once="console.log('只有第一次点击有效')">只点一次</button>
<!-- 链式修饰符 -->
<button @click.stop.prevent="console.log('阻止冒泡和默认行为')">点击</button>

<!-- 2. 键盘事件 -->
<h1>2. 键盘事件</h1>
<input @keydown="console.log('按键按下', $event.key)" @keyup="console.log('按键释放', $event.key)" placeholder="按下与释放">
<input @keydown.enter="console.log('按下了Enter键,输入内容为:',$event.target.value)" placeholder="按Enter">
<input @keyup.enter="console.log('按了Enter键')" placeholder="按Enter">
<input @keydown.ctrl.c="console.log('复制快捷键 Ctrl+C')" placeholder="按下Ctrl+C">
<input @keydown.esc="console.log('按了ESC键')" placeholder="按下ESC">
<div @click="console.log('父元素')">
<button @click.stop="console.log('子元素-阻止冒泡')">点击</button>
</div>

<!-- 3. 表单事件 -->
<h1>3. 表单事件</h1>
<input @focus="console.log('获得焦点')" @blur="console.log('失去焦点')" placeholder="焦点">
<input @change="console.log('值改变了 enter确认', $event.target.value)">
<input @input="console.log('正在输入:', $event.target.value)">
<select @change="console.log('选择了:', $event.target.value)">
<option value="1">选项1</option>
<option value="2">选项2</option>
</select>
<form @submit.prevent="console.log('表单提交被阻止')">
<button type="submit">提交</button>
</form>


<!-- 4. 修饰符示例 -->
<h1>4. 修饰符示例</h1>
<p>已融合在其他示例中</p>

<!-- 5. UI 事件 -->
<h1>5. UI 事件</h1>
<div @scroll="console.log('正在滚动')" style="height: 100px; overflow: auto;">
<div style="height: 500px">滚动区域</div>
</div>

<div @resize="console.log('大小改变了')" style="resize: both; border: 1px solid;">
可调整大小
</div>

<!-- 6. 媒体事件 -->
<h1>6. 媒体事件</h1>
<video @play="console.log('开始播放')"
@pause="console.log('视频暂停')"
@ended="console.log('播放结束')"
src="https://media.w3.org/2010/05/sintel/trailer.mp4" controls style="height: 200px;width: 200px;"></video>

<!-- 7. 拖放事件 -->
<h1>7. 拖放事件</h1>
<div draggable="true"
@dragstart="console.log('开始拖动')"
@drag="console.log('拖动中')"
@dragend="console.log('拖动结束')">拖我</div>
<div @dragenter="console.log('拖到我这里了')"
@dragover.prevent="console.log('在我上面拖动')"
@drop="console.log('放到我这里了')"
style="border: 2px dashed; padding: 20px;">
拖放区域
</div>

<!-- 8. 剪贴板事件 -->
<h1>8. 剪贴板事件</h1>
<input @copy="console.log('复制了内容:', $event.target.value.substring($event.target.selectionStart, $event.target.selectionEnd))" value="复制这段文字">
<input @paste="console.log('粘贴了内容', $event.clipboardData.getData('text'))" placeholder="粘贴到这里">
<input @cut="console.log('剪切了内容')" value="剪切这段文字">

<!-- 9. 上下文菜单 -->
<h1>9. 上下文菜单</h1>
<div @contextmenu="console.log('右键菜单')">右键点击这里</div>

<!-- 其他.$event(获取事件对象) -->
<h1>其他.$event(获取事件对象)</h1>
<button @click="console.log('事件对象:', $event)">查看事件对象</button>
<button @click="console.log('目标元素:', $event.target)">查看目标元素</button>
<button @click="console.log('点击位置:', $event.clientX, $event.clientY)">点击位置</button>
<input @input="console.log('输入的值:', $event.target.value)">
</template>

2.3.2 v-on事件的参数传递

  • 语法: @click="btnClick($event, string, number)" (btnClick为自定义事件methods)
<template id="my-app">
<!-- 这里的方法,无需(); btnClick()❌️-->
<button @click="btnClick($event, 'Tlyer233',22)">传递事件</button>
<button @click="btnClick2('Tlyer233',22, $event)">传递事件</button>
<!-- 无关顺序, $event就是DOM事件,不一定要放到第一个位置 -->
</template>
<script>
const App = {
template: "#my-app",
data() {
return {};
},
methods: {
btnClick(event, name, age) {
console.log(event, name, age);
},
btnClick2(name, age, event) {
console.log(name, age, event);
}
},
};
Vue.createApp(App).mount("#app");
</script>

第四节 小结

2.4.1 表格总结

名词解释: 对象属性: data中的返回的对象(如果是字符串, 一般用于显示, 如果是对象, 一般”对号入座”💺)

指令 作用 示例
{{}} ①展示”对象属性”(string类型)
②可以写表达式
③可以调用函数
④可以写三目操作符
<h1>{{message}}</h1>
<p>{{100*20/2}}</p>
<p>{{resverMessage()}}</p>
<p>{{flag?"True Vue3":"Fack Vue3"}}</p>
v-once ①加上后, 该组件(及其子组件)都只渲染一次 <h1 v-once>{{counter}}</h1>
v-text ①和{{}}效果一样 <h1 v-text="message"></h1>
v-html ①将HTML字符串解释为html而不是字符串 <h1 v-html="msg"></h1>
v-pre ①将{{}}解释为字符串,而不是mustache <h1 v-pre>{{message}}</h1>
v-bind ①绑定HTML基本元素(src, href, class…)

②绑定class属性
③绑定class数组
④可以放”对象属性”
⑤可以放”函数返回值”

⑥绑定style属性
⑦绑定style数组

⑧绑定自定义属性

⑨绑定多对属性
<a :href="links[index]"></a>

<h1:class="{'className1': 布尔值, 'className2': 布尔值}"></h1>
<h1 :class="['active',{title:isTitle},isStaticClass?'static-class':'']">绑定class数组</h1>
<h1 :class="classObj">{{msg}}</h1>
<h1 :class="getClassObj()">{{msg}}</h1>

<h1 :style="{'background-color':'skyblue', 'color':userColor, 'font-size':userSize+'px', }">Hello Vue3</h1>
<h1 :style="[styleObj1, styleObj2]">数组语法</h1>

<div :[attribute_name]="attribute_value">Hello Vue3!</div>
<h1 v-bind="multObj">Hello Vue3!!</h1>
效果→ <h1 name="Tlyer233" age="10" class="active">Hello Vue3!!</h1>
v-on ①绑定DOM事件
②事件参数传递
<button @click="console.log('点击事件')">点击</button>
<button @click="btnClick2('Tlyer233',22, $event)">传递事件</button>

第五节 v-if与v-show指令

3.5.1 v-if指令

  • 语法: ①v-if="Boolean":基础条件判断 ②v-else="Boolean":必须与v-if配合使用 ③v-else-if="Boolean":多条件分支判断
  • 示例: 根据用户输入分数, 判断等级
<template id="my-app">
<input type="text" v-model="score"> <!--v-model双向绑定-->
<h1 v-if="score>=90">优秀</h1>
<h1 v-else-if="score>60">良好</h1>
<h1 v-else>不及格</h1>
</template>
<!--data中score: 90-->

2.5.2 v-show指令

  • 语法: true的时候和v-if一样; false不一样;
  • 示例:
<template id="my-app">
<input type="text" v-model="score"> <!--v-model双向绑定-->
<h1 v-if="score>=90">优秀</h1>
<h1 v-show="score>=90">优秀</h1>
</template>
<!--data中score: 90-->

2.5.3 对比v-if和v-show

  • 示例: 两个和template标签的使用
<template id="my-app">
<template v-if="flag"><h1>{{message}}</h1></template> <!--显示-->
<template v-show="flag"><h1>{{message}}</h1></template> <!--不显示-->
<button @click="flag=!flag">switch</button>
</template>
v-if v-show
template标签一起使用? 支持 不支持
flase时 元素压根不会在DOM中 DOM是有渲染的, 只是通过CSS:display切换
如何选择 少量切换 频繁切换适用

image.png|850

第六节 v-for指令

2.6.1 v-for遍历数组
image.png|115
2.6.2 v-for遍历对象
image.png|528
2.6.3 v-for遍历数字
image.png|87
2.6.4 v-for和template的使用
image.png

2.6.1 v-for遍历数组

  • 语法:
    • 只获取元素: v-for="item in array"
    • 获取元素和索引: v-for="(item, index) in array"
  • 示例:
<template id="my-app">
<h1> miHoYo</h1>
<!-- 1.基本使用 -->
<ul><li v-for="game in games">{{game}}</li></ul>
<!-- 2.遍历带索引 -->
<ul><li v-for="(game,index) in games">{{index+1}}-{{game}}</li></ul>
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
games: [
"原神",
"崩坏3",
"星穹铁道",
"绝区零",
]
};
},
methods: {},
};
Vue.createApp(App).mount("#app");
</script>

2.6.2 v-for遍历对象

  • 语法: v-for="(value, key, index) in object"
  • 注意: value, key, index是固定位置, 但名称可以变!!!
  • 示例:
<template id="my-app">
<table border="2px">
<thead>
<th>属性值</th>
<th>属性的key</th>
<th>序号</th>
</thead>
<tbody>
<tr v-for="(value,key,index) in student">
<td>{{value}}</td>
<td>{{key}}</td>
<td>{{index}}</td>
</tr>
</tbody>
</table>
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
student: {
name: "Tlyer233",
age: 18,
score: {
"Math": 80,
"English": 85,
}
}
};
},
methods: {},
};
Vue.createApp(App).mount("#app");
</script>

2.6.3 v-for遍历数字

  • 语法: v-for 变量 in 10" 变量会从[1, 10] 进行遍历
  • 示例:
<template id="my-app">
<ul>
<li v-for="num in 10">{{num}}</li>
</ul>
</template>

2.6.4 v-for和template的使用

  • 语法: ①<template>标签是HTML原生标签; ②其作用是不会被展示在页面上,
  • 示例: 案例:点击移除按钮的时候,将该书从表格中删除
<template id="my-app">
<h1>书籍大全</h1>
<!-- 案例:点击移除的时候,将该书从表格中删除 -->
<table border="2px">
<thead>
<th>序号</th>
<th>书名</th>
<th>发行时间</th>
<th>操作</th>
</thead>
<tbody>
<!-- 错误示范: v-if和v-for在同一元素上时, Vue3中v-if优先级更高, 会先执行v-if, 此时book还未定义
会报book未定义的错误 -->
<tr v-for="(book, index) in bookArray" v-if="book.isShow"> 这是错误的❌️
<td>{{index+1}}</td>
<td>{{book.bookName}}</td>
<td>{{book.publishTime}}</td>
<td><button @click="book.isShow=false">移除</button></td>
</tr>

<!-- 正确做法: 使用<template>包裹, v-for放在template上, v-if放在tr上 -->
<template v-for="(book, index) in bookArray">
<tr v-if="book.isShow">
<td>{{index+1}}</td>
<td>{{book.bookName}}</td>
<td>{{book.publishTime}}</td>
<td><button @click="book.isShow=false">移除</button></td>
</tr>
</template>
</tbody>
</table>
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
bookArray: [
{ bookName: "算法导论", publishTime: "2006-9", isShow: true },
{ bookName: "UNIX编程艺术", publishTime: "2006-2", isShow: true },
{ bookName: "编程珠玑", publishTime: "2008-10", isShow: true },
{ bookName: "代码大全", publishTime: "2006-3", isShow: true }
]
};
},
methods: {},
};
Vue.createApp(App).mount("#app");
</script>

2.6.5 v-for和数组方法的更新检查

  • 语法: 用了js的数组相关方法后, 会自动更新v-for遍历的内容
  • 示例:
    image.png

2.6.6 v-for中key的源码解读

  • 结论: 使用v-for时, 都加上:key="唯一的内容" (这个唯一的内容是Bean的”唯一id”最好)
  • 示例:
<li v-for="item in letters" :key="item">{{item}}</li>
  • 源码解析:
    image.png|325
  1. 有key, vue3就使用 patchKeyedChildren()方法;
image.png
  1. 没有key, vue3会调用patchUnkeyedChildren()方法;
image.png|375 image.png|500 image.png|525 image.png|550 image.png|600

第七节 computed计算属性

2.7.1 computed的基本使用
image.png|144
2.7.2 computed计算属性的完整写法(get和set)
image.png

2.7.1 computed的基本使用

  • 使用场景: computed解决什么问题? {{表达式}} 如果表达式过于复杂, 不推荐写在{{}}
  • 语法: 注意没有(); {{getFullName}}✅️; {{getFullName()}}❌️;
  • 注意: ①这是一个属性, 和data, template, methods等同级; ②实时刷新的
  • 示例:
    1. 案例一:我们有两个变量: firstName和lastName ,希望它们拼接之后在界面上显示;
    2. 案例二:我们有一个分数:score; 当score大于60的时候,在界面上显示及格; 当score小于60的时候,在界面上显示不及格;
    3. 案例三:我们有一个变量message,记录一段文字:比如Hello World; 显示为World Hello
<template id="my-app">
<!-- 1. 案例一:我们有两个变量: firstName和lastName ,希望它们拼接之后在界面上显示; -->
<h1>{{getFullName}} </h1>
<!-- 2. 案例二:我们有一个分数:score; 当score大于60的时候,在界面上显示及格; 当score小于60的时候,在界面上显示不及格; -->
<input type="text" v-model="score">
<h1>{{calculate}}</h1>
<!-- 3. 案例三:我们有一个变量message,记录一段文字:比如Hello World; 显示为World Hello -->
<h1>{{reverseMessage}}</h1>
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
firstName: "Tlyer",
lastName: "233",
score: 60,
message: "Hello Vue3",
};
},
computed: {
getFullName() {
return this.firstName + this.lastName;
},
calculate() {
return this.score >= 60 ? "及格" : "不及格";
},
reverseMessage() {
return this.message.split(" ").reverse().join(" ");
}
},
methods: {},
};
Vue.createApp(App).mount("#app");
</script>
  • 对比三种实现:
mustache语法{{}}
image.png|925
methods方法
image.png|675
computed计算属性
image.png|575

2.7.2 computed计算属性的完整写法(get和set)

  • 语法: 第一种是完整写法; 第二种只有get可以理解为”语法糖”
<script>
computed: {
getFullName: {
get: function () { return this.firstName + this.lastName; },
set: function (newName) { this.firstName = newName; }
}
},
</script>
<script>
computed: {
getFullName function () { return this.firstName + this.lastName;}
},
</script>
  • 示例: 展示fullName; 有一个input框, 用户可以修改firstName;
<template id="my-app">
<h1>{{getFullName}}</h1>
<!-- 易混① 使用赋值语句触发 computed setter -->
<!-- setter 通过赋值触发, 如: this.getFullName = "newValue" 而不是getFullName($event.target.value)-->
<input type="text" @keyup.enter="getFullName = $event.target.value">
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
firstName: "Tlyer",
lastName: "233",
};
},
computed: {
getFullName: {
get: function () {
return this.firstName + this.lastName;
},
// 易混②: setter 不需要 return, 而是修改依赖的数据
set: function (newName) {
// return newName + this.lastName;
this.firstName = newName; // 修改 firstName

}
}
},
methods: {},
};
Vue.createApp(App).mount("#app");
</script>
  • 源码:
    image.png|775

第八节 watch监听器属性

2.8.1 watch的基本使用
image.png|700
2.8.2 watch监听整个对象
|500
2.8.3 watch监听对象中的属性(深度监听)-1
image.png|500
2.8.3 watch监听对象中的属性(深度监听)-2
image.png|600

2.8.1 watch监听变量

  • 作用: 监听”变量/对象”的变化
  • 语法: ①和data, template, computed同级的, 都是属性! ②注意要对应 ③newValue和oldValue都是简单类型(string, number….)
    image.png|500
  • 示例:
<template id="my-app">
<input type="text" v-model="message">
<h1>{{message}}</h1>
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
message: "Hello Vue3",
};
},
watch: {
message(newValue, oldValue) { console.log(newValue, oldValue);}
},
};
Vue.createApp(App).mount("#app");
</script>

2.8.2 watch监听整个对象

  • 语法: ①返回的是Proxy对象, 不是原始对象(但拿值还是一样拿)
  • 示例: 有一个按钮, 点击后, 改变整个对象
<template id="my-app">
<h1>{{student.name}}</h1>
<input type="text" v-model="student.name">
<button @click="student={'www':'hhh'}">改变整个对象 this.student={}</button>
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
student: {
name: "Tlyer233",
age: 18,
score: {"Math": 85, "English": 84,}
}
};
},
watch: {
student(newObj, oldObj) {console.log(newObj, oldObj);}
},
};
Vue.createApp(App).mount("#app");
</script>

2.8.3 watch监听对象中的属性(深度监听)

方式一: 使用深度监听
  • 语法: ①为语法糖 ②为完整写法, 才可以设置deep: true进行深度监听
<script>
watch:{
student (newVal, oldVal) { 处理逻辑 } // 语法糖
}
</script>
<script>
student: { // 完整写法
handler: function(newVal, oldVal) { 处理逻辑 }, // handler这个名字不能换啊
deep: boolean, // 是否深度监听(监听对象内部属性变化)
immediate: boolean // 是否初始化时立即执行一次
}
</script>
  • 示例: 改变student中的name属性
<template id="my-app">
<h1>{{student.name}}</h1>
<input type="text" v-model="student.name">
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
student: {
name: "Tlyer233",
age: 18,
score: { "Math": 85, "English": 84}
}
};
},
watch: {
// "语法糖"🍬写法: student(newObj, oldObj) { console.log(newObj, oldObj); }
// watch监听某个对象的完整写法
student: {
// 注意: newObj 和 oldObj 看起来一样, 因为 JavaScript 对象按引用传递, newObj 和 oldObj 都指向同一个对象 对象属性改变时, 这个对象被修改, 所以两个引用的内容都变了
// 解决: 在 data 中保存上一个值的深拷贝(没其他好办法)
handler: function (newObj, oldObj) {
console.log("newObj:", newObj, "oldObj:", oldObj);
},
deep: true, // 监听对象内部属性变化
immediate: true, // 初始化时立即执行一次
}
},
};
Vue.createApp(App).mount("#app");
</script>
方式二: 使用vue2的做法
  • 示例:
<template id="my-app">
<h1>{{info.name}}</h1>
<button @click="info.name='zhangsan'">click</button>
<button @click="clickEvent">click</button>
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
info: {
name: "Tlyer233",
age: 18,
}
};
},
watch: {
'info.name': function(newValue, oldValue) {
console.log("newValue", newValue, "oldValue", oldValue);
}
},
methods: {
clickEvent: function() {
// 尽管换对象了, 只要整个name不变,就不会触发'info.name'的watch
this.info = {
name: "Tlyer233",
}
}
},
};
Vue.createApp(App).mount("#app");
</script>

2.8.4 watch在生命周期(create)中的使用

  • 示例:

第九节 v-model指令

2.9.1 v-model的基本使用
image.png|486
2.9.2 v-model和其他”表单”组件
image.png|650

2.9.1 v-model的基本使用

  • 定义:用于双向绑定
  • 示例:
<template id="my-app">
<!-- v-model的本质 -->
<input type="text" :value="message" @input="message = $event.target.value">
<!-- 2.v-model的双向绑定 -->
<input type="text" v-model="message">
<h1>{{message}}</h1>
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
message: "Hello Vue3",
};
},
};
Vue.createApp(App).mount("#app");
</script>

2.9.2 v-model和其他”表单”组件

  • 示例:
<template id="my-app">
<h1>1. v-model和textarea</h1>
<label> <textarea v-model="selfIntroduction"></textarea></label>
<p>{{selfIntroduction}}</p> <!--data中要有selfIntroduction=""变量-->

<h1>2. v-model和checkbox(多选框)</h1>
<h4>示例一: 单选</h4>
<label> <input type="checkbox" v-model="isAgree">同意协议 </label>
<p>{{isAgree}}</p> <!--data中要有isAgree=false变量-->

<h4>示例二: 多选</h4>
<label>
你的爱好:
<label><input type="checkbox" v-model="hobbies" value="basketball">篮球</label>
<label><input type="checkbox" v-model="hobbies" value="football">足球</label>
<label><input type="checkbox" v-model="hobbies" value="tennis">网球</label>
</label>
<p>{{hobbies}}</p> <!--上面要有value下面hobbies数组才有值; data中要有hobbies=[]数组-->

<h1>3. v-model和redio(单选框)</h1>
<label>
性别:
<label><input type="radio" v-model="gender" value="male"></label>
<label><input type="radio" v-model="gender" value="female"></label>
</label>
<p>{{gender}}</p> <!--data中要有gender=""变量-->

<h1>4. v-model和select/option(列表选择)</h1>
<label>喜欢的水果:</label>
<!-- 方案1:单选(去掉multiple) -->
<select v-model="fruit" size=1>
<option value="apple">🍎苹果</option>
<option value="orange">🍊橘子</option>
<option value="banana">🍌香蕉</option>
</select>
<p>单选选择:{{fruit}}</p> <!--data中要有fruit=""变量-->

<!-- 方案2:多选(使用数组) -->
<label>喜欢的水果:</label>
<select v-model="fruits" multiple size=1>
<option value="apple">🍎苹果</option>
<option value="orange">🍊橘子</option>
<option value="banana">🍌香蕉</option>
</select>
<p>多选选择:{{fruits}}</p> <!--data中要有fruits=[]数组-->
</template>
<script>
const App = {
template: "#my-app",
data() {
return {
// 1. v-model和textarea
selfIntroduction: "大家好,我是...",

// 2. v-model和checkbox(多选框)
isAgree: false,
hobbies: [],

// 3. v-model和redio(单选框)
gender: "male",

// 4. v-model和select/option(列表选择)
fruit: "orange",
fruits: ["orange"],
};
},
};
Vue.createApp(App).mount("#app");
</script>

2.9.3 v-model的修饰符

lazy修饰符
image.png|1100
number修饰符
image.png|450
trim修饰符
image.png|1100

第十节 小结

第三章 vuecli和组件化开发

第一节 vuecli创建项目

STEP1: 下载vue cli

下载
npm install @vue/cli -g
升级
npm update @vue/cli -g

STEP2: 创建Vue项目 与 目录结构

vue create 项目名称
image.png|775 image.png|475

STEP3: 运行

npm install
npm run serve

第二节 组件化开发

3.2.1 组件的拆分和嵌套

  • 示例: 目标将【图一】拆分为【图二】并进行展示, 效果如【图三】
image.png|267 image.png|283
image.png|190

只有第一次写main.js, 后续每一节, 都在src下建立不同的文件夹, 然后mian.js引用App.vue

import { createApp } from 'vue'
import App from './01_component_splitting_and_nesting/App.vue'

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

引入: ①script里面: import XXX from 相对路径 ②script里面: 加到components属性里面(components是属性, 和watch, computed, methods一桌的) ③template里面:大写标签引用

<template>
<div>
<HeaderComponent></HeaderComponent>
<MainComponent></MainComponent>
<FooterComponent></FooterComponent>
</div>

</template>

<script>
import FooterComponent from './FooterComponent.vue';
import HeaderComponent from './HeaderComponent.vue';
import MainComponent from './MainComponent.vue';

export default {
components: {
HeaderComponent,
MainComponent,
FooterComponent,
}
}


</script>

<style scoped></style>
<template>
<div>
<h2>Header</h2>
<h2>NavBar</h2>
</div>
</template>
<script>
export default {

}
</script>
<style scoped></style>
<template>
<div>
<h2>Banner</h2>
<ul>
<li>商品列表1</li>
<li>商品列表2</li>
<li>商品列表3</li>
<li>商品列表4</li>
<li>商品列表5</li>
</ul>
</div>
</template>
<script>
export default {

}
</script>
<style lang="">

</style>
<template>
<div>
<h2>Footer</h2>
<h2>免责声明</h2>
</div>
</template>
<script>
export default {

}
</script>
<style lang="">

</style>

3.2.2 组件通讯(父传子props和emits属性)

  • 语法: ①props是子的, ②props可以是字符串数组(或对象, 对象可以约束更多【见下】)
  • 示例: 父组件传递
    image.png
<template>
<div>
<ShowMessage title="哈哈嘿" content="sdsfds"></ShowMessage>
<ShowMessage title="哈哈嘿" content="sdsfds"></ShowMessage>
<ShowMessage title="哈哈嘿" content="sdsfds"></ShowMessage>
<ShowMessage title="哈哈嘿" content="sdsfds"></ShowMessage>
</div>
</template>

<script>
import ShowMessage from './ShowMessage.vue';

export default {
components: {
ShowMessage,
}

}
</script>

<style lang="scss" scoped></style>
<template>
<div>
<h1>{{ title }}-{{ content }}</h1>
</div>
</template>

<script>
export default {
data() {
return {

}
},
props: ["title", "content"]


}
</script>

<style lang="scss" scoped></style>
  • 语法: 完整写法, props为对象【常用】
  • 示例:
  • 注意: ①只会报警告, 不会报错 ②props不能调用data的内容(因为组件未加载)
    image.png
<template>
<table border="2px">
<thead>
<th>排名</th>
<th>姓名</th>
<th>英语成绩</th>
<th>数学成绩</th>
</thead>
<tbody>
<!-- ShowMessage每一行都是一个<tr></tr> -->
<ShowMessage name="Jack" rank="1" :mark="{ English: 90, Math: 100 }"></ShowMessage>
<ShowMessage name="Tom" rank="2" :mark="{ English: 99, Math: 32 }"></ShowMessage>
<ShowMessage name="Tony" rank="3" :mark="{ English: 55, Math: 90 }"></ShowMessage>
</tbody>
</table>
</template>

<script>
import ShowMessage from './ShowMessage.vue';

export default {
components: {
ShowMessage,
},
}
</script>

<style lang="scss" scoped></style>

完整写法可以加①type ②默认值 ③验证 ④必须

<template>
<tr>
<td>{{ rank }}</td>
<td>{{ name }}</td>
<td>{{ mark.English }}</td>
<td>{{ mark.Math }}</td>
</tr>
</template>

<script>
export default {
props: {
name: {
type: String, // ①必须为String类型
default: "zhangSan", // ②默认值
validator(studentName) { // ③验证
console.log(studentName);
const StudentList = ['Tom', 'Jack', 'Jerry', 'Tony',];

return StudentList.includes(studentName);
}
},
rank: {
type: Number,
required: true, // ④父组件必须传递
},
mark: {
type: Object,
default() { // ⑤Object的默认返回不能只写 `{ English: 60, Math: 60 }`❌️
return { English: 60, Math: 60 }
}
},
},

data() {
return {
// props不能通过this.StudentList读取到这个;(生命周期的原因)
// StudentList: ['Tom', 'Jack', 'Jerry', 'Tony', 'zhangSans'],
}
},
}
</script>

<style lang="scss" scoped></style>

3.2.3 组件通讯(父传子)

  • 示例:
image.png|850 image.png|950
<template>
<div>
<h1>{{ counter }}</h1>
<!-- 父组件监听子组件自定义事件:@add、@sub、@addN,对应处理函数在 methods 中 -->
<CounterComponent @add="addOne"
@sub="subOne"
@addN="addNum">
</CounterComponent>
</div>
</template>

<script>
import CounterComponent from './CounterComponent.vue';
export default {
components: { CounterComponent, },
data() { return { counter: 0, } },
methods: {
addOne: function () { this.counter++; },
subOne: function () { this.counter--; },
// 步骤3:接收到携带参数的 'addN' 事件
// 步骤4:使用事件参数 inputN 更新父组件状态
addNum: function (inputN) { this.counter += inputN }
}

}
</script>

<style lang="sass" scoped>

</style>
<template>
<div>
<button @click="increase">+1</button>
<button @click="decrease">-1</button>
<!-- 2.传递值 -->
<input type="number" v-model.number="inputN" placeholder="输入要加的数值" />
<!-- 点击后触发 increaseN,将 inputN 作为事件参数传给父组件 -->
<button @click="increaseN">+n</button>
</div>
</template>

<script>
export default {
data() { return { inputN: 10, } },
emits: ["add", "sub", "addN"], // 子组件对父暴露的接口
methods: {
increase: function () {
this.$emit("add");
},
decrease: function () { this.$emit("sub"); },
increaseN: function () {
// 步骤1:子组件读取本地状态 inputN
// 步骤2:通过 $emit('addN', inputN) 携带参数通知父组件
this.$emit("addN", this.inputN);
},
}

}
</script>

<style lang="sass" scoped>

</style>

3.2.4 综合案例(衣服, 裤子, 鞋子)

  • 示例
    1. 页面上方有一个标签栏组件(TablControl),显示多个标签标题,点击标签可切换内容。
    2. 标签栏组件通过 props 接收标题数组,通过点击事件将当前选中标签索引以自定义事件 changeTab 通知父组件。
    3. 父组件监听 changeTab 事件,根据索引切换下方展示的内容文本。
    4. 标签栏组件内部高亮当前选中的标签。
      image.png
<template>
<div>
<TablControl :titles="titles" @changeTab="changeTabHandler">
</TablControl>
<h2>{{ content[activeContent] }}</h2>
</div>
</template>

<script>
import TablControl from './TablControl.vue';
export default {

components: {
TablControl,
},
data() {
return {
titles: ['热销', '流行', '热门', '近期'],
content: ['热销页面', '流行页面', '热门页面', '近期页面'],
activeContent: 0,
}
},
methods: {
changeTabHandler(activeIndex) {
this.activeContent = activeIndex;
},
}

}
</script>

<style lang="sass" scoped>

</style>
<template>
<div class="tab-control">
<div class="tab-control-item" :class="{ active: index == activeIndex }"
v-for="(title, index) in titles" :key="title"
@click="tabClickHanlder(index)">
<span>{{ title }}</span>
</div>
</div>
</template>

<script>
export default {
props: {
titles: {
type: Array,
default: function () { return ['衣服', '鞋子', '裤子']; }
}
},
data() {
return {
activeIndex: 0,
}
},
emits: ['changeTab'],
methods: {
tabClickHanlder(index) {
this.activeIndex = index;
this.$emit('changeTab', this.activeIndex);
}
}
}
</script>

<style scoped>
.tab-control {
display: flex;
}

.tab-control-item {
flex: 1;
text-align: center;
}

.tab-control-item.active {
color: red;
}

.tab-control-item.active span {
border-bottom: 5px solid red;
padding: 5px 10px;
}
</style>

3.2.5 祖辈通讯(provide和inject)

  • 定义: 适用于同一祖辈链之间的通讯【图一】
  • 语法: ①provideinject均为属性(和data, methods一桌的)
  • 注意: provide如果使用data中的”对象属性”, 后续data中的”对象属性”更新是拿不到更新的!
  • 示例: 如【图二】
image.png|325 image.png image.png

注意⚠️: 👀看注释, 如果硬想拿, 得用computed; ②且computed返回vaule才是原来的值

<template>
<div>
<HomeComponent></HomeComponent>
</div>
</template>

<script>
import HomeComponent from './HomeComponent.vue';
export default {
components: {
HomeComponent
},
provide() {
return {
name: "Tlyer233",
age: 22,
score: this.score, // 后续data中的score更新是拿不到的!
// 如果想拿到后续的更新
// score: computed(() => this.score), //import { computed } from 'vue';
}
},
data() {
return {score: { English: 90, Math: 120 }}
}

}
</script>

<style lang="sass" scoped>

</style>

这里没啥用, 只是作为一个媒介

<template>
<div>
<HomeContent></HomeContent>
</div>
</template>

<script>
import HomeContent from './HomeContent.vue';
export default {
components: {
HomeContent,
}

}
</script>

<style lang="sass" scoped>

</style>
<template>
<div>
<ul>
<li>{{ name }}</li>
<li>{{ age }}</li>
<li>{{ score.English }}</li>
<li>{{ score.Math }}</li>
</ul>
</div>
</template>

<script>
export default {
inject: ['name', 'age', 'score'], // here!
}
</script>

<style lang="sass" scoped>

</style>

3.2.6 任何组件之间的通讯(mitt事件总线)

  • 下载: npm install mitt
  • 示例:
image.png
image.png|425
import mitt from 'mitt'

const emitter = mitt();

export default emitter;
<template>
<div>
<HomeComponent></HomeComponent>
<AboutComponent></AboutComponent>
</div>
</template>

<script>
import HomeComponent from './HomeComponent.vue';
import AboutComponent from './AboutComponent.vue';
export default {
components: {
HomeComponent,
AboutComponent,
},
}
</script>

<style lang="sass" scoped>

</style>
<template>
<div>
<HomeContent></HomeContent>
</div>
</template>

<script>
import HomeContent from './HomeContent.vue';
export default {
components: {
HomeContent,
}

}
</script>

<style lang="sass" scoped>

</style>
<template>
<div>
<h1>HomeContent页面</h1>
<ul>
<li>{{ info.name }}</li>
<li>{{ info.age }}</li>
<li>{{ info.score.English }}</li>
<li>{{ info.score.Math }}</li>
</ul>
</div>
</template>

<script>
import emitter from './utils/eventbus';

export default {
data() {
return {
info: { name: '', age: 0, score: { English: 0, Math: 0, } }
}
},
created() {
// 1.监听指定type的内容
emitter.on("sendInfo", (info) => {
this.info = info;
});
// 接收方的要接受哪个名称传递来的: "sendInfo"; // 接收方如何处理: ()=>{}

// 2.全部监听
emitter.on("*", (type, value) => {
console.log("来自:", type, "内容:", value);
})

}

}
</script>

<style lang="sass" scoped>

</style>
<template>
<div>
<h1>关于页面</h1>
<tag>姓名:<input type="text" v-model="info.name"></tag>
<button @click="sendHandler">点击发送学生信息到HomeContent页面</button>
</div>
</template>

<script>
import emitter from './utils/eventbus'; // STEP1: 引入
export default {
data() {
return {
info: {
name: 'Tlyer233',
age: 18,
score: { English: 90, Math: 110, }
}
}
},
methods: {
sendHandler() {
emitter.emit("sendInfo", this.info);
// 发送方的名称: sendInfo; 发送方的数据: this.info
}
}

}
</script>

<style lang="sass" scoped>

</style>

第三节 插槽Slot

3.3.1 插槽的基本使用

  • 定义: 为什么要有插槽【图一】
  • 示例: 效果:【图二】; 目录结构:【图三】
image.png|500 image.png|215 image.png
<template>
<div>
<h1>这里是App.vue</h1>
<h2>case1:引用时不写内容</h2>
<SlotComponent></SlotComponent> <!--展示默认内容-->

<hr>
<h2>case2:引用时写内容</h2>
<SlotComponent>
<button>如果有内容,则默认内容不生效</button>
</SlotComponent>
</div>
</template>

<script>
import SlotComponent from './SlotComponent.vue';
export default {
components: {
SlotComponent,
}
}
</script>

<style lang="sass" scoped>

</style>
<template>
<div>
<h2>SlotComponent Begin</h2>
<slot><i>如果在SlotComponent里面的slot标签写东西,
<br>就是当引用没有内容的时候显示的默认内容</i>
</slot>
<h2>SlotComponent End</h2>
</div>
</template>

<script>
export default {

}
</script>

<style lang="sass" scoped>

</style>

3.3.2 具名插槽

  • 多个插槽: 插槽所在的组件中有多哥<slot>, 效果如下
    image.png
  • 具名插槽: 如果想对应放置
image.png|1050
image.png|444
<template>
<div>
<NavBar>
<template v-slot:left><button>左边</button></template>
<template v-slot:center><h1>中间</h1></template>
<template v-slot:right><i>右边</i></template>
</NavBar>
</div>
</template>

<script>
import NavBar from './NavBar.vue';
export default {
components: {
NavBar,
}

}
</script>

<style scoped></style>
<template>
<div>
<div class="left"><slot name="left">1</slot></div>
<div class="middle"><slot name="center">2</slot></div>
<div class="right"><slot name="right">3</slot></div>
</div>
</template>

<script>
export default {

}
</script>

<style scoped>
div {
display: flex;
height: 44px;
line-height: 44px;
}

.left {
width: 60px;
background-color: red;
justify-content: center;
align-items: center;
}

.middle {
flex: 1;
background-color: blue;
justify-content: center;
align-items: center;
color: white;
}

.right {
width: 60px;
background-color: red;
justify-content: center;
align-items: center;
color: white;
}
</style>

3.3.3 插槽作用域

  • 理解: 插槽组件与本组件之间的数据隔离开来的
    image.png|500
  • 传递数据: “作用域插槽”进行传递数据
    image.png|650
<template>
<div>
<h2>1.用li展示学生姓名</h2>
<ul>
<ShowNameCpn :students="students">
<template v-slot="slotProps"> <!--这个slotProps名字, 可以自己任意取-->
<li>{{ slotProps.index + 1 }}-{{ slotProps.student }}</li>
</template>
</ShowNameCpn>
</ul>

<h2>2.用tr展示学生姓名</h2>
<table border="2px">
<thead>
<th>序号</th>
<th>姓名</th>
</thead>
<tbody>
<ShowNameCpn :students="students">
<template v-slot="slotProps">
<tr>
<td>{{ slotProps.index + 1 }}</td>
<td>{{ slotProps.student }}</td>
</tr>
</template>
</ShowNameCpn>
</tbody>
</table>
</div>
</template>

<script>
import ShowNameCpn from './ShowNameCpn.vue';

export default {
data() {
return {
students: ['Tom', "Tony", "Jack", "Jerry"]
}
},
components: {
ShowNameCpn,
},


}
</script>

<style scoped></style>
<template>
<template v-for="(student, index) in students">
<slot :student="student" :index="index"></slot>
</template>
</template>

<script>
export default {
props: {
students: {
type: Array,
default: function () { return []; },
}
},
}
</script>

<style scoped></style>

子组件修改父组件变量

<PeriodCalendarPicker v-model:main-task="mainTask" />                               <!--语法糖-->
<PeriodCalendarPicker :main-task="mainTask" @update:mainTask="mainTask = $event"/> <!--两种等价-->

子组件<PeriodCalendarPicker/>

const emit = defineEmits<{
(e: 'update:mainTask', value: MainTask): void;
}>();
// 这里只是声明要触发什么事件,不会立即执行

子组件:<PeriodCalendarPicker/>

emit('update:mainTask', updatedMainTask)
// 这里实际触发事件,通知父组件

父组件: <TestPanel/>

<PeriodCalendarPicker v-model:main-task="mainTask" />
<!-- 这行代码自动处理了事件的监听和数据的更新 -->
  • Title: 1. vue3
  • Author: 明廷盛
  • Created at : 2026-02-12 01:17:04
  • Updated at : 2026-01-06 21:25:00
  • Link: https://blog.20040424.xyz/2026/02/12/😼Java全栈工程师/7. 前端部分/1. vue3/
  • License: All Rights Reserved © 明廷盛
On this page
1. vue3