为什么会出现,解决了啥痛点?

​ 新特性的出现肯定是为了解决和优化以往设计中的某个痛点,组合式API也一样。

​ 我们都知道,代码的可重用性会增强我们应用的可维护性和灵活性,所以几乎每个前端语言框架都有组件化开发的思想,vue也不例外。

​ 依据vue2的设计思想,使用 (datacomputedmethodswatch) 组件选项来组织逻辑通常都很有效,但当一个组件开始变得更大时,逻辑关注点的列表也会增长,这会导致组件难以阅读和理解,就像下面这样

  • 这种碎片化使得理解和维护复杂组件变得困难,选项的分离掩盖了潜在的逻辑问题,在处理单个逻辑关注点的时候得不断跳转相关代码选项块
  • 为了能够将同一个逻辑关注点相关代码收集在一起,组合式API应运而生。

怎么使用组合式API

​ 知道了它是怎么来的,我们还得学会怎么去使用

​ vue3是通过setup组件选项去使用组合式API的

setup调用时间

  • setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。
  • 执行setup时,组件实例尚未被创建,所以this在setup中无效,因为它不会找到组件实例。
  • setup位于生命周期图顶部

参数

  • setup 选项是一个接收 propscontext 的函数,setup 返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
setup(props,context) {
    return {} // 这里返回的任何内容都可以用于组件的其余部分
}
props
  • 需要注意的是,props是响应式的,即传入新的prop时,它将被更新,所以不能对props使用ES6结构,这样会消除props的响应性。需要解构可以在 setup 函数中使用 toRefs 函数来完成

    import { toRefs } from 'vue'
    setup(props) {
      const { title } = toRefs(props)
      console.log(title.value)
    }
  • 如果 title 是可选的 prop,则传入的 props 中可能没有 title。在这种情况下,toRefs 将不会为 title 创建一个 ref 。你需要使用 toRef 替代它:

    import { toRef } from 'vue'
    setup(props) {
      const title = toRef(props, 'title')
      console.log(title.value)
    }
context
  • context 是一个普通的 JavaScript 对象,它不是响应式的,可以安全地对 context 使用 ES6 解构。

    export default {
      setup(props, { attrs, slots, emit, expose }) {
        ...
      }
    }
  • attrsslots 是有状态的对象,它们总是会随组件本身的更新而更新。注意,是组件本身!这意味着我们应该避免对attrs和slots进行解构,并始终以attrs.x的形式去使用property,但由于property是非响应式的,如果想通过attrs的更改去做事情,我们应该要在onBeforeUpdate 生命周期钩子执行操作

  • 我们需要知道attrs,slots,emit,expose都是些啥

attrs
<Child @testAttrsItem="testAttrsItem" />
// 其实就是父组件传递的函数集合
slot
<TestSlots >
     <template v-slot:testSlot1>
       <div>
         我是插槽1
       </div>
     </template>
      <template v-slot:testSlot2>
       <div>
         我是插槽2
       </div>
     </template>
 </TestSlots>
 // 其实就是插槽的集合
emit
// 子组件
export default defineComponent({
  setup(props, { emit }) {
    const emitFun = () => {
      console.log("我是抛出去的函数");
    };
    const clickEmit = () => {
      emit("emitFun", "param1");
    };
    return {
      clickEmit
    }
  },
});
</script>
<template>
  <div>
    <button @click="clickEmit">点击抛出</button>
  </div>
</template>
// 父组件
const monitorChildEmitFun=(param: string)=>{
  console.log(param)
}

 <TestEmit @emitFun="monitorChildEmitFun" />
expose
  • expose函数接受一个对象参数,其中定义的property将可以被外部组件实例访问,未在其中定义变量将在父组件中访问不到
// 子组件
setup(props, { expose }) {
    const count = ref(0)
    const increment = () => ++count.value

    expose({
      increment
    })

    return () => h('div', count.value)
  }
// 父组件
const child=ref(null)
const handleClick=()=>{
  console.log(child.value)
  child.value.increment()
}


 <button @click="handleClick">点我</button>
 <TestExpose ref="child" />

模板

  • setup返回的对象中的property以及传递给setup的props属性可以在模板中访问的到
  • 但需要注意的是,从 setup 返回的 refs 在模板中访问时是被自动浅解包的,因此不应在模板中使用 .value

使用渲染函数

  • setup可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态

    setup(props, context) {
       const readersNumber = ref(0);
       const book = reactive({ title: "Vue 3 Guide" });
       // 请注意这里我们需要显式使用 ref 的 value
       return () => h("div", [readersNumber.value, book.title]);
     },
     // 父组件直接照常引入该组件进行渲染
     // h本质上是createElement函数的别名
  • 返回一个渲染函数将阻止我们返回任何其它的东西,如果我们想要将这个组件的方法通过模板ref暴露给父组件就不能了,我们可以通过expose方法解决这个问题