React 1

j0yy00n0·2025년 5월 7일
post-thumbnail

2025.04.07 ~ 04.09

React

React 의 특징

Virtual DOM

  • 실제 DOM(Document Object Model)을 추상화한 JavaScript 객체
  • 변경 사항이 발생하면 Virtual DOM에서 먼저 변경을 처리한 뒤, 실제 DOM과 비교(diffing)하여 필요한 부분만 효율적으로 갱신 (reconciliation)
  • 전체 DOM을 새로 렌더링하지 않고 변경된 부분만 업데이트함으로써 성능 최적화
  • 동적인 화면 구성 시 전체 페이지를 다시 그리지 않고 필요한 컴포넌트만 갱신 가능

Component 기반 구조

  • UI를 작은 단위의 컴포넌트로 나누어 개발
  • 컴포넌트는 자체 상태(State)와 부모로부터 전달받는 속성(Props)을 활용해 동작
  • 코드의 재사용성, 유지보수성, 테스트 용이성을 높일 수 있음
  • 하나의 컴포넌트를 다양한 곳에 재활용 가능

유연성 및 확장성

  • 크로스 플랫폼 개발에 대한 지원이 가능
  • 다양한 라이브러리 및 프레임워크와 쉽게 통합 가능
  • React Native 를 활용한 모바일 앱 개발 가능

SSR vs CSR

Server Side Rendering

  • Thymeleaf/JSP 를 사용하게 된다면 서버에서 HTML View 파일을 반환 하여 사용자 화면에 노출하게 된다.
  • Controller 계층은 ViewResolver 를 통해 어떠한 화면을 노출 시킬 지 결정하게 된다.
    - Backend Server 는 요청에 대한 데이터 응답 의무 + View 에 대한 결정권을 가지게 되어 단일 책임의 원칙을 벗어나게 될 수 있다.
  • 새로운 정보 , 업데이트가 발생하게 된다면 DOM Tree 변경 된 부분 뿐만 아니라 전체를 다시 받아서 새로 렌더링해야 한다.

Client Side Rendering

  • 초기 요청 시 최소한의 HTML + JavaScript 번들만 서버로부터 전달받고, 이후 JavaScript가 동적으로 View를 구성
  • 사용자 이벤트가 발생하면 JavaScript가 REST API를 호출하여 데이터를 받아옴 (Json or XML)
  • Backend Server는 단순히 데이터(API, RestAPI)만 응답하고, 화면 구성(View)은 Client Server가 전담
  • 덕분에 서버는 비즈니스 로직 및 데이터 응답에 집중할 수 있어 역할 분리가 명확해짐

React 의 주요 요소

엘리먼트(Element)

요소 노드(<>부분) , 어트리뷰트 노드(속성) , 텍스트 노드로 만들어지는 가상 DOM 개념

  • 컴포넌트의 실행 결과물
const element = <h1 align=”center”>엘리먼트!!</h1>

컴포넌트(Component)

엘리먼트를 반환하는 개념을 가진 화면의 구성 단위

  • 함수형 컴포넌트와 클래스형 컴포넌트가 존재
function App() {
	return (
			<h1>
				엘리먼트!!!!!!!!!!
			</h1>
		);
}

React 의 동작 원리

일반적인 웹 브라우저의 렌더링 동작 과정

HTML Parsing → CSS Parsing → Render → Layout → Paint

  • HTML 코드를 읽고 DOM(Document Object Model) 트리를 구성
  • CSS 파일을 읽고 CSSOM(CSS Object Model)을 생성
  • DOM과 CSSOM을 조합해 화면에 표시될 요소만 포함된 Render Tree를 만든다.
  • Layout은 Render Tree를 기반으로 각 요소의 크기와 위치를 계산
  • 출력한다.

React 의 동작 원리

Virtual DOM 사용 시 흐름

  • JSX로 작성된 컴포넌트를 바탕으로 Virtual DOM(가상 DOM)을 생성
  • 상태(state)나 props가 변경되면 React는 새로운 Virtual DOM을 생성
  • 이전 Virtual DOM과 비교(diffing)하여 변경된 부분을 찾아냄
  • 변경된 부분만 실제 DOM에 batch 처리(patch) 하여 반영
    -> Reconciliation 과정

일반적인 브라우저 렌더링 과의 차이

  • 가상 돔을 활용하지 않고 DOM의 일부분을 수정 : 전체 DOM Tree 및 CSSOM Tree를 처음부터 다시 모두 새로 재생성 하게 되어 속도가 매우 느려진다
  • 가상 돔을 활용 : 기존의 DOM과 차이나는 부분만 인지하고 해당 DOM Tree 하위 요소들만 수정하고 DOM Tree 및 CSSOM Tree를 재생성 하는 과정을 진행하지 않는다
  • 모든 차이 나는 부분이 한번에 적용되어 Layout 및 Paint 과정이 한 번만 진행
  • 가상 돔을 활용하는 React를 적용하면 동적인 변화가 발생하는 부분만 빠르게 적용하여 속도가 향상

React

React를 사용할 때 앱으로 만드는 방식과, CDN 링크를 이용하는 방식이 있다.
먼저 CDN 링크 방식으로 사용한다.

  • React, ReactDOM 모두 CDN을 통해 사용할 수 있다.
<!-- 개발용으로 적합, 배포용 버전에는 적합하지 않음 -->
<!-- <head> 안에 작성한다.-->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  • vscode에서 Live Server확장을 사용하여 브라우저에서 바로 결과를 확인할 수 있다.

Intro

HTML 파일 안에 < div id="root">< /div>와 같은 엘리먼트를 두고, < script> 안에 JavaScript 코드를 작성하여 ReactDOM을 사용해 렌더링

React.createElement

h1 태그와 같은 HTML 요소를 JavaScript 객체로 생성할 수 있다.

  • React.createElement(요소타입, 속성객체, 텍스트);
  • 첫 번째 인자: 만들려는 엘리먼트의 태그 이름을 문자열로 정의(요소)
  • 두 번째 인자: 만드는 엘리먼트의 속성(Attribute)을 표현(속성)
  • 세 번째 인자: 엘리먼트의 자식 노드를 표현(텍스트)
  • 리액트 엘리먼트는 실제 DOM이 아니라 가상 DOM(Virtual DOM)에 해당하는 JavaScript 객체
  • 엘리먼트의 속성(Attribute)은 객체 형태로 전달
  • class 속성은 className으로 표기
<div id="root"></div>
<script>
	const greetingElement = React.createElement('h1', {className: 'greeting', id: 'greeting'}, 'hello world!!!');
    const textElement = React.createElement('h3', {id: 'name'}, '안녕하세요. 홍길동입니다.');
        
    ReactDOM.createRoot(document.getElementById('root')).render([greetingElement, textElement]);
</script>
  • document 객체 : 브라우저, HTML 문서를 표현하는 최상위 객체
  • getElementById(id) : 해당 id를 가진 DOM 요소를 찾아 반환
  • .render(엘리먼트 요소) : 엘리먼트를 화면에 띄운다.
  • .createRoot() : 해당 DOM 요소를 리액트의 렌더링 루트(root) 로 지정

Component

엘리먼트들의 집합

  • 엘리먼트를 반환하는 개념을 가진 화면의 구성 단위

클래스형 컴포넌트

  • React.Component를 상속 받아서 사용
  • 권장되지 않음
class HelloWorld extends React.Component {
    render() {
        return React.createElement(
            'h1',
            {className: 'greeting'},
            'Hello World'
        );
    }
}

함수형 컴포넌트

  • 권장
function HelloWorld() {
    return React.createElement('h1', {className: 'greeting', id : 'test'}, 'Hello World');
}

JSX(Javascript Syntax eXtension)

자바스크립트에서 HTML처럼 보이는 문법을 사용할 수 있도록 만든 확장 문법 표현식

  • React.createElement로 엘리먼트를 정의하는 방식은 번거롭고 가독성이 좋지 않다
  • JavaScript에서 HTML의 markup 방식(<>)처럼 엘리먼트를 생성할 수 있는 확장 문법
  • JSX는 공식적인 JavaScript 문법이 아니기 때문에 Babel이라는 트랜스파일링 툴(번역기)이 필요
  • Babel/standalone으로 접속
  • < head>안에 작성
  • 바벨 적용하려면 바벨 CDN 구문을 추가하고 script 태그에 type 속성에 text/babel 속성값을 추가한다.
<div id="root"></div>

<script type="text/babel">
    const helloworld = <h1>Hello World!</h1>;
    ReactDOM.createRoot(document.getElementById('root')).render(helloworld);
</script>

표현식

JSX 문법 내에 중괄호를 이용하여 모든 javascript 표현식(문법)을 넣을 수 있다

  • html에서 {} 안은 단순 문자열 취급
<div id="root"></div>

<script type="text/babel">
    function formatName(user) {
        return `${user.lastName} ${user.firstName}`;
    }

    const user = {
        firstName: 'Gildong',
        lastName: 'Hong'
    };

    const element = <h1>Hello, {formatName(user)}</h1>;

    ReactDOM.createRoot(document.getElementById('root')).render(element);
</script>

JSX 규칙

동일 레벨의 노드들이 연달아 정의되는 경우 문법적인 에러가 발생

  • 최상위 엘리먼트가 두 개 이상이면 에러가 발생(하나의 돔 트리만 생성해야 한다)
const element = (
    <h1>Hello</h1>
    <h3>phone</h3>
);
  • < div> 태그로 감싸서 하나의 돔 트리를 생성
    - < div> 태그는 화면상 공간을 차지하게 된다.
const element = (
    <div>
        <h1>Hello, {user.name}</h1>
        <h3>phone: {user.phone}</h3>
    </div>
);
  • <React.Fragment>로 감싸서 형식상의 돔 트리상 상위 엘리먼트를 만든다.
    - 임시의 공간에 가상의 부모 역할을 한다.
const element = (
    <React.Fragment>
        <h1>Hello, {user.name}</h1>
        <h3>phone: {user.phone}</h3>
    </React.Fragment>
);
  • React라이브러리로부터 Fragment 객체만 구조분해 할당을 해주면 React.을 생략할 수 있다.
const {Fragment} = React;
<!-- const Fragment = React.Fragment; -->
const element = (
    <Fragment>
        <h1>Hello, {user.name}</h1>
        <h3>phone: {user.phone}</h3>
    </Fragment>
);
  • React.Fragment의 축약 문법인 <>< />로 감싸서 사용할 수 있다
const element = (
    <>
        <h1>Hello, {user.name}</h1>
        <h3>phone: {user.phone}</h3>
    </>
);

엘리먼트 정의 시 attribute 정의

class라는 속성은 className이라는 속성으로 사용

  • class는 자바스크립트에서 클래스형 컴포넌트의 예약어와 중복
  • 이벤트 관련 속성은 낙타봉 표기법
  1. 문자열로 속성값을 정의하는 방식
  • 문자열 방식은 이벤트 관련 어트리뷰트는 적용되지 않는다.
const element = <h1 id="title" className="highlight" onClick="test">Hello World!</h1>;
  1. JavaScript 표현식({})을 이용하여 정의하는 방식
const id = "title";
const className = "highlight";

const element = <h1 id={id} className={className} onClick={test}>Hello World!</h1>;

ReactDOM.createRoot(document.getElementById('root')).render(element);

function test(){
    console.log('클릭했네');
}

엘리먼트 스타일 적용

<script type="text/babel">

// 속성 값은 문자열 혹은 숫자 형태로 작성해야 한다.(객체 X) 
const style = {
    backgroundColor: 'black',
    color: 'white',
    cursor: 'pointer',
    textAlign: 'center',
    padding: 20             
    // 단위를 작성하려면 문자열로 사용하지만 단위를 생략하면 숫자만 사용 가능(생략 시 px를 붙인다.) 
}

const element = (
    <>
        <h1 style={style}>Hello World!</h1>
        <h3>inline styling test</h3>
    </>
);

    ReactDOM.createRoot(document.getElementById('root')).render(element);
</script>

엘리먼트 이벤트 적용

  1. 인라인으로 간단한 이벤트 적용 시 JSX의 자바스크립트 표현식 내에 이벤트 핸들러 함수(이벤트 처리 함수) 작성
const element = (
    <>
        <h1>Event Attribute</h1>
        <button onClick={() => alert('Hello World!')}>클릭하세요</button>
    </>
  );
  1. 이벤트 동작 시 사용할 이벤트 핸들러 사전 정의 후 JSX 내에 표현식으로 사용
const onClickHandler = () => {
    alert('Hello World!');
};

const element = (
    <>
        <h1>Event Attribute</h1>
        <button onClick={onClickHandler}>클릭하세요</button>
    </>
);     

  ReactDOM.createRoot(document.getElementById('root')).render(element);

JSX 주석 작성법

{} 안에 JavaScript 표현식처럼 주석을 작성

const element = (
    <>
        <h1>Comment in JSX</h1>
        {/* JSX 내에서의 주석은 이렇게 작성합니다. */}
        {
            // JSX 내의 한줄주석은 표현식을 한 줄이 아닌 여러 줄로 작성하면 적용할 수 있다.
        }
        <h3 
            className="text" // 시작 태그에도 주석을 작성할 수 있다.               
        >
            JSX 내의 주석 사용하는 방법
            {
                // 한 줄 주석
            }
        </h3>   {/* 닫기 태그는 태그 내부에 주석을 일반적으로 하지 않는다. */}
        /* 이런 주석이나 */
        // 이런 주석은 그대로 화면에 나타나게 된다.
    </>
);

Rendering

< div id="root"> 요소 내부에 포함되는 모든 React 엘리먼트는 ReactDOM이 관리하므로, 이 요소를 루트(root) DOM, 루트 노드(Node)라고 부른다

  • 일반적으로 React로 구현된 애플리케이션은 하나의 루트 DOM을 가진다.
  • ReactDOM.createRoot(루트 DOM 노드).render(엘리먼트) 형태로 JSX 또는 컴포넌트를 해당 루트에 렌더링
  • ReactDOM.createRoot(document.getElementById('root')).render(element);
  • < div id="root">< /div>는 처음에 아무 내용도 없는 빈 요소지만 ReactDOM.createRoot(...).render(element)를 실행하면 element에 담긴 내용이 div 내부에 삽입된다.
  • Render단계는 사용자 화면에 도출될 엘리먼트들을 정의한다.

Element Update

React 엘리먼트는 불변객체(immutable)

  • 엘리먼트를 생성한 이후에는 해당 엘리먼트의 자식이나 속성을 변경할 수 없다

  • 엘리먼트를 업데이트 하기 위해서는 완전히 새로운 엘리먼트를 만들고 ReactDOM.render로 다시 렌더링을 하는 방식을 사용

  • Virtual DOM은 메모리에 있는 가상의 트리, 실제 DOM 구조와 동일한 형태로 관리

  • React에서 UI가 바뀌면,

    • 새로운 리액트 엘리먼트(새로운 Virtual DOM 트리)를 생성
    • 기존 Virtual DOM과 비교(diff)해서 무엇이 바뀌었는지 판단
    • 바뀐 부분만 실제 DOM에 반영(patch)
    • 비교한 결과 변화가 있는 부분만 변경
  • DOM 전체를 다시 렌더링하지 않고도, 마치 UI가 동적으로 수정되는 것처럼 효율적으로 동작

  • 리액트의 컨셉은 변화가 있는 엘리먼트를 찾아서 어떻게 바꿀 것인지를 고민하는 것이 아닌 기존 가상돔의 엘리먼트를 지우고 새롭게 엘리먼트를 생성하는 방식으로 고안

조건부 렌더링

여러 개의 엘리먼트 중 특정 조건에 따라 하나의 엘리먼트만 렌더링

  • 렌더링 시 조건 비교 후 조건부 렌더링하기(최대한 조건식이 심플할 때)
<div id="root"></div>

<script type="text/babel">

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

    const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));

    const positiveElement = <h1>앞으로 점점 더 재미있어 질 거예요^^</h1>;
    const negativeElement = <h1>천천히 앞의 내용을 복습해 보세요^^</h1>;

    root.render(
        (answer === 1)? positiveElement: negativeElement
    );
</script>
  • JSX 내에서 조건부로 엘리먼트 생성하기
    1. if문으로 조건부 엘리먼트 생성
<div id="root"></div>

<script type="text/babel">

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

    const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));

    let element;
    if(answer === 1) {
        element = <h1>앞으로 점점 더 재미있어 질 거예요^^</h1>;
    } else {
        element = <h1>천천히 앞의 내용을 복습해 보세요^^</h1>;
    }
</script>
    1. 삼항연산자로 조건부 엘리먼트 생성하기(const로 할 수 있다는 것과 삼항연산자를 쓰는 구문을 더 많이 사용)(권장)
<div id="root"></div>

<script type="text/babel">

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

    const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));

    const element = (answer === 1)? (
        <h1>앞으로 점점 더 재미있어 질 거예요^^</h1>
    ): (
        <h1>천천히 앞의 내용을 복습해 보세요^^</h1>
    );
</script>
    1. && 연산자를 이용한 조건부 엘리먼트 생성하기(조건이 맞을 때만 엘리먼트가 보이게 할 경우)
<div id="root"></div>

<script type="text/babel">

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

    const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));

    const element = answer === 1 && <h1>앞으로 점점 더 재미있어 질 거예요^^</h1>
    console.log(element);
</script>
    1. 주의 사항
    • false 조건을 가지고 렌더링을 하는 경우 조건에 일치하지 않으면 렌더링 되는 요소가 없지만 0은 Falsy한 값임에도 조건부 엘리먼트 생성을 하게 되면 0을 반환
<div id="root"></div>

<script type="text/babel">

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

    const answer = parseInt(prompt('리액트가 재미있으신가요?\n1. 재미있다.\n2. 어렵다'));

    const number = 0;
    console.log(!!number);     // false
    const element = number && <h1>truthy일 때 나오는 태그</h1>;

    root.render(element);       // falsy 값이지만 element로 취급 됨
</script>

Component

Class형 Component

state 사용 및 라이프 사이클 기능, 임의의 메소드 정의 등은 클래스 컴포넌트에서만 사용 가능한 방식

<div id="root"></div>

<script type="text/babel">

    class Title extends React.Component {
        render() {
            return (
                <h1>Class Componenet</h1>
            );
        }
    }

    // 클래스형 컴포넌트를 만들게 되면 사용자 정의 컴포넌트를 엘리먼트 형태로 사용할 수 있다.(사용자 정의 태그처럼)
    ReactDOM.createRoot(document.getElementById('root')).render(<Title/>);  
    /* 1. 앞글자 대문자, 2. 닫기 태그 존재, 3. 클래스나 함수 이름과 동일해야 함 */
</script>
<!-- JSX 문법을 쓰지 않을 때는 React.createElement()를 직접 사용해야 한다 -->
ReactDOM.createRoot(document.getElementById('root')).render(React.createElement(Title));

Function Component

함수형 컴포넌트는 ReactElement를 반환하는 함수

  • 사용자 정의 엘리먼트를 이용할 시 JSX 태그 형태로 사용

  • 함수 이름의 첫 글자는 반드시 대문자

    • 소문자로 시작하면 React는 이를 HTML 태그로 오해
    • 존재하지 않는 HTML 태그로 렌더링하려 하므로 오류 혹은 무시
  • 함수형 컴포넌트의 단점 : state와 라이프사이클 API 사용이 불가능

    • 함수형 컴포넌트의 기능적 한계로 훅스(hooks)를 제공
<div id="root"></div>

<script type="text/babel">

    function Title() {
        return (
            <h1>Function Component</h1>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<Title/>);
</script>

Component 합성

여러 개의 작은 컴포넌트를 조합(합성)하여 하나의 큰 컴포넌트를 만드는 방식

  • 컴포넌트는 재사용 가능한 크기로 작게 유지해야 하며, 순수 함수처럼 작성
<div id="root"></div>

<script type="text/babel">

    let name = '홍길동';
    let age = 20;
    let phone = '010-1234-5678';
    let email = 'hong123@gmail.com';
        
    const user = {name, age, phone, email};     // 프로퍼티 단축 구문

    function NameCard() {
        return <h1>{user.name}</h1>;
    }

    function AgeCard() {
        return <h2 style={{color: 'orangered'}}>{user.age}</h2>;
    }

    function PhoneCard() {
        return <h3 style={{color: 'red'}}>{user.phone}</h3>;
    }

    function EmailCard() {
        return <h3 style={{backgroundColor: 'yellow'}}>{user.email}</h3>;
    }
    
    // 상위 컴포넌트 : 여러 하위 컴포넌트를 조합하여 구성 
    function UserInfo() {
        return (
            <div style={{width: 300, border: '1px solid black'}}>
                <NameCard/>
                <AgeCard/>
                <PhoneCard/>
                <EmailCard/>
            </div>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<UserInfo/>);
</script>

Props

properties의 약어
컴포넌트에 외부로부터 전달받은 값(속성), 부모 컴포넌트가 자식 컴포넌트에 값을 전달할 때 사용하는 읽기 전용 객체

  • 부모 컴포넌트가 없이도 props 재료를 컴포넌트에 줄 수 있다
  • 읽기 전용 객체이기 때문에 수정해서는 안된다
  • 모든 React 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야 한다
- 순수 함수 : 동일한 입력값에 대해 동일한 결과를 반환하는 것
  • 컴포넌트 간 데이터 이동

컴포넌트에 데이터를 직접 전달하여 내부적으로 표현할 수 있다.

<div id="root"></div>

<script type="text/babel">

    const name1 = '홍길동';
    const name2 = '유관순';

    /* 1. 컴포넌트에 props를 전체 객체로 받아서 내부에서 접근하는 방법 */
    function Title({props}){
        return <h1>안녕하세요 {props.name}님 반갑습니다.</h1>;
    }

    /* 2. 구조 분해 할당 방법 */
      const name = props.name;
      const {name} = props; -->
    function Title({name}){
        return <h1>안녕하세요 {name}님 반갑습니다.</h1>;
    }

    /* props.name이 존재하지 않는 경우 기본값 설정 */
    Title.defaultProps = {
        name: '기본이름'
    }


    ReactDOM.createRoot(document.getElementById('root')).render(
        [
            <Title name={name1}/>,
            <Title name={name2}/>,
            <Title name='이순신'/>,
            <Title/>                
        ]
    );
</script>

Children props(자식노드)

자식 노드를 전달하는 props.children

  • 리액트 컴포넌트를 사용할 때 태그 사이의 내용을 props의 children 속성에서 관리
  • 컴포넌트를 사용할 때 태그 사이에 들어가는 내용 전체를 참조
  • 텍스트 노드는 React에서 기본적으로 children이라는 prop을 통해 전달
<div id="root"></div>

<script type="text/babel">
    function ChildNodePrinter(props) {

        return (
            <>
                <h1>자식 노드가 가지고 있는 값은?</h1>
                <h3>children:<font style={{color: 'orangered'}}>{props.children}</font></h3>
            </>
        )
    }

    ReactDOM.createRoot(document.getElementById('root')).render(
        [
            <ChildNodePrinter>텍스트 노드</ChildNodePrinter>,
            <ChildNodePrinter><div>텍스트 노드</div></ChildNodePrinter>,
            <ChildNodePrinter><div>1</div><div>2</div><div>3</div></ChildNodePrinter>
        ]
    );
</script>

구조 분해 할당

  1. 전달 받은 props 인자를 내부에서 구조 분해 할당을 이용해 사용하는 방법
<div id="root"></div>

<script type="text/babel">

	function PropsPrinter(props){

        /* props 객체에서 name과 children이라는 key를 가진 프로퍼티 값을 꺼내 동일한 이름의 변수에 할당한다.(객체 구조 분해 할당) */
        const {name, children} = props;
        return (
            <>
                <h1>제 이름은 {name}입니다.</h1>
                <h3>제가 가지고 있는 children은 {children}입니다.</h3>
            </>
        );
    }
        
	ReactDOM.createRoot(document.getElementById('root')).render(<PropsPrinter name="홍길동">텍스트노드</PropsPrinter>);
</script>
  1. 매개변수에서 구조 분해 할당
<div id="root"></div>

<script type="text/babel">

	function PropsPrinter({name, children}){
        return (
            <>
                <h1>제 이름은 {name}입니다.</h1>
                <h3>제가 가지고 있는 children은 {children}입니다.</h3>
            </>
        );
    }
        
	ReactDOM.createRoot(document.getElementById('root')).render(<PropsPrinter name="홍길동">텍스트노드</PropsPrinter>);
</script>

PropsVerify(Props 검증)

React 컴포넌트에 전달되는 props의 자료형(Type)과 존재 여부(Required)를 검사(검증)해주는 도구

  • 문서화 주석 처럼, 전달되고 있는 props 대한 설명을 텍스트로 기입할 수 있다
  • 규칙을 지키지 않아도 에러가 아니라 콘솔 경고(WARNING)만 출력(컴파일 오류X)
  • 필수적인 설정은 아니지만 큰 규모의 프로젝트를 진행한다거나 다른 개발자와 협업한다면, 해당 컴포넌트에 어떤 props가 필요한지 쉽게 알 수 있게 해서 개발 능률이 좋아질 수 있다
  • https://github.com/facebook/prop-types
<div id="root"></div>

<script type="text/babel">

	// attribute형 값 두 개와 child 노드 
    function PropsVerify({name, favoriteNumber, children}){
        return (
            <>
                <h1>제 이름은 {name}입니다.</h1>
                <h2>제가 가장 좋아하는 숫자는 {favoriteNumber}입니다.</h2>
                <h3>제가 가지고 있는 children은 {children}입니다.</h3>
            </>
        );
    }

        
    PropsVerify.propTypes = {
    	// name props의 속성은 string 형이어야 한다. 
        name: PropTypes.string,
        // favoriteNumber prop의 속성은 number 형이면서 반드시 주어져야 한다.
        favoriteNumber: PropTypes.number.isRequired
    };

    ReactDOM.createRoot(document.getElementById('root')).render(
        [
            <PropsVerify name="홍길동" favoriteNumber={5}>텍스트노드</PropsVerify>,
            // 콘솔 경고 발생 (name은 string인데 number 전달) 
            <PropsVerify name={3}>텍스트노드</PropsVerify>
        ]
    );
</script>

State

컴포넌트 내부에서 바꿀 수 있는(바뀔 수 있는) 값

  • 컴포넌트가 자체적으로 관리하는 동적인 데이터

  • props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하고 주는 읽기 전용 값

  • 리액트에서 클래스형 컴포넌트는 state를 내부에서 직접 선언하고 사용할 수 있음

  • 함수형 컴포넌트에서는 기본적으로 직접 state를 사용하는 것이 불가능

    	- useState라는 Hook을 통해 state 관리 가능
  • 클래스형 컴포넌트에서 state를 다루는 방법

<div id="root"></div>

<script type="text/babel">

	// 클래스형 컴포넌트를 만들 때는 React.Component를 상속 
    class Counter extends React.Component{

        // 컴포넌트를 생성할 때 가장 먼저 호출되는 생성자 함수이다. 
        constructor(props){

            /* super(props)는 부모 클래스(React.Component)의 생성자에 props를 넘겨주는 것 (필수!) */
            super(props);
            /* 컴포넌트 내부에서 관리할 값들을 저장하는 객체
               state는 반드시 this.state 형태로 정의 */
            this.state = {
                // 변경 될 관리할 값의 초기값 설정 
                number: 3
            };
        }

        render() {
        
        	const {number} = this.state;
            
            return (
                <>
                    <h1>{this.state.number}</h1>
                    <button onClick={() => this.setState({number: number - 1})}>- 1</button>
                    <button onClick={() => this.setState({number: number + 1})}>+ 1</button>
                </>
            );
        }
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<Counter/>);
</script>
  • 생성자 안에서 state 설정 시 주의사항
  1. this.state로 선언
  2. 클래스 내부에서 this를 통해 접근
  3. state에 저장되는 값의 형태는 반드시 Object 리터럴 형태로 작성(예: { number: 3 }, 중괄호 형식)
  • state 변경 방법(setState)
- state를 직접 변경하면 안 됨(예: this.state.number = 10 ❌)
- state의 setter 메소드(setState())를 호출하고 관리할 값에 변화를 준 새로운 객체를 인수로 전달
- 반드시 this.setState({...})로 변경
- 변경할 값을 포함한 새로운 객체를 전달
  • setState의 작동 원리
    • setState()를 통해 state에 변화를 주면 자동으로 render()는 재호출(라이프 사이클 관련)
    • render()가 반환한 JSX는 새로운 Virtual DOM이 됨
    • React는 이전 Virtual DOM과 비교(Diff)하여 실제 DOM과 다른 부분만 최소한으로 갱신

prevState

prevState를 활용한 setState 함수의 사용

  • setState()는 기본적으로 비동기 방식으로 상태를 백그라운드에서 업데이트한다.
  • 하나의 이벤트 핸들러 안에서 여러 번 setState()를 호출해도 즉시 누적된 값으로 반영되지 않는다.
  • 이러한 문제를 해결하기 위해, setState()에 객체 대신 함수를 전달하는 방식을 사용한다.
  • 이때 전달하는 콜백 함수의 첫 번째 인자는 이전 상태 값(prevState), 두 번째 인자는 현재 컴포넌트의 props이다.
  • props가 필요 없다면 생략할 수 있다.

constructor(props) 생성자 함수는 생략이 가능

  • 클래스형 컴포넌트에서 생성자 함수(constructor(props))는 생략 가능
  • 생략할 경우, state는 클래스 필드 문법을 사용해 state = { ... }로 정의
  • 이 방식에서는 this.state = {} 대신 간결하게 초기 상태를 설정
  • super(props)도 필요 없다

setState의 비동기성 문제

  • 비동기 방식으로 state를 백그라운드에서 엡데이트 하기 때문에 상태를 변경하는 것이 반영되지 않는다.
  • 메모리에만 임시적으로 변경된 상태를 가지고 해당 함수가 종료되면 마지막 setState() 함수만 변경 내역으로 반영
  • this.state로는 함수 안에서 시간에 흐름에 따른 백그라운드 값을 활용한 누적 개념을 할 수 없다.
  • this.state.number의 값이 즉시 바뀌지 않아서, console에 찍힌 값은 기대와 다를 수 있다
<div id="root"></div>

<script type="text/babel">

	// 클래스형 컴포넌트를 만들 때는 React.Component를 상속 
    class Counter extends React.Component{

        // 클래스의 속성으로 state를 추가하는 것도 가능하다.
        // (이 때 this.은 사용 불가능하다.) 
        state = {
            number: 0
        };

        render() {
        
        	const {number} = this.state;
            
            return (
                <>
                    <h1
                        style={(number < 0)? {color:'red'} : {color: 'blue'}}
                    >
                        Count : {number}
                    </h1>
                    <button onClick={ () => this.setState({number: number - 1})}>- 1</button>
                    <button onClick={ () => {
                                                console.log(`before setState number: ${this.state.number}`);
                                                this.setState({number: number + 1})
                                                console.log(`after setState number: ${this.state.number}`);
                                                this.setState({number: this.state.number + 1})
                                                console.log(`final setState number: ${this.state.number}`);
                            				}}
                    >
                    + 1
                    </button>
            	</>
        	);
    	}
	}
    ReactDOM.createRoot(document.getElementById('root')).render(<Counter/>);
</script>

동기식 누적 업데이트 방식 (prevState 사용)

  • setState에 콜백함수를 넘기고 prevState를 이용
  • prevState를 사용하면, 이전 상태 값을 기반으로 연속적으로 업데이트
  • 실시간으로 변화되는 state 객체를 활용할 수 있고 결과적으로 수정하는 state도 실시간으로 변화된 결과를 반영할 수 있다.(state 변화 누적)
  • 특히 여러 번 setState()를 호출할 때, 각 호출이 정확히 누적된 상태에서 작동
    -마지막 줄처럼 리터럴 객체를 반환할 때는 소괄호로 감싸야 한다.
<div id="root"></div>

<script type="text/babel">

	// 클래스형 컴포넌트를 만들 때는 React.Component를 상속 
    class Counter extends React.Component{

        // 클래스의 속성으로 state를 추가하는 것도 가능하다.
        // (이 때 this.은 사용 불가능하다.) 
        state = {
            number: 0
        };

        render() {
        
        	const {number} = this.state;
            
            return (
                <>
                    <h1
                        style={(number < 0)? {color:'red'} : {color: 'blue'}}
                    >
                        Count : {number}
                    </h1>
                    <button onClick={ () => this.setState({number: number - 1})}>- 1</button>
                    <button onClick={ () => {

                            
                                             	this.setState((prevState, props) => {
                                                    console.log('prevState' , prevState);
                                                    console.log('props' , props);
                                                    return {
                                                        number: prevState.number + 1
                                                    };
                                                });

												// 전달받는 props가 없으면 생략해서 사용해도 된다.
                                                this.setState((prevState, props) => {
                                                    console.log('prevState' , prevState);
                                                    return {
                                                        number: prevState.number + 1
                                                    };
                                                });

                                                this.setState(prevState => {return {number: prevState.number + 1}});
                                                // 화살표 함수에서 중괄호({}) 생략 및 return 구문 생략 시 반환값이 리터럴 객체라면 소괄호(())를 씌워야 리터럴 객체를 반환 
                                                this.setState(prevState => ({number: prevState.number + 1}));  
                                            }}
                            
                    >
                    + 1
                    </button>
            	</>
        	);
    	}
	}
    ReactDOM.createRoot(document.getElementById('root')).render(<Counter/>);
</script>

setState 이후 특정 작업 실행

setState는 비동기적으로 작동하기 때문에, 상태 변경 후 특정 작업을 하고 싶을 때는 두 번째 인자로 콜백 함수를 전달하면 된다

this.setState(updatedState, () => {
  // 상태 업데이트가 완료된 후 실행될 작업
});
<div id="root"></div>

<script type="text/babel">
    class Light extends React.Component {
        state = {
            isOn: false
        };

        render() {

            const {isOn} = this.state;

            const style = {
                width: 200,
                height: 200,
                backgroundColor: isOn? 'green': 'red',
                transition: '2s'
            }

            return (
                <>
                    <div style={style}></div>
                    <button onClick={() => {
                        this.setState(
                        	/* 업데이트할 state 객체 */
                            {isOn: !isOn},
                            /* 상태가 반영된 후 실행할 콜백 함수 */
                            () => console.log(!isOn? '불이 켜졌습니다': '불이 꺼졌습니다')
                        )
                    }}>
                        {isOn? 'ON': 'OFF'}
                    </button>
                </>
            );
        }
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<Light/>);
</script>

useState

함수형 컴포넌트에서 상태(state)를 관리할 수 있게 해주는 React Hook
React객체에서 내부에 존재하는 함수형 속성

  • React.useState() 형태로 호출 가능
  • 보통은 구조 분해 할당으로 const [state, setState] = useState(...) 형태로 사용
  • 초기 상태값을 인자로 전달하면, [현재값, 상태변경함수] 형태의 배열을 반환
  • 이 상태 변경 함수(setState)를 호출하면 컴포넌트가 다시 실행(리렌더링)됨
  • 원래 함수형 컴포넌트는 state를 사용할 수 없었지만, useState와 같은 Hook이 등장하면서 가능해짐
  • React 16.8 이후부터는 클래스형보다 함수형 컴포넌트 + Hooks 사용이 권장됨
<div id="root"></div>

<script type="text/babel">

    const {useState} = React;

    function Say() {
            
        const [message, setMessage] = useState('기본상태');

        /* 여러 개의 상태들을 한 컴포넌트 내에서 사용해도 된다. */
        const [color, setColor] = useState('black');
        const [backgroundColor, setBackgroundColor] = useState('white');

        const onClickEnter = () => setMessage('안녕하세요!');
        const onClickLeave = () => setMessage('안녕히 가세요!');

        return (
            <>
                <h1 style={{color, backgroundColor}}>{message}</h1>
                <div>
                    <button onClick={onClickEnter}>입장</button>
                    <button onClick={onClickLeave}>퇴장</button>
                </div>
                <div>
                    <button onClick={() => setColor('red')}>빨간색</button>
                    <button onClick={() => setColor('purple')}>보라색</button>
                    <button onClick={() => setColor('green')}>초록색</button>
                </div>
                <div>
                    <button onClick={() => setBackgroundColor('white')}>기본 배경</button>
                    <button onClick={() => setBackgroundColor('black')}>반전 배경</button>
                </div>
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<Say/>);
</script>

JSX attribute 내부에서 함수를 정의할 수 있지만 attribute 방식으로 이벤트를 할당하게 되면 코드가 길어진다는 단점이 존재한다.

<button onClick={() => alert('클릭됨!')}>클릭</button>

-> 외부에 핸들러 함수를 정의하고 이벤트가 발생했을 때 해당하는 함수가 참조만 하도록 작성

function handleClick() {
  alert('클릭됨!');
}

<button onClick={handleClick}>클릭</button>

함수의 호출과 참조

  • onClick={handleClick}

    • 소괄호()를 붙이지 않으면 단순히 함수를 참조하겠다는 의미
    • 이벤트가 발생했을 때 해당 함수를 찾아가게 됨
  • onClick={handleClick()}

    • 소괄호()를 붙이면 함수를 호출하겠다는 의미
    • 순서와 상관없이 component 실행 시 바로 실행

Event

  • https://ko.reactjs.org/docs/events.html
  • React의 이벤트는 HTML의 DOM 이벤트와 다소 다르며, SyntheticEvent라는 래퍼 이벤트 객체를 사용
  • on으로 시작
  • 이벤트 속성의 이름은 캐멀 표기법으로 작성
  • 이벤트에 실행 할 자바스크립트의 코드를 전달하는 것이 아닌 함수 형태의 값을 전달
html -> <button onclick="alert('hello world')">클릭</button>
react -> <button onClick={() => alert('hello world')}>클릭</button>
  • DOM 요소에만 이벤트를 설정할 수 있다

    • 컴포넌트에 onClick이라고 해서 전달하면 props 객체에 담기는 값일 뿐이다

      function CustomButton(props) {
        return <button onClick={props.onClick}>클릭</button>;
      }
      
      // 사용 예시
      <CustomButton onClick={handleClick} />
      
      			```
      
    • 컴포넌트 내부에서 props에 전달되어 온 함수를 이벤트 핸들러로 추가할 수 있다

      			<button onClick={this.props.onClick}>클릭</button>
      			```

리액트의 이벤트 시스템

  1. 직접 이벤트 속성에 이벤트 핸들러 함수 정의하며 이벤트 연결
<div id="root"></div>

<script type="text/babel">
    class EventButton extends React.Component {

        /* constructor를 생략하면 React가 자동으로 아래 구문을 적어준다. */
        constructor(props) {
            super(props);
        }

        /* 1. 직접 이벤트 속성에 이벤트 핸들러 함수 정의하며 이벤트 연결 */
        render() {
            console.log(this.props);

            return (
                <button onClick={() => alert('인라인 함수 이벤트 동작 확인')}>{this.props.children}</button>
            );
        }
    }

    ReactDOM.createRoot(document.getElementById('root')).render(
            
        <EventButton>이벤트버튼</EventButton>
    );
</script>
  1. render 함수 외부에 이벤트 핸들러 함수 정의 후 함수 전달하여 이벤트 연결
<div id="root"></div>

<script type="text/babel">
    class EventButton extends React.Component {

        /* constructor를 생략하면 React가 자동으로 아래 구문을 적어준다. */
        constructor(props) {
            super(props);
        }

        /* 2. render 함수 외부에 이벤트 핸들러 함수 정의 후 함수 전달하여 이벤트 연결 */
        onClickHandler = () => alert('외부 함수 이벤트 동작 확인');

        /* 클래스 내부의 함수(메소드)를 호출 할 때는 this.을 반드시 붙여줘야 한다. */
        render() {
            return (
                <button onClick={this.onClickHandler}>{this.props.children}</button>
            );
        }
    }

    ReactDOM.createRoot(document.getElementById('root')).render(
            
        <EventButton>이벤트버튼</EventButton>
    );
</script>
  1. component에 이벤트를 props로 전달 후 연결
<div id="root"></div>

<script type="text/babel">
    class EventButton extends React.Component {

        /* constructor를 생략하면 React가 자동으로 아래 구문을 적어준다. */
        constructor(props) {
            super(props);
        }

        /* 3. component에 이벤트를 props로 전달 후 연결 */
        render() {
            console.log(this.props);

            const {onClick, children} = this.props;

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

    ReactDOM.createRoot(document.getElementById('root')).render(
            
        <EventButton onClick={() => alert('props로 이벤트 전달 후 연결 확인')}>이벤트 버튼</EventButton>
    );
</script>

Event Handling(이벤트 핸들링)

onChange 이벤트

  • 입력 필드의 값이 변경될 때마다 호출되는 이벤트

  • onChange 이벤트의 event.target은 해당 input 요소

  • 대부분의 onChange 이벤트 핸들러는 내부적으로 이벤트에 대한 전반적인 정보를 관리하는 이벤트 객체(event / e)를 자동으로 첫번째 매개변수로 전달받는다

  • 이벤트 객체로 값을 꺼내고 전달할 수 있다.

    	- e.target.value
<div id="root"></div>

<script type="text/babel">
    class EventComponent extends React.Component {

        state = {
            message: ''
        };

        render() {
            return (
                <>
                    <h1>이벤트 핸들링</h1>
                    <input 
                        type="text"
                        name="message"
                        placeholder="텍스트를 입력해주세요"
                        // 입력을 하면 글이 계속 변화하므로 변화 시에 발생하는 이벤트라고 보면 된다.
                        // 입력할 때마다 변화를 감지
                        onChange={
                            // event의 target은 input이다
                            (e) => {
                                console.log(e);
                                this.setState({
                                    message: e.target.value
                                });
                            }
                        }
                        value={this.state.message}
                    />
                    <button
                        onClick={
                            () => {
                                alert(`${this.state.message} 지워짐!`);
                                this.setState({
                                    message: ''
                                });
                            }
                        }
                    >
                    확인
                    </button>
                </>
            );
        }
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<EventComponent/>);
</script>

Event Handling Method Separation(이벤트 핸들러 메서드 분리)

함수가 호출될 때 this는 호출부에 따라 결정

  • 메소드 호출 대상에 따라 달라짐
  • javascript에서 this는 함수를 어떻게 호출하느냐에 따라 결정된다
  • 특히 React 클래스 컴포넌트에서 중요
  • 클래스 메서드를 이벤트 핸들러로 전달하면, 메서드와 컴포넌트 인스턴스(this)의 연결이 끊어질 수 있다
  • 따라서 이벤트 핸들러 메서드를 this와 명시적으로 바인딩(bind) 해야 this.setState 등이 정상 작동한다
  • 바인딩하지 않으면 this는 undefined가 되어 에러 발생
  • 함수형 컴포넌트는 해당 X, 클래스형 컴포넌트만 해당

해결방법
1. constructor에서 bind(this) 사용

<div id="root"></div>

<script type="text/babel">
    class EventComponent extends React.Component {
        state = {
            message: ''
        };

        constructor(props) {
            super(props);

            /* constructor에서 bind, this 고정 */
            this.onChangeHandler = this.onChangeHandler.bind(this);
            this.onClickHandler = this.onClickHandler.bind(this);
        }

        onChangeHandler(e) {
            this.setState({
                message: e.target.value
            });
        }

        onClickHandler(e) {
            alert(this.state.message);

            this.setState({
                message: ''
            });
        }

        render() {
            return (
                <>
                    <h1>이벤트 핸들링 - 핸들러 메소드 분리</h1>
                    <input 
                        type="text"
                        name="message"
                        placeholder="텍스트를 입력해주세요"
                        onChange={this.onChangeHandler}
                        value={this.state.message}
                    />
                    <button onClick={this.onClickHandler}>확인</button>
                </>
            );
        }
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<EventComponent/>);
</script>
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this); // this 고정
  }

  handleClick() {
    console.log(this); // this는 컴포넌트 인스턴스
  }

  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}

2. 클래스 필드에서 화살표 함수 사용

<div id="root"></div>

<script type="text/babel">
    class EventComponent extends React.Component {
        state = {
            message: ''
        };

        // 화살표 함수로 this 자동 바인딩
        onChangeHandler = (e) => {
            this.setState({
                message: e.target.value
            });
        };

        onClickHandler = () => {
            alert(this.state.message);

            this.setState({
                message: ''
            });
        };

        render() {
            return (
                <>
                    <h1>이벤트 핸들링 - 화살표 함수 방식</h1>
                    <input 
                        type="text"
                        name="message"
                        placeholder="텍스트를 입력해주세요"
                        onChange={this.onChangeHandler}
                        value={this.state.message}
                    />
                    <button onClick={this.onClickHandler}>확인</button>
                </>
            );
        }
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<EventComponent />);
</script>

Multiple Event Handling

한 state의 valuse를 각각 별로의 이벤트 핸들러로 작성하여 관리하게 되면 이벤트가 많아질수록 외부함수도 많아지게 되어 코드가 복잡해진다.

  • 이러한 불필요한 과정을 막고자 묶을 수 있는 이벤트는 하나의 핸들러로 통합해서 관리

1. 클래스형 컴포넌트(class component) 이벤트 핸들링 방식

<div id="root"></div>

<script type="text/babel">

    class LoginComponent extends React.Component {

        state = {
            username: '',
            password: ''
        }

        /* 이벤트가 발생한 input 태그의 name 속성 값을 활용하여 하나의 핸들러 메소드로 처리할 수 있다. */
        onChangeHandler = e => {
            this.setState({
            	/* e.target.name : 입력된 input의 name 속성 값
                   e.target.value : input에 실제로 사용자가 입력한 값
                   [e.target.name] : 'username' 또는 'password'를 키로 사용
                   하나의 함수로 두 input을 모두 처리 
                   e.target.name에 대괄호를 씌우는 이유는 해당 변수 안에 있는 값을 프로퍼티 키로 적용하기 위함 */
                [e.target.name]: e.target.value 
            });
        }

        onClickHandler = e => {
            alert(`username: ${this.state.username} \n password: ${this.state.password}`);
            this.setState({
                username: '',
                password: ''
            });
        }

        render() {
            return (
                <>
                    <h1>로그인</h1>
                    <label>아이디: </label>
                    <input
                        type="text"
                        name="username" 
                        placeholder="아이디를 입력하세요"
                        value={this.state.username}
                        onChange={this.onChangeHandler}
                    />
                    <br/>
                    <label>비밀번호: </label>
                    <input
                        type="password"
                        name="password" 
                        placeholder="비밀번호를 입력하세요"
                        value={this.state.password}
                        onChange={this.onChangeHandler}
                    />
                    <br/>
                    <button onClick={this.onClickHandler}>로그인</button>
                </>
            );
        }
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<LoginComponent/>);
</script>

2. 함수형 컴포넌트(function component) 이벤트 핸들링 방식
useState hooks을 사용해 하나의 객체 state로 여러 input 값을 통합 관리

  • 여러 input 값을 하나의 form 객체로 관리
  • state로 관리되는 객체의 부분적인 변수값을 수정할 때 나머지 부분을 스프레드 연산자(...)를 이용하여 기존 값을 복사한 후, 덮어쓰기 방식 사용
  • 클래스형 컴포넌트에서는 setState()가 자동으로 이전 상태를 자동 병합(merge) 해주기 때문에, 스프레드 연산자(...)를 직접 쓸 필요가 없다
<div id="root"></div>

<script type="text/babel">

	/* React.useState */
    const {useState} = React;

    function LoginComponent(){

		/* form이라는 상태 선언 */
        const [form, setForm] = useState({
            username: '',
            password: ''
        });

        const {username, password} = form;

        /* 클래스형 컴포넌트와 다른 점 */
        const onChangeHandler = e => {
            const changedForm = {
                /* 기존 form 내용 복사 
                   새로운 객체를 생성하기 때문에 기존 값들을 유지하려면 먼저 복사해야 한다.*/
                ...form,
                /* 이벤트가 발생한 name과 일치하는 프로퍼티 값은 value값으로 수정 */
                [e.target.name]: e.target.value
            }

            /* 통째로 수정한 객체를 setForm에 던져준다. */
            setForm(changedForm);  
        };

        const onClickHandler = () => {
            alert(`username: ${username} \n password: ${password}`);

            setForm({
                username: '',
                password: ''
            });
        }

        return (
            <>
                <h1>로그인</h1>
                <label>아이디: </label>
                <input
                    type="text"
                    name="username"
                    placeholder="아이디를 입력하세요"
                    value={username}
                    onChange={onChangeHandler}
                />
                <br/>
                <label>비밀번호: </label>
                <input
                    type="password"
                    name="password"
                    placeholder="비밀번호를 입력하세요"
                    value={password}
                    onChange={onChangeHandler}
                />
                <br/>
                <button onClick={onClickHandler}>로그인</button>
            </>
        );
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<LoginComponent/>);
</script>

Iteration(반복자)

추가적인 배열 메서드

arr.map(callback);

  • callback함수의 파라미터로 전달 된 배열 내 각 요소를 원하는 규칙에 따라 변환 후 그 결과로 새로운 배열을 반환
  • 원본 배열은 변경되지 않고 반환되는 결과는 새로운 배열이다
  • callback: 새로운 배열 요소를 생성하는 규칙을 담은 함수
  • .map(currentValue, index, array)
  • currentValue: 현재 처리하고 있는 요소
  • index: 현재 처리하고 있는 요소의 index값(선택)
  • array: 현재 처리하고 있는 원본 배열(선택)
  • 반환 형태는 배열이다.

Key

리액트에서 컴포넌트 배열을 렌더링 했을 때 어떤 원소에 변동이 있는지를 알아내기 위해 사용하는 식별자 역할

  • key가 존재하지 않을 때는 React 가상DOM이 실제DOM과 비교하는(diff) 과정에서 전체 리스트를 순차적으로 다 비교해 가며 변화 감지
  • key가 존재한다면 이 값을 이용하여 어떤 변화가 일어났는지를 더 빨리 감지
  • 일반적으로는 db에서 조회한 데이터인 경우 pk컬럼값을 key로 설정

key 없이 렌더링(경고 발생)
props 객체의 배열을 map을 활용하여 각각의 요소를 가지는 li요소 배열을 반환

  • 렌더링 된 화면은 정상적으로 li 태그들이 출력되지만 개발자 도구 콘솔에는 key가 존재하지 않는다고 경고 발생
<div id="root"></div>

<script type="text/babel">

    function Items({names}) {

        console.log(names);
        return (
            <ul>
                {names.map(name => <li>{name}</li>)}
                <br/>
                /* 배열로 만든 요소들 */
                {[<li>{'홍길동'}</li>, <li>{'유관순'}</li>, <li>{'윤봉길'}</li>, <li>{'이순신'}</li>, <li>{'임꺽정'}</li>]}
                <br/>
                <li>{'홍길동'}</li>
                <li>{'유관순'}</li>
                <li>{'윤봉길'}</li>
                <li>{'이순신'}</li>
                <li>{'임꺽정'}</li>
            </ul>
        );
    }

    const names = ['홍길동', '유관순', '윤봉길', '이순신', '임꺽정'];

    ReactDOM.createRoot(document.getElementById('root')).render(<Items names={names}/>);
</script>

key를 지정한 경우 (정상 렌더링, 경고 없음)

<div id="root"></div>

<script type="text/babel">

    function Items({names}) {

        console.log(names);
        return (
            <ul>   
                {names.map((name, index) => <li key={index}>{name}</li>)}
            </ul>
        );
    }

    const names = ['홍길동', '유관순', '윤봉길', '이순신', '임꺽정'];

    ReactDOM.createRoot(document.getElementById('root')).render(<Items names={names}/>);
</script>

DB에서 값을 가져오는 경우 key 지정 방식

return (
        <ul>   
            {names.map((name) => <li key={name.id}>{name.name}</li>)}
        </ul>
    );
}

const names = [{id:1, name: '값1'}, {id:2, name: '값2'}];

ReactDOM.createRoot(document.getElementById('root')).render(<Items names={names}/>);

LifeCycle


컴포넌트의 라이프 사이클 메소드는 총 9가지가 있다.

  • 라이프 사이클 메소드는 클래스형 컴포넌트에서만 사용이 가능
  • 함수형 컴포넌트에서는 useEffect 등 Hook으로 대체
  • 라이프 사이클은 총 3가지 카테고리인 마운트, 업데이트, 언마운트로 나뉜다
  • 마운트: DOM이 생성되고 웹 브라우저상 나타나는 것, 컴포넌트가 DOM에 처음 삽입되는 과정
  • 업데이트: 컴포넌트의 상태가 바뀌는 경우를 의미, 총 4가지의 업데이트 상황이 있다.
    - props변경, state변경, 부모 컴포넌트 리렌더링, this.forceUpdate로 강제 렌더링 트리거
  • 언마운트: 컴포넌트를 DOM에서 제거하는 것

Mount

마운트 시
constructor -> getDerivedStateFromProps -> render -> componentDidMount 순으로 호출

<div id="root"></div>

<script type="text/babel">

    class Greeting extends React.Component {

        /* 1. 컴포넌트를 새로 만들 때마다 호출되는 클래스 생성자 메소드이다. */
        constructor(props) {

            /*
                컴포넌트를 만들 때 처음으로 실행된다. 
                생성자 내에서는 state를 초기화 할 수 있다.
                (getDerivedStateFromProps를 쓸려면 반드시 state 초기화를 해야 한다.)
            */
            super(props);

            console.log('constructor');

            this.state = {
                text: '초기값'
            };
        }

        /* 2. props에 있는 값을 state에 넣을 때 사용하는 메소드이다. */
        static getDerivedStateFromProps(nextProps, nextState) {

            /*
                리액트 16.3 이후에 새로 만든 라이프 사이클 메소드이다.
                props로 받아온 값을 state에 동기화 시키는 용도로 사용하며, 마운트와 업데이트 시 호출된다.
            */
            console.log('getDerivedStateFromProps');

            return null;        // state를 변경할 필요가 없다면 null을 반환한다.
        }

        /* 3. 리액트 엘리먼트를 가상돔으로 구성하고 실제 렌더트리까지 구성하는 시점에 호출되는 메소드이다. */
        render() {

            /*
                랜더링 될 컴포넌트의 형태를 리액트 컴포넌트로 반환한다.
                라이프 사이클 메소드 중 유일한 필수 메소드이다.
                render 메소드는 this.props와 this.state에 접근할 수 있다. 
                아무런 컴포넌트도 보여주고 싶지 않다면 null 혹은 falsy 값을 반환하도록 한다. 

                주의 사항
                1. 이 메소드 내에서는 이벤트 설정이 아닌 곳에서 setState를 사용하면 안된다. 
                    this.setState({
                        text: '수정'
                    });

                2. 브라우저의 DOM에 접근해서도 안된다.

                DOM 정보를 가지고 오거나 state에 변화를 줄 때 componentDidMount에서 처리해야 한다.
            */
            console.log('render');
                    
            return (
                <>
                    <h1>현재 상태값, {this.state.text}</h1>
                </>
            )
        }

        /* 4. 컴포넌트가 웹 브라우저 상에 나타난 후 호출되는 메소드이다. */
        componentDidMount() {

            /*
                컴포넌트를 다 만든 후에 첫 렌더링을 마치고 나면 호출된다.(DOM이 그려지고 난 후, Mount 이후)
                다른 자바스크립트 라이브러리 또는 프레임워크의 함수를 호출하거나
                이벤트 등록, setTimeout, setInterval, 네트워크 요청 같은 비동기 작업을 처리하면 된다.
            */
            console.log('componentDidMount');
        }
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<Greeting/>);
</script>

Update

컴포넌트 업데이트 생명주기에 대한 업데이트 기준은 크게 4가지로 나뉜다
1. props 변경
2. state 변경
3. 부모 컴포넌트 리렌더링
4. this.forceUpdate로 강제 렌더링 트리거(render 내에서는 가급적 사용하지 않아야 한다. 사실상 쓸 일은 거의 없음)

  • getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate 순으로 호출
  • 4의 경우는 바로 render 함수를 호출
  • 처음 화면을 시작하면 mount가 진행된다(constructor -> getDerivedStateFromProps -> render -> componentDidMount)
  • 그 뒤 컴포넌트가 업데이터 된다면 shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate 순으로 호출이 시작된다.
<div id="root"></div>

<script type="text/babel">
    class TimePrinter extends React.Component {

        constructor(props) {

            super(props);
            this.state = {
                now: new Date().toLocaleTimeString()
            };
        }

        /* 1. props에 있는 값을 state에 넣을 때 사용하는 메소드이다. */
        static getDerivedStateFromProps(nextProps, nextState) {

            console.log('getDerivedStateFromProps');

            console.log(nextProps);  // 리렌더링 시 변경된 props값
            console.log(nextState);  // 리렌더링 시 변경된 state값

            /* 아래와 같은 조건문으로 인수들을 활용해 prop에 변화가 생기면 state에도 변화를 줄 수 있는 코드를 작성할 수도 있다. */
            // if(nextProps.text !== nextState.prevProp) {
            //     return {now: nextProps.text};
            // }

            return null;        // state에 변화를 주고 싶지 않다면 null을 반환
        }

        /* 2. 컴포넌트가 리렌더링을 할 것인지 말 것인지를 결정하는 메소드이다.(render() 메소드 호출 유무 결정) */
        shouldComponentUpdate(nextProps, nextState) {

            /*
                자주 사용되지는 않는 생명주기 메소드이다. 
                주로 성능 최적화를 목적으로 하며, 상황에 따라 리렌더링을 방지할 목적으로 사용한다.
            */
            console.log('shouldComponentUpdate');

            console.log(nextProps);
            console.log(nextState);

            /* 반드시 boolean을 반환해야 하며, false를 반환할 시 render부터 이후 생명주기 메소드 호출은 여기서 중단된다. */
            return true;
        }

        /* 3. 컴포넌트를 렌더링 하는 메소드이다. */
        render() {
            console.log('render');

            return (
                <>
                    <button
                        onClick={() => this.setState({now: new Date().toLocaleTimeString()})}
                    >
                        현재 시간 확인하기
                    </button>
                    <h1>{this.state.now}</h1>
                </>
            );
        }

        /* 4. 컴포넌트의 변화를 실제 DOM에 반영하기 직전에 호출하는 메소드이다. */
        getSnapshotBeforeUpdate(prevProps, prevState) {

            /*
                리액트 16.3 이후 만들어진 메소드이다.
                render에서 만들어진 결과물이 브라우저에 실제 반영되기 직전에 호출되며, 
                해당 메소드의 반환값은 componentDidUpdate에서 전달받을 수 있다. 
                주로 업데이트 하기 직전의 값을 참고할 일이 있을 때 활용한다.(대표적으로 스크롤바 위치 유지)
             */ 
            console.log('getSnapshotBeforeUpdate');

            // return null;

            /* componentDidUpdate의 sanpshot 인수(3번째)로 전달 된다. */
            return {
                message: '스냅샷 입니다.'
            }
        }

        /* 5. 컴포넌트 업데이트 작업이 끝난 후 호출하는 메소드이다. */
        componentDidUpdate(prevProps, prevState, snapshot) {

            /*
                리렌더링이 끝나고 화면이 update 된 후 실행된다.
                업데이트가 끝난 직후이기 때문에 DOM관련 처리를 해도 되며,
                컴포넌트가 이전에 가졌던 데이터를 prevProps, prevState로 접근할 수 있다. 
                또한, getSnapshotBeforeUpdate에서 반환하는 값을 세 번째 파라미터로 전달 받을 수도 있다.
            */
            console.log('componentDidUpdate');
            console.log(snapshot);
        }
    }

    ReactDOM.createRoot(document.getElementById('root')).render(<TimePrinter/>);
</script>

UnMount

컴포넌트가 DOM에서 null로 초기화될 때 호출되는 생명주기

  • componentWillUnmount
  • 컴포넌트가 화면에서 null로 초기화 되기 직전에 딱 한 번 호출
  • 보통 componentDidMount에서 등록했던 이벤트 리스너, 타이머, 외부 리소스 정리 작업을 여기서 수행
<div id="root"></div>

<script type="text/babel">

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

    class Greeting extends React.Component {
        render() {
            return <h1>Hello World</h1>;
        }

        /* 컴포넌트가 웹 브라우저 상에서 사라지기 직전에 호출하는 메소드이다. */
        componentWillUnmount() {

            /*componentDidMount 시점에 등록한 이벤트, 타이머, 직접 생성한 DOM이 있다면 여기서 다 null로 초기화 작업을 주로 하게 된다. */
            console.log('componentWillUnmount');
        }
    }

    root.render(<Greeting/>);

    /* 5초 뒤 아무런 컴포넌트도 렌더링하지 않는 것으로 변경하여 언마운트 상황을 연출 */
    window.setTimeout(() => {
        root.render(null);
    }, 5000);
</script>

Error (오류 처리 생명주기)

static getDerivedStateFromError(error)

  • 자손 컴포넌트에서 오류가 발생했을 때 자동 호출되는 메소드
  • 매개변수로 오류 객체(error)를 전달 받고, 갱신된 state값을 반드시 반환해야 한다
  • 오류 표시용 플래그를 state에 저장하여 UI 변경 처리
  • 주의할 점은 render 단계에서 호출되기 때문에, 부수적인 효과를 여기서는 발생시키면 안된다

componentDidCatch(error, info)

  • 자손 컴포넌트에서 오류가 발생했을 때 호출
  • error : 실제 발생한 오류 객체
  • info : 어떤 컴포넌트가 오류를 발생시켰는지에 대한 정보를 포함한 객체
  • 오류 로그들을 기록하는 특정 메소드를 호출한다거나 하는 일들을 처리
  • UI 업데이트 목적이라면 해당 메소드에서 setState를 호출하는 대신 getDerivedStateFromError 메소드에서 변경 될 state 객체를 반환하여 UI를 핸들링 하는 것이 더 좋다

참고

문서화 주석

/**
@params - 함수의 매개변수 설명
@return - 반환 값 설명
*/

!!

  • JavaScript 문법
  • Boolean 값으로 강제 변환
  • 참인지 거짓인지 명확하게 Boolean으로 변환하는 표현
profile
잔디 속 새싹 하나

0개의 댓글