如何用TypeScript实现InputType指定属性至少一个必填及访问?
问题背景
我定义了以下 TypeScript 类型:
export type InputType = { configJsonUrl?: string; configJsObject?: DataType; rawData?: { [key: string]: string }; action?: () => void; }; export type DataType = { id: string; type: string; 'product-configuration': { input: string }; };
对于 InputType,configJsonUrl、configJsObject、rawData 三个属性本身是可选的,但使用该类型的 React 组件要求至少传入其中一个,示例用法如下:
<ComponentX configJsonUrl='./data/info.json' /> <ComponentX configJsObject={jsObject} /> <ComponentX rawData={adhocData} />
我知道可以通过统一 input 字段的方式实现必填约束,但希望保留这三个属性的原名来做条件渲染,请问该怎么实现?这在 TypeScript 中可行吗?
我的 React 组件当前写法如下:
const FormGenerator = ({ configJsonUrl, configJsObject, rawData, }: InputType ): JSX.Element => { const [propForForm, setPropForForm] = useState<string>('dummy'); const [loading, setLoading] = useState(false); useEffect(() => { if (configJsonUrl) { setPropForForm("URL"); setLoading(false); // Stop loading } else if (rawData) { setPropForForm("adhoc object"); setLoading(false); // Stop loading } else if (configJsObject) { setPropForForm("JS object"); setLoading(false); // Stop loading } }, [configJsonUrl, rawData, configJsObject]); // 其余渲染逻辑 }
后来根据建议修改了 InputType:
export type InputType = { input: {configJsonUrl: string} | {configJsObject: DataType} | {rawData: {[key: string]: string}} action?: () => void; };
对应的组件改为:
const FormGenerator = ({input}: InputType ) // 其余渲染逻辑
现在想请教:如何从 input 中访问 configJsonUrl、configJsObject 和 rawData 这三个属性?
解决方案
一、保留原属性名同时实现「至少传一个」的类型约束
如果想保留三个属性的原名,同时强制要求至少传入其中一个,可以通过联合类型定义 InputType,替代原本的可选属性写法:
export type InputType = // 仅传入 configJsonUrl 的情况 { configJsonUrl: string; configJsObject?: never; rawData?: never; action?: () => void } | // 仅传入 configJsObject 的情况 { configJsObject: DataType; configJsonUrl?: never; rawData?: never; action?: () => void } | // 仅传入 rawData 的情况 { rawData: { [key: string]: string }; configJsonUrl?: never; configJsObject?: never; action?: () => void };
这里用 ?: never 约束未传入的属性不能赋值,确保每次只能传入三个中的一个(若业务允许同时传多个,可去掉对应属性的 ?: never 限制)。
这种写法下,组件依然可以直接解构三个属性,TypeScript 会自动推断哪个属性有值,原有的条件渲染逻辑无需改动,还能在编译阶段拦截「一个属性都不传」的错误。
二、统一 input 字段后的属性访问方式
如果已经采用了统一 input 字段的写法,可通过类型守卫判断 input 的具体类型,从而安全访问对应属性:
const FormGenerator = ({ input, action }: InputType ): JSX.Element => { const [propForForm, setPropForForm] = useState<string>('dummy'); const [loading, setLoading] = useState(false); useEffect(() => { setLoading(true); // 类型守卫:判断 input 是否包含 configJsonUrl 属性 if ('configJsonUrl' in input) { setPropForForm("URL"); // 安全访问 input.configJsonUrl console.log(input.configJsonUrl); } else if ('rawData' in input) { setPropForForm("adhoc object"); console.log(input.rawData); } else if ('configJsObject' in input) { setPropForForm("JS object"); console.log(input.configJsObject); } setLoading(false); }, [input]); // 其余渲染逻辑 };
TypeScript 会在类型守卫的分支中自动缩小 input 的类型范围,因此每个分支内都能安全访问对应属性,不会出现类型报错。若业务允许同时传入多个属性,可将 else if 改为独立的 if 判断。
内容的提问来源于stack exchange,提问作者moys

