现有2个组件
- svgPreviewTest.vue 父组件
- svgPreview.vue 子组件
实现逻辑,父组件有一个按钮,点击父组件按钮时,向子组件传递一个值,并弹出一个窗口;
一. 在父组件 vsvgPreviewTest.vue 中,v-model="showPreview" 是一个Vue的双向绑定语法糖
const showPreview = ref(false); // 用于控制预览弹窗的显示状态
javascript
<template>
<div class="preview-test-container">
<vxe-button status="primary" @click="openPreview">打开弹窗</vxe-button>
<!-- SVG组件 -->
<svg-preview
v-model="showPreview"
:initial-svg="currentSvg"
title="预览示例"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import SvgPreview from './svgPreview.vue';
// 控制预览弹窗的显示状态
const showPreview = ref(false);
// 示例SVG字符串
const sampleSvg = `
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
<rect x="20" y="20" width="60" height="60" stroke="blue" stroke-width="2" fill="none" />
</svg>`;
// 当前SVG内容
const currentSvg = ref(sampleSvg);
// 打开预览弹窗的方法
const openPreview = () => {
showPreview.value = true;
};
</script>
二.在子组件svgPreview.vue中,
javascript
<template>
<vxe-modal v-model="visible" :title="title" width="1200" height="800" :mask="true" :loading="loading"
:resize="true" :show-zoom="false" :show-Footer="true">
<div class="svg-editor-container">
<textarea v-model="svgCode" placeholder="内容"></textarea>
</div>
</vxe-modal>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import message from '@/message';
// Props
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
title: {
type: String,
default: '预览'
},
initialSvg: {
type: String,
default: ''
}
});
// Emits
const emit = defineEmits(['update:modelValue']);
const handleInput = () => {
svgCode.value = formatSvgCode(svgCode.value);
};
const clearCode = () => {
svgCode.value = '';
};
const closeModal = () => {
visible.value = false;
};
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
});
const svgCode = ref(props.initialSvg);
const zoomLevel = ref(1);
const loading = ref(false);
</script>
- 定义以下Props,用于接收父组件传过来的值
javascript
// Props
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
- 子组件中的实现
javascript
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
});
这里的工作原理是:
- v-model 是一个语法糖,当你在父组件中写 **v-model="showPreview" **时,Vue实际上会将其转换为:
javascript
:modelValue="showPreview"
@update:modelValue="showPreview = $event"
- 在子组件中:
- 通过 defineProps 定义了一个 modelValue 属性来接收父组件的值
- 通过emit('update:modelValue', value) 来向父组件发送更新事件
- 子组件使用 computed 属性创建了一个 visible 计算属性:
- get 方法返回props中的 modelValue 值
- set 方法触发 update:modelValue 事件,通知父组件更新值
所以当: - 父组件改变 showPreview 的值时,子组件的 props.modelValue 会更新
- 子组件改变 visible 的值时,会触发 update:modelValue 事件,父组件的 showPreview 会相应更新
这就是为什么 showPreview 的值能够在父子组件之间同步。这是Vue的一个重要特性,使得组件之间的数据交互变得简单和可维护。
这种模式被称为"受控组件"模式,它确保了数据流的可预测性:数据总是从父组件流向子组件,而子组件通过事件通知父组件进行更新。这样的设计使得数据流向清晰,便于调试和维护。
三. 子组件 svgPreview.vue 定义的 defineProps 中,modelValue 一定是得叫这个名吗?
- 默认情况下,v-model 会使用 modelValue 作为 prop 名称,但你可以通过在子组件中添加 defineOptions 来自定义这个名称。
- 使用默认的 modelValue: 方式
javascript
<!-- 父组件 -->
<child-component v-model="showPreview" />
<!-- 子组件 -->
const props = defineProps({
modelValue: Boolean // 默认名称
})
const emit = defineEmits(['update:modelValue'])
- 自定义方式
- 自定义 prop 名称:
javascript
<!-- 父组件 -->
<child-component v-model:visible="showPreview" />
<!-- 子组件 -->
const props = defineProps({
visible: Boolean // 自定义名称
})
const emit = defineEmits(['update:visible'])
- 就拿刚开始的例子说,如果要改自定义的方式
- 首先子组件更改
javascript
// Props
const props = defineProps({
visible: { // 改为visible而不是modelValue
type: Boolean,
default: false
},
title: {
type: String,
default: 'SVG预览'
},
initialSvg: {
type: String,
default: ''
}
});
// Emits
const emit = defineEmits(['update:visible']); // 改为update:visible
// visible
const isVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
});
- 然后我们需要相应地修改父组件:
javascript
<!-- SVG预览组件 -->
<svg-preview
v-model:visible="showPreview"
:initial-svg="currentSvg"
title="SVG预览示例"
/>
这个例子展示了:
- 在子组件中:
- 我们将 prop 名从 modelValue 改为 visible
- 将 emit 事件从** update:modelValue** 改为 update:visible
- 将计算属性从 visible 改为isVisible(因为visible现在是prop名)
- 在父组件中:
- 我们将 v-model="showPreview" 改为 v-model:visible="showPreview"
- 这种语法明确指定了要绑定到哪个prop
关键点:
- 如果使用默认的 v-model ,prop 名必须是 modelValue ,emit 事件必须是 update:modelValue
- 如果要自定义名称,需要:
- 在父组件使用 v-model:propName 的语法
- 在子组件中使用相应的prop名称
- emit事件 名必须是 update:propName
- 这种灵活性让你可以:
- 使用更有语义的 prop 名称
- 在同一个组件上使用多个 v-model 绑定
- 更清晰地表达组件的意图
例如,你甚至可以在一个组件上使用多个 v-model:
javascript
<my-component
v-model:visible="isVisible"
v-model:title="dialogTitle"
v-model:content="dialogContent"
/>
每个 v-model 都会对应到组件中的一个 prop 和一个 update 事件。这使得组件的数据交互更加灵活和清晰。