공부공부/React

[react 공식문서] 27 Lifecycle of Reactive Effects

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

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

Lifecycle of Reactive Effects

Effect는 컴포넌트와 라이프사이클이 다름.

Effect의 수명 주기

컴포넌트가 마운트되면 React가 동기화를 시작 하고 컴포넌트가 마운트 해제되면 동기화를 중지한다고 생각할 수 있음.
경우에 따라 구성 요소가 마운트된 상태로 유지되는 동안 동기화를 여러 번 시작하고 중지 해야 할 수도 있음.

동기화가 두번 이상 필요한 이유

ex) 채팅방에서 방이 바뀔때 연결을 재설정하는 것을 예제로 들고 있음.

Effect를 재동기화하는 법

정리를 하자

Effect의 관점에서 생각하기

컴포넌트 관점

  1. ChatRoomroomId로 설정 하여 장착"general"
  2. ChatRoomroomId로 설정하여 업데이트됨"travel"
  3. ChatRoomroomId로 설정하여 업데이트됨"music"
  4. ChatRoom 언마운트

Effect 관점

  1. Effect가 "general"방에서 연결 해제되고 "travel"방에 연결됨
  2. Effect가 "travel"방에서 연결 해제되고 "music"방에 연결됨
  3. Effect가 "music"방 에서 연결 해제되었습니다.

Effect를 재동기화할 수 있는지 확인하는 방법

개발 단계에 두번 마운트되니까 그때 확인하면 됨.

Effect를 재동기화해야 한다는 것을 아는 방법

종속성으로 값을 전달하기때문

각 Effect는 별도의 동기화 프로세스를 나타냄

기능별로 따로 만들 것

반응 값에 반응하는 효과

변경되지 않는 상수는 종속성에 넣을 필요없음

종속성이 비어있는 효과의 의미

컴포넌트 관점에선 빈배열의 종속성은 마운트, 언마운트 될때만 실행됨.
Effect 입장에선 마운팅과 언마운트에 대해 생각할 필요없음. 종속성만 생각하면 됨.

컴포넌트 본문의 변수는 반응형임

컴포넌트 내의 props와 state를 가지고 계산하는 값도 반응형임.
전역 변수는 반응적이지 않음.

모든 반응값이 종속성으로 지정했는지 확인함

Linter가 잘 알려줌

재동기화를 원하지 않을 때

빈 배열을 종속성으로 넣으삼

과제 1

인풋에 키를 누를때마다 재연결이 되는 것 수정
👉 ChatRoom에서 roomId가 변경됐을때만 재연결되도록 종속성 추가

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

const ChatRoom = ({ roomId }) => {
  const [message, setMessage] = useState('');

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

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
    </>
  );
}

const App = () => {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}
export default App;

과제 2

마우스 커서 따라다니기 온오프
👉 handleMove에서 canMove 체크

import { useState, useEffect } from 'react';

const App = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [canMove, setCanMove] = useState(true);

  useEffect(() => {
    function handleMove(e) {
      if (canMove) {
        setPosition({ x: e.clientX, y: e.clientY });
      }
    }
    window.addEventListener('pointermove', handleMove);
    return () => window.removeEventListener('pointermove', handleMove);
  }, [canMove]);

  return (
    <>
      <label>
        <input type="checkbox"
          checked={canMove}
          onChange={e => setCanMove(e.target.checked)} 
        />
        The dot is allowed to move
      </label>
      <hr />
      <div style={{
        position: 'absolute',
        backgroundColor: 'pink',
        borderRadius: '50%',
        opacity: 0.6,
        transform: `translate(${position.x}px, ${position.y}px)`,
        pointerEvents: 'none',
        left: -20,
        top: -20,
        width: 40,
        height: 40,
      }} />
    </>
  );
}
export default App;

과제 3

마우스 커서 따라다니기 온오프
👉 handleMove를 useEffect 안으로 이동하고 canMove를 종속성으로 전달하여 잦은 리스너 추가 삭제를 방지한다.

import { useState, useEffect } from 'react';

const App = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [canMove, setCanMove] = useState(true);

  useEffect(() => {
    console.log('Resubscribing')
    function handleMove(e) {
      if (canMove) {
        setPosition({ x: e.clientX, y: e.clientY });
      }
    }

    window.addEventListener('pointermove', handleMove);
    return () => window.removeEventListener('pointermove', handleMove);
  }, [canMove]);

  return (
    <>
      <label>
        <input type="checkbox"
          checked={canMove}
          onChange={e => setCanMove(e.target.checked)} 
        />
        The dot is allowed to move
      </label>
      <hr />
      <div style={{
        position: 'absolute',
        backgroundColor: 'pink',
        borderRadius: '50%',
        opacity: 0.6,
        transform: `translate(${position.x}px, ${position.y}px)`,
        pointerEvents: 'none',
        left: -20,
        top: -20,
        width: 40,
        height: 40,
      }} />
    </>
  );
}
export default App;

과제 4

채팅방을 선택하지 않으면 암호화 미동작하는 것을 체크만 해도 동작하게
👉 isEncrypted를 종속성에 추가하고 연결에 적용

import { useState, useEffect } from 'react';
import {
  createEncryptedConnection,
  createUnencryptedConnection,
} from './chat.js';

const ChatRoom = ({ roomId, isEncrypted }) => {
  useEffect(() => {
    const createConnection = isEncrypted ?
      createEncryptedConnection :
      createUnencryptedConnection;
    const connection = createConnection(roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, isEncrypted]);

  return <h1>Welcome to the {roomId} room!</h1>;
}
export default ChatRoom;

과제 5

select 연결되게
👉 custom 훅으로 분리

import { useState } from 'react';
import { useSelectOptions } from './useSelectOptions.js';

const Page = () => {
  const [
    planetList,
    planetId,
    setPlanetId
  ] = useSelectOptions('/planets');

  const [
    placeList,
    placeId,
    setPlaceId
  ] = useSelectOptions(planetId ? `/planets/${planetId}/places` : null);

  return (
    <>
      <label>
        Pick a planet:{' '}
        <select value={planetId} onChange={e => {
          setPlanetId(e.target.value);
        }}>
          {planetList?.map(planet =>
            <option key={planet.id} value={planet.id}>{planet.name}</option>
          )}
        </select>
      </label>
      <label>
        Pick a place:{' '}
        <select value={placeId} onChange={e => {
          setPlaceId(e.target.value);
        }}>
          {placeList?.map(place =>
            <option key={place.id} value={place.id}>{place.name}</option>
          )}
        </select>
      </label>
      <hr />
      <p>You are going to: {placeId || '...'} on {planetId || '...'} </p>
    </>
  );
}
export default Page;

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

export const useSelectOptions = (url) => {
  const [list, setList] = useState(null);
  const [selectedId, setSelectedId] = useState('');
  useEffect(() => {
    if (url === null) {
      return;
    }

    let ignore = false;
    fetchData(url).then(result => {
      if (!ignore) {
        setList(result);
        setSelectedId(result[0].id);
      }
    });
    return () => {
      ignore = true;
    }
  }, [url]);
  return [list, selectedId, setSelectedId];
}

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