Vue3.x 官网
注:新版vue3的setup语法糖更为简洁
,详见 Vue3 script setup 语法糖,超爽体验
一、Composition API 简介
Vue2 时的形式在代码很少的时候,逻辑构造还是蛮清晰的,然而随着组件性能越来越多,代码量越来越大,整体内容全副放在其中必定会显得臃肿。因为每个功能模块的代码会散落散布在各个地位,让整个我的项目的内容难以浏览和保护。如下图:
而到了 Vue3,它会依据逻辑性能来进行组织,把同一个性能的不同代码都放在一起,或者把它们独自拿进去放在一个函数中,所以 Composition API
又被称为基于函数组合的API:
1. setup 函数
setup 函数是 Vue3 中新增的函数,它是咱们在编写组件时,应用 Composition API
的入口。
同时它也是 Vue3 中新增的一个生命周期函数,会在 beforeCreate
之前调用。因为此时组件的 data
和 methods
还没有初始化,因而在 setup 中是不能应用 this
的。所以 Vue 为了防止咱们谬误的应用,它间接将 setup 函数中的 this
批改成了undefined
。并且,咱们只能同步应用setup函数,不能用async将其设为异步。
setup 函数接管两个参数 props
和 context
, 语法为:setup(props,context){}
props
props
外面蕴含父组件传递给子组件的所有数据。在子组件中应用 props
进行接管。
props
是响应式的, 当传入新的 props
时,会及时被更新。
因为是响应式的, 所以不能够应用 ES6 解构,解构会打消它的响应式。
父组件:
<template> <!-- 父组件向子组件传递数据 --> <Sub :name="name" :age="age" /></template><script>import { ref } from 'vue'import Sub from './Sub.vue'export default { setup () { const name = ref('张三'); const age = ref(20) return { name, age } }, components: { Sub },}</script>
子组件(Sub.vue):
<template> <div>{{name}}{{age}}</div></template><script>export default { props: { name: String, age: Number }, mounted () { // vue2.x 的写法 console.log(this.name); // 张三 console.log(this.age); // 20 }, setup (props) { // vue3.x 的写法 console.log(props.name); // 张三 console.log(props.age); // 20 // let { name ,age } = props; // 不能间接解构 let { name, age } = toRefs(props); console.log(name.value, age.value); // 张三 20 }}</script>
context
context
外面蕴含 attrs
, slots
, emit
等数据办法:
attrs
:获取组件上的属性slots
:获取 slot 插槽的节点emit
:emit 办法(子组件向父组件传递数据)
父组件:
<template> <Sub subData="some other data" @subClick='subClick'>parent</Sub></template><script>import Sub from './Sub.vue'export default { setup () { function subClick (e) { console.log(e); // 接管子组件传递过去的数据 } return { subClick } }, components: { Sub },}</script>
子组件(Sub.vue):
<template> <!-- 父组件向子组件传递数据 --> <div @click="handleClick">Child</div></template><script>export default { mounted () { // vue2.x 获取组件上的属性 console.log(this.$attrs.subData); // 'some other data' // vue2.x 获取slot插槽的节点 console.log(this.$slots); }, methods: { // vue2.x emit办法(子组件向父组件传递数据) handleClick () { this.$emit('subClick', 'vue2.x - this is subData') }, }, setup (props, context) { let { attrs, slots, emit } = context; // vue3.x 获取组件上的属性 console.log(attrs.subData); // 'some other data' // vue3.x 获取slot插槽的节点 console.log(slots.default()); // vue3.x emit办法(子组件向父组件传递数据) function handleClick () { emit('subClick', 'vue3.x - this is subData'); } return { handleClick } }}</script>
2. 生命周期
Vue3.x 生命周期
setup 函数是 Vue3 中新增的一个生命周期函数
- setup 函数会在
beforeCreate
之前调用,因为此时组件的data
和methods
还没有初始化,因而在 setup 中是不能应用this
的。 - 所以 Vue 为了防止咱们谬误的应用,它间接将 setup 函数中的
this
批改成了undefined
。 - setup函数,只能是同步的不能是异步的
Vue2.x (Option API) | Vue3.x (Composition API) |
---|---|
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
errorCaptured | onErrorCaptured |
- - | onRenderTracked |
- - | onRenderTriggered |
初始化加载程序:
setup
=> beforeCreate
=> created
=> onBeforeMount
=> onMounted
<script>import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onRenderTracked, onRenderTriggered } from 'vue'export default { setup () { console.log('setup'); // 生命周期钩子(没有beforeCreate和created) onBeforeMount(() => { console.log('onBeforeMount'); }) onMounted(() => { console.log('onMounted'); }) onBeforeUpdate(() => { console.log('onBeforeUpdate'); }) onUpdated(() => { console.log('onUpdated'); }) onBeforeUnmount(() => { console.log('onBeforeUnmount'); }) onUnmounted(() => { console.log('onUnmounted'); }) // 新增的debug钩子 生产环境中会被疏忽 onRenderTracked(() => { console.log('onRenderTracked'); }) // 每次渲染后从新收集响应式依赖,在onMounted前触发,页面更新后也会触发 onRenderTriggered(() => { console.log('onRenderTriggered'); }) // 每次触发页面从新渲染时的主动执行,在onBeforeUpdate之前触发 }, beforeCreate () { console.log('beforeCreate'); }, created () { console.log('created'); }}</script>
3. 返回值
setup
函数中返回一个对象,能够在模板中间接拜访该对象中的属性和办法。
<template> <div @click="handleClick">{{name1}} - {{name2}}</div></template><script>import { ref, provide } from 'vue'export default { setup () { const name1 = ref('张三'); return { name1, name2: 'zhangsan', handleClick () { console.log(name1.value); // 张三 console.log(this.name2); // zhangsan } } }}</script>
4. ref 与 reactive
创立一个响应式数据
- ref:任意类型(倡议根本类型)数据的响应式援用(设置、获取值时须要加
.value
)。
ref 的实质是拷贝,批改数据是不会影响到原始数据。 - reactive:只能是简单类型数据的响应式援用
<template> <ul> <li>ref 根本类型:{{name1}}</li> <li>ref 简单类型:{{name2.name}}</li> <li>reactive 简单类型:{{name3.name}}</li> </ul></template><script>import { ref, reactive } from 'vue'export default { setup () { let nameStr = '张三'; let name1 = ref(nameStr); // ref为根本数据类型增加响应式状态 setTimeout(() => { name1.value = 'zhangsan'; console.log(name1.value); // 'zhangsan' console.log(nameStr); // '张三' => 不会影响到原始数据 }, 1000) let nameObj2 = { name: '张三' }; let name2 = ref(nameObj2); // ref为简单数据类型增加响应式状态(不倡议) setTimeout(() => { // 设置值须要加.value name2.value.name = 'zhangsan'; console.log(name2.value.name); // 'zhangsan' => 获取值须要加.value console.log(nameObj2); // {name: "zhangsan"} => 会影响到原始数据 }, 2000) let nameObj3 = { name: '张三' }; const name3 = reactive(nameObj3);// reactive只能为简单数据类型增加响应式状态 setTimeout(() => { name3.name = 'zhangsan'; console.log(name3.name); // 'zhangsan' console.log(nameObj3); // {name: "zhangsan"} => 会影响到原始数据 }, 3000) return { name1, name2, name3 } }}</script>
5. toRef 与 toRefs
也能够创立一个响应式数据
- toRef:用来给抽离响应式对象中的某一个属性,并把该属性包裹成 ref 对象,使其和原对象产生链接。
toRef 的实质是援用,批改响应式数据会影响原始数据。 - toRefs:用来把响应式对象转换成一般对象,把对象中的每一个属性,包裹成 ref 对象。
toRefs 就是 toRef 的升级版,只是toRefs 是把响应式对象进行转换,其余的个性和 toRef 无二
<template> <ul> <li>{{name1}}</li> <li>{{name2.name.value}}</li> <li>{{name}}</li> </ul></template><script>import { toRef, toRefs, reactive } from 'vue'export default { setup () { let nameObj1 = reactive({ name: '张三' }); let name1 = toRef(nameObj1, 'name'); let age1 = toRef(nameObj1, 'age '); // age不存在 setTimeout(() => { name1.value = 'zhangsan'; age1.value = 20; // 即使age不存在也能失常响应式赋值 }, 1000) let nameObj2 = reactive({ name: '张三' }); let age2 = toRef(nameObj3, 'age '); // age不存在 let name2 = toRefs(nameObj2); setTimeout(() => { name2.name.value = 'zhangsan'; age2.value = 20; // age不存在,赋值无反馈 }, 2000) let nameObj3 = reactive({ name: '张三' }); setTimeout(() => { nameObj3.name = 'zhangsan'; }, 3000) let { name } = toRefs(nameObj3); // 解构后仍需放弃响应式(重要) return { name1, name2, name } }}</script>
6. readonly 只读属性
示意响应式对象不可批改
<template> <ul> <li>{{nameObj.name}}</li> </ul></template><script>import { reactive, readonly } from 'vue'export default { setup () { let nameObj = reactive({ name: '张三' }); let readonlyObj = readonly(nameObj); // 对nameObj响应式对象设置成只读,不可批改 setTimeout(() => { readonlyObj.name = 'zhangsan'; // 无奈设置属性 => Set operation on key "name" failed: target is readonly. Proxy {name: "张三"} }, 1000) return { nameObj } }}</script>
7. method办法
<template> <!-- 调用办法 --> <button @click='changeName'>按钮</button> </template><script setup>import { reactive } from 'vue'export default { setup () { const state = reactive({ name: '张三' }); // 申明method办法 const changeName = () => { state.name = 'zhangsan'; } return { state , changeName } }}</script>
8. computed 计算属性
<template> <div> <button @click="add">+</button> <p>{{addCount}}</p> </div></template><script>import { ref, computed } from 'vue'export default { setup () { const num = ref(0); const add = () => { num.value++ } // 计算属性 const addCount = computed(() => { return num.value * 2; }) return { add, addCount } }}</script>
9. watch 与 watchEffect 监听属性
watch
函数:
用来侦听特定的数据源,并在回调函数中执行副作用。
默认状况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。watchEffect
函数:
1.立刻执行、立刻监听(immediate)
2.主动会感知代码依赖(主动收集依赖),不须要传递监听的内容(不须要像watch
一样手动传入依赖)
3.无奈取得变动前的值(oldVal)
<template> <div> <p>{{name}}</p> <p>{{nameObj.name}}</p> </div></template><script>import { ref, reactive, watch, watchEffect } from 'vue'export default { setup () { // 一、监听根本类型 const name = ref('张三'); setTimeout(() => { name.value = '李四'; }, 1000); watch(name, (newVal, oldVal) => { console.log(newVal, oldVal); }, { immediate: true }) // 立刻执行 // 二、监听简单类型 const nameObj = reactive({ name: 'zhangsan' }) setTimeout(() => { nameObj.name = 'lisi'; }, 2000); // 1.简单数据无奈间接监听、惰性 watch(() => nameObj, (newVal, oldVal) => { console.log(newVal, oldVal); // 不会触发 }) // 2.须要深度监听、不惰性 watch(() => nameObj, (newVal, oldVal) => { console.log(newVal, oldVal); // newVal、oldVal具备响应式 }, { deep: true, // 深度监听 immediate: true, // 立刻监听 }) // 3.也能够间接监听对象的属性 watch(() => nameObj.name, (newVal, oldVal) => { console.log(newVal, oldVal); }) // 三、同时监听多个对象 watch([() => nameObj.name, () => nameObj.lastName], ([newName, newLastName], [oldName, oldLastName]) => { console.log(newName, oldName, newLastName, oldLastName); }) const stop = watchEffect(() => { console.log(name); console.log(nameObj.name); }) // 3秒后进行监听 setTimeout(()=>{ stop(); },3000) return { name, nameObj } }}</script>
二、获取DOM节点
<template> <input type="text" value="张三" ref="hello"></template><script>import { ref, onMounted } from 'vue'export default { setup () { const hello = ref(null); // 获取组件中ref="hello"的实在dom元素 onMounted(() => { console.log(hello.value); // <input type="text"> console.log(hello.value.value); // 张三 }) return { hello } }}</script>
三、provide 与 inject
父组件向子组件传递数据与办法
父组件:
<template> <Sub /></template><script>import { ref, provide } from 'vue'import Sub from './Sub.vue'export default { setup () { const name = ref('张三'); // provide(别名, 要传递的数据和办法) provide('myName', name) provide('handleClick', () => { name.value = 'zhangsan'; }) }, components: { Sub },}</script>
子组件(Sub.vue):
<template> <div @click="handleClick">{{name}}</div></template><script>import { inject } from 'vue'export default { setup () { //调用 inject 办法,通过指定的别名,接管到父级共享的数据和办法 const name = inject('myName'); const handleClick = inject('handleClick'); return { name, handleClick } }}</script>
四、Teleport 传送门
Teleport
翻译过去是传送的意思,就像是哆啦 A 梦中的 「任意门」 ,任意门的作用就是能够将人霎时传送到另一个中央。
举例:
咱们心愿 Dialog
渲染的 dom 和顶层组件是兄弟节点关系, 在 App.vue
文件中定义一个供挂载的元素:
<template> <router-view /> <div id="model"></div> <!-- 挂载点 --></template>
定义一个 Dialog 组件 Dialog.vue
, 注意 to
属性, 与下面的 id选择器
统一:
<template> <teleport to="#model"> <!-- 挂载内容 --> <div>title</div> <div>I'am a Dialog.</div> </teleport></template>
DOM 渲染成果如下:
咱们应用 teleport 组件
,通过 to 属性
,指定该组件渲染的地位在 App.vue
中,然而 Dialog 又是齐全由 Dialog.vue
组件管制。
五、Suspense
Suspense组件用于在期待某个异步组件解析时显示后备内容。
在 Vue2.x 中常常遇到这样的场景:
<template><div> <div v-if="!loading"> <!-- 异步渲染 --> ... </div> <div v-if="loading"> 加载中... </div></div></template>
在异步数据没加载前,个别咱们都会提供一个加载中( loading )的动画,当数据返回时配合 v-if
来控制数据显示。
当初 Vue3.x 新出的内置组件 Suspense
, 它提供两个template slot
, 刚开始会渲染一个 fallback
状态下的内容, 直到达到某个条件后才会渲染 default
状态的正式内容, 通过应用 Suspense
组件进行展现异步渲染就更加的简略。
<Suspense> <template #default> <!-- 异步渲染 --> ... </template> <template #fallback> <div> Loading... </div> </template></Suspense>
Suspense
组件 只是一个带插槽的组件,只是它的插槽指定了 default
和 fallback
两种状态。
六、碎片化节点 Fragment
在 Vue2.x 中, template
中只容许有一个根节点:
<template> <div> <span></span> <span></span> </div></template>
然而在 Vue3.x 中,能够有多个根元素,也能够有把文本作为根元素
<template> <span></span> <span></span></template>
七、插槽与作用域插槽
Vue之slot插槽和作用域插槽
1. 插槽
父组件:
<template> <Child> <!-- Vue2.x写法 <div slot="parent"> <div>父组件</div> </div> --> <template v-slot:parent> <div>父组件</div> </template> </Child></template>
子组件(Child.vue
):
<template> <slot name='parent'>子组件</slot></template>
2. 作用域插槽
父组件:
<template> <Child> <!-- <template slot="content" slot-scope="scoped"> --> <template v-slot:content="scoped"> <div>{{scoped.myName}}</div> </template> </Child></template>
子组件:
<template> <slot name="content" :myName="myName"></slot></template><script>import { ref } from 'vue'export default { setup () { let myName = ref("猫老板的豆") return { myName } },}</script>