🌟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
}