npm i merge-anything
オブジェクトと他のタイプを再帰的にマージします。 TypeScriptを完全サポート!シンプルで小規模な統合。
このパッケージを作成したのは、マージ/ディープマージ/再帰的オブジェクト割り当てなどを行う同様のパッケージをたくさん試したからです。しかし、すべてに癖があり、それらのすべてが壊れるべきではないものを壊します...?
探していたもの:
Object.assign()
のような単純だが奥深いマージ関数この最後のものが重要です! JavaScript では、確かにほとんどすべてがオブジェクトですが、マージ関数がマージしようとするのは望ましくありません。 2 つ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
の上にカーソルを置くと、実際にその場で新しいオブジェクトの型を確認できます。これは非常に強力です。なぜなら、 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 オブジェクト参照に関する注記を必ずお読みください。
デフォルトの動作では、配列は上書きされます。配列を連結する必要がある場合は、 mergeAndConcat
インポートできます。ただし、これが必要ない場合でも心配する必要はありません。このライブラリはツリーシェイク可能であり、使用しないコードはインポートされません。
import { mergeAndConcat } from 'merge-anything'
mergeAndConcat (
{ nested : { prop : { array : [ 'a' ] } } } ,
{ nested : { prop : { array : [ 'b' ] } } }
)
// returns { nested: { prop: { array: ['a', 'b'] } } },
2 つのものをマージするときにロジックを調整する必要がある場合があります。値が上書きされるたびにトリガーされる独自のカスタム関数を提供できます。
この場合、 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 )
}
※もちろん、まれなケースや追加機能に対応するために、実際のソースコードとは多少の違いがあります。実際のソースコードはこちらです。