React funtion component를 호출하면 안되는 이유

2yunseong·2023년 4월 17일
0

갑자기 이런 의문이 들었다.

리액트 함수형 컴포넌트를 호출해서 사용하지 않고 왜 JSX를 사용하는가? 결국은 똑같은 것 아닌가?

왜냐하면 함수가 리턴하는 값은 얼핏보아 똑같아 보이기 때문이다.

이러한 궁금증을 해결해줄 아티클을 보았다. 원문 링크

function Counter() {
  const [count, setCount] = useState(0)
  const increment = () => setCount(c => c + 1)
  return <button onClick={increment}>{count}</button>
}

function App() {
  const [items, setItems] = useState([])
  const addItem = () => setItems(i => [...i, {id: i.length}])
  return (
    <div>
      <button onClick={addItem}>Add Item</button>
      <div>{items.map(Counter)}</div>
    </div>
  )
}

다음과 같은 코드가 있다. 얼핏 보면 잘 동작할 것 같지만, 잘 동작하지 않는다.

map함수에 전달한 컴포넌트가, 콜백으로 쓰이니 정상적으로 동작해야 되지 않는가? 똑같은 코드가 아닌가 싶을 것이다.

그럼, 저 코드를 리팩터링 해보자. Counter를 inline 해보는 것이다.

function Counter() {
  const [count, setCount] = useState(0)
  const increment = () => setCount(c => c + 1)
  return <button onClick={increment}>{count}</button>
}

function App() {
  const [items, setItems] = useState([])
  const addItem = () => setItems(i => [...i, {id: i.length}])
  return (
    <div>
      <button onClick={addItem}>Add Item</button>
      <div>{items.map(function () {
			  const [count, setCount] = useState(0)
			  const increment = () => setCount(c => c + 1)

			  return <button onClick={increment}>{count}</button>				
			}}</div>
    </div>
  );
}

여기서 눈치가 빠른사람은 쉽게 알 수 있다. 바로 Hook의 규칙을 위배하고 있었던 것이다.

map 안의 Counter 함수의 useState가 동적으로 불러지는 것이다.

정확한 동작원리는 모르지만, 공식문서에 따르면 React가 특정 state가 어떤 useState 호출에 해당하는지 알 수 있는건 React가 호출되는 순서에 의존한다는 것이다.

즉, JSX를 써서 명확하게 컴포넌트임을 알려주어야 한다. 이 동작은 Babel이 도와준다.

차이를 살펴보자.

// Use Counter as function
function App() {
  const [items, setItems] = useState([]);
  const addItem = () => setItems(i => [...i, {
    id: i.length
  }]);
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
    onClick: addItem
  }, "Add Item"), /*#__PURE__*/React.createElement("div", null, items.map(Counter)));
}

// Use Counter as Component(JSX)
function App() {
  const [items, setItems] = useState([]);
  const addItem = () => setItems(i => [...i, {
    id: i.length
  }]);
  return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
    onClick: addItem
  }, "Add Item"), /*#__PURE__*/
}

겹치는 코드가 있으므로 알짜배기만 살펴보면..

// Use Counter as function
React.createElement("div", null, items.map(Counter)));
// Use Counter as Component 
React.createElement("div", null, items.map(() => React.createElement(Counter, null))));

ReactElement가 전달되냐, 표현식이 전달되냐의 큰 차이가 있다고 본다. 아직은 잘 모르지만 동작원리에서 큰 차이가 있지 않을까?

물론.. 아래 예시처럼 hook이 있지않다면 상관은 없다. 근데… 당연하게도 앞선 사례처럼 예기치 못한 작동방식을 우려할 수 있다.

const Dummy = () => {
   return <div>dummy</div>;
}

function App() {
  return (
    <div>
      {Dummy()}
      {Dummy()}
      {Dummy()}
      {Dummy()}
    </div>
  )
}

reference

https://kentcdodds.com/blog/dont-call-a-react-function-component
https://ko.reactjs.org/docs/hooks-rules.html

profile
개발 발자국 남기기

0개의 댓글