npm i merge-anything
Рекурсивно объединяйте объекты и другие типы. Полная поддержка TypeScript ! Простая и небольшая интеграция.
Я создал этот пакет, потому что пробовал много подобных пакетов, которые выполняют слияние/глубокое слияние/рекурсивное назначение объектов и т. д. Но у всех были свои особенности, и все они ломают вещи, которые не должны ломаться ...?
Я искал:
Object.assign()
но глубокая Последнее имеет решающее значение! Конечно, в JavaScript почти все является объектом , но мне не нужна функция слияния, пытающаяся объединить, например. два new Date()
! Очень многие библиотеки используют собственные классы, которые создают объекты со специальными прототипами, и все такие объекты ломаются при попытке их объединения. Поэтому нам нужно быть осторожными!
merge-anything объединит объекты и вложенные свойства, но только до тех пор, пока они являются «простыми объектами». Как только дополнительный объект не является «простым объектом» и имеет специальный прототип, он скопирует этот экземпляр «как есть». ♻️
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
, вы можете увидеть тип вашего нового объекта прямо здесь и сейчас. Это очень мощно, потому что вы можете объединять объекты, и TypeScript без any
будет точно знать, как выглядят ваши вновь объединенные объекты!
Тип возвращаемого значения функции 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 ниже.
Поведение по умолчанию заключается в том, что массивы перезаписываются. Вы можете импортировать 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. ?'
Главное правило, которое следует запомнить:
Любое свойство, вложенное более чем на один уровень без перекрывающегося родительского свойства, будет реактивным и связанным как в результате слияния, так и в источнике.
Однако есть действительно простое решение . Мы можем просто скопировать результат слияния, чтобы избавиться от любой реактивности. Для этого мы можем использовать библиотеку копирования чего угодно. Эта библиотека также гарантирует, что экземпляры специальных классов не сломаются , поэтому вы можете использовать ее, не опасаясь что-то сломать!
Посмотрите ниже, как мы интегрируем «копировать что угодно»:
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 )
}
* Конечно, есть небольшие различия с реальным исходным кодом, чтобы справиться с редкими случаями и дополнительными функциями. Фактический исходный код находится здесь.