React useEffect

Volc·2022년 8월 17일
0

React

목록 보기
6/6
post-thumbnail

useEffect

useEffect는 컴포넌트가 렌더링 될 때마다 특정 작업을 수행할 수 있도록 하는 Hook이다.
useEffect의 가장 기본 형태는 다음과 같다.

useEffect(function,deps);
  • 렌더링 시 작업을 수행할 function은 함수이다.
  • deps는 배열 형태이며 검사하고자 하는 특정 값 혹은 빈 배열이다.

다음 코드에서 useEffect를 사용해보자.

  • App.js
  import React,{useEffect, useRef, useState} from 'react';
  import './index.css';
  import CreateChampion from './CreateChampion';

  function Champion({champion, onRemove, onToggle}){
    return(
      <div>
        <b style={{cursor:'pointer'}} onClick={()=>onToggle(champion.id)}>{champion.name}</b> <br/>
        <span>스킬: {champion.skill}</span>
        <div style={{color : 'red'}}>{champion.op && <b>OP 챔피언</b>}</div>
        <button onClick={() => onRemove(champion.id)}>삭제</button>
        <div><br/></div>
      </div>
    )
  }

  function ChampionList({champions, setChampions}) {

    const onToggle = (id) => {
      setChampions(
        champions.map(champion => 
          champion.id === id ? {...champion, op: !champion.op} : champion))
    }

    const onRemove = (id) => {
      setChampions(champions.filter( champ => champ.id !== id));
    }

    return (
      <div>
        {champions.map(champion =>(
          <Champion champion={champion} key={champion.id} onRemove={onRemove} onToggle={onToggle}/>
        ))}
      </div>
    );
  }

  function App(){
    const listId = useRef(4);

    const [champions, setChampions] = useState(
      [
        {
          id: 1,
          name: '갱플랭크',
          skill: '혀어어어업상',
          op: false
        }
      ]
    )

    return(
      <div>
        <CreateChampion listId={listId} champions={champions} setChampions={setChampions}/>
        <ChampionList champions={champions} setChampions={setChampions}/>
      </div>
    )
  }

  export default App;
  • CreateChampion.js
  import React,{useState} from 'react';

  function CreateChampion({listId, champions, setChampions}){
      const [inputs, setInputs] = useState({
          name : '',
          skill : ''
      });
      const {name, skill} = inputs;

      const onChange = e => {
          const {name, value} = e.target;
          setInputs({
              ...inputs,
              [name] : value
          })
      }

      const onInsert = () => {
          const Champion = {
              id: listId.current,
              name: name,
              skill: skill
          }
          console.log(Champion)
          setChampions(champions.concat(Champion));
          setInputs({
             name: '',
             skill: ''
          })
          listId.current+=1;
      }

      return(
          <div>
              <input name="name" onChange={onChange} placeholder="챔피언 이름" value={name}/>
              <input name="skill" onChange={onChange} placeholder="스킬" value={skill}/>
              <button onClick={onInsert}>등록</button>
          </div>
      )
  }

  export default CreateChampion;
  • index.js
  import React from 'react';
  import ReactDOM from 'react-dom/client';
  import './index.css';
  import reportWebVitals from './reportWebVitals';
  import App from './App'

  const root = ReactDOM.createRoot(document.getElementById('root'));

  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );

  reportWebVitals();

위 코드에서 Champion function에 useEffect를 넣어보자.

  function Champion({champion, onRemove, onToggle}){
    useEffect(()=>{
      console.log('컴포넌트가 처음에만 실행됩니다.')
    },[]);

    return(
      <div>
        <b style={{cursor:'pointer'}} onClick={()=>onToggle(champion.id)}>{champion.name}</b> <br/>
        <span>스킬: {champion.skill}</span>
        <div style={{color : 'red'}}>{champion.op && <b>OP 챔피언</b>}</div>
        <button onClick={() => onRemove(champion.id)}>삭제</button>
        <div><br/></div>
      </div>
    )
  }

수정 후 코드를 실행해보면 다음과 같이 나온다.

  • 위 그림과 같이 처음 렌더링 될 때만 문장이 출력되며 수정을 하거나 삭제를 할 때에는 출력되지 않는다.
  • 등록 시 새로운 컴포넌트가 생성되기 때문에 문장이 출력된다.
  • 2번씩 출력이 되는 이유는 index.js에 있는 <React.StrictMode> 때문이며 1번씩만 출력되게 하려면 <React.StrictMode>를 지우면 된다.

이번에는 deps를 빼보자.

function Champion({champion, onRemove, onToggle}){
  useEffect(()=>{
    console.log('컴포넌트가 처음에만 실행됩니다.')
  });

  return(
    <div>
      <b style={{cursor:'pointer'}} onClick={()=>onToggle(champion.id)}>{champion.name}</b> <br/>
      <span>스킬: {champion.skill}</span>
      <div style={{color : 'red'}}>{champion.op && <b>OP 챔피언</b>}</div>
      <button onClick={() => onRemove(champion.id)}>삭제</button>
      <div><br/></div>
    </div>
  )
}

deps를 아예 빼면 리렌더링이 될 때마다 함수가 호출된다.

이번에는 특정 변수에 따라 useEffect의 함수가 실행되도록 해보자.

function Champion({champion, onRemove, onToggle}){
  const [level, setLevel] = useState(0);

  useEffect(()=>{
    console.log(level);
  },[level]);

  return(
    <div>
      <b style={{cursor:'pointer'}} onClick={()=>onToggle(champion.id)}>{champion.name}</b> <br/>
      <span>스킬: {champion.skill}</span>
      <div style={{color : 'red'}}>{champion.op && <b>OP 챔피언</b>}</div>
      <div>레벨 : {level}</div>
      <button onClick={() => setLevel(level + 1)}>레벨</button>
      <button onClick={() => onRemove(champion.id)}>삭제</button>
      <div><br/></div>
    </div>
  )
}
  • level 버튼을 추가하였다. useEffect는 레벨에 의존하여 level버튼을 누를 때마다 함수가 호출된다.
  • champion의 상태가 변경 되고 리렌더링이 되어도 champion은 의존하고 있지 않기 때문에 함수가 호출되지 않는다.

이번에는 useEffect 함수에 cleaup 함수를 설정해보자.
cleanup 함수는 사라지거나 의존하고 있는 deps가 업데이트 되기 전에 호출된다.

function Champion({champion, onRemove, onToggle}){
  const [level, setLevel] = useState(0);

  useEffect(()=>{
    console.log('렌더링 후 레벨: ', level);
    return () => {
      console.log('사라지기 전 레벨 : ', level)
      console.log('사라짐')
    }
  },[level]);

  return(
    <div>
      <b style={{cursor:'pointer'}} onClick={()=>onToggle(champion.id)}>{champion.name}</b> <br/>
      <span>스킬: {champion.skill}</span>
      <div style={{color : 'red'}}>{champion.op && <b>OP 챔피언</b>}</div>
      <div>레벨 : {level}</div>
      <button onClick={() => setLevel(level + 1)}>레벨</button>
      <button onClick={() => onRemove(champion.id)}>삭제</button>
      <div><br/></div>
    </div>
  )
}

갱플랭크 -> 레벨 -> 레벨 -> 삭제 순으로 클릭을 해보면 다음과 같이 나올 것이다.

  • 처음 화면에 렌더링이된다.
  • 갱플랭크 클릭 시 level에만 의존하기 때문에 아무런 변화가 없다.
  • 레벨을 증가 시키면 컴포넌트가 리렌더링이 되기 때문에 사라질 때 cleanup 함수가 실행되고 렌더링 될 때 useEffect의 함수가 호출된다.
  • 레벨을 또 누르면 위와 같은 동작이 수행된다.
  • 삭제를 누를 시 컴포넌트가 사라지므로 cleanup 함수가 호출된다.

컴포넌트가 unmount 시에만 cleanup 함수를 실행시키고 싶다면 다음과 같이 deps를 빈 배열로 넣어준다.

function Champion({champion, onRemove, onToggle}){
  const [level, setLevel] = useState(0);

  useEffect(()=>{
    console.log('렌더링 후 레벨: ', level);
    return () => {
      console.log('사라지기 전 레벨 : ', level)
      console.log('사라짐')
    }
  },[]);

  return(
    <div>
      <b style={{cursor:'pointer'}} onClick={()=>onToggle(champion.id)}>{champion.name}</b> <br/>
      <span>스킬: {champion.skill}</span>
      <div style={{color : 'red'}}>{champion.op && <b>OP 챔피언</b>}</div>
      <div>레벨 : {level}</div>
      <button onClick={() => setLevel(level + 1)}>레벨</button>
      <button onClick={() => onRemove(champion.id)}>삭제</button>
      <div><br/></div>
    </div>
  )
}

useEffect는 언제 써야 하는가?

  • mount 시 하는 작업
    • props로 받은 값을 컴포넌트의 로컬 상태로 설정
    • 외부 API 요청
    • 라이브러리 사용
    • setInterval을 통한 반복작업
    • setTimeout을 통한 작업 예약
  • unmount 시 하는 작업
    • setInterval, setTimeout을 사용하여 등록한 작업들 clear 하기 (clearInterval, clearTimeout)
    • 라이브러리 인스턴스 제거

주의할 점

  • useEffect 안에서 사용하는 상태나, props가 있다면 deps에 넣어주어야 하는 것이 규칙이다.
  • 만약 사용한 값을 deps에 넣어주지 않는다면, useEffect 안의 함수가 실행될 때 최신 상태, props를 가리키지 않는다.
  • deps에 파라미터를 생략하면 컴포넌트가 리렌더링 될 때마다 useEffect 함수가 호출된다.
profile
미래를 생각하는 개발자

0개의 댓글