9

"组合API"是vue3扩充的全新语法, 前面基础课中讲解的语法叫"选项API". 2种API在vue3中都支持.

解决了什么?

"组合API"可以更进一步拆分"选项API"中的JS逻辑. 可以把某一逻辑的"data/computed/watch/methods/声明周期钩子"单独封装到一个函数(也可单独一个文件)中. 一般给拆分后的函数命名"useXxx".
image.png

拆分实际需求

分析下图的购物车模块, 我计划把JS部分拆分成2部分(函数): 一个用来获取购物车商品数据和总价, 一个用来计算获取优惠劵并计算优惠后的总价. 分表是函数: "useGetCart"和"useCoupon".
1.gif

setup结构

拆机成2个函数你一定很疑惑, 老的"选项API"不是有"methods"字段也可以封装函数, 暂不解释, 先看下"组合API"的代码格式, 一个新的字段"setup", 他是"组合API"的标志属性, 是个函数, 其返回值可以被模板识别并渲染, 类似"data".

特别注意2个函数的返回值, 他们返回了数据(类似data)和函数(类似methods). 实际函数内部包含独立的"watch/computed/生命周期钩子", 想当于把1个vue组件的"data"和"methods"的内容给分组了, 这也就是为什么叫"组合API".

<template>
  <article v-if="cart.length > 0">
    <ul>
      <li v-for="item in cart" :key="item.name">
        {{ item.name }} : {{ item.price }}元
        <input v-model="item.count" type="number" style="width: 48px" />
      </li>
    </ul>
    <h5 style="margin-left: 100px">原价:{{ totalPrice }}元</h5>
    <h5 style="margin-left: 100px; color: #f10">总价:{{ realTotalPrice }}元</h5>
    <button @click="createOrder">支付</button>
  </article>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
  name: 'Cart',

    setup(){
    // 购物车详情和总价
      const [cart,totalPrice,createOrder] = useGetCart();
    
    // 优惠后的价格
    const realTotalPrice = useCoupon();
    
    // 返回模板识别的数据,和data定义的数据一样
    return {cart,totalPrice,realTotalPrice,createOrder};
  }
});
</script>

"useXxx"函数内部实现

这个"use"作为函数名前缀是一个命名习惯, 实际起名并没有限制. 每一个函数中的"watch/computed/生命周期钩子", 他们都以函数的形式出现.

import {computed} from 'vue';
/**
 * 获取购物车详情
 */
function useGetCart() {
  //  购物车详情(reactive)
  const cart = reactive<{ name: string; count: number; price: number }[]>([]);

  // 模拟异步请求
  setTimeout(() => {
    cart.push(
      { name: "苹果", count: 10, price: 10 },
      { name: "香蕉", count: 20, price: 20 }
    );
  }, 1000);

  // 总价格(computed)
  const totalPrice = computed(() => {
    return cart.reduce((total, item) => item.count * item.price + total, 0);
  });

  return [cart, totalPrice] as const;
}

这出现了一个新的函数"ref", 他是用来"定义响应数据"的, 接下来我们就讲"ref"是什么.

注意

  1. "as const"表示断言数组类型为元祖, 如果大家忘记了ts部分的内容, 在学习后面知识之前, 可以温习下ts.
  2. 这里只有生命周期的钩子名字前面多了"on"前缀, 比如mounted => onMounted

    定义响应数据(reactive/ref)

    "响应数据"就是值变化可以驱动dom变化的数据, 我们之前在"data"中定义的数据就是响应数据. 但是在"setup"中如果我们要定义数据, 这里并没有"data"函数, 取而代之的是"reactive/ref"函数:

    reactive

    定义响应数据, 输入只能是对象类型, 返回输入对象的响应版本.

    <template>
     <h1>{{count}}</h1>
    </template>
    
    <script lang="ts">
    import { defineComponent } from "vue";
    export default defineComponent({
     setup(){
     return reactive({count:99});
      }
    });
    </script>

    image.png
    实际这个例子中可以不用"reactive", 结果一样, 但是如果"count"数据被修改, 那么界面就不会自动变化了,一直显示"99".

    ref

    同样是定义响应数据, 和"reactive"的区别是返回值响应数据的格式不同, ref返回的数据需要用".value"访问.

    const n = ref(110);
    console.log(n);

    image.png
    可以看到返回值在value字段中, 这么做是因为js中对数据变化的监视只支持"引用数据类型", 对于string和number类型如果需要监视需要构造一个对象, 所以这里ref内部就需要构造一个{value:110}的变量.

    reactive和ref的选择

    重要: 如果要监视的数据是引用型数据(object)那么就是用reactive, 如果是(number/boolean/string)等原始数据类型就用ref.

    封装函数(useXxx)

    现在我们回头看2个函数的实现.

    useGetCart

    返回购物车中商品信息和总价格, 总价格使用了"计算属性"函数(computed), 同时我们封装了"生成订单"函数, 因为其需要购物车商品信息做参数 ,所以把2者做为一组.

    import {computed} from 'vue';
    /**
     * 获取购物车详情
     */
    function useGetCart() {
      //  购物车详情(reactive)
      const cart = reactive<{ name: string; count: number; price: number }[]>([]);
    
      // 模拟异步请求
      setTimeout(() => {
     cart.push(
       { name: "苹果", count: 10, price: 10 },
       { name: "香蕉", count: 20, price: 20 }
     );
      }, 1000);
    
      // 总价格(computed)
      const totalPrice = computed(() => {
     return cart.reduce((total, item) => item.count * item.price + total, 0);
      });
      
      
      // 生成订单(methods)
      function createOrder(){
       // 模拟生成订单
     setTimeout(()=>{
         console.log(`成功购买${cart.length}件商品`);
     },1000)
      }
    
      return [cart, totalPrice,createOrder] as const;
    }

    useCoupon

    获取优惠金额, 并返回计算优惠后金额. 用watch来监视"总价格", 当变化的时候重新计算"优惠后总价", 使用"onMounted"控制数据请求触发时机(本例并无实际意义,此处仅为了展示"onMounted"用法).

    import {watch,onMounted} from 'vue';
    /**
     * 获取优惠劵
     */
    function useCoupon(totalPrice: Ref<number>) {
      const realTotalPrice = ref(0);
      // 此处实际可以不用onMouted,
      // 仅仅为了演示用法
      onMounted(() => {
     // 模拟异步请求
     setTimeout(() => {
       const coupon = 9;
       watch(
         totalPrice,
         (value) => {
           realTotalPrice.value = value - coupon;
         },
         { immediate: true }
       );
     }, 1000);
      });
    
      return realTotalPrice;
    }

    "watch"作为函数, 其第二个参数是个对象, 有字段"immediate"表示初始化即运行回调, "deep"表示深度监视数据(object).

完整源码

https://github.com/any86/vue3-start/blob/master/src/views/Setup.vue

微信群

感谢大家的阅读, 如有疑问可以加我微信, 我拉你进入微信群(由于腾讯对微信群的100人限制, 超过100人后必须由群成员拉入)

未完待续

最新动态请关注我的语雀

www.yuque.com_russell-qqjgt_rfbdgd(iPhone X).png


铁皮饭盒
5k 声望1.2k 粉丝

喜欢写程序: [链接]