순수함수란 동일한 인자가 주어졌을 때 항상 동일한 결과를 반환해야 하며 외부의 상태를 변경하지 않는 함수이다. 즉 함수 내 변수 외에 외부의 값을 참조, 의존하거나 변경하지 않아야한다.
let obj = {val: 20}
const notPureFn = (object, num) => {
object.val += num;
}
// 외부 obj 배열의 값을 수정함
const notPureFn = (num) => {
object.val += num;
}
// 함수 외부의 변수를 참조함
const notPureFn = (object, num) => {
num = 10
object.val += num;
}
// 함수의 인자를 수정함
그래서 왜 순수함수여야 하는가??
Redux Reducer를 통해 우리는 state를 업데이트해왔다.
즉 불변해야한다는 것이 state를 변경하면 안된다는 뜻이 아니라, 수정해서는 안된다는 것이다.
function counter(state = initialState, action) {
switch(action.type) {
case types.INCREMENT:
return { ...state, number: state.number + 1 };
case types.DECREMENT:
return { ...state, number: state.number - 1 };
default:
return state;
}
}
위의 카운터 Reducer를 보면 원본 state를 수정하는 것이 아닌, spread 연산자를 사용해 복사한 후 수정하고 있다.
복사에는 얕은 복사와 깊은 복사가 있다.
얕은 복사는 참조(주소)값의 복사를 의미한다.
const obj = { value: 10 }
const newObj = obj
newObj.value = 5;
console.log(obj.value); // 2
console.log(newObj.value); // 2
obj 객체를 새로운 newObj 객체에 할당하였으며 이를 참조 할당이라 부른다.
복사 후 newObj 객체의 value값을 변경하였더니 기존의 obj.value값도 같이 변경된 것을 알 수 있다.
이는 데이터가 그대로 생성되는 것이 아닌 해당 데이터의 참조 값(메모리 주소)를 전달하여 결국 한 데이터를 공유하는 것이다.
깊은 복사는 값 자체의 복사를 나타내 다른 주소값을 지닌다.
const obj = { value: 10 }
const newObj = { ...obj }
newObj.value = 5
console.log(obj.value); // 10
console.log(newObj.value); // 5
자바스크립트의 원시 타입(string, number, boolean, undefined, ES6 부터 추가된 symbol)은 깊은 복사가 되며, 이는 독립적인 메모리에 값 자체를 할당하여 생성하는 것이라 볼 수 있다.
깊은 복사를 할 때 주로 사용되는 Object.assign()
메소드와 위의 spread 연산자가 있다.
이렇게 원본 state를 수정하는 것이 아니라, 주소값이 다른 state 복사해 수정을 하는 이유는 Redux의 변경감지 알고리즘에 있다.
redux는 reducer를 거친 state가 변경됐는지를 검사하기 위해 state 객체의 주소를 비교한다. state의 복제본을 만들어 반환하면 이전의 state와 다른 주소값을 가르키기 때문에 state가 변경되었다고 판단한다. 반대로 state를 복제하는것이 아닌 속성만 수정하여 반환하면 기존의 state 객체와 가리키는 주소값이 같기 때문에 변경감지가 되지 않는다.
변경을 알아차리기위해 바뀐 속성을 찾아나서려면 성능과 복잡성의 문제를 일으킨다. 객체의 속성을 비교하는 것은 깊은 비교라하는데, 이는 복잡하고 무거운 알고리즘을 필요로 한다.
아래는 단순 주소를 비교하는 알고리즘과 속성을 비교하는 알고리즘이다.
// 객체의 주소 비교
const compareReference = (object1, object2) => object1 === object2;
// 객체의 속성 비교
const compareProps = (object1, object2) => {
const object1Keys = Object.keys(object1);
const object2Keys = Object.keys(object2);
if (object1Keys.length !== object2Keys.length) {
return false;
}
for (const key of object1Keys) {
if (typeof object1[key] === 'object' && typeof object2[key] === 'object'){
return compareProps(object1[key], object2[key]);
} else if (object1[key] !== object2[key]) {
return false;
}
}
return true;
}
속성비교는 먼저 키값만을 배열로 만들고, 이들의 키값의 개수가 동일한지 체크하고, 만약 개수가 동일하다면 키에 해당하는 값의 타입을 비교해서 같다면... 아무튼 복잡하다.
Redux Reducer는 순수함수(외부의 값을 참조하거나 변경x, 함수 인수를 변경x)여야하고, 순수함수여야하는 이유는 이전 state의 주소값과 리턴된 state의 주소값을 비교해서 변경을 감지하지하기 때문이다.
참조 사이트:
https://velog.io/@recordboy/JavaScript-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%ACShallow-Copy%EC%99%80-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%ACDeep-Copy
https://velog.io/@kimu2370/redux%EC%9D%98-reducer%EA%B0%80-%EC%88%9C%EC%88%98%ED%95%A8%EC%88%98%EC%9D%B8-%EC%9D%B4%EC%9C%A0
https://boxfoxs.tistory.com/406