Vue3.0 composition-api 详解

760 阅读6分钟

创建项目之前的准备

在创建项目之前需要确认是否安装了vue脚手架

安装脚手架

npm install -g @vue/cli

上面安装的是Vue CLI3版本

创建项目

vue create 项目名称

创建步骤

node版本在10.0以上

1.png

2.png

3.png

4.png

5.png

6.png

7.png

8.png

9.png

10.png

11.png

值得注意的Vue3 新特性

Composition API(组合 API)

composition-api解决了什么问题

使用传统的option配置方法写组件的时候问题,随着业务复杂度越来越高,代码量会不断的加大;由于相关业务的代码需要遵循option的配置写到特定的区域,导致后续维护非常的复杂,同时代码可复用性不高,而composition-api就是为了解决这个问题而生的。

Composition API在使用时需要先引入

import { reactive, toRefs, computed } from "vue";

1.setup是composition-api的入口函数
2.在声明周期beforeCreate事件之前被调用;
3.可以返回一个对象,这个对象的属性被合并到渲染上下文,并可以在模板中直接使用;
接收props对象作为第一个参数,接收来的props对象,可以通过watchEffect监视其变化。
接受context对象作为第二个参数,这个对象包含attrs,slots,emit三个属性。 4.context替代了this

由于setup被执行的时候,组件示例还没有创建完成,因此在setup里面没有this。这意味着除了props,你无法使用组件内定义的其他任何属性,包括本地状态、计算属性和方法等。

setup(props, ctx) {
    ctx.emit('传参')
}
当然,还能把emit直接解构出来使用更方便:
setup(props, { emit }) {
    emit('ooxx')
}

语法糖介绍

compositon-api提供了一下几个函数

  • ref
  • reactive
  • toRefs
  • computed
  • watch
  • watchEffect
  • 生命周期的hooks

ref

接受一个参数值并返回一个响应式且可改变的ref对象

  1. ref对象拥有一个指向内部值的单一属性 .value
  2. 当ref在模板中使用的时候,他会自动解套,无需在模板内额外书写 .value
import { reactive, toRefs, computed } from "vue";
export default {
  const count = ref(0)
  const countAdd = ()=>{
    count.value ++
    }
    return {
     count
     countAdd
    
    }
    
}

reactive

接收一个普通对象然后返回该普通队形的响应式代理,等同于2.x的Vue.observable() 1. 响应式转换是“深层的”:会影响对象内部所有嵌套的属性

import { reactive, toRefs, computed } from "vue";
export default {
const state = reactive({ count: 0,
double: computed(()=>state.count * 2)
})
    return {
    ... toRefs(state)
    double
    }
    
}
import { reactive, toRefs, computed } from "vue";
export default {
const state = reactive({ count: 0,
double: computed(()=>state.count * 2)
})
    return  toRefs(state)
    
}

reactiveref之间做选择

二者都用于将普通数据转为响应式数据

1.当把 ref() 创建出来的响应式数据对象,挂载到 reactive() 上时,会自动把响应式数据对象展开为原始的值,不需通过 .value 就可以直接被访问
2.ref加入到reactive中,调用时使用state.ref形式,新传入的ref会覆盖掉原来的ref值,但是ref和state是 相互独立的,state.ref值得改变不会影响到ref值,因此新传入的ref覆盖旧的ref,覆盖的只是指向数据的指针。
3.ref用于简单的数据类型,reactive用于复杂的数据类型
4.ref在return时不需要解构,state在return时需要解构
5.ref在外界调用直接调用响应数据变量,reactive调用的时普通数据变
6.关联性比较强的数据选择reactive,代码显得比较完整和整洁

toRefs

1.toRefs API提供了一个方法可以把reactive的值处理为ref
2.将reactive创建出来的响应式数据对象转为普通对象,只不过这个普通对象的每一个属性节点都是ref类型的响应式数据,此处用到了扩展运算符,会将state转为普通对象,因此需要用toRefs将其转为响应式数据。

import { reactive, toRefs, computed } from "vue";
export default {
    setup() {
        const state = reactive({ count: 0 })
        const increment = () => {    // 定义页面上可用的事件处理函数
            state.count++
        }
        // 这个返回对象中可以包含响应式的数据,也可以包含事件处理函数
        return {
          ...toRefs(state),
          increment
        }
    }
}

Computed()

  • 创建计算属性,返回值是一个ref实例,因此也有.value属性
  • 使用前需导入
  • 创建只读的计算属性:
export default {
    setup(props, context) {
        const tempData = ref(0);
        const computeData = computed(() => { 
            tempData.value += 1;    //返回的computeData也是ref类型
        })
        console.log(tempData.value)    //0
        console.log(computeData.value)    //1
    }
}
  • 创建可读可写的计算属性:
export default {
    setup(props, context) {
        const tempData = ref(0);
        const computeData = computed({
            get: () => {
                tempData.value += 1;
            },
            set: (val) => {
                tempData.value = val - 1;
            }
        })
        console.log(tempData.value)    //0
        console.log(computeData.value)    //1
        computeData.value = 9;
        console.log(tempData.value)    //8
    }
}

watch(arg1,arg2,arg3)

  • 监视数据变化,创建时会自动调用一次,可以通过watch的第三个参数{ lazy: true }关闭。第三个参数是用来配置监听参数,例如 immediately 等
  • 需要先导入
  • 单一数据监听:
export default {
    setup(prop, context) {
        //ref类型
        const tempData = ref(0)
        watch(
            tempData,
            (newVal, oldVal) => {
                console.log(newVal);
            },
            {
                lazy: true
            }
        )
 
        //reactive类型
        const tempData1 =reactive({count: 0})
        watch(
            ()=>tempData1.count,
            (newVal, oldVal) => {
                console.log(newVal);
            },
            {
                lazy: true
            }
        )
    }
}

  • 监听多个数据变化
export default {
    setup() {
        //ref类型
        const count = ref(0)
        const name = ref('jack')
        watch(
            [count, name],
            ([newcount, newname], [oldcount, oldname]) => {
                console.log(newcount, oldcount);
            },
            {
                lazy: true
            }
        )
        setTimeout(() => {
            count.value++
            name.value = 'lc'
        }, 1000)
 
        //reactive类型
        const state = reactive({
            count: 0,
            name : 'jack'
        })
        watch(
            [()=>state.count, ()=>state.name],    //监听的数据组
            ([newcount, newname], [oldcount, oldname]) => {    //回调函数
                console.log(newVal);
            },
            {
                lazy: true
            }
        )
        setTimeout(() => {
            state.count++
            state.name = 'lc'
        }, 1000)
    }
}
  • 清除监听
// 调用watch的返回值,执行一下就清除了。
const stop = watch(...)
stop()     //清除监听

  • 清除异步无效任务

        这个清除函数会在如下情况下被调用:

watch 被重复执行了

watch 被强制 stop 了

export default {
    setup() {
        const count = ref(0)
        //声明监听
        const stop = watch(
            count,
            (newVal, oldVal, onClear) => {
                const timeId = asyncPrint(newVal)    //调用异步方法
                onClear(() => {    //重复监听会删除之前的操作
                   clearTimeout(timeId)
                })
            },
            {lazy: true}
        )
        //异步方法
        const asyncPrint = (val) => {
            return setTimeout(() => {
                console.log(val)
            },3000)
        }
    }
}

watchEffect

watchEffect在一开始的时候就会收集依赖,相比watch可控制初始化时是否立即监听,watchEffect在一开始的时候必须执行一遍,用于收集依赖,也因此watchEffect不会像watch那样会有第一个参数去指定依赖,但是在watchEffect中必须有需要监听的响应式数据才能触发监听,如果在外边改变ref1,而在watchEffect中console.log(ref2)是没效果的,但是watch是有效果的

import {  watchEffect  } from 'vue'
 
const state1 = reactive({name: 'jack'})
setTimeout(() => {
    state1.name = 'lc'
},8000)
watchEffect(() => {
    console.log(state1.name)      //无法监听到
})
  • vue3.0中,如果watch的是一个数组对象,那么push方法不会触发监听,必须重新给数组赋值才会触发。

provide

  • 可以实现嵌套组件之间的数据传递, 父级组件中使用 provide() 函数向子组件传递数据
  • 不限层级,只要是嵌套的子级组件
  • 在setup() 中使用
  • 需要先引入
  • 共享普通数据
export default {
    setup() {
        provide('globalColor','red')
    }
}
  • 共享ref响应式数据
export default {
    setup() {
        const color = ref('red')
        provide('globalColor', color)
    }
}

inject

  • 可以实现嵌套组件之间的数据传递, 子级组件中使用 inject() 函数接收父组件的数据
  • 不限层级,只要是其父级组件,
  • 在setup() 中使用
  • 需要先引入
  • 接收父级的传参
export default {
    setup() {
        inject('globalColor')
    }
}

template—refs

  • 通过ref() 实现对页面DOM元素和组件的引用和操作
  • 父组件可以操作子组件,能拿到子组件的数据
  • 实例
<template>
    <h3 ref='h3ref'><h3/>    //绑定return的响应对象数据
    <com-child ref='comChild'/>
<template/>
<script>
export default {
    setup() {
        const h3ref = ref(null)    //定义ref,值为null
        const comChild = ref(null)
        onMounted(()=>{
            // h3ref.value相当于一个原生DOM
            h3ref.value.style.color = 'red'
        })
 
        //能拿到子组件的DOM
        const showNumber = () => {
            console.log(comChild.value.count)
        }
        return {
            h3ref,
            comChild,
            showNumber
        }
    }
}
 
</script>

生命周期

生命周期写在setup()中

需提前import引入

import { onBeforeMount, onMounted, reactive, watchEffect } from 'vue'

新旧生命周期对比:

    beforeCreate       --- setup()

    created            --- setup()    

    beforeMount        --- onBeforeMount(()=>{…})

    mounted            --- onMounted(()=>{…})

    beforeUpdate       --- onBeforeUpdate(()=>{…})

    updated            --- onUpdated(()=>{…})

    beforeDestroy      --- onBeforeUnmount(()=>{…})

    destroyed          --- onUnmounted(()=>{…})

    errorCaptured      --- onErrorCaptured(()=>{…})