typescript 的可区分联合是一个强大的功能,它将模式匹配提升到一个新的水平。它们使我们能够创建复杂的、类型安全的条件逻辑,而不仅仅是简单的 switch 语句。我在最近的项目中广泛使用了这种技术,它改变了我在 typescript 中处理控制流的方式。
让我们从基础开始。可区分联合是一种使用公共属性来区分不同变体的类型。这是一个简单的例子:
type shape = | { kind: 'circle'; radius: number } | { kind: 'rectangle'; width: number; height: number }
这里的“种类”属性是我们的判别式。它允许 typescript 根据其值推断我们正在处理的特定形状。
现在,让我们看看如何使用它进行模式匹配:
function getarea(shape: shape): number { switch (shape.kind) { case 'circle': return math.pi * shape.radius ** 2 case 'rectangle': return shape.width * shape.height } }
这很简洁,但这只是开始。我们可以更进一步。
受歧视工会最强大的方面之一是详尽检查。 typescript 可以确保我们在模式匹配中处理了所有可能的情况。让我们向我们的联合添加一个新形状:
type shape = | { kind: 'circle'; radius: number } | { kind: 'rectangle'; width: number; height: number } | { kind: 'triangle'; base: number; height: number } function getarea(shape: shape): number { switch (shape.kind) { case 'circle': return math.pi * shape.radius ** 2 case 'rectangle': return shape.width * shape.height // typescript will now warn us that we're not handling the 'triangle' case } }
为了使其更加健壮,我们可以添加一个引发错误的默认情况,确保我们永远不会意外忘记处理新情况:
function assertnever(x: never): never { throw new error("unexpected object: " + x); } function getarea(shape: shape): number { switch (shape.kind) { case 'circle': return math.pi * shape.radius ** 2 case 'rectangle': return shape.width * shape.height case 'triangle': return 0.5 * shape.base * shape.height default: return assertnever(shape) } }
现在,如果我们添加新形状而不更新 getarea 函数,typescript 会给我们一个编译时错误。
但是我们可以通过模式匹配走得更远。让我们看一个涉及嵌套模式的更复杂的示例。
想象一下我们正在为交通灯构建一个简单的状态机:
type trafficlightstate = | { state: 'green' } | { state: 'yellow' } | { state: 'red' } | { state: 'flashing', color: 'yellow' | 'red' } function getnextstate(current: trafficlightstate): trafficlightstate { switch (current.state) { case 'green': return { state: 'yellow' } case 'yellow': return { state: 'red' } case 'red': return { state: 'green' } case 'flashing': return current.color === 'yellow' ? { state: 'red' } : { state: 'flashing', color: 'yellow' } } }
在这里,我们不仅匹配顶级状态,而且当我们处于“闪烁”状态时也匹配嵌套属性。
我们还可以使用守卫为我们的模式匹配添加更复杂的条件:
type weatherevent = | { kind: 'temperature', celsius: number } | { kind: 'wind', speed: number } | { kind: 'precipitation', amount: number } function describeweather(event: weatherevent): string { switch (event.kind) { case 'temperature': if (event.celsius > 30) return "it's hot!" if (event.celsius < 0) return "it's freezing!" return "the temperature is moderate." case 'wind': if (event.speed > 100) return "there's a hurricane!" if (event.speed > 50) return "it's very windy." return "there's a gentle breeze." case 'precipitation': if (event.amount > 100) return "it's pouring!" if (event.amount > 0) return "it's raining." return "it's dry." } }
这种模式匹配方法不仅限于 switch 语句。我们可以将它与 if-else 链一起使用,甚至可以与对象文字一起使用以实现更复杂的场景:
type action = | { type: 'increment' } | { type: 'decrement' } | { type: 'reset' } | { type: 'set', payload: number } const reducer = (state: number, action: action): number => ({ increment: () => state + 1, decrement: () => state - 1, reset: () => 0, set: () => action.payload, }[action.type]())
这种方法在实现访问者模式时特别有用。这是我们如何使用可区分联合来实现简单表达式求值器的示例:
type expr = | { kind: 'number'; value: number } | { kind: 'add'; left: expr; right: expr } | { kind: 'multiply'; left: expr; right: expr } const evaluate = (expr: expr): number => { switch (expr.kind) { case 'number': return expr.value case 'add': return evaluate(expr.left) + evaluate(expr.right) case 'multiply': return evaluate(expr.left) * evaluate(expr.right) } } const expr: expr = { kind: 'add', left: { kind: 'number', value: 5 }, right: { kind: 'multiply', left: { kind: 'number', value: 3 }, right: { kind: 'number', value: 7 } } } console.log(evaluate(expr)) // outputs: 26
这种模式允许我们轻松地使用新类型的表达式来扩展我们的表达式系统,并且 typescript 将确保我们处理评估函数中的所有情况。
这种方法最强大的方面之一是它如何允许我们将大型、复杂的条件块重构为更易于管理和扩展的结构。让我们看一个更复杂的例子:
想象一下我们正在构建一个系统来处理不同类型的金融交易:
type Transaction = | { kind: 'purchase', amount: number, item: string } | { kind: 'refund', amount: number, reason: string } | { kind: 'transfer', amount: number, to: string } | { kind: 'deposit', amount: number } | { kind: 'withdrawal', amount: number } type TransactionProcessor = { [K in Transaction['kind']]: (transaction: Extract<Transaction, { kind: K }>) => void } const processTransaction: TransactionProcessor = { purchase: ({ amount, item }) => { console.log(`Processing purchase of ${item} for $${amount}`) // Implement purchase logic }, refund: ({ amount, reason }) => { console.log(`Processing refund of $${amount} for reason: ${reason}`) // Implement refund logic }, transfer: ({ amount, to }) => { console.log(`Processing transfer of $${amount} to ${to}`) // Implement transfer logic }, deposit: ({ amount }) => { console.log(`Processing deposit of $${amount}`) // Implement deposit logic }, withdrawal: ({ amount }) => { console.log(`Processing withdrawal of $${amount}`) // Implement withdrawal logic } } function handleTransaction(transaction: Transaction) { processTransaction[transaction.kind](transaction as any) }
在这个示例中,我们使用 typescript 的映射类型和条件类型来创建一个类型安全的对象,其中每个键对应一个事务类型,每个值都是处理该特定类型事务的函数。这种方法使我们能够轻松添加新类型的交易,而无需更改handletransaction函数的核心逻辑。
这种模式的美妙之处在于它既是类型安全的又是可扩展的。如果我们添加一种新的交易类型,typescript 会强制我们添加相应的处理器函数。如果我们尝试处理不存在的事务类型,我们将收到编译时错误。
这种具有可区分联合的模式匹配方法可以产生更具表现力、更安全和自记录的 typescript 代码,尤其是在复杂的应用程序中。它使我们能够以可读且可维护的方式处理复杂的逻辑。
随着我们的应用程序变得越来越复杂,这些技术变得越来越有价值。它们使我们能够编写不仅正确而且易于理解和修改的代码。通过充分利用 typescript 的类型系统,我们可以创建健壮、灵活的系统,并且使用起来很愉快。
请记住,我们的目标不仅仅是编写有效的代码,而是编写能够清楚表达其意图并且能够在需求变化时防止错误的代码。与可区分联合的模式匹配是实现此目标的强大工具。
根据我的经验,采用这些模式可以显着提高代码质量和开发速度。需要一些时间来习惯用可区分的联合和详尽的模式匹配来思考,但一旦你这样做了,你会发现它为以清晰、类型安全的方式构建代码开辟了新的可能性。
当您继续探索 typescript 时,我鼓励您寻找机会在自己的代码中应用这些模式。从小处开始,也许可以将复杂的 if-else 链重构为可区分联合。随着您对这项技术越来越熟悉,您将开始看到越来越多的地方可以应用它来简化和澄清您的代码。
请记住,typescript 的真正力量不仅在于它捕获错误的能力,还在于它能够引导我们获得更好、更具表现力的代码结构。通过采用可区分联合和详尽模式匹配等模式,我们可以创建不仅正确,而且易于阅读和维护的代码。
我们的创作
一定要看看我们的创作:
投资者中心 | 智能生活 | 时代与回响 | 令人费解的谜团 | 印度教 | 精英开发 | JS学校
我们在媒体上
科技考拉洞察 | 时代与回响世界 | 投资者中央媒体 | 令人费解的谜团 | 科学与时代媒介 | 现代印度教