React 알아가기 (5)

삔아·2023년 5월 8일
0
post-thumbnail

해당 내용은 https://www.udemy.com/course/best-react/ 강의를 들으며 정리하고 스스로 공부 한 내용을 기록 하였습니다.

Section 5. 고급 리듀서를 사용하여 부작용 처리 & 컨텍스트 API 사용하기

About useEffect

일반적인 컴포넌트 함수 에서는 JSX코드를 평가하고 렌더링 하며, State나 props를 관리하고 사용자 이벤트나, 컴포넌트에서 state, props가 바뀌었을 때 재평가 등의 일을 하게 된다.

하지만 이런 경우도 있다. 예를들어 Browser Storage에 데이터를 저장하거나 Http리퀘스트를 백엔드 서버에 보내는 등의 일들. 일반적인 컴포넌트 함수 밖에서 일어나는 모든 것들인데 이를 바로 사이드 이펙트 라 얘기한다.
사이드 이펙트는 버그나 무한루프가 발생할 가능성이 높을 뿐 아니라 http리퀘스트가 많이 보내질 수도 있기 때문에 직접적으로 컴포넌트 함수에 들어가서는 안되는데 이런 문제점을 useEffect 훅을 이용하여 해소 시킬 수 있다.

useEffect(() => { ... }, [ dependencies ]);

useEffect 훅은 리액트 내장 훅이며 두개의 인자가 필요하다.

  1. 모든 컴포넌트 평가가 끝난 후 실행 되어야 하는 함수. (지정된 의존성이 변경 된 경우)
  2. 그리고 지정된 의존성을 두 번째 인수로 넣어준다. (의존성으로 구성된 배열)

의존성이 변경될 때 마다 첫번째 함수가 실행 된다.

useEffect() 훅 사용해보기

  ...
  useEffect(() => {
    const storedUserLoggedInInformation = localStorage.getItem("isLoggedIn");

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

이 예시는 유저가 로그인 했다면 로그인 유/무 의 상태를 로컬스토리지에 저장 하려고 하는 코드 중 일부분이다.
이 부분은 의존성이 없는 경우를 보여주고 있는데, 해당 코드 처럼 두번째 인수에 아무것도 없을 경우에는 앱이 시작될 때 한번만 실행한다.

두 번째 인수에 [] 같은 빈 배열을 넣을 때는 앱이 시작되는 경우에 한번 실행 된다.

강의를 들으며 프로젝트에서 유저가 로그인 한 후 새로고침 하면 로그인이 풀리는 현상이 있었는데, 해당 부분에서는 로그인을 했다면 로컬스토리지에 로그인을 했는지 안했는지 를 저장을 하고 탐색을 한다.
우리가 원하는 현상은 앱이 실행 되었을 때에 단 한번 함수가 실행되는 부분이다.
따라서 첫 번째 인수의 함수 에서 로컬스토리지에 접근하여 로그인 상태인지 아닌지를 확인 후 state에 저장을 한다. 후에는 두 번째 인수에는 아무것도 없으므로 (의도적으로) 다시는 실행되지 않는 함수이다.
모든 컴포넌트가 렌더링 될 때 마다 첫 번째 함수가 실행되지 않으며 우리가 실행 시키고 싶을 때만 실행이 될 것이다.

두 번째 인수에 아무것도 넣지 않을 경우에는?

...
  useEffect(() => {
    setFormIsValid(
      enteredEmail.includes("@") && enteredPassword.trim().length > 6
    );
  });
...

해당 부분은 유효성 체크를 하는 부분이다.
위의 코드와는 다르게 두 번째 인수에 아무것도 들어가있지않다.
위 코드는 결국

setFormIsValid(
      enteredEmail.includes("@") && enteredPassword.trim().length > 6
);
useEffect(() => {
    
});

이렇게 내부에 있었던 setFormIsValid 함수는 컴포넌트 자체 내부에 있는 함수로 작성되어진다고 볼 수 있다.

의존성이 없기 때문에 컴포넌트가 다시 렌더링 될 때 마다 해당 함수가 실행되는 것과 같기 떄문이다.

심지어 저 setFormIsValid 는 state에 저장하는 함수인데, 이렇게 된다면 재렌더링 주기 자체를 트리거하면서 무한루프가 발생 할 수 있다.
( -> 처음 마운트 될 때를 포함하여 모든 컴포넌트 렌더링 주기 후에 실행 된다.)

useEffect() 권장사항

썼던거를 또 쓰자

useEffect 훅을 사용하면서 따르면 좋을 컨벤션이 있다.
바로 useEffect 의 첫 번째 함수에 쓰인 것들을 두 번째 인수에 추가하는 것이다.

useEffect(() => {
    setFormIsValid(
      enteredEmail.includes("@") && enteredPassword.trim().length > 6
    );
  }, [setFormIsValid, enteredEmail, enteredPassword]);

해당 코드에서, setFormIsValid , enteredEmail, enteredPassword 가 쓰였으니 이를 두 번쨰 인수에 추가해준다.
이렇게 작성하게 되면 리액트에게 모든 로그인 컴포넌트 함수 실행 후에 useEffect 함수를 실행하라고 말하는 것이다.
하지만 setFormIsValid , enteredEmail, enteredPassword 가 마지막 컴포넌트 렌더링 주기에서 변경된 경우에만 실행하라고 말하는 것이며, 셋 중 하나라도 바뀌지 않았다면 해당 함수는 실행하지 않는다.

여기서 setFormIsValid 는 생략 할 수 있다. 이 함수는 useState 를 통해 가져온 함수이기 때문 (해당 글에는 생략되어 있지만...)
이처럼 리액트에 의해 절대 변경되지 않는다는 것을 보장 받는 함수들은 재렌더링 주기에 따라 변하지 않기 때문에 생략이 가능하다.

useEffect() 는 사이드이펙트를 처리 하는 것이 주요 임무이다.
-> 어떤 액션에 대한 응답으로 실행되는 액션이 있다면 그것은 사이드이펙트 다.

+ 종속성으로 추가 할 항목 및 추가하지 않을 항목

useEffect 함수에서 사용하는 모든 상태 변수와 함수를 종속성으로 추가해야 함이라고 말했지만, 몇 가지 예외 사항이 있다.

  • 상태 업데이트 기능을 추가 할 필요가 없다.
    리액트는 해당 함수가 절대 변경되지 안도록 보장하므로 종속성으로 추가 할 필요가 없다. (useState)
  • 내장 API 또는 함수를 추가 할 필요가 없다.
    fetch() 혹은 localStorage 같은 브라우저에 내장된 함수 및 기능은 리액트 구성 요소 렌더링 주기와 관련이 없으며 변경되지 않기 때문에 추가 할 필요가 없다.
  • 구성 요소 외부에서 정의 한 변수나 함수를 추가 할 필요 없다.
    별도의 파일에 새 헬퍼 함수를 만드는 경우와 같이 구성 요소 외부에서 정의 하게 된다면 변경해도 구성 요소에 영향을 주지 않기 때문에 추가 할 필요가 없다.

즉, 구성 요소(또는 일부 상위 구성 요소) 가 다시 렌더링 되어 이러한 것들이 변경될 수 있는 경우에는 종속성에 추가 한다.

그렇기 때문에 컴포넌트 함수에 정의된 변수나 상태, 컴포넌트 함수에 정의된 Props 또는 함수는 종속성으로 추가가 되어야 한다.

useEffet() 에서 Cleanup 함수 사용하기

위에서 본 코드의 예시는 email, password의 input창에서의 value를 확인하고 매번 sideEffect 함수를 동작하도록 한 부분이다.

하지만 너무 불필요하게 여러번 해당 함수를 실행한다고 느껴지는데, 이럴 때 cleanup 함수를 사용 하는 것이다.

해당 예시에서는 디바운스 의 개념을 사용을 하였다.

useEffect(() => {
    const identifier = setTimeout(() => {
      setFormIsValid(
        enteredEmail.includes("@") && enteredPassword.trim().length > 6
      );
    }, 500);

    return () => {
      clearTimeout(identifier);
    };
  }, [enteredEmail, enteredPassword]);

useEffect() 에서의 첫 번째 함수는 반환 값(return) 이 있는데, 함수 명이 될 수 있고 화살표 함수 또한 될 수 있다.
이 함수는 cleanup function이 된다.

return cleanup function

해당 useEffect 함수가 실행될 때 마다 (실행 되기 전, 처음 실행되는 경우 제외) cleanup 함수가 실행한다.
이펙트를 특정한 컴포넌트가 DOM에서 마운트 해제될 때 마다 실행 되는데 이는 즉 컴포넌트가 재사용 될때 마다 실행된다는 뜻이다.
해당 함수는 모든 새로운 사이드이펙트 함수가 실행되기 전에, 그리고 컴포넌트가 제거되기 전에 실행한다.
첫 번째 사이드이펙트 함수가 실행 되기 전에는 실행되지 않는다. 그 이후에는 실행 된다.

해당 코드 처럼 작성하면, 매번 setFormIsValid 를 호출하는 것이 아닌, 사용자가 입력을 멈추었다고 생각 했을 때 (500ms) 해당 함수가 호출이 되어진다.

이 cleanup (clearTimeout) 함수를 통해 디바운스 를 구현해낼 수 있다.

마지막으로 cleanup 함수는 컴포넌트가 제거(unmount) 되면 마지막에 실행 한다.


중첩 속성을 useEffect에 종속성으로 추가하기

useEffect() 에 객체 속성을 종속성으로 추가하기 위해서는 destructuring을 사용해야 한다.

 const { someProperty } = someObject;
 useEffect(() => {
   // code that only uses someProperty ...
 }, [someProperty]);

위 예시가 매우 일반적인 패턴 및 접근 방식인데,
핵심은 destructuring을 사용한다는 것이 아니라, 전체 객체 대신 특정 속성을 종속성으로 전달한다는 것 이다.

많은 실수 중 하나는 아래와 같이 코드를 작성하는 것인데 얼핏 보면 위와 같은 방식으로 작동하는 것 처럼 보인다.

useEffect(() => {
  // code that only uses someProperty ...
}, [someObject]);

해당 코드에서 useEffect 함수는 someObject 가 변경될 때마다 재실행되기 때문에 이러한 방법은 피해야 한다.

profile
Frontend 개발자 입니다, 피드백은 언제나 환영 입니다

0개의 댓글