vue-cli:需要版本在4.5.0以上。
npm init vite@latest
setup
是所有组合API的“表演舞台”。
组件中用到的数据、方法等等,都要配置在setup
中。也就是说不在需要单写data
、methods
、声明周期
等等。
setup
函数的两种返回值:
1、若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(重点)
<template>
<h1>App组件h1>
<p>姓名:{{name}}p>
<p>年龄:{{age}}p>
<button @click="sayHello">按钮button>
template>
<script>
export default {
name: 'App',
setup() {
let name = "Tom";
let age = 23;
const sayHello = function() {
alert(`我叫${name},今年${age}岁。`);
};
// 返回一个对象(常用)
return {
name,
age,
sayHello
};
}
}
script>
2、若返回一个渲染函数。(了解)
h
需要两个参数,第一个是元素类型,第二个是内容。
如果返回一个渲染函数,无论模板中是什么,都只渲染渲染函数中的内容。
import { h } from '@vue/runtime-core';
export default {
name: 'App',
setup() {
let name = "Tom";
let age = 23;
const sayHello = function() {
alert(`我叫${name},今年${age}岁。`);
};
// 返回一个渲染函数
return () => h('h1','哈哈哈哈')
}
}
注意:
setup
优先。setup
不能是一个async
函数。因为加入了async
,返回值就不再是对象,而是promise,模板看不到return对象中的属性。我们写一个按钮,给按钮绑定一个点击事件,想要实现点击更新页面显示信息。
<template>
<h1>App组件h1>
<p>姓名:{{name}}p>
<p>年龄:{{age}}p>
<button @click="changeInfo">修改信息button>
template>
<script>
export default {
name: 'App',
setup() {
let name = "Tom";
let age = 23;
function changeInfo() {
name = "李四";
age = 48;
}
return {
name,
age,
changeInfo,
};
}
}
script>
点击以后发现页面并没有发生改变,但是我们在changeInfo()
中打印,会发现输出的name和age已经改变,但是由于vue没有检测到数据改变,所以没有渲染到页面上。
vue3有了一个新的re
函数:
import {ref} from 'vue';
export default {
name: 'App',
setup() {
let name = ref("Tom");
let age = ref(23);
这时候我们发现点击按钮页面还是没有变化,先不在changeInfo()
里面修改数据,打印一下name,age:console.log(name.age);
发现被ref
绑定为响应式数据的name和age变成了对象。
需要加.value
触发原型对象上的getter方法改变数据。
import {ref} from 'vue';
export default {
name: 'App',
setup() {
let name = ref("Tom");
let age = ref(23);
function changeInfo() {
name.value = "李四";
age.value = 48;
console.log(name, age);
}
return {
name,
age,
changeInfo,
};
}
}
vue3的模板字符串不需要写 年龄:{{age}} 年龄:{{age。value}}.value
,当模板解析到ref
的时候,会自动加上.value
,所以我们写的
被vue解析成了
语法:cosnt xxx = ref(initValue)
xxx.value
{{xxxx}}
备注:
Object.defineProperty()
的get
和set
完成的。reactive
函数。作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref
函数)
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)给上面用ref
写的job对象,换成reactive
,打印:
import {ref, reactive} from 'vue';
export default {
name: 'App',
setup() {
let name = ref("Tom");
let age = ref(23);
let job = reactive({
type: "门卫",
salary: "0k",
});
function changeInfo() {
name.value = "李四";
age.value = 48;
console.log(job);
}
return {
name,
age,
job,
changeInfo,
};
}
}
function changeInfo() {
=======================
job.type = "保安";
job.salary = "1k";
}
上面的所有属性我们可以直接放在一个对象中,创建一个person
变量来接收这个对象,使用reactive
来使对象变为响应式。
import {reactive} from 'vue';
export default {
name: 'App',
setup() {
// let name = ref("Tom");
// let age = ref(23);
// let job = reactive({
// type: "门卫",
// salary: "0k",
// a:{
// b:{
// c: 300
// }
// }
// });
// let hobby = ["1", "2", "3"];
let person = reactive({
name: "Tom",
age: 18,
job: {
type: "门卫",
salary: "0k",
a: {
b: {
c: 300
}
},
hobby: ["吃饭", "睡觉", "打豆豆"],
}
})
function changeInfo() {
// name.value = "李四";
// age.value = 48;
// job.type = "保安";
// job.salary = "1k";
// job.a.b.c = 10101010;
// hobby[0] = 100;
person.name = "李四";
person.age = 22;
person.job.type = "保安";
person.job.salary = "1k";
person.job.a.b.c = 1008711;
person.job.hobby[2] = "烫头";
}
return {
person,
changeInfo,
};
}
}
Vue2的响应式存在一些问题:
1、新增属性、删除属性,页面不会更新。
2、直接修改数组下标,页面不会更新。
模拟响应式:
Vue2的响应式是通过Object.defineProperty()
来实现的:
let person = {
name: "张三",
age: "30"
}
// 模拟Vue2实现响应式
let p = {}
Object.defineProperty(p, "name",{
get(){
return person.name
},
set(Newvalue) {
console.log("模拟响应式修改name属性")
return person.name = Newvalue
}
})
Vue3采用es6的Proxy代理对象和Reflect反射对象来实现响应式。
let person = {
name: "张三",
age: "30"
}
const p = new Proxy(person, {
// 查
get(target, propName){
console.log(`读取了${propName}属性`)
return Reflect.get(target, propName)
},
// 改和增
set(target, propName, value){
console.log(`修改了${propName}为${value}`);
Reflect.set(target, propName, value)
},
// 删
deleteProperty(target, propName){
console.log(`删除了${propName}属性`)
return Reflect.defineProperty(target, propName)
}
})
reactive
转为代理对象。Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。.value
,读取数据时模板中直接读取不需要.value
。.value
。beforeCreate
之前执行一次,this是undefinedbeforeCreate() {
console.log("----start beforeCreate-----");
},
setup() {
console.log("-----start setup------");
console.log(this);
},
props:['age'],
setup(props) {
console.log(props);
},
this.$attrs
。this.$slots
。 vue3舍弃了slot="xxxx"
的用法,只有v-solt:xxxx
一种用法。this.$emit
。set
<template>
<h1>个人信息h1>
姓:<input type="text" v-model="person.firstName">
名:<input type="text" v-model="person.lastName">
<br>
<p>姓名:{{person.fullName}}p>
<br>
全名:<input type="text" v-model="person.fullName">
template>
<script setup>
import {reactive,computed} from "vue";
const person = reactive({
firstName:"",
lastName:"",
});
// 计算属性简写:没有考虑计算属性被修改
person.fullName = computed(() => {
return person.firstName +" " + person.lastName
});
// 计算属性完整写法,考虑计算属性被修改。是一个对象
person.fullName = computed({
get(){
return person.firstName + " " + person.lastName;
},
set(value){
const nameArr = value.split(" ")
person.firstName = nameArr[0]
person.lastName = nameArr[2]
}
})
script>
Vue3中watch
成了一个组合api,所以需要引入。Vue3中的watch
可以监视一个或者多个属性。
watch
可以接收三个参数:第一个是被监视的属性名字,第二是监视的回调函数,第二个是监视的配置:immediate
等等。
监视一个:
<template>
<h1>当前求和为:{{ sum }}h1>
<button @click="sum++">点击+1button>
template>
<script>
import { ref, watch } from "vue";
export default {
setup() {
let sum = ref(0);
// 情况1:监视ref定义的单个响应式数据
watch(sum, (newValue, oldValue)=>{
console.log("sum值变化", newValue, oldValue);
})
return {
sum,
};
},
};
script>
watch
监听的必须是响应式数据,如果是通过refs
解构出来的,则需要写成() =>
形式:
import { ref, reactive, toRefs, watch } from 'vue'
let obj = {
num: 30
}
let objRet = reactive(obj)
let { num } = toRefs(objRet)
watch(() => obj.num, () => {
})
监视多个:把被监视的属性写在一个数组里面。
// 情况2:监视ref定义的多个响应式数据
watch([sum, msg], (newValue, oldValue)=>{
console.log("sum或者msg值变化", newValue, oldValue);
})
监视的第三个参数:监视的配置
watch(sum, (newValue, oldValue)=>{
console.log("sum值变化", newValue, oldValue);
},{immediate:true})
Vue3监视属性的坑:
监视reactive的对象响应式数据,但是Vue3中存在一些bug:无法获取oldValue
:
let person = reactive({
name: "张三",
age: 23,
})
watch(person,(newValue, oldValue)=>{
console.log("变化了", newValue, oldValue);
})
执行代码会发现打印出的oldValue是错误的:
Vue3强制开启了深度监视(deep配置无效)
<template>
<h1>薪资:{{person.job.work.salary}}h1>
<button @click="person.job.work.salary++">点击涨薪button>
template>
<script>
import { ref, reactive, watch } from "vue";
export default {
setup() {
let person = reactive({
job:{
work:{
salary: 2
}
}
})
// 默认开启深度监视,无法关闭
watch(person,(newValue, oldValue)=>{
console.log("变化了", newValue, oldValue);
},{deep: false})
return {
sum,
msg,
person,
}
}
}
script>
以上代码中,我们配置了deep: false
所以person.job.work.salary
按理说应该监视不到,但是实际却是可以监听到:
监视reactive定义的一个响应式数据中的某一个属性
需要把想检测的那个属性写成方法:(该方法可以正确的监测oldValue
)
<template>
<h1>名字:{{person.name}}h1>
<h1>年龄:{{person.age}}h1>
<h1>薪资:{{person.job.work.salary}}h1>
<button @click="person.name+='e'">点击修改namebutton>
<button @click="person.age++">点击修改年龄button>
<button @click="person.job.work.salary++">点击涨薪button>
template>
<script>
import { reactive, watch } from "vue";
export default {
setup() {
let person = reactive({
name: "张三",
age: 23,
job:{
work:{
salary: 2
}
}
})
// 情况4:监视reactive所定义的一个响应式数据中的某个属性
watch(()=>person.age,(oldValue, newValue)=>{
console.log("age改变了", oldValue, newValue);
})
return {
person,
}
}
}
script>
上面代码想要监视person中的name属性,所以写了()=>person.name
来监视person中的name属性。
监视reactive中的某一个响应式数据的某些属性:(和监视ref一样,写成数组)
// 情况5:监视reactive所定义的一个响应式数据中的某些属性
watch([()=>person.age, ()=>person.name], (oldValue, newValue)=>{
console.log("age或者name改变了", oldValue, newValue);
})
特殊情况: 如果我们想监视person下的job,按照正常思路我们会写成:
// 特殊情况
watch(()=>person.job,(oldValue, newValue)=>{
console.log("person的job改变了", oldValue, newValue);
})
但是我们如果这么写的话,会发现监视不到,但是我们加上deep:true
开启深度监视,就可以监视到了:
// 特殊情况
watch(()=>person.job,(oldValue, newValue)=>{
console.log("person的job改变了", oldValue, newValue);
}, {deep: true})
总结:当我们监视的reactive中的某一个属性,但是该属性依然是一个对象的时候,需要deep:true
开启深度监视才能监视到。
watchEffect的用法是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
watchEffect有点像computed:
import { ref, reactive, watchEffect } from "vue";
export default {
setup() {
let sum = ref(0)
let msg = ref("hello")
let person = reactive({
name: "张三",
age: 23,
job:{
work:{
salary: 2
}
}
})
watchEffect(()=>{
// 使用了谁就去监听谁
const x1 = sum.value
const x2 = person.job.work.salary
console.log("指定的回调执行了", x1, x2)
})
return {
sum,
msg,
person,
}
}
}
Vue3依然可以使用Vue2的生命周期钩子,但是有两个被改名了:
beforeDestory
被更名为beforeUnmount
destroyde
被更名为unmounted
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
因为变成了函数,所以需要引入import { } from "vue"
,语法:
import { ref, onBeforeMount } from "vue"
export default {
setup() {
let sum = ref(0)
onBeforeMount(()=>{
console.log("----------onBeforeMount----------");
})
return {
sum
}
}
}
可以让一个变量接收指定的一个响应式数据的某个属性。
语法toRef(哪个对象, "对象中的哪个属性")
<template>
<h1>名字:{{name}}h1>
<h1>年龄:{{person.age}}h1>
<h1>薪资:{{salary}}h1>
<button @click="name+='e'">点击修改namebutton>
<button @click="person.age++">点击修改年龄button>
<button @click="salary++">点击涨薪button>
template>
<script setup>
import {reactive, toRef} from "vue"
let person = reactive({
name: "张三",
age: 22,
job:{
work:{
salary: 3
}
}
})
let name = toRef(person, 'name')
let salary = toRef(person.job.work, "salary")
script>
语法:toRefs(要解析的响应式对象)
与toRef
功能一致,可以批量处理多个ref
对象,如果ref
是多层嵌套,toRefs
只能解析到最外面一层:
import {reactive, toRef, toRefs} from "vue"
let person = reactive({
name: "张三",
age: 22,
job:{
work:{
salary: 3
}
}
})
let salary = toRefs(person)
console.log(salary);
浅响应式数据:只处理对象的最外层属性的响应式。
<template>
<h1>名字:{{person.name}}h1>
<h1>年龄:{{person.age}}h1>
<h1>薪资:{{person.job.work.salary}}h1>
<button @click="person.name+='e'">点击修改namebutton>
<button @click="person.age++">点击修改年龄button>
<button @click="person.job.work.salary++">点击涨薪button>
template>
<script setup>
import {shallowReactive} from "vue"
let person = shallowReactive({
name: "张三",
age: 22,
job:{
work:{
salary: 3
}
}
})
script>
只处理基本类型的响应式,不处理对象类型的响应式。
如果只像下面让 shallowRef
处理基本数据类型,那么可以处理未响应式。
<template>
<h1>{{x}}h1>
<button @click="x++">点击x+1button>
template>
<script setup>
import {shallowReactive, shallowRef} from "vue"
let x = shallowRef(0)
script>
当shallowRef
处理对象数据类型的时候,则不会变为响应式:
<template>
<h1>{{x}}h1>
<button @click="x++">点击x+1button>
template>
<script setup>
import {shallowReactive, shallowRef} from "vue"
let x = shallowRef({
y:0
})
script>
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
设置一个响应式对象为只读模式:
<template>
<h1>和为:{{sum}}h1>
<button @click="sum++">点击++button>
<hr>
<h1>名字:{{person.name}}h1>
<h1>年龄:{{person.age}}h1>
<h1>薪资:{{person.job.work.salary}}h1>
<button @click="person.name+='e'">点击修改namebutton>
<button @click="person.age++">点击修改年龄button>
<button @click="person.job.work.salary++">点击涨薪button>
template>
<script setup>
import {reactive, ref, readonly} from "vue"
let person = reactive({
name: "张三",
age: 22,
job:{
work:{
salary: 3
}
}
})
person = readonly(person)
let sum = ref(0)
script>
上面代码把person
这个响应式数据设置为了readonly
,所以不能对他进行修改,当我们尝试更改的时候,控制台会报出警告。
让一个响应式数据变为只读的(浅只读)。
<template>
<h1>和为:{{sum}}h1>
<button @click="sum++">点击++button>
<hr>
<h1>名字:{{person.name}}h1>
<h1>年龄:{{person.age}}h1>
<h1>薪资:{{person.job.work.salary}}h1>
<button @click="person.name+='e'">点击修改namebutton>
<button @click="person.age++">点击修改年龄button>
<button @click="person.job.work.salary++">点击涨薪button>
template>
<script setup>
import {reactive, ref, shallowReadonly} from "vue"
let person = reactive({
name: "张三",
age: 22,
job:{
work:{
salary: 3
}
}
})
person = shallowReadonly(person)
let sum = ref(0)
script>
上面的代码把person
定义为了浅只读,所以我们在修改person
最外层的name,age属性的时候,是不可以的,但是修改person,job.work.salary
就可以更改,因为它是嵌套在job内部。
将一个reactive
生成的响应式对象转为普通对象。
<template>
<h1>名字:{{person.name}}h1>
<h1>年龄:{{person.age}}h1>
<h1>薪资:{{person.job.work.salary}}h1>
<button @click="person.name+='e'">点击修改namebutton>
<button @click="person.age++">点击修改年龄button>
<button @click="person.job.work.salary++">点击涨薪button>
<button @click="showRawPerson">输出原始的信息button>
template>
<script setup>
import {reactive} from "vue"
let person = reactive({
name: "张三",
age: 22,
job:{
work:{
salary: 3
}
}
})
function showRawPerson() {
const p = (person)
console.log(p)
}
script>
上面定义了一个showRawPerson
的点击事件来获取原始的person。如果不加toRaw
获取的就是Proxy
代理的数据:
而我们想要获取最原始的数据,就需要const p = toRaw(person)
:
标记一个对象,使其永远不会再成为响应式对象。
<template>
<h1>名字:{{person.name}}h1>
<h1>年龄:{{person.age}}h1>
<h1>薪资:{{person.job.work.salary}}h1>
<h2 v-show="person.car">座驾:{{person.car}}h2>
<button @click="person.name+='e'">点击修改namebutton>
<button @click="person.age++">点击修改年龄button>
<button @click="person.job.work.salary++">点击涨薪button>
<button @click="showRawPerson">输出原始的信息button>
<button @click="getCar">买一辆车button>
<button @click="changeCarName">换车button>
template>
<script setup>
import {reactive, toRaw, markRaw} from "vue"
let person = reactive({
name: "张三",
age: 22,
job:{
work:{
salary: 3
}
}
})
function showRawPerson() {
const p = toRaw(person)
console.log(p)
}
function getCar() {
let car = {name: "自行车", price: 200}
person.car = markRaw(car)
}
function changeCarName() {
let newName = person.car.name += "!"
console.log(newName)
return newName
}
script>
上面我们给person.car
添加了markRaw
属性,使其不会再成为响应式对象,为了验证确实触发了点击事件,可以打印一下:
markRaw是可以更改数据,但是无法成为响应式。
readOnly直接无法更改数据。
isRef
:检查一个值是否为一个ref对象
isReactive
:检查一个对象是否由reactive
创建的响应式数据
isReadonly
:检查一个对象是否由readOnly
穿件的只读代理
isProxy
:检查一个对象是否由reactive
或者readOnly
方法创建的代理
异步引入组件
import {defineAsyncComponent} from "vue"
const Demo = defineAsyncComponent(()=>import('./components/Demo.vue'))
使用Suspense
包裹组件,并配置好default
与fallback
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中.....</h3>
</template>
</Suspense>
</div>
</template>
//注册全局组件
Vue.component('MyButton', {
data: () => ({
count: 0
}),
template: ''
})
//注册全局指令
Vue.directive('focus', {
inserted: el => el.focus()
}
Vue3.0中对这些API做出了调整:
将全局的API,即:Vue.xxx
调整到应用实例(app
)上
2.x 全局 API(Vue ) |
3.x 实例 API (app ) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
data选项应始终被声明为一个函数。
过度类名的更改:
Vue2.x写法
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
Vue3.x写法
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes
移除v-on.native
修饰符
父组件中绑定事件
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
子组件中声明自定义事件
<script>
export default {
emits: ['close']
}
</script>
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的