1

🚀快速掌握vue3新语法(一) - 初识(setup/reactive/ref)

 2 years ago
source link: https://segmentfault.com/a/1190000040755977
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

"组合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".

    同样是定义响应数据, 和"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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK