그동안 얕은 복사, 깊은 복사는 익숙했다. Styletron을 공부하던 중에 Deep Merging이라는 익숙한거 같지만 조금은 어색한 단어가 접했다. Styletron에서 중요하게 사용되는 개념이라 이참에 정리하려고 한다.
우선 병합에는 얕은 병합과 깊은 병합이 있다.
얕은 병합은 1단계 Depth까지만 비교하고 key가 있으면 value를 그냥 바꿔치기 해버린다. 아래 예제를 살펴보자. Shallow Merging은 일치하는 key값이 있으면 value 값을 덮어써 버린다.
Object.assign()로 얕은 병합을 할 수 있다. 하지만 원본 b1의 값들을 변경시킨다.
const b1 = {
name: "hello",
age: 10,
address: {
city: "Seoul",
region: "Asia"
}
}
const b2 = {
name: "hi",
age: 13,
address: {
city: "Japan",
region: "Asia",
"post-code": 12232
}
}
console.log(Object.assign(b1, b2));
{
name: 'hi',
age: 13,
address: {
city: 'Japan',
region: 'Asia',
'post-code': 12232
}
}
console.log(b1);
{
name: 'hi',
age: 13,
address: {
city: 'Japan',
region: 'Asia',
'post-code': 12232
}
}
Spread Operator로도 얕은 병합을 진행할 수 있다. Object.assign()과는 다르게 원본 b1을 변경하지 않는다.
const b1 = {
name: "hello",
age: 10,
address: {
city: "Seoul",
region: "Asia"
}
}
const b2 = {
name: "hi",
age: 13,
address: {
city: "Japan",
region: "Asia",
"post-code": 12232
}
}
console.log({...b1, ...b2})
{
name: 'hi',
age: 13,
address: {
city: 'Japan',
region: 'Asia',
'post-code': 12232
}
}
console.log(b1);
{
name: 'hello',
age: 10,
address: { city: 'Seoul', region: 'Asia' }
}
Shallow Merging과 반대로 Deep Merging은 일치하는 key가 있더라도 그 자식 요소들이 서로 다른 value를 가지고 있다면 그 자식들을 함께 병합한다. Deep Merge가 하위 레벨의 자식 요소를 결합시키기는 한다지만 배열에 대해서는 고려해야 할 케이스가 있다. 배열의 요소들을 어떻게 병합할지인데 고려해야 할 케이스가 정말 많다. 그렇기 때문에 직접 알고리즘을 구현할 수도 있지만 이는 시간이 많이 소요되기 떄문에 라이브러리를 사용하는것이 좋다. Deep merge 관련해서 유명한 라이브러리는 Loadsh와 Deepmerge 가 있다. 두 라이브러리에서 배열을 처리하는데서 차이가 있으니 꼭 공식문서를 살펴보자.
import { merge } from 'lodash';
const object = {
'a': [{ 'b': 2 }, { 'd': 4 }],
'b': {
cd: {
ef: 11,
a123123a: 'alkjl'
},
asdasd: {
asasdadsaasdasdadaadas: 123123123123123
}
}
};
const other = {
'a': [{ 'c': 3 }, { 'e': 5 }],
'b': {
cd: {
ef: 1123123,
aa: 'alkjl'
},
asa: {
aq2132: "aasd"
}
}
};
_.merge(object, other);
a: Array(2)
0: {b: 2, c: 3}
1: {d: 4, e: 5}
length: 2
[[Prototype]]: Array(0)
b:
asa: {aq2132: 'aasd'}
asdasd: {asasdadsaasdasdadaadas: 123123123123123}
cd: {ef: 1123123, a123123a: 'alkjl', aa: 'alkjl'}
const x = {
foo: { bar: 3 },
array: [{
does: 'work',
too: [ 1, 2, 3 ]
}]
}
const y = {
foo: { baz: 4 },
quux: 5,
array: [{
does: 'work',
too: [ 4, 5, 6 ]
}, {
really: 'yes'
}]
}
const output = {
foo: {
bar: 3,
baz: 4
},
array: [{
does: 'work',
too: [ 1, 2, 3 ]
}, {
does: 'work',
too: [ 4, 5, 6 ]
}, {
really: 'yes'
}],
quux: 5
}
merge(x, y) // => output
아래 예제엔 해당 되는 내용은 아니지만 만약 array값의 요소들이 중복이 되는 경우가 있을텐데 그 부분은 고려하지 않았다.
const x = {
foo: { bar: 3 },
array: [
{
does: 'work',
too: [1, 2, 3],
},
],
};
const y = {
foo: { baz: 4 },
quux: 5,
array: [
{
does: 'work',
too: [4, 5, 6],
},
{
really: 'yes',
},
],
};
console.log({
...x,
...y,
foo: { ...x.foo, ...y.foo },
array: [...x.array, ...y.array],
});
기능을 구현할 떄 Deep Merge를 해야될떄가 왔을떄 고려해야할 사항이 굉장히 많다고 느꼈다. 따라서 자료구조를 설계할떄 depth가 깊어지는 부분은 지양해야겠다.
참고
https://blog.ull.im/engineering/2019/04/01/javascript-object-deep-copy.html
https://velog.io/@sangminnn/%EB%B6%80%EB%A1%9D-Shallow-merge-VS-Deep-Merge
https://www.npmjs.com/package/deepmerge
https://lodash.com/docs/4.17.15#merge