一、Vue3初体验
1、初体验案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue3+ts</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app"></div>
<script>
/**
* 1、Vue的引入方式
* - CDN
* - 下载到本地
* - npm
* - Vue CLI
* 2、编程范式
* - 命令式(原生:how to do)
* - 声明式(Vue:what to do,how由框架完成)
* 3、模型
* - MVC:Model-View-Controller
* - MVVM:Model-View-ViewModel
*/
Vue.createApp({
template: `
<div>{{message}}</div>
<button @click="btnClick">按钮</button>
`,
// data:Vue3必须是函数,Vue2可以是对象(会报警告)
data() {
return {
message: "黄婷婷"
}
},
methods: {
btnClick() {
this.message = '孟美岐'
}
}
}).mount("#app")
</script>
</body>
</html>
2、template写法
<div id="app"></div>
<!--写法一-->
<script type="x-template" id="comp">
<div>{{message}}</div>
<button @click="btnClick">按钮</button>
</script>
<!--写法二-->
<template id="comp">
<div>{{message}}</div>
<button @click="btnClick">按钮</button>
</template>
<script>
Vue.createApp({
template: "#comp",
data() {
return {
message: "黄婷婷"
}
},
methods: {
btnClick() {
this.message = '孟美岐'
}
}
}).mount("#app")
</script>
3、调试vue3源码
* 下载Vue3源码:
- https://codeload.github.com/vuejs/core/zip/refs/tags/v3.0.11
- 初始化git版本库
* Vue3.0.11使用yarn包管理工具:
- npm install yarn -g
- yarn install
* package.json脚本修改:
- "dev": "node scripts/dev.js --sourcemap"
- yarn dev
* html引入:
- packages/vue/dist/vue.global.js
二、Vue3基本指令
1、methods方法绑定this
* 问题一:为什么不能使用箭头函数?
- 箭头函数不绑定this,this会指向window
* 问题二:this的指向是什么?
- this指向组件实例的代理对象
(packages/runtime-core/src/componentOptions.ts:627)
2、webstorm设置文件模板
* file -> settings -> file and code templates
- name:模板名
- extension:扩展名
- enable live templates:启用实时模板(右击创建文件时显示文件模板)
3、双大括号语法
<template id="comp">
<!--
* 双大括号语法(mustache语法):
- data函数返回的对象的属性
- 表达式
- methods中的方法(需手动调用)
- computed中的方法(无需手动调用)
- 三元运算符
* mustache语法错误用法:
- 赋值语句
- if语句
-->
<div>{{message}}</div>
</template>
4、v-once
<template id="comp">
<!-- 元素或者组件以及其所有的子元素只渲染一次 -->
<div v-once>{{message}}</div>
</template>
5、v-text
<template id="comp">
<div>{{message}}</div>
<!-- 等价 -->
<div v-text="message"></div>
</template>
6、v-html
<template id="comp">
<!-- 解析成html -->
<div v-html="message"></div>
</template>
7、v-pre
<template id="comp">
<!-- 元素和它的子元素不解析 -->
<div v-pre>{{message}}</div>
</template>
8、v-cloak
<template id="comp">
<!-- 解析之前会有v-cloak属性,解析之后会去掉v-cloak属性,
常和css规则如[v-clock]{display:none}一起用 -->
<div v-cloak>{{message}}</div>
</template>
三、v-bind和v-on
1、v-bind基本使用
<!-- vue2 template标签只能有一个根元素
vue3 template标签可以有多个根元素 -->
<template id="comp">
<!-- 对属性值进行动态绑定 -->
<img v-bind:src="imgSrc">
<!-- v-bind语法糖: -->
<a :href="link"></a>
</template>
2、v-bind绑定class
<template id="comp">
<!-- 对象语法:{active:boolean} -->
<div :class="{static:true,active:true}">黄婷婷</div>
<!-- 数组语法:['static',{active:boolean}] -->
<div :class="['static',{active:true}]">孟美岐</div>
<!-- 动态绑定的class可与普通的class属性共存 -->
<div class="static" :class="{active:true}">姜贞羽</div>
</template>
3、v-bind绑定style
<template id="comp">
<!-- 对象语法(对象属性有驼峰和短横线两种命名方式) -->
<div :style="{fontWeight:900,'font-size':'18px'}">黄婷婷</div>
<!-- 数组语法 -->
<div :style="[{fontWeight:900},{'font-size':'18px'}]">孟美岐</div>
<!-- 动态绑定与普通属性共存 -->
<div style="font-weight: 900" :style="{fontSize:'18px'}">姜贞羽</div>
</template>
4、v-bind动态属性名
<template id="comp">
<div :[name]="value"></div>
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
name: "attr",
value: "黄婷婷"
}
}
}).mount('#app')
</script>
5、v-bind值为对象
<template id="comp">
<div v-bind="obj"></div>
<div :="obj"></div>
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
obj: {
attr: "孟美岐",
}
}
}
}).mount('#app')
</script>
6、v-on基本使用
<template id="comp">
<!-- 完整语法:v-on:事件="methods的方法" -->
<button v-on:click="clickHandler">按钮</button>
<!-- 语法糖 -->
<button @click="clickHandler">按钮</button>
<!-- 绑定一个表达式:inline statement -->
<button @click="person='张婧仪'">按钮</button>
<!-- 绑定一个对象 -->
<button v-on="{click:clickHandler,mousemove:mousemoveHandler}">按钮</button>
<button @="{click:clickHandler,mousemove:mousemoveHandler}">按钮</button>
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
person: "黄婷婷"
}
},
methods: {
clickHandler() {
console.log('孟美岐')
},
mousemoveHandler() {
console.log('姜贞羽')
},
}
}).mount('#app')
</script>
7、v-on参数传递
<template id="comp">
<!-- 不传参时,默认传入Event对象 -->
<button @click="clickHandler">按钮</button>
<!-- 传参时,需通过$event传入Event对象 -->
<button @click="clickHandler($event,'孟美岐')">按钮</button>
</template>
8、v-on修饰符
<template id="comp">
<!--
.stop:调用event.stopPropagation()
.prevent:调用event.preventDefault()
.{keyAlias}:仅当事件是从特定键触发时才触发回调
.capture:添加事件侦听器时使用capture模式
.self:只当事件是从侦听器绑定的元素本身触发时才触发回调
.once:只触发一次回调
.left:只当点击鼠标左键时触发
.right:只当点击鼠标右键时触发
.middle:只当点击鼠标中键时触发
.passive:{passive:true}模式添加侦听器
-->
<div @click="divClick">
<button @click.stop="btnClick">按钮</button>
</div>
<input type="text" @keyup.enter="iptHandler">
</template>
四、条件渲染
1、v-if
<template id="comp">
<div v-if="true">黄婷婷</div>
<div v-else-if="true">孟美岐</div>
<div v-else>姜贞羽</div>
</template>
2、v-if和template结合使用
<template id="comp">
<!-- 类似小程序的block,template元素不会被渲染出来 -->
<template v-if="true">
<div>黄婷婷</div>
</template>
</template>
3、v-if和v-show区别
<template id="comp">
<!--
* 用法区别:
- v-show不支持template
- v-show不可以和v-else一起使用
* 本质区别:
- v-show通过css的display控制显示隐藏
- v-if为false时DOM不渲染
* 如何选择:
- 显示隐藏需要频繁切换则使用v-show
-->
<div v-if="false">黄婷婷</div>
<div v-show="false">孟美岐</div>
</template>
五、列表渲染
1、v-for
<template id="comp">
<!-- 遍历数组 -->
<div v-for="(item,index) in arr">{{item}}-{{index}}</div>
<!-- 遍历对象 -->
<div v-for="(value,key,index) in obj">{{value}}-{{key}}-{{index}}</div>
<!-- 遍历数字 -->
<div v-for="(num,index) in 3">{{num}}-{{index}}</div>
<!-- in可用of替代 -->
<div v-for="(num,index) of 3">{{num}}-{{index}}</div>
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
arr: ['黄婷婷', "孟美岐", "鞠婧祎"],
obj: {
name: "姜贞羽",
age: 18,
gender: "女"
}
}
},
methods: {}
}).mount('#app')
</script>
2、v-for和template结合使用
<template id="comp">
<template v-for="(item,index) in arr">
<div>{{item}}-{{index}}</div>
</template>
</template>
3、数组检测
<template id="comp">
<!--
* 会改变原数组的方法,Vue对这些方法进行了包裹,它们会触发视图的更新
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
* 不会改变原数组的方法,可以通过重新给数组赋值的方式触发视图更新
- filter()
- concat()
- slice()
-->
<div v-for="(item,index) in arr">{{item}}-{{index}}</div>
</template>
4、v-for中的key
<template id="comp">
<!--
一、key属性的作用(packages/runtime-core/src/renderer.ts:1606)
* 主要用做Vue的虚拟DOM算法的提示,以在比对新旧节点组时辨识VNodes。
* 如果不使用key,Vue会使用一种算法来最小化元素的移动并且尽可能尝试就地修改/复用相同类型元素。
1、获取旧VDOM和新VDOM的长度,取小的VDOM的长度用以遍历
2、从0位置开始依次(patch(n1,n2))比较,如果VNode(n1、n2)对比有差异则进行更新
3、旧VDOM长度大于新VDOM长度,则移除剩余的VNode,反之则创建新的VNode
* 而使用key时,它会基于key的顺序变化重新排列元素,并且那些使用了已经不存在的key的元素将会被移除/销毁。
1、新旧VDOM同时从头部开始遍历(patch),判断VNode的type和key是否有差异,不同则直接跳出循环
2、新旧VDOM同时从尾部开始遍历(patch),判断VNode的type和key是否有差异,不同则直接跳出循环
3、如果旧VDOM遍历完,新VDOM还有VNode没有遍历,则新增节点(patch(n1,n2),当n1为null时表示是挂载)
4、如果新VDOM遍历完,旧VDOM还有VNode没有遍历,则移除节点(卸载)
5、如果新旧VDOM都没有遍历完(是未知序列):
- 遍历新VDOM剩余的VNode,剩余VNode的key作为键,索引作为值,存入Map
- 遍历旧VDOM剩余的VNode,根据Map判断,哪些VNode可以复用,
并在长度是新VDOM剩余VNode长度的数组中做标记,哪些需要卸载
- 新VDOM剩余的VNode可根据数组中的复用标记,决定哪些VNode要挂载,
哪些需要移动(使用最长递增子序列算法,以最小成本移动)
二、认识VNode
* 全称Virtual Node,也就是虚拟节点
* VNode本质是一个JavaScript对象
Node:<div class="title" style="color:red">黄婷婷</div>
VNode:{
type:"div",
props:{
class:"title",
style:{
color:"red"
}
},
children:["黄婷婷"]
}
三、虚拟DOM(VDOM):多个VNode形成的树结构
-->
<div v-for="item in arr" :key="item.id">{{item.name}}</div>
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
arr: [{
id: "18",
name: "黄婷婷"
}, {
id: "19",
name: "孟美岐"
}, {
id: "20",
name: "鞠婧祎"
}]
}
}
}).mount('#app')
</script>
六、Vue3的Options-API
1、计算属性computed
<template id="comp">
<div>{{person+"婷婷"}}</div>
<div>{{personCompute}}</div>
<div>{{personMethod()}}</div>
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
person: "黄"
}
},
/**
* 一:使用计算属性的好处
* - 对比插值语法:计算属性可对模板中的复杂逻辑进行封装
* - 对比methods:计算属性有缓存
* 二:计算属性中所有的getter和setter的this上下文自动地绑定为组件实例
* 三:计算属性的缓存:计算属性会基于它们的依赖关系进行缓存。
* 数据不发生变化,计算属性不需要重新计算。
* 如果依赖的数据发生变化,计算属性会重新进行计算。
*/
computed: {
personCompute() {
return this.person + "婷婷"
}
},
methods: {
personMethod() {
return this.person + "婷婷"
}
}
}).mount('#app')
</script>
2、计算属性的getter和setter
<script>
Vue.createApp({
template: '#comp',
data() {
return {
person: "黄婷婷"
}
},
computed: {
/**
* (packages/runtime-core/src/componentOptions.ts:668)
* 1、personCompute(){return ""}是getter的语法糖
* 2、personCompute=""会调用setter方法
*/
personCompute: {
get() {
return this.person
},
set(val) {
this.person = val
}
}
}
}).mount('#app')
</script>
3、侦听器watch
<script>
Vue.createApp({
template: '#comp',
data() {
return {
person: "黄婷婷"
}
},
watch: {
/**
* 侦听器常用于数据变化时,进行一些逻辑的处理(执行函数、网络请求)
* person:可以是data option,也可以是computed option
* newValue:新的值
* oldValue:旧的值
*/
person(newValue, oldValue) {
console.log(newValue, oldValue)
}
}
}).mount('#app')
</script>
4、watch的配置选项
<template id="comp">
<!-- v-model是input事件实现双向绑定
v-model.lazy是change事件实现双向绑定 -->
<input type="text" v-model.lazy="person.name">
<hr>
<input type="text" v-model="address">
<hr>
<input type="text" v-model="friend">
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
person: {name: "黄婷婷", age: 18},
address: "",
friend: ""
}
},
watch: {
person: {
handler(newValue, oldValue) {
// 修改对象本身:false,修改的是对象内部值时:true
console.log(newValue === oldValue)// true
},
deep: true,// 侦听对象内部值的变化,默认false
immediate: true// 立即以当前值触发回调,默认false
},
address: 'addressInput',// 对应methods option
friend: [// 会被逐一调用
"friendInput",
function (newValue, oldValue) {
console.log(newValue, oldValue)
},
{
handler(newValue, oldValue) {
console.log(newValue, oldValue)
},
deep: false,
immediate: false
}
]
},
methods: {
addressInput(newValue, oldValue) {
console.log(newValue, oldValue)
},
friendInput(newValue, oldValue) {
console.log(newValue, oldValue)
}
}
}).mount('#app')
</script>
5、watch其他方式
<template id="comp">
<input type="text" v-model="person.name">
<hr>
<input type="text" v-model="address">
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
person: {name: "黄婷婷", age: 18},
address: "",
firends: [{name: "孟美岐"}]
}
},
watch: {
// 侦听对象的属性
"person.name": function (newValue, oldValue) {
console.log(newValue, oldValue)
},
// 侦听数组的元素
"firends.0.name": function (newValue, oldValue) {
console.log(newValue, oldValue)
}
},
created() {
this.firends[0].name = "姜贞羽"
/**
* 1、"person.age"不能侦听
* 2、参数2可以是箭头函数
* 3、返回值是函数,调用该函数可取消侦听
*/
const unwatch = this.$watch("address", (newValue, oldValue) => {
console.log(newValue, oldValue)
}, {
deep: false,
immediate: false
})
// unwatch()
}
}).mount('#app')
</script>
七、Vue3的表单
1、v-model的基本使用
<!-- <input type="text" :value="searchText" @input="searchText=$event.target.value"> -->
<!-- 等价(packages/runtime-dom/src/directives/vModel.ts:53) -->
<input type="text" v-model="searchText">
2、v-model绑定其他表单
<template id="comp">
<!-- 1、textarea -->
<label>自我介绍:<textarea rows="3" cols="20" v-model="intro"></textarea></label>
<h2>intro:{{intro}}</h2>
<!-- 2、checkbox -->
<!-- 2.1、单选框 -->
<label><input type="checkbox" v-model="isAgree">统一协议</label>
<h2>isAgree:{{isAgree}}</h2>
<!-- 2.2、多选框 -->
<span>你的爱好:</span>
<label><input type="checkbox" value="basketball" v-model="hobbies">篮球</label>
<label><input type="checkbox" value="football" v-model="hobbies">足球</label>
<label><input type="checkbox" value="tennis" v-model="hobbies">网球</label>
<h2>hobbies:{{hobbies}}</h2>
<!-- 3、radio -->
<span>你的爱好:</span>
<label><input type="radio" value="male" v-model="gender">男</label>
<label><input type="radio" value="female" v-model="gender">女</label>
<h2>gender:{{gender}}</h2>
<!-- 4、select -->
<select v-model="fruit" multiple size="2">
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
<h2>fruit:{{fruit}}</h2>
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
intro: "孟美岐",
isAgree: false,
hobbies: [],
gender: "",
fruit: []
}
}
}).mount('#app')
</script>
3、v-model的修饰符
<template id="comp">
<!-- 1、lazy修饰符:触发的是onchange事件 -->
<!-- <input type="text" v-model.lazy="message"> -->
<!-- <div>{{message}}</div> -->
<!-- 2、number修饰符
- 数字字符串与number类型进行逻辑判断时(不考虑===),
数字字符串会隐式转化成number类型
- 值只取开头连续的数字,为number类型 -->
<!-- <input type="text" v-model.number="message"> -->
<!-- <div>{{message}} | {{typeof message}}</div> -->
<!-- 3、trim修饰符:两边去除空格 -->
<input type="text" v-model.trim="message">
</template>
<script>
Vue.createApp({
template: '#comp',
data() {
return {
message: ""
}
},
watch: {
message() {
console.log(this.message)
}
}
}).mount('#app')
</script>
八、Vue3的组件化(一)
1、注册全局组件
<template id="my-app">
<component-a></component-a>
<component-a></component-a>
</template>
<template id="component-a">
<button @click="btnClick">{{btnText}}</button>
<hr>
</template>
<script>
const app = Vue.createApp({template: '#my-app'})
// 注册全局组件:全局组件可在任何组件模板中使用。可注册多个全局组件
app.component("component-a", {
template: '#component-a',
data() {
return {
btnText: "按钮"
}
},
methods: {
btnClick() {
console.log("点击按钮")
}
}
})
app.mount('#app')
</script>
2、组件命名方式
* 短横线分割符(kebab-case):使用时<my-component-name>
* 驼峰标识符(PascalCase):使用时<my-component-name>或<MyComponentName>。
直接在DOM中使用时,只有<my-component-name>有效
3、注册局部组件
<template id="my-app">
<component-a></component-a>
<component-a></component-a>
</template>
<template id="component-a">
<button @click="btnClick">{{btnText}}</button>
<hr>
</template>
<script>
const app = Vue.createApp({
template: '#my-app',
// 注册局部组件:只能在#my-app组件模板中使用。
// key:组件名;value:组件对象
components: {
ComponentA: {
template: '#component-a',
data() {
return {
btnText: "按钮"
}
},
methods: {
btnClick() {
console.log("点击按钮")
}
}
}
}
})
app.mount('#app')
</script>