可定制性本质是语义化控制:只暴露业务明确的属性(value、disabled等),状态通过事件(change、error)和方法(focus、validate)管理,避免内部状态映射;attributeChangedCallback中延迟DOM更新,首次渲染在connectedCallback中同步;对外方法统一返回Promise;扩展靠<slot>、事件和CSS自定义属性。

可定制性不等于参数堆砌,而是让使用者用最接近语义的方式控制行为——value 就该管值,disabled 就该禁用,其余状态靠事件和方法暴露,不是靠一堆 isHovered、hasAnimation 属性手动同步。
只暴露语义化属性,别把内部状态全挂成 public
常见错误是把组件内部变量直接映射为属性:比如 isFocused、isLoading、errorCount。这会让调用方陷入“我改了 value,是不是还得手动设 hasError = false”的泥潭。
- 只保留业务含义明确的属性:
value、min、disabled、placeholder - 布尔属性必须用
this.hasAttribute('disabled')判断,不能依赖this.disabled === true(未设置时值为undefined) - 状态变化统一走事件:
change、input、error;控制动作统一走方法:focus()、reset()、validate()
attributeChangedCallback 里别直接更新 DOM
这个回调不是“属性一变就立刻重绘”的快捷通道。浏览器可能批量触发它(比如 SSR 注入多个属性),直接操作 DOM 会卡顿,还容易和初始化逻辑冲突。
- 用
Promise.resolve().then(() => { /* 更新逻辑 */ })推迟到微任务末尾,自动合并多次变更 - 对字符串类属性(如
format)做浅比较:if (oldValue === newValue) return - 绝对避免在回调里调
this.setAttribute(),否则会再次触发自身,形成死循环(尤其带默认值处理时) - 首次渲染时属性已存在,但
attributeChangedCallback不会执行 —— 需在connectedCallback中补一次同步
对外方法统一返回 Promise,哪怕当前是同步实现
同步方法看似简单,但一旦组件内部涉及异步加载(图标字体、i18n 资源)、动画或 Web Worker 通信,就会阻塞主线程或抛出意外错误。
立即学习“前端免费学习笔记(深入)”;
- 所有可能异步的方法(
validate()、submit()、loadOptions())统一返回Promise - 错误处理统一走
reject(new Error('')),不要混用console.error+ 返回false - 即使当前是同步逻辑,也包装成
return Promise.resolve(result),为后续扩展留余地
<slot> 和事件才是真正的扩展点
属性和方法解决的是“我怎么控制你”,而 <slot> 和事件组合解决的是“你怎么融入我的结构和流程”。这才是可定制性的核心出口。
- 内容分发靠
<slot name="header">、<slot>,不是靠headerTemplate字符串属性 - 交互反馈靠事件:
this.dispatchEvent(new CustomEvent('submit', { detail: { data } })),不是靠onSubmit属性传函数 - 样式定制靠 CSS 自定义属性:
button { background: var(--btn-bg, #007bff); },用户写<my-button style="--btn-bg: red">即可生效
真正难的不是写多少配置项,而是判断哪些该收进属性、哪些该放给 slot、哪些该扔进事件——边界模糊的地方,往往就是定制性崩塌的起点。



















