Vue.js学习笔记 06、Vue3新特性
@[toc]
前言
本篇博客是在学习技术胖-Vue3.x从零开始学-第一季 基础语法篇过程中记录整理的笔记,若文章中出现相关问题,请指出!
- 当前该链接失效了,有需要的小伙伴可以去b站或者技术胖官网去找相应的视频与笔记。
所有博客文件目录索引:博客目录索引(持续更新)
Composition API(基于函数组合)
在VUE2中,对于data数据、methods、生命周期都写在一个对象中这就显得十分臃肿。因为每个功能模块的代码会散落分布在各个位置,让整个项目的内容难以阅读和维护。这也导致我们需要考虑用其他方式来维护代码,比如Vuex。
Vue3
,它会根据逻辑功能来进行组织,把同一个功能的不同代码都放在一起,或者把它们单独拿出来放在一个函数中,所以Composition API
又被称为基于函数组合的API。
一、setup()函数(Composition API的入口)
setup()
:是vue3中新增的一个生命周期函数,在beforeCreate之前调用,此时组件的data和methods还没有初始化,因此在setup中是不能使用this的,并且对于setup使用只能是同步的。
注意点:在setup()内部是不能够使用this的,外部的话可以使用this来调用setup中返回的属性。
效果:能够将对象中的属性与方法等添加到Vue的实例中。
示例:
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
// 1、setup()函数使用
// props:外部组件传递过来的一些内容;context:上下文。
setup(props, context) {
console.log("setup的props=>", props);
console.log("setup的context=>", context);
console.log(this);//由于在beforeCreate之前调用,所有this执行Window对象
return {
name: 'changlu',
handleClick: () => {
alert(123);
}
}
},
beforeCreate() {
console.log("beforeCreate的this=>", this);
console.log("beforeCreate的this.$options=>", this.$options);//可以看到在$options对象中包含setup属性
},
template: `
<div @click="handleClick">{{name}}</div>
`
});
const vm = app.mount("#app");
</script>
</body>
二、ref与reactive函数使用
2.1、ref与reactive引用方式与原理
ref
与reactive
指的是什么?
- 是在Vue3中提供给我们的函数,可通过Vue类来获取到函数。
目的:在setup()生命周期中使用ref
、reactive
函数包装的基本类型或引用类型能够变为一个响应式的引用,从而实现双向绑定!
核心点:
- 外界不能直接修改内部值,只能够通过调用setup暴露出来的方法来进行修改。
- 外界拿到setup()返回的值或对象:
this.xxx
即可拿取。 - 外界调用setup()中的方法:
this.xxx()
即可。 - 外界想要绑定值到dom元素上:与绑定基本的data对象属性相同
- ref引用:
{{xxx}}
即可,可直接省略掉xxx.value
- reactive引用:
{{obj.xxx}}
即可。
- ref引用:
示例:包含ref、reactive定义与使用
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
// console.log(Vue); //Vue类中包含了ref、reactive函数
const app = Vue.createApp({
setup() {
const { ref, reactive } = Vue;//从Vue中拿到ref、reactive函数
// 1.1、ref=>处理基本类型,底层封装为proxy({value: 'changlu'})
let name = ref("changlu");
function modName(newName) {
name.value = newName;
}
// 1.2、reactive=>处理引用类型,底层封装为proxy({age: 18})
let obj = reactive({ age: 18 });
function modObjAge(newAge) {
obj.age = newAge
}
// 定义函数
const change = () => console.log("change()....");
console.log(name, obj);
return { name, obj, change, modName, modObjAge }
},
methods: {
// 外界使用setup()中定义的属性与方法
handleClick() {
this.change();//调用setup()中方法
console.log(this.name);//调用ref引用的基本类型
console.log(this.obj.age);//调用reactive包装的引用类型
}
},
// 对于ref包装后的值:想要拿到应该name.value才拿到,不过这里可以省略.value,对于ref类型可以直接name拿取
// 对于reactive包装的以及函数方法:可以直接使用
template: `
<div>{{name}}</div>
<div>{{obj.age}}</div>
<button @click="handleClick">点我触发方法</button>
<button @click="modName('liner')">点我修改name</button>
<button @click="modObjAge(20)">点我触发方法</button>
`
});
const vm = app.mount("#app");
</script>
</body>
2.2、toRefs与toRef使用(让reactive包装的引用类型解构后响应依然生效)
toRefs函数
当我们在setup()生命周期中使用reactive()来创建引用对象类型时,我们只想将其中的某个属性暴露出去,首先的思路就是通过解构赋值的方式拿到其中的属性暴露出去。
问题描述:若是仅仅使用解构赋值拿到的属性返回出去,此时若是引用对象的属性发生改变就不会有响应的效果!
原理:关注下面的正确方式
const { ref, reactive, toRefs } = Vue;
//对于obj直接进行解构赋值:没有响应的效果!
const obj = reactive({name:"changlu"});//obj: Proxy({name:"chaglu"})
let {name} = obj;//若是直接进行解构赋值 此时name="changlu",仅仅就只是拿到值,之后若是对obj.name进行修改,外界就不会响应,因为该name仅仅就只是一个字符串而已
return name;
//正确方式:使用toRefs()来完成引用类型解构
const obj = reactive({name:"changlu"});
let {name} = toRefs(obj);//核心:使用toRefs来进行包装=> name:proxy({value: 'changlu'}),此时底层就会默认将该单个值转为ref包装的对象,此时返回出现就能够响应了
return name;
示例:
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
setup() {
const { ref, reactive, toRefs } = Vue;
let obj = reactive({ name: "changlu", age: 18 });
//let { name, age } = obj; //这种形式解构赋值仅仅是将值的绑定到了对应的name与age上,不会有响应效果
let { name, age } = toRefs(obj);
// 定时2秒动态修改
setTimeout(() => {
obj.name = "liner";
obj.age = 10;
}, 2000);
return { name, age };
},
template: `
<div>{{name}}</div>
<div>{{age}}</div>
`
});
const vm = app.mount("#app");
</script>
</body>
toRef()
方式:let age = toRef(obj, 'age');
,注意这里需要写两个参数,意思是从obj对象中拿到名为age的属性,若是拿不到就赋值一个空,之后若是age值改变依旧会生效!
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
setup() {
const { ref, reactive, toRefs, toRef } = Vue;
let obj = reactive({ name: "changlu" });
//toRef()形式
let age = toRef(obj, 'age');//从obj对象中取age这个属性,若是不能取到则给它一个空依旧能够响应数据
// 定时2秒动态修改
setTimeout(() => {
age.value = 10;//需要对age对象来进行数据操作
}, 2000);
return { age };
},
template: `
<div>{{age}}</div>
`
});
const vm = app.mount("#app");
</script>
</body>
两者区别:使用方式不同,toRef()需要去指定拿属性,并且若是没有取到属性会默认赋值给一个null值,之后若是赋值依旧会响应更新到dom元素上;而对于toRefs()则会报错并且不会生效。
2.3、context属性介绍与使用(attrs、emit、slots)
介绍
这里介绍的是setup()生命周期函数中的参数context。在Context
对象中包含三个比较常用属性分别是attrs
、emit
、slots
,分别作用是拿到父组件传递的属性、内部组件向外调用执行父组件方法以及插槽。
使用
通过从context
拿到对应的三个对象实际上与之前使用$attrs
、$emit
、$slots
都大差不差,效果都是相同,通过在Context对象中拿到并使用:
示例:
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
setup(props, context) {
console.log(context);
//父组件方法
const handleChange = () => console.log("执行父组件方法");
return { handleChange };
},
template: `
<cl-item name="changlu" @change="handleChange">
I am ChangLu.
</cl-item>
`
});
app.component('cl-item', {
setup(props, context) {
const { attrs, emit, slots } = context;
// 1、attrs使用:获取父组件传递属性
console.log("attrs=>", attrs.name);
// 2、emit使用:向父组件发出执行指令
function handleClickInCL() {
emit("change");//向外传递,配合@change="handleClick"
}
// 3、slots使用:获取父组件中使用子组件里的内容
const myslots = slots.default()[0].children;
console.log("slots.default()[0].children=>", myslots);
return { myslots, handleClickInCL };
},
// 测试单击事件是否向外传递以及获取父组件传递的插槽内容
template: `
<div>
<button @click="handleClickInCL">点我一下</button>
<div>{{myslots}}</div>
</div>
`
})
const vm = app.mount("#app");
</script>
</body>
三、computed函数(Context实例中获取,计算属性)
3.1、computed参数为箭头函数
语法:
const {computed} = Vue;
let computedNum = computed(()=>{
return xxx; //只要其中的值与属性相关联,该属性一旦改变就会调用计算函数重新执行
});
示例:
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
setup() {
// 1、获取计算函数
const { ref, computed } = Vue;
const num = ref(0);
// 2、编写计算函数(只要num.value发生改变,该计算函数就会重新计算返回)
let addFive = computed(() => {
return num.value + 5;
});
setTimeout(() => {
num.value = 10; //设置一个延时器来更新num的值,来看2秒后,计算属性是否重新计算
}, 2000);
return { num, addFive };
},
// 3、对于计算函数,我们可以直接看做一个属性(其返回值为一个值)
template: `
<div>
<span>{{num}}--{{addFive}}</span>
<span>{{name}}</span>
</div>
`
});
const vm = app.mount("#app");
</script>
</body>
3.2、computed函数设置set、get方法
在computed函数可以设置set、get方法,在对computed函数重新赋值或者取值时都会触发!
- set触发:对computed函数变量赋值即可触发set以及get方法。
- get触发:将computed函数作为值在标签里响应时就会执行get()拿取,同时只要与其相关联的值更新也会触发。
示例:
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
setup() {
// 1、获取计算函数
const { reactive, computed } = Vue;
const obj = reactive({ num: 5 });
// 计算函数:添加get、set方法
// addFive=>底层是ref包装
let addFive = computed({
get: () => {
return obj.num + 5;
},
set: (param) => {
obj.num = param;
}
});
setTimeout(() => {
// 由于底层是ref包装,所以调用xxx.value才能修改值
addFive.value = 100;//即可触发set(),并传入参数。(触发set之后,由于对obj.num重新赋值就会触发get)
}, 2000);
return { obj, addFive };
},
template: `
<div>
<span>{{obj.num}}--{{addFive}}</span>
</div>
`
});
const vm = app.mount("#app");
</script>
</body>
四、watch和watchEffect函数(监听函数)
4.1、watch函数使用(指定ref、reactive属性监控及多个同时监控)
介绍
watch
函数:通过从context对象中拿取,可以对单个值或者多个值(数组)形式来进行监听,是惰性的(当监听的值发生修改时才会触发)。
语法:watch(监控属性,(currentval,preval)=>{})
,回调函数第一个是现有值,第二个是之前值。
注意点:
- 对于ref包装的基本类型,第一个参数直接设置其值即可
- 对于reactive包装的引用类型想对其某个属性监控,第一个参数需要设置为一个函数返回才可生效,如
watch(()=>obje.name,()=>{})
。 - 若是想要监控多个值,第一个参数写为数组形式如
[()=>属性1,()=>属性2]
,第二个参数函数回调函数的参数要写成数组形式如([属性1现有值,属性2现有值],[属性1改变前值,属性2改变前值])=>{}
。
示例
1、针对于ref
类型
注意点:对于ref基本类型,监控的值可以直接添加一个变量。
const app = Vue.createApp({
setup() {
const { watch, ref } = Vue;
const num = ref('');
// 1、针对于ref基本类型:具有惰性,对num值进行监控
watch(num, (curval, preval) => {
console.log(curval, preval);
});
return { num };
},
template: `
<div>
<input v-model="num" />
</div>
`
});
2、监控reactive
包装对象的某个属性
说明:这里监控对象中的两个属性
示例:对reactive包装对象的两个属性进行监控,并且实时打印值
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
setup() {
const { watch, reactive, toRefs } = Vue;
const obj = reactive({ name: "changlu", age: 18 });
// 监控reactive包装对象的属性,需要使用函数形式返回
// 若是监控多个属性,需要写成数组形式,并且回调函数也写成数组形式
watch([() => obj.name, () => obj.age], ([curval1, curlval2], [preval1, preval2]) => {
console.log("name当前与之前值=>", curval1, preval1);
console.log("age当前与之前值=>", curlval2, preval2);
});
// 解构导出指定的属性(使用toRefs保证具有相应效果)
let { name, age } = toRefs(obj);
return { name, age };
},
template: `
<div>
name:<input v-model="name" />
age: <input v-model="age" />
</div>
`
});
const vm = app.mount("#app");
</script>
</body>
4.2、watchEffect函数(无指定监控属性,自动感知内部)
watchEffect
函数:不需要指定监控属性,其会自动感知内部的属性值是否有更新,一旦有更新就会执行。
语法:watchEffect(()=>{})
,会对回调函数内部的内容进行监控。
注意:
- 立即执行,无惰性初始化会直接执行。
- 没有当前值与现有值在回调函数参数中获取。
示例:
const { reactive, watchEffect } = Vue;
const obj = reactive({ name: "changlu", age: 18 });
//仅仅只需要写回调函数内部的代码体即可,监控内部参数
watchEffect(() => {
console.log(obj.name);
console.log(obj.age);
});
说明:可以看到页面加载初始化时默认就会执行该函数体,之后也会有监控效果!
4.3、停止监听(函数.stop()即可)
watch()
函数停止:
//1、声明监听函数
const watch1 = watch(name,()=>{
xxx
//设置定时器后停止监听
setTimeout(()=>{
watch1(); //2、调用本身函数即可两秒后停止监听
},2000);
});
watchEffect()
:停止方式与watch()函数一致,都是声明后调用自己本身声明函数即可。
示例:由于watchEffect是非惰性,上来就会执行所以5秒后就会默认停止监听
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
setup() {
const { watch, reactive, toRefs, watchEffect } = Vue;
const obj = reactive({ name: "changlu", age: 18 });
// 监控reactive包装对象的属性,需要使用函数形式返回
// 若是监控多个属性,需要写成数组形式,并且回调函数也写成数组形式
// watch(() => [obj.name, obj.age], ([curval1, curlval2], [preval1, preval2]) => {
// console.log("name当前与之前值=>", curval1, preval1);
// console.log("age当前与之前值=>", curlval2, preval2);
// });
const mywatchEff = watchEffect(() => {
console.log(obj.name);
console.log(obj.age);
// 5秒后停止监听
setTimeout(() => {
mywatchEff();//函数调用一下即可停止监听
}, 5000);
});
// 解构导出指定的属性(使用toRefs保证具有相应效果)
let { name, age } = toRefs(obj);
return { name, age };
},
template: `
<div>
name:<input v-model="name" />
age: <input v-model="age" />
</div>
`
});
const vm = app.mount("#app");
</script>
</body>
说明:初始是有效监听的,过了5秒就停止监听了。
4.4、设置watch()函数为非惰性(设置参数3 {immediate: true})
回顾一下,有惰性指的是只有当指定监控的属性发生改变才会被监控到,非惰性指的是初始化时默认会自动执行一次仅此而已。
方式:添加第三个参数配置对象中的immediate
为true
。
示例:
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
setup() {
const { watch, reactive, toRefs, watchEffect } = Vue;
const obj = reactive({ name: "changlu", age: 18 });
watch([() => obj.name, () => obj.age], ([curval1, curlval2], [preval1, preval2]) => {
console.log("name当前与之前值=>", curval1, preval1);
console.log("age当前与之前值=>", curlval2, preval2);
}, {
immediate: true //立即属性
});//核心:设置第三个参数为非惰性
},
template: `
`
});
const vm = app.mount("#app");
</script>
</body>
五、生命周期函数的新写法
介绍
在Composition API中,提供了生命周期函数的新写法,同样是从Vue类中获取到对应的生命周期函数:
# 挂载前、后
beforeMount => onBeforeMount
mounted => onMounted
# 更新前、后
beforeUpdate => onBeforeUpdate
updated => onUpdated
# 失效前、后
beforeUnmount => onBeforeUnmount
unmounted => onUnmounted
//新的两个生命周期
onRenderTracked:每次渲染之后收集依赖时候自动执行的函数(第一次渲染就会执行)。
页面每次渲染之后,vue会去收集响应式的依赖,一旦需要被重新收集的时候,该函数就会自动的执行一下。
onRenderTriggered:每次重新渲染被触发的时候。(第一次渲染不会执行)
示例
说明:我们来通过在setup函数中编写调用生命周期函数来进行测试。
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
setup() {
const { onBeforeMount, onMounted,
onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted,
onRenderTracked, onRenderTriggered
} = Vue;
// 挂载前、后
onBeforeMount(() => {
console.log("onBeforeMount");
});
onMounted(() => {
console.log("onMounted");
});
//更新前、后
onBeforeUpdate(() => {
console.log("onBeforeUpdate");
});
onUpdated(() => {
console.log("onUpdated");
});
//失效前、后
onBeforeUnmount(() => {
console.log("onBeforeUnmount");
});
onUnmounted(() => {
console.log("onUnmounted");
});
//渲染之后收集依赖时
onRenderTracked(() => {
console.log("onRenderTracked");
});
//重新被渲染之后触发
onRenderTriggered(() => {
console.log("onRenderTriggered");
});
console.log(Vue);
const { ref } = Vue;
let say = ref("hello,world!");
function handleClick() {
say.value = "hello,changlu!"
}
return { say, handleClick };
},
template: `
<div @click="handleClick">{{say}}</div>
`
});
const vm = app.mount("#app");
</script>
</body>
六、provide属性与inject属性配合使用
6.1、provide与inject取值与修改值(含示例)
provide
与inject
在之前学习是用来父子组件中快速传递值的,其是单向流,子组件仅仅只能取值不能修改值。
- 注意:在Compostition API中是可以对父组件传递来的provide修改值的,所以在设置provide传递时最好设置为readonly。
示例:使用provide与inject来让子组件使用父组件的属性与方法,在设置provide的name值时将其设置为readonly,防止子组件直接去修改值。
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
//父组件
const app = Vue.createApp({
setup() {
const { provide, ref, readonly } = Vue;
const name = ref("changlu");
// 1、设置provide键值对
provide("name", readonly(name));//设置为readOnly,限制inject得到属性后修改
// 提供给子组件一个函数方法,让其能够调用父组件方法来对name进行值的修改
provide("changeName", (value) => {
name.value = value;
})
return {};
},
template: `
<cl-item></cl-item>
`
});
// 子组件
app.component('cl-item', {
setup() {
const { inject } = Vue;
// 2、通过inject拿取到,若是没有拿取到值,就会将参数2作为默认值
let name = inject("name", "liner");
const changeName = inject("changeName");
// name.value = "tiantian"; //子组件不应该对父组件进行修改
return { name, changeName };
},
template: `
<div>{{name}}</div>
<button @click="changeName('liner')">修改name</button>
`
})
const vm = app.mount("#app");
</script>
</body>
七、通过ref获取真实的Dom元素节点
大致流程:回顾之前获取dom元素是通过$refs.xxx形式拿取到的,在这里的话通过定义一个ref包装类型暴露出去,在指定的dom元素中设置ref=xxx,接着我们在setup()中定义onmounted()生命周期来获取到!
示例:
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
//父组件
const app = Vue.createApp({
setup() {
const { ref, onMounted } = Vue;
// 1、设置ref包装基本类型,传入null
const divDom = ref(null);
// 设置挂载后的生命周期函数
// 4、在生命周期函数中(挂载后)拿取到指定dom元素中的值
onMounted(() => {
console.log(divDom);
console.log(divDom.value);
// 最终拿到dom元素中的内容
console.log(divDom.value.textContent);
})
// 2、将该类型暴露出去
return { divDom };
},
// 3、在指定的dom元素上设置ref键值对
template: `
<div ref="divDom">changluya</div>
`
});
const vm = app.mount("#app");
</script>
</body>
实际应用
1、todolist(包含增、删)
案例:将对应的各个模块部分内容抽离到一个函数进行返回,接着配合setup()实现todolist!
示例如下:其实对应input输入框直接使用v-model绑定即可,这里的话为了巩固学习就使用绑定监听事件。
<body>
<div id="app"></div>
<script src="https://unpkg.com/vue@3.1.5/dist/vue.global.js"></script>
<script>
// 列表值及列表相关操作
const listRelativeEffect = () => {
const { reactive } = Vue;
const items = reactive([]);
// 提交、删除
const handleSubmit = (value) => {
items.push(value);
};
const handleDel = (index) => {
items.splice(index, 1);
};
return { items, handleSubmit, handleDel };
};
// 输入值及相关的内容绑定
const inputRelativeEffect = () => {
const { ref } = Vue;
let inputvalue = ref("");
// 输入函数
const handleInput = (e) => {
inputvalue.value = e.target.value;//对于ref包装的基本值需要使用xxx.value来设置
}
return { inputvalue, handleInput };
};
const app = Vue.createApp({
setup() {
let { inputvalue, handleInput } = inputRelativeEffect();
let { items, handleSubmit, handleDel } = listRelativeEffect();
return { inputvalue, items, handleInput, handleSubmit, handleDel };
},
template: `
<div>
<input type="text" @input="handleInput" :value="inputvalue"/>
<button @click="()=>handleSubmit(inputvalue)">提交</button>
<ul>
<li v-for="(item,index) of items" :key="index">
{{item}}
<button @click="handleDel(index)">删除</button>
</li>
</ul>
</div>
`
});
const vm = app.mount("#app");
</script>
</body>
- 点赞
- 收藏
- 关注作者
评论(0)