npm i merge-anything
递归合并对象和其他类型。完全支持TypeScript !一个简单且小型的集成。
我创建这个包是因为我尝试了很多类似的包,这些包可以进行合并/深度合并/递归对象分配等。但是它们都有其怪癖,并且它们都破坏了它们不应该破坏的东西......?
我正在寻找:
Object.assign()
但很深入最后这一点至关重要!当然,在 JavaScript 中几乎所有东西都是对象,但我不希望合并函数尝试合并,例如。两个new Date()
实例!许多库使用自定义类来创建具有特殊原型的对象,而这些对象在尝试合并它们时都会崩溃。所以我们一定要小心!
merge-anything 将合并对象和嵌套属性,但前提是它们是“普通对象”。一旦子 prop 不是“普通对象”并且具有特殊原型,它就会“按原样”复制该实例。 ♻️
import { merge } from 'merge-anything'
const starter = { name : 'Squirtle' , types : { water : true } }
const newValues = { name : 'Wartortle' , types : { fighting : true } , level : 16 }
const evolution = merge ( starter , newValues , { is : 'cool' } )
// returns {
// name: 'Wartortle',
// types: { water: true, fighting: true },
// level: 16,
// is: 'cool'
// }
在上面的示例中,如果您使用 TypeScript,并将鼠标悬停在evolution
上,您实际上可以立即看到新对象的类型。这非常强大,因为您可以合并事物,并且不需要any
,TypeScript 就会准确地知道您新合并的对象的外观!
merge()
函数的返回类型也可用作 TypeScript 实用程序:
import type { Merge } from 'merge-anything'
type A1 = { name : string }
type A2 = { types : { water : boolean } }
type A3 = { types : { fighting : boolean } }
type Result = Merge < A1 , [ A2 , A3 ] >
该包将递归地遍历普通对象并将值合并到新对象上。
请注意,该包可以识别特殊的 JavaScript 对象,例如类实例。在这种情况下,它不会像对象一样递归地合并它们,而是“按原样”将类分配给新对象!
// all passed objects do not get modified
const a = { a : 'a' }
const b = { b : 'b' }
const c = { c : 'c' }
const result = merge ( a , b , c )
// a === {a: 'a'}
// b === {b: 'b'}
// c === {c: 'c'}
// result === {a: 'a', b: 'b', c: 'c'}
// However, be careful with JavaScript object references with nested props. See below: A note on JavaScript object references
// arrays get overwritten
// (for "concat" logic, see Extensions below)
merge ( { array : [ 'a' ] } , { array : [ 'b' ] } ) // returns {array: ['b']}
// empty objects merge into objects
merge ( { obj : { prop : 'a' } } , { obj : { } } ) // returns {obj: {prop: 'a'}}
// but non-objects overwrite objects
merge ( { obj : { prop : 'a' } } , { obj : null } ) // returns {obj: null}
// and empty objects overwrite non-objects
merge ( { prop : 'a' } , { prop : { } } ) // returns {prop: {}}
merge-anything 正确地保持特殊对象的完整性,如日期、正则表达式、函数、类实例等。
然而,了解如何解决 JavaScript 对象引用非常重要。请务必阅读下面关于 JavaScript 对象引用的#a note。
默认行为是覆盖数组。如果需要连接数组,可以导入mergeAndConcat
。但如果您不需要这个,请不要担心,这个库是可摇树的,并且不会导入您不使用的代码!
import { mergeAndConcat } from 'merge-anything'
mergeAndConcat (
{ nested : { prop : { array : [ 'a' ] } } } ,
{ nested : { prop : { array : [ 'b' ] } } }
)
// returns { nested: { prop: { array: ['a', 'b'] } } },
当两个事物合并时,有时您可能需要调整逻辑。您可以提供自己的自定义函数,每次覆盖值时都会触发该函数。
对于本例,我们使用mergeAndCompare
。下面是一个使用比较函数连接字符串的示例:
import { mergeAndCompare } from 'merge-anything'
function concatStrings ( originVal , newVal , key ) {
if ( typeof originVal === 'string' && typeof newVal === 'string' ) {
// concat logic
return ` ${ originVal } ${ newVal } `
}
// always return newVal as fallback!!
return newVal
}
mergeAndCompare ( concatStrings , { name : 'John' } , { name : 'Simth' } )
// returns { name: 'JohnSmith' }
TypeScript 用户请注意。此函数返回的类型可能不正确。在这种情况下,您必须将结果转换为您自己提供的接口
请小心 JavaScript 对象引用。任何嵌套的属性都将是反应性的,并在原始对象和合并对象之间链接!下面我们将展示如何防止这种情况。
const original = { airport : { status : 'dep. ?' } }
const extraInfo = { airport : { location : 'Brussels' } }
const merged = merge ( original , extraInfo )
// we change the status from departuring ? to landing ?
merged . airport . status = 'lan. ?'
// the `merged` value will be modified
// merged.airport.status === 'lan. ?'
// However `original` value will also be modified!!
// original.airport.status === 'lan. ?'
要记住的关键规则是:
任何嵌套超过 1 层且没有重叠父属性的属性都将在合并结果和源中进行响应和链接
然而,有一个非常简单的解决方案。我们可以复制合并结果来消除任何反应性。为此,我们可以使用复制任何内容库。该库还确保特殊类实例不会损坏,因此您可以使用它而不必担心损坏东西!
请参阅下面我们如何集成“复制任何内容”:
import { copy } from 'copy-anything'
const original = { airport : { status : 'dep. ?' } }
const extraInfo = { airport : { location : 'Brussels' } }
const merged = copy ( merge ( original , extraInfo ) )
// we change the status from departuring ? to landing ?
merged . airport . status = 'lan. ?' ( merged . airport . status === 'lan. ?' ) (
// true
// `original` won't be modified!
original . airport . status === 'dep. ?'
) // true
然后,您可以尝试放置copy()
函数的位置。
Copy Anything 也完全支持 TypeScript!
它实际上只是递归地遍历一个对象并将值分配给一个新对象,如下所示。但是,它被包装以允许额外的参数等。下面的代码是基本集成,这将使您了解其工作原理的基础知识。
import { isPlainObject } from 'is-what'
function mergeRecursively ( origin , newComer ) {
if ( ! isPlainObject ( newComer ) ) return newComer
// define newObject to merge all values upon
const newObject = isPlainObject ( origin )
? Object . keys ( origin ) . reduce ( ( carry , key ) => {
const targetVal = origin [ key ]
if ( ! Object . keys ( newComer ) . includes ( key ) ) carry [ key ] = targetVal
return carry
} , { } )
: { }
return Object . keys ( newComer ) . reduce ( ( carry , key ) => {
const newVal = newComer [ key ]
const targetVal = origin [ key ]
// early return when targetVal === undefined
if ( targetVal === undefined ) {
carry [ key ] = newVal
return carry
}
// When newVal is an object do the merge recursively
if ( isPlainObject ( newVal ) ) {
carry [ key ] = mergeRecursively ( targetVal , newVal )
return carry
}
// all the rest
carry [ key ] = newVal
return carry
} , newObject )
}
* 当然,与实际源代码存在细微差别,以应对罕见情况和额外功能。实际的源代码在这里。