TypeScript泛型componentControl函数的作用与用法解析
1. 函数入参说明
这不是二选一的问题,componentControl是柯里化形式的高阶组件工厂函数,分两次调用接收不同参数:
- 第一次直接调用
componentControl本身时,入参是BaseProps<P>类型的options,用来配置组件生命周期钩子、各状态下的展示组件 - 第一次调用的返回值是一个标准React函数组件,后续在JSX里使用这个组件时传入的props,才是泛型
P类型的wrapped
IDE给出的类型提示(options: BaseProps<P>): (wrapped: P) => JSX.Element已经明确了这个调用链:输入options配置,输出一个接收wrapped参数、返回JSX的组件函数。
2. wrapped参数的作用
wrapped就是最终生成的业务组件对外暴露的业务属性集合,和你平时给普通React组件传的props没有区别:
- 它会被透传给内部封装的
BaseComponent,和公共配置base合并 - 所有生命周期钩子(
onInit/onLoaded/onSubmit等)、各状态对应的展示组件收到的参数里,都会包含你传入的wrapped业务属性,供业务逻辑调用 - 你在options里写的自定义组件、钩子函数里拿到的业务字段,本质都来自这个wrapped参数。
3. 返回值说明
没错,你贴的内层函数就是componentControl的直接返回值。
这个内层函数完全符合类型声明里的React.FunctionComponent<P>规范:接收P类型的props(也就是wrapped),返回JSX元素,和IDE提示的返回值类型完全等价。它的核心逻辑很简单:把你传入的自定义options和内置的默认props做浅合并,再把合并后的公共配置、业务props一起传给底层的BaseComponent,由BaseComponent统一处理状态流转、生命周期触发、对应状态组件渲染。
4. 实际使用方式
使用分两步,不需要关心内部BaseComponent的实现逻辑,只需要传需要自定义的配置即可,未配置的字段会自动使用内置默认值(比如默认加载态是PageSpinner,默认生命周期是空的Promise.resolve):
- 第一步:调用工厂函数生成业务组件,传入自定义配置
// 举个例子:生成一个用户编辑表单组件 const UserEditForm = componentControl({ // 自定义加载完成后展示的表单UI LoadedComponent: (props) => { return ( <form onSubmit={props.submit}> <input defaultValue={props.userInfo.name} name="name" /> <button type="submit">保存</button> </form> ) }, // 组件初始化时拉取用户详情 onInit: async (props) => { const data = await fetchUserDetail(props.userId) props.updateUserInfo(data) }, // 提交时调用更新接口 onSubmit: async ({submittedData}) => { await updateUserApi(submittedData) }, // 自定义提交成功提示 SubmitSuccessComponent: () => <div>用户信息更新完成</div> })
- 第二步:像使用普通React组件一样,在页面中渲染生成的组件,传入业务props即可
function UserEditPage() { const { userId } = useParams() const [userInfo, updateUserInfo] = useState(null) return ( <div className="page"> <h1>编辑用户</h1> <UserEditForm userId={userId} userInfo={userInfo} updateUserInfo={updateUserInfo} /> </div> ) }
这个封装的核心作用是把通用的「初始化-加载中-加载失败-加载完成-提交中-提交成功/失败」的状态管理逻辑做了统一收敛,业务侧不需要每个页面都重复写loading状态、error兜底、状态流转判断的样板代码,只需要关心对应状态的UI和业务逻辑即可。
内容的提问来源于stack exchange,提问作者codeedoc

