一、引言
Mixin 的概念
在编程中,Mixin 是一种代码复用的技术,它允许你将多个类中的代码提取出来,形成一个独立的模块,并在需要的时候将其应用到其他类中。Mixin 可以用来实现代码的重用、扩展和定制。
Mixin 的主要作用
- 代码重用:通过将共同的代码提取到一个 Mixin 中,可以避免在多个类中重复编写相同的代码,从而提高代码的可维护性和可读性。
- 功能扩展:使用 Mixin 可以在不修改原始类的情况下,向类中添加新的功能或行为。这对于已经在使用的类特别有用,因为你可以通过添加 Mixin 来扩展其功能,而无需修改现有代码。
- 灵活定制:Mixin 允许你根据具体需求组合不同的功能,从而创建出具有特定行为的类。你可以选择应用一个或多个 Mixin,以及自定义 Mixin 的实现,以满足项目的特定要求。
- 更好的代码组织: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 用例
表单验证
- 表单验证 Mixin:这个 Mixin 可以用于验证表单中的输入数据,确保其符合特定的规则
创建一个表单验证 Mixin
此处编写的就是一个 Vue 组件实例的配置项,通过一定语法,可以直接混入到组件内部 data methods computed 生命周期函数
注意点:
- 如果此处和组件内,提供了同名的
data
或methods
,则组件内优先级更高 - 如果编写了生命周期函数,则
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% ; | |
} | |
} | |
.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项目
- 参数传递
有一个基础的 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! 这条消息。
- 动态 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 可以更快地构建和复用代码,减少重复编写相同功能的时间和精力。