如何优雅处理TypeScript条件接口?API多类型日志场景优化
API返回多类型日志的处理方案优化探讨
问题场景与当前实现
我遇到这样一种场景:API会返回两种不同类型的历史日志。
接口定义
操作成功对应的接口:
export interface IsyncSuccess { deleted_count: number, new_count: number }
操作失败对应的接口:
export interface IsyncFailed { error_message: string }
完整日志的接口定义:
export interface logType { type: "SYNC_SUCCESS" | "SYNC_FAILED" // 为简洁用字符串,实际应为枚举 data: IsyncSuccess | IsyncFailed }
当前组件实现
我在表格行组件中做了如下条件处理:
const HistoryTableRow: React.FC<{ log: logType }> = React.memo(({ log }) => { const getLog = () => { if (log.type === "SYNC_SUCCESS") { const data = log.data as IsyncSuccess return <> <Td> {data.new_count} </Td> <Td> {data.deleted_count} </Td> </> } else { const data = log.data as IsyncFailed return <> <Td colspan={2}> {data.error_message} </Td> </> } } return ( <TbodyTr> {getLog()} </TbodyTr> ) }, (prevProps, nextProps) => (true))
提问
当前代码可正常运行,想请教:
- 这种条件处理接口的方式是否为最佳实践?
- 针对API返回不同日志类型的场景,有无更优雅的解决方案?
回答
关于当前实现的评价
你的当前实现可行但不算最佳实践——虽然能正常运行,但手动用as做类型断言是绕过TypeScript的类型检查,后续新增日志类型时,很容易因遗漏分支导致类型错误;而且所有渲染逻辑堆在一个函数里,随着类型增多会越来越臃肿。
更优雅的解决方案
1. 用自定义类型守卫替代类型断言
先定义类型守卫函数,让TypeScript自动推导data的类型,彻底避免手动断言:
function isSyncSuccess(log: logType): log is logType & { data: IsyncSuccess } { return log.type === "SYNC_SUCCESS"; } function isSyncFailed(log: logType): log is logType & { data: IsyncFailed } { return log.type === "SYNC_FAILED"; }
修改组件逻辑,TS会自动识别log.data的类型:
const getLog = () => { if (isSyncSuccess(log)) { return <> <Td>{log.data.new_count}</Td> <Td>{log.data.deleted_count}</Td> </> } else if (isSyncFailed(log)) { return <Td colspan={2}>{log.data.error_message}</Td> } // 新增类型时,TS会强制提示此处需要处理,避免遗漏 return <Td colspan={2}>未知日志类型</Td> }
2. 拆分组件,按类型单一职责渲染
把不同类型的日志行拆成独立组件,让每个组件只负责对应类型的渲染:
const SyncSuccessRow: React.FC<{ data: IsyncSuccess }> = ({ data }) => ( <> <Td>{data.new_count}</Td> <Td>{data.deleted_count}</Td> </> ); const SyncFailedRow: React.FC<{ data: IsyncFailed }> = ({ data }) => ( <Td colspan={2}>{data.error_message}</Td> );
主组件通过类型判断渲染对应子组件:
const HistoryTableRow: React.FC<{ log: logType }> = React.memo(({ log }) => { let content; if (isSyncSuccess(log)) { content = <SyncSuccessRow data={log.data} />; } else if (isSyncFailed(log)) { content = <SyncFailedRow data={log.data} />; } else { content = <Td colspan={2}>未知日志类型</Td>; } return <TbodyTr>{content}</TbodyTr>; }, (prevProps, nextProps) => prevProps.log.type === nextProps.log.type && JSON.stringify(prevProps.log.data) === JSON.stringify(nextProps.log.data) );
注意:原代码中React.memo的比较函数直接返回true会导致组件永远不更新,这里改成对比日志类型和数据内容才是正确的缓存逻辑。
3. 用映射表实现配置化渲染
如果后续要新增更多日志类型,用映射表可以避免写一堆if-else,扩展性更强:
// 定义类型到组件的映射关系 const logComponentMap: Record<logType["type"], React.FC<{ data: any }>> = { SYNC_SUCCESS: SyncSuccessRow, SYNC_FAILED: SyncFailedRow, }; const HistoryTableRow: React.FC<{ log: logType }> = React.memo(({ log }) => { const Component = logComponentMap[log.type]; if (Component) { return <TbodyTr><Component data={log.data} /></TbodyTr>; } return <TbodyTr><Td colspan={2}>未知日志类型</Td></TbodyTr>; }, (prevProps, nextProps) => prevProps.log.type === nextProps.log.type && JSON.stringify(prevProps.log.data) === JSON.stringify(nextProps.log.data) );
新增类型时,只需要添加对应的子组件和映射关系即可,无需修改主组件逻辑。
内容的提问来源于stack exchange,提问作者Mormen
相关产品推荐
相关产品推荐

