[React] sideEffect 와 useEffect훅

SuamKang·2023년 7월 8일
0

React

목록 보기
17/34
post-thumbnail
post-custom-banner
  • 들어가기 앞서서

리액트 라이브러리는 인터페이스를 좀더 간편하게 해주는 UI라이브러리로 사용되고 있는건 익히 다들 알고 있는 사실이다.
내가 초반에 리액트를 접할때 혹은 프로젝트에 접목하여 사용할때 이론적으론 알겠지만 이게 실제적으로 브라우저에서 화면이 랜더링될때 혹은 사라질때 보여지는 모습이 피부로 와닿지 못했다.
이번 기회에 좀더 자세하게 다뤄보고싶었고, 이를 통해 더욱 더 리액트를 활용해 보고싶다!


'SideEffet'가 그게 뭔데??


"effet"는 명사형 의미론 '영향, 결과, 효과'라고 알고있는단어이다.
허나 동사형의 의미론 '~(어떤 결과)를 가져오다'라고 해석된다.

따라서 이는 어떠한 행위에대한 결과라고 인식할 수 있을거같고, 부수적인 특징이 추가된다는 느낌도 받을 수 있다.


리액트 앱의 컴포넌트나 그 자체는 목적이 뚜렷하다.
브라우저 화면에 UI를 렌더링 하는것이다.

state와 이벤트를 활용해서
사용자 입력에 반응해 필요에 따라 UI를 다시 렌더링 하고, 이로써 사용자와 상호작용(Interaction)하게 하는것이다.

또한 이벤트에 따라 화면에 보이는 것은 언제든 변경될 수 있다.
(예를들면, 버튼이 클릭되거나 어떤 텍스트가 입력될 때)

그래서 리액트는 본인의 역할에 맞게 useState훅과 props등으로
모든 컴포넌트에 필요한 데이터가 있는지, 사용자 입력을 잘 반영하고 있는지 확인하기 위해서 JSX코드와 DOM노드들을 평가하고 그것을 렌더링해야한다.



결론부터 얘기하자면,
사이드이펙트는 리액트 앱에서 일어나는 다른 모든것(anything else)을 뜻한다.

  • http 요청보내기(비동기)
  • 브라우저 저장소(로컬스토리지, 세션스토리지 등)에 데이터 저장하기
  • 코드내 타이머(setTimeout()) 또는 시간간격(setTiemInterval())등의 시간관리

위의 경우들 중 서버로 데이터 요청을 보내는 작업이 화면에 직접적으로 무언가 가져오는것과는 관련이 딱히 없다.

❗️ 물론 http요청을 통해 데이터를 응답받고 화면에 무언가를 나타내게 할 순 있지만, 요점은 해당 요청을 보내는 자체나 잠재적 오류를 처리하는 것등은 그 외적인 일이며 리액트가 신경쓰는 작업이 아니라는 것이다.

즉 일반적인 컴포넌트 함수 바깥에서 수행되는 일인것이다!


⭐️ 무한루프가 발생할 수 있다?



만약 리액트 컴포넌트 안에서 직접 http 요청을 보내게 되면 보내는 함수가 다시 실행될 때마다 요청을 보내게 될것이다.

그렇게 되면 컴포넌트 내에 관리되고 있는 state가 요청에 대한 응답으로 변경하게 되고 또다른 응답으로 또 변경하게되고 또...

따라서 이러한 사이드이펙트는 직접적으로 해당 컴포넌트 함수에 들어가선 안되며 그리 되면 버그나 무한루프가 발생할 가능성이 높아진다.(렌더링 지옥..)


이로인해 리액트는 이러한 사이드이펙트를 적절히 잘 관리할 수 있도록
특별한 리액트 훅을 제공한다.

이게 바로

useEffect이다.

-> 이 훅은 리액트 내장함수로 React객체에서 import해와 사용하고, 2개의 매개변수를 받는다.

1️⃣ 첫번째 매개변수 : 콜백함수이며 모든 컴포넌트 평가된 이후에 실행되어야 하는 함수
-> 두번째 인자에 지정한 의존성이 변경된 경우라면!!!
2️⃣ 두번째 매개변수 : 의존성(dependecies)으로 구성된 배열


의존성배열안의 값들이 변경 될 때마다 첫번째인자의 함수가 다시 실행되는 형태이다.

따라서 사이드이펙트 코드는 이곳에 지정하여 넣어줄 수 있고, 변경되는 경우에만 컴포넌트가 다시 렌더링되며 그외는 실행되지 않는다.


이어서 예시코드와 살펴보자!

아래는 간단한 로그인 로직이며 서버와 통신은 하지않고 인증을 통해서 사용자가 인증상태를 확인할 수 없지만, 더미로 자격증명만 실행하여 로그인 로직을 성공할 수 있도록 작성한 코드이다.

function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const loginHandler = (email, password) => {
    setIsLoggedIn(true);
  };

  const logoutHandler = () => {
    setIsLoggedIn(false);
  };

  return (
    <React.Fragment>
      <MainHeader isAuthenticated={isLoggedIn} onLogout={logoutHandler} />
      <main>
        {!isLoggedIn && <Login onLogin={loginHandler} />}
        {isLoggedIn && <Home onLogout={logoutHandler} />}
      </main>
    </React.Fragment>
  );
}

export default App;

이렇게 isLoggendIn상태를 관리하고 있고 따라서 앱을 다시 로드할때 리액트 스크립트 전체가 다시 시작된다.(새로고침이겠죠?)

따라서 가장 최근 실행해서 얻은 모든 변수값은 사라지게 될테다.(모든 웹이나 스크립트,브라우저는 이런식으로 동작한다.)

그럼 드는 생각은?
불러온 데이터를 어디다 저장하고 싶은 충동(?)이 생길것이다.
앱이 다시 실행될때마다 사용자는 다시 입력할 필요가 없어지고 데이터가 유지되었는지 확인 할 수 있는 그런게 필요할 것이 분명하다.

이를 위해 useEffect를 사용해준다.
현재 'loginHandler'함수가 사이드 이펙트를 처리하는 함수이다.
이메일과 패스워드를 받고 로그인이 성공됐다고만(true)처리한 로직이지만, 여기서 브라우저 저장소의 정보를 저장하는 로직을 추가해 저장을 해보면 좋을거 같다.


- 로컬 저장소를 이용 -



localStrage객체 안에 setItem()메소드 사용한다.
이는 브라우저에서 사용할 수 있는 전역객체이며 첫번째 인자로 식별가능한 문자열 형식의 이름을 정해준다. 이는 로컬스토리지 객체의 프로퍼티의 "키"값으로 정해진다.
두번째 인자에도 문자열로 기재해야하며, 실제 값(데이터)을 넣어준다. 이는 프로퍼티의 "값"에 해당한다.

이로써 "키-값"쌍의 형태로 로컬스토리지에 정보가 저장되게 되는것이다.

  const loginHandler = (email, password) => {
    localStorage.setItem("isLoggedIn", "1");
    setIsLoggedIn(true);
  };

이렇게 로컬스토리지에 데이터를 저장하는 로직을 작성해주면

한번 위처럼 이메일과 비밀번호를 입력하고 보냈을때
개발자도구 Application탭에 있는 로컬스토리지안 호스트 공간에

이런식으로 키-값 쌍이 보이며 저장한 값이 그대로 담겨진걸 볼 수 있다.
이러한 이벤트가 해당 정보를 저장하고 싶을때만 이루어져도 되지만,

앱이 새로고침 되는 순간에 혹은 사용자가 페이지를 이탈하고 다시 돌아오는 경우를 생각해보면 이렇게 저장되어 다시 불필요한 요청을 서버로 보내지 않게 될 이점이 있다는 것이다.


그다음 다시 리로딩 될때 저장된 값을 불러와야하니
해당되는 로직도 추가해 주어야한다.

  const userInfo = localStorage.getItem('isLoggedIn');


  if(userInfo === "1"){
    setIsLoggedIn(true);
  }
  
   const loginHandler = (email, password) => {
    localStorage.setItem("isLoggedIn", "1");
    setIsLoggedIn(true);
  };

이렇게 되면 다시 loginHandler함수가 트리거 되지 않더라도 조건에 부합하면 값을 불러올 수 있게된다.

하지만 이 모든 작업들은 앞서 얘기했다시피 무한루프를 발생시킬 위험이 있다.

이걸 useEffect로 제어해보자.

  useEffect(() => {
    const userInfo = localStorage.getItem('isLoggedIn');

    if(userInfo === "1"){
      setIsLoggedIn(true);
    }
  }, [])

useEffect안에있는 로직들을 우리가 작성해 두면 리액트가 이제 그걸 실행한다.

모든 컴포넌트가 재평가가 된 후, 혹은 만약 의존성이 있다면 그게 변경된 경우에만 실행된다는것이고
그 말은 즉, App 컴포넌트 함수가 실행된 이후에 안에있는 로직이 실행된다는것이다! (매우중요***)

두번째 인자에 [] 빈배열이 들어간다는건 의존성이 없다는 것이고
맨 처음 화면에 렌더링 될대 한번만 실행된다. 왜냐하면 그 이후론 의존성이 없기때문에 절대 변경되지 않기 때문이다.


따라서 useEffect로 사이드 이펙트 로직을 관리할때 내가 이 로직이 화면상 랜더링시 한번만 나타나게 할것인지,아닌지에대한 고민이 항상 필요할것 같다.

위 예시코드는 로그인 코드이며,
평가되는 순서를 나열해보자.

렌더링 순서

  1. 페이지 접속시 첫 렌더링(이땐 어떠한 인터렉션이 없으니 useEffect안 로직은 가장 최신의 상태로 업데이트 된걸 기억하고 있고 렌더링 됨.)
  2. 사용자가 로그인정보 입력하고 로그인버튼 클릭 -> 로컬스토리지에 값 저장 후 로그인상태변경
  3. 컴포넌트 전체 재평가(jsx코드 평가 후 dom 업데이트)
  4. 재평가가 이루어 졌으니 useEffect안 로직 실행
  5. 로컬스토리지에서 불러온 값이 조건이 맞기때문에 다시 로그인 상태 변경
  6. 컴포넌트 전체 리랜더링
  7. 리액트는 여기서 다시 useEffect내부 실행하려 준비
  8. 그러나 여기선 의존성이 없기때문에 더이상 실행 안됨.

따라서 새로고침 해도 다시 인증화면 돌아가지않고 여전히 로그인상태에 있다.

만약, 수동으로 지운다면 useEffect에 접근해서 실행준비시 조건에 맞는 로컬스토리지 데이터가 없기때문에 상태가 바뀌지않고 이상태에서 새로고침 하면 state는 초기화되는것이다.



마운트(Mount) vs 렌더링(Rendering)


리액트를 공부하다보면 가장 자주 듣는말이 '렌더링'이다. 렌더링..렌더링..
난 '렌더링'과 useEffect 훅에서 '마운트'라는 개념과 매우 헷갈렸었다.

알아보니
뭔가 비슷해보이지만 조금은 다른 의미를 갖고 있는 녀석들이였다.


마운트(Mount)
: 컴포넌트가 최초로 화면에 그려질 때를 말한다.
이때, 컴포넌트의 jsx코드가 평가되어 실제 DOM요소로 변환되고(업데이트), 그게 브라우저에 추가가 되어 화면에 "마운트"되는 것이다.

렌더링(Rendering)
: 컴포넌트의 jsx코드가 평가되어 가상 DOM을 구성하고, 변경된 부분들만 실제 DOM에 업데이트 하는 과정을 말한다.
리액트에서 컴포넌트의 렌더링은 컴포넌트의 상태(state)나 속성(props)이 변경되어 컴포넌트를 업데이트 하거나. 부모 컴포넌트가 리렌더링 되어 자식컴포넌트도 함께 리렌더링되는 상황에서 발생된다.


⭐️ 마운트는 최초의 렌더링을 의미한다.

즉, 컴포넌트가 화면에 처음으로 그려질 때, jsx코드가 평가되고 실제 DOM에 추가되는데 이때 useEffect 훅의 첫번째 매개변수로 전달한 함수가 실행된다.
위 과정 자체가 "마운트" 인것!!!


⭐️ 렌더링은 컴포넌트가 업데이트되는 과정을 의미한다.

즉, 상태나 속성이 변경되어 리렌더링이 발생할 때, 마운트와 마찬가지로 jsx코드 평가 -> DOM 업데이트 되며 이때도 useEffect훅의 첫번째 매개변수로 전달한 함수가 실행될 수 있다.


요약

마운트 : 컴포넌트가 화면에 그려지는 시점!
렌더링 : 컴포넌트의 상태나 속성이 변경되어 컴포넌트를 업데이트하는 과정!

사실 그래서 렌더링이 좀 더 포괄적인 의미로 해석되는것 같다.


useEffect 함수의 생애주기?


이제 구분 가능한 마운트와 렌더링 개념에 기반해서
그럼 "생애주기"라는 말은 무엇을 의미하는 걸까?

useEffect함수 안의 "생애주기"
컴포넌트의 마운트(Mount), 언마운트(Unmount), 업데이트(Update) 단계에 따라 실행되는 주기를 의미한다.


마운트 단계
: 컴포넌트가 처음으로 화면에 그려질 때를 말한다.
useEffect 함수의 첫번째 매개변수로 전달한 함수가 실행된다. 이때 의존성 배열이 빈배열일 경우, 해당함수는 마운트 시 한번만 실행된다.

언마운트 단계
: 컴포넌트가 화면에서 제거(사라질 때)될 때를 말한다.
useEffect 함수의 첫번째 매개변수로 전달한 함수가 컴포넌트가 사라지기 직전에 실행된다. 이때 보통 반환되는 함수(클린업 함수)가 실행되며, 주로 구독 해제나 리소스 정리등의 작업을 처리한다.

업데이트 단계
: 컴포넌트의 상태속성이 변경되어 리렌더링이 발생할 때를 말한다.
useEffect 함수의 첫번째 매개변수로 전달한 함수가 실행된다. 이때 의존성 배열이 있는경우, 배열에 포함된 값이 변경될 때만 해당함수가 실행된다.
즉, 여기서 다시 해당함수가 실행되게 하려면 해당함수(사이드이펙트함수)에서 사용되는것을 의존성으로 추가해주면 정상적으로 실행되는것이다.

따라서 useEffect 함수 안의 "생애주기"는
컴포넌트의 마운트,언마운트,업데이트 단계에 따라 실행되는 주기를 의미하고,
의존성 배열의 상태에 따라 실행 여부도 결정된다고 보면 된다.

profile
마라토너같은 개발자가 되어보자
post-custom-banner

0개의 댓글