一、快速上手Vue3.0
1、使用vue-cli
2、使用vite
开发环境中,无需打包
轻量快速热重载
npm init vite-app vue3_test_vite
二、常用Composition API(组合式API)
1、Setup
组件中所用到的:数据、方法等等,均要配置在setup中。
在setup中不能访问到vue2配置
<template>
<h1>我是App组件</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="sayHello">说话(VUE3)</button>
<button @click="sayWelcome">说话(VUE2)</button>
</template>
<script>
export default {
name: 'App',
data(){
return {
sex:'男'
}
},
methods: {
sayWelcome(){
alert('欢迎欢迎')
}
},
//不考虑响应式的问题
setup(){
let name = '张三'
let age = 18
function sayHello(){
alert(`我叫${name},我${age}岁了,你好啊`)
}
return {
name,
age,
sayHello
}
}
}
</script>
2、ref函数
作用:定义一个响应式的数据
语法: const xxx = ref(initValue)
- 创建一个引用对象
- JS中操作数据:
xxx.value
- 模板中读取数据不需要.value
接收的基本类型数据:响应式依然是靠 Object.defineProperty()
的get 与set完成的
对象类型的数据(reactive)
<template>
<h1>我是App组件</h1>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h3>工作薪水:{{job.salary}}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
//不考虑响应式的问题
setup(){
let name = ref('张三')
let age = ref(18)
let job = ref({
type:'前端工程师',
salary:'30k'
})
function changeInfo(){
name.value = '李四',
age.value = 48
console.log(job.value);
}
return {
name,
age,
job,
changeInfo
}
}
}
</script>
3、reactive函数
作用:定义一个对象类型的响应式数据
语法: const 代理对象 = reactive(源对象)
接收一个对象或数组,返回一个代理对象(proxy的实例对象)
内部基于ES6的proxy实现,通过代理对象操作源对象内部数据进行操作
<script>
import {ref,reactive} from 'vue'
export default {
name: 'App',
setup(){
let person = reactive({
name:'张三',
age:18,
job:{
type:'前端工程师',
salary:'30k',
a:{
b:{
c:666
}
}
}
})
function changeInfo(){
person.name = '...'
person.age = 48
person.job.type = '....'
person.a.b.c = ***
person.hobby[0] = '学习'
}
return {
name,
age,
job,
changeInfo
}
}
}
</script>
4、Vue3响应式
实现vue2的响应式原理:
let person = {
name:'张三',
age:18
}
// 模拟Vue2中实现响应式
let p = {}
Object.defineProperty(p,'name',{
get(){
//有人读取name时调用
return person.name
},
set(value){
//有人修改name时调用
console.log('有人修改了name,我去更新页面');
person.name = value
}
})
Object.defineProperty(p,'age',{
get(){
//有人读取name时调用
return person.age
},
set(value){
//有人修改name时调用
console.log('有人修改了age,我去更新页面');
person.age = value
}
})
实现原理:
通过Proxy(代理):拦截对象中任意属性变化。包括增删读写
通过Reflect(反射):对被代理对象的属性进行操作
let person = {
name:'张三',
age:18
}
const p = new Proxy(person,{
get(target,propName){
console.log(`有人读取了p身上的${propName}属性`);
return Reflect.get(target,propName)
},
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`);
Reflect.set(target,propName,value)
},
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`);
return Reflect.deleteProperty(target,propName)
}
})
5、reactive和ref的对比
-
从定义数据角度对比:
ref定义基本数据
reactive定义对象或数组类型
从原理角度对比:
ref通过
Object.defineProperty()
的get与set来实现响应式。 reactive通过使用
Proxy
来实现响应式(数据劫持)并通过Reflect操作源对象内部数据。从使用角度对比:
ref定义的数据:操作数据需要
.value
,读取数据模板中直接读取不需要.value
reactive定义的数据:操作读取均不需要
.value
6、Setup的注意点
setup执行时机:在beforecreate之前执行一次,this是undefined
Setup的参数:props值为对象,包含组件外传递过来且组件内声明接收了的属性
context:上下文对象
attrs:值为对象,包含组件外部传递过来但没有props配置中声明的属性,相当于 this.$attrs
slots:收到的插槽内容,相当于 this.$slots
emit:分发自定义事件的函数,相当于 this.$emit
7、计算属性与监视
1、computed写法
import {computed} from 'vue'
setup(){
...
//简写
//let fullName = computed(()=>{
// return person.firstName + '-' + person.lastName
//})
//计算属性完整
let fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}
2、watch写法
监视reactive所定义的一个响应式数据的全部属性,无法正确地获取oldValue,deep失效
监视的是reactive定义的对象中的某个对象属性,deep配置有效
import {ref,reactive,watch} from 'vue'
setup(){
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
//情况一:监视ref所定义的一个响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变了',newValue,oldValue)
},{immediate:true})
//情况二:监视ref所定义的多个响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变了',newValue,oldValue)
},{immediate:true})
}
//情况三:监视reactive所定义的一个响应式数据的全部属性
//此处无法正确地获取oldValue
//强制开启深度监视
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{deep:false})//deep配置无效
//情况四:监视reactive所定义的一个响应式数据的某个属性
watch(()=>person.name,(newValue,oldValue)=>{
console.log('person的name变化了',newValue,oldValue)
})
//情况五:监视reactive所定义的一个响应式数据中的某些属性
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log('person的name或age变化了',newValue,oldValue)
})
//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true})//此处监视的是reactive定义的对象中的某个对象属性,所以deep配置有效
return{
sum,
msg,
person
}
3、watchEffect函数
watch的套路是:既要指明监视属性,也指明监视回调
watchEffect:不用指明监视哪个属性,监视回调中用到哪个就监听哪个
import {ref,reactive,watch,watchEffect} from 'vue'
setup(){
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
watchEffect(()=>{
const x1 = sum.value
const x2 = person.job.j1.salary
console.log('watchEffect所指定的回调执行了')
})
return{
sum,
msg,
person
}
8、生命周期
Vue3.0中可以继续使用Vue2的生命周期钩子
beforeDestory
改为 beforeUnmount
destroyed
改为 unmounted
Composition API形式的生命周期钩子,与Vue2中钩子对应关系如下:
beforeCreate
===> setup()
created
===>setup()
beforeMount
===> onBeforeMount
mounted
===> onMounted
beforeUpdate
===> onBeforeUpdate
updated
===> onUpdated
beforeUnmount
===> onBeforeUnmount
unmounted
===> onUnmounted
通过组合式API的形式去使用生命周期钩子
<template>
<h2>求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
</template>
<script>
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default {
name:'DemoOne',
setup(){
console.log('---setup---');
let sum = ref(0)
//通过组合式API的形式去使用生命周期钩子
onBeforeMount(()=>{
//函数体
console.log('--onBeforeMount--');
})
onMounted(()=>{
//函数体
console.log('--onMounted--');
})
onBeforeUpdate(()=>{
//函数体
console.log('--onBeforeUpdate--');
})
onUpdated(()=>{
//函数体
console.log('--onUpdated--');
})
onBeforeUnmount(()=>{
//函数体
console.log('--onBeforeUnmount--');
})
onUnmounted(()=>{
//函数体
console.log('--onUnmounted--');
})
return {sum}
},
//#region
// beforeCreate(){
// console.log('---beforeCreate---');
// },
// created(){
// console.log('---created---');
// },
// beforeMount(){
// console.log('---beforeMount---');
// },
// mounted(){
// console.log('---mounted---');
// },
// beforeUpdate(){
// console.log('---beforeUpdate---');
// },
// updated(){
// console.log('---updated---');
// },
// beforeUnmount() {
// console.log('---beforeUnmount---');
// },
// unmounted(){
// console.log('---unmounted---');
// }
//#endregion
}
</script>
9、自定义hook函数
- 本质是一个函数,把setup函数中使用的Composition API进行了封装
- 类似于mixin
- 复用代码,让setup中的逻辑更清楚
hooks/usePoint.js
import {onMounted,reactive,onBeforeUnmount} from 'vue'
export default function(){
//实现“打点”的数据
let point = reactive({
x:0,
y:0
})
//实现打点的方法
function savePoint(event){
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX,event.pageY);
}
//实现打点的钩子
onMounted(()=>{
//函数体
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
components/DemoOne.vue
<template>
<h2>求和为:{{sum}}</h2>
<button @click="sum++">点我+1</button>
<hr>
<h2>鼠标坐标为x{{point.x}},y:{{point.y}}</h2>
</template>
<script>
import {ref} from 'vue'
import usePoint from '../hooks/usePoint'
export default {
name:'DemoOne',
setup(){
console.log('---setup---');
let sum = ref(0)
let point = usePoint()
return {sum,point}
},
}
</script>
10、toRef
作用:创建一个Ref对象,其value值指向另一个对象中的某个属性
语法 : const name = toRef(person,'name')
应用:要将响应式对象中的某个属性提供给外部使用时
扩展: toRefs(person)
批量创建ref对象
<template>
<h1>{{person}}</h1>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>薪资:{{ job.j1.salary }}</h2>
<button @click="name += '~'">修改姓名</button>
<button @click="age++">增长年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</template>
<script>
import { reactive,toRefs } from "vue";
export default {
name: "DemoOne",
setup() {
let person = reactive({
name: "张三",
age: 18,
job: {
j1: {
salary: 20,
},
},
});
// return {
// person,
// name:toRef(person,'name'),
// age:toRef(person,'age'),
// salary:toRef(person.job.j1,'salary')
// }
return{
...toRefs(person)
}
},
};
</script>
三、其他Composition API
1、shallowReactive与shallowRef
shallowRecative只考虑对象类型第一层(最外层)的响应式
shallowRef不去处理对象类型的响应式,只处理基本数据类型响应式
使用:
如果一个对象数据结构比较深,但只用外层属性===>shallowRecative
如果一个对象数据后续不会修改对象中的属性,如要修改则直接替换为新的对象===>shallowRef
2、readonly与shallowReadonly
不希望数据被修改时
readonly:使一个响应式数据变为只读
shallowReadonly:使一个响应式数据外层只读
person = readonly(person)
person = shallowReadonly(person)
3、toRaw与markRaw
toRaw
- 作用:将一个由
reactive
生成的响应式对象转为普通对象 - 使用:读取响应式对象对应的普通对象,对普通对象的所有操作不会引起页面变化
const p = toRaw(person)
markRaw
作用:标记一个对象,使其永远不会再成为响应式对象
使用:
有些值不应该被设为响应式,例如第三方类库axios等
渲染不可变数据的大列表时,可跳过响应式转换提高性能
let car = {name:'奔驰',price:40}
person.car = markRaw(car)
4、customRef
创建一个自定义Ref,并对其依赖项跟踪和更新触发进行显式控制
实现防抖效果:
<template>
<input type="text" v-model="keyWord">
<h3>{{keyWord}}</h3>
</template>
<script>
import { customRef } from "vue";
export default {
name: "App",
setup(){
//自定义ref
function myRef(value,delay){
let timer
return customRef((track,trigger)=>{
return {
get(){
console.log(`有人从myRef读取数据了,${value}`);
track() //通知vue追踪数据的变化
return value
},
set(newValue){
console.log(`有人把myRef数据改为了:${newValue}`);
clearTimeout(timer)
timer = setTimeout(()=>{
value = newValue
trigger() //通知vue重新解析模板
},delay)
}
}
})
}
// let keyWord = ref('hello') //使用Vue的ref
let keyWord = myRef('hello',500) //使用程序员自定义的ref
return {keyWord}
}
}
</script>
5、provide与inject
作用:实现祖孙组件间通信
使用:父组件有一个 provide
来提供数据,后代组件有一个 inject
选项来使用数据
父组件App.vue
<template>
<div class="app">
<h3>App组件(祖),{{name}}--{{price}}</h3>
<Child />
</div>
</template>
<script>
import { reactive,toRefs,provide } from 'vue'
import Child from './components/Child.vue'
export default {
name:'App',
components:{Child},
setup(){
let car = reactive({
name:'奔驰',
price:40
});
provide('car',car)//给自己后代组件传递数据
return{
...toRefs(car)
}
}
}
</script>
<style scoped>
.app{
background-color: aqua;
padding: 10px;
}
</style>
后代组件Son.vue
<template>
<div class="son">
<h3>Son组件(孙){{car.name}} -- {{car.price}} </h3>
</div>
</template>
<script>
import {inject} from 'vue'
export default {
name:'SonOne',
setup(){
let car = inject('car')
return{car}
}
}
</script>
<style scoped>
.son{
background-color: gray;
padding: 10px;
}
</style>
6、响应式数据的判断
isRef()
:检查一个值是否为ref对象
isReactive()
:检查一个对象是否为reactive创建的响应式代理
isReadonly()
:检查一个对象是否为readonly创建的只读代理
isProxy()
:检查一个对象是否为代理
四、新的组件
1、Fragment
Vue3组件可以没有根标签,会将多个标签包含在一个Fragment虚拟元素中
减少层级嵌套
2、teleport
将组件html结构移动到指定位置
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>
我是一个弹窗
</h3>
<button @click="isShow=false">
关闭弹窗
</button>
</div>
</div>
</teleport>
3、Suspense
等待异步组件时渲染一些额外内容,增加用户体验
异步引入组件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
使用 Suspense
包裹组件,配置 default
和 fallback
<template>
<div class="app">
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中</h3>
</template>
</Suspense>
</div>
</template>
五、其他变化
略写~~~~
移除 v-on.native
修饰符
父组件中绑定事件
<fu v-on:click="clickEvent" v-on:close="closeEvent">
</fu>
子组件中声明自定义事件
<script>
export default{
emits:['close']
}
</script>
</h3>
<button @click="isShow=false">
关闭弹窗
</button>
</div>
</div>
```
3、Suspense
等待异步组件时渲染一些额外内容,增加用户体验
异步引入组件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
使用 Suspense
包裹组件,配置 default
和 fallback
<template>
<div class="app">
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中</h3>
</template>
</Suspense>
</div>
</template>
五、其他变化
略写~~~~
移除 v-on.native
修饰符
父组件中绑定事件
<fu v-on:click="clickEvent" v-on:close="closeEvent">
</fu>
子组件中声明自定义事件
<script>
export default{
emits:['close']
}
</script>