Vue之Mixin【一种代码重用机制】

Vue
145
0
0
2024-07-03

一、引言

Mixin 的概念
在编程中,Mixin 是一种代码复用的技术,它允许你将多个类中的代码提取出来,形成一个独立的模块,并在需要的时候将其应用到其他类中。Mixin 可以用来实现代码的重用、扩展和定制。
Mixin 的主要作用
  1. 代码重用:通过将共同的代码提取到一个 Mixin 中,可以避免在多个类中重复编写相同的代码,从而提高代码的可维护性和可读性。
  2. 功能扩展:使用 Mixin 可以在不修改原始类的情况下,向类中添加新的功能或行为。这对于已经在使用的类特别有用,因为你可以通过添加 Mixin 来扩展其功能,而无需修改现有代码。
  3. 灵活定制:Mixin 允许你根据具体需求组合不同的功能,从而创建出具有特定行为的类。你可以选择应用一个或多个 Mixin,以及自定义 Mixin 的实现,以满足项目的特定要求。
  4. 更好的代码组织:Mixin 有助于将相关的功能组织到一个单独的模块中,使代码更易于理解和维护。

二、Vue 中的 Mixin

解释 Mixin 在 Vue 中的工作原理

在 Vue 中,Mixin 是一种用于代码复用的特性。它允许你将一个组件中的部分功能提取出来,并在其他组件中重复使用。

Mixin 的工作原理是通过将 Mixin 的内容合并到组件的选项中。当一个组件使用了 Mixin,它会将 Mixin 中的属性、方法和生命周期钩子函数合并到自己的选项中。这样,组件就可以访问和使用 Mixin 中定义的属性和方法。

如何在脚手架环境中创建和使用 Mixin

首先,创建一个名为mixinJs的文件,以便存放mixin。在该文件中,定义一个名为myMixin的mixin对象:

mixin.js file

export const myMixin = {
  data() {
    return {
      mixinData: 'Hello, I am data from Mixin'
    }
  },
  methods: {
    mixinMethod() {
      alert("This method is from Mixin");
    }
  },
  mounted() {
    console.log(this.mixinData)
  }
}

在这个mixin中定义了:

  • 一个数据 mixinData
  • 一个方法 mixinMethod
  • 一个生命周期钩子函数 mounted

然后,在需要使用的组件中导入并使用这个mixin:

component file

import { myMixin } from './mixin.js'

export default {
  name: 'YourComponent',
  mixins: [myMixin],
  methods: {
    yourComponentMethod () {
      this.mixinMethod()
    }
  }
}

在组件中,我们使用mixins选项来引入myMixin。现在我们可以访问在mixin中定义的所有数据和方法,并在组件的生命周期钩子函数中使用它们。

yourComponentMethod方法中,我调用了mixin中定义的mixinMethod方法,此方法将打印出一个警告信息。

实际上,也可以在组件中定义与mixin中相同的方法或生命周期钩子,Vue将优先使用组件内部的定义。

三、使用 Mixin 的注意事项

  • 命名冲突:当应用多个 Mixin 到同一个类时,可能会出现命名冲突。为了避免这种情况,应该仔细设计 Mixin 中的属性和方法,并确保它们具有唯一的命名。
  • 继承顺序:在应用多个 Mixin 时,继承顺序可能会影响代码的执行结果。如果两个 Mixin 中定义了同名的方法,那么子类将继承最近的方法。因此,在设计 Mixin 时,需要考虑它们的继承顺序。
  • 可读性:由于 Mixin 可以在运行时动态地应用到类上,所以代码的可读性可能会受到影响。为了提高可读性,可以使用注释来说明 Mixin 的作用和应用方式。
  • 性能:在某些情况下,使用 Mixin 可能会导致性能下降,特别是当应用大量的 Mixin 或在运行时动态地应用 Mixin 时。如果对性能有严格要求,可以考虑其他实现方式。
  • 可维护性:随着项目的发展,可能会添加更多的 Mixin,这可能会增加代码的复杂性。为了保持代码的可维护性,应该定期审查和整理 Mixin,并确保它们的功能是必要的。

常见的 Mixin 用例

表单验证

  1. 表单验证 Mixin:这个 Mixin 可以用于验证表单中的输入数据,确保其符合特定的规则

创建一个表单验证 Mixin

此处编写的就是一个 Vue 组件实例的配置项,通过一定语法,可以直接混入到组件内部 data methods computed 生命周期函数

注意点:

  1. 如果此处和组件内,提供了同名的 datamethods,则组件内优先级更高
  2. 如果编写了生命周期函数,则mixins中的生命周期函数 和 页面的生命周期函数,会用数组管理,统一执行
export default {
  methods: {
    loginConfirm () {
      // 判断用户是否登录
      if (!this.$store.getters.token) {
        this.$dialog.confirm({
          title: '温馨提示',
          message: '此时需要先登录才能继续操作哦',
          confirmButtonText: '去登录',
          cancelButtonText: '再逛逛'
        })
          .then(() => {
            this.$router.replace({
              path: '/login',
              query: {
                backUrl: this.$route.fullPath
              }
            })
          })
          .catch(() => {})
        return true
      }
      return false
    }
  }
}

此处是对于该 mixin 的使用 用以进行表单的校验

<template>
  <div class="pay">
    <van-nav-bar fixed title="订单结算台" left-arrow @click-left="$router.go(-1)" />

    <!-- 地址相关 -->
    <div class="address">

      <div class="left-icon">
        <van-icon name="logistics" />
      </div>

      <div class="info" v-if="selectedAddress.address_id">
        <div class="info-content">
          <span class="name">{{ selectedAddress.name }}</span>
          <span class="mobile">{{ selectedAddress.phone }}</span>
        </div>
        <div class="info-address">
          {{ longAddressList }}
        </div>
      </div>

      <div class="info" v-else>
        请选择配送地址
      </div>

      <div class="right-icon">
        <van-icon name="arrow" />
      </div>
    </div>

    <!-- 订单明细 -->
    <div class="pay-list" v-if="order.goodsList">
      <div class="list">
        <div class="goods-item" v-for="item in order.goodsList" :key="item.goods_id">
            <div class="left">
              <img :src="item.goods_image" alt="" />
            </div>
            <div class="right">
              <p class="tit text-ellipsis-2">
                {{ item.goods_name }}
              </p>
              <p class="info">
                <span class="count">x{{ item.total_num }}</span>
                <span class="price">¥{{ item.total_pay_price }}</span>
              </p>
            </div>
        </div>
      </div>

      <div class="flow-num-box">
        <span>共 {{ order.orderTotalNum }} 件商品,合计:</span>
        <span class="money">¥{{ order.orderTotalPrice }}</span>
      </div>

      <div class="pay-detail">
        <div class="pay-cell">
          <span>订单总金额:</span>
          <span class="red">¥{{ order.orderTotalPrice }}</span>
        </div>

        <div class="pay-cell">
          <span>优惠券:</span>
          <span>无优惠券可用</span>
        </div>

        <div class="pay-cell">
          <span>配送费用:</span>
          <span v-if="!selectedAddress">请先选择配送地址</span>
          <span v-else class="red">+¥0.00</span>
        </div>
      </div>

      <!-- 支付方式 -->
      <div class="pay-way">
        <span class="tit">支付方式</span>
        <div class="pay-cell">
          <span><van-icon name="balance-o" />余额支付(可用 ¥ {{ personal.balance }} 元)</span>
          <!-- <span>请先选择配送地址</span> -->
          <span class="red"><van-icon name="passed" /></span>
        </div>
      </div>

      <!-- 买家留言 -->
      <div class="buytips">
        <textarea v-model="remark" placeholder="选填:买家留言(50字内)" name="" id="" cols="30" rows="10"></textarea>
      </div>
    </div>

    <!-- 底部提交 -->
    <div class="footer-fixed">
      <div class="left">实付款:<span>¥{{ order.orderTotalPrice }}</span></div>
      <div class="tipsbtn" @click="submitOrder">提交订单</div>
    </div>
  </div>
</template>

<script>
import { getAddressList } from '@/api/address'
import { checkOrder, submitOrder } from '@/api/order'
import loginConfirm from '@/mixins/loginConfirm'
export default {
  name: 'PayIndex',
  mixins: [loginConfirm],
  data () {
    return {
      addresslist: [],
      order: {},
      personal: {},
      remark: '' // 留言
    }
  },
  created () {
    this.getAddressList()
    this.getOrderList()
  },
  computed: {
    selectedAddress () {
      return this.addresslist[0] || {}
    },
    longAddressList () {
      const region = this.selectedAddress.region
      return region.province + region.city + region.regin
    },
    mode () {
      return this.$route.query.mode
    },
    cartIds () {
      return this.$route.query.cartIds
    },
    goodsId () {
      return this.$route.query.goodsId
    },
    goodsSkuId () {
      return this.$route.query.goodsSkuId
    },
    goodsNum () {
      return this.$route.query.goodsId
    }
  },
  methods: {
    async getAddressList () {
      const { data: { list } } = await getAddressList()
      this.addresslist = list
    },
    async getOrderList () {
      // 购物车结算
      if (this.mode === 'cart') {
        const { data: { order, personal } } = await checkOrder(this.mode, {
          cartIds: this.cartIds
        })
        this.order = order
        this.personal = personal
      }
      // 立刻购买结算
      if (this.mode === 'buyNow') {
        const { data: { order, personal } } = await checkOrder(this.mode, {
          goodsId: this.goodsId,
          goodsSkuId: this.goodsSkuId,
          goodsNum: this.goodsNum
        })
        this.order = order
        this.personal = personal
      }
    },
    async submitOrder () {
      if (this.mode === 'cart') {
        await submitOrder(this.mode, {
          cartIds: this.cartIds,
          remark: this.remark
        })
      }
      if (this.mode === 'buyNow') {
        await submitOrder(this.mode, {
          goodsId: this.goodsId,
          goodsSkuId: this.goodsSkuId,
          goodsNum: this.goodsNum,
          remark: this.remark
        })
      }
      this.$toast.success('支付成功')
      this.$router.replace('/myorder')
    }
  }
}
</script>

<style lang="less" scoped>
.pay {
  padding-top: 46px;
  padding-bottom: 46px;
  ::v-deep {
    .van-nav-bar__arrow {
      color: #333;
    }
  }
}
.address {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 20px;
  font-size: 14px;
  color: #666;
  position: relative;
  background: url(@/assets/border-line.png) bottom repeat-x;
  background-size: 60px auto;
  .left-icon {
    margin-right: 20px;
  }
  .right-icon {
    position: absolute;
    right: 20px;
    top: 50%;
    transform: translateY(-7px);
  }
}
.goods-item {
  height: 100px;
  margin-bottom: 6px;
  padding: 10px;
  background-color: #fff;
  display: flex;
  .left {
    width: 100px;
    img {
      display: block;
      width: 80px;
      margin: 10px auto;
    }
  }
  .right {
    flex: 1;
    font-size: 14px;
    line-height: 1.3;
    padding: 10px;
    padding-right: 0px;
    display: flex;
    flex-direction: column;
    justify-content: space-evenly;
    color: #333;
    .info {
      margin-top: 5px;
      display: flex;
      justify-content: space-between;
      .price {
        color: #fa2209;
      }
    }
  }
}

.flow-num-box {
  display: flex;
  justify-content: flex-end;
  padding: 10px 10px;
  font-size: 14px;
  border-bottom: 1px solid #efefef;
  .money {
    color: #fa2209;
  }
}

.pay-cell {
  font-size: 14px;
  padding: 10px 12px;
  color: #333;
  display: flex;
  justify-content: space-between;
  .red {
    color: #fa2209;
  }
}
.pay-detail {
  border-bottom: 1px solid #efefef;
}

.pay-way {
  font-size: 14px;
  padding: 10px 12px;
  border-bottom: 1px solid #efefef;
  color: #333;
  .tit {
    line-height: 30px;
  }
  .pay-cell {
    padding: 10px 0;
  }
  .van-icon {
    font-size: 20px;
    margin-right: 5px;
  }
}

.buytips {
  display: block;
  textarea {
    display: block;
    width: 100%;
    border: none;
    font-size: 14px;
    padding: 12px;
    height: 100px;
  }
}

.footer-fixed {
  position: fixed;
  background-color: #fff;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 46px;
  line-height: 46px;
  border-top: 1px solid #efefef;
  font-size: 14px;
  display: flex;
  .left {
    flex: 1;
    padding-left: 12px;
    color: #666;
    span {
      color:#fa2209;
    }
  }
  .tipsbtn {
    width: 121px;
    background: linear-gradient(90deg,#f9211c,#ff6335);
    color: #fff;
    text-align: center;
    line-height: 46px;
    display: block;
    font-size: 14px;
  }
}
</style>
<template>
  <div class="prodetail" v-if="detail.goods_name">
    <van-nav-bar fixed title="商品详情页" left-arrow @click-left="$router.go(-1)" />

    <van-swipe :autoplay="3000" @change="onChange">
      <van-swipe-item
      v-for="(image, index) in images"
      :key="index"
      >
        <img :src="image.external_url" />
      </van-swipe-item>

      <template #indicator>
        <div class="custom-indicator">{{ current + 1 }} / {{ images.length }}</div>
      </template>
    </van-swipe>

    <!-- 商品说明 -->
    <div class="info">
      <div class="title">
        <div class="price">
          <span class="now">¥ {{ detail.goods_price_min }}</span>
          <span class="oldprice">¥ {{ detail.goods_price_max }}</span>
        </div>
        <div class="sellcount">已售 {{ detail.goods_sales }} 件</div>
      </div>
      <div class="msg text-ellipsis-2">
       {{ detail.goods_name }}
      </div>

      <div class="service">
        <div class="left-words">
          <span><van-icon name="passed" />七天无理由退货</span>
          <span><van-icon name="passed" />48小时发货</span>
        </div>
        <div class="right-icon">
          <van-icon name="arrow" />
        </div>
      </div>
    </div>

    <!-- 商品评价 -->
    <div class="comment" v-if="total > 0">
      <div class="comment-title">
        <div class="left">商品评价 ({{ total }}条)</div>
        <div class="right">查看更多 <van-icon name="arrow" /> </div>
      </div>
      <div class="comment-list">
        <div class="comment-item" v-for="item in commentList" :key="item.comment_id">
          <div class="top">
            <img :src="item.user.avatar_url || defaultImg" alt="">
            <div class="name">{{ item.user.nick_name }}</div>
            <van-rate :size="16" :value="item.score / 2" color="#ffd21e" void-icon="star" void-color="#eee"/>
          </div>
          <div class="content">
            {{ item.content }}
          </div>
          <div class="time">
            {{ item.create_item }}
          </div>
        </div>
      </div>
    </div>

    <!-- 商品描述 -->
    <div class="tips">商品描述</div>
    <div class="desc" v-html="detail.content"></div>

    <!-- 底部 -->
    <div class="footer">
      <div @click="$router.push('/')" class="icon-home">
        <van-icon name="wap-home-o" />
        <span>首页</span>
      </div>
      <div class="icon-cart">
        <span v-if="cartTotal > 0" class="num">{{ cartTotal }}</span>
        <van-icon @click="$router.push('/cart')" name="shopping-cart-o" />
        <span>购物车</span>
      </div>
      <div @click="addFn" class="btn-add">加入购物车</div>
      <div @click="buyFn" class="btn-buy">立刻购买</div>
    </div>

    <!-- 加入购物车弹层 -->
    <van-action-sheet v-model="showPannel" :title="mode === 'cart' ? '加入购物车' : '立刻购买'">
      <div class="product">
        <div class="product-title">
          <div class="left">
            <img :src="detail.goods_image" alt="">
          </div>
          <div class="right">
            <div class="price">
              <span>¥</span>
              <span class="nowprice">{{ detail.goods_price_min }}</span>
            </div>
            <div class="count">
              <span>库存</span>
              <span>{{ detail.stock_total }}</span>
            </div>
          </div>
        </div>
        <div class="num-box">
          <span>数量</span>
          <!-- v-model 本质上是 :value 和 @input 的简写-->
          <!-- 也就相当于是父传子 value 了-->
          <!-- 也就相当于是来了一个自定义事件 @input 了 -->
          <CountBox v-model="addCount"></CountBox>
        </div>
        <div class="showbtn" v-if="detail.stock_total > 0">
          <div class="btn" v-if="mode === 'cart'" @click="addCart">加入购物车</div>
          <div @click="goBuyNow" class="btn now" v-else>立刻购买</div>
        </div>
        <div class="btn-none" v-else>该商品已抢完</div>
      </div>
    </van-action-sheet>
  </div>
</template>

<script>
import { addCart } from '@/api/cart.js'
import defaultImg from '@/assets/default-avatar.png'
import { getProDetail, getProComments } from '@/api/product.js'
import CountBox from '@/components/CountBox.vue'
import loginConfirm from '@/mixins/loginConfirm'
export default {
  name: 'ProDetail',
  mixins: [loginConfirm],
  components: {
    CountBox
  },
  data () {
    return {
      images: [],
      current: 0,
      detail: {},
      total: 0,
      commentList: [],
      defaultImg,
      showPannel: false,
      mode: 'cart',
      addCount: 1, // 数字框绑定的数据
      cartTotal: 0
    }
  },
  computed: {
    goodsId () {
      return this.$route.params.id
    }
  },
  async created () {
    this.getDetail()
    this.getComments()
  },
  methods: {
    onChange (index) {
      this.current = index
    },
    async getDetail () {
      const { data: { detail } } = await getProDetail(this.goodsId)
      this.detail = detail
      this.images = detail.goods_images
    },
    async getComments () {
      const { data: { list, total } } = await getProComments(this.goodsId, 3)
      this.total = total
      this.commentList = list
    },
    addFn () {
      this.mode = 'cart'
      this.showPannel = true
    },
    buyFn () {
      this.mode = 'buyNow'
      this.showPannel = true
    },
    async addCart () {
      // 未登录处理:需要弹出一个确认框
      if (this.loginConfirm()) {
        return
      }
      const { data } = await addCart(this.goodsId, this.addCount, this.detail.skuList[0].goods_sku_id)
      this.cartTotal = data.cartTotal
      this.$toast('加入购物车成功')
      this.showPannel = false
    },
    goBuyNow () {
      // 未登录处理:需要弹出一个确认框
      if (this.loginConfirm()) {
        return
      }
      this.$router.push({
        path: '/pay',
        query: {
          mode: 'buyNow',
          goodsId: this.goodsId,
          goodsSkuId: this.detail.skuList[0].goods_sku_id,
          goodsNum: this.addCount
        }
      })
    }
  }
}
</script>

<style lang="less" scoped>
.prodetail {
  padding-top: 46px;
  ::v-deep .van-icon-arrow-left {
    color: #333;
  }
  img {
    display: block;
    width: 100%;
  }
  .custom-indicator {
    position: absolute;
    right: 10px;
    bottom: 10px;
    padding: 5px 10px;
    font-size: 12px;
    background: rgba(0, 0, 0, 0.1);
    border-radius: 15px;
  }
  .desc {
    width: 100%;
    overflow: scroll;
    ::v-deep img {
      display: block;
      width: 100%!important;
    }
  }
  .info {
    padding: 10px;
  }
  .title {
    display: flex;
    justify-content: space-between;
    .now {
      color: #fa2209;
      font-size: 20px;
    }
    .oldprice {
      color: #959595;
      font-size: 16px;
      text-decoration: line-through;
      margin-left: 5px;
    }
    .sellcount {
      color: #959595;
      font-size: 16px;
      position: relative;
      top: 4px;
    }
  }
  .msg {
    font-size: 16px;
    line-height: 24px;
    margin-top: 5px;
  }
  .service {
    display: flex;
    justify-content: space-between;
    line-height: 40px;
    margin-top: 10px;
    font-size: 16px;
    background-color: #fafafa;
    .left-words {
      span {
        margin-right: 10px;
      }
      .van-icon {
        margin-right: 4px;
        color: #fa2209;
      }
    }
  }

  .comment {
    padding: 10px;
  }
  .comment-title {
    display: flex;
    justify-content: space-between;
    .right {
      color: #959595;
    }
  }

  .comment-item {
    font-size: 16px;
    line-height: 30px;
    .top {
      height: 30px;
      display: flex;
      align-items: center;
      margin-top: 20px;
      img {
        width: 20px;
        height: 20px;
      }
      .name {
        margin: 0 10px;
      }
    }
    .time {
      color: #999;
    }
  }

  .footer {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 55px;
    background-color: #fff;
    border-top: 1px solid #ccc;
    display: flex;
    justify-content: space-evenly;
    align-items: center;
    .icon-home, .icon-cart {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      font-size: 14px;
      .van-icon {
        font-size: 24px;
      }
    }
    .btn-add,
    .btn-buy {
      height: 36px;
      line-height: 36px;
      width: 120px;
      border-radius: 18px;
      background-color: #ffa900;
      text-align: center;
      color: #fff;
      font-size: 14px;
    }
    .btn-buy {
      background-color: #fe5630;
    }
  }
}

.tips {
  padding: 10px;
}

// 弹层样式
.product {
  .product-title {
    display: flex;
    .left {
      img {
        width: 90px;
        height: 90px;
      }
      margin: 10px;
    }
    .right {
      flex: 1;
      padding: 10px;
      .price {
        font-size: 14px;
        color: #fe560a;
        .nowprice {
          font-size: 24px;
          margin: 0 5px;
        }
      }
    }
  }

  .num-box {
    display: flex;
    justify-content: space-between;
    padding: 10px;
    align-items: center;
  }

  .btn, .btn-none {
    height: 40px;
    line-height: 40px;
    margin: 20px;
    border-radius: 20px;
    text-align: center;
    color: rgb(255, 255, 255);
    background-color: rgb(255, 148, 2);
  }
  .btn.now {
    background-color: #fe5630;
  }
  .btn-none {
    background-color: #cccccc;
  }
}

.footer .icon-cart {
  position: relative;
  padding: 0 6px;
  .num {
    z-index: 999;
    position: absolute;
    top: -2px;
    right: 0;
    min-width: 16px;
    padding: 0 4px;
    color: #fff;
    text-align: center;
    background-color: #ee0a24;
    border-radius: 50%;
  }
}
</style>

五、高级 Mixin 技巧

首先在脚手架环境下创建一个新的Vue项目

  1. 参数传递

有一个基础的 Mixin,打印一条消息,并且希望可以动态地改变这条消息:

baseMixin.js

export default {
  created() {
    console.log(this.message)
    this.message = '胡昌城是最牛逼的人'
  }
}

然后我们在一个 Vue 组件中使用这个 Mixin,并传入参数:

HelloWorld.vue

import baseMixin from './baseMixin'

export default {
  mixins: [baseMixin],
  data() {
    return {
      message: 'Hello World from mixin!'
    }
  }
}

当组件创建的时候,mixin 的 created 生命周期钩子会被调用,会打印出 Hello World from mixin! 这条消息。

  1. 动态 Mixin

在组件选项或实例化 Vue 之后,动态添加 Mixin。

dynamicMixin.js

export default {
  created() {
    console.log('Dynamic mixin!')
  }
}

HelloWorld.vue

import dynamicMixin from './dynamicMixin';

export default {
  created() {
    if (this.needsMixin) {
      this.$options.mixins.push(dynamicMixin)
    }
  },
  data() {
    return {
      needsMixin: true
    }
  }
}

如果 needsMixin 为 true,那么动态的 mixin 会被添加到组件中。

三、扩展 Mixin

可以使用 Mixin 来扩展另一个 Mixin 的功能:

baseMixin.js\

export default {
  methods: {
    hello() {
      console.log('Hello from base mixin!')
    }
  }
}

extendedMixin.js

import baseMixin from './baseMixin';

export default {
  mixins: [baseMixin],
  methods: {
    hello() {
      this.$super.hello()
      console.log('Hello from extended mixin!')
    }
  },
  created() {
    this.hello()
  }
}

extendedMixin 扩展了 baseMixin 的 hello 方法。 这里使用了一个名为 $super 的特殊对象来调用基础 Mixin 中的方法。

六、总结

Mixin 是一种在 JavaScript 中实现代码复用的设计模式。它的重要性和优势包括:

  • 代码复用:Mixin 允许你将可复用的功能提取到独立的模块中,并在多个组件中共享这些功能,从而减少代码的冗余。
  • 模块解耦:Mixin 有助于将复杂的组件分解成更小、更独立的模块,从而提高代码的可维护性和可读性。
  • 灵活性:通过使用 Mixin,你可以在运行时动态地组合和扩展组件的功能,使代码更具灵活性和扩展性。
  • 可定制性:Mixin 可以通过参数传递或扩展来实现定制化,允许你根据具体需求进行微调。
  • 提高开发效率:使用 Mixin 可以更快地构建和复用代码,减少重复编写相同功能的时间和精力。