组件api设计应克制暴露内部状态,仅提供语义化属性(如value、disabled),内部状态通过事件通知;attributechangedcallback中延迟更新dom并做浅比较;对外方法统一返回promise;扩展优先用slot和事件而非配置项。

只暴露语义化属性,别暴露内部状态
组件 API 设计的第一道防线是「克制」:value、disabled、required 这类属性必须直接支持,isFocused、hasError、isLoading 这类内部状态绝不挂成公开属性。否则使用者就得手动同步:el.value = 'x'; el.hasError = false; el.isLoading = true;——逻辑耦合、易漏、难维护。
正确做法是:
- 只保留对外有明确业务含义的 attribute:如
value(字符串/数字)、min/max(数值约束)、disabled(用this.hasAttribute('disabled')判断,不是this.disabled === true)、placeholder(仅输入类组件需要) - 其余状态一律通过事件通知:
change、input、focus、blur、error - 控制行为统一提供方法:
focus()、reset()、validate()
attributeChangedCallback 里别直接更新 DOM
这个回调不是“一改就重绘”的快捷通道。浏览器可能批量触发它(比如 SSR 注入多个属性),直接操作 DOM 会卡顿,还容易和初始化逻辑冲突。
稳妥做法是延迟合并执行:
- 用
Promise.resolve().then(() => { /* 更新逻辑 */ })推迟到微任务末尾,自动合并多次变更 - 对字符串类属性(如
format)做浅比较:if (oldValue === newValue) return - 绝对避免在回调里调
this.setAttribute(),否则会再次触发自身,形成死循环(尤其带默认值处理时)
对外方法统一返回 Promise
哪怕当前实现是同步的,所有公开方法也应返回 Promise。这不是为了炫技,而是防明天加个 i18n 加载、图标懒加载、或 Web Worker 校验——同步方法一旦上线,再改就要破接口、推倒重来。
示例写法:
async validate() {
if (this._pendingValidation) return this._pendingValidation;
this._pendingValidation = this._doValidate();
try {
await this._pendingValidation;
} finally {
this._pendingValidation = null;
}
return this._pendingValidation;
}
调用方始终可写 await el.validate(),不用关心底层是否已异步化;错误统一 reject(new Error()),不混用 console.error + return false。
扩展靠 slot 和事件,而不是配置项
当别人想定制按钮图标、错误提示模板、或添加额外操作按钮时,第一反应不该是加个 iconTemplate 或 extraActions 属性——这会让 API 膨胀、类型难定义、文档越来越厚。
更自然的做法是:
- 用
<slot name="icon"></slot>让使用者塞任意 HTML - 用
<slot name="error"></slot>替代errorTemplate配置 - 通过
customEvent暴露钩子:el.addEventListener('before-submit', handler),比onBeforeSubmit属性更灵活且不污染属性命名空间
真正难的是边界判断:什么时候该收口为属性(比如 size="small"),什么时候该放行为给 slot/event(比如自定义整个标签页头部)。答案往往藏在“这个东西是否会被反复重写”——如果一个项目里 80% 的使用场景都换掉它,那就别配,直接 slot。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











