You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优雅处理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))

提问

当前代码可正常运行,想请教:

  1. 这种条件处理接口的方式是否为最佳实践?
  2. 针对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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 07:55:29