Web Front-end/React

[React/flow/JS/Basic/hook] React의 Component의 흐름(Flow)

Component의 흐름??

프론트엔드에서는 "비동기"함수가 일상적입니다. 예를들어, addEventListener도 비동기함수이고, setInterval과 같은 이벤트 루프도 다 비동기 함수입니다. 즉, 나의 코드의 순서와 실행순서가 다르다는 이야기이고, 논리는 맞지만 실행순서가 의도와 다르게 될 수 있습니다.(생각보다 빈번하다)

 

따라서, React에서는 Component 간의 "흐름"을 잘 파악하는게 에러를 발생시키지 않을 수 있습니다.

React LifeCycle이라는 용어가 따로 있을정도로 , React에서는 이러한 흐름을 잘 제어하는게 ,프로그래머의 중요한 역량 중 하나입니다.

Like버튼 예제로 알아보자

Like 예제를 조금 변형해서 , Like 버튼을 누르면 , "좋아요를 눌렀어요!"라는 문구가 나오도록 만들고, useEffect도 사용해보고 , Component간의 부모-자식관계에서 어떻게 데이터흐름이 형성되는지 log를 찍어봅시다.

 

아래와 같이 간단한 코드를 작성해보겠습니다.

App.js

 * useState의 set함수에는 prev라는 이전값을 받아오는 Parmeter가 준비되어있습니다.

   const App = () =>
   {
      console.log("App Start!") //App 시작점 
      const [show,setshow] = React.useState(()=>{
        console.log("App useState");
        return false 
      }); //useState 호출, useState는 비동기이기 때문에, 흐름을 제어하기 위해 Lazy initialize를 해주었습니다.
      
      //useEffect , 사이드 이펙트 친구들의 흐름은??
      React.useEffect(()=>{
        console.log("App useEffect , no depth")
      })
      
      React.useEffect(()=>{
        console.log("App useEffect , Empty depth")
      },[])
      
      React.useEffect(()=>{
        console.log("App useEffect , [show]")
      },[show])
      
      const handleClick = () =>{
        setshow((prev)=>!prev);
      }
      console.log("App is END"); //App 끝 , return
      return(
        <>
          <button onClick={handleClick}>Like</button>
          {show ? <Count/>: null}
        </>
      );
    };
    ReactDOM.createRoot(rootEl).render(<App/>);

Count.js

const Count = () =>{
      console.log("count 시작!");
      const element= <p>좋아요를 눌렀어요!</p>
      console.log("count 끝!");
      return element;
    }

SandBox 환경에서 , 실행결과를 보고 , Log를 한번 출력해봅시다. 

See the Pen LifeCycle(example) by Doge (@DogeIsFree) on CodePen.

 

 

최초 Rendering시에 App Start -> useState -> App End -> useEffect 순서대로 실행되는걸 볼 수 있습니다. 그리고, Empty depth 또한 useEffect가 실행되는걸 볼 수 있습니다. 

그 다음, 이제 버튼을 클릭하면 어떻게 될까요?

아마, show의 조건에 따라서, Count Component가 실행이 되어질거고, Count Component가 시작되어 질겁니다.

한번 직접 클릭해보면, "앱 시작 -> App useState -> 앱 종료 -> Count 시작 ->count 종료 -> App의 useEffect 순서대로" 실행이 되어집니다. 이때, App의 useEffect에서 empty depth는 더 이상 실행이 되지 않는걸 볼 수 있습니다. 

Count에게 useEffect 적용 

Count Component에 useEffect와 useState를 사용하는 아래 코드를 추가해봅시다.

    const Count = () =>{
      console.log("count 시작!");
      // state 추가 
      const [show,setShow] =React.useState(()=>{
        console.log("Child useState")
        return ""
      })
      //use Effect 추가 
      React.useEffect(()=>{
        console.log("Child useEffect, Empty depth")
      },[])
      React.useEffect(()=>{
        console.log("Child useEffect, No depth")
      })
      React.useEffect(()=>{
        console.log("Child useEffect,[Show] ")
      },[show])
      //
      const element= <p>좋아요를 눌렀어요!</p>
      console.log("count 끝!");
      return element;
    }

결과는 의외였습니다.

" 앱 시작 -> 앱 종료 -> Count -> child useState -> count 끝 -> Count의 useEffect -> App의 useEffect"

App useEffect가 먼저 실행될 줄 알았는데, Child(count)의 useEffect가 먼저 실행되고, 부모의 useEffect는 제일 마지막에 실행이 되었습니다.

UseEffect의  CleanUp

useEffect는 Component의 실행이 끝나고 나서, 실행이 됩니다. useEffect에는 cleanUp 기능이 준비되어 있는데, useEffect의 Return문에 Clean Up을 위한 함수를 작성해서 넣어주면 됩니다.

Clean Up은 useEffect는 모든 렌더링이 끝나고 실행이 되기 때문에, "이전"의 상태를 기억하게 됩니다.  따라서, useEffect를 여러번 사용하거나(Update) , useEffect를 지우고 싶을때 사용하게 됩니다. 메모리 관리 측면이나, 이벤트루프와 같은 함수를 등록했던걸 정리하는데 주로 이용이 됩니다.

 

따라서, state 업데이트 -> ReRendering -> Clean up -> New useEffect .. 이런식으로 Flow가 생기게 됩니다.

이런 흐름은 , React가 Hook들의 불변성(기존의 것을 완전히 새로운 것으로 교체하는 방식 , 기존의 것을 수정하는게 아님)

을 지키기 위함으로 보입니다.

See the Pen LifeCycle(example) by Doge (@DogeIsFree) on CodePen.