5 Vue3

1.Vue3简介
2.Vue3带来了什么
1.性能的提升
打包大小减少41%
初次渲染快55%, 更新渲染快133%
内存减少54%
……
2.源码的升级
3.拥抱TypeScript
4.新的特性
Composition API(组合API)
setup配置
ref与reactive
watch与watchEffect
provide与inject
- ……
新的内置组件
Fragment
Teleport
Suspense
其他改变
- 新的生命周期钩子
data 选项应始终被声明为一个函数
- 移除
keyCode支持作为 v-on 的修饰符
- ……
一、创建Vue3.0工程
1.使用 vue-cli 创建
官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
1 2 3 4 5 6 7 8 9
| vue --version
npm install -g @vue/cli
vue create vue_test
cd vue_test npm run serve
|
2.使用 vite 创建
官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
vite官网:https://vitejs.cn
- 什么是
vite?—— 新一代前端构建工具。
- 优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(
HMR)。
- 真正的按需编译,不再等待整个应用编译完成。
- 传统构建 与 vite构建对比图

1 2 3 4 5 6 7 8
| npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
|
二、常用 Composition API
官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html
1.拉开序幕的setup
- 理解:
Vue3.0中一个新的配置项,值为一个函数。
setup是所有Composition API(组合API)“
表演的舞台 ”。
- 组件中所用到的:数据、方法等等,均要配置在setup中。
setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与
Vue2.x配置混用
Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
- 但在
setup中不能访问到Vue2.x配置(data、methos、computed...)。
- 如果有重名,
setup优先。
setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return
对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
main.js
1 2 3 4 5 6 7 8 9
| import {createApp} from 'vue' import App from './App.vue'
const app = createApp(App)
app.mount('#app')
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| <template> <h1>一个人的信息</h1> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>性别:{{sex}}</h2> <h2>a的值是:{{a}}</h2> <button @click="sayHello">说话(Vue3所配置的——sayHello)</button> <br> <br> <button @click="sayWelcome">说话(Vue2所配置的——sayWelcome)</button> <br> <br> <button @click="test1">测试一下在Vue2的配置中去读取Vue3中的数据、方法</button> <br> <br> <button @click="test2">测试一下在Vue3的setup配置中去读取Vue2中的数据、方法</button>
</template>
<script> // import {h} from 'vue' export default { name: 'App', data() { return { sex: '男', a: 100 } }, methods: { sayWelcome() { alert('欢迎来到尚硅谷学习') }, test1() { console.log(this.sex) console.log(this.name) console.log(this.age) console.log(this.sayHello) } }, //此处只是测试一下setup,暂时不考虑响应式的问题。 async setup() { //数据 let name = '张三' let age = 18 let a = 200
//方法 function sayHello() { alert(`我叫${name},我${age}岁了,你好啊!`) }
function test2() { console.log(name) console.log(age) console.log(sayHello) console.log(this.sex) console.log(this.sayWelcome) }
//返回一个对象(常用) return { name, age, sayHello, test2, a }
//返回一个函数(渲染函数) // return ()=> h('h1','尚硅谷') } } </script>
|
2.ref函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
JS中操作数据: xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()的get与set完成的。
- 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive函数。
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <template> <h1>一个人的信息</h1> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h3>工作种类:{{job.type}}</h3> <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) // job.value.type = 'UI设计师' // job.value.salary = '60K' // console.log(name,age) }
//返回一个对象(常用) return { name, age, job, changeInfo } } } </script>
|
3.reactive函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref函数)
- 语法:
const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个
代理对象(Proxy的实例对象,简称proxy对象)
reactive定义的响应式数据是“深层次的”。
- 内部基于
ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <template> <h1>一个人的信息</h1> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h3>工作种类:{{person.job.type}}</h3> <h3>工作薪水:{{person.job.salary}}</h3> <h3>爱好:{{person.hobby}}</h3> <h3>测试的数据c:{{person.job.a.b.c}}</h3> <button @click="changeInfo">修改人的信息</button> </template>
<script> import {reactive} from 'vue'
export default { name: 'App', setup() { //数据 let person = reactive({ name: '张三', age: 18, job: { type: '前端工程师', salary: '30K', a: { b: { c: 666 } } }, hobby: ['抽烟', '喝酒', '烫头'] })
//方法 function changeInfo() { person.name = '李四' person.age = 48 person.job.type = 'UI设计师' person.job.salary = '60K' person.job.a.b.c = 999 person.hobby[0] = '学习' }
//返回一个对象(常用) return { person, changeInfo } } } </script>
|
4.Vue3.0中的响应式原理
vue2.x的响应式
实现原理:
存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
Vue3.0的响应式
Vue3的响应式原理.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title>Document</title> </head> <body> <script type="text/javascript"> 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) } })
let obj = {a: 1, b: 2}
</script> </body> </html>
|
5.reactive对比ref
- 从定义数据角度对比:
ref用来定义:基本类型数据。
reactive用来定义:对象(或数组)类型数据。
- 备注:
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。
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <template> <h1>一个人的信息</h1> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <button @click="test">测试触发一下Demo组件的Hello事件</button> </template>
<script> import {reactive} from 'vue'
export default { name: 'Demo', props: ['msg', 'school'], emits: ['hello'], setup(props, context) { // console.log('---setup---',props) // console.log('---setup---',context) // console.log('---setup---',context.attrs) //相当与Vue2中的$attrs // console.log('---setup---',context.emit) //触发自定义事件的。 console.log('---setup---', context.slots) //插槽 //数据 let person = reactive({ name: '张三', age: 18 })
//方法 function test() { context.emit('hello', 666) }
//返回一个对象(常用) return { person, test } } } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <template> <Demo @hello="showHelloMsg" msg="你好啊" school="尚硅谷"> <template v-slot:qwe> <span>尚硅谷</span> </template> <template v-slot:asd> <span>尚硅谷</span> </template> </Demo> </template>
<script> import Demo from './components/Demo'
export default { name: 'App', components: {Demo}, setup() { function showHelloMsg(value) { alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`) }
return { showHelloMsg } } } </script>
|
7.计算属性与监视
1.computed函数
与Vue2.x中computed配置功能一致
写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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] } }) }
|
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <template> <h1>一个人的信息</h1> 姓:<input type="text" v-model="person.firstName"> <br> 名:<input type="text" v-model="person.lastName"> <br> <span>全名:{{person.fullName}}</span> <br> 全名:<input type="text" v-model="person.fullName"> </template>
<script> import {reactive, computed} from 'vue'
export default { name: 'Demo', setup() { //数据 let 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[1] } })
//返回一个对象(常用) return { person } } } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <Demo/> </template>
<script> import Demo from './components/Demo'
export default { name: 'App', components: {Demo}, } </script>
|
2.watch函数
与Vue2.x中watch配置功能一致
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true})
watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) })
watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false})
watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true})
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true})
watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true})
|
watch函数以及watch函数相关问题
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| <template> <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}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.j1.salary++">涨薪</button> </template>
<script> import {ref, reactive, watch} from 'vue'
export default { name: 'Demo', 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所定义的一个响应式数据的全部属性 1.注意:此处无法正确的获取oldValue 2.注意:强制开启了深度监视(deep配置无效) */ /* 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 } } } </script>
|
watch监视ref数据的说明
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <template> <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}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.j1.salary++">涨薪</button> </template>
<script> import {ref, reactive, watch} from 'vue'
export default { name: 'Demo', setup() { //数据 let sum = ref(0) let msg = ref('你好啊') let person = ref({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
console.log(person)
watch(sum, (newValue, oldValue) => { console.log('sum的值变化了', newValue, oldValue) })
watch(person, (newValue, oldValue) => { console.log('person的值变化了', newValue, oldValue) }, {deep: true})
//返回一个对象(常用) return { sum, msg, person } } } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <Demo/> </template>
<script> import Demo from './components/Demo'
export default { name: 'App', components: {Demo}, } </script>
|
3.watchEffect函数
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <template> <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}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.j1.salary++">涨薪</button> </template>
<script> import {ref, reactive, watch, watchEffect} from 'vue'
export default { name: 'Demo', setup() { //数据 let sum = ref(0) let msg = ref('你好啊') let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
//监视 /* watch(sum,(newValue,oldValue)=>{ console.log('sum的值变化了',newValue,oldValue) },{immediate:true}) */
watchEffect(() => { const x1 = sum.value const x2 = person.job.j1.salary console.log('watchEffect所指定的回调执行了') })
//返回一个对象(常用) return { sum, msg, person } } } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <Demo/> </template>
<script> import Demo from './components/Demo'
export default { name: 'App', components: {Demo}, } </script>
|
8.生命周期


1Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| <template> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">点我+1</button> </template>
<script> import {ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
export default { name: 'Demo',
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>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button> <Demo v-if="isShowDemo"/> </template>
<script> import {ref} from 'vue' import Demo from './components/Demo'
export default { name: 'App', components: {Demo}, setup() { let isShowDemo = ref(true) return {isShowDemo} } } </script>
|
9.自定义hook函数
src/hooks/usePoint.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import {reactive, onMounted, 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 }
|
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <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: 'Demo', setup() { //数据 let sum = ref(0) let point = usePoint()
//返回一个对象(常用) return {sum, point} } } </script>
|
Test.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <h2>我是Test组件</h2> <h2>当前点击时鼠标的坐标为:x:{{point.x}},y:{{point.y}}</h2> </template>
<script> import usePoint from '../hooks/usePoint'
export default { name: 'Test', setup() { const point = usePoint() return {point} } } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button> <Demo v-if="isShowDemo"/> <hr> <Test/> </template>
<script> import {ref} from 'vue' import Demo from './components/Demo' import Test from './components/Test'
export default { name: 'App', components: {Demo, Test}, setup() { let isShowDemo = ref(true) return {isShowDemo} } } </script>
|
10.toRef
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
语法:const name = toRef(person,'name')
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefs 与toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <template> <h4>{{person}}</h4> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{job.j1.salary}}K</h2> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="job.j1.salary++">涨薪</button> </template>
<script> import {ref, reactive, toRef, toRefs} from 'vue'
export default { name: 'Demo', setup() { //数据 let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
// const name1 = person.name // console.log('%%%',name1)
// const name2 = toRef(person,'name') // console.log('####',name2)
const x = toRefs(person) console.log('******', x)
//返回一个对象(常用) return { person, // name:toRef(person,'name'), // age:toRef(person,'age'), // salary:toRef(person.job.j1,'salary'), ...toRefs(person) } } } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button> <Demo v-if="isShowDemo"/> </template>
<script> import {ref} from 'vue' import Demo from './components/Demo'
export default { name: 'App', components: {Demo}, setup() { let isShowDemo = ref(true) return {isShowDemo} } } </script>
|
三、其它 Composition API
1.shallowReactive 与 shallowRef
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <template> <h4>当前的x.y值是:{{x.y}}</h4> <button @click="x={y:888}">点我替换x</button> <button @click="x.y++">点我x.y++</button> <hr> <h4>{{person}}</h4> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{job.j1.salary}}K</h2> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="job.j1.salary++">涨薪</button> </template>
<script> import {ref, reactive, toRef, toRefs, shallowReactive, shallowRef} from 'vue'
export default { name: 'Demo', setup() { //数据 // let person = shallowReactive({ //只考虑第一层数据的响应式 let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } }) let x = shallowRef({ y: 0 }) console.log('******', x)
//返回一个对象(常用) return { x, person, ...toRefs(person) } } } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button> <Demo v-if="isShowDemo"/> </template>
<script> import {ref} from 'vue' import Demo from './components/Demo'
export default { name: 'App', components: {Demo}, setup() { let isShowDemo = ref(true) return {isShowDemo} } } </script>
|
2.readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <template> <h4>当前求和为:{{sum}}</h4> <button @click="sum++">点我++</button> <hr> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{job.j1.salary}}K</h2> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="job.j1.salary++">涨薪</button> </template>
<script> import {ref, reactive, toRefs, readonly, shallowReadonly} from 'vue'
export default { name: 'Demo', setup() { //数据 let sum = ref(0) let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
person = readonly(person) // person = shallowReadonly(person) // sum = readonly(sum) // sum = shallowReadonly(sum)
//返回一个对象(常用) return { sum, ...toRefs(person) } } } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button> <Demo v-if="isShowDemo"/> </template>
<script> import {ref} from 'vue' import Demo from './components/Demo'
export default { name: 'App', components: {Demo}, setup() { let isShowDemo = ref(true) return {isShowDemo} } } </script>
|
3.toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive生成的响应式对象转为
普通对象。
- 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
Demo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <template> <h4>当前求和为:{{sum}}</h4> <button @click="sum++">点我++</button> <hr> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{job.j1.salary}}K</h2> <h3 v-show="person.car">座驾信息:{{person.car}}</h3> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="job.j1.salary++">涨薪</button> <button @click="showRawPerson">输出最原始的person</button> <button @click="addCar">给人添加一台车</button> <button @click="person.car.name+='!'">换车名</button> <button @click="changePrice">换价格</button> </template>
<script> import {ref, reactive, toRefs, toRaw, markRaw} from 'vue'
export default { name: 'Demo', setup() { //数据 let sum = ref(0) let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
function showRawPerson() { const p = toRaw(person) p.age++ console.log(p) }
function addCar() { let car = {name: '奔驰', price: 40} person.car = markRaw(car) }
function changePrice() { person.car.price++ console.log(person.car.price) }
//返回一个对象(常用) return { sum, person, ...toRefs(person), showRawPerson, addCar, changePrice } } } </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button> <Demo v-if="isShowDemo"/> </template>
<script> import {ref} from 'vue' import Demo from './components/Demo'
export default { name: 'App', components: {Demo}, setup() { let isShowDemo = ref(true) return {isShowDemo} } } </script>
|
4.customRef
5.provide 与 inject

Child.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <template> <div class="child"> <h3>我是Child组件(子)</h3> <Son/> </div> </template>
<script> import {inject} from 'vue' import Son from './Son.vue'
export default { name: 'Child', components: {Son}, /* setup(){ let x = inject('car') console.log(x,'Child-----') } */ } </script>
<style> .child { background-color: skyblue; padding: 10px; } </style>
|
Son.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <template> <div class="son"> <h3>我是Son组件(孙),{{car.name}}--{{car.price}}</h3> </div> </template>
<script> import {inject} from 'vue'
export default { name: 'Son', setup() { let car = inject('car') return {car} } } </script>
<style> .son { background-color: orange; padding: 10px; } </style>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <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: '40W'}) provide('car', car) //给自己的后代组件传递数据 return {...toRefs(car)} } } </script>
<style> .app { background-color: gray; padding: 10px; } </style>
|
6.响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive 创建的响应式代理
- isReadonly: 检查一个对象是否是由
readonly 创建的只读代理
- isProxy: 检查一个对象是否是由
reactive 或者 readonly 方法创建的代理
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <h3>我是App组件</h3> </template>
<script> import {ref, reactive, toRefs, readonly, isRef, isReactive, isReadonly, isProxy} from 'vue'
export default { name: 'App', setup() { let car = reactive({name: '奔驰', price: '40W'}) let sum = ref(0) let car2 = readonly(car)
console.log(isRef(sum)) console.log(isReactive(car)) console.log(isReadonly(car2)) console.log(isProxy(car)) console.log(isProxy(sum))
return {...toRefs(car)} } } </script>
<style> .app { background-color: gray; padding: 10px; } </style>
|
四、Composition API 的优势
1.Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
2.Composition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
五、新的组件
1.Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
2.Teleport
Child.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div class="child"> <h3>我是Child组件</h3> <Son/> </div> </template>
<script> import Son from './Son'
export default { name: 'Child', components: {Son}, } </script>
<style> .child { background-color: skyblue; padding: 10px; } </style>
|
Son.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div class="son"> <h3>我是Son组件</h3> <Dialog/> </div> </template>
<script> import Dialog from './Dialog.vue'
export default { name: 'Son', components: {Dialog} } </script>
<style> .son { background-color: orange; padding: 10px; } </style>
|
Dialog.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <template> <div> <button @click="isShow = true">点我弹个窗</button> <teleport to="body"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <h4>一些内容</h4> <h4>一些内容</h4> <h4>一些内容</h4> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport> </div> </template>
<script> import {ref} from 'vue'
export default { name: 'Dialog', setup() { let isShow = ref(false) return {isShow} } } </script>
<style> .mask { position: absolute; top: 0; bottom: 0; left: 0; right: 0; background-color: rgba(0, 0, 0, 0.5); }
.dialog { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; width: 300px; height: 300px; background-color: green; } </style>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <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: '40W'}) provide('car', car) //给自己的后代组件传递数据 return {...toRefs(car)} } } </script>
<style> .app { background-color: gray; padding: 10px; } </style>
|
3.Suspense
Child.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <template> <div class="child"> <h3>我是Child组件</h3> {{sum}} </div> </template>
<script> import {ref} from 'vue'
export default { name: 'Child', async setup() { let sum = ref(0) let p = new Promise((resolve, reject) => { setTimeout(() => { resolve({sum}) }, 3000) }) return await p } } </script>
<style> .child { background-color: skyblue; padding: 10px; } </style>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>稍等,加载中...</h3> </template> </Suspense> </div> </template>
<script> // import Child from './components/Child'//静态引入 import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(() => import('./components/Child')) //异步引入 export default { name: 'App', components: {Child}, } </script>
<style> .app { background-color: gray; padding: 10px; } </style>
|
六、其他
1.全局API的转移
Vue 2.x 有许多全局 API 和配置。
例如:注册全局组件、注册全局指令等。
1 2 3 4 5 6 7 8 9 10 11 12
| Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' })
Vue.directive('focus', { inserted: el => el.focus() }
|
Vue3.0中对这些API做出了调整:
2.其他改变