能否编写带类型的通用函数式组件供非React等多运行时使用?
完全没问题——你可以编写带类型约束的通用函数式组件,让它能在React、Preact、Inferno、Hyperapp等兼容h函数的运行时中使用。核心思路是基于通用的虚拟DOM创建函数h来编写组件,同时用TypeScript给组件加上类型保障,再通过配置确保JSX转译不绑定到特定框架。
下面是具体的实现步骤和注意事项:
1. 配置JSX转译目标为通用h函数
首先要让TypeScript(或Babel)把JSX语法转译为h函数调用,而不是React的createElement。在tsconfig.json里做如下配置:
{ "compilerOptions": { "jsx": "react", "jsxFactory": "h", // 指定JSX转译为h函数调用 "jsxFragmentFactory": "Fragment", // 如果需要支持Fragment,可指定对应的工厂函数 "target": "ESNext", "module": "ESNext", "declaration": true // 生成类型声明文件,方便用户使用 } }
如果用Babel,也可以通过@babel/plugin-transform-react-jsx插件配置pragma: "h"来实现同样的效果。
2. 编写带类型的通用组件
要避免依赖React的内置类型(比如React.FC),而是定义通用的Props类型,确保组件能适配不同运行时。你可以自己定义最小化的类型,或者复用社区的跨框架类型(比如Preact的类型就很通用)。
举个带类型的Title组件示例:
// 先定义通用的虚拟DOM节点类型和h函数类型 type VNode = { type: string | Function; props: Record<string, any>; children: VNode[] | string | null; }; // 通用组件Props类型,包含可选的children type ComponentProps<T = {}> = T & { children?: VNode[] | string | null; }; // 声明h函数的类型(如果运行时没有提供类型,需要自己定义) declare function h( type: string | Function, props: Record<string, any> | null, ...children: (VNode | string)[] ): VNode; // 带类型约束的Title组件 const Title = ({ title, url, children }: ComponentProps<{ title: string; url?: string; // 可选的链接属性 }>) => { return h('div', { className: 'component-heading' }, // 根据url是否存在渲染链接或纯文本 url ? h('a', { href: url }, title) : title, children ); };
3. 避免依赖特定框架的API
为了保证通用性,组件里不要使用React独有的API,比如useState、useEffect这类React Hooks(除非你确认目标运行时都兼容Hooks规范,比如Preact、Inferno)。如果需要状态管理,可以用组件内部的闭包状态,或者把状态逻辑交给组件使用者处理。
另外,注意不同运行时对Props的细微差异:比如部分运行时可能用class而非className,或者事件名用小写(比如click而非onClick)。你可以在组件里做兼容处理,或者在文档中明确说明这些细节。
4. 分发组件的注意事项
- 不要打包
h函数:把h声明为外部依赖,让用户从各自的运行时中导入(比如React用户导入createElement并别名h,Preact用户直接导入h)。比如在Rollup配置中:export default { input: 'src/index.tsx', output: { file: 'dist/index.js', format: 'es' }, external: ['h'] // 标记h为外部依赖 }; - 提供类型声明:确保分发的包包含
.d.ts类型文件,让使用TypeScript的用户能获得类型提示。 - 多模块格式输出:可以同时输出ES模块和UMD格式,适配不同的导入场景。
用户使用你的组件时,只需要从他们的运行时导入h即可:
// React环境下的使用示例 import { createElement as h } from 'react'; import { Title } from 'your-component-library'; function App() { return <Title title="Hello World" url="/home">副标题</Title>; }
内容的提问来源于stack exchange,提问作者i_like_robots

