공부공부/React

[react 공식문서] 25 Synchronizing with Effects

고생쨩 2024. 2. 14. 08:04
728x90

리액트 공식문서 학습기록
https://react.dev/learn

Synchronizing with Effects

React에서 Effect는 렌더링으로 인한 부작용을 나타냄.

Effect가 필요없을 수 있음

브라우저 API, 타사 위젯, 네트워킹 등

Effect 작성방법

  1. Effect 선언
  2. 종속성 지정 - 필요할때만 실행되어야하므로 종속성을 지정하여 이를 제어함.
  3. 정리 추가 - connect + disconnect, subscribe + unsubscribe 등

1단계 - Effect 선언

import { userEffect } from 'react';

//컴포넌트 최상위에 호출
const App = () => {
  useEffect(() => {
    //
  });
  return <div />;
};

useEffect는 렌더링이 끝난 후 동작함.

비디오 플레이어 예제

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  });

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

Effect는 모든 렌더링 후에 실행됨. 아래와 같이 작성하면 무한 루프가 발생함.

const [count, setCount] = useState(0);
useEffect(() => {
  setCount(count + 1);
});

2단계 - 종속성 지정

기본적으로 효과는 모든 렌더링 후 실행되나 이렇게 동작하면 안되는 경우가 있음.

  • 외부 시스템과의 동기화가 즉각적으로 필요하지 않은 경우. ex) 키를 누를때 채팅 서버에 재연결
  • 구성요소가 처음 나타날때만 애니메이션을 보여주고 싶은 경우
useEffect(() => {
  if (isPlaying) {
    // ...
  } else {
    // ...
  }
}, [isPlaying]); // 종속성으로 isPlaying 설정
  • 종속성으로 표기된 Ref가 변경될 시에만 useEffect가 동작함. ( 재실행 방지 )
  • 종속성은 배열로 여러개 선언할 수 있음
  • 종속성 배열이 없는 동작과 [] 빈 종속성 배열이 있는 동작은 다름
useEffect(() => {
  // 항상 실행됨
});

useEffect(() => {
  // 마운트 될때 한번만 실행됨
}, []);

useEffect(() => {
  a나 b가 변경되었을때 실행됨 (or 조건)
}, [a, b]);

종속성 배열에 ref를 생략하는 이유

  • 기본적으로 ref는 항상 동일한 개체를 가르키므로 생략해도 됨.
  • ref가 부모 컴포넌트에서 전달된 경우에는 동일하지 않을 수 있으므로 종속성 배열에 전달하여야함.

3단계 - 정리

useEffect(() => {
  const connection = createConnection();
  connection.connect();
  return () => {
    connection.disconnect();
  };
}, []);

컴포넌트를 재마운팅할때 연결이 이중으로 동작하지 않아야됨.
마운트가 해제되는 시점에 동작이 멈추도록 설정해야함.

비 Rect 위젯 제어

  • 두번 호출해도 아무 작업이 수행되지 않는 경우 패스. 어차피 프로덕션에선 한번만 돌아감.
  • API가 두번 연속 호출을 허용하지 않는 경우. -> 정리 기능 구현

이벤트 구독

Effect가 무언가를 구독하는 경우 정리 기능은 구독을 취소해야함. ex) addEventListener

트리거 애니메이션

정리할때 초기값으로 돌려줘야함.

데이터를 가져올때

정리할때 가져오기를 중단하거나 결과를 무시해야함.

어플리케이션 초기화

어플리케이션이 시작될때 한번만 실행되는 코드는 구성요소 외부에 넣자.

if (typeof window !== 'undefined') {
  // Check if we're running in the browser.
  checkAuthToken();
  loadDataFromLocalStorage();
}

function App() {
  // ...
}

과제 1

Show form 클릭 후 focus 동작되게
👉 useEffect를 쓰자

import { useEffect, useRef } from 'react';

const MyInput = ({ value, onChange }) => {
  const ref = useRef(null);

  useEffect(() => {
    ref.current.focus();
  }, []);

  return <input ref={ref} value={value} onChange={onChange} />;
};
export default MyInput;

과제2

두개의 입력 필드에서 첫번째 꺼에 포커스 맞추기
👉 종속성을 주자

import { useEffect, useRef } from 'react';

const MyInput = ({ shouldFocus, value, onChange }) => {
  const ref = useRef(null);

  useEffect(() => {
    if (shouldFocus) {
      ref.current.focus();
    }
  }, [shouldFocus]);

  return <input ref={ref} value={value} onChange={onChange} />;
};
export default MyInput;

과제 3

초당 두번 증가하는 카운터 수정
👉 정리를 하자

import { useState, useEffect } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    function onTick() {
      setCount((c) => c + 1);
    }

    const intervalId = setInterval(onTick, 1000);
    return () => clearInterval(intervalId);
  }, []);

  return <h1>{count}</h1>;
};
export default Counter;

과제 4

빠르게 선택했을때 select 밑에 이전 문구가 출력되는거 수정
👉 역시 정리를 잘하자

import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';

const Page = () => {
  const [person, setPerson] = useState('Alice');
  const [bio, setBio] = useState(null);
  useEffect(() => {
    let ignore = false;
    setBio(null);
    fetchBio(person).then((result) => {
      if (!ignore) {
        setBio(result);
      }
    });
    return () => {
      ignore = true;
    };
  }, [person]);

  return (
    <>
      <select
        value={person}
        onChange={(e) => {
          setPerson(e.target.value);
        }}
      >
        <option value="Alice">Alice</option>
        <option value="Bob">Bob</option>
        <option value="Taylor">Taylor</option>
      </select>
      <hr />
      <p>
        <i>{bio ?? 'Loading...'}</i>
      </p>
    </>
  );
};
export default Page;

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.