我正在尝试编写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 } } } });
...其中qazxsw poi(可选集链)是我模仿osc
的qazxsw poi函数的函数。
我期待结果有点类似于opt-chain
上面的方法是写,读和维护的痛苦。因此推理这个功能。
我试图做到这一点如下:
oc
不幸的是,我得到了错误: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? (https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#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);
}
(和osc(...).a.b.c.d is not a function
)函数返回的是osc
类型,它扩展了接口_osc
。接口TSOSCType
指定继承接口的对象本身是可调用的:
TSOSCDataSetter
从TSOSCDataSetter
和interface TSOSCDataSetter<R, T> {
(value: Readonly<T>): Readonly<R>;
}
返回的是osc
类型的_osc
(很像Proxy
)。此Proxy对象有助于构建完成对象链的链和类型。但更重要的是,对于这个问题,实现TSOSCType
方法:
ts-optchain
那么为什么类型apply
不可调用?
发生这种情况的原因是因为只能调用函数周围的代理(并设置apply: function (target, thisArg, args: [Readonly<V>]): Readonly<R> {
return set_chain(args[0]);
}
陷阱)。你的TSOSCType
演员掩盖了这样一个事实:你正在做的事情在运行时是不可能的,并指示TypeScript信任你(错误地)。
将该语句更改为apply
使其按预期工作。
{} as TSOSCType<R, V>
。
作为一般经验法则,每当您在使用TypeScript时获得运行时TypeError时,这意味着TypeScript信任您并且您背叛了它。这几乎总是意味着一个演员。当你遇到这样的错误时,你的演员应该是直接的嫌疑人。