React에서 side Effect의 올바른 발생 시점
const App = () => {
const doSideEffect = () => // do some side effect }; // 함수 정의
doSIdeEffect() // 함수 호출
return <h1> hello world </h1>; //jsx 리턴
}
👆 위 코드는 리턴문 위에서 하고 싶은 동작을 실행 시킨다
문제점
1. sideEffect가 렌더링을 blocking 함
코드는 위에서 아래로 읽음, doSideEffect의 동작이 끝날 때까지 jsx를 return 하는 코드로 넘어가지 않음
만약 여기서 doSideEffect가 시간이 오래 걸리는 sideEffect 라면 함수 컴퍼넌트가 jsx를 리턴하기 전까지 브라우저 상의 UI가 렌더링 되기까지 오랜시간이 걸린다 -> 사용자가 UI가 업데이트 되는 것을 보기까지 오랜 시간이 소요됨
2. 매 렌더링 마다 sideEffect가 수행됨
예를 들어 인스타 그램 피드를 보여주기 위해서 인스타 정보를 가져오는 사이드 이펙트가 필요함
이때 좋아요 하트 버튼을 누른다 했을 때 하트를 누를 때마다 UI 변화가 필요하다면, 하트를 누를때마다 리렌더링을 발생 시켜야 함
리액트에서 함수 컴포넌트 리렌더링은 함수 컴포넌트를 다시 호출하는 방식으로 실행됨
즉, 매 렌더링마다 함수 컴포넌트가 호출되는 것
data fetching 등 특정한 상황에서만 필요한 사이드 이펙트가 매 렌더링마다 무조건 발생하게 된다면 비효율적
올바른 발생 시점
1. 렌더링을 blocking 하지 않기 위해 렌더링이 모두 완료되고 난 후 실행할 수 있어야 한다
2. 매 렌더링마다 실행되는 것이 아니라 원할 때만 조건부로 실행할 수 있어야 한다.
-> 다행히 리액트에선 useEffect 가 있어서 편하게 코드를 짤 수 있다
❗️ 그렇다면 useEffect 란?
렌더링이 모두 완료된 후 실행할 수 있고
원할 때만 조건부로 실행할 수 있다
useEffect
하지만 위 코드👆는 side Effect가 렌더링을 blocking 한다는 점만 해결함
👇 버튼을 누르거나 텍스트를 입력할 때마다 함수들이 같이 발동함
👇 계속 쌓이는 side effect with useEffect, 비효율적이다
이를 해결하기 위해서 의존성 배열을 넣어주는데
👆 이렇게 의존성 배열을 같이 넣어주면 원하는 시점에 사이드 이펙트를 발생시킬 수 있다
위 콘솔창을 보면 버튼을 누를 때마다 text change는 콘솔에 안 찍히는 것을 확인할 수 있다
마찬가지로 text 를 입력할 때마다 count change도 찍히지 않는다
배열에 담긴 값에 하나라도 변화가 있다면 실행되는 것!
when? 은 [count, text] 두 인자를 넣은 useEffect로
둘 중 하나라도 변했기에 이펙트를 계속 실행된 것이다
마지막에 구분을 위해 넣어둔 console.log '------'은 따로 의존성 배열을 주지 않았기 때문에
계속 찍히는 것을 확인할 수 있다
❗️ 찻반쩨 렌더링에서만 콜백함수를 호출하고 두번째 렌더링에서부턴 호출하지 않게 하려면?
-> 의존성 배열에 [] 빈값을 넣어주면 됨
하나라도 변화가 있다면 실행되는 useEffect 의존성 배열에 애초에 비교할 값이 없기 때문!
effect - rendering cycle
1. 컴포넌트가 렌더링 된다 ( 브라우저에 컴포넌트가 보였다는 의미로 'mount'라 표현)
2. useEffect 첫번째 인자로 넘겨준 콜백 함수가 호출된다 (side Effect)
3. 컴포넌트 state 또는 props가 변경되었을 경우 리렌더링이 발생 (update)
4. useEffect는 두번째 인자에 들어있는 의존성 배열을 확인
5. state 또는 props 가 변경된다면 3-4의 과정을 반복
6. 컴포넌트가 더 이상 필요없어지면 화면에서 사라진다 ( 브라우저 화면에서 사라졌다는 의미로 'unmount' 라고 표현
clean up
그냥 들어가면 콘솔에 interval 1,2,3,4 숫자 세는 함수를 넣음
그리고 다른 페이지인 other 에 갈 수 있도록 링크를 걸어주었다
그리고 콘솔창을 확인했을 때, 다른 창으로 넘어가도 계속 실행되고 있는 interval
이런 것들은 지속적으로 구독을 하면서 남아 있는 이펙트이다
이는 other 창에선 불필요한 상태임으로 비효율적임!
바로 이럴 때
우리가 필요할 때만 사용하고 정리해주는 cleanup 이라는 개념이 있다
다시 코드를 정비해서
effect를 콘솔에 출력하는 useEffect
button 을 누를 때마다 콘솔에 button clicked 를 출력하는 useEffect
렌더링이 돌 때마다 찍히는 render 가 있다
콘솔에 찍어보면👇
일단 처음엔 모두 다 렌더링이 된다
다음 print console 버튼을 5번 눌렀더니 button clicked 이 5번 찍혔고
다음 up 버튼을 눌러 리렌더링을 한 다음
다시 button clicked 한번 눌렀더니 button clicked가 2번 찍혔다
후에 up 버튼을 30번 누른 후
button clicked 을 한번 눌렀더니
총 31번의 button clicked 이 생겼다
이벤트 리스너가 중첩되고 있는 것...!
이때 다음 이펙트가 발생하기 전에
기존 부착해둔 eventListener 를 제거하고
새롭게 effect를 발생시키면 된다
클린업은 우리가 동작하고 싶은 것을 함수 형태로 만들어서 return 하면 된다
button useEffect 안에 cleanUp 함수를 넣어주었다
확인해보기 위해 clean up 콘솔을 넣고
다음 이펙트가 발생하기 전에 기존 이펙트를 제거하는 remove.EventListener 를 넣었다
👇결과
렌더링이 되어 나오므로 콘솔에 한번씩 찍히고
다음 Print console 버튼을 10번 누르고 리렌더링하러 up을 눌렀다
그랬더니 clean up 이라는 콘솔이 마지막에 따라 찍혔다
3번 더 누르고 button clicked을 다시 눌러봤을 때
이벤트가 중첩이 되지 않아 아까와 다르게 한번만 찍힌 것을 확인할 수 있다
이렇게 cleanUp 함수를 적용해주면 다음 이펙트가 실행되기 전에 클린업 함수를 호출해준다
그리고 useEffect는 다음 이펙트가 발생하기 전뿐만 아니라
컴포넌트가 언마운트 될 때에도 마찬가지로 클린업 함수를 호출한다
컴포넌트가 언마운트 될 때 클린업 함수 호출
👆 setInterval을 적용한 useEffect
useEffect 밖에 있는 console.log("render")가 먼저 콘솔에 찍히고
순서대로 effect, interval 이 찍힌다
그리고 이걸
👇요렇게 바꾸어주면
이렇게 other 라는 페이지에 가면
clean up 이라는 콘솔이 뜨며 interval이 멈춘다
마지막 정리
react 에서 side Effect를 발생시킬 때 충족 시켜야 하는 조건
1. 렌더링 이후에 발생 시킬 것
2. 매 렌더링 이후가 아니라 조건부로 원하는 순간에만 실행할 수 있을 것
-> React 에서는 이를 충족시킬 수 있는 useEffect라는 Hook을 제공해준다
-> 하지만 일부 SideEffect 는 발생시킨 후 다시 cleanUp 하는 과정이 필요할 수 있다
-> useEffect에 인자로 전달한 콜백 함수에서 함수를 return 하면 다음 effect가 실행되기 전과, 컴포넌트가 unmount 될 때
return 된 함수를 호출해주며 이를 통해 기존의 side effect 를 cleanup 할 수 있다
'TIL' 카테고리의 다른 글
Monster 과제 (react, map함수, 컴포넌트) (0) | 2022.08.15 |
---|---|
상수데이터란? (0) | 2022.08.12 |
SideEffect 란? (0) | 2022.08.08 |
인스타 main창 클론 react편 (2) | 2022.08.07 |
인스타 login창 클론 react편 (0) | 2022.08.06 |
댓글