TypeScript类型级别与值级别编程详解及实战挑战

作者:函数茶馆

时间:2026-10-27

浏览:367

本文详细解析了TypeScript中值级别与类型级别编程的区别。值级代码负责运行时逻辑,而类型级代码用于编译期静态检查。文章介绍了类型级TypeScript作为纯函数语言的核心特性,包括泛型、条件分支、递归等,并通过具体的代码挑战(如实现泛型函数、安全头部提取等)帮助读者掌握类型级编程技巧。

文章目录

区分值级别编程与类型级别编程

首先,我们需要明确值级别编程和类型级别编程之间的关键区别。

  • 值级别编程旨在编写实际在生产环境中运行的代码,即在运行时执行,并为用户提供实际的功能价值。
  • 类型级别编程则致力于在代码发布前(即编译阶段)确保代码不包含错误,这些类型检查在运行时会被完全移除,不产生任何运行时代价。

由于JavaScript本身没有类型系统,因此所有的JavaScript代码都属于值级别编程的范畴:

// A simple Javascript function:
function sum(a, b) {
  return a + b;
}

TypeScript允许我们在JavaScript代码中添加类型注释,从而确保我们编写的sum函数只能被数字类型的参数调用,而不会被其他类型的参数错误调用:

// Using type annotations:
function sum(a: number, b: number): number {
  return a + b;
}

然而,TypeScript的类型系统远比简单的类型注释强大得多。在实际开发中,我们编写的代码有时需要具备通用性,能够接受我们在编写代码时并不知晓具体类型的参数。

在这种情况下,我们可以在尖括号<A, B, ...>中定义类型参数。随后,我们可以将这些类型参数传递给一个类型级函数,该函数会根据输入的类型计算出输出的类型:

// Using type level programming:
function genericFunction<A, B>(a: A, b: B): DoSomething<A, B> {
  return doSomething(a, b);
}

这就是类型级编程的核心概念!DoSomething<A,B> 是一种使用专门的语言编写的类型级函数,它与用于处理值的语言不同,但具备同样强大的表达能力。我们可以将这种专门用于类型的语言称为“类型级TypeScript”。

// This is a type-level function:
type DoSomething<A, B> = ...
// This is a value-level function:
const doSomething = (a, b) => ...

类型级编程基础

类型级TypeScript本质上是一种极简的纯函数式语言。

在类型层面,函数被称为“泛型类型”:它们接受一个或多个类型参数,并返回单个输出类型。下面是一个简单的函数示例,它接收两个类型参数并将它们包装在一个元组中:

type SomeFunction<A, B> = [A, B];
/*                ----    ------
                   ^         \
                  type        return type
               parameters
     \-------------------------/
                 ^
              Generic
*/

类型级别的TypeScript功能相对精简,毕竟它的核心设计目的是为你的代码提供类型约束。尽管如此,它确实拥有足够的特性来实现(几乎)图灵完备,这意味着你可以利用它解决任意复杂度的问题。

  • 代码分支:根据条件执行不同的代码路径(这相当于值级别中的if/else关键字)。
  • 变量赋值:声明一个变量并在表达式中使用它(这相当于值级别中的var/let关键字)。
  • 函数:可复用的逻辑单元,如我们在前面的示例中看到的泛型定义。
  • 循环:通常通过递归的方式来实现迭代逻辑。
  • 相等检查:使用==符号,但这里的比较对象是类型而非值。
  • 此外还有更多特性等待探索!

以上就是我们将要在后续章节中学习的类型级语言的简要概述。现在,让我们开始第一次实战挑战吧!

挑战机制解析

在每一章的末尾,你将面临一些需要解决的挑战,旨在将新学到的技能付诸实践。这些挑战的代码结构如下所示:

namespace challenge {
  // 1. implement a generic to get the union
  // of all keys in an object type.
  type GetAllKeys<Obj> = TODO;
  type res1 = GetAllKeys<{ a: number }>;
  type test1 = Expect<Equal<res1, "a">>;
}
  • namespace 是一个鲜为人知的TypeScript特性,它允许我们在专用的命名空间范围内隔离每个挑战,避免污染全局类型环境。
  • TODO 是占位符代码。这是你需要替换为实际实现的部分!
  • res1=。。。 是泛型针对某些输入类型所返回的具体类型。你可以将鼠标悬停在此处以检查其当前的推导结果。
  • type test1=Expect<Equal<res1,…>> 是类型级的单元测试。它用于判断TODO部分的代码实现是否正确符合预期。

在开始挑战之前,你需要先定义好Expect和Equal这两个辅助类型工具。

type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends { [k in keyof X]: X[k]; } ? 1 : 2) extends <T>() => T extends { [k in keyof Y]: Y[k]; } ? 1 : 2 ? true : false;

实战挑战

准备好迎接你的第一个挑战了吗?让我们开始:

/**
 * 1. The `identity` function takes a value of any type
 *    and returns it. Make it generic!
 */
namespace genericFunction {
  function identity(a: TODO): TODO {
    return a;
  }
  let input1 = 10;
  let res1 = identity(input1);
  type test1 = Expect<Equal<typeof res1, number>>;
  let input2 = "Hello";
  let res2 = identity(input2);
  type test2 = Expect<Equal<typeof res2, string>>;
}
/**
 * 2. `safeHead` takes an array, a default value
      and returns the first element of the array
      if it isn't empty. Make it generic!
 */
namespace safeHead {
  function safeHead(array: TODO[], defaultValue: TODO): TODO {
    return array[0] ?? defaultValue;
  }
  let input1 = [1, 2, 3];
  let res1 = safeHead(input1, 0);
  type test1 = Expect<Equal<typeof res1, number>>;
  let input2 = ["Hello", "Hola", "Bonjour"];
  let res2 = safeHead(input2, "Hi");
  type test2 = Expect<Equal<typeof res2, string>>;
}
/**
 * 3. `map` transforms all values in an array to a value of
 *    different type. Make it generic!
 */
namespace map {
  function map(array: TODO[], fn: (value: TODO) => TODO): TODO[] {
    return array.map(fn);
  }
  let input1 = [1, 2, 3];
  let res1 = map(input1, value => value.toString());
  type test1 = Expect<Equal<typeof res1, string[]>>;
  let input2 = ["Hello", "Hola", "Bonjour"];
  let res2 = map(input2, str => str.length);
  type test2 = Expect<Equal<typeof res2, number[]>>;
}
/**
 * 4. `pipe2` takes a value and pipes it into 2 functions
 *    sequentially. For example, `pipe2(x, f1, f2)` will
 *    result in `f2(f1(x))`. Make it generic!
 *    
 */
namespace pipe2 {
  function pipe2(
    x: TODO,
    f1: (value: TODO) => TODO,
    f2: (value: TODO) => TODO
  ): TODO {
    return f2(f1(x));
  }
  let res1 = pipe2(
    [1, 2, 3],
    arr => arr.length,
    length => `length: ${length}`
  );
  type test1 = Expect<Equal<typeof res1, string>>;
  let res2 = pipe2(
    { name: 'Alice' },
    user => user.name,
    name => name.length > 5
  );
  type test2 = Expect<Equal<typeof res2, boolean>>;
}

以上就是TypeScript类型级别和值级别示例详解的详细内容,更多关于TypeScript类型级别值级别的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章