main.js中写发生变化,并不兼容vue2的写法
javascript
//vue3
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')
//vue2
import Vue from 'vue'
import './style.css'
import App from './App.vue'
const vm = new Vue({
render:h=>h(app)
})
vm.$mount("#app")
vue3中template模版可以不使用根标签。
Composition API
setup:组件中所用到的数据,方法,计算属性,生命周期等,均配置在这里,返回有两种值:
- 对象:其属性,方法在模版中可以直接使用。
- 返回一个渲染函数,可自定义渲染内容,会直接代替模版内容,不常用
setup在beforeCreate之前执行一次,this是undefined
setup含有两个参数:
props:值为对象,包含组件外部传递过来的,且组件内部声明接收了的属性,想要获取props的属性,一定需要props接收参数,这点和vue2相同,倘若不使用props接收,那么数据会存储在context参数中的attrs属性中。
context:上下文对象,包括
attrs:包含组件外部传递过来,但没有在props中声明的属性,相当于替换$attrs
slots:收到的插槽内容,相当于this.$slots,
emit:分发自定义时间的函数,相当于挺好$emit(见下方代码)
emits与vue2有区别,在vue3中传递给父组件需要emits来声明已绑定在子组件的事件。
vue3不要和vue2混用,vue2可以访问到setup中的数据,但setup不能访问到vue2的数据,如果有重复数据,setup的优先级更高。setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模版会看不到return中的属性。
html
<script>
import { h } from 'vue';
import HelloWorld from './components/HelloWorld.vue'
export default{
name:"app",
props:['name','age'],
emits:['test']
compponents:{HelloWorld},
setup(props,context) {
//非响应式数据
let name = props.name?props.name:'张三'
let age = props.age?props.agge:18
function Greeting(){
console.log(name,age);
}
//返回一个对象(常用)
return {
name,
age,
Greeting
}
function test(){
context.emit(test,'123')
}
//返回渲染函数
// return ()=> h("h1",'你好')
}
}
</script>
<template>
<div>
app
</div>
<p>{{name }}-{{ age }}</p>
</template>
<style scoped>
</style>
响应式数据
ref函数 适用于基本类型数据,也可以是对象类型,创建一个reference的引用对象,修改数据需要.value的值,模版中读取数据不需要.value,直接使用即可。
接收的数据可以是基本类型也可以是对象类型,基本类型的书记是靠数据劫持Object.defineProperty()完成的,而对象类型会转为Proxy对象,借助了reactive函数。
reactive函数 定义一个对象类型的响应式数据(基本类型不需要它,使用ref),包括数组类型也支持响应式。
内部基于es6的proxy实现,返回一个代理对象Proxy,通过代理对象内部数据进行操作,reactive定义的响应式数据是深层次的。
html
<script>
import {ref,reactive} from 'vue'
import HelloWorld from './components/HelloWorld.vue'
export default{
name:"app",
props:['name','age'],
compponents:{HelloWorld},
setup(props) {
//定义响应式数据
let name = ref('张三')
let age = ref(19)
let obj = reactive({type:"ui",salary:"100k"})
function msgChange(){
name.value = "李四"
age.value = 20
obj.type = 'web'
obj.salary = '200k'
}
//返回一个对象(常用)
return {
name,
age,
msgChange,
obj
}
}
}
</script>
<template>
<div>
app
</div>
<p>{{name }}-{{ age }}</p>
<p>{{obj.type }}-{{ obj.salary }}</p>
<button @click="msgChange">修改信息</button>
</template>
<style scoped>
</style>
proxy的响应式实现原理:
通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写,属性的添加,属性的删除等,比起vue2多出了增加和删除属性的监听。
通过Reflect(反射):对源对象的属性进行操作。类似于objec的操作方法。
使用Reflect的原因:在封装框架的过程中,Object.defineProperty()如果添加了相同的属性名,那么会直接报错,导致js线程阻塞,如果要解决那么就要写很多的 try{} catch(){} ,Reflect.defineProperty()会有个返回值,可以通过判断决定是否即系往下走,所以Reflect相对友好一点。
html
<script>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive} from 'vue'
export default{
name:"app",
props:['name','age'],
compponents:{HelloWorld},
setup(props) {
let person = {name:"张三",age:18}
const p = new Proxy(person,{
//target就是person本身
//以下两种方式都可以,vue3主要采用Reflect的方式
get(target,propName){
// return target[propName]
return Reflect.get(target,propName)
},
//读取和新增属性,都会调用这个方法
set(target,propName,value){
// target[propName] = value
Reflect.set(target,propName,value)
},
deleteProperty(target,propName){
// return delete target[propName]
return Reflect.defineProperty(target,propName)
}
})
function msgChange(){
person.name='李四'
}
//返回一个对象(常用)
return {
person
}
}
}
</script>
<template>
<div>
app
</div>
<p>{{person.name }}-{{ person.age }}</p>
<button @click="msgChange">修改信息</button>
</template>
<style scoped>
</style>
计算属性computed:
需要引入computed函数,配置与vue相似。
html
<script>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive,computed} from 'vue'
export default{
name:"app",
setup(props) {
//定义响应式数据
let name = ref('张三')
let age = ref(19)
//简写
let fullName = computed(()=>{
return name.value
})
//完整写法
let fullName2 = computed({
get(){
return name.value
},
set(value){
name.value = value
}
})
//返回一个对象(常用)
return {
name,
age,
fullName,
fullName2
}
}
}
</script>
<template>
<div>
app
</div>
<p>{{fullName}}</p>
<p>{{fullName2}}</p>
</template>
<style scoped>
</style>
监听watch:
html
<script>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive,computed,watch} from 'vue'
export default{
name:"app",
setup(props) {
//定义响应式数据
let name = ref('张三')
let age = ref(19)
let person = reactive({
name:"李四",
age:19,
job:{
job1:'20k'
}
})
//监听ref定义的数据,多个使用数组
watch([name,age],(newVal,oldVal)=>{
console.log(111);
},{immediate:true,deep:true})
//监听reactive定义的数据,会强制开启深度监听模式,拿不到oldValue
watch(person,(newVal,oldVal)=>{
console.log(newVal,oldVal);
},{immediate:true,deep:true})
//监听reactive定义的数据的一个属性,多个属性用数组
watch([()=>person.name,()=>person.age],(newVal,oldVal)=>{
console.log(newVal,oldVal);
})
//特殊情况,监听reactive对象属性,这个属性是个对象,需要开启深度监听,否则无效,同时也拿不到oldValue
watch(()=>person.job,(newVal,oldVal)=>{
console.log(newVal,oldVal);
},{deep:true})
function nameChange(e){
name.value = '333'
}
function nameChange(e){
name.value = '333'
person.name='222'
}
//返回一个对象(常用)
return {
name,
age,
nameChange,
person
}
}
}
</script>
<template>
<div>
app
</div>
<p>{{name}}</p>
<button @click="nameChange">修改</button>
</template>
<style scoped>
</style>
监听reactive对象时,不能获取到oldvalue,并且强制开启了deep深度监听配置,而且配置无效。
监听reactive对象的某个属性(为对象)时,必须deep配置才有效。
监听ref对象时,需要添加.value,实际上就是监听proxy代理的reactive的对象数据,强制开启了深度监听。
监听ref对象时,也可以直接使用对象名称,但是需要自己开启deep深度监听。
javascript
let person = ref({
name:"李四",
age:19,
job:{
job1:'20k'
}
})
//监听ref定义的数据,深度监听要自己手动添加deep深度监听
watch(person,(newVal,oldVal)=>{
console.log(111);
},{deep:true})
//实际是reactive数据,强制开启了deep深度监听
watch(person.value,(newVal,oldVal)=>{
console.log(newVal,oldVal);
})
watchEffect:同样需要先引入,不用指明监视的属性,监视的回调中用到哪个属性,就监视哪个属性。
javascript
<script>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive,computed,watch,watchEffect} from 'vue'
export default{
name:"app",
setup(props) {
//定义响应式数据
let name = ref('张三')
let age = ref(19)
let person = reactive({
name:"李四",
age:19,
job:{
job1:'20k'
}
})
watchEffect((()=>{
const x1 = name.value
const x2 = person.job.job1
console.log('watchEffect');
}))
function nameChange(e){
name.value = '333'
}
function nameChange(e){
name.value = '333'
person.name='222'
}
//返回一个对象(常用)
return {
name,
age,
nameChange,
person
}
}
}
</script>
<template>
<div>
app
</div>
<p>{{name}}</p>
<button @click="nameChange">修改</button>
</template>
<style scoped>
</style>
生命周期
vue2通过new Vue()的形式创建vue实例,在没有el的情况下需要等$mount函数调用才可以继续往下走,但已经执行了beforeCreate created生命周期,其实只创建了vm,并没有被挂载,会造成资源浪费;vue3通过createApp并且执行了mount函数之后,再执行生命周期函数,并且减少了判断次数,提高执行效率。
beforeDestroy替换为beforeUnmount
destroyed替换为unmounted
组合式生命周期api:
beforeCreate => setup()
created => setup()
beforeMount => onBeforeMount
mounted => onMounted
beforeUpdate => onBeforeUpdate
updated => onUpdated
beforeUnmount => onBeforeUnmount
unmounted => onUnmounted
html
<script>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive,computed,watch,watchEffect,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default{
name:"app",
setup(props) {
onBeforeMount(() => {
}),
onMounted(() => {
}),
onBeforeUpdate(() => {
}),
onUpdated(() => {
}),
onBeforeUnmount(() => {
}),
onUnmounted(() => {
}),
//返回一个对象(常用)
return {}
}
}
</script>
<template>
<div>
app
</div>
</template>
<style scoped>
</style>
自定义hook函数 :把setup函数中使用的组合式api进行封装。
javascript
//useMounted.js
import {onMounted, reactive,} from 'vue'
export default function (){
let obj = reactive({
name:null,
age:null
})
onMounted(()=>{
obj.name = '789'
obj.age=19
})
return obj
}
javascript
<script>
import HelloWorld from './components/HelloWorld.vue'
import {ref,reactive,computed,watch,watchEffect} from 'vue'
import useMounted from './hooks/useMounted'
export default{
name:"app",
setup(props) {
let obj = useMounted()
//返回一个对象(常用)
return {
obj
}
}
}
</script>
<template>
<div>
app
</div>
<p>{{obj.name}}-{{ obj.age }}</p>
</template>
<style scoped>
</style>
toRef和toRefs:使对象里所有的属性都变为ref响应式数据。toRef智能操作一个属性,toRefs类似于浅拷贝,可以直接将reactive响应式对象放入,实现多个属性的转变,将数据拆散了return出去。
javascript
<script>
import HelloWorld from './components/HelloWorld.vue'
import {toRefs,reactive,computed,watch,watchEffect,toRef} from 'vue'
export default{
name:"app",
setup(props) {
let person = reactive({
name:"李四",
age:19,
job:{
job1:'20k'
}
})
let student = reactive({
grade:3,
class:4
})
function nameChange(e){
person.name = '333'
person.age=99
}
//返回一个对象(常用)
return {
name:toRef(person,'name'),
age:toRef(person,'age'),
...toRefs(student),
nameChange,
}
}
}
</script>
<template>
<div>
app
</div>
<p>{{name}}-{{ age }}</p>
<p>{{grade}}-{{ classes }}</p>
<button @click="nameChange">修改</button>
</template>
<style scoped>
</style>
其他组合API(都需要在顶部引入对应的api)
shallowReactive与shallowRef
用法和ref相同,shallowReactive只会将对象最外层的属性作为响应式数据,内层的不是响应式。
shallowRef 如果传入是个基本类型,ref和shallowRef没有区别,如果传入是个对象,shallowRef不再进行响应式处理,但是ref会转为proxy的响应式。
readOnly和shallowReadOnly
readOnly将传入的对象类型的响应式数据全部变为只读,不可更改值,shallowReadOnly将响应式对象类型的数据第一层处理为readOnly深层次不处理,对于基本类型都处理为只读
javascript
obj = readOnly(obj)
obj = shadowReadOnly(obj)
toRaw与markRaw
toRaw 将一个由reactive生成的响应式对象转为普通对象,不会引起页面更新。
markRaw 标记一个对象,使其永远不会再成为响应式对象(有些值不应该被设为响应式,例如第三方的库等等),跳过响应式转换可以提高性能。
javascript
let obj = toRaw(person)
obj.car = markRaw(car)
customRef(自定义ref)
创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制
示例:
javascript
<script>
import { customRef } from 'vue'
export default {
name: "app",
setup(props) {
let keyWord = myRef('hello',1000)
function myRef(value,delay) {
let timer;
return customRef((track, trigger) => {
return {
get() {
track() //通知vue追踪数据变化
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger() //通知vue去重新解析模版
}, delay)
}
}
})
}
//返回一个对象(常用)
return {
keyWord
}
}
}
</script>
<template>
<input type="text" v-model="keyWord">
<h3>{{ keyWord }}</h3>
</template>
<style scoped></style>
provide与inject(需要引入)
实现祖孙组件之间的通信:
祖组件:
javascript
//祖组件
setup(){
let car = reactive({name:'张三',age:9})
provide('car',car)
}
//孙组件
setup(){
const car = inject('car')
return{
car
}
}
响应式数据的判断
isRef:是否为ref对象
isReactive:是否为reactive创建的响应式
isReadonly:是否是由readonly创建的只读代理
isProxy:是否由reactive或者readonly方法创建的代理
一些新的组件
Fragment
vue2中组件必须有一个根标签,vue3中可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中,减少了标签层级,减少内存占用。
Teleport
是一种能将我们组件html结构移动到指定位置的技术
to:指定所要传送的位置(body / html /css选择器),会改变页面的html的布局。
html
<teleport to="body">
<Demo />
<teleport/>
Suspense组件
等待异步组件时渲染一些额外内容,用户体验变好。
html
//父组件
<Suspense>
<template v-slot:default>
<Child />
<template />
<template v-slot:fallback> //有个加载中的画面提示
<h3>加载中。。。<h3/>
<template />
<Suspanse/>
//异步引入
<script>
import {defineAsyncCompponent} from 'vue'
const Child = defineAsyncCompponent(()=>import('./Child')) //动态引入
export default{
components:{Child}
}
</script>
html
//子组件
<template>
<h3>子组件<h3/>
<template/>
//异步引入
<script>
import {defineAsyncCompponent,ref} from 'vue'
const Child = defineAsyncCompponent(()=>import('./Child')) //动态引入
export default{
setup(){
let sum = ref(0)
let p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(sum)
},3000)
})
return await p
}
}
</script>
全局api的转移
全部调整带应用实例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 |
| Vie.use | app.use |
| Vue.prototype | app.config.globalProperties |
其他改变:
data选项应始终被声明为一个函数,防止组件在被复用的时候产生数据的关联关系,造成干扰。
过渡类名的更改:
html
<style>
//vue2
.v-enter,
.v-leave-to{
opacity:0;
};
.v-leave,
.v-enter-to{
opacity:1;
}
//vue3
.v-enter-from,
.v-leave-to{
opacity:0;
};
.v-leave-from,
.v-enter-to{
opacity:1;
}
<style/>
移除keyCode作为v-on的修饰符,会存在一定的兼容性问题,所以移除。
已不被支持keyUp.13
不支持config.keyCodes自定义别名按键(Vue.config.keyCodes.huiche=13)
移除v-on.native修饰符:子组件中emits不接受的click事件为原生事件,否则为自定义事件。
父组件:
<div>
<Child @click = "handleAdd" @close="handleClose" />
</div>
子组件:
export default{
emits:['close']
}
移除过滤器filter
过滤器虽然看起来很方便,但需要一个自定义语法,打破了大括号内表达式是'只是javascript'的假设,不仅有学习成本,而且还有实现成本,建议使用方法调用或者计算属性替换过滤器。
其他的可以参考官网: 快速上手 | Vue.js