웹 시스템 개발 #React State(1)

학교 공부를 복습할 겸 적는 것이기에 내용이 부족할 수 있습니다.

 

부족한 것은 상관 없으나, 잘못된 부분이 발견된다면 지적해주시면 감사하겠습니다.

 

 


Event Handling

이벤트 핸들러는 JavaScript의 이벤트 처리와 유사하지만 구문과 동작에 몇 가지 주요 차이점이 있습니다. 

 

React에서는 camelCase 명명 규칙(예: onClickonSubmit)을 사용하여 요소에 이벤트 핸들러를 연결하며, 컴포넌트에 props로 전달됩니다.

function Button() {
  const handleClick = () =>{
    alert('안녕');
  }

  return <button onClick={handleClick}>이벤트</button>
}

function App() {
  return (
    <Button />
  );
}

 

form의 예시

form 및 해당 요소에 이벤트 핸들러를 추가하는 작업도 비슷하게 수행할 수 있습니다. 

export default function ColorForm() {
  const handleSubmit = (event) => {
    event.preventDefault();
    // Handle form submission logic here
    alert("Form submitted!");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="color title..." required /> 
      <input type="color" required />
      <button type="submit">ADD</button> 
    </form>
  );
}

 


이벤트 핸들러에서 Prop에 액세스하기

React에서 컴포넌트 내부에 정의된 이벤트 핸들러는 컴포넌트의 props에 접근할 수 있습니다. 받은 props을 기반으로 대화형 및 동적 구성 요소를 만들 수 있게 해줍니다.

function AlertButton({ message, children }) {
  return (
    <button onClick={() => alert(message)}> 
      {children}
    </button>
  );
}

 

export default function Toolbar() {
  return (
    <div>
      <AlertButton message="Playing!">Play Movie</AlertButton>
      <AlertButton message="Uploading!">Upload Image</AlertButton>
    </div>
  );
}

 

Function Props

"Function Props"이라고 하는 이벤트 핸들러를 소품으로 전달하는 것은 부모-자식 구성 요소 통신을 위한 React의 강력한 패턴입니다. 이를 통해 상위 구성 요소는 하위 구성 요소에서 실행할 수 있는 동작을 정의할 수 있고, 컨텍스트에 따라 재사용 가능한 구성 요소의 동작을 사용자 정의하는 데 매우 유용합니다.

 

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

function PlayButton({ movieName }) {
  function handlePlayClick() {
    alert(`Playing ${movieName}!`);
  }

  return (
    <Button onClick={handlePlayClick} children={movieName}>
      
    </Button>
  );
}

function UploadButton() {
  return (
    <Button onClick={() => alert('Uploading!')}>
      Upload Image
    </Button>
  );
}

function App() {
  return (
    <div>
    <PlayButton movieName="Kiki's Delivery Service" />
    <UploadButton />
  </div>
  );
}

 

 

Event Propagation

Event Propagation은 DOM 및 React에서 이벤트를 처리하는 데 중요한 개념입니다. 본질적으로 이벤트가 발생하면 DOM 트리의 상위-하위 계층 구조를 통해 전파(또는 버블)될 수 있습니다. 

 

버블링업: 기본 DOM에서와 마찬가지로 React에서도 이벤트는 대상 요소에서 DOM 트리의 조상을 통해 버블링됩니다. 즉, 하위 요소와 상위 요소 모두 동일한 이벤트에 대한 이벤트 핸들러(예: 'onClick')가 있는 경우 하위 요소의 이벤트 핸들러가 먼저 실행되고 상위 요소의 이벤트 핸들러가 이어서 실행됩니다.

전파 중지: 때로는 이벤트가 상위 요소까지 버블링되는 것을 원하지 않을 수도 있습니다. event.stopPropagation()을 사용하여 이벤트 전파(이벤트 버블링)를 중지할 수 있습니다. 

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => { 
      alert('You clicked on the toolbar!');
    }}>
      <button onClick={(e) => {
        e.stopPropagation();
        alert('Playing!');
      }}>Play Movie</button>
      <button onClick={(e) => {
        //e.stopPropagation();
        alert('Uploading!');
      }}>Upload Image</button> 
    </div>
  );
}

 

Play Movie 버튼을 누를 시 Playing 알람만 뜹니다.

Upload Image 버튼을 누를 시 Uploading 알람이 뜬 후 You clicked on the toolbar! 알람이 뜹니다.

 

useState

지역 변수는 React의 렌더링 전반에 걸쳐 유지되지 않습니다. 구성요소가 다시 렌더링될 때마다 'index'는 초기 값으로 재설정됩니다. 또한, index와 같은 일반 지역 변수를 변경해도 구성 요소의 상태가 변경되었으며 다시 렌더링되어야 한다는 사실을 React에 알리지 않습니다. 

export default function Gallery() {
  let index = 0;

  const items = [
    { name: 'Image 1', description: 'Description of Image 1', url: 'url_to_image_1.jpg', alt: 'Alt text for image 1' },
    { name: 'Image 2', description: 'Description of Image 2', url: 'url_to_image_2.jpg', alt: 'Alt text for image 2' },
    { name: 'Image 3', description: 'Description of Image 3', url: 'url_to_image_3.jpg', alt: 'Alt text for image 3' }
  ];

  function handleClick() {
    index = (index + 1) % items.length;
  }

  const item = items[index];

  return (
    <>
      <button onClick={handleClick}>Next</button>
      <h2>{item.name}</h2>
      <img src={item.url} alt={item.alt} />
      <p>{item.description}</p>
    </>
  );
}

 

이러한 문제를 해결하기 위해 React는 함수 구성 요소에 React State를 추가할 수 있는 'useState' Hook를 제공해줍니다.

  • 상태 지속성: 'useState'는 다시 렌더링할 때에도 지속되는 상태 변수를 제공합니다.
  • 재렌더링 트리거: 'useState'에서 제공하는 setter 함수를 통해 상태를 업데이트하면 새 상태로 구성요소가 다시 렌더링됩니다.
import React, { useState } from 'react';

export default function Gallery() {
  const [index, setIndex] = useState(0);

  const items = [
    { name: 'Image 1', description: 'Description of Image 1', url: 'url_to_image_1.jpg', alt: 'Alt text for image 1' },
    { name: 'Image 2', description: 'Description of Image 2', url: 'url_to_image_2.jpg', alt: 'Alt text for image 2' },
    { name: 'Image 3', description: 'Description of Image 3', url: 'url_to_image_3.jpg', alt: 'Alt text for image 3' }
  ];

  function handleClick() {
    setIndex((prevIndex) => (prevIndex + 1) % items.length);
  }

  const item = items[index];

  return (
    <>
      <button onClick={handleClick}>Next</button>
      <h2>{item.name}</h2>
      <img src={item.url} alt={item.alt} />
      <p>{item.description}</p>
    </>
  );
}

 

 

AND(&&) 표현식

AND(&&) 연산자는 일반적으로 JSX에서 구성 요소를 조건부로 렌더링하는 데 사용됩니다. 첫 번째 표현식이 진실이면 React는 두 번째 표현식을 렌더링합니다. 첫 번째 표현식이 거짓이면 React는 두 번째 표현식 렌더링을 건너뜁니다.

{showMore && <p>{sculpture.description}</p>}

 

 

State 추가 개념 

Component의 각 인스턴스는 다른 인스턴스와 격리된 자체 상태를 가지므로, 동일한 구성 요소의 인스턴스가 여러 개 있는 경우에도 각 인스턴스는 해당 상태를 독립적으로 관리됩니다.

State는 비공개이며 구성 요소에 의해 완전히 제어되며, 부모는 props를 전달할 수 있지만 자식의 상태를 직접 수정할 수는 없습니다.

Lifting State Up / 출처 - 아주대학교 웹시스템 수업 자료

 

구성 요소 간에 상태를 공유하려면 상태를 가장 가까운 공통 상위 항목으로 이동한 다음 필요한 하위 항목에 전달하는 경우가 많습니다. 이를 "Lifting State Up"이라고 합니다. 

 

데이터는 상위에서 하위로 단방향으로 흐릅니다. 형제 구성 요소는 서로 직접 통신할 수 없으며 공통의 부모를 통해 의사소통을 해야하며, 형제 간에 데이터를 공유하기 위해 데이터는 공통 상위로 "리프트 업"된 다음 형제에게 props으로 전달됩니다.

상위 구성 요소는 데이터를 props으로 전달하고 콜백 함수를 통해 하위 구성 요소로부터 정보를 다시 받을 수 있습니다. 이 패턴은 React의 단방향 데이터 흐름 원칙을 유지하면서 양방향 통신을 가능하게 합니다.

 

예제코드

'App' 구성요소는 상태를 유지하고 이를 하위 요소에게 전달합니다.

import React, { useState } from 'react';

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

  function incrementCount() {
    setCount(prevCount => prevCount + 1);
  }

  return (
    <div>
      <Counter onIncrement={incrementCount} />
      <Display count={count} />
    </div>
  );
}

 

'Counter' 구성요소는 카운트를 증가시키는 함수 prop을 받습니다.

function Counter({ onIncrement }) {
  return (
    <button onClick={onIncrement}>Increment</button>
  );
}

 

'Display' 구성 요소는 현재 개수를 prop으로 받습니다.

function Display({ count }) {
  return <h2>Count: {count}</h2>;
}

 

  • 격리된 상태: 'App' 구성 요소의 'count' 상태가 격리되어 있습니다. 
  • 비공개 상태: App의 상태는 App에 비공개입니다. 'Counter' 및 'Display' 구성요소는 이를 직접 수정할 수 없습니다. props을 통해서만 상호작용할 수 있습니다.
  • state 상승: 상태(count)가 Counter 및 Display의 가장 가까운 공통 조상인 App 구성 요소로 상승됩니다.
  • 렌더링 및 커밋: 상태(count)가 변경되면React는 App 구성 요소와 그 하위 항목을 다시 렌더링합니다.
  • 상위-하위 통신: App은 onIncrement prop(함수 prop)을 통해 Counter와 통신합니다. 'Counter'가 이 함수를 호출하면 'App'(부모)과 다시 통신합니다.

 

UI 요청 및 제공 과정

UI 요청 및 제공 과정은 식당에서 손님의 주문을 받고 서빙하는 과정에 비유할 수 있습니다. 

1. 렌더링 트리거하기 (손님의 주문을 주방에 전달하기)

  • 초기 렌더링: 애플리케이션이 시작될 때 초기 렌더링이 트리거됩니다.
  • createRoot: index.js에서 대상 DOM 노드를 가지고 createRoot를 생성하고 render 메소드를 호출하여 초기 렌더링을 수행합니다.

2. 컴포넌트 렌더링하기 (주방에서 주문 준비하기)

  • 컴포넌트 렌더링: React는 현재 상태와 props에 기반하여 UI가 어떻게 보여야 할지 결정합니다. 이 과정은 주방에서 주문을 준비하는 것과 같습니다.

3. DOM에 커밋하기 (테이블에 주문 서빙하기)

  • DOM 업데이트: React는 변경사항을 실제 DOM에 적용하여 최신 상태의 UI를 화면에 표시합니다. 이것은 식당에서 준비된 음식을 테이블에 서빙하는 것과 같습니다.

4. 렌더 트리거하기

  • 상태 업데이트 시 재렌더링: 상태를 업데이트하는 set 함수를 호출함으로써 렌더링을 트리거할 수 있습니다.
  • 상태 업데이트: 상태를 업데이트하면 React는 자동으로 컴포넌트를 다시 렌더링하도록 큐에 넣습니다.