We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
/** 使用解构,通过浅拷贝更新对象属性 */ function objectSet< Obj extends Record<string, unknown>, Key extends keyof Obj, Val extends Obj[Key] >(obj: Obj, key: Key, value: Val): Obj { return { ...obj, [key]: value, }; } const testObj = { a: 1, b: 2 }; const testObj2 = objectSet(testObj, "b", 0); /** 通过回调函数,以浅拷贝的方式更新对象属性 */ function update< Obj extends Record<string, unknown>, Key extends keyof Obj, Modify extends (v: Obj[Key]) => Obj[Key] >(obj: Obj, key: Key, modify: Modify): Obj { const value = obj[key]; const newValue = modify(value); const newObj = objectSet(obj, key, newValue); return newObj; } const testObj3 = update(testObj, "b", (v) => v + 666); /** 借助递归,针对任意嵌套的 keys,通过浅拷贝的方式更新,不推荐直接使用 */ function _nestedUpdate< Obj extends Record<string, unknown>, Keys extends string[], Modify extends (v: unknown) => unknown >(obj: Obj, keys: Keys, modify: Modify): Obj { if (keys.length === 0) return modify(obj) as Obj; const key1 = keys[0]; const [, ...restOfKeys] = keys; return update( obj as Record<string, unknown>, key1 as string, function (value1) { return _nestedUpdate( value1 as Record<string, unknown>, restOfKeys, modify ); } as Modify ) as Obj; } type GetKeyWhichValueIsRecord<T, K extends keyof T> = K extends keyof T ? Exclude<T[K], undefined | null> extends Record<string, unknown> | unknown[] ? K : never : never; type OneLevelPathOf< T, Hint = GetKeyWhichValueIsRecord<T, keyof T> & string > = T extends unknown[] ? 0 : never extends Hint ? keyof T & string : Hint; type PathForHint<T> = OneLevelPathOf<Exclude<T, undefined | null>>; type HintPrifix = "🤨🤨 路径有误,我猜了一些你可能想输入的路径"; type PathOf< MaybeObject, K extends string, P extends string = "", RealObject = Exclude<MaybeObject, undefined | null> > = K extends `${infer U}.${infer V}` ? U extends keyof RealObject // Record ? PathOf<RealObject[U], V, `${P}${U}.`> : RealObject extends unknown[] // Array ? // 在嵌套更新场景,我们选择不支持数组路径 never : HintPrifix | `${P}${PathForHint<RealObject>}` // just for hint : K extends keyof RealObject ? `${P}${K}` : K extends `${number}` ? // 在嵌套更新场景,我们选择不支持数组路径 never : HintPrifix | `${P}${PathForHint<RealObject>}`; // just for hint type SplitTemplateStringTypeToTuple< T > = T extends `${infer First}.${infer Rest}` ? First extends `${number}` ? [number, ...SplitTemplateStringTypeToTuple<Rest>] : [First, ...SplitTemplateStringTypeToTuple<Rest>] : T extends `${number}` ? [number] : [T]; type StringableKey<T> = T extends readonly unknown[] ? // 在嵌套更新场景,我们选择不支持数组路径 never : string; type AllPathsOf<T> = T extends object ? { [P in keyof T & StringableKey<T>]: `${P}` | `${P}.${AllPathsOf<T[P]>}`; }[keyof T & StringableKey<T>] : never; type GetDeepType< MaybeDeepObj, // Path extends PathOf<MaybeDeepObj, AllPathsOf<MaybeDeepObj>> Path extends AllPathsOf<MaybeDeepObj>, RealPath = PathOf<MaybeDeepObj, Path> > = HintPrifix extends RealPath ? RealPath : DeepPick<MaybeDeepObj, SplitTemplateStringTypeToTuple<RealPath>>; type IsNullable<T> = undefined extends T ? true : null extends T ? true : false; // 在 objectPureUpdate 场景下,路径中只要遇到 null 或 undefined,最终结果都会包含 undefined type DeepPick< MaybeDeepObj, Path extends unknown[], Nullable = IsNullable<MaybeDeepObj>, RealDeepObj = Exclude<MaybeDeepObj, undefined | null> > = Path extends [infer First, ...infer Rest] ? First extends keyof RealDeepObj ? Nullable extends true ? DeepPick<RealDeepObj[First], Rest, true> : DeepPick<RealDeepObj[First], Rest> : never : Nullable extends true ? MaybeDeepObj | undefined : MaybeDeepObj; /** - 可根据 obj 的类型自动补全 path * - 可自动根据 path 限定 modify 函数的入参和及返回值类型 */ function nestedUpdate< Obj extends Record<string, unknown>, Path extends AllPathsOf<Obj>, Modify extends (v: GetDeepType<Obj, Path>) => GetDeepType<Obj, Path> >(obj: Obj, path: PathOf<Obj, Path>, modify: Modify): Obj { const keys = path.split("."); return _nestedUpdate(obj, keys, modify as any); } type NullPart<T> = null | undefined extends T ? null | undefined : undefined extends T ? undefined : null extends T ? null : never type ExactShadowTransform<T, K extends keyof NonNullable<T>, Transform> = (Omit<NonNullable<T>, K> & Record<K, Transform>) | NullPart<T> /** 将一个嵌套类型的某一部分换成其它类型 */ type DeepTransform<Obj, Path, TransformReturn> = Path extends keyof Obj ? ExactShadowTransform<Obj, Path, TransformReturn> : { [Key in keyof Obj]: Path extends `${infer First}.${infer Rest}` ? First extends `${number}` ? never : First extends Key ? Key extends First ? Rest extends keyof NonNullable<Obj[Key]> ? ExactShadowTransform<Obj[Key], Rest, TransformReturn> : DeepTransform<Obj[Key], Rest, TransformReturn> : Obj[Key] : Obj[Key] : never }; type GoodDeepTransform<Obj, Path extends AllPathsOf<Obj>, TransformReturn> = DeepTransform<Obj, Path, TransformReturn> /** - 可根据 obj 的类型自动补全 path * - 可自动根据 path 限定 modify 函数的入参类型 * - 可自动根据 obj, path, 以及 modify 的返回值类型推断 nestedTransform 本身的返回值类型*/ function nestedTransform< Obj extends Record<string, unknown>, Path extends AllPathsOf<Obj>, Transform extends (v: GetDeepType<Obj, Path>) => unknown >( obj: Obj, path: PathOf<Obj, Path>, transform: Transform ): DeepTransform<Obj, Path, ReturnType<Transform>> { const keys = path.split("."); return _nestedUpdate(obj, keys, transform as any) as any; } const nestedObj = { a0: { a10: 2, a11: { a20: 3, a21: { a30: 4, a31: { a40: "", a41: "", }, }, }, }, b0: { b10: "2", b11: { b20: "3", b21: { b30: "4", }, }, }, c0: [{ d0: 1 }, { d1: "2" }], }; /** 根据路径更新的例子之一:更新前后类型保持不变 * - path 参数能得到友好的自动补全和类型错误提示 * - modify 是个函数,函数入参是路径对应的值,返回值会被更新 * - update 场景下, modify 的返回值类型需要与 nestedObj path 下的类型保持一致 */ const testNestedUpdate = nestedUpdate( nestedObj, "b0.b11.b20", (v) => v + v + v ); /** 根据路径更新的例子之二:更新后类型可以发生相应变化 * - path 参数能得到友好的自动补全和类型错误提示 * - modify 是个函数,函数入参是路径对应的值,返回值会被更新 * - transform 场景下, modify 的返回值类型会直接影响到 nestedTransform 的返回值类型 */ const testNestedTransform = nestedTransform(nestedObj, "b0.b11.b20", (v) => ({ b666: v + v + v, })); console.log(testNestedUpdate); console.log(testNestedTransform); type UnionToTuple<T> = ( ( ( T extends any ? (t: T) => T : never ) extends infer U ? (U extends any ? (u: U) => any : never ) extends (v: infer V) => any ? V : never : never ) extends (_: any) => infer W ? [...UnionToTuple<Exclude<T, W>>, W] : [] ); type SingleWithDeepRequired<T, Path> = DeepTransform<T, Path, NonNullable<DeepPick<T, SplitTemplateStringTypeToTuple<Path>>>> type WithDeepRequiredHelper<T, TuplePaths> = TuplePaths extends [infer First, ... infer Rest] ? WithDeepRequiredHelper<SingleWithDeepRequired<T, First>, Rest> : T type WithDeepRequired<T, Path extends AllPathsOf<T>> = WithDeepRequiredHelper<T, UnionToTuple<Path>> type QueryType = { a?: {c0?: string | null; c1?: {c2?: string | null} | null} | null } type NewQuery = WithDeepRequired<QueryType, 'a.c1.c2' | 'a.c0' | 'a'> const jkjk: NewQuery = { a: { c0: '', c1: {c2: ''} } } // 添加转换类型的工具 type _PipeDeepTransform<Obj, Path1 = never, T1 = never, Path2 = never, T2 = never, Path3 = never, T3 = never, Path4 = never, T4 = never, Path5 = never, T5 = never, Path6 = never, T6 = never, Path7 = never, T7 = never> = // never 的判断需要特殊处理 [Path1] extends [never] ? Obj : _PipeDeepTransform<DeepTransform<Obj, Path1, T1>, Path2, T2, Path3, T3, Path4, T4, Path5, T5, Path6, T6, Path7, T7> type PipeDeepTransform<Obj, Path1 extends AllPathsOf<Obj>, T1 = never, Path2 extends AllPathsOf<Obj> = never, T2 = never, Path3 extends AllPathsOf<Obj> = never, T3 = never, Path4 extends AllPathsOf<Obj> = never, T4 = never, Path5 extends AllPathsOf<Obj> = never, T5 = never, Path6 extends AllPathsOf<Obj> = never, T6 = never, Path7 extends AllPathsOf<Obj> = never, T7 = never> = _PipeDeepTransform<Obj, Path1, T1, Path2, T2, Path3, T3, Path4, T4, Path5, T5, Path6, T6, Path7, T7> type TransF = PipeDeepTransform<QueryType, 'a.c1', number, 'a.c0', {cc: string}> const aaa: TransF = { a: { c1: 22, c0: { cc: '33' } } }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
The text was updated successfully, but these errors were encountered: