Vue3.x 官网
新版vue3的setup语法糖更为简洁,详见 Vue3 script setup 语法糖,超爽体验

一、Composition API 简介

Vue2 时的形式在代码很少的时候,逻辑构造还是蛮清晰的,然而随着组件性能越来越多,代码量越来越大,整体内容全副放在其中必定会显得臃肿。因为每个功能模块的代码会散落散布在各个地位,让整个我的项目的内容难以浏览和保护。如下图:

而到了 Vue3,它会依据逻辑性能来进行组织,把同一个性能的不同代码都放在一起,或者把它们独自拿进去放在一个函数中,所以 Composition API 又被称为基于函数组合的API


1. setup 函数

setup 函数是 Vue3 中新增的函数,它是咱们在编写组件时,应用 Composition API 的入口。
同时它也是 Vue3 中新增的一个生命周期函数,会在 beforeCreate 之前调用。因为此时组件的 datamethods 还没有初始化,因而在 setup 中是不能应用 this。所以 Vue 为了防止咱们谬误的应用,它间接将 setup 函数中的 this 批改成了undefined。并且,咱们只能同步应用setup函数,不能用async将其设为异步。

setup 函数接管两个参数 propscontext, 语法为: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 之前调用,因为此时组件的 datamethods 还没有初始化,因而在 setup 中是不能应用 this
  • 所以 Vue 为了防止咱们谬误的应用,它间接将 setup 函数中的 this 批改成了undefined
  • setup函数,只能是同步的不能是异步
Vue2.x (Option API)Vue3.x (Composition API)
beforeCreatesetup
createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
activatedonActivated
deactivatedonDeactivated
errorCapturedonErrorCaptured
- -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 组件 只是一个带插槽的组件,只是它的插槽指定了 defaultfallback 两种状态。


六、碎片化节点 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>