쉽고 빠르게 커링(Currying) 기법 이해하기(feat.자바스크립트)

커링(Currying) 기법의 이해와 사용, 그리고 장단점

커링(Currying)은 카레와 같은 스펠링을 갖고 있지만 그 유명한 하스켈(Haskell)이라는 이름의 출처인 수학&논리학자 하스켈 브룩스 커리(Haskell Brooks Curry)의 성(Family Name)입니다.

커링은 하나 이상의 매개변수(Parameter)를 갖는 함수를 부분적으로 나누어 각각 단일 매개변수를 갖는 함수로 설정하는 기법입니다.

식으로 설명하면 다음과 같습니다.

func(a, b, c) -> f(a)(b)(c)

위 식을 코드로 변경하면 함수를 리턴하면서 다음 매개변수를 받아 또 함수를 리턴합니다.

//non-curried
function plusFunc(a, b, c){
  console.log(a + b + c);
}

plusFunc(1, 2, 3);   // 6

//curried
function plusFunc(a){
    return function(b){
       return function(c){
          console.log(a + b + c);
       }
    }
}

plusFunc(1)(2)(4);  // 7

논커리와 커리의 차이를 확인해 보겠습니다.

non-curried : 함수 실행 시 파라미터가 모자라도 문제 없이 실행이 가능함
👉 함수 정의 : func(a, b, c)
👉 함수 실행 : func(a)
👉 실행 결과 : func(a, undefined, undefined)

curried : 함수가 인수를 전부 받을 때까지 실행을 보류함.
👉 함수 정의 : func(a, b, c)
👉 함수 실행 : func(a)
👉 실행 결과 : func(a)상태에서 b 함수 입력 대기

위 특징에 따라 다음과 같이 사용할 수 있습니다.

function setAppell(appell){
    return function(name) {
       console.log(appell + name);   
    }
}

const setName = setAppell('Mr.');
setName('Right');  // Mr.Right
setAppell('Mr.')('Baker'); //Mr.Baker

//ES6 화살표 함수
const setAppell = appell => name => console.log(appell + name);

const setName = setAppell('Miss.');
setName('Dior');  // Miss.Dior

또한 다음과 같이 이벤트와 파라미터를 동시에 전달할 때도 유용합니다.

const setNumber = (num) => (event) => {
     console.log(event.target.id+':'+num);
}

<div onClick="setNumber(2)(event)" id='myNum'>
  click
</div>

// myNum:2

그럼 커링의 장점을 요약하면 무엇이 있을까요?

장점👍
재사용 가능
– 생산성 향상(코드 양 감소)
– 가독성 향상

그렇다면 커링의 단점은 무엇일까요?

단점👎
– 함수가 깊이 깊이 중첩되면 메모리를 과다하게 점유할 가능성이 있는 것과 같은 맥락에서 커링을 과용하면
메모리와 속도에 문제점 발생 가능성이 있음

커링의 수학적인 설명이 필요하신 분을 위한 링크



커링을 사용하지 않아도 원하는 기능을 대부분 구현할 수 있지만 재사용성이나 가독성에 따른 생산성 향상을 위해 커링을 사용하면 좋은 효과를 기대할 수 있습니다. 커링을 직접 구현하지 않더라도 커링의 개념과 사용법에 대해 알아두면 다른 사람의 코드를 읽거나 로직을 이해하는데 큰 도움이 될 것이라 생각합니다.

리액트(React) 컴포넌트, 라이프사이클의 과거와 현재(useEffect)

클래스형 컴포넌트와 함수형 컴포넌트의 라이프사이클과 이벤트

1. 클래스형 컴포넌트와 라이프사이클(Life-cycle)

라이프사이클 이벤트는 컴포넌트의 렌더링과 DOM 이벤트 등을 의도대로 관리할 수 있으며, 이를 통해 성능을 개선할 수 있습니다.

클래스형 컴포넌트의 라이프사이클은 다음과 같이 세 가지로 분류하며, 각 메소드는 자주 사용하거나 유의할 부분만 정리하겠습니다.


  • 마운팅(Mounting) – 컴포넌트가 생성될 때 한 번만 실행(아래 순서대로 실행)

– constructor()

UNSAFE_componentWillMount() -> version 17부터 공식적으로 권장X

render()

– componentDidMount()

  • 업데이트(Update) – props 또는 state가 변경될 때마다 실행

UNSAFE_componentWillReceiveProps() -> 권장X

UNSAFE_componentWillUpdate() -> 권장X

render()

– componentDidUpdate(prevProps, prevState, snapshot)

  • 언마운팅(Unmounting) – 컴포넌트가 제거될 때 한 번만 실행

– componentWillUnmount()


위와 같이 각각의 컴포넌트는 [ 마운팅 -> 업데이트(반복) -> 언마운팅 ]의 라이프사이클을 갖습니다.

이전에는 직관적인 이름을 갖는 다양한 메소드(componentWillMount, componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate)를 통해 이벤트 발생 시점마다 세세한 조작이 가능했으나 현재는 버그나 안전성의 이유로 점점 더 단순해지고 있습니다.

위 기능을 모두 대체하는 훅(Hook)이 너무 편해서 클래스형 컴포넌트를 반드시 사용해야 하는 상황이 아니라면 함수형 컴포넌트와 함께 useEffect를 사용하는 방법이 권장되고 있습니다.

라이프사이클 별로 메소드를 사용하는 방법은 다음과 같습니다.

class TestList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter:0
    }
  }

  // 마운팅 직후 실행
  // 마운팅 전 실행은 constructor() 사용
  componentDidMount(){
     console.log('component Mounted');
  }

  // 업데이트 후 이전 데이터를 파라미터로 가져옴
  componentDidUpdate(prevProps) {
     if(prevProps !== this.props) {
            this.setState({counter:++this.state.counter})
     }
  }

  // 언마운팅 직전 실행
  componentWillUnmount(){
     console.log('component Unmounted');
  }

  render() {
    return (
      <div>{this.state.counter}</div>
    );
  }
}

마운팅 이벤트는 실제 DOM에 컴포넌트를 추가하는 이벤트입니다.

마운팅 이벤트는 다른 프레임워크나 라이브러리 또는 데이터와 연결하는 작업에 적절합니다.

업데이트 이벤트는 컴포넌트의 업데이트와 관련이 있으며, props, state 등의 변경이 있을 때 렌더링 관련한 작업을 설정합니다.

언마운팅은 DOM에서 요소를 분리하거나 제거하는 이벤트입니다.

언마운팅 이벤트는 타이머 제거, 요소 제거, 이벤트 제거 등 설정한 요소의 정리, 제거에 사용합니다.


2. 함수형 컴포넌트와 useEffect(Hook)

리액트 16.8부터 추가된 훅(Hook)은 클래스를 사용하지 않아도 state 또는 리액트의 여러 기능을 편하게 사용하도록 해주는 기능입니다.

이 중 useEffect 훅은 라이프사이클과 관련이 있는데요.

리액트 공식 문서에서 useEffect를 위와 같이 설명합니다.

즉 componentDidMount, componentDidUpdate, componentWillUnmount를 모두 합쳐 놓은 것과 같은 기능을 하는 것이 useEffect입니다.

모두 합쳐 놓았지만 작성 방법에 따라 각각의 기능을 구현할 수 있습니다.

먼저 함수의 시그니쳐는 다음과 같은 모습이며, 첫 번째는 실행할 함수, 두 번째는 조건을 배열로 전달하며 두 번째 매개변수는 생략이 가능합니다.

useEffect(함수, 배열);

  useEffect(() => {
      console.log('useEffect is working');
  });

– 마운팅만 설정(=componentDidMount)

마운팅 시에만 실행하고 싶은 경우에는 두 번째 매개변수로 빈 배열을 전달하면 됩니다.

function testStatus(props) {

  useEffect(() => {
      console.log('useEffect on mounting');
  },[]);

  return <div>test</div>;
}

– 언마운팅만 설정(=componentWillUnmount)

언마운팅 시에만 실행하고 싶은 경우에는 함수를 리턴하면서 두 번째 매개변수로 빈 배열을 전달하면 됩니다.

function testStatus(props) {

  useEffect(() => {
      
      console.log('useEffect on mounting');

      return () => {
        console.log('useEffect on unmounting');
      }

  },[]);

  return <div>test</div>;
}

– 데이터 업데이트마다 설정(=componentDidUpdate)

useEffect에서 두 번째 매개변수를 전달하지 않으면 렌더링마다 해당 훅이 실행됩니다.

하지만 관련 없는 데이터로 인한 리렌더링에도 훅이 실행되면 의도치 않은 결과를 낳거나 성능의 저하를 불러올 수 있으므로 실행의 기준이 되는 데이터를 지정하고 실행 시점을 설정할 수 있습니다.

이를 위해서는 두 번째 매개변수인 배열에 체크할 데이터를 넣으면 됩니다.

function testEffect(props) {

  useEffect(() => {
      console.log('re-rendering');
  });

  useEffect(() => {     
      console.log('props is changed');
  },[props.data]);

  return <div>test</div>;
}


useEffect를 사용하면서 몇 번의 시행착오만 거치면 원하는 기능을 어느 정도 구현할 수 있습니다.

하지만 라이프사이클에 대한 이해 없이 계속 사용한다면 어느 순간 ‘시간을 갈아 넣어 짠 코드’는 시간 앞에 무릎을 꿇게 될 수도 있을 것 같습니다.