在vue3中自定义组件双向绑定语法的改变,使得写法和vue2大为不同。我们以element-plus的dialog组件为例。基于它。封装一个自己的对话框,同时介绍两种实现思路:

思路一:数据驱动型:

我们封装一个test-dialog组件:

<el-button @click="open">打开</el-button>

<test-dialog  ref="testDom"  v-model:visible="flag" ></test-dialog>

import TestDialog from "@/components/Dialogs/TestDialog"

import { reactive, toRefs, ref, onMounted, watch } from "vue"

setup() {

const state = reactive({

      flag: false

    })

 const open = () => {

      state.flag = true

    }

 watch(() => state.flag, (val) => {

      console.log("父组件监听flag:", val)

    })

}

子组件:

<template>

  <el-dialog

    title="提示"

    v-model="dialogVisble"

    width="30%"

    :before-close="close"

  >

    <span>这是一段信息</span>

    <template #footer>

      <span class="dialog-footer">

        <el-button @click="close">取 消</el-button>

        <el-button

          type="primary"

          @click="close"

        >确 定</el-button>

      </span>

    </template>

  </el-dialog>

</template>

<script>

import { ref, watch } from "vue"

export default {

  name: "TestDialog",

  components: {},

  props: {

    visible: {

      type: Boolean,

      default: false

    }

  },

  setup(props, ctx) {

    const dialogVisble = ref(false)

    const close = () => {

      ctx.emit("update:visible", false)

    }

    watch(() => dialogVisble.value, (val) => {

      ctx.emit("update:visible", val)

    })

    watch(() => props.visible, (val) => {

      dialogVisble.value = val

    })

    return {

      dialogVisble,

      open,

      close

    };

  },

};

</script>

<style scoped>

</style>

第二种思路:

类似直接操作dom,我们先通过ref拿到test-dialog组件的引用,每次点击打开按钮时,直接通过子组件去操作他内部的变量让它显示和隐藏

父页面:

<el-button @click="openByParentMethod ">打开</el-button>

<test-dialog  ref="testDom"  v-model:visible="flag" ></test-dialog>

import TestDialog from "@/components/Dialogs/TestDialog"

 setup() {

     const testDom = ref(null)

     const openByParentMethod = () => {

        testDom.value.dialogVisble = true

    }

    const closeByParentMethod= () => {

      testDom.value.dialogVisble = false

    }

   return {

      testDom,

      openByParentMethod,

     closeByParentMethod,

   }

}

子组件:

<template>

  <el-dialog

    title="提示"

    v-model="dialogVisble"

    width="30%"

    :before-close="close"

  >

    <span>这是一段信息</span>

    <template #footer>

      <span class="dialog-footer">

        <el-button @click="close">取 消</el-button>

        <el-button

          type="primary"

          @click="close"

        >确 定</el-button>

      </span>

    </template>

  </el-dialog>

</template>

 setup(props, ctx) {

    const dialogVisble = ref(false)

    const open = () => {

      dialogVisble.value = true

    }

    const close = () => {

      dialogVisble.value = false

    }

  return {

      dialogVisble,

      open,

      close

    };

  },

而对于vue2来说。实现这样的一个弹窗。则是通过如下方式:

 <select-city v-model="cityShow"></select-city>父组件通过v-model绑定变量cityShow,

而子组件中通过定义props:value,然后通过v-model 操控value来实现,如下:

1子组件在props中申明vaule,注意value名称是约定的不能写成其他的名称

2 子组件中通过 v-model="value"绑定改value

3关闭弹窗的时候,直接通过emit一个input事件来派发当前value的值。注意,这里只能写成input事件

(1)

 <van-action-sheet

      v-model="value"

    @close="close"

>

  内容xxx

</van-action-sheet>

export default {

  name: "selectCity",

  components: {},

  props: {

    value: {

      type: Boolean,

      default: false

    }

  },

 methods: {

    close() {

      console.log("close!!!!")

      this.$emit("input", this.cityShow)

    },

  },

针对vue2我们通过一个inputNm组件来具体看一看

<view class="item-menu-name">
			<view class="pt6 txtRt">
				{{item1.name}}
			</view>

			<view class=" pt6 vip-price txtRt">
				¥ {{item1.vipPrice}}会员价
			</view>
			<view class=" pt6 txtRt sale-price">
				¥ {{item1.salePrice}}
			</view>
			<view class="num-box">
				<InputNum v-model="item1.num" @increase="increseNum" @decresae="decreseNum"
									
			</view>
</view>
<script>

methods:{
    caculateTotal() {
		let total=0;
		this.tabbar.forEach(item => {
		item.foods.forEach(k => {
			console.log(" k.num", k.num)
						total += k.num
					})
		})
				this.total=total
				console.log("计算toatl",this.total)
		},
		increseNum() {
				this.caculateTotal()
		},
		decreseNum() {
				this.caculateTotal()

		},
		inputNum() {
				this.caculateTotal()

		},
}


</script>

没错InputNum 通过v-model绑定了我们购物车里的商品的数量。我们单独抽出这个组件进行计算

接下来我们看inputMum的具体实现

<template>
	<view class="inputnum-page">
		<view class="inputnum-container">
				<!-- <uni-transition :mode-class="fade" :show="num>0"> -->
					<view class="lt-container" v-if="curNum>0">
						<view class="oper-left-wrap">
							<view class="btn-left"  @click="decrease">
								-
							</view>
							<view class="oper-input">
								<input v-model="curNum" type="number"   class="ipt"/>
							</view>
						</view>
					</view>
				<!-- </uni-transition> -->
				<view class="rt-container">
					<view class="btn-right" @click="increase">
						+
					</view>
				</view>
		</view>
	</view>
</template>
<script>
	export default {
		props: {
			value: {
				type: Number,
				default: 0
			}
		},
		data() {
			return {
				maskClass: {
					opacity: 0,
				},
			}
		},
		computed:{
			curNum:{
				get(){
					return this.value
				},
				set(val){
					this.$emit("input", val);
				}
				
			}
			
		},

		methods: {
			decrease(){
				
				this.curNum--;
				if(this.curNum<=0){
					this.curNum=0;
					return 
				} 
				this.$emit("decrease",this.curNum)
			},
			
			increase(){
			
				this.curNum++;
				console.log("this.curNum",this.curNum)
				this.$emit("increase",this.curNum)
			}
		},
		mounted() {}
	}
</script>
<style scoped lang='scss'>
	.inputnum-page{
		margin-top: 10rpx;
	}
	.inputnum-container {
		width: 100%;
		display: flex;
	}
	.lt-container{
		width: 80%;
	}
	.rt-container{
		flex:1
	}
	.oper-left-wrap {
		width: 120rpx;
		display: flex;
		justify-content: flex-end;
	}
	.oper-input {
		width: 70rpx;
	}
	.btn-left,
	.btn-right {
		width: 40rpx;
		height: 40rpx;
		line-height: 40rpx;
		border-radius: 50%;
		background: #f00;
		color:#fff;
		display: flex;
		justify-content: center;
		align-items: center;
		font-size: 32rpx;
	}
	.ipt{text-align: center;}
</style>

props里为什么是value?对。这就是vue多v-model处理时的约定俗成。默认value和input事件就是v-model的语法糖。当然如果你想换成其他值,则需要单独配置model选项。

特别注意:这里computed的使用。如果我们不是通过computed来引用props的值而是单独把value作为inputNum;里v-model的绑定值。这样页面会报错Avoid mutating a prop directly since the value will be overwritten whenever...因为vue默认遵守单向数据流的思想。不允许子组件直接去更改父组件的props的值。而通过computed我们可以巧妙的绕开这个限制

Logo

前往低代码交流专区

更多推荐