可调用的打字稿接口不可调用?

jak*_*451 7 typescript

我正在尝试编写ts-optchain. 功能将尝试返回具有拼接更改的根对象的副本。这样原件不会以任何方式改变或修改。然而,对于尚未修改的对象区域,它们作为引用(通过Object.assign(...))复制到浅复制操作中。

我试图验证的测试如下:

const example = { a: { b: { c: { d: 5 } } } };
const out = osc(example).a.b.c.d(6);
expect(out).to.be.deep.eq({ a: { b: { c: { d: 6 } } } });
Run Code Online (Sandbox Code Playgroud)

... 其中osc(可选设置链)是我对 mimmicopt-chainoc功能所做的功能。

我期待结果有点类似于 Object.assign({}, example, {a: Object.assign({}, example.a, {b: Object.assign({}, example.a.b, {c: Object.assign({}, example.a.b.c, {d: 6})})})});

上述方法编写、阅读和维护都很痛苦。因此,使这个功能的推理。

实施

我的尝试如下:

// ----- Types -----
// Generic type "R" -> The returned root object type when setting a value
// Generic type "T" -> The type for the proxy object
interface TSOSCDataSetter<R, T> {
  (value: Readonly<T>): Readonly<R>;
}

type TSOSCObjectWrapper<R, T> = { [K in keyof T]-?: TSOSCType<R, T[K]> };

interface TSOSCArrayWrapper<R, T> {
  length: TSOSCType<R, number>;

  [K: number]: TSOSCType<R, T>;
}

interface TSOSCAny<R> extends TSOSCDataSetter<R, any> {
  [K: string]: TSOSCAny<R>; // Enable deep traversal of arbitrary props
}

type TSOSCDataWrapper<R, T> =
  0 extends (1 & T) // Is T any? (/sf/ask/3494926641/#49928360)
    ? TSOSCAny<R>
    : T extends any[] // Is T array-like?
    ? TSOSCArrayWrapper<R, T[number]>
    : T extends object // Is T object-like?
      ? TSOSCObjectWrapper<R, T>
      : TSOSCDataSetter<R, T>;

export type TSOSCType<R, T> = TSOSCDataSetter<R, T> & TSOSCDataWrapper<R, T>;

// ----- Helper functions -----
function setter<K extends keyof V, V>(original: () => (Readonly<V> | undefined), key: K, value: Readonly<V[K]>): Readonly<V> {
  // Shallow copies this layer with the spliced in value specified. Works with both dictionaries and lists.
  return Object.assign(typeof key === "string" ? {} : [], original(), { [key]: value });
}

function getter<K extends keyof V, V>(object: Readonly<V> | undefined, key: K): Readonly<V[K]> | undefined {
  // Assists in optionally fetching down a continuous recursive chain of index-able objects (dictionaries & lists)
  return object === undefined ? object : object[key];
}

// ----- Internal recursive optional set chain function -----
function _osc<R, K extends keyof V, V>(root: Readonly<R> | undefined, get_chain: () => (Readonly<V> | undefined), set_chain: (v: Readonly<V>) => Readonly<R>): TSOSCType<R, V> {
  // `root` is passed in as an argument and never used. This is just to maintain the typing for <R>.
  // `get_chain` is a constructed recursive function that will return what the value of this object is at this node.
  // `set_chain` is a constructed recursive function that will assist in building and splicing in the specified value.

  return new Proxy(
    {} as TSOSCType<R, V>, // Blank object. I don't use `target`.
    {
      get: function (target, key: K): TSOSCType<R, V[K]> {
        const new_get_chain = (): (Readonly<V[K]> | undefined) => getter(get_chain(), key);
        const new_set_chain = (v: Readonly<V[K]>): Readonly<R> => set_chain(setter(get_chain, key, v));
        return _osc(root, new_get_chain, new_set_chain);
      },
      apply: function (target, thisArg, args: [Readonly<V>]): Readonly<R> {
        return set_chain(args[0]);
      }
    }
  );
}

// ----- Exposed optional set chain function -----
export function osc<R, K extends keyof R>(root: Readonly<R> | undefined): TSOSCType<R, R> {
  const set_chain = (value: Readonly<R>): Readonly<R> => value;
  return _osc(root, () => root, set_chain);
}
Run Code Online (Sandbox Code Playgroud)

问题

不幸的是,我收到错误:osc(...).a.b.c.d is not a function. 这就是我的困惑开始的地方。从osc(and _osc) 函数返回的是 type TSOSCType,它扩展了接口TSOSCDataSetter。接口TSOSCDataSetter指定继承接口的对象本身是可调用的:

interface TSOSCDataSetter<R, T> {
  (value: Readonly<T>): Readonly<R>;
}
Run Code Online (Sandbox Code Playgroud)

从两者中重新调整osc并且_oscProxy类型的TSOSCType(很像ts-optchain)。这个 Proxy 对象有助于构建链并完成对象链的类型。但对于这个问题更重要的是,实现apply方法:

apply: function (target, thisArg, args: [Readonly<V>]): Readonly<R> {
  return set_chain(args[0]);
}
Run Code Online (Sandbox Code Playgroud)

那么为什么该类型TSOSCType不可调用呢?

Mad*_*iha 9

发生这种情况的原因是因为只能调用函数周围的代理(并设置apply陷阱)。你的演员{} as TSOSCType<R, V>掩盖了你正在做的事情在运行时是不可能的事实,并指示 TypeScript 信任你(错误地)。

更改该语句以function(){} as unknown as TSOSCType<R, V>使其按预期工作。

在 Playground 中看到这个

作为一般经验法则,每当您在使用 TypeScript 时遇到运行时 TypeError,就意味着 TypeScript 信任您,而您背叛了它。这几乎总是意味着演员。当您遇到此类错误时,您的演员表应该是最直接的嫌疑人。

  • “TypeScript 信任了你,而你背叛了它”——为什么我感到如此内疚?:D (7认同)