언어/자바스크립트

Deep dive의 순수함수, 불변성, feat. 리덕스, 리듀서, 미들웨어

youngble 2023. 2. 5. 21:17

Step

1. 왜 순수함수, 불변성을 정리하고자 하는지

2. 순수함수와 불변성은 무엇인가?

3. 리덕스/리듀서와는 무슨 관계가 있는가?

4. 미들웨어는 무슨 관계인가?

 


그전에 다 끝내지 못한 모던 자바스크립트 Deep Dive 책을 읽고 있는데, 이 책을 읽은지가 일년 반정도 되었고 1/3 정도만 읽었던 상태라 그전에 읽었을때 이해하는 시야와 잊어버렸던 부분을 다시 다잡기위해서 처음부터 읽기 시작했는데, 확실히 그전에 읽었을때와 지금 읽었을때 이해하는 바가 많이 다르고 현시점에서 어떻게 실질적인 실무에서 적용하는 부분인지 생각하면서 보게 된거 같다.

확실히 읽는 속도도 예전보다 빠르게 읽히기도 해서 좀더 가속도를 높이고 있다. 

1. 왜 순수함수, 불변성을 정리하고자 하는지

그전 내용들은 따로 블로그에 정리하지 않고 이해만했는데(시간도 없고, 기본적으로 중복되는 내용들도 많을거 같았다) 오늘 공부한 순수함수 부분을 공부하면서 리덕스 리듀서를 생각했고 왜 리듀서는 순수함수여야하고 불변성을 유지해야하는가 에 대해서 한번더 생각하게 되었고 서치한 부분과 deep dive에서 본내용을 접목한 결과, 실무이면서도 기본 자바스크립트 원리와 너무 잘맞아 떨어지는 부분이라 그전에 제대로 이해하지 못한 부분의 연결고리들이 오늘 공부를 통해서 서로 맞물리며(미들웨어, 순수함수, 불변성, 리덕스 등) 많은 부분이 이어지기 때문에 이거는 따로 TIL 형식으로 써놓으면 좋을 거같다고 생각해서 생각나는대로 내용과 느낀점을 써가며 짧게 정리하는게 좋다고 생각했다.

 

2. 순수함수와 불변성은 무엇인가?

본론에 앞서 기본적인 순수함수가 무엇이고 불변성이 무엇인지를 설명을 하려고 한다.

순수함수

순수함수의 정의는 오직 매개변수를 통해 전달된 인수외에 외부 상태에 의존하거나 변경하지 않는다. 고로 부수효과가 없는 함수 라고 말한다. 정의만 보면 의존 하지 않는다 변경하지 않는다 라는 말때문에 혼동이 왔던 부분도 조금 있었다. 순수함수라고 해도 더하고 빼고 나누고 등등  값들은 정의와는 다르게 무엇인가를 바꾸고 input 과 output 이 다르기 때문이다.

 

하지만 다시한번 이해를 하자면 외부상태 라는 말에 포커싱을 해야한다. 순수함수는 매개변수에만 의존하여 같은 값을 반환한다고 하는데, 외부상태라고 한다면 이 함수정의 밖에서 사용하는 부분을 말한다.

//비순수 함수 예시
let a = 1;
let testObj ={
	test: 'aaa',
}
//ex1 : value라는 인수로 넘기는 매개변수 외에도 외부상태인 a 변수값에 의존하기 때문에 비순수함수
function func1(value){
return value + a; 
}
func1(1);

//ex2 : 외부상태값인 a 값을 변경하기 때문에 비순수함수
function func2(value){
a=2;
return value ; 
}
func2(2);

//ex3: 인수로 testObj로 넘겨주고 매개변수로써 참조값을 복사하여 사용하였지만, 
// 객체의 프로퍼티값에 접근하여 test를 변경하기 때문에 비순수함수
function func3(obj){
 obj.test = 'bbb';
 return obj;
}
func3(testObj);

위처럼 a라는 외부상태를 func 라는 함수는 의존을 하고 그 값을 변경한다. 이렇게 되면 a값이 바뀔때마다 func 함수에서의 결과는 달라질 것이고 달라진다는 것은 부수효과가 있다는 말이기 때문에 비순수함수가 되는 것이다.

 

또한 testObj 를 인수로 넘겨줘서 매개변수로 사용하기 때문에 어떻게 보면 매개변수로써만 의존하고 사용하는 것처럼 보이지만, 원시값이 아닌 인수는 참조값으로 사용하기 때문에 참조값을 복사하여 사용하지만 실질적인 객체 값들은 변경될수 있고 그 참조값은 결국 외부 testObj에 의존하고 있기때문에 순수함수가 아닌 것이다.

 

// 순수함수 예시
let a = 2;
let testObj = {
	test: 'aa'
}
//ex1: 인수로 넘겨준 2를 매개변수로 값을 복사하여 사용하고 외부상태 값에 
// 의존하거나 변경하는게 없기 떄문에 순수함수
function func1(value){
	return value + 1;
}
func1(2)

//ex2: func2함수안에서 생성한 arr은 외부에 영향을 주거나 외부에서 가져온 데이터가 아니고 의존하거나 변경하지 않고 
// 함수내에서 사용하고 있기때문에 언제가 input, output이 일정한 순수함수
function func2(value){
	let arr = ["test", value];
	return arr;
}
func2("haha");

// ex3: a라는 외부 값을 인수로 사용했지만 매개변수로는 값을 복사하여 새로운 변수에 저장하여 사용하기 때문에
// a차에 의존하거나 변경하지않음
function func3(value){
	value = value + 1;
	return value;
}
func3(a);

//ex4: 외부 객체 testObj를 인수로 받아 매개변수로 사용했을때 참조값만 복사하여 사용하는게 아닌 
// 새로운 객체로써 스프레드 연산자사용하여 만들었기 때문에 외부에 의존하지 않는 순수함수
function func4(obj){
 	let newObj = {...obj};
 	newObj.test = 'ccc';
 	return newObj;
}
func4(testObj);

 

위의 예시에서 특히 func4의 경우 외부 객체 testObj를 인수로 넘겨주었고 test라는 프로퍼티에 접근하여 값을 바꿔주었지만 이는 스프레드 연산자를 통해 복사한 새로운 newObj라는 변수에 담아주었고 외부 testObj.test 의 값을 변경하는게 아니기 때문에 순수함수이다.

불변성

이렇게 위의 순수함수를 이해할때 등장하는게 불변성이다. 위의 예시에서 사용한 객체 testObj 와 a라는 변수를 사용할때 알아야하는 부분이 객체 testObj는 객체타입 참조값이라는 것이고, a 변수는 숫자타입으로써의 원시타입 값이라는 것이다. 

이때 원시값은 불변성을 가진다는 말을 하고 객체는 수정이 가능하다고 말한다.

 

이것을 제대로 이해하기 위해선 메모리의 주소와 공간을 이해해야하고 값 자체가 수정이 되는 것인지 복사하여 새로운 값을 가진 공간의 주소로 바꾸는지를 알아야지 무엇이 불변성인지 알기 때문이다. 안다는 가정하에 설명을 하면 객체는 참조값을 메모리 공간에 저장하고 이는 그저 객체의 값을 접근하기 위한 메모리공간의 주소이기 때문에, 함수의 인수로 넘겨주어 매개변수로 사용한다고 해도 프로퍼티에 접근하여 수정하게 되면 실질적으로 참조값만을 복사하여 사용한 매개변수는 참조값을 통해 실질적인 객체값에 접근하여 test를 바꾸기 때문에 불변성을 지키지 않을 뿐더러 외부 상태값을 바꾸기 때문에 순수함수가 되지 않는다.

 

3. 리덕스/리듀서와는 무슨 관계가 있는가?

그래서 이제 실무적으로 사용했던 리덕스와 리듀서에 접목해서 이해해야하기 때문이다.

먼저 리듀서를 사용하고 정의할때 항상 이야기하는 것이 리듀서는 순수함수로써 사용해야한다는 것이고 리덕스의 state는 불변해야한다는 불변성을 말한다. 먼저 불변성을 말하자면 위의 처럼 윈시타입에서 말하는 불변성과 다르다. 윈사타입은 값 자체를 변경할수 없고 새로운 메모리공간에 값을 넣어 할당해줄뿐 수정자체는 안된다는 불변성을 말하지만 여기서 말하는 리덕스 state의 불변성은 변경을 할 수 없다는 이야기가 아니라 바꾸지 않아야한다는 불변성을 강조하는 말이다.

 

왜 이렇게 불변성을 말하는지에 좀더 설명하자면

리덕스가 state의 변경을 감지할때 객체의 참조값이 변하였는지 체크하기 때문인데, 참조값이 변한다는 것은 객체가 참조값이 같은지 다른지를 본다는 말이고 결국 그것은 원본 state 를 바꾸지 않고 새로운 객체를 만들어 다른 참조값으로 수정한다는 말이다. 

 

리듀서가 순수함수여야 한다는 말은 결국 state를 직접으로 의존하거나 변경하는게 아닌 action을 통해 넘어오는 매개변수를 통해 새로운 객체를 만들고 기존의 state값을 복사하여 사용한다는 것이다. 그렇다면 왜 state는 불변해야하고 프로퍼티를 체크하여 리덕스가 변화를 감지하지 않는지에 대해서는 성능과 복잡성이라는 이유때문이라고 한다.

 

만약 state에 담은 값들이 1000000개 라고 해보자 하나하나 프로퍼티에 접근하여 무엇이 바뀌었는지를 체크하여 감지한다면 성능면에서 매우 오래걸리고 번거로운 작업이다. 하지만 참조값만 바뀌었는지 비교하여 감지한다면 O(1) 시간복잡도를 가지기때문에 성능면에서도 좋다. 따라서 리덕스는 state의 불변성을 강조하는 것은 이것이기 때문이다. 원본 state를 바꾼다면 이를 리덕스는 감지하지 않을 뿐더러 이러한 방법으로는 성능적으로 매우 좋지 않기 때문이다.

 

아래는 리덕스 공식홈페이지에서 발췌한 내용이다.

Shallow equality checking (or reference equality) simply checks that two different variables reference the same object; in contrast, deep equality checking (or value equality) must check every value of two objects' properties.

https://redux.js.org/faq/immutable-data#how-do-shallow-and-deep-equality-checking-differ

위의 설명 그대로  두개의 참조값을 비교하여 같은 객체인지 비교하는 것 a===b 과 deep equality 로써 두 객체의 프로퍼티의 모든 각각의 값을 비교하는 것 a.test === b.test 을 설명한다. 

4. 미들웨어는 무슨 관계인가?

마지막으로 미들웨어는 왜 나왔는지 이다. 그전부터 나는 미들웨어 라는 정의가 매우 헷갈리고 지금도 매우 추상적인 단어라고 생각해서 정확하게 이해하기가 힘들다고 생각한다. 그런데 리덕스를 사용하면 미들웨어라는 말이 무조건 나오는데 여러 관점이 있겠지만 여기서 순수함수 리듀서와 관련이 있고 이해에 필요하기 때문이다.

 

보통 미들웨어는 무엇이고 왜 쓰는가 를 리듀서의 순수함수의 관점에서 보자면, 리듀서에서 외부상태값(서버 api 통한 값)을 변경하거나 의존하여 사용/변경한다면 결국 순수함수가 아니게 된다. 이 순수함수를 유지하기 위해서는 처리해야하는 부분이 필요한데 이러한 개념이 미들웨어인 것이다. 리듀서가 state의 불변성을 유지하기위해서 순수함수가 되는 것과 순수함수의 조건이 외부 state객체의 변경뿐만아니라 결국 서버에서 가져오는 데이터 역시도 외부 상태값이기 때문에 미들웨어로써 api 통신을 하고 비동기 처리를 하여 부수효과를 막기 때문에도 분리가 필요하다. 또한 미들웨어를 통해 비동기 처리를 하지 않고 리듀서에서 작업한다고 생각한다면, 서버의 응답이 있을때까지 다음작업 state update가 이루워지지 않기 때문에 바뀔때 state를 업데이트하는 다음 코드를 읽어서 결국 리렌더링 된다는 것이다. 따라서 리듀서안에서 비동기 처리를 하는게 아니라 미들웨어에서 작업함으로써 이러한 의존성이나 멈춤을 방지 할 수 있다. 또한 미들웨어에서 비동기 처리가 완료된 시점에서 바로 리듀서로 상태변경이 아닌 다른 미들웨어를 거친다거나 다른 작업을 한후 진행해야한다면 리듀서에서 작업하게 된다면 이러한 로직이 성립이 안된다. 

 

이렇게 글로만 썼지만 자바스크립트를 공부를 하면서 결국엔 실무에도 많은 영향이 있던 부분과 여러가지 연결고리가 많았기 때문에 적어보았다.

 

오늘도 이렇게 한단계 더 성장하였다.