🌟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)"
clearable
style="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
}