본문 바로가기

Study/React

[React Native] useEffect의 dependency array 사용 시 주의점

useEffect란?


useEffect Hook은 클래스형 컴포넌트에서 쓰이던 componentDidMountcomponentDidUpdate, componentWillUnmount가 합쳐진 형태로 최초 렌더링 시 무조건 실행할 작업, 컴포넌트가 업데이트 될 때 작업, 컴포넌트가 언마운트(파괴, 제거)되었을 때 작업을 실행하게 해준다.

 

useEffect 사용 예시


useEffect는 앞서 언급한 것처럼 최초 렌더링시, 컴포넌트 업데이트시, 컴포넌트 언마운트시 실행되는데 실행되는 내용은 useEffect의 디폴트 파라미터인 콜백함수 내부에 들어가게 된다.

 

예를 들면

 

import React, { useState, useEffect } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const [appTitle, setAppTitle] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button
        onClick={() => {
          setCount(count + 1);
          setAppTitle(count);
        }}
      >
        Click me
      </button>
      <p>{appTitle}</p>
    </div>
  );
}

 

버튼을 클릭할 때마다 count가 1씩 증가하게 되고 증가된 값은 appTitle에 저장되어 화면에 나타난다.

 

단, appTitle에 보이는 것은 count의 값보다 1 작게 나타낼 것이다. countappTitle은 공통적으로 0부터 시작하지만 count는 값이 증가하면서 리렌더링 되는 반면 appTitle은 기존의 count 값을 갖고 있는 상태이기 때문에 count 값이 바뀌더라도 바로 렌더링 되지 않는다.

 

그래서 count 값이 바뀔 때마다 appTitle의 값을 같은 값으로 변경해주기 위해서 useEffect 훅이 필요한 것이다.

 

import React, { useState, useEffect } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const [appTitle, setAppTitle] = useState(0);

  useEffect(() => {
    setAppTitle(count);
    console.log(count);
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <p>{appTitle}</p>
    </div>
  );
}

 

이렇게 바꿔주면 count 변수의 변화에 따라 appTitle 변수도 함께 변하게 된다. 다만 componentDidMountcomponentDidUpdate가 함께 작동하기 때문에 콘솔을 확인해보면 두번 실행됨을 알 수 있다.

 

그래서 dependency array를 두번째 파라미터로 추가하여 count 값이 변경될 때만 이를 감지하여 useEffect 내에 있는 함수를 호출하게 된다.

 

import React, { useState, useEffect } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const [appTitle, setAppTitle] = useState(0);

  useEffect(() => {
    setAppTitle(count);
    console.log(count);
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <p>{appTitle}</p>
    </div>
  );
}

 

주의할 점


만약 axios 모듈을 통해 비동기로 처리되어 List 형태의 데이터가 넘어온다고 가정할 때, List 내부 아이템이 바뀌는 경우 의존성 배열에 해당 리스트 변수를 넣어주면 무한루프를 돈다. useEffect의 의존성 배열에는 반드시 state만 존재해야 한다.

 

아래는 React Native 예시, false로 세팅되어 있던 값을 버튼 클릭시 true로 바꿔주고 useEffect를 통해 false로 컴포넌트를 언마운트하게 하여 배열 안에 있는 객체의 값이 바뀌었을 때만 리렌더링 할 수 있도록 한다. (무한루프 방지)

 

// ...

export default function App() => {
  const [completed, setCompleted] = useState(false);
  const [data, setData] = useState([]);

  const Item = ({/*parameters...*/}) {
    return (
      { // 백엔드로부터 넘어온 paymentDel(구매 취소여부 체커)이 0이면 구매완료 상태이므로 환불접수 버튼을 보여줌.  
        del === 0
          ? <TouchableOpacity 
              style={styles.refundBtn}
              onPress={() => {
                Alert.alert("환불접수", "환불을 접수하시겠습니까?", [
                  {text: "예", onPress: () => {
                    confirm = true;
                    console.log(`confirm: ${confirm}`);
                    onClickOrderCancel(confirm);
                    setCompleted(true)
                  }},
                  {text: "아니오"}
                ]);
              }}
            >
              <Text style={{color: '#fff'}}>환불접수</Text>
            </TouchableOpacity>

          : <Text />
      }
    )
  }

  // ...

  useEffect(() => {        
    //Alert.alert("useEffect start", "useEffect 실행")
    const fetchData = async () => {
      await axios.get(
        "http://192.168.0.13:3000/payment/goodsPurchaseList", 
        { params: { memberId: userId }
      })
      .then((res) => {
        console.log(res.data);
        setData(res.data);
      })
      .catch((err) => console.log(err));
    }

    fetchData();

    return () => {
      setCompleted(false)
        //Alert.alert("useEffect", "useEffect 실행")
    }

  }, [completed]);

  // ...
}