Typescript 字符串模板的类型编程实践

字符串模板是 TypeScript 4.1 新增的特性,可以对字符串进行更多的限制,下面通过简单介绍它的来源,进而来解决实际开发当中的问题。

背景知识


在 TypeScript 中,字符串可以是类型比如:

type name = 'Lucy';

const username: name = 'LI Lei';
// Type '"LI Lei"' is not assignable to type '"Lucy"'.

TypeScript 4.1 中,带来了 Template Literal Types,它可以用来限制模板字符串,比如:

type World = "world";
 
type Greeting = `hello ${World}`;
// type Greeting = "hello world"

实际需求


现在有个实际需求,限制一个请求参数的类型,它的值可能是

"contains(extension_number,'变量')"
"contains(extension_number,'变量') or contains(display_name,'变量')"
"contains(extension_number,'变量') or contains(display_name,'变量') or contains(name,'变量')"

解决方案


观察三个字符串都有相同的结构单元contains(字段,'变量'),那么首先来实现这个单元

// 为了扩展性,泛型 T 为字段名称
type FilterSearch<T> = `contains(${T},'${string}')`;

观察这个字段可以进行枚举,直接使用字符串类型来实现

// 用所需字段来限制泛型T
type Field= `extension_number` | `display_name` | 'name';
type FilterSearch<T extends Field> = `contains(${T},'${string}')`;

此时需求中第一个字符串可以通过以上类型进行限制了

type Field= `extension_number` | `display_name` | 'name';
type FilterSearch<T extends Field> = `contains(${T},'${string}')`;

const extSearch: FilterSearch<'extension_number'> = `contains(extension_number,'${string}')`;

然后就是实现or语句,使其变为可选的

...
type FuzzySearch<F extends Field, S extends Field, T extends Field> =
    F extends Field ?
      S extends Field ?
        T extends Field ?
          `${FilterSearch<F>} or ${FilterSearch<S>} or ${FilterSearch<T>}`
        : `${FilterSearch<F>} or ${FilterSearch<S>}`
      : FilterSearch<F>
    : never

以上类型实现了以下作用:

const key: string = `搜索值`;
const params: FuzzySearch<'extension_number', `display_name`, 'name'> = 
`contains(extension_number,'${key}') or contains(display_name,'${key}') or contains(name,'${key}')`;

再眼看需求,观察到除了extension_number以外,其他的字段都是可选的,为了方便使用,增加泛型的默认值:

...
type FuzzySearch<F extends Field = 'extension_number', S extends Field | '' = '', T extends Field | '' = ''> =
    F extends Field ?
    S extends Field ?
      T extends Field ?
          `${FilterSearch<F>} or ${FilterSearch<S>} or ${FilterSearch<T>}`
        : `${FilterSearch<F>} or ${FilterSearch<S>}`
      : FilterSearch<F>
    : never

在使用时可以根据需要来传入查询的字段:

const params: FuzzySearch<'extension_number', `display_name`, 'name'> = 
`contains(extension_number,'${key}') or contains(display_name,'${key}') or contains(name,'${key}')`;

const params1: FuzzySearch = `contains(extension_number,'${key}')`;

const params2: FuzzySearch<'extension_number', `name`> = 
`contains(extension_number,'${key}') or contains(name,'${key}')')`;

需注意的问题


由于我们在字符串模板中定义了变量,所以一下这种形式的代码也可以通过类型检查:

const params1: FuzzySearch<'extension_number'> = `contains(extension_number,'101') or contains(name,'101')`;
                                                                             ⬆ 此段字符会被认为字符串变量 ⬆

但是以下的类型检查依然是无法通过的:

const params2: FuzzySearch<'extension_number','display_name'> = 
`contains(extension_number,'${key}') or contains(name,'${key}')')`;
// Type '`contains(extension_number,'${string}') or contains(name,'${string}')')`' is not assignable to type '`contains(extension_number,'${string}') or contains(display_name,'${string}')`'.

也就是说对于该类型,只要传了第二或者第三泛型参数,类型限制将会完全如意料中的生效;
如果只传了第一个泛型参数或者不传,将没办法完全限制类型:因为字符串内可以是任意字符

总结


通过泛型、extends、默认参数限制了一个字符串模板的形状。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容