상세 컨텐츠

본문 제목

[리액트 스타터3] project 1 [카운터] 앱 만들기 6장. 라이프 사이클과 리액트 개발자 도구

23-24/React.js 3

by YUZ 유즈 2023. 11. 17. 10:00

본문

728x90

project 1 [카운터] 앱 만들기

프로젝트 준비하기

  • [카운터] 앱 프로젝트 진행
  • 숫자를 더하고 빼는 기능을 구현

요구사항 분석하기

 

  • 3개의 컴포넌트의 역할
    • App컴포넌트 : Viewer과 Controller 컴포넌트를 감싸는 템플릿
    • Viewer 컴포넌트 : 현재의 카운트를 표시함
    • Controller 컴포넌트 : 카운트를 제어할 수 있는 기능 제공

리액트 앱 만들기

  1. 7week > project1 생성
  2. npx create-react-app . 명령어 입력
  3. src 폴더에서 파일 삭제
    • src/App.test.js
    • src/logo.svg
    • src/reportWebVitals.js
    • src/setupTest.js
  4. index.js 수정하기기
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

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

 

5. App.js 수정하기

import './App.css';

function App() {

  return (
    <div className="App"></div>
  );
}

export default App;

 

6. npm run start 명령어 입력하여 리액트 앱 실행

 

UI 구현하기

  • UI : 사용자 인터페이스라는 뜻, 웹페이지에서 사용자와 상호작용 하는 요소를 의미

Viewer 컴포넌트 만들기

  1. src > component > Viewer.js 파일 만들기
  2. Viewer.js 작성하기
const Viewer = () => {
    return (
        <div>
            <div>현재 카운트: </div>
            <h1>0</h1>
        </div>
    )
};

export default Viewer;

 

3. App.js 에 import 작성하기

import './App.css';
import Viewer from './component/Viewer'; //component 폴더의 Viewer 불러오기

function App() {

  return (
    <div className="App">
      <h1>Simple Counter</h1> //제목 렌더링
      <section>
        <Viewer /> //Viewer 컴포넌트 렌더링
      </section>
    </div>
  );
}

export default App;

 

Controller 컴포넌트 만들기

  1. src > component > Controller.js 파일 생성
  2. Controller.js 작성하기
const Controller = () => {
    return (
        <div>
            <button>1</button>
            <button>10</button>
            <button>100</button>
            <button>+100</button>
            <button>+10</button>
            <button>+1</button>
        </div>
    );
};

export default Controller;

 

3. App.js 수정하기

import './App.css';
import Viewer from './component/Viewer';
import Controller from './component/Controller'; //Controller 컴포넌트 불러오기

function App() {

  return (
    <div className="App">
      <h1>Simple Counter</h1>
      <section>
        <Viewer />
      </section>
      <section>
        Controller /> // Controller 컴포넌트 렌더링
      </section>
    </div>
  );
}

export default App;

 

 

컴포넌트 스타일링 하기

  1. App.css 작성하기
body{
  padding: 20px;
}

.App{
  margin: 0 auto;
  width: 500px;
}

.App > section {
  padding: 20px;
  background-color: rgb(245, 245, 245);
  border: 1px solid rgb(240, 240, 240);
  border-radius: 5px;
  margin-bottom: 10px;
}

 

기능 구현하기

State를 이용해 카운터 기능 구현하기

  • Controller 컴포넌트에 있는 버튼을 클릭하면, Viewer 컴포넌트에 있는 카운트가 증가하거나 감소해야한다.
    • ex) <+100> 버튼을 누르면 Viewer 컴포넌트는 0에서 100으로 바껴야한다.

[방식]

  1. State(count)를 만들고 초깃값을 0으로 설정하기
  2. Controller 컴포넌트의 버튼을 클릭하면 현재 State 값을 버튼이 전달하는 값과 계산해 변경하기
  3. 변경된 State 값은 Viewer컴포넌트에 전달되어 페이지의 카운트값을 업데이트한다.

State는 어떤 컴포넌트에 만들까?

  • 어떤 컴포넌트에서 [카운터] 앱의 State를 만들어야할까?
    • App 컴포넌트
      • App 컴포넌트에서 받은 Props를 Viewer로 넘겨줘서 페이지에 렌더링할 수 있다.
      • App 컴포넌트에서 Controller로 이벤트 핸들러를 전달할 수 있다.(App 컴포넌트의 State 값을 업데이트 한다)
    • View, Controller 컴포넌트에 작성하면 안되는 이유
      • Controller 와 Viewer 컴포넌트에 setCount, count 를 전달할 수 없음
      • 다른 컴포넌트에 데이터를 전달할 때 Props를 사용하는데, Props는 부모만이 자식한테 전달할 수 있기 때문이다.
      • View - Controller 컴포넌트는 부모-자식 관계가 아니므로 어떤 값도 전달할 수 없다.
    • App.js
    import './App.css';
    import { useState } from "react";
    import Viewer from './component/Viewer';
    import Controller from './component/Controller';
    
    function App() {
    
      const[count, setCount] = useState(0);
      const handleSetCount = (value) => {
        setCount(count + value);
      };
    
      return (
        <div className="App">
          <h1>Simple Counter</h1>
          <section>
            <Viewer count = {count}/> //Viewer 컴포넌트에서 State변수 count의 값을 Props로 전달
          </section>
          <section>
            <Controller handleSetCount={handleSetCount} /> // Controller 컴포넌트에 State값을 변경하는 함수 setCount 를 Props로 전달
          </section>
        </div>
      );
    }
    
    export default App;
    
    • Viewer.js
    const Viewer = ({count}) => {
        return (
            <div>
                <div>현재 카운트: </div>
                <h1>{count}</h1>
            </div>
        )
    };
    
    export default Viewer;
    
    ⇒ 리액트에서는 부모가 리렌되거나 전달된 Props가 변경되면 자식 컴포넌트도 자동으로 리렌더 된다.
    • Controller .js
    const Controller = ({handleSetCount}) => {
        return (
            <div>
                <button onClick={() => handleSetCount(-1)}>-11</button>
                <button onClick={() => handleSetCount(-10)}>-10</button>
                <button onClick={() => handleSetCount(-100)}>-100</button>
                <button onClick={() => handleSetCount(+100)}>+100</button>
                <button onClick={() => handleSetCount(+10)}>+10</button>
                <button onClick={() => handleSetCount(+1)}>+1</button>
            </div>
        );
    };
    
    export default Controller;
    
    ⇒ App 컴포넌트에서 이벤트 핸들러를 Props로 전달받아 count를 업데이트 시킨다.

<+100> 버튼을 눌렀을 때

 

리액트답게 설계하기

  • 단방향 데이터 흐름 : Props의 전달 방향은 부모로부터 자식에게 전달하는 방식
  • State를 변경하는 이벤트는 자식에서 부모를 향해 역방향으로 전달되어야 한다.
  • 데이터 전달 방향 : 단방향
  • 이벤트 전달방향 : 역방향

6장 라이프 사이클과 리액트 개발자 도구

리액트 컴포넌트의 라이프 사이클

  • 라이프 사이클
  1. 탄생(Mount) : 컴포넌트를 페이지에 처음 렌더링할 때
  2. 업데이트(Update) : State 업데이트, Props 업데이트, 부모 컴포넌트 리렌더, 자신도 리렌더
  3. 죽음(Unmount) : 더 이상 페이지에 컴포넌트를 렌더링하지 않을 때

UseEffect

  • 어떤 값이 변결될 때마다 특정 코드를 실행하는 리액트 훅이다.
  • 컴포넌트의 State 값이 바뀔 때마다 변경된 값을 콘솔에 출력하게 하는 것이다.

하나의 값 검사하기

  • App 컴포넌트에서 State 변수 count의 값이 바뀌면, 변경된 값을 콘솔에 출력하기
  • App.js 수정하기
import './App.css';
**import { useEffect, useState } from "react"; // useEffect 라이브러리 추가하기**
import Viewer from './component/Viewer';
import Controller from './component/Controller';

function App() {

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

  useEffect(() => { // useEffect를 호출하고, (콜백함수, 배열)을 전달한다.
    console.log("count 업데이트 : ", count);
  }, [count]);

  return (
    <div className="App">
      <h1>Simple Counter</h1>
      <section>
        <Viewer count = {count}/> 
      </section>
      <section>
        <Controller handleSetCount={handleSetCount} />
      </section>
    </div>
  );
}

export default App;

 

[useEffect의 용법]

useEffect(callback, [deps])

  • deps(배열)의 요소 값이 변경되면 callback 함수를 실행한다.
  • 배열 요소에 State변수 count가 있으므로, count가 변경되면 callback 함수를 실행한다.

여러 개의 값 검사하기

  • 배열 요소 중 하나가 변경되어도 useEffect는 콜백 함수를 실행한다.
  1. 변경 요소가 count밖에 없어 text라는 State 변수를 하나 더 추가하자
  • App.js
import './App.css';
import { useEffect, useState } from "react";
import Viewer from './component/Viewer';
import Controller from './component/Controller';

function App() {

  const[count, setCount] = useState(0);
  const[text, setText] = useState(""); //State 변수 text 생성
  const handleSetCount = (value) => {
    setCount(count + value);
  };
  const handleChangeText = (e) => { //핸들러 함수 만들기
    setText(e.target.value);
  };
  useEffect(() => {
    console.log("count 업데이트 : ", count);
  }, [count]);

  return (
    <div className="App">
      <h1>Simple Counter</h1>
      <section>
        <input value={text} onChange={handleChangeText} /> //value 속성을 text에 전달하는 handleChangeText 함수 지정하기
      </section>
      <section>
        <Viewer count = {count}/> 
      </section>
      <section>
        <Controller handleSetCount={handleSetCount} />
      </section>
    </div>
  );
}

export default App;

 

2. text 값이 변경되었을 때도 useEffect의 콜백 함수를 호출하도록 하기

  • App.js
useEffect(() => {
    console.log("count 업데이트 : ", text, count);
  }, [count, text]); // text 변수 추가하기

 

useEffect로 라이프 사이클 제어하기

  1. 의존성 배열에 아무것도 전달하지 않았을 때
  • 라이프 사이클 중 업데이트(update)가 발생하면 특정 코드를 실행시키기
  • App.js 수정
useEffect(() => {
    console.log("컴포넌트 업데이트");
  });

의존성 배열에 아무것도 전달되지 않았고, useEffect는 컴포넌트를 렌더링할 때마다 콜백 함수를 실행한다.

  • 마운트 시점, 컴포넌트를 리렌더하는 업데이트 시점 두번 출력됨

 

2. 의존성 배열에는 아무것도 전달하지 않지만, 콜백함수 실행시키기

  • useRef를 이용하여 특정 시점에 코드 실행시키기
import { useRef, useEffect, useState } from "react";

const didMountRef = useRef(false); //마운트 했는지 판단하는 변수(Ref 객체로 생성)

  useEffect(() => { // 마운트 시점에 '컴포넌트 업데이트' 문자열 출력하지 않도록 하는 조건문
		//의존성 배열이 없으므로 콜백함수는 마운트 시점에 실행되어야 함.
    if(!didMountRef.current){ // 처음 마운트니?(!false = true : true니?)
      didMountRef.current = true; //엉 마운트야(true로 바뀜)
      return;
    } else{// true니? (!true != true)
      console.log("컴포넌트 업데이트"); //true니까 마운트 시점아니야 -> 리렌더구나
    }
  });

마운트, 업데이트 시점 모두 콜백함수를 호출한다.

  • 내부 조건문과 Ref 객체로 특정 시점에만 코드를 실행할 수 있다.
  • 마운트 시점: 아무것도 출력하지 않는다.
  • 업데이트 시점: 문자열을 출력한다.

 

컴포넌트의 마운트 제어하기

  • 마운트 시점에 실행되는 코드 작성하기

컴포넌트의 마운트를 제어한다 라고 표현한다.

  • App.js
useEffect(() => {
    if(!didMountRef.current){
      didMountRef.current = true;
      return;
    } else{
      console.log("컴포넌트 업데이트");
    }
  });

  **useEffect(() => { //빈 배열을 전달하면 컴포넌트의 마운트 시점에만 콜백함수를 실행시킨다.
    console.log("컴포넌트 마운트");
  }, []);**

⇒ App 컴포넌트는 처음에만 마운트하므로 “컴포넌트 마운트” 문자열은 한 번만 출력된다.

 

컴포넌트 언마운트 제어하기

  • 언마운트 : 컴포넌트가 페이지에서 제거될 때이다.

클린업

  • 언마운트 시점에 필요한 코드 실행 방법
  • 특정 함수가 실행되고 종료된 후에, 미처 정리하지 못한 사항을 처리하는 일
    • App. js
    useEffect(() => {
        setInterval(() => { //콜백함수는 다시 setInterval 함수를 호출
          console.log("깜빡"); //밀리초 시간이 경과하면 첫번째 인수의 콜백함수를 실행한다.
        }, 1000); //1초마다 문자열을 출력한다.
      });
    

문제!!!!

  • useEffect 의 두번째 인수로 아무것도 전달하지 않았기 때문에, 버튼을 클릭해 State를 변경하면 새 인터벌 함수를 생성한다.

⇒ setInterveral에서 인터벌을 생성한 다음에 이를 종료하지 않아서 발생!!!

해결 : clearInterval 내장함수를 호출하여 종료해줘야 한다.

  • App.js
useEffect(() => {
    const intervalID = setInterval(()=>{ //1. setInterval은 생성될 때 인터벌 식별자(id)를 반환한다.(**마운트 시점**에 실행)
		// id가 intervalID에 저장됨.
      console.log("깜빡");
    }, 1000);
		
    return () => { //2. useEffect에 인수로 전달된 콜백함수로 새 함수를 반환 (콜백 함수가 실행되기 전 이므로 **언마운트 시점에 실행**)
      console.log("클린업");
      clearInterval(intervalID); //3. 인수로 1에서 생성한 인터벌 식별자를 전달하여 앞서 생성된 인터벌을 삭제한다.
    }
  });

useEffect의 콜백함수가 반환하는 함수클린업 함수라고 한다.

컴포넌트를 리렌더링할 때마다 새 인터벌을 생성하고 기존 인터벌은 삭제한다.

useEffect의 콜백 함수가 또 다른 함수를 반환하는 클린업 기능을 이용하면 인터벌 같은 종료 후에도 남아있는 작업을 삭제할 수 있다.

 

클린업을 활용하여 컴포넌트 언마운트 제어하기

  • 컴포넌트 언마운트 : 컴포넌트가 페이지에서 사라질 때 원하는 코드 실행시키기
  1. Even.js 컴포넌트 만들기
  • 기능 : count 값이 짝수면 특정 문자열을 페이지에 렌더링하기(조건부 렌더링으로 구현)
  • Even.js
function Even() {
    return <div>현재 카운트는 짝수입니다</div>

}

export default Even;
  •  App.js
**import Even from './component/Even'; //Even 컴포넌트 import**

<section>
        <Viewer count = {count}/> 
        **{count % 2 === 0 && <Even />} // count가 짝수일 때(참일때), Even 컴포넌트를 렌더링해라**
      </section>

 

2. Even 컴포넌트에서 useEffect를 사용기

  • 언마운트될 때 콘솔에 특정 문자열 출력하기
    • Even.js
    import { useEffect } from "react";
    
    function Even() {
    
        **useEffect(() => { //의존성 배열로 빈 배열 전달, 콜백함수가 함수가 화살표 함수를 반환
            //마운트 시점에서
    				return() => { // 언마운트 시점에서 호출
                console.log("Even 컴포넌트 언마운트");
            };
        }, []);** 
        return <div>현재 카운트는 짝수입니다.</div>
    }
    
    export default Even;
    
    ⇒ 콜백함수가 함수를 반환하면 이 함수는 컴포넌트의 언마운트 시점에 실행된다.

"빈 의존성 배열로 인해 언마운트 시에만 클린업 함수가 호출되기 때문에 'Even 컴포넌트 언마운트'가 출력되지 않는다.”

  • 의존성이 없으므로, useEffect는 한 번만 실행되고, 컴포넌트가 마운트되었을 때만 실행된다.
  • useEffect에 전달된 콜백 함수 내에서 반환된 함수(클린업 함수)컴포넌트가 언마운트될 때 호출된다.
  • 짝수일 때는 Even 컴포넌트가 마운트 되어있는 상태이므로 “Even 컴포는트 언마운트”가 출력되지 않는다.

 

리액트 개발자 도구

리액트 개발자 도구 설치하기

  • [Components] 탭 : 현재 리액트 앰의 컴포넌트 트리와 각 컴포넌트가 관리하는 State 등의 정보를 확인할 수 있다.
  • [Profiler] 탭 : 리액트 컴포넌트의 렌더링 성능을 측정한다.

컴포넌트 트리 살펴보기

  • [Components] 탭 선택하기
    • App > Viewer, Even, Controller 3개의 자식 컴포넌트
    • Props와 State
    • 리액트 훅 기능 : State, Ref, Effect

  • State 모니터링하기(App 컴포넌트)
    • <+10>>을 눌렀을 때 State 값이 10으로 변경된다.
  • Props 모니터링하기(Viewer 컴포넌트)
    • App 컴포넌트가 전달한 State 변수 count 10을 Props로 받는다.
  • 리렌더 하이라이트 기능 사용하기
                    •  

 

Quiz

1. Props의 전달방향은 (단방향 데이터 흐름)이고, State를 변경하는 이벤트는 (역방향)으로 전달된다.

2. 리액트 컴포넌트의 라이프 사이클 3가지는 (탄생(Mount)), (업데이트(update)), (죽음(unmount)) 이다.

3. useEffect에서 두번째 인수인 배열의 요소 값이 변경되면 (callback 함수)가 호출된다.

4. 의존성 배열에 아무것도 전달되지 않으면 (마운트 시점), (업데이트 시점)이 모두 실행된다.

5. 언마운트 시점에 미처 정리하지 못한 사항을 처리하는 일을 (클린업)이라고 한다.

6. (5번 정답)을 하기 위해서(clearInterval) 내장함수를 호출하여 종료해줘야 한다.

7. useEffect의 콜백함수가 화살표 함수를 반환하면 이 함수는 컴포넌트의 (언마운트 시점)에 실행된다.

 

프로그래밍 문제

1. useEffect()를 활용하여 마운팅 상태일때 "마운팅 상태입니다."를 콘솔창에 출력하고, 언마운팅 상태일때 "언마운팅 상태입니다." 라고 출력하는 함수를 작성하시오.

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
  	//여기를 채우시오.
    };
  }, []);

 

 

2. 마운팅 상태일 때, 콘솔에 2초마다 "안녕"을 출력한다. clearInterval 내장함수를 이용하여 클린업 기능을 구현하여라.

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
  	// 여기를 채우시오
  }, []);

 

 

 

 

 

 

 

 

 


정답

1. 

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // 컴포넌트가 마운트될 때 실행될 코드
    console.log('마운트 상태입니다.');

    // 언마운트될 때 실행될 코드
    return () => {
      console.log('언마운트 상태입니다.');
    };
  }, []);

 

2. 

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    const intervalId = setInterval(() => {
      console.log('안녕');
    }, 2000);

    // 언마운트될 때 클린업 함수 실행
    return () => {
      clearInterval(intervalId);
      console.log('클린업 함수: clearInterval 실행');
    };
  }, []);

 

출처 : 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023), p-.

Corner React.js 3 

Editor: smurfs

728x90

관련글 더보기