리액트(React) - 고차 함수, 커링(Currying) 기법
고차 함수(Higher-Order Function)와 커링(Currying) 기법이란?
이 두 가지 개념은 리액트에서 정말 많이 사용되는 개념입니다.
우선 고차 함수는 함수를 인자로 전달받거나 리턴값으로 함수를 반환하는 함수를 의미합니다. 파이썬의 데코레이터(Decorator)와 비슷한 개념이라고 생각하시면 됩니다.
커링(Currying) 기법은 인자가 여러개인 함수의 일부 인자를 고정시키는 새로운 함수를 만드는 기법을 의미합니다. 인자가 n개인 함수를 n개로 분리하여 사용하게끔 만드는 기법입니다.
일단 이 정도로만 알고, 자세한 내용은 천천히 설명 드리겠습니다.
고차 함수(Higher-Order Function)
앞서 말씀드렸듯이 고차 함수는 함수를 인자로 전달받거나 리턴값으로 함수를 반환하는 함수를 의미합니다.
파이썬 문법에 익숙하신 분은 데코레이터 게시물을 참조해서 이해하시면 됩니다.
고차 함수는 리액트에서 고차 컴포넌트(High-Order Component)의 형태로 자주 사용되며, 반복되는 특정 로직을 쉽게 재사용하기 위해 사용합니다.
(고차 컴포넌트에 대해서는 따로 다루겠습니다.)
const sayHello = function () { #1
console.log("Hello");
};
const func1 = function (func) { #2
function innerFunc(word) {
func(); #3
console.log(word);#4
}
return innerFunc; #5
};
const worldHello = func1(sayHello); #6
worldHello("world"); #7
// Hello
// world 출력
고차 함수의 특징을 모두 보여주는 간단한 예제입니다.
#1. 우선 고차 함수의 인자로 선언할 함수를 하나 선언했습니다. 이 함수는 단순히 console.log를 통해 Hello를 출력합니다.
#2. 고차 함수 func1을 선언했습니다. 인자로 func라는 함수를 받고 내부에 함수를 하나 가지고 있습니다. 그리고 그 내부 함수를 리턴합니다.
#3. func1의 내부 함수 innerFunc에서 인자로 받았던 func 함수를 실행합니다.
#4. 그 후 console.log를 통해 innerFunc의 인자로 받은 word를 출력합니다. 이 과정을 통해 처음 인자로 받은 func 함수에 새로운 로직을 추가해 주었습니다.
#5. innerFunc를 리턴함으로써 고차 함수를 사용할 수 있게 만듭니다. 이 innerFunc는 func를 클로저로 가지게 됩니다.
#6. worldHello라는 상수에 함수를 할당합니다. 그 값은 func1에 인자로 sayHello를 전달한 결과입니다.
#7. worldHello에 인자로 world라는 문자열을 전달합니다. 이 문자열은 아까 보았던 innerFunc의 word 인자로 들어가게 됩니다.
const func1 = (func) => (word) => {
func();
console.log(`${word}`);
};
위에서 본 func1의 화살표 함수(Arrow Function) 버전입니다. 동작은 아까와 같으나 훨씬 가독성이 좋은 것 같네요.
커링(Currying) 기법
위에서 잠시 말씀드렸듯 커링(Currying) 기법은 인자가 여러개인 함수의 일부 인자를 고정시키는 새로운 함수를 만드는 기법입니다.
도대체 무슨 말인지 이해가 안되실테니 코드로 살펴봅시다.
function helloFunc(word, name) {
console.log(`${word}, ${name}`);
}
word와 name이라는 두 개의 인자를 받아서 출력해주는 단순한 형태의 함수입니다.
이 함수에 커링을 적용해 봅시다.
function helloFunc(word) {
return function (name) {
console.log(`${word}, ${name}`);
};
}
const printHello = helloFunc("hello");
printHello("Tibetan Fox"); // hello, Tibetan Fox
printHello("Tiger"); // hello, Tiger
아까의 함수에 커링을 적용하면 이렇게 됩니다.
n(2)개의 인자를 받던 함수가 n(2)개로 쪼개진 것을 볼 수 있습니다.
또한 첫 번째로 받던 인자인 word를 hello라는 값으로 고정하고 name만 변경하면서 사용 가능한 것 또한 볼 수 있습니다.
즉 커링 기법은 일부 인자에 같은 값을 반복적으로 사용할 때 그 반복되는 인자를 고정함으로써 중복을 최소화 하기에 적합한 기법입니다.
function helloFunc(word, name, word2, name2) {
console.log(`${word}, ${name} || ${word2}, ${name2}`);
}
좀 더 어려운 예제를 살펴봅시다.
위 함수는 인자가 무려 4개나 됩니다. 여기에 커링을 적용하면?
function helloFunc(word) {
return function (name) {
return function(word2){
return function(name2){
console.log(`${word}, ${name} || ${word2}, ${name2}`)
}
}
};
}
정말 변태같은 모습으로 변했습니다.
커링 기법 자체가 부분 부분 나눈 함수를 체인으로 생성하여 사용하게끔 하는 방식이다보니 어쩔 수 없이 이런 변태같은 모습이 되어버린 것 같습니다.
const printHello = helloFunc("hello")("Tibetan Fox")("Good morning");
printHello("Tiger"); // hello, Tibetan Fox || Good morning, Tiger 출력
printHello("Ant"); // hello, Tibetan Fox || Good morning, Ant 출력
다행히 사용법은 어렵지 않습니다.
위와 같이 체이닝을 통해 3개의 인자를 고정하고 마지막 인자만 유동적으로 사용할 수 있습니다.
모습은 끔찍한데 생각보다 순박한 친구였네요..
const printHello = helloFunc("hello")("Tibetan Fox")("Good morning")("Tiger"); // 직접호출
////////////////////////////////////////////////////
const printHello = helloFunc("hello")("Tibetan Fox"); // 두 개의 인자만 고정
printHello("Good morning")("Tiger");
printHello("Good evening")("Crow");
물론 이렇게 직접 커링 함수를 호출하거나 변경할 인자를 적절히 증감할 수도 있습니다.
const helloFunc = (word) => (name) => (word2) => (name2) => {
console.log(`${word}, ${name} || ${word2}, ${name2}`);
};
참고로 위에서 잠시 봤던 화살표 함수를 사용하면 좀 덜 변태같은 모습으로 만들어 줄 수 있습니다.
주의할 점
커링 기법을 적용할 때는 인자의 순서가 매우 중요합니다. 변동 가능성이 적은 인자는 앞에, 변동 가능성이 높은 인자는 뒤에 배치해야 합니다. 반드시 이 점을 고려하면서 커링을 사용해야 합니다.