用 Object Maps 将环形复杂度换成 O(1)
Source: Dev.to
介绍
你是否有过每次需要修改复杂业务规则(尤其是 UI)时像踩在鸡蛋上那种感觉?无论是处理订单状态、支付网关还是权限等级,保持代码的一致性常常像在雷区里行走。
很多开发者的自然反应(这让我想起了自己的入门阶段 :D)是把这些变体写进 switch 或者一长串 if/else。问题是只要忘记了一个 case 或者链式写错了,立刻就会出现一个在生产环境中悄然运行的 bug。
很多人会尝试用 Guard Clauses(著名的 “提前返回”)来解决。这是一种优秀的技巧,能够清理验证逻辑并避免过度嵌套。顺便说一句,这本身就是一个值得单独写篇文章的主题。
但今天的目标更进一步。我不只是想整理 if,而是要消除它们。目标是停止编写防御性条件,转而写出意图。
如何在实践中应用
当我们面对可预见的规则时,Object Maps 配合 Mapped Types 提供了一种更为稳健的方案。关键在于把条件判断的脆弱性转化为合约的安全性。
真实案例(React Native + Zustand)
想象一个外卖应用,每个订单状态都对应:显示文字、颜色、是否可取消以及点击时的行为。
定义域
export type OrderStatus =
| 'pending'
| 'confirmed'
| 'preparing'
| 'delivered'
| 'cancelled';
type OrderStatusConfig = {
label: string;
canCancel: boolean;
// Note que tirei a cor daqui. Já explico o porquê! 👇
};
Mapped Type 以确保完整覆盖
// O compilador obriga que TODAS as chaves de OrderStatus existam aqui.
type OrderStatusMap = {
[K in OrderStatus]: OrderStatusConfig;
};
export const orderStatusMap: OrderStatusMap = {
pending: { label: 'orderStatus.pending', canCancel: true },
confirmed:{ label: 'orderStatus.confirmed', canCancel: true },
preparing:{ label: 'orderStatus.preparing', canCancel: false },
delivered:{ label: 'orderStatus.delivered', canCancel: false },
cancelled:{ label: 'orderStatus.cancelled', canCancel: false },
};
如果你新增了一个状态却忘记映射,TypeScript 会直接报错,阻止构建。
更实用的方式
如果想要更精确的类型推断且不失检查,satisfies 操作符非常合适。它会验证对象是否符合合约,同时保留字面量类型。
export const orderStatusMap = {
pending: { label: 'orderStatus.pending', canCancel: true },
confirmed:{ label: 'orderStatus.confirmed', canCancel: true },
preparing:{ label: 'orderStatus.preparing', canCancel: false },
delivered:{ label: 'orderStatus.delivered', canCancel: false },
cancelled:{ label: 'orderStatus.cancelled', canCancel: false },
} satisfies Record;
职责分离(Domain 与 UI)
规则映射 (orderStatusMap) 并不需要了解你的设计系统。我们可以再创建一个仅负责把状态映射到主题的映射表:
// Vinculando chaves do status às chaves do seu tema
// (Ex: NativeBase, Restyle, etc)
export const orderStatusColorMap = {
pending: 'warning',
confirmed:'info',
preparing:'orange',
delivered:'success',
cancelled:'danger',
} satisfies Record;
在 UI 中使用(零 ifs)
使用 Zustand 的 Store
import { create } from 'zustand';
type OrderStore = {
status: OrderStatus;
setStatus: (status: OrderStatus) => void;
};
export const useOrderStore = create(set => ({
status: 'pending',
setStatus: status => set({ status }),
}));
React Native 组件
import { useTranslation } from 'react-i18next';
import { useTheme } from '@react-navigation/native';
import { Pressable, Text } from 'react-native';
import { orderStatusMap, orderStatusColorMap } from './maps';
import { useOrderStore } from './store';
export function OrderStatusView() {
const { t } = useTranslation();
const status = useOrderStore(state => state.status);
const theme = useTheme();
// Desestruturação direta da regra de negócio
const { label, canCancel } = orderStatusMap[status];
// Resolução da cor via Design System
const color = theme.colors[orderStatusColorMap[status]];
return (
<>
{t(label)}
{canCancel && (
console.log('Cancelar')
)}
</>
);
}
更进一步
这种模式并非只适用于 UI。想象一下处理不同网关(Stripe、Mercado Pago)下的 PIX 与信用卡支付时,使用 Object Map 返回对应的执行策略:
export const paymentStrategies = {
pix: async (data, gateway) => {
return adaptersMap[gateway].pix(data);
},
credit_card: async (data, gateway) => {
return adaptersMap[gateway].creditCard(data);
},
} satisfies Record;
// Uso na aplicação:
const result = await paymentStrategies[method](negotiation, selectedGateway);
为什么这改变了局面?
- 复杂度 O(1) – 消除了阅读多个
else带来的认知成本。 - 安心 – 当我新增状态或支付方式时,无需去寻找所有
if的位置,编译器会指明错误所在。 - 合约清晰 – 代码不再是防御式的,而是表达意图的。
那么,你平时会使用 Object Maps 吗?还是仍然依赖记忆去维护 switch case 的完整性? 👇