setState Props로 넘길까 말까?

Sally·2023년 2월 11일
2

What-I-Learn

목록 보기
5/6
post-thumbnail

지금까지 가끔 setState를 Props로 넘길 때가 있었다.
주로, 부모와 자식 컴포넌트 간에 동일한 상태를 공유하거나
자식컴포넌트 간에 동일한 상태를 공유해야할 때에 말이다.

하지만 마음에 들지 않았다...

묘하게 찝찝한 이 마음 나는 왜 setState를 그대로 Props로 넘기는 것을 싫어했을까

setState 그대로 props에 넘겨도 되는 걸까?


예를 들어, 부모 컴포넌트에서 toggle 상태를 관리해야하고
(toggle 여부에 따라 다른 상태가 제어되서 부모컴포넌트에서 반드시 가지고 있어야한다고 생각하자)

버튼 컴포넌트에서는 버튼을 누를 때마다 toggle 여부 상태가 변경되고 현재 toggle 상태를 보여주어야 한다고 가정해보자

setState를 바로 props로 넘긴다면 아래와 같이 구현하게 될 것이다.

const Container = () => {
	const [toggle, setToggle] = useState(false);
    
    return (
    	<Container>
        	<Button state={toggle} setState={setToggle}/> 
        </Container>
    )

}


const Button = ({state, setState}) => {
	const onClick = () => {
    	setState(!state);
    }

	return (
    	<button onClick={onClick}>{state}</button>
    )

}

이런 방식은 나름 장점도 가진다.

  1. 로직 구현부를 자식컴포넌트 단에 숨기면서 부모 컴포넌트 단이 깔끔해질 수 있다.
  2. 만약 Button에서 Button의 상태와 부모 상태를 같이 관리하는 함수를 구현해야한다면, 구현이 쉬워진다.
  3. props의 네이밍 고민에서 자유로워질수 있다.

난 왜 고민하는가 🤔

setState 로직을 자식컴포넌트에 넘기는 것에 반대하는 것은 아니다.
그저 예시처럼setStatesetState라는 이름 그대로 props로 넘기는 것에 반대한다.

코딩을 할 때 다른 사람들이 보았을 때 이해가 쉽도록 하는 것이 제일 우선시 되어야 한다고 생각한다.

이는 물론 다른 이와 협업할 때에도 도움이 되지만, 시간이 지나 내가 나의 코드를 다시 볼 때에도 이 원칙은 큰 도움이 된다.

그런 의미에서 setState를 그대로 Props로 넘기는 것은 이 원칙에서 벗어난다.

예시를 다시 한 번 살펴보자

const Container = () => {
	const [toggle, setToggle] = useState(false);
    
    return (
    	<Container>
        	<Button state={toggle} setState={setToggle}/> 
        </Container>
    )

}

해당 컴포넌트를 보고 Button컴포넌트가 setToggle을 가지고 어떤 Action을 수행하는지 바로 알 수 있는가?

아마도 컴포넌트의 네이밍을 통해서 Button을 누르면 toggle이 발생하는구나! 라고 예측을 할 수 있을 것이다.
하지만 이것은 Button이라는 이름의 특수성에 있는 것이다.

네이밍이 Button이 아니고 Profile이나 Card 등이라면 그 때에도 setState로 무엇을 할 지, 언제 setState가 사용될지 Container컴포넌트에서 바로 알아차릴 수 있을까?

대부분은 바로 알아차리지 못하고 자세한 내용을 자식 컴포넌트 즉 setState를 props로 넘겨받은 컴포넌트 단에 이동해야 setState를 왜 넘기는지 알 수 있게 될 것이다.

결론적으로 저는 setState 그대로 props에 넘기는 것에 반대합니다.

그러면, setState를 어떻게 넘겨주어야 할까?
자식 컴포넌트에서 부모 컴포넌트의 상태를 조작해야할 상황은 분명히 있을 수 있는데 말이다.

개인적으로는 아래의 방법들을 사용하고 있다.

(더 좋은 방법이 있다면 댓글로 추천부탁드립니다🙇‍♀️)

setState의 이름을 바꿔서 넘기기

setState를 한 번더 함수로 감싸서 setState 그대로 자식 컴포넌트의 props로 넘기기 보다는 어떤 Action에 setState가 사용되는지 명시하는 것으로 개선할 수 있다.

예시1

const onToggleButtonClick = () => {
	setToggle(!toggle);
} 

<Button onToggleButtonClick={onToggleButtonClick()} /> 

예시2

<Button onToggleButtonClick={() => {setToggle(!toggle)} />

주로 클릭이벤트에서 코드 한줄만 적게 되어서 setState랑 뭐가 다를까 싶지만
setToggle이 어떤 Action에서 발생할 수 있는지 알 수 있기 때문에 이점 하나만으로 충분한 이유가 된다고 생각한다.

커링 함수를 이용하기

해당 방식은, 부모컴포넌트의 state와 자식컴포넌트에 종속된 state가 모두 한 Action에서 변경되어야 할 때 괜찮은 방법이라고 생각한다.

커링함수의 특징을 활용해서 자식 컴포넌트의 로직을 넘겨받아서 하나의 함수 안에서 모두 사용할 수 있다.

예를 들어 Button을 누를 때마다 Buttond의 컴포넌트에 보여지는 name이 달라진다고 가정해보자

const Container = () => {
  const [toggle, setToggle] = useState(false);

  const onToggleButtonClick = () => (
    setName: Dispatch<SetStateAction<string>>
  ) => {
    setToggle(!toggle);
    setName(toggle ? "hi" : "bye");
  };

  return (
    <div>
      <Button 
      onToggleButtonClick={onToggleButtonClick()} />
    </div>
  );
}

const Button = ({
  onToggleButtonClick
}: {
  onToggleButtonClick: 
  	(setName: Dispatch<SetStateAction<string>>) => void;
}) => {
  const [name, setName] = useState("hi");

  return (
    <button
      onClick={() => {
        onToggleButtonClick(setName);
      }}
    >
      {name}
    </button>
  );
};

자바스크립트에서는 함수가 일급 객체이기 때문에 함수를 다른 함수의 인자로 넘길 수 있다.
이 덕분에 자식 컴포넌트에서 구현한 함수를 인자로 넘겨받아 로직을 완성할 수 있다.

이렇게 되면, 자식 컴포넌트에서 사용되는 함수는 해당 컴포넌트에서 선언하고 넘겨줌으로써 응집성까지 챙길 수 있다.

유의할점! Hook 🪝
hook은 로직을 재사용하기 위해서 만들어졌다. 상태값을 공유하기 위함이 아니기 때문에 (사실 함수의 특징을 생각해보면 간단하다. 호출될 때마다 생성되고 사라지기 때문에) 같은 커스텀 훅을 부모와 자식 컴포넌트에 각각 호출하면 이 둘은 다른 상태값을 가지게 된다. 부모와 자식 컴포넌트간에 동일한 상태값을 공유하기 위해서라면 각각 커스텀 훅을 호출하는 것이 아니라 부모 컴포넌트에서 커스텀 훅을 호출해 자식 컴포넌트에 Props로 넘겨야 한다

전역상태관리 사용하기

Props drilling이 발생할 정도로 자식의 자식의 자식... 까지 props를 넘겨야 하거나 전역으로 관리해야하는 상태라면 전역상태 관리도구를 사용하여 상태관리를 하는 것이 좋다.

전역 상태관리 도구를 사용하게 되면 자식 컴포넌트 단에서 전역상태를 꺼내서 사용하면 되기 때문에 useState를 props로 넘겨야 하냐에 자유로워지게 된다.

0개의 댓글