React.js

김민지·2024년 7월 3일

🍪One Bite React.js

목록 보기
4/6

React.js란?

  • Meta(Facebook)이 개발한 오픈소스 JavaScript 라이브러리
    대규모 웹서비스의 UI를 더 편하게 개발하기 위해 만들어진 기술

React의 장점:

  1. 컴포넌트(Header, Main, Footer)를 기반으로 UI를 표현함
    Header.js, Main.js, Footer.js처럼 각각의 컴포넌트를 각각의 .js파일로 표현가능 ➡️ 유지보수가 용이!
    기존의 html 코드들:

    리엑트:

  2. 화면 업데이트 구현이 쉬움
    업데이트란? 사용자의 행동(클릭, 드래그)에 따라 웹페이지가 스스로 모습을 바꾸어 상호작용하는 것 (예: 버튼을 클릭하면 화면이 바뀜)
    업데이트 구현이 쉬운 이유: 선언형 프로그래밍(과정은 생략하고 목적을 작성하는 방식)이므로 업데이트를 위한 복잡한 동작을 직접 정의할 필요 없이 특정 변수의 값을 바꾸는 것만으로도 화면을 업데이트 시킬 수 있음

    리엑트의 방식:

    랜더링이란? UI요소를 화면에 그려내는 것

  3. 화면 업데이트가 빠르게 처리됨

    업데이트 과정:
    (1) HTML과 CSS를 각각 DOM과 CSS Object Model로 변환

    (2) DOM(요소들의 위치, 배치, 모양에 관한 모든 정보)과 CSS Object Model(요소들의 스타일과 관한 모든 정보)을 웹페이지의 설계도라고 할 수 있는 Render Tree로 변환
    (3) Render Tree에 포함된 요소의 배치를 잡는 작업인 Layout 과정 수행
    (4) 요소를 실제로 화면에 그려내는 Painting 수행
    업데이트 발생 시점: JavaScript가 DOM을 수정했을 때!
    이 때, 다른 작업들에 비해 특히 Layout과 Painting을 다시 하는 것은 굉장히 오래 걸리는 작업이므로 따로 Reflow(Layout을 다시 한다), Repaint(Painting을 다시 한다)의 용어가 존재함
    ✨ DOM을 여러번 수정하면 지연시간이 지나치게 늘어나서 worst case가 되므로 동시에 발생하는 업데이트들을 모아서 한번에 DOM을 수정해야 하는데, 이 과정을 React는 자동으로 해결해줌!
    내부적으로 Virtual DOM이라는 가상의 DOM을 사용해서 업데이트가 발생하면 실제 DOM을 수정하기 전에 가상의 DOM에 먼저 반영해봄

React App 생성하기

  • 큰 흐름: Node.js 패키지 생성 ➡️ React 라이브러리 설치 ➡️ 기타 도구 설치 및 설정 (설정과정이 복잡하므로 Vite라는 기본 설정이 적용된 React App 생성 가능한 프론트엔드 개발툴 사용)
  1. npm create vite@latest 명령어로 vite 설치
  2. node-modules 폴더가 없으므로 npm i 명령어로 package.json에는 기재되어있지만 내 로컬에는 설치되지 않은 기타 라이브러리들을 한꺼번에 설치해주기
  3. 구조 설명
    public 폴더: 코드가 아닌 .svg, .jpg, .png 등의 이미지 파일, 폰트, 동영상 등의 정적인 파일들을 보관하는 저장소
  4. npm run dev 명령어를 통해 vite가 React app을 작동시키게 함 ➡️ h를 눌러서 도움말도 확인하고, 제공된 url로 접속해서 하나씩 확인해보기

React App 구동 원리?

// main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx' // App.jsx 파일을 확인하면, HTML 태그들을 return하는 함수를 포함한다는 것을 알 수 있음! 
// 화면에 표시되는 내용들은 App 파일에서 수정 가능
import './index.css'

// .createRoot 메소드: 인수로 전달받은 HTML 요소를 React의 root로 만들어줌
// .render: App 컴포넌트를 렌더링함
ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App /> // </>는 컴포넌트를 나타내는 문법
  </React.StrictMode>,
)
  • 컴포넌트: HTML 태그들을 return하는 함수
  • 웹페이지 단축키
    r + enter: restart the server
    u + enter: show server url
    o + enter: open in browser
    c + enter: clear console
    q + enter: quit

React 입문

  • 불필요한 파일 및 코드 삭제
  • 확장탭 > ESLint 설치
module.exports = {
  root: true,
  env: { browser: true, es2020: true },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:react/jsx-runtime',
    'plugin:react-hooks/recommended',
  ],
  ignorePatterns: ['dist', '.eslintrc.cjs'],
  parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
  settings: { react: { version: '18.2' } },
  plugins: ['react-refresh'],
  rules: {
    'react/jsx-no-target-blank': 'off',
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
    "no-unused-vars": "off", // 실제로 사용하지 않는 코드가 있을 때 오류를 표시하는 기능을 끄기
    "react/prop-types": "off" // 아직 배우지 않은 부분의 기능 끄기
  },
}

React 컴포넌트

HTML 태그들을 return하는 함수로, 반드시 첫글자를 대문자로 작성해야 컴포넌트로 인정받을 수 있으므로 주의해야 함!

  • main.jsx
ReactDOM.createRoot(document.getElementById('root')).render(
  <App />
)

함수 안에 있는 App 컴포넌트는 이후에 생성되는 모든 컴포넌트들의 조상이며, 이런 컴포넌트를 root 컴포넌트라고도 부름!
이후 생성되는 모든 컴포넌트들은 src > components 폴더를 생성한 후 해당 폴더에 아래와 같이 새 .jsx 파일을 생성하고 export로 내보내기

function Header() {
    return (
      <header>
        <h1>Header</h1>
      </header>
    )
  }

  export default Header;

이를 App.jsx 파일에서 불러와서, 아래와 같이 App 컴포넌트 내에 추가해주는 식으로 연결해줌

import './App.css'
import Header from './components/Header' // 불러옴
// React에서는 확장자 생략 가능 (ES모듈시스템과는 좀다름)

function App() {
  return (
    <>
      <Header/> // 추가됨
      <h1>Hey React!</h1>
    </>
  )
}

export default App

JSX(JavaScript eXtensions)

확장된 자바스크립트의 문법으로, JavaScript와 HTML을 혼용해서 사용할 수 있음 (HTML return, 랜더링 등)

function Main(){
    const number = 10;

    return(
        <main>
            <h1>main</h1>
            <h2>{number % 2 === 0 ? "짝수" : "홀수"}</h2>
        </main>
    );
}

export default Main;

✨ JSX 주의사항

  1. {} 내에는 자바스크립트 표현식 (삼항연산자, 어떤 값, 어떤 변수)만을 넣을 수 있음. if문, for문은 사용이 불가함.
  2. 숫자, 문자열, 배열 값만 정상적으로 출력됨
    true, undefined, null 등은 오류는 발생시키지 않지만 화면에 출력(랜더링)되지는 않음
    객체를 넣으면 오류가 발생함 (반드시 객체.아이디로 하나의 값을 넣어줘야 함)
  3. 모든 태그는 닫혀있어야 한다
    html에서는 <img>태그같은 몇몇 태그들은 단독으로도 사용이 가능했으나, jsx에서는 반드시 <img></img>로 닫아줘야 함
  4. 하나의 jsx 파일에는 반드시 하나의 최상위태그 만이 존재해야 한다
    html에서는 <head>, <body>, <div> 등의 다양한 최상위태그가 화면을 부분부분으로 구분하며 하나의 파일 안에 존재하는 반면, jsx 파일에서는 최상위태그는 하나여야 한다. 만약 최상위태그가 꼭 필요하지 않은 파일이라면 <></>처럼 빈 태그로라도 설정해야 한다.
  • main 컴포넌트가 조건에 따라 각각 다른 UI를 랜더링하도록 하기
// 방법 1
function Main(){
    const user = {
        name : "김민지",
        isLogin : true,
    }
    return(
        <>
            {user.isLogin ? <div>로그아웃</div> : <div>로그인</div>}
        </>
    );
}
  • 삼항연산자? 조건문 ? 조건문이 true일 때 실행될 명령문 : 조건문이 false일 때 실행될 명령문
// 방법 2
function Main(){
    const user = {
        name : "김민지",
        isLogin : true,
    }
    if (user.isLogin) {
        return <div>로그아웃</div>
    } else {
        return <div>로그인</div>
    }
}
  • 스타일 추가하기 (1): jsx 파일에 바로 추가하기
function Main(){
    const user = {
        name : "김민지",
        isLogin : true,
    }
    if (user.isLogin) {
        return <div style = {{
            backgroundColor:"beige", // 카멜표기법으로 표기해야함
            borderBottom: "5px dotted pink" // CSS랑 다름!!!
        }}>로그아웃</div>
    } else {
        return <div>로그인</div>
    }
}

export default Main;
  • 스타일 추가하기 (2): .css 파일 생성해서 불러오기
    이 때는 css 문법을 그대로 사용 가능
.logout {
    background-color: beige;
    border-bottom: 5px dotted pink;
}
import "./Main.css";

function Main() {
    const user = {
        name: "김민지",
        isLogin: true,
    };

    if (user.isLogin) {
        return <div className="logout">로그아웃</div>;
    } else {
        return <div>로그인</div>;
    }
}

export default Main;
  • html과는 달리 class가 아닌 className이라고 표기함

Props(Properties)

데이터를 전달하는 기능을 수행하며, 마치 함수를 호출하듯 작동함

// App.jsx
import './App.css'
import Header from './components/Header' // 확장자 생략 가능
import Main from './components/Main'
import Footer from './components/Footer'
import Button from './components/Button'

function App() {
  // 만약 전달해야 할 props가 늘어난다면?? 
  const buttonProps = {
    text: "메일",
    color: "red",
  }
  return (
    <>
      <Button {...buttonProps}/>
      <Button text={"카페"}/>
      <Button text={"블로그"}/>
    </>
  )
}

export default App
// Button.jsx
function Button(props) {
    return <button style={{
        color: props.color
    }}>{props.text} - {props.color.toUpperCase()}</button>
}

Button.defaultProps = {
    color: "black"
}

export default Button;
// 표현방식만 변경: 
function Button({text, color}) {
    return <button style={{
        color: color
    }}>{text} - {color.toUpperCase()}</button>
}

Button.defaultProps = {
    color: "black"
}

export default Button;

✨ React 19 버전부터는 defaultProps의 사용이 중지됨
대체방안:

const App = ( {data = '기본값} ) => { ... }

  • 부모 컴포넌트에서 자식 컴포넌트로 값을 전달하는 것은 불가함

이벤트 처리하기(Event Handling)

  • 이벤트란? 웹 내부에서 발생하는 사용자의 모든 행동 (예: 버튼클릭, 메시지 입력, 스크롤 ... )
  • 이벤트 핸들러 함수 사용하기!
function Button({text, color, children}) {
    return <button onClick={() => {
        console.log(text)
    }}
    style={{
        color: color
    }}>{text} - {color.toUpperCase()}
    {children}
    </button>
}

Button.defaultProps = {
    color: "black"
}

export default Button;
// 함수가 길 경우
function Button({text, color, children}) {
    function onClickButton() {
        console.log(text)
    }
    return <button 
    onClick={onClickButton} // click
    onMouseEnter={onClickButton} // 마우스 hover
    style={{
        color: color
    }}>{text} - {color.toUpperCase()}
    {children}
    </button>
}

Button.defaultProps = {
    color: "black"
}

export default Button;
  • 합성 이벤트 객체 (Synthetic Base Event)란?
    합성이벤트는 모든 웹브라우저(크롬, 사파리, 엣지, 파이어폭스, 웨일,,,)의 이벤트객체를 하나로 통일한 형태
    Cross Browsing Issue 해결 가능
    function onClickButton(e) { // e : 합성이벤트객체
        console.log(text)
    }
  • Cross Browsing Issue: 브라우저 별 스펙이 달라 발생하는 문제

state: 상태(변화 가능한 동적인 값)관리하기

  • state의 값이 바뀔 때마다 UI를 자동으로 다시 랜더링해줌 (re-render)

useState()는 2개의 값을 배열 형태로 묶어서 반환함 → 초기값: [undefined, f]

  1. 첫번째 값: 현재 상태값을 저장하는 변수 (보통 state라고 받아옴)로, useState(상태값)의 괄호 안 상태값에 넣은 값이 그대로 저장됨
  2. 두번째 값: 상태값을 업데이트하는 로직을 담은 함수 (보통 setState로 받아옴)
  • useState() 함수 설명
import './App.css'
// 함수 컴포넌트에서 state 생성하기 위해서 react에서 제공하는 내장함수 호출하기
import { useState } from 'react';

function App() {
  // const state = useState(); 이렇게 단일변수에 할당받기보다는, 
  const [state, setState] = useState(0); // 이렇게 구조분해로 할당받는 것이 일반적임
  // 버튼을 하나 만들어서 사용자가 해당 버튼을 클릭할 때마다 state의 값 증가시키기

  return (
    <>
      <h1>{state}</h1>
      <button onClick={()=>{
        setState(state + 1);
      }}>
      +
      </button>
    </>
  )
}

export default App
// state -> count로 바꿔서 명명
import './App.css'
import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  // 전구 상태 표시하기
  const [light, setLight] = useState("OFF");

  return (
    <>
    <div>
      <h1>{light}</h1>
      <button onClick = {() => {
        setLight(light === "ON" ? "OFF" : "ON")
      }}>
        {light === "ON" ? "스위치 끄기" : "스위치 켜기"}
      </button>
    </div>
    <div>
      <h1>{count}</h1>
      <button onClick={()=>{
        setCount(count + 1);
      }}>
      +
      </button>
    </div>
    </>
  )
}

export default App

State와 Props

  • 리엑트 컴포넌트들은 자신이 가지는 state가 변경되지 않아도 부모로부터 받아오는 props의 변화에 따라 리랜더링됨
    ✨ 리엑트 컴포넌트들이 rerendering되는 상황 (3)
  1. 자신이 관리하는 state의 값이 변경되었을 때
  2. 자신이 제공받는 props의 값이 변경되었을 때
  3. 부모 컴포넌트가 rerendering되었을 때
import './App.css'
import { useState } from 'react';

function Bulb({light}) { // App 컴포넌트의 자식컴포넌트임
  console.log(light) // 리렌더링 될때마다 출력됨
  // 아무 상관없어보이는 App컴포넌트의 + 버튼을 눌렀을 때에도 함께 리렌더링됨 (비효율적인 코드임!!)
  return <div>{light === "ON" ? 
  <h1 style={{backgroundColor: 'orange'}}>ON</h1> 
  : 
  <h1 style={{backgroundColor: "grey"}}>OFF</h1>}</div>
}

function App() {
  const [count, setCount] = useState(0);
  // 전구 상태 표시하기
  const [light, setLight] = useState("OFF");

  return (
    <>
    <div>
      <Bulb light = {light}/>
      <button onClick = {() => {
        setLight(light === "ON" ? "OFF" : "ON")
      }}>
        {light === "ON" ? "스위치 끄기" : "스위치 켜기"}
      </button>
    </div>
    <div>
      <h1>{count}</h1>
      <button onClick={()=>{
        setCount(count + 1);
      }}>
      +
      </button>
    </div>
    </>
  )
}

export default App
// 개선된 코드: 
import './App.css'
import { useState } from 'react';

function Bulb() { 
  const [light, setLight] = useState("OFF");
  console.log(light); 
  return(
    <div>
    {light === "ON" ? 
      <h1 style={{backgroundColor: 'orange'}}>ON</h1> : 
      <h1 style={{backgroundColor: "grey"}}>OFF</h1>
    }

    <button onClick = {() => {
      setLight(light === "ON" ? "OFF" : "ON");
    }}>

    {light === "ON" ? "스위치 끄기" : "스위치 켜기"}
  </button>
  </div>
  ) 
}

function Count() {
  const [count, setCount] = useState(0);
  return(
    <div>
    <h1>{count}</h1>
    <button onClick={()=>{
      setCount(count + 1);
    }}>
    +
    </button>
  </div>
  )
}

function App() {
  return (
    <>
    <Bulb />
    <Count />
    </>
  )
}

export default App
  • 추가로, 각각 BulbCount 함수를 별도의 jsx 파일로 생성해서 export하고, App.jsx 파일에서 import해서 사용할 수 있음

State로 사용자 입력 관리하기

  • 간단한 회원가입 폼 렌더링하는 컴포넌트 생성하기
// initial code
import {useState} from "react";

function Register() {
    const [name, setName] = useState() // 초기값 설정: useState()
    const [birth, setBirth] = useState()
    const [country, setCountry] = useState()
    const [bio, setBio] = useState()

    const onChangeName = (e) => {
        setName(e.target.value)
    }
    const onChangeBirth = (e) => {
        setBirth(e.target.value)
    }
    const onChangeCountry = (e) => {
        setCountry(e.target.value)
    }
    const onChangeBio = (e) => {
        setBio(e.target.value)
    }

    return ( // 이름, 생일, 국적, 
        <>
        <h1>Register</h1>
        <div>
        <div> 
            <input  
                placeholder="이름"
                onChange = {onChangeName}
                value = {name}
            />
        </div>
        <div>
            <input
                type="date"
                onChange = {onChangeBirth}
                value={birth}
            />
        </div>
        <div>
            <select value = {country} onChange = {onChangeCountry}>
                <option value="">선택없음</option>
                <option value="us">미국</option>
                <option value="ch">중국</option>
                <option value="ge">독일</option>
                <option value="kr">한국</option>
            </select>
            {country}
        </div>
        <div>
            <textarea value={bio} onChange={onChangeBio}/>
            {bio}
        </div>
        </div>
        </>
    )
}

export default Register;
  • 저장 객체 [input, setInput]을 사용해서 하나로 통합
// updated compact code
import {useState} from "react";

function Register() {
    const [input, setInput] = useState({ 
        name : "",
        birth : "",
        country : "",
        bio : ""
    });
  // input을 간단한 객체 형태로 변경함
  // input.name, input.birth, ... 로 불러와야 함
    console.log(input)

    const onChangeName = (e) => {
        setInput({
            ...input, 
          // 해당 함수와 관련없는 값들인 birth, country, bio는 그대로 유지되도록 spread 형태로 작성
            name: e.target.value // name만 수정
        })
    }
    const onChangeBirth = (e) => {
        setInput({
            ...input, 
            birth: e.target.value
        })
    }
    const onChangeCountry = (e) => {
        setInput({
            ...input, 
            country: e.target.value
        })    }
    const onChangeBio = (e) => {
        setInput({
            ...input, 
            bio: e.target.value
        })    }

    return ( 
        <>
        <h1>Register</h1>
        <div>
        <div> 
            <input  
                placeholder="이름"
                onChange = {onChangeName}
                value = {input.name}
            />
        </div>
        <div>
            <input
                type="date"
                onChange = {onChangeBirth}
                value={input.birth}
            />
        </div>
        <div>
            <select value = {input.country} onChange = {onChangeCountry}>
                <option value="">선택없음</option>
                <option value="us">미국</option>
                <option value="ch">중국</option>
                <option value="ge">독일</option>
                <option value="kr">한국</option>
            </select>
        </div>
        <div>
            <textarea value={input.bio} onChange={onChangeBio}/>
        </div>
        </div>
        </>
    )
}

export default Register;
  • 이벤트 핸들러를 onChange 함수 하나로 통합
import {useState} from "react";

function Register() {
    const [input, setInput] = useState({
        name : "",
        birth : "",
        country : "",
        bio : ""
    });
    console.log(input)

    const onChange = (e) => {
        setInput({
            ...input, 
            [e.target.name]: e.target.value,
          // 변수 e.target.name을 []로 감싸서 객체 생성 시 key로 사용!
          // e.target은 return문의 각 html 자식태그들(input, input, select, textarea)이고, 
          // .name은 각 태그 내에 name=""로 설정해준 정보
        });
    };

   

    return ( 
        <>
        <h1>Register</h1>
        <div>
        <div> 
            <input 
                name = "name" 
                placeholder="이름"
                onChange = {onChange}
                value = {input.name}
            />
        </div>
        <div>
            <input
                name="birth"
                type="date"
                onChange = {onChange}
                value={input.birth}
            />
        </div>
        <div>
            <select name="country" value = {input.country} onChange = {onChange}>
                <option value="">선택없음</option>
                <option value="us">미국</option>
                <option value="ch">중국</option>
                <option value="ge">독일</option>
                <option value="kr">한국</option>
            </select>
        </div>
        <div>
            <textarea name="bio" value={input.bio} onChange={onChange}/>
        </div>
        </div>
        </>
    )
}

export default Register;

useRef: 컴포넌트 변수 생성하기

  • useRef: 컴포넌트 내부에서 새로운 Reference 객체를 생성하는 기능
    컴포넌트 내부의 변수로 활용 가능
    ✨ useState와는 달리 어떤 경우에도 컴포넌트를 리렌더링하지 않음 (useState는 값이 변경될 때마다 컴포넌트가 리렌더링됨)
import {useState, useRef} from "react";

function Register() {
    const [input, setInput] = useState({
        name : "",
        birth : "",
        country : "",
        bio : ""
    });
    const countRef = useRef(0); // 최선의 방법!
    // 그냥 let count = 0; 으로 선언해서 사용하면 안되는 이유? 
    // 컴포넌트가 리랜더링 될때마다 초기화되므로! 

    const inputRef = useRef();
    // console.log(countRef.current);

    console.log(input)

    const onChange = (e) => {
        countRef.current ++;
        console.log(countRef.current);
        setInput({
            ...input, 
            [e.target.name]: e.target.value,
        });
    };

   const onSubmit = () => {
    if (input.name === ""){
        // 이름을 입력하는 DOM 요소 포커스
        inputRef.current.focus(); 
    }
   }

    return ( 
        <>
        <h1>Register</h1>
        <div>
        <div> 
            <input 
                ref = {inputRef}
                name = "name" 
                placeholder="이름"
                onChange = {onChange}
                value = {input.name}
            />
        </div>
        <div>
            <input
                name="birth"
                type="date"
                onChange = {onChange}
                value={input.birth}
            />
        </div>
        <div>
            <select name="country" value = {input.country} onChange = {onChange}>
                <option value="">선택없음</option>
                <option value="us">미국</option>
                <option value="ch">중국</option>
                <option value="ge">독일</option>
                <option value="kr">한국</option>
            </select>
        </div>
        <div>
            <textarea name="bio" value={input.bio} onChange={onChange}/>
        </div>
        <button onClick={onSubmit}>
            제출
        </button>
        </div>
        </>
    )
}

export default Register;

React Hooks

  • class 컴포넌트에서만 이용할 수 있는 react의 특수한 기능들을 함수 컴포넌트에서도 사용할 수 있도록 돕는 메소드
  • useState는 State 기능을 낚아채오는 Hook이고, useRef는 Reference 기능을 낚아채오는 Hook임
  • React Hooks의 메소드들은 앞에 use를 붙임

✨ React Hooks 규칙

  1. 함수 컴포넌트, 커스텀 훅 내부에서만 호출이 가능
    일반함수도 불가 (반드시 컴포넌트여야 함)
  2. 조건부로 호출될 수는 없음
    조건문, 반복문에서는 호출 불가
  3. 나만의 Hook 생성도 가능
    (1) 함수 앞에 use 붙이기
    (2) 관례상, src 폴더에서 새 폴더로 hooks 폴더 생성 > hooks 폴더 내부에 새 파일로 use 붙인 함수 옮기고 export, import문 작성
import { useState } from "react";

function useInput() {
    const [input, setInput] = useState(""); 
    const onChange = (e) => {
        setInput(e.target.value)
    }
    return [input, onChange]
}

function HookExam() {
    const [input, onChange] = useInput();

    return (
        <div>
            <input value = {input} onChange={onChange} />
        </div>
    )
}

export default HookExam;

라이프사이클(LifeCycle)

React Component의 LifeCycle이란?

  1. Mount
    컴포넌트가 처음 탄생해서 화면에 최초로 렌더링되는 순간
  2. Update
    컴포넌트가 리렌더링되는 순간들
  3. UnMount
    컴포넌트가 화면에서 사라지는 순간, 렌더링에서 제외되는 순간

LifeCycle 제어 ➡️ useEffect를 사용!

  • LifeCycle 제어: Mount 단계에서 서버에서 데이터를 불러오거나 Update 단계에서 변경된 값을 콘솔에 출력하고, UnMount 단계에서 컴포넌트가 사용하던 메모리를 정리하는 등의 동작들을 수행하는 것

  • useEffect란? 리엑트 컴포넌트의 side effect를 제어하는 새로운 react Hook

  • useEffect(()=>{}, [])에서 []를 deps(dependency array)라고 부름

// App.jsx만 수정
import './App.css'
import Viewer from './components/Viewer'
import Controller from './components/Controller'
import {useState, useEffect } from "react"

function App() {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState("");

  useEffect(() => {
    console.log(`count: ${count} / input: ${input}`); // count의 값이 바뀔 때마다 콘솔에 출력
  }, [count, input]);
  // []로 전달한 배열의 값이 바뀌게 될 때마다 
  // sideEffect로서 ()=>{}로 전달한 함수의 동작을 실행함
  // 만약 input의 값이 바뀔때마다도 useEffect함수를 실행하고 싶다면? []안에 input도 추가!

  const onClickButton = (value) => {
    setCount(count + value) // value는 버튼의 -1, +10 등의 값들!
  }

  return (
    <div className = "App">
      <h1>Simple Counter</h1>
      <section>
        <input value={input} onChange={(e)=>{
          setInput(e.target.value);
        }} />
      </section>
      <section>
        <Viewer count = {count}/>
      </section>
      <section>
        <Controller onClickButton = {onClickButton}/>
      </section>
    </div>
  );
}

export default App

  • LifeCycle 구현하기
import './App.css'
import Viewer from './components/Viewer'
import Controller from './components/Controller'
import {useState, useEffect, useRef } from "react"
// useRef: 브라우저가 불려온건지 아닌지 체크
import Even from './components/Even'

function App() {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState("");
  const isMount = useRef(false);


  // 1. Mount
  useEffect(()=>{
    console.log("Mount")
  }, []) 
  // []가 빈 배열인 경우, 오로지 처음 랜더링 될 때만 실행됨 (=Mount)
  // 2. Update
  // useEffect(()=>{
  //   console.log("Update")
  // })
  // []를 생략하는 경우, Mount 될 때 + Update 될 때마다 실행됨
  // 예: 버튼을 누를 때마다, input 태그 안의 text를 수정할 때마다 Update가 출력될 것

  // 만약 Update 될 때마다'만' 실행하고 싶다면?? 
  useEffect(()=>{
    if (!isMount.current){
      isMount.current = true;
      return;
    }
    console.log("Update")
  })
  // 3. UnMount


  const onClickButton = (value) => {
    setCount(count + value) 
  }

  return (
    <div className = "App">
      <h1>Simple Counter</h1>
      <section>
        <input value={input} onChange={(e)=>{
          setInput(e.target.value);
        }} />
      </section>
      <section>
        <Viewer count = {count}/>
        {count % 2 === 0 ? <Even /> : null}
      </section>
      <section>
        <Controller onClickButton = {onClickButton}/>
      </section>
    </div>
  );
}

export default App
import {useEffect} from "react";

function Even() {
    useEffect(()=>{
       return () => {
        console.log("UnMount"); // Even component가 사라질 때마다 출력됨
       };     
    }, []);
    return <div>Even</div>
}

export default Even;

React 개발자 도구 사용하기

0개의 댓글