93일차 TIL : 리액트 컴포넌트 코드 실행 순서

변시윤·2023년 1월 31일
0

내일배움캠프 4기

목록 보기
101/131

학습내용

  • 라이프사이클
  • useEffect에서의 실행 순서
  • useQuery에서의 실행 순서

체크리스트

특정 컴포넌트의 Mounting, Updating, Unmounting 이 일어나는 시점을 설명할 수 있다.
useEffect / useQuery가 있는 상태에서 리렌더링이 일어날 때의 코드 실행 순서를 설명할 수 있다.
setState 가 비동기적이며 batching 처리되는 개념에 대해 설명할 수 있다.


라이프사이클

컴포넌트가 Mount ➡️ Update ➡️ Unmount 되는 과정

-MountingUpdatingUnmounting
시기컴포넌트가 브라우저상에 처음 생성 됐을 때New Props, setState(), 강제 업데이트 등의 이유로 컴포넌트가 업데이트 됐을 때컴포넌트가 부라우저상에서 사라졌을 때
render첫 렌더링리렌더링X
side effectcomponentDidMountcomponentDidUpdatecomponentWillUnmount
side effect 시기mount 직후렌더링 이후unmount 직전

side effect는 useEffect의 영향을 받는다.


useEffect에서의 실행 순서

export default function Home() {

  console.log("최상단 - 렌더링 시작");
  const [string, setString] = useState("초기값");
  const [number, setNumber] = useState(0);
  console.log("string:", string);
  console.log("number:", number);

  return (
    <div>
      {console.log("return문 실행")}
    </div>
  );
}
  1. 최상단 - 렌더링 시작
  2. string: 초기값
  3. number: 0
  4. return문 실행

useEffect가 없는 경우엔 위와 같이 최상단부터 순서대로 코드가 실행된다. 이 과정이 Mounting 내에서 render까지 해당되는 과정이다.


componentDidMount

useEffect(()=>{
	// componentDidMount (최초의 컴포넌트를 렌더링 한 이후 실행)
}, [])

예제 코드에 useEffect를 적용하면 아래와 같은 순서로 실행된다.

export default function Home() {

  console.log("최상단 - 렌더링 시작");
  const [string, setString] = useState("초기값");
  const [number, setNumber] = useState(0);
  console.log("string:", string);
  console.log("number:", number);
  
     useEffect(() => {
       console.log("componentDidMount"); 
       setString("변경값");
       setNumber(100);
       console.log("비동기 테스트");
     }, []);
     
     return (
        <div>
          {console.log("return문 실행")}
        </div>
  	);
}
  1. 최상단 - 렌더링 시작
  2. string: 초기값
  3. number: 0
  4. return문 실행
  5. componentDidMount
  6. 비동기 테스트
  7. 최상단 - 렌더링 시작
  8. string: 변경값
  9. number: 100
  10. return문 실행

4번까지는 기본 예제와 같다. 5번부터는 최초 렌더링이 직후에 useEffect가 실행된 부분인데, setState에서 상태가 변화하였으므로 리렌더링이 일어났다.(=Updating)

batching 처리

setState 함수들이 console.log("비동기 테스트")보다 앞에 있음에도 늦게 실행되는 이유는 비동기 함수이기 때문이다. 비동기 함수는 같은 스코프 내에서 동기적인 코드가 먼저 실행된 후 마지막에 실행된다. 또한 동일한 스코프 내에서 2개 이상의 setState가 존재한다면 setState를 모아서 한 번에 모든 state를 변경한 후 리렌더링을 실행한다. 이것을 batching 처리라고 한다. batching 처리가 일어나지 않는다면 setState가 실행될 때마다 리렌더링이 일어나야 하므로 유저 경험적인 측면에서 바람직 하지 못하다.


componentDidUpdate

useEffect(()=>{
	// componentDidMount (최초의 컴포넌트를 렌더링 한 이후 실행)
    // componentDidUpdate (변경된 state로 컴포넌트 렌더링 된 이후 실행)
}, [state])

componentDidMount의 경우엔 의존성 배열이 비어있기 때문에 한 번만 일어나지만, componentDidUpdate는 의존성 배열의 state가 변경될 때마다 일어난다. 그러나 state가 변경되지 않더라도 마운트 직후에는 useEffect가 무조건 실행되기 때문에 componentDidUpdate에서 componentDidMount와 componentDidUpdate 두 가지 기능을 모두 수행한다.

export default function Home() {

  console.log("최상단 - 렌더링 시작");
  const [string, setString] = useState("초기값");
  const [number, setNumber] = useState(0);
  console.log("string:", string);
  console.log("number:", number);
  
     useEffect(() => {
       console.log("componentDidMount"); 
       setString("변경값");
       setNumber(100);
       console.log("비동기 테스트");
     }, []);
     
     useEffect(() => {
       console.log("componentDidMount or componentDidUpdate");
     }, [string]);
     
     return (
        <div>
          {console.log("return문 실행")}
        </div>
  	);
}
  1. 최상단 - 렌더링 시작
  2. string: 초기값
  3. number: 0
  4. return문 실행
  5. componentDidMount
  6. 비동기 테스트
  7. componentDidMount or componentDidUpdate
  8. 최상단 - 렌더링 시작
  9. string: 변경값
  10. number: 100
  11. return문 실행
  12. componentDidMount or componentDidUpdate

7번은 마운트 직후에 실행된 componentDidMount이고 11번은 string값이 변경되면서 일어난 componentDidUpdate에 해당된다.


componentWillUnmount

useEffect(()=>{
	// componentDidMount (최초의 컴포넌트를 렌더링 한 이후 실행)
    
	return () => {
	// componentWillUnmount (컴포넌트가 사라지기 직전에 실행)
	}
    
}, [])

페이지 이동시 (= 컴포넌트가 사라지기 직전에) 한 번만 실행된다. 즉, 아래 코드에서 페이지 이동시 콘솔창에 componentWillUnmount만 찍히게 된다.

export default function Home() {

  console.log("최상단 - 렌더링 시작");
  const [string, setString] = useState("초기값");
  const [number, setNumber] = useState(0);
  console.log("string:", string);
  console.log("number:", number);
  
     useEffect(() => {
       console.log("componentDidMount"); 
       setString("변경값");
       setNumber(100);
       console.log("비동기 테스트");
       
       return () => {
         console.log("componentWillUnmount"); 
       };
     }, []);
     
     return (
        <div>
          {console.log("return문 실행")}
        </div>
  	);
}

페이지를 이동한 상태에서 다시 Home 컴포넌트로 뒤로가기 실행시 mount가 일어난다.

useEffect는 렌더링 과정에서는 실행되지 않는다. 즉, return문이 끝난 직후(=렌더링 이후)에 실행되기 때문에 return문 바로 위에 위치하는 것이 좋다.



useQuery에서의 실행 순서

export default function Home() {

  console.log("최상단 - 렌더링 시작");
 
  const { data, isLoading } = useQuery("QueryKey", QueryFn);
  console.log("data:", data);
  console.log("isLoading:", isLoading);
  
  return (
    <div>
      {console.log("return문 실행")}
    </div>
  );
}
  1. 최상단 - 렌더링 시작
  2. data: undefined
  3. isLoading: true
  4. return문 실행
  5. QueryFn 함수 실행
  6. 최상단 - 렌더링 시작
  7. data: (data 값)
  8. isLoading: false
  9. return문 실행

useQuery 함수 역시 useEffect와 마찬가지로 렌더링 이후에 실행되기 때문에 2,3번과 같은 결과가 나왔다.

최초의 마운트가 끝나면 useQuery는 상태를 변경한다. useQuery 안에 setState가 내장되어 있기 때문이다. 그래서 두 번째 렌더링시에는 7,8번과 같은 결과가 도출된다.

이러한 이유로 return문에서 data.map을 실행하면 오류가 발생하므로 반드시 옵셔널 체이닝과 함께 사용해야 한다.

profile
개그우먼(개발을 그은성으로 하는 우먼)

0개의 댓글