노마드코더

[ReactJS로 영화 웹 서비스 만들기] 5. Effects

졸려질려 2023. 5. 31. 16:51
반응형

 본 글은 노마드코더의 "ReactJS로 영화 웹 서비스 만들기" 강의를 듣고 정리한 글입니다. 더 자세한 설명과 정보는 영상을 통해 확인할 수 있습니다.

 

ReactJS로 영화 웹 서비스 만들기 – 노마드 코더 Nomad Coders

React for Beginners

nomadcoders.co

 지난 글에서는 CRA(Create-React-App) 에 대한 강의를 정리하여 올렸습니다. 이번에는 Effects 에 대한 강의를 듣고 정리합니다.

 

[ReactJS로 영화 웹 서비스 만들기] 4. Create React App

본 글은 노마드코더의 "ReactJS로 영화 웹 서비스 만들기" 강의를 정리한 글입니다. 더 자세한 내용과 설명은 무료 강의를 들어주세요! ReactJS로 영화 웹 서비스 만들기 – 노마드 코더 Nomad Coders 왕

choboit.tistory.com


1. Overview

 이전 강의 중에서 State 를 통해 값을 변경하고, 그에 맞게 화면을 다시 렌더링하는 것을 보았다. 처음 봤을 때는 신기하고, 좋은 기능이지만, 많이 사용한다면 살짝 부담감이 느껴지는 기능이기도 하다. 예를 들어, 다음과 같이 간단한 State 사용 코드를 작성해보자. 기본적인 템플릿은 CRA 를 기반으로 하고 있다.

// App.js

import { useState } from "react";

function App() {
  const [counter, setCounter] = useState(0);
  const onClick = () => {
    setCounter((prev) => prev + 1)
  };

  console.log("I run all the time");

  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={onClick}>Click Me</button>
    </div>
  );
}

export default App;

 CRA 의 index.js 에서 <App /> 을 불러내고, App 의 내부 코드는 위와 같이 작성되었다. 버튼을 클릭할 때마다, counter 라는 값을 가진 State 의 값이 1 증가하는 코드이다. 그런데, 코드 안에 어색하게 보이는 코드 한 줄이 있다. 바로 "I run all the time" 이라는 문자열을 콘솔로 찍어내는 코드이다. 위의 코드 전체를 브라우저에서 돌리면 어떤 결과가 나올까?

 콘솔 문구가 1번 찍힐 줄 알았는데, 2번이 찍혀있다. App 컴포넌트가 2번 만들어졌다는 것일까? "index.js" 파일을 보면, CRA 에서 기본적으로 구성해준 그대로 유지되어 있다.

// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

 <App /> 이 한 번만 쓰인 것을 확인할 수 있다. 콘솔 문구가 2번 찍힌 이유는 위 코드에서 <React.StrictMode> 때문이다. 본론과 중요하게 관련이 없으므로 간단하게 정리하면, <React.StrictMode> Production 이 아닌 Development 개발 환경에서 잠재적인 문제점을 도출하고자, 내부에 컴포넌트들을 두 번씩 호출하는 기능을 가지고 있다. 자세한 정보는 위 링크를 참고하면 된다.

 본론으로 돌아와서, 간단하게 <React.StrictMode> 를 지우고 다시 브라우저를 열어보자. 그리고, 버튼도 눌러본다.

 버튼을 10번 눌렀으니, State 의 값을 변경하는 함수가 10번 실행되고, State 가 10번 바뀐만큼 렌더링도 10번 다시 진행된 것을 볼 수 있다. 위와 같이 페이지가 간단하다면 큰 문제가 되지 않겠지만, 만약 규모가 좀 더 커진다면 작은 액션으로 여러 코드가 다시 실행될 수 있다는 것을 뜻한다. 만약에 콘솔 로그 함수가 아닌, 서버 API 와 통신하는 코드였다면, 관련 없는 버튼을 누른 것 만으로 서버와 다시 통신하게 되는 불상사가 일어날 수 있다. 따라서, 컴포넌트가 다시 렌더링 되더라도 동일한 코드를 2번 이상 호출되게 하고 싶지 않을때, useEffect 를 사용한다.


2. useEffect

 "useEffect" 는 useState 같이 react 모듈로부터 사용할 수 있는 기능이다. 간단한 사용 방법은 다음과 같다.

 useEffect 는 react 모듈에서 제공하는 function 중 하나이며, 2개의 인자값을 받는다. 자세한 내용은 잠시 뒤에 기술하고, 어떻게 사용하는지는 다음 코드와 같다.

// App.js

import { useEffect, useState } from "react";

function App() {
  ...
  useEffect(() => {
    console.log("I run Only once");
  }, []);
  ...
}
...

 위와 같이 첫번째 인자값에는 함수를 넣어주고, 두번째 인자값에는 빈 배열을 넣어주었다. 그 후, 브라우저에서 로그를 살펴보면 다음과 같이 출력된다.

 useEffect 안에 있었던 "I run Only once" 라는 문구는 페이지가 로드되면서 한 번 출력되고, 그 외에 콘솔 로그는 클릭할 때마다 출력된다. 이처럼 useEffect 는 페이지가 최초에 렌더링 되었을 때만 호출하고 싶은 함수를 지정할 수 있다.


3. Deps

 이번에는 useEffect 의 두번째 인자값을 사용해본다. 우선, 위 코드에서 <input> 을 추가하여 간단한 검색 입력칸을 만든다. 그리고, keyword 라는 이름의 State 를 추가하여 <input> 과 연동한다. 코드는 다음과 같다.

import { useEffect, useState } from "react";

function App() {
  const [counter, setCounter] = useState(0);
  const [keyword, setKeyword] = useState("");

  const onClick = () => {
    setCounter((prev) => prev + 1);
  };

  const onChange = (event) => {
    setKeyword(event.target.value);
  };
  console.log(`Search For: ${keyword}`);
  console.log("I run all the time");

  useEffect(() => {
    console.log("I run Only once");
  }, []);

  return (
    <div>
      <input value={keyword} onChange={onChange} type="text" placeholder="Search here..." />
      <h1>{counter}</h1>
      <button onClick={onClick}>Click Me</button>
    </div>
  );
}

export default App;

 위 코드는 <input> 에 무언가 입력되면, State 의 특성 때문에 다시 페이지가 렌더링 된다.

 그런데 문제는 keyword 와 counter 가 같은 선상에 있기 때문에, <input> 에 문자열을 입력한 후에 <button> 을 클릭하면 keyword 에 대한 것도 같이 렌더링이 된다는 것이다. 이 때, useEffect 를 사용해서 keyword 와 관련된 렌더링을 counter 와 독립적으로 떨어지게 만들 수 있다.

import { useEffect, useState } from "react";

function App() {
  const [counter, setCounter] = useState(0);
  const [keyword, setKeyword] = useState("");

  const onClick = () => {
    setCounter((prev) => prev + 1);
  };

  const onChange = (event) => {
    setKeyword(event.target.value);
  };
  console.log("I run all the time");

  useEffect(() => {
    console.log("I run Only once");
  }, []);

  useEffect(() => {
    console.log(`Search For: ${keyword}`);
  }, [keyword]);

  return (
    <div>
      <input value={keyword} onChange={onChange} type="text" placeholder="Search here..." />
      <h1>{counter}</h1>
      <button onClick={onClick}>Click Me</button>
    </div>
  );
}

export default App;

 위와 같이 useEffect 의 두번째 인자값에 넣을 배열에 keyword 를 넣어주면 된다. 그러면 keyword 가 들어간 useEffect 는 keyword 의 State 가 변경될 때만 첫번째 인자에 있는 함수를 호출한다.

 여기에 더해, counter 와 관련된 렌더링도 완전히 독립시키고 싶다면 useEffect 를 똑같이 추가해주면 된다.

import { useEffect, useState } from "react";

function App() {
  const [counter, setCounter] = useState(0);
  const [keyword, setKeyword] = useState("");

  const onClick = () => {
    setCounter((prev) => prev + 1);
  };

  const onChange = (event) => {
    setKeyword(event.target.value);
  };

  useEffect(() => {
    console.log("I run Only once");
  }, []);

  useEffect(() => {
    console.log(`Search For: ${keyword}`);
  }, [keyword]);

  useEffect(() => {
    console.log("I run when counter changed");
  }, [counter]);

  return (
    <div>
      <input value={keyword} onChange={onChange} type="text" placeholder="Search here..." />
      <h1>{counter}</h1>
      <button onClick={onClick}>Click Me</button>
    </div>
  );
}

export default App;

 counter 만의 useEffect 를 추가했다. 이제 위 코드를 실행해보면 다음과 같이 실행된다.

 정리를 해보자면, useEffect 는 첫번째 인자값에 호출할 함수가 들어가고, 두번째 인자값에는 옵저빙(Observing) 할 State 의 배열이 들어간다. useEffect 는 페이지가 로드되는 최초에 한 번씩 실행되며, 그 이후로는 두번째 인자값에 들어간 State 의 변경에 따라 실행된다. 두번째 인자값에 빈 배열이 들어간다면, 추가로 옵저빙 할 State 가 없다는 뜻이므로, 맨 처음에만 실행되고 이후에 다시 호출되지 않는다. 반대로, 배열에 2개 이상의 State 를 넣을 수 있으며, 다수의 State 를 넣는다면 해당 State 들이 각각 변경될 때마다 useEffect 는 호출된다.


4. CleanUp

 useEffect 는 기본적으로 Component 가 렌더링될 때 한 번 실행된다. 그와 반대로 Component 가 사라질 때에 특정 함수를 호출하는 방법이 존재한다.

import { useEffect, useState } from "react";

function Hello() {
  useEffect(() => {
    console.log("Hello Created");
    return () => { console.log("Hello Destroyed"); };
  }, []);
  return <h1>Hello</h1>
}

function App() {
  const [showing, setShowing] = useState(false);
  const onClick = () => { setShowing((prev) => !prev)}

  return (
    <div>
      {showing ? <Hello /> : null}
      <button onClick={onClick}>{showing ? "Hide" : "Show"}</button>
    </div>
  );
}

export default App;

 위 코드는 showing State 에 따라 버튼의 문구가 바뀌고, <Hello> 컴포넌트가 나타났다 사라졌다 하게 된다. 코드 중에서 useEffect 에 다른 점이 존재한다.

function Hello() {
  useEffect(() => {
    console.log("Hello Created");
    return () => { console.log("Hello Destroyed"); };
  }, []);
  return <h1>Hello</h1>
}

 바로 useEffect 가 호출하는 함수 내에 return 문이 존재하는 것이다. return 문에 들어가는 함수가 곧 <Hello> 컴포넌트가 사라질 때 호출되는 함수이다.

 위와 같이 <button> 을 클릭 했을 때, <Hello> 컴포넌트가 생성되며 "Hello Created" 로그가 출력된다. 그리고 다시 <button> 을 클릭하면, <Hello> 컴포넌트가 사라지면서 "Hello Destroyed" 로그가 출력되는 것을 확인할 수 있다. 이처럼 특정 컴포넌트가 사라질 때 실행하고자 하는 함수가 있다면, useEffect 의 첫번째 인자로 넣는 함수 안에 return 문을 추가해주면 된다.

반응형