본문 바로가기
📄Today I Learned

React에서 중복되는 컴포넌트 리팩토링과 함수 전달 방식의 차이점

by 영진학생 2025. 2. 6.

💡오늘의 한마디

트러블 슈팅으로 addPokemon과 deletePokemon의 함수 전달 방식이 달랐던 이유에 대해 명확하게 알게 되었고 단순히 리팩토링을 하는게 아니라 우리가 개발자로서 왜 리팩토링을 해야하는지에 대해 생각할수 있는 기회가 되었다.


🔥 React 컴포넌트 리팩토링: 중복 제거와 함수 전달 방식 이해하기


문제 상황

처음에는 PokemonList와 DashBoard에서 하드 코딩으로 각각 별도의 PokemonCard 컴포넌트를 사용하고 있었다. 하지만, 동일한 UI 구조를 여러 곳에서 재사용하면서 불필요한 중복 코드가 발생했기 때문에 컴포넌트의 재사용성을 높이기 위해 PokemonCard 하나로 통합해야겠다고 마음먹었다. 그래서 PokemonCard 컴포넌트 내부에 if else문을 사용해서 boolean으로 true일땐 ADD버튼을 false일땐 Delete버튼을 동작시키게 만들어 볼까 생각도 했는데 각 부모 컴포넌트에서 ADD 또는 DELETE 로직을 제어하는 방법이 컴포넌트의 재사용적인 측면에서 조금 더 우리가 컴포넌트를 사용하기 위한 이유를 더 잘 설명해주지 않을까? 라는 의문이 들었다. 하지만 코드를 리팩토링 하는 과정에서 DELETE 버튼이 정상적으로 동작하지 않는 문제가 발생했다.


문제 발생: DELETE 버튼이 작동하지 않음

ADD 버튼은 정상 동작했지만, DELETE 버튼이 동작하지 않았다.

이상했던 점은 PokemonList에서는

clickButton={addPokemon}

이렇게 함수명만 전달하면 됐는데,

DashBoard에서는

clickButton={(pokemon) => deletePokemon(pokemon.id)}

이런 방식으로 파라미터를 직접 전달하는 함수를 사용해야만 했는데.. 왜 addPokemon은 그대로 전달할 수 있고, deletePokemon은 이렇게 사용해야 할까?
.


함수 전달 방식 차이점 분석

함수 참조 전달 (Function Reference)

clickButton={addPokemon}

함수 자체를 그대로 전달하는 방식으로 clickButton이 실행될 때 addPokemon(pokemon)이 실행된다는 의미이다.
즉, clickButton={addPokemon} 와 clickButton={(pokemon) => addPokemon(pokemon)} 는 같은 의미를 가진다.

const addPokemon = (pokemon) => {
    setSelectedPokemon([...selectedPokemon, pokemon]); // 포켓몬 전체 객체를 추가
};

addPokemon은 새로운 포켓몬을 추가하는 함수이기 때문에 포켓몬의 모든 정보(img, name, types, id)가 있으면 나중에 다른 기능을 구현하는데 큰 이점이 있다. img가 필요하면 img만 빼내면 되고 types 이나 id 가 필요하다면 필요한 것만 빼내서 화면에 보여주기만 하면 되기 때문이다. 물론 내가 작성한 코드에서는 객체의 모든 정보를 많이 필요로 하진 않지만 협업을 한다면 이러한 사고방식이 큰 도움이 될거라 믿는다 😊

아래 코드처럼 즉시 실행되는 함수를 전달하는 실수를 자주 하기도 하는데

clickButton={addPokemon(pokemon)}

이렇게 작성하면 clickButton이 함수가 아니라 함수 실행 결과값(반환하는 값)을 전달하게 된다.
즉, 버튼을 클릭하기도 전에 함수가 실행돼버리는 문제가 발생한다.


그렇다면.. DELETE 로직에서는 왜 id만 전달해야 할까?

clickButton={(pokemon) => deletePokemon(pokemon.id)}

이 방식은 함수를 실행하는 새로운 래퍼(wrapper) 함수를 만들어 전달하기 때문에 코드를 작성하는 사람이 원하는 파라미터 값을 넣어서 clickButton(pokemon)이 호출될 때, deletePokemon(pokemon.id)이 실행되게 할수 있어서 React를 처음 배울때 부터 자주 사용해온 방법이다.

const deletePokemon = (id) => {
    setSelectedPokemon(selectedPokemon.filter(pokemon => pokemon.id !== id));
};

그리고 잘 생각해보면, deletePokemon은 포켓몬을 삭제하는 함수이므로, 포켓몬의 id 값만 알면 삭제하는 데 문제가 없다. 객체를 솎아 낼수 있는 객체마다 가지고 있는 고유한 값을 알기만 하면 되지 않을까? 무언가를 추가할 때는 여러가지 정보를 가지고 있으면 장점이 되지만 그냥 filtering으로 인해 특정 객체를 제거하는 로직을 구현하는 데에는 많은 정보가 필요가 없을 뿐만 아니라 오히려 독이 될수도 있다.


id만 전달하면 좋은 3가지 이유

  1. 가독성과 의도 명확성
    • deletePokemon(id)는 포켓몬을 삭제하는 함수가 실행될 때 내가 id만 사용하겠다는 의도를 더 명확하게 표현할 수 있음.
    • 반면 deletePokemon(pokemon)은 전체 객체를 전달하지만, 실제로 필요한 것은 id뿐이므로 불필요한 데이터가 포함된다.
  2. 불필요한 데이터 전달 방지
    • deletePokemon(pokemon) 방식에서는 불필요한 데이터를 함수에 넘기게 된다. 물론 이런 작은 개인 프로젝트에서는 크게 문제가 되지 않겠지만 팀 프로젝트를 하거나 현업에서 일하게 된다면 사소한 데이터낭비 하나하나가 모여서 성능을 충분히 저하시킬 수 있다.
    • 리스트에서 특정 id를 가진 포켓몬을 필터링하는 과정에서 오직 id 값만 있으면 되니, 전체 객체를 넘길 필요가 없다.
  3. 함수의 일관성 유지
    • 대부분의 삭제 로직에서는 ID를 기반으로 항목을 찾고 삭제하는 것이 일반적이고 다른사람이 코드를 봐도 이해하기가 쉽다.

📌결론

deletePokemon(pokemon)으로도 동작하게 만들 수 있지만, id만 넘기는 방식이 더 효율적이고 유지보수하기 쉽다!