Skip to content

组件通信

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。