💻 Frontend

순수함수란?

category
💻 Frontend
useState를 조사하면서 React에선 초기화 함수나 setState, 컴포넌트는 순수함수여야 한다는 것을 보고 순수함수가 무엇인지 조사하게 되었습니다.

순수함수 조건

  • 동일한 인자가 들어갈 경우 항상 같은 값이 나와야 한다.
  • 부수적인 효과가 일어나면 안된다.
    • 부수효과란 외부의 상태를 변경하는 것 또는 함수로 들어온 인자의 상태를 직접 변경하는 것
  • return 값으로만 소통한다.
⇒ 어떤 함수에 동일한 인자를 주었을 때 항상 같은 값을 반환하는 함수, 외부의 상태를 변경하지 않는 함수
 

예제

function add(a, b) { return a + b; } console.log(add(10, 5)); console.log(add(10, 5)); console.log(add(10, 5));
add는 순수함수입니다.
언제, 어디서 실행해도 add(10,5)는 항상 15를 리턴하고 외부 상태를 변경하지 않았기 때문이죠.

 
let c = 10; function add2(a, b) { return a + b + c; } console.log(add2(5,5)) // 20 c = 15 console.log(add2(5,5)) //25
해당 예제는 항상 동일한 인자가 들어가도 c값에 따라서 결괏값이 달라질수 있습니다. 따라서 add2는 순수 함수가 아닙니다.
만약 c가 상수 즉, 변하지 않는 수라면 add2는 순수 함수입니다. 외부의 값을 참조해도 결과값이 동일한 인자에 대해서 같기 때문이죠. (즉, 리턴 값을 보장해주기 때문)

 
var c = 20; function add3(a,b){ c = b; // 외부상태에 영향을 미치네? = 부수효과 return a + b; } console.log('c : ',c); console.log(add3(20,30)); console.log('c : ',c);
add3도 순수 함수가 아닙니다. 함수가 외부의 값을 변경하는 코드를 가지고 있기 때문입니다.
리턴하는 값이 항상 일정하더라도 외부의 상태를 변경하는 코드가 있으면 순수 함수가 아니게 됩니다.

 
var obj1 = {val : 10}; function add4(obj, b){ obj.val += b; }
add4는 순수 함수가 아닙니다. 객체를 인자로 받아서 그 상태를 변경 시키는 코드를 가지고 있기 때문입니다.
그렇다면 객체는 어떻게 순수 함수로 나타낼까요?

var obj1 = {val : 10}; function add5(obj, b){ return { val: obj.val + b }// obj의 val만 참조만 할 뿐 변경없음. } console.log(obj1.val);//변경 전 10 var obj2 = add5(obj1,5); console.log(obj1.val);//변경 후 10 console.log(obj2.val);//15
add5는 순수 함수입니다. 위 처럼 객체의 값을 참조만 하는 식으로 작성하면됩니다.
이렇게 순수 함수로 만들고, 객체 값의 변경이 필요하면 함수 외부에서 객체가 값을 적용하는 방식으로 사용 가능합니다.

컴포넌트를 순수하게 유지하기

일부 JavaScript 함수는 순수합니다. 순수 함수는 계산만 수행하며 그 이상은 수행하지 않습니다. 컴포넌트를 순수한 함수로만 엄격하게 작성한다면 코드베이스가 커짐에 따라 황당한 버그와 예측할 수 없는 동작의 전체 클래스를 피할 수 있습니다. 그러나 이러한 이점을 얻으려면 따라야 할 몇 가지 규칙이 있습니다.
 
📖
배울 내용
  • 순수(순도)란 무엇이며 버그를 피하는 것을 어떻게 도와주는지
  • 렌더링 단계에서 변경사항을 유지하여 컴포넌트를 순수하게 유지하는 방법
  • Strict 모드를 사용하여 컴포넌트에서 실수를 찾는 방법
 

순수: 공식과 같은 컴포넌트

컴퓨터 과학(특히 함수형 프로그래밍의 세계)에서 순수 함수는 다음과 같은 특성을 가진 함수입니다:
  • 그것은 자신의 역할만 수행합니다. 호출되기 전에 존재했던 객체나 변수는 변경하지 않습니다.
  • 동일한 입력, 동일한 출력. 동일한 입력이 주어지면 순수 함수는 항상 동일한 결과를 반환해야 합니다.
 
순수 함수의 한 예인 수학 공식에 이미 익숙할 것입니다.
다음 수학 공식을 고려해보세요: y = 2x.
 
x = 2이면 항상 y = 4입니다.
x = 3이면 항상 y = 6입니다.
x = 3이면 시간이나 주식 시장 상태에 따라 y가 9 또는 -1 또는 2.5가 아닌 경우가 있습니다.
y = 2x이고 x = 3이면 y는 항상 6입니다.
 
y = 2x이고, x = 3이면 y는 항상 6입니다. 이것을 JavaScript 함수로 만들면 다음과 같습니다:
function double(number) { return 2 * number; }
위의 예에서 double은 순수 함수입니다. 3을 전달하면 6을 반환합니다. 항상이요.
React는 이 개념을 중심으로 설계되었습니다. React는 작성하는 모든 컴포넌트가 순수 함수라고 가정합니다. 즉, 작성한 React 구성 요소는 동일한 입력이 주어지면 항상 동일한 JSX를 반환해야 합니다.
순수함수인 컴포넌트는 다음과 같은 것이죠.
function Recipe({ drinkers }) { return ( <ol> <li>Boil {drinkers} cups of water.</li> <li>Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.</li> <li>Add {0.5 * drinkers} cups of milk to boil and sugar to taste.</li> </ol> ); } export default function App() { return ( <section> <h1>Spiced Chai Recipe</h1> <h2>For two</h2> <Recipe drinkers={2} /> <h2>For a gathering</h2> <Recipe drinkers={4} /> </section> ); }
drinkers={2}를 Recipe에 전달하면 언제나 물 2컵이 포함된 JSX가 반환됩니다.
drinkers={4}를 전달하면 언제나 물 4컵이 포함된 JSX가 반환됩니다.
마치 수학 공식처럼이요.
컴포넌트를 레시피로 생각할 수 있습니다. 컴포넌트를 따르고 요리 과정에서 새로운 재료를 도입하지 않으면 매번 같은 요리를 얻을 수 있습니다. 그 "접시"는 컴포넌트가 렌더링을 위해 React에 제공하는 JSX입니다.
 

부작용(side effects): 의도하지 않은 결과

React의 렌더링 프로세스는 항상 순수해야 합니다. 컴포넌트는 JSX만 반환해야 하며 렌더링 전에 존재했던 객체나 변수를 변경하면 안 됩니다.
다음은 이 규칙을 위반하는 컴포넌트입니다.
let guest = 0; function Cup() { // Bad: changing a preexisting variable! guest = guest + 1; return <h2>Tea cup for guest #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup /> <Cup /> <Cup /> </> ); }
이 컴포넌트는 외부에서 선언된 게스트 변수를 읽고 쓰고 있습니다. 즉, 이 컴포넌트를 여러 번 호출하면 다른 JSX가 생성됩니다! 또한 다른 컴포넌트가 게스트를 읽는 경우 렌더링된 시점에 따라 다른 JSX도 생성합니다! 이것은 예측할 수 없습니다.
공식 y = 2x로 돌아가서 이제 x = 2이더라도 y = 4를 신뢰할 수 없습니다. 테스트가 실패하고 사용자가 당황하고 비행기가 하늘에서 떨어질 수 있습니다. 이것이 어떻게 혼란스러운 버그로 이어지는지 알 수 있습니다!
대신 guest를 prop으로 전달하여 이 컴포넌트를 수정할 수 있습니다:
function Cup({ guest }) { return <h2>Tea cup for guest #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup guest={1} /> <Cup guest={2} /> <Cup guest={3} /> </> );
이제 구성 요소가 반환하는 JSX가 guest prop에만 의존하므로 컴포넌트가 순수합니다.
일반적으로 컴포넌트가 특정 순서로 렌더링될 것으로 기대해서는 안 됩니다. y = 5x 이전 또는 이후에 y = 2x를 호출하는 것은 중요하지 않습니다. 두 공식은 서로 독립적으로 해결됩니다. 같은 방식으로 각 컴포넌트는 "스스로 생각"해야 하며 렌더링 중에 다른 컴포넌트와 조정하거나 의존하려고 시도해서는 안 됩니다. 렌더링은 학교 시험과 같습니다.
각 컴포넌트는 자체적으로 JSX를 계산해야 합니다!
 

Local mutation: 컴포넌트의 작은 비밀

위의 예에서 문제는 컴포넌트가 렌더링하는 동안 기존 변수를 변경했다는 것입니다. 이것은 좀 더 무섭게 들리도록 종종 "돌연변이"라고 합니다. 순수 함수는 함수 범위 밖의 변수나 호출 전에 생성된 객체를 변경하지 않습니다.
그러나 렌더링하는 동안 새로 만든 변수와 개체를 변경하는 것은 전혀 문제가 없습니다. 이 예제에서는 [ ] 배열을 만들고 cups 변수에 할당한 다음 12개의 컵을 배열에 푸시합니다.
function Cup({ guest }) { return <h2>Tea cup for guest #{guest}</h2>; } export default function TeaGathering() { let cups = []; for (let i = 1; i <= 12; i++) { cups.push(<Cup key={i} guest={i} />); } return cups; }
cups 변수나 [ ] 배열이 TeaGathering 함수 외부에서 생성된 경우 큰 문제가 됩니다! item을 해당 배열로 푸시하여 기존 개체를 변경하게 됩니다.
그러나 TeaGathering 내에서 동일한 렌더링 중에 생성했기 때문에 괜찮습니다. TeaGathering 외부의 어떤 코드도 이러한 일이 발생했음을 알 수 없습니다. 이것을 “local mutation"(직역 하면 지역 돌연변이..?)라고 합니다. 컴포넌트의 작은 비밀과 같습니다.
 

부작용(side effects)을 일으킬 수 있는 곳

함수형 프로그래밍은 순도에 크게 의존하지만 어느 시점, 어딘가에는 무언가가 변경되어야 합니다. 그것이 프로그래밍의 요점입니다! 이러한 변경 사항(화면 업데이트, 애니메이션 시작, 데이터 변경)을 사이드 이펙트(side effects - 부작용)이라고 합니다. 렌더링 도중이 아니라 "측면에서(side)" 발생하는 것입니다.
React에서 사이드 이펙트는 일반적으로 이벤트 핸들러 내부에 속합니다. 이벤트 핸들러는 예를 들어 버튼을 클릭할 때와 같이 어떤 작업을 수행할 때 React가 실행하는 함수입니다. 이벤트 핸들러는 컴포넌트 내부에 정의되어 있지만 렌더링 중에는 실행되지 않습니다! 따라서 이벤트 핸들러는 순수할 필요가 없습니다.
다른 모든 옵션을 모두 사용했고 side effect에 대한 올바른 이벤트 핸들러를 찾을 수 없는 경우 컴포넌트에서 useEffect 호출을 사용하여 반환된 JSX에 연결할 수 있습니다. 이렇게 하면 side effect가 허용될 때 렌더링 후 나중에 실행하도록 React에 지시합니다. 그러나 이 방법은 최후의 수단이어야 합니다.
 

복습 및 마무리!

  • 컴포넌트는 순수해야 합니다. 즉, 다음을 의미합니다:
    • 그것은 자신의 역할만 수행합니다. 호출되기 전에 존재했던 객체나 변수는 변경하지 않습니다.
    • 동일한 입력, 동일한 출력. 동일한 입력이 주어지면 순수 함수는 항상 동일한 결과를 반환해야 합니다.
  • 렌더링은 언제든지 발생할 수 있으므로 컴포넌트는 서로의 렌더링 순서에 의존해서는 안 됩니다.
  • 컴포넌트가 렌더링에 사용하는 입력을 변경해서는 안 됩니다. 여기에는 props, state 및 context가 포함됩니다. 화면을 업데이트하려면 기존 개체를 변경하는 대신 state를 "설정(set)"하십시오.
  • 반환하는 JSX에서 컴포넌트의 논리를 표현하도록 노력하십시오. "변경"이 필요한 경우 일반적으로 이벤트 핸들러에서 변경하는 것이 좋습니다. 최후의 수단으로 useEffect를 사용할 수 있습니다.
  • 순수 함수를 작성하려면 약간의 연습이 필요하지만 React 패러다임의 힘을 잠금 해제합니다.
 

References