打字稿中的可变类型?

阿明·罗斯塔

我希望我的函数memoize(fn)返回与fn.

我有一个丑陋的解决方案,如下所示:
编写可变参数泛型的正确方法是什么?

export const memoize = <FN>(fn: FN) : FN => {
  const cache = { };
  const run : any = (...args) => {
    const key = JSON.stringify(args);
    if(!cache[key]) {
      cache[key] = (fn as any)(...args).catch(up => {
        delete cache[key];
        throw up;
      });
    }
    return cache[key];
  };
  return run as FN;
}

const get = memoize((url: string) => fetch(url, {method: 'GET'}));
杰卡兹

不幸的是,目前在 Type Script 中不支持可变参数类型。幸运的是,您可能不需要它。

函数有两个方面:“外部”,即调用者使用的签名,以及“内部”,即实现。理想情况下,您希望签名将外部调用者限制在函数的安全使用范围内,同时您希望 TypeScript 确保函数的内部实现也是安全的。我们先来看看外面:

看起来您想要memoize使用任意数量的任意类型参数的函数来返回 Promise(对吗?),并且您希望它返回相同类型的函数。您现有的签名<FN>(fn: FN) : FN获得“返回相同类型”部分,但不执行任何其他操作。因此,例如,没有什么可以阻止调用者这样做:

const bad = memoize((x: string)=>x+"!"); // runtime explosion, no .catch()
const veryBad = memoize("whoops"); // runtime explosion, not a function

这是一个只允许输入正确类型的函数的签名:

export const memoize = <FN extends (...args: any[]) => Promise<{}>>(fn: FN): FN => { 
  // ... same implementation 
}

现在来电者会很高兴:

const get = memoize((url: string) => fetch(url, { method: 'GET' })); // okay
const getBody = memoize((url: string, body: any) => fetch(url, { method: 'GET', body: body })); // okay
const bad = memoize((x: string) => (x + "!")); // error: string is not a Promise
const veryBad = memoize("whoops"); // error: "whoops" is not a function

这留下了内部:实施安全。现在,您依赖于断言和断言any(其中隐含了一些any)。现在 TypeScript 知道fn返回 a Promise,您可以放宽其中一些断言:

export const memoize = <FN extends (...args: any[]) => Promise<{}>>(fn: FN): FN => {
  const cache: { [k: string]: Promise<{}> } = {}; // holds promises
  const run = (...args: any[]) => {
    const key = JSON.stringify(args);
    if (!cache[key]) {
      // fn doesn't have to be any to typecheck
      cache[key] = fn(...args).catch(up => { 
        delete cache[key];
        throw up;
      });
    }
    return cache[key];
  };
  return run as FN;
}

您还需要断言run的类型FN,因为所有的打字稿知道的是,FN是的类型的子类型run,而不是它类型run这样做有一个很好的理由:您可以传入一个具有额外属性的函数,并且您做出了无根据的断言,即您还将返回额外的属性:

const crazyFunction = Object.assign((url: string) => fetch(url, { method: 'GET' }), { color: 'purple' });
crazyFunction('blah');
console.log(typeof crazyFunction.color); // string
const whoops = memoize(crazyFunction);
console.log(typeof whoops.color); //TS says string, but is undefined!!

我猜你不关心那些在调用之前开始对他们的函数做奇怪事情的人会发生什么memoize尤其是因为那个人可能就是你,而你知道你不会那样做。所以这对你来说可能已经足够了。


如果你真的想让实现和调用签名真正安全,你可能会发现你需要可变参数类型,而我们在 TypeScript 中没有。您可以通过接受最多包含一些大但有限数量的参数的函数来伪造它,比如 9:

type Func<R, A1, A2, A3, A4, A5, A6, A7, A8, A9> = (a1: A1, a2?: A2, a3?: A3, a4?: A4, a5?: A5, a6?: A6, a7?: A7, a8?: A8, a9?: A9) => R;
export const memoize = <R, A1=never, A2=never, A3=never, A4=never, A5=never, A6=never, A7=never, A8=never, A9=never>(fn: Func<Promise<R>, A1, A2, A3, A4, A5, A6, A7, A8, A9>): Func<Promise<R>, A1, A2, A3, A4, A5, A6, A7, A8, A9> => {
  const cache: { [k: string]: Promise<R> } = {};
  const run : Func<Promise<R>, A1, A2, A3, A4, A5, A6, A7, A8, A9> = (a1,a2,a3,a4,a5,a6,a7,a8,a9) => {
    const key = JSON.stringify([a1,a2,a3,a4,a5,a6,a7,a8,a9]);
    if (!cache[key]) {
      cache[key] = fn(a1,a2,a3,a4,a5,a6,a7,a8,a9).catch(up => {
        delete cache[key];
        throw up;
      });
    }
    return cache[key];
  };
  return run;
}
const get = memoize((url: string) => fetch(url, { method: 'GET' })); // okay
get('hello') // okay
get('hello', 2); // error, 2 is not assignable to undefined

但这对你来说可能有点矫枉过正。


希望有帮助。祝你好运!

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章