共计 35032 个字符,预计需要花费 88 分钟才能阅读完成。
天命有余畏,祖宗有余法。——王安石
前言
本文并非题目党,而是实实在在的硬核文章,如果有想要学习 Vue3 的网友,能够大抵的浏览一下本文,总体来说本篇博客涵盖了 Vue3 中绝大部分内容,蕴含罕用的 CompositionAPI(组合式 API)、其它 CompositionAPI 以及一些新的个性:Fragment、Teleport、Suspense、provide 和 inject。
我的项目搭建
既然是学习 Vue3,那么首先应该须要的是如何初始化我的项目,在这里提供了两种形式供大家参考
- 形式一:vue-cli 脚手架初始化 Vue3 我的项目
官网文档:https://cli.vuejs.org/zh/guid…
// 查看 @vue/cli 版本,确保 @vue/cli 版本在 4.5.0 以上
vue --version
// 装置或者降级你的 @vue/cli
npm install -g @vue/cli
// 创立
vue create vue_test
// 启动
cd vue_test
npm run serve
- 形式二:vite 初始化 Vue3 我的项目
vite 官网:https://vitejs.cn/
// 创立工程
npm init vite-app <project-name>
// 进入工程目录
cd <project-name>
// 装置依赖
npm install
// 运行
npm run dev
我的项目目录构造剖析
这里的我的项目目录构造剖析次要是 main.js 文件
-
Vue2 外面的 main.js
new Vue({ el: '#app', components: {}, template: '' });
-
Vue3 外面的 main.js
import {createApp} from 'vue' import App from './App.vue' createApp(App).mount('#app')
在 Vue2 外面,通过 new Vue({})构造函数创立利用实例对象,而 Vue3 引入的不再是 Vue 的构造函数,引入的是一个名为 createApp 的工厂函数创立利用实例对象。
Vue3-devtool 获取
devtool:https://chrome.zzzmh.cn/info?…
Composition API
setup
- 了解:Vue3.0 中一个新的配置项,值为一个函数
- setup 是所有 Composition API(组合式 API)的入口
- 组件中所用到的数据、办法等等,均要配置在 setup 外面
-
setup 函数的两种返回值
- 若返回一个对象,则对象中的属性、办法,在模板中均能够间接应用
- 若返回一个渲染函数,则能够自定义渲染内容
-
setup 的执行机会
- 在 beforeCreate 之前执行一次,此时 this 为 undefined
-
setup 的参数
props:值为对象,蕴含:组件内部传递过去,且组件外部申明接管了的属性
context:上下文对象
- attrs:值为对象,蕴含:组件内部传递过去,但没有在 props 配置中申明的属性,相当于 this.$attrs
- slots:收到的插槽内容,相当于 this.$slots
- emit:散发自定义事件的函数,相当于 this.$emit
注意事项:
-
尽量不要与 Vue2x 的配置应用
- Vue2x 的配置 (data、methods、computed) 均能够拜访到 setup 中的属性、办法
- setup 中不能拜访 Vue2x 的配置(data、methods、computed)
- 如果 data 外面的属性和 setup 外面的属性有重名,则 setup 优先
- setup 不能是一个 async 函数,因为返回值不再是 return 的对象,而是 Promise, 模板看不到 return 对象中的属性,然而前期也能够返回一个 Promise 实例,须要 Suspense 和异步组件的配合
示例一:setup 函数的两种返回值
<template>
<h2> 练习 setup 相干内容 </h2>
<!--<h2>setup 返回一个对象,并应用对象中的属性和办法 </h2>-->
<!--<p> 姓名:{{student.name}}</p>-->
<!--<p> 年龄:{{student.age}}</p>-->
<!--<button @click="hello"> 点击查看控制台信息 </button>-->
<hr>
<h2>setup 返回一个函数 </h2>
</template>
<script>
import {h} from 'vue'
export default {
name: "setupComponent",
setup(){
// 属性
let student={
name:'张三',
age:18,
}
// 办法
function hello() {console.log(` 大家好,我叫 ${student.name}, 往年 ${student.age}`)
}
return{ // 返回一个对象
student,
hello,
}
// return()=>h('h1','你好') // 返回一个函数
}
}
</script>
<style scoped>
</style>
这里须要留神的是 setup 外面定义的 属性和办法均要 return 进来,否则无奈应用
示例二:setup 外面的参数和办法和配置项混合应用
<template>
<h2>setup 和配置项混用 </h2>
<h2> 姓名:{{name}}</h2>
<h2> 年龄:{{age}}</h2>
<h2> 性别:{{sex}}</h2>
<button @click="sayHello">sayHello(Vue3 外面的办法)</button>
<button @click="sayWelcome">sayWelcome(Vue2 外面的办法)</button>
</template>
<script>
export default {
name: "setup01_component",
data(){
return{
sex:'男',
sum:0,
}
},
methods:{sayWelcome(){console.log(`sayWelcome`)
},
},
setup(){
let sum=100;
let name='张三';
let age=18;
function sayHello() {console.log(` 我叫 ${name},往年 ${age}`)
}
return{
name,
age,
sayHello,
test02,
sum
}
}
}
</script>
<style scoped>
</style>
这段代码是先实现了 setup 外面的属性和办法,以及 Vue2 中配置项外面的属性和办法。接下来增加对应的混合办法
<template>
<h2>setup 和配置项混用 </h2>
<h2> 姓名:{{name}}</h2>
<h2> 年龄:{{age}}</h2>
<h2> 性别:{{sex}}</h2>
<button @click="sayHello">sayHello(Vue3 外面的办法)</button>
<button @click="sayWelcome">sayWelcome(Vue2 外面的办法)</button>
<br>
<br>
<button @click="test01"> 测试 Vue2 外面调用 Vue3 外面的属性和办法 </button>
<br>
<br>
<button @click="test02"> 测试 Vue3setup 外面调用 Vue2 外面的属性和办法 </button>
<br>
<h2>sum 的值是:{{sum}}</h2>
</template>
<script>
export default {
name: "setup01_component",
data(){
return{
sex:'男',
sum:0,
}
},
methods:{sayWelcome(){console.log(`sayWelcome`)
},
test01(){console.log(this.sex); // Vue2 外面的属性(data 外面的属性)
// setup 外面的属性
console.log(this.name);
console.log(this.age);
// setup 外面的办法
this.sayHello();}
},
setup(){
let sum=100;
let name='张三';
let age=18;
function sayHello() {console.log(` 我叫 ${name},往年 ${age}`)
}
function test02() {
// setup 外面的属性
console.log(name);
console.log(age);
// data 外面的属性
console.log(this.sex);
console.log(this.sayWelcome);
}
return{
name,
age,
sayHello,
test02,
sum
}
}
}
</script>
<style scoped>
</style>
这里新增了 test01 和 test02 办法,别离点击,控制台能够看到,点击配置项外面 test01 办法时,除了本身的 sex 属性有值,setup 外面定义的属性也有值以及办法也能够调用,点击 setup 外面定义的 test02 办法时,控制台只能输入 setup 外面定义的属性和办法,而配置项外面定义的属性和办法值均为 undefined。
- setup 外面定义的属性和办法均能够在配置项外面应用(methods、computed、watch 等),而配置项外面定义的属性和办法无奈在 setup 外面调用
- 如果 setup 外面的属性和 data 外面的属性有重名,则 setup 外面的属性优先
示例三:setup 的执行机会
setup 会在 beforeCreate 之前执行一次
<template>
<h2>setup 的执行机制 </h2>
</template>
<script>
export default {
name: "setup_component03",
setup(){console.log('setup')
},
beforeCreate(){console.log('beforeCreate')
}
}
</script>
<style scoped>
</style>
查看控制台的话咱们看到的程序是 setup>beforeCreate
setup 外面 context 和 props 的应用
Vue2 外面 props 和 slot 的应用
解说 setup 这外面的两个参数之前,先回顾一下 Vue2 外面的相干常识
- props 和自定义事件的应用
- attrs
- slot(插槽)
示例一:Vue2props 和自定义事件的应用
筹备两个组件,别离为 parent.vue 组件和 child.vue 组件
parent.vue
<template>
<div class="parent">
我是父组件
<child msg="传递信息" name="张三" @sendParentMsg="getMsg"/>
</div>
</template>
<script>
import Child from "./Child";
export default {
name: "Parent",
components: {Child},
methods:{getMsg(msg){console.log(msg)
}
}
}
</script>
<style scoped>
.parent{
padding: 10px;
background-color: red;
}
</style>
child.vue
<template>
<div class="child">
<h2> 我是子组件 </h2>
<p> 父组件传递过去的音讯是:{{msg}}</p>
<p> 父组件传递过去的音讯是:{{name}}</p>
<button @click="sendMsg"> 向父组件的传递信息 </button>
</div>
</template>
<script>
export default {
name: "Child",
props:{
msg:{
type:String,
default:''
},
name:{
type:String,
default:''
}
},
mounted(){console.log(this);
},
methods:{sendMsg(){this.$emit("sendParentMsg",'告诉父组件更新')
}
}
}
</script>
<style scoped>
.child{
padding: 10px;
background-color: orange;
}
</style>
child 组件对应的代码如下:
<template>
<div class="child">
<h2> 我是子组件 </h2>
<!--<p> 父组件传递过去的音讯是:{{msg}}</p>-->
<!--<p> 父组件传递过去的音讯是:{{name}}</p>-->
<p> 父组件传递过去的音讯是:{{$attrs.msg}}</p>
<p> 父组件传递过去的音讯是:{{$attrs.name}}</p>
<button @click="sendMsg"> 向父组件的传递信息 </button>
</div>
</template>
<script>
export default {
name: "Child",
// props:{
// msg:{
// type:String,
// default:''
// },
// name:{
// type:String,
// default:''
// }
// },
mounted(){console.log(this);
},
methods:{sendMsg(){this.$emit("sendParentMsg",'告诉父组件更新')
}
}
}
</script>
<style scoped>
.child{
padding: 10px;
background-color: orange;
}
</style>
子组件通过 props 接管父组件传递的信息,通过 this.$emit()自定义事件向父组件传递信息。当应用 props 接收数据的时候,attrs 外面的数据为空,如果没有应用 props 接收数据的话,那么 props 外面就有值。
示例二:Vue2 外面 slot 的应用
同理筹备两个组件,一个 Index.vue 组件,另一个为 MySlot.vue 组件
Index.vue
<template>
<div class="index">
<h2> 我是 Index 组件 </h2>
<!-- 写法一 -->
<my-slot>
<!-- 插槽外面的内容 -->
<h2> 传入的 slot 参数 </h2>
<h2> 传入的 slot 参数 </h2>
<h2> 传入的 slot 参数 </h2>
<h2> 传入的 slot 参数 </h2>
</my-slot>
<!-- 写法二 -->
<my-slot>
<template slot="header">
<h2> 我是 header 组件 </h2>
</template>
<template slot="footer">
<h2> 我是 footer 附件 </h2>
</template>
</my-slot>
</div>
</template>
<script>
import MySlot from "./MySlot";
export default {
name: "Index",
components: {MySlot}
}
</script>
<style scoped>
.index{
padding: 10px;
background: red;
}
</style>
MySlot.vue
<template>
<div class="slot">
<h2> 我是 MySlot 组件 </h2>
<slot></slot>
<br>
<slot name="header"></slot>
<br>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name: "MySlot",
mounted(){console.log(this);
}
}
</script>
<style scoped>
.slot{
padding: 10px;
background: orange;
}
</style>
ref
- 作用:定义一个响应式数据
- 语法:const xxx=ref(initValue)
- 创立一个蕴含响应式数据的援用对象(reference 对象);
- JS 中操作数据:xxx.value=xxx;
-
模板中读取数据:不须要.value, 间接:<div>{{xxx}}</div>
备注:
- 接管的数据能够是:根本类型,也能够是对象类型
- 根本类型的数据:响应式仍然是靠 Object.defineProperty()的 get 和 set 实现的
-
对象类型的数据:外部求助了 Vue3.0 中的一个新函数 -reactive 函数
示例<template> <h1>ref</h1> <h2>ref 定义根本数据类型 </h2> <p> 姓名:{{name}}</p> <p> 年龄:{{age}}</p> <p> 婚否:{{isMarry}}</p> <h2>ref 定义对象类型 </h2> <p> 喜好:{{hobby}}</p> <p> 证件类型:{{user.idCard}}</p> <p> 国籍:{{user.nation}}</p> <button @click="changeName"> 批改信息 </button> </template> <script> import {ref} from 'vue' export default { name: "refComponent", setup(){ // 应用根本数据类型 number,string,boolean, let name=ref('张三'); let age=ref(18); let isMarry=ref(false); // 应用 ref 定义数组 let hobby=ref(['吃饭','睡觉','打豆豆']); // 应用 ref 定义对象 let user=ref({ idCard:'身份证', nation:['中国','美国','英国','俄罗斯'] }) function changeName() { // 批改根本数据数据类型 name.value='李四'; // ref 定义的响应式数据批改数据时必须要.value age.value=20; isMarry.value=true; // 批改对象数据类型 hobby.value[0]='玩游戏'; user.value.idCard='港澳台居民身份证'; user.value.nation[0]='挪威'; } return{ name, age, isMarry, changeName, user, hobby } } } </script> <style scoped> </style>
留神:
- ref 定义的响应式数据批改数据时必须要.value
- ref 定义的对象数据类型,外部求助了 Vue3.0 中的一个新函数 -reactive 函数
- 模板中应用数据时不须要.value
reactive 函数
- 作用:定义一个对象类型的响应式数据(根本类型别用它,用 ref 函数)
- 语法:const 代理对象 =reactive(被代理的对象)接管一个对象(或数组),返回一个代理器对象(Proxy 的实例对象,简称 Proxy 对象)
- reactive 定义的响应式数据是深层次的
-
外部基于 ES6 的 Proxy 实现,通过代理对象的操作源对象的外部数据都是响应式的
<template> <h2>reactive 响应式数据 </h2> <p> 姓名:{{student.name}}</p> <p> 年龄:{{student.age}}</p> <p> 喜好:{{student.hobbies}}</p> <button @click="changeStuInfo"> 扭转学生信息 </button> </template> <script> import {reactive} from 'vue' export default { name: "reactiveComponent", setup(){ // 数据 let student=reactive({ name:'张三', age:19, hobbies:['吃饭','睡觉','打豆豆'] }); console.log(student) // 办法 function changeStuInfo() { student.name='李四'; student.age=20; student.hobbies[0]='做家务' } return{ student, changeStuInfo, } } } </script> <style scoped> </style>
reactive 比照 ref
-
从定义数据的角度比照
- ref 用来定义:根本类型数据
- reactive 用来定义:对象 (或数组) 类型数据
- 备注:ref 也能够用来定义对象(或数组)类型的数据,它外部会主动通过 reactive 转为代理对象
-
从原理角度比照
- ref 通过 Object.defineProperty()的 get 和 set 实现 (响应式) 数据劫持
- reactive 通过应用 Proxy 来实现响应式(数据劫持), 并通过 Reflect 操作源对象外部的数据
-
从应用角度
- ref 定义的数据:操作数据须要.value, 读取数据时模板中间接读取不须要.value
- reactive 定义的数据:操作数据与读取数据均不须要.value
watch 和 watchEffect
// attr 示意须要监督的属性
// 状况一:监督单个 ref 定义的响应式数据
watch(attr,(newValue,oldValue)=>{console.log('attr 变动了',newValue,oldValue);
})
// 状况二; 监督多个 ref 定义的响应式数据
watch([attr1,attr2,....,attrn],(newValue,oldValue)=>{console.log('attr1 或 attrn 变动了',newValue,oldValue);
})
// obj 示意须要监听的对象
// 状况三:监督 reactive 定义的响应式数据
// 若 watch 监督的是 reactive 定义的响应式数据,则无奈正确取得 oldValue
// 若 watch 监督的是 reactive 定义的响应式数据,则强制关上开启了深度监督
watch(obj,(newValue,oldValue)=>{console.log('obj 变动了',newValue,oldValue)
},{immediate:true,deep:false}); // 此处 deep 配置不在见效
// 状况四, 监督 reactive 定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{console.log('person 的 job 变动了',newValue,oldValue)
},{immediate:true,deep:true})
// 状况五:监督 reactive 定义的响应式数据中的某一些属性
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{console.log('person 的 job 变动了',newValue,oldValue)
})
// 非凡状况
watch(()=>person.job,(newValue,oldValue)=>{console.log('person 的 job 变动了',newValue,oldValue)
},{deep:false});// 此处因为是监督 reactive 所定义的对象中的某个属性,所以 deep 配置无效
-
watch
- 监督 reactive 定义的响应式数据时:oldValue 无奈正确获取、强制开启了深度监督(deep 配置生效)
-
监督 reactive 定义的响应式数据中某个属性时 deep 配置无效
示例一:wath 监听 ref 定义的响应式数据<template> <h2>watch 监听 ref 定义的响应式数据 </h2> <h2> 姓名:{{userName}}</h2> <h2> 年龄:{{age}}</h2> <button @click="userName+='!'"> 批改姓名 </button> <button @click="age++"> 批改年龄 </button> <hr> <h2> 姓名:{{user.name}}</h2> <h2> 年龄:{{user.age}}</h2> <button @click="user.name+='!'"> 批改姓名 </button> <button @click="user.age++"> 批改年龄 </button> </template> <script> import {ref,watch} from 'vue'; export default { name: "watch_component01", setup(){let userName=ref('张三'); let age=ref(18); let user=ref({ name:'张三', age:21, }) // watch 监听 ref 定义的单个响应式数据 watch(userName,(newValue,oldValue)=>{console.log(`userName 产生了变动, 新值是:${newValue}, 旧值是:${oldValue}`) }); watch(age,(newValue,oldValue)=>{console.log(`age 产生了变动, 新值是:${newValue}, 旧值是:${oldValue}`); }); // 如果须要监听多个 ref 定义的响应式数据的话,代码如下 /** * newValue 和 oldValue 都是数组的模式,其中数组的第 n 位示意监听地位的第 n 位 * 例如:此例子中,监听属性的第一位是 userName, 那位 newValue 和 oldValue 对应的第一位也是 * userName。* 如果有立刻执行,那么最开始的值为[], 而不是[undefined,undefined] */ watch([userName,age],(newValue,oldValue)=>{console.log('userName 或 age 中的其中一个产生了变动,',newValue,oldValue) }) // watch 监督 ref 定义的响应式对象数据 watch(user.value,(newValue,oldValue)=>{console.log('person 产生了变动',newValue,oldValue) }) watch(user,(newValue,oldValue)=>{console.log('person 产生了变动',newValue,oldValue); },{deep:false}) return{ userName, age, user } } } </script> <style scoped> </style>
示例二:watch 监听 reactive 定义的响应式数据
<template> <h1>watch 监听 reactive 定义的响应式数据 </h1> <p> 姓名:{{user.name}}</p> <p> 年龄:{{user.age}}</p> <p> 薪水:{{user.job.salary}}K</p> <button @click="user.name+='!'"> 扭转姓名 </button> <button @click="user.age++"> 扭转年龄 </button> <button @click="user.job.salary++"> 扭转薪水 </button> </template> <script> import {watch,reactive} from 'vue' export default { name: "watch_component02", setup(){ let user=reactive({ name:'张三', age:18, job:{salary:20} }); // 状况一:监听 reactive 定义的响应式数据,无奈正确获取 oldValue /** * 此时的 newValue 和 oldValue 都是最新的数据 * 默认强制开启深度监督,此时深度监督生效 */ watch(user,(newValue,oldValue)=>{console.log(newValue,oldValue); },{deep:false}); // 状况二,监督 reactive 定义的响应式数据的单个属性 // watch(()=>user.name,(newValue,oldValue)=>{// console.log('name 产生了变动',newValue,oldValue); // }); // watch(()=>user.age,(newValue,oldValue)=>{// console.log('age 产生了变动',newValue,oldValue); // }) // 状况三:监督 reactive 定义的响应式数据的多个属性 /** * newValue 和 oldValue 都是数组的模式,其中数组的第 n 位示意监听地位的第 n 位 * 例如:此例子中,监听属性的第一位是 userName, 那位 newValue 和 oldValue 对应的第一位也是 * userName, */ // watch([()=>user.name,()=>user.age],(newValue,oldValue)=>{ // 写法一 // console.log('name 或 age 中的某个属性产生了变动',newValue,oldValue); // }) // watch(()=>[user.name,user.age],(newValue,oldValue)=>{ // 写法二 // console.log('name 或者 age 中的某个属性产生了变动',newValue,oldValue) // }) // 状况四:监督 reactive 定义的响应式数据的对象的某个属性,此时 deep 无效 /** * 留神:此时须要区别是 reactive 定义的对象还是 reactive 定义的对象外面的某个属性 * 此时 deep 无效,敞开了监督 */ // watch(()=>user.job,(newValue,oldValue)=>{// console.log(newValue,oldValue); // },{deep:false}); return{user} } } </script> <style scoped> </style>
-
watchEffect
- watch 的套路是:既要指明监督的属性,也要指明监督的回调
- watchEffect 的套路是:不必指明监督那个属性,监督的回调中用到那个属性,那就监督那个属性
-
watchEffect 有点像 computed
- 但 computed 重视的是计算出来的值(回调函数的返回值),所以必须要写返回值
-
而 watchEffect 更重视的是过程(回调函数的函数体), 所以不必写返回值
示例<template> <h1>watchEffect 监督 ref 和 reactive 定义的响应式数据 </h1> <h2> 以后求和:{{sum}}</h2> <button @click="sum++"> 点我加 1 </button> <hr> <h2> 以后的信息:{{msg}}</h2> <button @click="msg+='!'"> 批改信息 </button> <hr> <h2> 姓名:{{person.name}}</h2> <h2> 年龄:{{person.age}}</h2> <h2> 薪资:{{person.job.j1.salary}}</h2> <button @click="person.name+='!'"> 批改姓名 </button> <button @click="person.age++"> 批改年龄 </button> <button @click="person.job.j1.salary++"> 涨薪 </button> </template> <script> import {ref,reactive,watchEffect} from 'vue'; export default { name: "watch_effect_component01", setup(){let sum=ref(0); let msg=ref('你好'); let person=reactive({ name:'张三', age:18, job:{ j1:{salary:100,} } }); /** * 在 watchEffect 外面写须要监督的属性,默认会执行一次 * 如果是监督 ref 定义的响应式书则须要.value * 如果是监督 reactive 定义的响应式数据则间接监督 */ watchEffect(()=>{ let x1=sum.value; let x2=person.job.j1.salary; console.log('watchEffect 所指定的回调函数执行了'); }) return{ sum, msg, person } } } </script> <style scoped> </style>
Vue2 响应式原理 VSVue3 响应式原理
在 Vue2 中次要是通过数据劫持来实现响应式原理的,也就是基于 Object.defineProperty 来实现的,而 Vue3 中是通过 Proxy 和 Reflect 来实现的(集体最低档次上的了解,还望各位大佬海涵)。
那么咱们来比照一下 Vue3 实现响应式的原理比 Vue2 实现响应式的原理好在哪里?
Vue2 响应式原理
-
实现原理
- 对象类型:通过 Object.defineProperty()对属性的读取,批改进行拦挡(数据劫持)
- 数组类型:通过重写更新数组的的一系列办法来实现拦挡。(对数组的变更办法进行了包裹)
Object.defineProperty(data,'count',{get(){} set(){} })
-
存在问题
- 新增属性、删除属性、界面不会自动更新
- 间接通过下标批改数组,界面不会自动更新
先看一个简略的示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <h1> 学生信息 </h1> <h4> 姓名:{{student.name}}</h4> <h4> 年龄:{{student.age}}</h4> <h4 v-if="student.sex"> 性别:{{student.sex}}</h4> <h4> 喜好:{{student.hobbies}}</h4> <button @click="addSex"> 新增性别 </button> <button @click="deleteAge"> 删除年龄 </button> <button @click="updateHobbies"> 批改喜好 </button> </div> <script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script> <script> let vm=new Vue({ el:'#app', data(){ return{ student:{ name:'张三', age:18, hobbies:['吃饭','睡觉','打豆豆'] } } }, methods:{addSex(){ // 新增性别 this.student.sex='male'; console.log(this.student); }, deleteAge(){ // 删除年龄 delete this.student.age; console.log(this.student); }, updateHobbies(){ // 批改喜好 this.student.hobbies[0]='玩游戏'; console.log(this.student); } } }) </script> </body> </html>
别离调用按钮对应的办法,控制台能够看到,新增性别属性时,student 外面有性别这个属性,然而并没有实现响应式(视图没有更新),同理,其它两个按钮对应的办法也是一样。
起因:Vue2.0 想要实现响应式数据的话,必须先在 data 外面定义,之后从新增加的数据无奈实现响应式。
解决方案:
-
新增 / 批改:vue.$set(target,propName/index,value)
- {Object | Array} target
- {string | number} propertyName/index
- {any} value
-
删除:vue.$delete(target,propName/index)
- {Object | Array} target
- {string | number} propertyName/index
此时咱们批改对应的代码如下
addSex(){ // 新增性别
// this.student.sex='male';
// vm.$set(this.student,'sex','male');
this.$set(this.student,'sex',male);
console.log(this.student);
},
deleteAge(){ // 删除年龄
// delete this.student.age;
// this.$delete(this.student,'age');
vm.$delete(this.student,'age');
console.log(this.student);
},
updateHobbies(){ // 批改喜好
// this.student.hobbies[0]='玩游戏';
// this.$set(this.student.hobbies,0,'玩游戏');
// vm.$set(this.student.hobbies,0,'玩游戏');
/**
* 或者应用数组变异的办法
* push()
* pop()
* shift()
* unshift()
* splice()
* sort()
* reverse()
*/
this.student.hobbies.splice(0,1,'玩游戏');
console.log(this.student);
}
弊病
- 必须定义在 data 外面的数据能力实现响应式
- 如果前面增加的数据想要实现响应式,那么就须要调用对应的 API
Object.defineProperty 的简略示例
let student={
name:'张三',
age:18,
}
let p={}
Object.defineProperty(p,'name',{get(){ // 读取 name 时触发
console.log('读取了 name 属性');
return student.name;
},
set(value){ // 批改 name 时触发
console.log('name 产生了变动, 视图产生了变动');
student.name=value;
}
});
console.log(p.name);
p.name='李四';
p.sex='male';
delete p.name;
Vue3 响应式原理
对于 Proxy 和 Reflect 的用法这里不过多介绍,如果有想要理解的举荐看 MDN 或者阮一峰老师的 ES6
- https://es6.ruanyifeng.com/
- https://developer.mozilla.org…
示例
let user={
name:'张三',
age:18,
}
let p=new Proxy(user,{get(target,propName){console.log(` 读取了 p 外面的 ${propName}属性 `);
Reflect.get(target,propName);
// return target[propName];
},
set(target,propName,value){console.log(` 批改了 p 外面的 ${propName}属性 `);
// target[propName]=value;
Reflect.set(target,propName,value);
},
deleteProperty(target,propName){console.log(` 删除了 p 外面的 ${propName}属性 `);
// delete target[propName];
Reflect.deleteProperty(target,propName);
}
});
console.log(p.name);
p.name='李四';
p.sex='male';
delete p.age;
查看控制台,当读取 name 属性的时候触发 get()办法,新增或者批改属性的时候触发 set()办法,删除的时候触发 deleteProperty()办法,这就是 Vue3.0 对响应式的改良。
生命周期和钩子函数
Vue2.0 生命周期和钩子函数
vue3.0 生命周期和钩子函数
Vue2 和 Vue3 生命周期比照 | ||
---|---|---|
beforeCreate | Not needed* | |
created | Not needed* | |
beforeMount | onBeforeMount | |
mounted | onMounted | |
beforeUpdate | onBeforeUpdate | |
updated | onUpdated | |
beforeUnmount | onBeforeUnmount | |
unmounted | onUnmounted | |
activated | onActivated | |
deactivated | onDeactivated |
因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不须要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该间接在 setup 函数中编写。
Vue3x 中能够持续应用 Vue2x 中的生命周期钩子,但有两个被更名
- beforeDestory 改名为 beforeUnmout
-
destoryed 改名为 unmouted
<template> <!--Vue3x 生命周期和钩子函数 --> <h3>Vue3x 生命周期和钩子函数 </h3> <h3> 数字:{{num}}</h3> <button @click="num++"> 点我加加 </button> </template> <script> import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue' export default { name: "lifeCycleComponent", setup(){let num=ref(0); console.log("======setup========="); onBeforeUnmount(()=>{console.log("======onBeforeUnmount========="); }); onMounted(()=>{console.log("======onMounted========="); }); onBeforeUpdate(()=>{console.log("======onBeforeUpdate========="); }); onUpdated(()=>{console.log("======onUpdated========="); }); onBeforeUnmount(()=>{console.log("======onBeforeUnmount========="); }) onUnmounted(()=>{console.log("======onUnmounted========="); }); return{num,} } } </script> <style scoped> </style>
自定义 hook
- 什么是 hook:实质是一个函数,把 setup 函数中应用的 CompositionAPI 进行了封装
- 相似于 vue2x 中 mixin
- 自定义 hook 的劣势:复用代码,让 setup 中的逻辑更分明易懂
作为初学 hook 的我来说,我对于 hook 并没有理解多少,这里贴一些本人练习中的示例,现阶段的我感觉不进去 export 导出函数和 hook 的区别和长处。示例的话是鼠标点击的时候获取以后坐标
示例
<template>
<h2> 自定义 hook</h2>
<h2> 以后 x 的坐标:{{x}}, 以后 y 的坐标:{{y}}</h2>
</template>
<script>
import {reactive,toRefs,onMounted,onUnmounted} from 'vue'
export default {
name: "hook_component01",
setup(){
// 数据
let point=reactive({
x:0,
y:0,
})
// 办法
function getPoint(event){console.log(event)
point.x=event.clientX;
point.y=event.clientY;
}
// 生命周期钩子函数
onMounted(()=>{window.addEventListener('click',getPoint);
})
onUnmounted(()=>{window.removeEventListener('click',getPoint);
})
return{...toRefs(point)
}
}
}
</script>
<style scoped>
</style>
抽离独自的 hook
- 新建目录 hook
- hook 目录下新建文件 usePoint.js
usePoint.js
import {reactive,onMounted,onUnmounted} from 'vue'
export let getPoint=()=>{
// 数据
let point=reactive({
x:0,
y:0,
})
// 办法
function getPoint(event){console.log(event)
point.x=event.clientX;
point.y=event.clientY;
}
// 生命周期钩子函数
onMounted(()=>{window.addEventListener('click',getPoint);
})
onUnmounted(()=>{window.removeEventListener('click',getPoint);
})
return point
}
须要引入 hook 的.vue 文件
import {reactive,toRefs,onMounted,onUnmounted} from 'vue'
import {getPoint} from "./hook/usePoint";
export default {
name: "hook_component01",
setup(){let point=getPoint();
return{...toRefs(point)
}
}
}
这个就是最简略 hook 的用法,如果有晓得 export 导出函数和 hook 区别的大佬,能够在下方评论区留言,感谢不敬!!!
其它 Composition API
toRef 与 toRefs
toRef
- 作用:创立一个 ref 对象,其 value 值指向另一个对象中的某个属性值
- 语法:const name=toRef(obj,’name’)
- 利用:要将响应式对象中的某个属性独自提供给内部应用时
- 扩大:toRefs 与 toRef 性能统一,但能够批量创立多个 ref 对象,toRefs(obj)
示例一
<template>
<h2>toRef 与 toRefs</h2>
<h2> 姓名:{{person.name}}</h2>
<h2> 年龄:{{person.age}}</h2>
<h2> 薪水:{{person.job.salary}}k</h2>
<button @click="person.name+='!'"> 批改姓名 </button>
<button @click="person.age++"> 批改年龄 </button>
<button @click="person.job.salary++"> 涨点薪资 </button>
</template>
<script>
import {reactive} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{salary:20}
})
return{person,}
}
}
</script>
<style scoped>
</style>
示例一外面间接返回 person 对象,导致每次取值的时候都须要 person.xxx, 这样既不美观也不优雅, 批改一下代码。
示例二
<template>
<h2>toRef 与 toRefs</h2>
<h2> 姓名:{{name}}</h2>
<h2> 年龄:{{age}}</h2>
<h2> 薪水:{{salary}}k</h2>
<button @click="name+='!'"> 批改姓名 </button>
<button @click="age++"> 批改年龄 </button>
<button @click="salary++"> 涨点薪资 </button>
</template>
<script>
import {reactive,toRef} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{salary:20}
})
return{name:toRef(person,'name'),
age:toRef(person,'age'),
salary:toRef(person.job,'salary')
}
}
}
</script>
<style scoped>
</style>
谬误用法示例一
<template>
<h2>toRef 与 toRefs</h2>
<h2> 姓名:{{name}}</h2>
<h2> 年龄:{{age}}</h2>
<h2> 薪水:{{salary}}k</h2>
<button @click="name+='!'"> 批改姓名 </button>
<button @click="age++"> 批改年龄 </button>
<button @click="salary++"> 涨点薪资 </button>
</template>
<script>
import {reactive,toRef,toRefs} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{salary:20}
})
return{
name:person.name,
age:person.age,
salary:person.job.salary
}
}
}
</script>
<style scoped>
</style>
谬误用法示例二
<template>
<h2>toRef 与 toRefs</h2>
<h2> 姓名:{{name}}</h2>
<h2> 年龄:{{age}}</h2>
<h2> 薪水:{{salary}}k</h2>
<h2>peron 对象{{person}}</h2>
<button @click="name+='!'"> 批改姓名 </button>
<button @click="age++"> 批改年龄 </button>
<button @click="salary++"> 涨点薪资 </button>
</template>
<script>
import {reactive,toRef,toRefs,ref} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{salary:20}
})
return{
person,
name:ref(person.name),
age:ref(person.age),
salary:ref(person.job.salary)
}
}
}
</script>
<style scoped>
</style>
toRefs
示例一
<template>
<h2>toRef 与 toRefs</h2>
<h2> 姓名:{{name}}</h2>
<h2> 年龄:{{age}}</h2>
<h2> 薪水:{{job.salary}}k</h2>
<button @click="name+='!'"> 批改姓名 </button>
<button @click="age++"> 批改年龄 </button>
<button @click="job.salary++"> 涨点薪资 </button>
</template>
<script>
import {reactive,toRef,toRefs} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{salary:20}
})
return{...toRefs(person)
}
}
}
</script>
<style scoped>
</style>
shallowRef 和 shallowReactive
- shallowReactive:只解决对象最外层属性的响应式(浅响应式)
- shallowRef: 只解决根本数据类型的响应式,不进行对象的响应式解决
-
什么时候用
- 如果有一个对象数据,构造比拟深,但变动时只是外层属性变动用 shallowReactive
- 如果有一个对象数据,后续性能不会批改该对象中的属性,而是生新的对象来替换用 shallowRef
shallowRef 示例
<template>
<h2>shallowRef 示例 </h2>
<h2> 以后 sum 的值是:{{sum}}</h2>
<button @click="sum++"> 点我 sum 加 1 </button>
<h2> 以后 x.y 的值是:{{x.y}}</h2>
<button @click="x.y++"> 点我 x.y 加 1 </button>
<button @click="x={y:100}"> 点我替换 y 的值 </button>
</template>
<script>
import {shallowRef,ref} from 'vue'
export default {
name: "shallowRef_component",
setup(){let sum=ref(0);
let x=shallowRef({y:0,})
return{
sum,
x,
}
}
}
</script>
<style scoped>
</style>
这里咱们通过 ref 和 shallowRef 进行比对,点击 x.y 加 1 按钮的时候,视图不会触发更新,因为 y 的值对象作为深层次的,而间接点击 sum 加 1 的按钮的时候能够触发更新,sum 间接是浅层次的,替换 y 的值的时候替换的是整个 x 的值(即整个对象),而不是 x 外面的值进行操作。
shallowReactive 示例
<template>
<h2>shallowReactive 示例 </h2>
<h2> 姓名:{{name}}</h2>
<h2> 年龄:{{age}}</h2>
<h2> 薪资:{{job.salary}}k</h2>
<button @click="name+='!'"> 批改姓名 </button>
<button @click="age++"> 批改年龄 </button>
<button @click="job.salary++"> 涨点薪资 </button>
</template>
<script>
import {shallowReactive,toRefs} from 'vue'
export default {
name: "shallowReactive01_component",
setup(){
let person=shallowReactive({
name:'张三',
age:18,
job:{salary:20,}
})
return{...toRefs(person)
}
}
}
</script>
<style scoped>
</style>
点击批改姓名和批改年龄的按钮时,能够看到视图发生变化,点击涨薪的时候视图不会发生变化,然而数据产生了变动,这个大家能够应用控制台进行测试。
readonly 和 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)
-
shallowReadonly: 让一个响应式变为只读的 (浅只读)
示例一<template> <h2>readonly 与 shallowReadonly</h2> <h2> 姓名:{{name}}</h2> <h2> 年龄:{{age}}</h2> <h2> 薪资:{{job.salary}}</h2> <h2> 以后 sum 的值是:{{sum}}</h2> <button @click="name+='!'"> 批改姓名 </button> <button @click="age++"> 批改年龄 </button> <button @click="job.salary++"> 涨薪(readonly)</button> <button @click="job.salary++"> 涨薪(shallowReadonly)</button> <button @click="sum++"> 点我加 1 </button> </template> <script> import {ref,reactive,readonly,shallowReadonly,toRefs} from 'vue' export default { name: "shallowReadonly_component", setup(){let sum=ref(0); let person=reactive({ name:'二郎神杨戬', age:21, job:{salary:200} }); person=readonly(person); sum=readonly(sum); // person=shallowReadonly(person); // sum=readonly(sum); return{ sum, ...toRefs(person) } } } </script> <style scoped> </style>
应用 readonly 的时候,按钮点击全副生效,咱们看下 shallowReadonly 的成果
应用 shallowReadonly 的时候,批改姓名,批改年龄都不会发生变化,只有涨薪产生了变动
toRaw 和 markRaw
-
toRaw
- 作用:将一个由 reactive 生成的响应式对象转为一般对象
- 应用场景:用于读取响应式对象对应的一般对象,对这个一般对象的所有操作,不会引起页面更新
-
markRow
-
作用:标记一个对象,使其永远不会再成为响应式对象
利用场景:
- 有些值不应该被设置为响应式的,例如简单的第三方类库,
- 当渲染具备不可变的数据源的大列表时,跳过响应式转换能够进步性能
示例一
-
<template>
<div style="width: 800px;margin: 0 auto">
<h2>toRaw 与 markRow</h2>
<h2> 姓名:{{name}}</h2>
<h2> 年龄:{{age}}</h2>
<h2> 薪资:{{job.salary}}k</h2>
<button @click="name+='!'"> 批改姓名 </button>
<button @click="age++"> 批改年龄 </button>
<button @click="job.salary++"> 涨点薪资 </button>
<button @click="showRawPerson"> 输入最原始的 person 对象 </button>
</div>
</template>
<script>
import {ref,reactive,toRaw,markRaw,toRefs} from 'vue'
export default {
name: "toRaw01_component",
setup(){let sum=ref(0);
let person=reactive({
name:'二郎神杨戬',
age:18,
job:{salary:20}
})
function showRawPerson() {let p=toRaw(person)
console.log(p);
let sum=toRaw(sum);
console.log(sum); // 对 ref 定义的响应式数据有效
}
return{
sum,
...toRefs(person),
showRawPerson
}
}
}
</script>
<style scoped>
</style>
调用 showRawPerson 办法的时候,控制台能够看到输入最原始的 person,sum 的话,输入的 undefined,toRaw 对 ref 定义的响应式数据有效,接下来看下 markRow 的成果
示例二
<template>
<div style="width: 800px;margin: 0 auto">
<h2>toRaw 与 markRow</h2>
<h2> 姓名:{{name}}</h2>
<h2> 年龄:{{age}}</h2>
<h2> 薪资:{{job.salary}}k</h2>
<button @click="name+='!'"> 批改姓名 </button>
<button @click="age++"> 批改年龄 </button>
<button @click="job.salary++"> 涨点薪资 </button>
<button @click="showRawPerson"> 输入最原始的 person 对象 </button>
<h2> 车的信息是:{{person.car}}</h2>
<button @click="addCar"> 给人增加一辆车 </button>
<template v-if="person.car">
<button @click="person.car.name+='!'"> 批改车名 </button>
<button @click="person.car.price++"> 批改车的价格 </button>
<button @click="changeCarPrice"> 批改车的价格 </button>
</template>
</div>
</template>
<script>
import {ref,reactive,toRaw,markRaw,toRefs} from 'vue'
export default {
name: "toRaw01_component",
setup(){let sum=ref(0);
let person=reactive({
name:'二郎神杨戬',
age:18,
job:{salary:20}
})
function showRawPerson() {let p=toRaw(person)
console.log(p);
let sum=toRaw(sum);
console.log(sum); // 对 ref 定义的响应式数据有效
}
function addCar() {let car={name:'宝马',price:40}
person.car=markRaw(car);
}
function changeCarPrice() {
person.car.price++;
console.log(person.car.price)
}
return{
sum,
person,
...toRefs(person),
showRawPerson,
addCar,
changeCarPrice
}
}
}
</script>
<style scoped>
</style>
这里新增了一个车信息的办法和相干属性到 person 对象外面,失常状况下,间接在 reactive 外面的追加的数据会实现响应式的,然而这里应用了 markRaw 办法,所以点击批改车的名字和价格时数据产生了变动,然而视图不会更新。
customRef
- 作用:创立一个自定义的 ref, 并对其依赖项跟踪和更新触发进行显示管制, 它须要一个工厂函数,该函数接管 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。
实现防抖成果: - 1、先实现自定义双向绑定
-
2、实现了双向绑定之后,实现防抖
自定义双向绑定<template> <h2>customRef 示例 </h2> <input type="text" v-model="msg"> <h2>{{msg}}</h2> </template> <script> import {customRef,} from 'vue' export default { name: "customRef01_component", setup(){function myRef(msg){ // 自定义 ref 函数 return customRef((track,trigger)=>{ return{get(){console.log('读取了值') track(); return msg; }, set(newValue){console.log(` 批改了值, 批改后的值是:${newValue}`); msg=newValue; trigger();} } }) } let msg=myRef('你好'); return{msg} } } </script> <style scoped> </style>
在这里咱们实现了数据的双向绑定,接下来是实现防抖
<template>
<div style="width: 800px;margin: 0 auto">
<h2>customRef 示例 </h2>
<input type="text" v-model="msg">
<h2>{{msg}}</h2>
</div>
</template>
<script>
import {customRef,} from 'vue'
export default {
name: "customRef01_component",
setup(){function myRef(msg,delay){ // 自定义 ref 函数
let timer;
return customRef((track,trigger)=>{
return{get(){console.log('读取了值')
track();
return msg;
},
set(newValue){timer=setTimeout(()=>{console.log(` 批改了值, 批改后的值是:${newValue}`);
msg=newValue;
trigger();},delay)
}
}
})
}
let msg=myRef('你好',500);
return{msg}
}
}
</script>
<style scoped>
</style>
响应式数据的判断
- isRef:查看一个值是否为 ref 对象
- isReactive:查看一个对象是否由 reactive 创立的响应式代理
- isReadonly:查看一个对象是否由 readonly 创立的只读代理
- isProxy:查看一个对象是否由 reactive 或者 readonly 办法创立的代理
provide 和 inject
- 作用:实现祖与后辈组件间通信
- 套路:父组件有一个 provide 选项来提供数据,后辈组件有一个 inject 选项来开始应用这些数据
祖组件
<template>
<h2> 我是祖组件 </h2>
<h3> 汽车信息 </h3>
<p> 名称:{{name}}</p>
<p> 价格:{{price}}</p>
<inject_component></inject_component>
</template>
<script>
import {reactive,toRefs,provide} from 'vue'
export default {
name: "provide_component",
setup(){
let car=reactive({
name:'宝马',
price:'40w'
});
provide('car',car); // 提供 provide
return{...toRefs(car)
}
}
}
</script>
<style scoped>
</style>
后辈组件
<template>
<h2> 我是孙组件 </h2>
<h3> 汽车信息 </h3>
<p> 名称:{{name}}</p>
<p> 价格:{{price}}</p>
</template>
<script>
import {inject,toRefs,ref} from 'vue'
export default {
name: "inject_component",
setup(){let car=inject("car"); // 应用 inject 接管
return{...toRefs(car)
}
}
}
</script>
<style scoped>
</style>
Fragment
- 在 vue2 中:组件必须有一个根标签
- 在 vue3 中:组件能够没有根标签,外部会将多个根标签蕴含在一个 Fragment 虚构元素中
益处:缩小标签层级,缩小内存占用
Teleport
Teleport 是一种可能将咱们的组件 html 构造挪动到指定地位的技术
<teleport to='挪动地位'>
<div v-if='isShow' class='mark'>
<div class="dialog">
<h3> 我是一个弹窗 </h3>
<button @click='isShow=true'> 敞开弹窗 </button>
</div>
</div>
</teleport>
实现一个弹窗居中显示,有四个组件,别离为:teleport_parent,teleport_child,teleport_son,teleport_dialog,而后须要实现的成果是在 teleport_son 组件引入 teleport_dialog 组件,teleport_dialog 显示的时候在屏幕正地方
teleport_parent.vue
<template>
<div class="parent">
<h2> 我是 parent 组件 </h2>
<teleport_child/>
</div>
</template>
<script>
import Teleport_child from "./teleport_child";
export default {
name: "teleport_parent",
components: {Teleport_child}
}
</script>
<style scoped>
.parent{
background-color: red;
padding: 10px;
}
</style>
teleport_child.vue
<template>
<div class="child">
<h2> 我是 child 组件 </h2>
<teleport_son/>
</div>
</template>
<script>
import Teleport_son from "./teleport_son";
export default {
name: "teleport_child",
components: {Teleport_son}
}
</script>
<style scoped>
.child{
background-color: orange;
padding: 10px;
}
</style>
teleport_son.vue
<template>
<div class="son">
<h2> 我是 son 组件 </h2>
<teleport_dialog/>
</div>
</template>
<script>
import Teleport_dialog from "./teleport_dialog";
export default {
name: "teleport_son",
components: {Teleport_dialog}
}
</script>
<style scoped>
.son{
background-color: yellow;
padding: 10px;
}
</style>
teleport_dialog.vue
<template>
<div>
<button @click="isShow=true"> 点我弹窗 </button>
<div class="dialog_container" v-if="isShow">
<h2> 我是弹窗组件 </h2>
<div class="dialog_body">
<h2> 我是内容 </h2>
<h2> 我是内容 </h2>
<h2> 我是内容 </h2>
<h2> 我是内容 </h2>
</div>
<button @click="isShow=false"> 敞开按钮 </button>
</div>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: "teleport_dialog",
setup(){let isShow=ref(false);
return{isShow}
}
}
</script>
<style scoped>
.dialog_container{
width: 500px;
height: 300px;
background: red;
}
</style>
实现的成果如下
当咱们点击按钮的时候,成果是这样的
点击按钮的时候,弹窗显示,然而在 son 组件外面会扭转 son 的高度,这样子不太好看,如果弹窗应用定位的话能够使其脱离文档流,而不会撑开 son 外面的高度,然而 postion:absolute 是依据最近的有定位元素的父元素进行定位的,所以此办法不牢靠,咱们须要实现的成果是依据 body 来定位
批改 dialog 的款式
<template>
<div>
<button @click="isShow=true"> 点我弹窗 </button>
<teleport to="body">
<div class="mask" v-if="isShow">
<div class="dialog_container">
<h2> 我是弹窗组件 </h2>
<div class="dialog_body">
<h2> 我是内容 </h2>
<h2> 我是内容 </h2>
<h2> 我是内容 </h2>
<h2> 我是内容 </h2>
</div>
<button @click="isShow=false"> 敞开按钮 </button>
</div>
</div>
</teleport>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: "teleport_dialog",
setup(){let isShow=ref(false);
return{isShow}
}
}
</script>
<style scoped>
.mask{
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background: rgba(0,0,0,.5);
}
.dialog_container{
width: 500px;
height: 300px;
background: red;
position: absolute;
left: 50%;
top: 50%;
margin-left: -250px;
margin-top: -150px
}
</style>
Suspense
作用:期待异步组件时渲染一些额定的内容,让利用有更好的用户体验
应用步骤:
- 异步引入组件
import {defineAsyncComponent} from 'vue'
const child=defineAsyncComponent(()=>import('./components/Child.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>
示例
suspense.vue
<template>
<div class="suspense">
<h2> 我是 suspense 组件 </h2>
<child/>
</div>
</template>
<script>
import Child from "./child";
export default {
name: "suspense01_component",
components: {Child}
}
</script>
<style scoped>
.suspense{
background-color: red;
padding: 10px;
}
</style>
child.vue
<template>
<div class="child">
<h2> 我是 child 组件 </h2>
</div>
</template>
<script>
export default {name: "child"}
</script>
<style scoped>
.child{
background-color: orange;
padding: 10px;
}
</style>
如果应用以上代码引入的话,那么 suspense 和 child 组件将会同时加载,如果 child 外面还有其它子组件的话,子组件外面还有子组件,在网络慢的状况下,用户可能就看不到 child 组件以及 child 外面其它的组件。这会影响用户体验,批改对应的代码
suspense.vue
<template>
<div class="suspense">
<h2> 我是 suspense 组件 </h2>
<suspense>
<template v-slot:default>
<child/>
</template>
<template v-slot:fallback>
<h3> 请稍等,加载中。。。</h3>
</template>
</suspense>
</div>
</template>
<script>
// import Child from "./child"; // 动态引入
import {defineAsyncComponent} from 'vue'
const Child=defineAsyncComponent(()=>import('./child')) // 异步引入
export default {
name: "suspense01_component",
components: {Child}
}
</script>
<style scoped>
.suspense{
background-color: red;
padding: 10px;
}
</style>
child.vue
<template>
<div class="child">
<h2> 我是 child 组件 </h2>
<h2> 以后 sum 的值是:{{sum}}</h2>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: "child",
setup(){let sum=ref(0);
return new Promise((resole,reject)=>{setTimeout(()=>{
resole({sum});
},3000)
})
}
}
</script>
<style scoped>
.child{
background-color: orange;
padding: 10px;
}
</style>
为了看到成果,提早了 3 秒之后显示组件
Vue2x 和 Vue3x 的其它变动
1. 全局 API 的转移
vue2.x 有许多全局 API 和配置,例如:全局注册组件、注册全局指令等
- 注册全局组件
Vue.component('MyButton',{data:()=>{count:0,},
template:'<button @click="count++">clicked {{count}} times</button>'
});
- 注册全局指令
Vue.directive('focus',{inserted:el=>el.foucus})
- Vue3.0 中对这些 API 做出了调整
将全局的 API, 即 Vue.xxx 调整到利用实例 app 上
2.x 全局 API(Vue) | 3.x 实例 API(app) |
---|---|
Vue.config.xxx | app.config.xxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
2. 其它扭转
- 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-to,.v-enter-to{opacity:1}
- 移除 keyCode 作为 v -on 的修饰符,同时也不再反对 config.keyCodes
- 移除 v -on.navtive 修饰符
父组件绑定事件
<my-component>
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
</my-component>
子组件中申明自定义组件
export default{emits:['close']
}
-
移除过滤器(filter)
过滤器尽管看起来不便,但它须要一个自定义语法,突破大括号内表达式 ’ 只是 javascript’ 的假如,这不仅有学习老本,而且有实现成 本,倡议用法调用或者计算属性去替换过滤器
Vue3x 相干材料
相干库名称 | 在线地址 |
---|---|
Vue 3.0 官网文档(英文) | 在线地址 |
Vue 3.0 中文文档 | 在线地址 |
Composition-API 手册 | 在线地址 |
Vue 3.0 源码学习 | 在线地址 |
Vue-Router 官网文档 | 在线地址 |
Vuex 4.0 | Github |
vue-devtools | Github |
Vite 源码学习 | 线上地址 |
Vite 2.0 中文文档 | 线上地址 |
Vue3 新动静 | 线上地址 |
Vue3xUI 组件库
Element-plus
- 仓库地址:https://github.com/element-pl…
- 文档地址:https://element-plus.gitee.io…
-
开源我的项目
- Vue 3.0 + Vite 2.0 + Vue-Router 4.0 + Element-Plus + Echarts 5.0 + Axios 开发的后盾管理系统:https://github.com/newbee-ltd…
- Vue3.0+TypeScript+NodeJS+MySql 编写的一套后盾管理系统:https://github.com/xiaoxian52…
Ant Design of Vue
- 仓库地址:https://github.com/vueCompone…
- 文档地址:https://antdv.com/docs/vue/in…
-
开源我的项目
- AntdV 后盾管理系统:https://github.com/iczer/vue-…
- vue3.x + ant-design-vue(beta 版本,收费商用,反对 PC、平板、手机):https://github.com/chuzhixin/…
- 基于 Vue3.0 + Vite + Ant Design Vue:https://github.com/lirongtong…
Vant
- 仓库地址:https://github.com/youzan/vant
- 文档地址:https://vant-contrib.gitee.io…
-
开源我的项目
- newbee-mall Vue3 版本:https://github.com/newbee-ltd…
- 高仿微信笔记本:https://github.com/Nick930826…
- 仿京东淘宝电商:https://github.com/geekskai/v…
NutUI 3
- 仓库地址:https://github.com/jdf2e/nutui
-
文档地址:https://nutui.jd.com/#/index
Vue3X 相干视频
相干库名称 | 在线地址 |
---|---|
Vue 3.0 实战星座物语 H5 我的项目 | 在线地址 |
Vue 3.0 UI 组件库开发 | 在线地址 |
Vue 3.0 + Vite 手册浏览 | 在线地址 |
Vue 3.0 入门之我的项目搭建(杨村长) | 在线地址 |
Vue 3.0 入门(技术胖)【不太倡议举荐】 | 在线地址 |
Vite 2.0 插件开发指南 | 在线地址 |
Vue 3.0 + Vite 2.0 疾速搭建 Electron 利用 | 在线地址 |
Vue3.0 视频(强烈推荐) | 在线地址 |
Vue3.0 驾照题库 | 在线地址 |
参考资料
掘金地址:https://juejin.cn/post/695512…
Vue.js 官网:https://v3.cn.vuejs.org/
bilibili 地址:https://www.bilibili.com/vide…
MDN 地址:https://developer.mozilla.org…
ES6 地址:https://es6.ruanyifeng.com/
总结
总体来说,学完这篇博客根本涵盖了 Vue3 中的绝大部分内容,写的也特地具体,笔记,配套材料和实战视频都有,次要看你愿不愿意抽出工夫去学习,我本人认为学习是一辈子的事件,当然学习也是一件特地孤单的事件。如果本篇文章对您有所帮忙的话,记得点赞、珍藏和关注,原创不易,三连反对,感激大家!