【问题标题】:Define Types for recursive functions in typescript在打字稿中定义递归函数的类型
【发布时间】:2021-12-15 00:52:36
【问题描述】:

我已经在 J​​S 中定义了一个函数,它一直使用函数(params => 字符串),直到它接收到一个对象,然后它将最终的字符串解析为对最后传递的同一对象的函数调用的串联

这里是函数

export function prefixer(func1?: any) {

    function identity(x: any) { return x; }

    function curriedPrefixer(fns: any[]) {

        return function next(param: any) {

            if (typeof param == 'function') {
                return curriedPrefixer(
                    fns.concat(param)
                );
            }

            if (typeof param == 'object')
                return fns.reduce(
                    (prev, next) => prev + next(param), ''
                );

            return undefined;
        }
    }

    return curriedPrefixer([func1 || identity])
}

我的问题是为它的参数和返回类型定义正确的类型,所以这个函数的用户可以传递一个泛型类型(帮助函数知道最终参数对象的类型是什么)并且可以帮助用户重新- 一遍又一遍地调用该函数的输出。

这是一个测试用例中的函数:

test('should auto prefix', () => {

    let prefix1: any = prefixer((params: any) => `https://wwww.${params.domain}.com`)
    let prefix2: any = prefix1(() => '/path/to/item')
    let prefix3: any = prefix2((params: any) => `/${params.itemId}`)

    let params = {
        domain: 'google',
        itemId: '5444'
    }

    let resolvedString1 = prefix1(params);
    let resolvedString2 = prefix2(params);
    let resolvedString3 = prefix3(params);

    let trueResult1 = `https://wwww.${params.domain}.com`
    let trueResult2 = `https://wwww.${params.domain}.com/path/to/item`
    let trueResult3 = `https://wwww.${params.domain}.com/path/to/item/${params.itemId}`

    expect(resolvedString1).toEqual(trueResult1);
    expect(resolvedString2).toEqual(trueResult2);
    expect(resolvedString3).toEqual(trueResult3);
});

我尝试了一些无意义的想法,但没有得到任何接近,也没有找到关于打字稿中递归函数的帮助答案。

这是我尝试过但没有解决类型定义的方法

export function prefixer<T>(func1?: any) {

    function identity(x: any) { return x; }

    function curriedPrefixer<M>(fns: any[]) {

        return function next<S>(param: S | M | ((p: S | M) => any)) {

            if (typeof param == 'function') {
                return curriedPrefixer(
                    fns.concat(param)
                );
            }

            if (typeof param == 'object')
                return fns.reduce(
                    (prev, next) => prev + next(param), ''
                );

            return undefined;
        }
    }

    return curriedPrefixer<T>([func1 || identity])
}

// I still have to pass (p: any)...
let prefix1 = prefixer<{ domain: string }>((p: any) => `https://wwww.${p.domain}.com`)
let prefix2 = prefix1<{ itemId: string }>((p: any) => `https://wwww.${p.itemId}.com`)

【问题讨论】:

  • a) 不要编写这样的函数,它不适用于期望函数作为参数的函数 b) 您需要使用函数重载来区分对象和函数输入

标签: typescript functional-programming


【解决方案1】:

键入 this 的第一步是编写此前缀的类型签名而不编写实现。它应该看起来像这样:

// Utility types to make definitions below shorter
type Pojo = Record<string, unknown>
type EmptyPojo = Record<never, unknown>

// TData represents the object that is expected by all the previous 
// prefixer functions that you've already passed to this prefixer
interface CurriedPrefixer<TData extends Pojo> {
  // First overload: when you pass a function to curried prefixer
  // T represents fields that a prefixer function you now pass
  // to curried prefixer expects
  <T extends Pojo = EmptyPojo>(
    // You could change type of `data` to `TData & T` instead of `T`
    // if you want your prefixer function to have access to data that 
    // all previous prefixer function use, but this is less safe, I would 
    // recommend explicitly typing every iteration
    prefixerFn: (data: T) => string
  ): CurriedPrefixer<TData & T>

  // Second overload: when you pass an object
  (data: TData): string
}

您现在可以再次验证此类型签名是否按预期工作,而无需考虑目前的实现:see sandbox

现在唯一剩下的就是使实现适合这种类型签名。它可能看起来像这样:sandbox

【讨论】:

  • 效果很好,非常感谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-10
  • 1970-01-01
  • 2019-04-09
相关资源
最近更新 更多