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