🌟Vue 3 + Element Plus 常见开发问题与解决方案手册
🧠 本文整理了常见但容易混淆的几个 Vue 3 前端开发问题,包括插槽、原型链、响应式数据处理、v-model 报错、样式阴影控制等,建议收藏学习!
📌一、动态插槽 fallback 原理详解
✅ 场景
在组件中使用如下代码:
<slot :name="item.prop"><component:is="getComponentType(item.type)"v-model="formModel[item.prop]"v-bind="getComponentProps(item)"clearablestyle="width: 100%"/>
</slot>
 
✅ 疑问
为什么加了 <slot :name="xxx">默认内容</slot>,父组件传了插槽就会生效,没传就自动使用默认内容?
✅ 解答
这是 Vue 插槽的 fallback(回退)机制:
- 父组件有传 
<template #xxx>插槽,就渲染插槽内容; - 没传,就渲染 
<slot>标签中的默认内容。 
✅ 示例
子组件
<slot name="gender"><el-input v-model="formModel.gender" />
</slot>
 
父组件 A(没传插槽)
<ChildComponent />
 
➡️ 渲染默认 <el-input />
父组件 B(传了插槽)
<ChildComponent><template #gender><el-radio-group v-model="formModel.gender"><el-radio label="male">男</el-radio><el-radio label="female">女</el-radio></el-radio-group></template>
</ChildComponent>
 
➡️ 渲染插槽内容
📌二、h() 函数参数说明
 
h(type, props?, children?)
 
| 参数 | 含义 | 
|---|---|
| type | 标签名或组件(字符串、对象) | 
| props | class、style、事件、props | 
| children | 字符串、VNode数组、插槽函数等 | 
✅ 示例
h('div', { class: 'box' }, 'Hello') // <div class="box">Hello</div>
h(MyComponent, { propA: 1 }, { default: () => h('span', '插槽内容') })
 
📌三、为什么 unref() 不能去掉 Proxy?
 
function handleSearch() {emit('search', unref(props.queryParams))
}
 
❓ 疑问
打印结果为什么还是 Proxy?
✅ 解答
unref()只能解包ref()类型reactive()返回的是 Proxy,不会被unref()解包- 所以 
unref(reactiveObj)仍然是 Proxy 
✅ 正确做法
方式一:toRaw()(浅解包)
 
import { toRaw } from 'vue'
emit('search', toRaw(props.queryParams))
 
方式二:cloneDeep()(推荐,深拷贝)
 
import cloneDeep from 'lodash-es/cloneDeep'
emit('search', cloneDeep(props.queryParams))
 
📌四、toRaw vs unref 的区别
 
| 方法 | 用途 | 解包对象 | 是否保留响应式 | 
|---|---|---|---|
unref() | 取出 ref 的 .value | 只能 ref | 是(ref.value 仍可能是 reactive) | 
toRaw() | 获取原始对象(去 Proxy) | 只能 reactive | 否 | 
📌五、原型链到底有几层?
✅ 答案:没有固定层数,直到原型为 null 为止。
 
✅ 示例
const obj = {}
Object.getPrototypeOf(obj)          // → Object.prototype
Object.getPrototypeOf(Object.prototype) // → null ✅
 
| 类型 | 原型链结构 | 
|---|---|
对象 {} | obj → Object.prototype → null | 
数组 [] | → Array.prototype → Object.prototype → null | 
函数 function () {} | → Function.prototype → Object.prototype → null | 
📌六、v-model 报错:v-model cannot be used on a prop
 
❓ 报错场景
在组件中这样写:
<el-input v-model="modelValue" />
 
而 modelValue 是 defineProps() 得到的 prop,Vue 提示该属性是只读的!
✅ 正确做法(支持 v-model)
 
<el-input:model-value="modelValue"@input="val => emit('update:modelValue', val)"
/>
 
✅ setup 完整写法
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
 
📌七、如何让 box-shadow 去掉右边?
原始样式(四边阴影):
box-shadow: 0 0 0 1px var(--el-input-border-color) inset;
 
✅ 改成只保留左上右:
box-shadow:inset 1px 0 0 var(--el-input-border-color),   /* 左 */inset 0 1px 0 var(--el-input-border-color),   /* 上 */inset 0 -1px 0 var(--el-input-border-color);  /* 下 */
 
👉 不写右边的就相当于去掉右侧边框效果。
📌附录:使用 Element Plus 时注意
- 所有输入类组件(如 el-input、el-select)都使用 
modelValue作为 v-model 的绑定值; - 插槽使用 fallback 内容时,记得 slot name 要和父组件一致;
 - 使用样式定制时,Element Plus 常用的 CSS 变量有:
--el-input-border-color、--el-border-color、--el-color-primary等。 
✅ 推荐工具函数(Bonus)
你可以封装一个自动脱 Proxy 工具:
import { toRaw, isRef, unref } from 'vue'
import cloneDeep from 'lodash-es/cloneDeep'export function cleanReactive(val: any) {if (isRef(val)) return unref(val)if (val && val.__v_isReactive) return cloneDeep(toRaw(val))return val
}