📝강의를 시청하면서 궁금한 점이 있거나, 질문 또는 의견을 남기고 싶다면? 페이지 하단(↓) "토론" 섹션에 남겨주세요.
# 불변 데이터 관리
영상 강의를 시청하며 글을 읽고 실습하면 더욱 효과적입니다.
React, Redux의 상태는 불변(Immutable) 데이터 입니다. 상태가 변경되어야 할 때는 기존 데이터를 변형하는 것이 아니라, 새로운 데이터로 교체해야 합니다. 이러한 불변 데이터를 관리하는 방법에 대해 정리합니다.
# 배열 아이템 추가
불변(Immutable) 데이터인 상태를 업데이트 하려할 때 배열 객체의 .push()
, .unshift()
같은 데이터 변경 메서드를 사용하면 안됩니다.
불변 데이터를 변경 시도하면 'object is not extensible' 오류가 발생합니다.
이전 상태 데이터를 변형하는 것이 아니라, 새로운 데이터를 다음 상태로 반환해야 합니다. 예를 들어 .concat()
메서드를 사용하거나,
전개 연산자를 사용해 새로운 데이터를 반환할 수 있습니다. (위의 라이브 코드 창에서 Console 패널 열고 실습해보세요.)
const addItem = (list, newItem) => {
// 방법 1.
return list.concat(newItem)
// 방법 2.
return [...list, newItem]
}
2
3
4
5
6
# 배열 아이템 제거
'추가'와 마찬가지로 '제거' 또한 배열 객체의 .pop()
, .shift()
, .splice()
같은 데이터 변경 메서드를 사용하면 안됩니다.
불변 데이터를 변경 시도하면 'object is not extensible' 오류가 발생합니다.
.concat()
, .slice()
와 같은 메서드는 새로운 배열을 반환하는 메서드, 전개 연산자 또는
.filter()
같은 메서드를 사용해야 합니다. (위의 라이브 코드 창에서 Console 패널 열고 실습해보세요.)
const removeItem = (list, index) => {
// 방법 1.
return list.slice(0, index).concat(list.slice(index + 1))
// 방법 2.
return [...list.slice(0, index), ...list.slice(index + 1)]
// 방법 3.
return list.filter((item, i)) => i !== index)
}
2
3
4
5
6
7
8
# 배열 아이템 변경
'추가', '제거'와 마찬가지로 '변경' 또한 배열 객체의 .splice()
같은 데이터 변경 메서드를 사용하면 안됩니다.
불변 데이터를 변경 시도하면 'object is not extensible' 오류가 발생합니다.
.concat()
, .slice()
와 같은 메서드는 새로운 배열을 반환하는 메서드, 전개 연산자 또는
.map()
같은 메서드를 사용해야 합니다. (위의 라이브 코드 창에서 Console 패널 열고 실습해보세요.)
const changeItem = (list, index, newItem) => {
// 방법 1.
return list.slice(0, index).concat(newItem).concat(list.slice(index + 1))
// 방법 2.
return [...list.slice(0, index), newItem, ...list.slice(index + 1)]
// 방법 3.
return list.map((item, i) => (i === index ? newItem : item))
}
2
3
4
5
6
7
8
# 객체 속성 변경
불변 객체 또한 속성을 변경 시도하면 오류가 발생합니다. "Cannot assign to read only property 'done' of object"
오류 없이 문제를 해결하려면 Object.assign() 메서드를 사용하거나, 전개 연산자를 활용합니다. (위의 라이브 코드 창에서 Console 패널 열고 실습해보세요.)
const changePropInObject = (o, prop, value) => {
// 방법 1.
return Object.assign({}, o, { [prop]: value })
// 방법 2.
return {
...o,
[prop]: value
}
}
2
3
4
5
6
7
8
9
# 얕은 동결 vs 깊은 동결
앞서 다룬 불변 객체의 데이터를 업데이트 하는 방법에서 객체를 불변 설정하기 위해 Object.freeze() (opens new window)를 활용했습니다.
// 아이템 변경 테스트
const testChangeItem = () => {
const listBefore = ['react', 'angular', 'redux']
const listAfter = ['react', 'react native', 'redux']
// 불변 데이터로 변경
Object.freeze(listBefore)
expect(
changeItem(listBefore, 1, 'react native')
).toEqual(listAfter)
}
2
3
4
5
6
7
8
9
10
11
12
예제에서 다룬 불변 객체인 배열은 아이템 구조가 얕아(shallow) Object.freeze()
를 사용해도 큰 문제는 없습니다. 다만, 객체의 구조가 깊어(deep) 지면 Object.freeze()
는 문제 해결 수단으로 적합하지 않습니다.
Object.freeze()
는 얕은 구조의 객체만 동결 시킬 수 있기 때문입니다. 아래 예시 코드를 살펴보세요.
// 구조가 깊은 객체
const o = {
shallowProp: true,
deepProp: {
isFreeze: true,
isShallow: 'No!'
}
}
// o 객체를 불변 객체로 변경
Object.freeze(o)
// -------------------------------
// 얕은 속성 변경 시도
o.shallowProp = false
// 변경되지 않음 (결과: true)
console.log(o.shallowProp)
// -------------------------------
// 깊은 속성 변경 시도
o.deepProp.isShallow = '아니오!'
// 변경 됨 (결과: '아니오!')
console.log(o.deepProp.isShallow)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# deepFreeze 유틸리티
Object.freeze()의 얕은 동결 대신 깊은 동결이 요구되는 불변 데이터인 경우, deepFreeze() 헬퍼 함수를 만들어 사용할 수 있습니다.
export default function deepFreeze(obj) {
// 객체 속성 이름 집합 반환
const propNames = Object.getOwnPropertyNames(obj)
// 객체 속성 이름 순환 처리
for ( let name of propNames ) {
// 값 = 객체[속성]
let value = obj[name]
// 객체[속성] = 새로운 값으로 변경 (value가 객체 유형인 경우 재귀 함수 처리)
obj[name] = value && typeof value === 'object' ?
deepFreeze(value) : value
}
// 객체 동결(재귀 함수에서는 객체.속성인 객체 또한 동결)
return Object.freeze(obj)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
아래 코드는 Object.freeze()를 사용해 객체를 불변 객체로 만들었으나, 얕은 동결이라 깊은 속성까지 동결시키지 못해 기대와 달리 오류를 발생하지 않습니다. 앞서 소개한 deepFreeze 모듈을 사용해 깊은 동결을 설정해보세요. 깊은 속성까지 동결되어 의도한 오류가 발생하게 됩니다. 즉, 완전한 불변 객체가 됩니다.
# Immutable.js 라이브러리
앞서 다룬 것처럼 불변 데이터를 변형하지 않고, 새로운 데이터로 교체하는 과정은 기존의 JavaScript 프로그래밍과 달라
생소할 수 있습니다. .push()
, .pop()
, .shift()
, .unshift()
, .splice()
등을 사용하면 문제가 발생할 수 있으니까요.
다행히도 Immutable.js (opens new window) 라이브러리를 사용하면
문제가 발생할 수 있어 사용에 제약이 생긴 메서드를 불변 데이터 관리에 사용할 수 있습니다.
아래 영상은 Facebook, Lee Byron이 2015년 React와 Immutable Data에 대해 소개하는 내용을 담고 있습니다. 시간 되실 때 Immutable Data의 필요성과 관리 방법에 대한 이야기를 살펴보세요.
# 배열 아이템 추가
Immutable.js 라이브러리를 사용하면 불변 데이터를 관리하는데 있어 금기(?) 된 .push()
메서드를 사용할 수 있습니다.
Array가 아닌 List의 .push()
를 사용하는 것이지만, 배열과 동일한 경험을 제공하므로 손쉽게 아이템을 추가할 수 있습니다.
import { List } from 'immutable'
// 불변 데이터
const cookBefore = ['고추장 삽겹살 볶음', '베이컨 버섯말이']
Object.freeze(cookings)
// 비교 데이터
const cookAfter = ['고추장 삽겹살 볶음', '베이컨 버섯말이', '바지락 해물 칼국수']
// Immutable 라이브러리 > List 활용
// 배열 → List로 변경
const newCookBefore = List(cookings)
// List의 push() 메서드 활용
.push('바지락 해물 칼국수')
// List → 배열로 변경
.toArray()
expect(newCookBefore).toEqual(cookAfter) // 통과!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
아래 라이브 코드를 살펴보면 .push() (opens new window) 메서드를 사용했지만 오류는 발생하지 않습니다.
# 배열 아이템 제거
List의 .splice() (opens new window) 메서드를 활용해 아이템을 제거한 예입니다.
# 배열 아이템 변경
List의 .splice()
메서드를 활용해 아이템을 변경한 예입니다.
# 객체 속성 변경
Map의 .update() (opens new window) 메서드를 활용해 객체의 속성을 변경한 예입니다.