组件通信
WARNING
组件通信先选最简单的方式。不要为了“统一”把所有场景都塞进 provide/inject 或 expose。
| 需求 | 推荐 |
|---|---|
| 父传子 | Props |
| 子通知父 | 事件 |
| 父子共同维护一个值 | v-model |
| 父传内容 | 插槽 |
| 跨多层共享上下文 | Provide / Inject |
| 父调用子方法 | 模板引用 + 组件暴露 |
Provide / Inject
ts
import { createInjectionKey, provide } from "@elfui/core";
export const themeKey = createInjectionKey<"light" | "dark">("theme");
provide(themeKey, "dark");子组件:
ts
import { inject } from "@elfui/core";
import { themeKey } from "./keys";
const theme = inject(themeKey, "light");注入沿逻辑组件父链解析,可以跨 open/closed Shadow Root;Teleport 到 body 后也不会丢失 原 Provider 或 App 上下文。嵌套 Provider 以最近者优先,后插入的 Custom Element 会读取 连接时可见的值。需要动态更新时应 provide Ref 或响应式对象:
ts
const theme = useRef<"light" | "dark">("light");
provide(themeKey, theme);WARNING
Provide / Inject 适合表单、主题、菜单这类层级上下文,不适合替代普通 props。
WARNING
hasInjectionContext() 可用于通用 composable:它只判断当前是否处于组件 setup 的可注入上下文,不能替代 inject() 的实际读取。
Ref + Expose
WARNING
当父组件必须调用子组件方法时,子组件用 defineExpose() 暴露,父组件用 useTemplateRef() 获取。
WARNING
这类通信是命令式 API,适合 focus()、validate()、reset(),不适合普通数据流。
暴露 focus()、blur() 等 HTMLElement 已有成员时,子组件需要通过 defineExpose(api, { overrideNative: ["focus"] }) 明确声明覆盖。
ts
type SearchInputHost = HTMLElement & {
focus(): void;
clear(): void;
};
const search = useTemplateRef<SearchInputHost>("search");
const focusSearch = (): void => search.value?.focus();
export const Page = defineHtml(`
<search-input ref="search"></search-input>
<button @click=${focusSearch}>聚焦搜索框</button>
`);WARNING
模板 ref 当前必须是静态 ref="name"。项目引入生成的 HTMLElementTagNameMap 后,原生 tag 查询也会得到 expose 类型;否则用上面的泛型明确 host API。
