vue3
模板语法
1、动态参数
<!-- 动态参数 -->
<!-- 如果你的组件实例有一个数据属性 attributeName,其值为 "href",那么这个绑定就等价于 v-bind:href -->
<a:[a.href]="a.url">百度一下</a>
<!-- 自定义绑定事件名 -->
<a@[eventName]="doSomething">{{ count }}点击加1</a>
data () {
return {
rawHtml: '<spanstyle="color: red">This should be red.</span>',
href: 'href',
a: {
href: 'href',
url: 'http://www.baidu.com'
},
eventName: 'click'
};
},
methods: {
doSomething() {
console.log(1)
}
}
响应式基础
1、reactive()
<template>
<div>
<div>{{ state.count }}</div>
<button@click="addCount">点击count累加</button>
</div>
</template>
<scriptsetup>
import { reactive } from'vue';
exportdefault {
setup() {
// 利用reactive设置响应式数据
conststate=reactive({ count: 0 })
// 使count++的函数
functionaddCount() {
state.count++
}
// 暴露state,addCount
return {
state,
addCount
}
}
}
</script>
2、<script setup>
在 setup() 函数中手动暴露大量的状态和方法非常繁琐。幸运的是,我们可以通过使用构建工具来简化该操作。当使用单文件组件(SFC)时,我们可以使用 <script setup> 来大幅度地简化代码。
备注:什么是单文件组件
在 Vue 应用中,提供了 .vue 为后缀的文件,我们将这类的文件,称之为“单文件组件”,也就是说,每一个文件,都是一个单独的组件。(其实我们平时每个.vue文件都是单文件组件)
单文件组件写法
<template>
<div>
<div>{{ state.count }}</div>
<button@click="addCount">点击count累加</button>
</div>
</template>
<scriptsetup>
import { reactive } from'vue';
// 利用reactive设置响应式数据
conststate=reactive({ count: 0 })
// 使count++的函数
functionaddCount() {
state.count++
}
</script>
<stylescopedlang='less'>
</style>
3、reactive()的局限性
仅对对象类型有效(对象、数组和 Map、Set这样的集合类型),而对 string、number 和 boolean 这样的 原始类型无效。
因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:
<template>
<div>
<div>{{ state.count }}</div>
<button@click="countValue(count)">点击触发函数</button>
</div>
</template>
<scriptsetup>
import { reactive } from"vue";
/**
* 因为 Vue 的响应式系统是通过属性访问进行追踪的,
* 因此我们必须始终保持对该响应式对象的相同引用。
* 这意味着我们不可以随意地“替换”一个响应式对象,
* 因为这将导致对初始引用的响应性连接丢失:
*/
// let state = reactive({ count: 0 })
// state = reactive({ count: 1 })
letstate=reactive({ count: 0 })
// 此处的n不是响应式的
letn=state.count
n++
// count也不是响应式的
let { count } =state
count++
functioncountValue(count) {
consta=state.count++
console.log('解构后的count,失去了响应式', count)
console.log('响应式count', a)
}
</script>
4、ref()
允许创建可以使用任何值类型的响应式 ref
<template>
<div>
<!-- 模板中使用不需要加value -->
<div>ref定义响应式(简单): {{ count }}</div>
<div>ref定义响应式(复杂):{{ countObj }}</div>
<button@click="addCount">+1</button>
</div>
</template>
<scriptsetup>
import { ref } from"vue";
// 简单数据类型设置响应式
letcount=ref(100)
letcountObj=ref({ count: 1 })
letobj= {
foo: ref(1)
}
// foo是解构出来的,但也是响应式的
const { foo } =obj
functionaddCount() {
count.value++
countObj.value.count++
obj.foo.value++
console.log(countObj.value, foo.value)
}
</script>
<stylescopedlang='less'>
</style>
5、响应性语法糖(待总结)
计算属性
computed()`方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value访问计算结果。
计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value
<template>
<div>
<p>Has published books:</p>
<!-- 不使用计算属性时 -->
<div>{{ author.books.length > 0 ? 'Yes' : 'No' }}</div>
<!-- 使用计算属性时 -->
<div>{{ publishedBooksMessage }}</div>
<button@click="changeComputed">尝试修改计算属性</button>
<!-- 可修改的计算属性 -->
<div>你的名字是: {{ fullName }}</div>
</div>
</template>
<scriptsetup>
import { computed, reactive, ref } from'vue'
constauthor=reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 计算属性
letpublishedBooksMessage=computed(() => {
returnauthor.books.length>0?'Yes' : 'No'
})
constfirstName=ref('John')
constlastName=ref('Doe')
constfullName=computed({
// getter
get() {
returnfirstName.value+' '+lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] =newValue.split(' ')
}
})
constchangeComputed= () => {
fullName.value='Xiao Fu'
}
</script>
<stylescopedlang='less'>
</style>
1、计算属性和方法的区别
不考虑其他东西,两者可实现相同的效果,都可以去依赖其他项去计算
区别在于计算属性值会基于其响应式依赖被缓存,计算属性仅会在其响应式依赖更新时才会重新计算,依赖项不变,计算属性也不会发生变化,不会去重复执行getter函数
而方法总是会在重新渲染时再次执行函数,不管依赖项有没有发生改变
函数形式的计算属性是只读的,当你尝试修改一个计算属性时,你会收到一个运行时警告(官网解释,但自己试了一下没有警告,但模板渲染的值没有进行更新)
计算属性设置为对象形式时,会有get和set方法,set方法可以对计算属性进行修改,对象形式的计算属性是可读可写的,修改之后如果模板用到这个计算属性时,也会发生相应的更新
2、总结
官方建议计算属性是基于原有数据的一个派生,依赖于其他值进行变化,其他值发生变化计算属性才会跟着变化,计算属性应该是只读的,不要去直接更改它,更建议通过依赖项的改变去触发它的重新计算。
类与样式绑定
<template>
<div>
<!-- 字面量方式绑定 -->
<div:class="{ active: isActive }">
1111
</div>
<!-- 对象方式绑定 -->
<div:class="classObject">222</div>
<!-- 数组形式绑定 -->
<div:class="[activeClass, errorClass]">333</div>
</div>
</template>
<scriptsetup>
import { reactive, ref } from'vue'
constisActive=ref(true)
// 对象方式直接绑定
constclassObject=reactive({
active: true,
'text-danger': true
})
constactiveClass=ref('active')
consterrorClass=ref('text-danger')
</script>
<stylescoped>
.active {
margin: 10px;
width: 100px;
height: 100px;
background-color: orange;
}
.text-danger {
font-size: 30px;
}
</style>
条件渲染
v-if和v-show
和vue2一致
两者都能控制dom元素的显隐,区别在于
v-if实现元素显隐的原理是DOM的创建销毁,会经历组件的创建和销毁
v-show实现元素显隐的原理是进行样式的控制,通过操控display: none / block进行控制,无论元素是否显示,它都会进行渲染,在显隐切换的过程中不会经历组件的创建和销毁
因此来说,v-if有更高的切换开销,v-show有更高的渲染开销,当频繁进行切换时,v-show相对较好,当绑定条件很少改变时,v-if相对更加合适
和vue2的区别
v-if和v-for同时使用时,v-if的优先级更高
也就意味着v-if的条件无法访问到v-for作用域内定义的变量别名
不建议两个出现在一起,造成性能的浪费
列表渲染
v-for
和vue2基本一致
小妙招
假设有一个数组,数组每一项是数组,如果想要遍历小数组的每一项该怎么弄
可以先遍历大数组,v-for的item就是每一个小数组,再遍历item,item的每一项就是小数组的每一项
<ulv-for="numbers in sets">
<liv-for="n in even(numbers)">{{ n }}</li>
</ul>
<script>
constsets=ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])
functioneven(numbers) {
returnnumbers.filter((number) =>number%2===0)
}
</script>
事件处理
v-on:事件类型 / @事件类型进行触发事件执行相应的方法逻辑
可以跟一个方法,也可以编写内联JavaScript代码
方法没有参数时可以只写方法名,当有参数时可以(参数)进行传参处理
需要访问原生dom事件时,可以传入一个特殊的$event, 或者使用内联箭头函数
<template>
<div>
<!-- 使用特殊的 $event 变量 -->
<button@click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
<!-- 使用内联箭头函数 -->
<button@click="(event) => warn('Form cannot be submitted yet.', event)">
Submit
</button>
</div>
</template>
<scriptsetup>
functionwarn(a, e) {
console.log(a, e);
}
</script>
暂时未写功能描述
事件修饰符
.stop
.prevent
.self
.capture
.once
.passive
按键修饰符
.enter
.tab
.delete (捕获“Delete”和“Backspace”两个按键)
.esc
.space
.up
.down
.left
.right
系统按键修饰符
.ctrl
.alt
.shift
.meta
当出现链式使用时,例如@keyup.ctrl.alt,它会再ctrl + alt 键弹起时触发
.exact:使用系统按键修饰符不跟.exact时,指定按键触发时同时按下别的键也会触发,但加上.exact后就不会触发,有且只能指定按键触发,多一个都不行
鼠标按键修饰符
.left
.right
.middle
生命周期钩子(待定)
watch侦听器
侦听存在的数据,当数据发生变化时,可以执行一些逻辑代码
watch(侦听数据, () => { 逻辑代码编写处 })
// 侦听数据可以是单个响应式数据,也可以是getter函数或者是多个数据组成的数组
// 单个 ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter 函数
watch(
() =>x.value+y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多个来源组成的数组
watch([x, () =>y.value], ([newX, newY]) => {
console.log(`x is ${newX}and y is ${newY}`)
})
不能直接侦听响应式对象的属性,需要通过getter的方式侦听
<template>
<div>
<div>商品的数量是:{{ goods.num }}</div>
<button@click="changeNum">点击增加商品数量</button>
</div>
</template>
<scriptsetup>
import { reactive, watch } from"vue";
letgoods=reactive({
name: '饼干',
num: 5,
children: {
name: '旺旺',
num: 10
}
})
constchangeNum= () => {
goods.num++
}
// 直接侦听响应式对象,会隐式地创建一个深层侦听器,不管多深层次,一个值改变了都会触发
// watch(goods, ({ children : { num } }) => {
// console.log(num)
// })
// 直接侦听响应式对象的某个属性,会警告并且侦听器不执行
// watch(goods.num, (obj) => {
// console.log(obj)
// })
// 要侦听单个属性时,可以用getter函数的形式
watch(
() =>goods.num, (num) => {
console.log(num)
},
)
</script>
直接侦听响应式对象的属性时控制台的警告
侦听器watch有第三个参数,可开启立即执行以及深层侦听,对象的方式设置
watch(
() =>state.someObject,
(newValue, oldValue) => {
// 注意:`newValue` 此处和 `oldValue` 是相等的
// *除非* state.someObject 被整个替换了
},
{ deep: true }
)
watchEffect()
// 不需要专门侦听todoId,watchEffect会立即执行,并且也会依赖于侦听值,再本例子中当todoId发生改变时,就会触发函数进行执行
watchEffect(async () => {
constresponse=awaitfetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value=awaitresponse.json()
})
watch和watchEffect的区别
watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。
// 沿用上述例子
// watch写法
watch(() =>goods.num, () => {
console.log(num)
}, { immediate: true })
// watchEffect写法
// 相较而言更加简洁
// 当goods的num发生改变时就会触发回调中的逻辑,并且他是立即执行的,刚加载未做出改变时会立即执行一次
watchEffect(() => {
console.log(goods.num)
})
侦听器回调触发的时机
当我们更改了响应式状态,它可能会同时触发Vue组件更新和侦听器回调
默认情况下,用户创建的侦听器回调,都会在Vue组件更新之前被调用,这意味着我们在侦听器回调中访问的DOM将是被Vue更新之前的状态
想要在侦听器回调访问被Vue更新之后的DOM,需要指明flush: 'post'选项
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
// 也有一个专门的东西
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
停止侦听器
一般而言只要我们在setup()或者<script setup>中以同步的方式编写侦听器,会自动绑定到宿主组件实例,并且会在宿主组件卸载时自动停止。因此,在大多数情况下,你无需关心怎么停止一个侦听器。
但如果时异步创建的侦听器的话,他不会绑定到当前组件上,必须手动停止它,防止内存泄漏
constunwatch=watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
模板引用
ref,在vue2中可以获取到dom元素,vue3中同样如此,但写法又有所不同
组合式api中获取模板应用,需要声明一个同名的ref
<scriptsetup>
import { ref, onMounted } from'vue'
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
constinput=ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<inputref="input"/>
</template>
我们只可以在组件挂载后才能访问模板引用。如果我们想在模板中的表达式上访问 input,在初次渲染时会是 null。这是因为在初次渲染前这个元素还不存在。
如果你需要侦听一个模板引用 ref 的变化,确保考虑到其值为 null 的情况:
watchEffect(() => {
if (input.value) {
input.value.focus()
} else {
// 此时还未挂载,或此元素已经被卸载(例如通过 v-if 控制)
}
})
v-for中也可以使用模板引用
<scriptsetup>
import { ref, onMounted } from'vue'
constlist=ref([1, 2, 3])
constitemRefs=ref([])
onMounted(() => {
alert(itemRefs.value.map(i=>i.textContent))
})
</script>
<template>
<ul>
<liv-for="item in list"ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
ref 数组并不保证与源数组相同的顺序。
组件上的 ref
和vue2类似,ref绑定到子组件上时,可以获取到子组件实例,可以获取到子组件的属性以及方法
<scriptsetup>
import { ref, onMounted } from'vue'
importChildfrom'./Child.vue'
// 2、同名声明
constchild=ref(null)
onMounted(() => {
// 3、child.value 是 <Child /> 组件的实例
})
</script>
<template>
<!--1、子组件绑定ref-->
<Childref="child"/>
</template>