컴포넌트(Component)

JOY🌱·2023년 4월 17일
0

🐳 React

목록 보기
1/5
post-thumbnail

💁‍♀️ 컴포넌트(Component)란,
UI(User Interface)를 구성하는 블록(block)이며, 재사용 가능한 UI 조각(piece)

  • 컴포넌트는 일반적으로 입력(input)값을 받아들이고, 출력(output)을 반환하는 방식(순수 함수)으로 작동
  • 입력값은 컴포넌트의 속성(props)으로 전달
  • 상태(state)를 가질 수 있으며, 상태(state)는 컴포넌트 내부에서 변경되는 값
  • 리액트에서 컴포넌트는 클래스(class) 형태함수(function) 형태로 작성
    • 클래스 형태의 컴포넌트는 React.Component 클래스를 상속받아 작성
    • 함수 형태의 컴포넌트는 함수가 반환하는 JSX 코드로 작성

🙋‍ 렌더링 방식 🔥
React Element를 Root DOM node에 렌더링 하려면 ReactDOM.createRoot(Root DOM node).render(렌더링 할 엘리먼트)로 처리

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

👀 JSX

💁‍♀️ JSX란?
자바스크립트를 확장한 문법으로 ReactElement를 XML문법 형식으로 정의할 수 있는 방법

  • createElement를 이용해 엘리먼트를 정의하면 복잡하고 가독성이 떨어짐. 때문에 리액트 팀은 ReactElement를 정의하는 간편한 방법으로 JSX 문법을 제공
  • 단, JSX는 공식적인 자바스크립트 문법이 아니어서 바벨(Babel)이라는 트랜스 컴파일링 툴이 필요
  • 바벨은 ES6+ ES6 next 문법을 ES5 문법으로 변환해주는 도구인데, JSX를 순수 리액트로 변환해주는 기능도 포함 (바벨이 JSX 처리 표준)

1) JSX의 사용

<!DOCTYPE html>
<html lang="ko">
<head>
    ...
    <title>01_JSX-intro</title>
    <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>
    <script src='https://unpkg.com/@babel/standalone/babel.min.js'></script>
</head>
<body>

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

    <!-- head에 CDN 추가 후, script의 type에 "text/babel" 추가 -->
    <script type="text/babel">
        const helloworld = <h1>Hello World 🤡</h1>;

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

</body>
</html>

2) JSX에서의 표현식

<body>

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

    <script type="text/babel">

        function formatName(user) {
            return `${user.lastName} ${user.firstName}`;
        }

        const user ={
            firstName : 'Cheese🧀',
            lastName : 'Heo'
        };

        /* JSX 문법 내에 중괄호를 이용하여 모든 javascript 표현식(문법)을 넣을 수 있음 */
        const element = <h1>Good to see u🐾🐶 { formatName(user) }</h1>;
        
        ReactDOM.createRoot(document.getElementById('root')).render(element);

    </script>
    
</body>

3) Fragment

<body>

    <div id="root"></div>
    
    <script type="text/babel">

        const user = {
            name : '꼬순',
            phone : '010-7272-7272'
        };

        /* 1. 최상위 엘리먼트가 두 개 이상이면 에러 발생
           (하나의 DOM 트리만 생성해야함 ) */
        const element = (
            <h1>How are you { user.name } 😎</h1>
            <h3>phone : { user.phone }</h3>
        );
        /* 2. <div> 태그로 감싸서 하나의 돔 트리를 생성할 수 있도록 함 */
        const element = (
            <div>
                <h1>How are you { user.name } 😎</h1>
                <h3>phone : { user.phone }</h3>
            </div>
        );
        /*  3. <React.Fragment>로 감싸서 형식상의 DOM 트리 상 상위 엘리먼트를 만들어줌 */
        const element = (
            <React.Fragment>
                <h1>How are you { user.name } 😎</h1>
                <h3>phone : { user.phone }</h3>
            </React.Fragment>
        );
        /* 4. React 라이브러리로부터 Fragment 객체만 구조분해할당 해주면 React. 생략 가능 */
        const { Fragment } = React;
        
        const element = (
            <Fragment> {/* 일종의 변수를 사용하는 것 */}
                <h1>How are you { user.name } 😎</h1>
                <h3>phone : { user.phone }</h3>
            </Fragment>
        );
        /* 5. React.Fragment의 축약 문법인 <></>로 감싸서 사용 가능 (Babel 17이상부터 지원)*/
        const element = (
            <>
                <h1>How are you { user.name } 😎</h1>
                <h3>phone : { user.phone }</h3>
            </>
        );


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

    </script>

</body>

4) 어트리뷰트

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

    <script type="text/babel">

        /* JSX는 HTML 태그처럼 보이지만 실제로는 자바스크립트 
           (React.createElement를 호출하는 코드로 컴파일 됨)
           따라서 자바스크립트의 예약어와 동일한 이름의 attribute는 다른이름으로
           우회적으로 사용하도록 정의되어있음 */

        /* 1. 문자열로 속성 값 정의하는 방법 */
        // const element = <h1 id="title" className="highlight">Hello World💟</h1>;

        /* 2. 자바스크립트 표현식을 이용하는 방법 */
        const id = "title";
        const className = "highlight";
        const element = <h1 id={ id } className={ className }>Hello World💟</h1>;

        /* rendering하는 코드 */
        ReactDOM.createRoot(document.getElementById('root')).render(element);

    </script>
    
</body>

5) 인라인 스타일 어트리뷰트

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

    <script type="text/babel">

        /* style 속성에 객체 형태의 attribute를 추가해야함
           객체의 속성명은 카멜 케이스로 작성
           속성 값은 문자열 혹은 숫자 형태로 작성해야함 
        */

        const style = {
            backgroundColor : '#c2e3f9',
            color : 'white',
            cursor : 'pointer',
            textAlign : 'center',
            padding : '20px'
            // 단위를 작성하려면 문자열로 사용하지만 단위를 생략하면 숫자만 사용 가능
        };

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

        /* rendering하는 코드 */
        ReactDOM.createRoot(document.getElementById('root')).render(element);

    </script>
</body>

6) 이벤트 어트리뷰트

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

    <script type="text/babel">

        /* 1. 인라인으로 간단한 이벤트 적용 시 JSX의 자바스크립트 표현식 내에 이벤트 처리 함수 작성 가능 */
        // const element = (
        //     <>
        //         <h1>Event Attribute</h1>
        //         <button onClick={ ()=> alert('Hello Cheese🧀') }>Click to say Hello</button>
        //     </>
        // );

        /* 2. 이벤트 동작 시 사용할 핸들러 메소드 사전 정의 후 JSX 내에 표현식으로 사용 */
        const onClickHandler = () => {
            alert('Hello Cheese🧀💖');
        }

        const element = (
            <>
                <h1>Event Attribute</h1>
                <button onClick={ onClickHandler }>Click to say Hello</button>
            </>
        );

        /* rendering하는 코드 */
        ReactDOM.createRoot(document.getElementById('root')).render(element);

    </script>
    
</body>

7) JSX에서의 주석처리

<body>

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

    <script type="text/babel">

        const element = (
            <>
                <h1>Comment in JSX</h1>
                {/*  JSX내에서의 주석은 이렇게 작성 */}
                <h3
                    id="text"           // 시작 태그를 여러 줄로 작성한다면 여기에도 주석 작성 가능
                    className="text"    
                >
                    JSX 내의 주석 사용하는 방법    
                </h3>
                /* 하지만 이런 주석이나 */
                // 이런 주석은 페이지에 그대로 나타나게 됨
            </>
        );

        /* rendering하는 코드 */
        ReactDOM.createRoot(document.getElementById('root')).render(element);

    </script>
    
</body>

👀 Rendering

💁‍♀️ Rendering이란?
React 엘리먼트를 실제 DOM 요소로 변환하는 프로세스를 의미

  • React는 Virtual DOM을 사용하여 렌더링을 최적화하고 Virtual DOM은 React 엘리먼트의 변경 사항을 비교하고 최소한의 DOM 조작을 수행하는 것을 가능하게 함. 이를 통해 불필요한 DOM 조작을 피하고, 빠른 UI 업데이트를 제공 가능
  • React에서는 상태(state) 또는 속성(props)이 변경될 때마다 렌더링이 발생

1) Rendering element

<body>
    <!-- 
        이 안에 들어가는 모든 엘리먼트를 ReactDOM에서 관리하기 때문에 이것을 Root DOM node라고 부름
        일반적으로 React로 구현 된 애플리케이션은 하나의 Root DOM node가 있음
        단, 리액트를 기존 앱에 통합하려는 경우에는 원하는 만큼의 독립된 Root DOM node를 가질 수도 있음
     -->
    <div id="root"></div>

    <script type="text/babel">

        const element = (
            <>
                <h1>What time is it now? 🤔</h1>
                <h3>It is { new Date().toLocaleTimeString() }</h3>
            </>
        );

        /* React Element를 Root DOM node에 렌더링 하려면 
           ReactDOM.createRoot(Root DOM node).render(렌더링 할 엘리먼트)로 처리 */
        ReactDOM.createRoot(document.getElementById('root')).render(element);

    </script>
    
</body>

2) Rendered element update

🙋‍ 리액트 엘리먼트는 불변 객체(immutable)
엘리먼트를 생성한 이후에는 해당 엘리먼트의 자식이나 속성을 변경할 수 없음
따라서 엘리먼트를 업데이트 하기 위해서는 완전히 새로운 엘리먼트를 만들고 다시 render를 호출해야함

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

    <script type="text/babel">

        /* 2. ReactDOM을 만드는 동작은 한 번만 일어나도록 변수에 담기 */
        const ReactClientDOM = ReactDOM.createRoot(document.getElementById('root'));

        /* 1. element를 function안에 넣기 */
        function tick() {

            const element = (
                <>
                    <h1>What time is it now? 🤔</h1>
                    <h3>It is { new Date().toLocaleTimeString() }</h3>
                </>
            );

            /* 3. 해당 함수 안에서 rendering을 할 것 */
            ReactClientDOM.render(element);

        };

        tick();
        setInterval(tick, 1000);
        
        /* 1초마다 tick 함수를 호출하여 새로운 엘리먼트를 생성하고 렌더링
           매 초마다 새로운 엘리먼트가 다시 렌더링 되지만 개발자 도구의 elements를 살펴보면
           실제 업데이트 되는 부분만 갱신 되는 것을 확인 가능 
        
           리액트 엘리먼트는 메모리 상에서 연산되는 가상의 돔
           가상 돔은 실제 돔과 동일한 렌더 트리를 가지고 있음 (복제본)
           render 호출 시 새로운 렌더 트리를 만들고 가상의 돔과 비교 (diff 알고리즘)
           비교 결과 변화가 있는 부분만 실제 돔에 반영 !!
        */
    </script>
    
</body>

3) 조건부 렌더링

<body>

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

    <script type="text/babel">

        /* ReactDOM을 만드는 동작은 한 번만 일어나도록 변수에 담기 (여기서는 상관 X) */
        const ReactClientDOM = ReactDOM.createRoot(document.getElementById('root'));

        /* 조건부 렌더링
           여러 개의 엘리먼트 중 특정 조건에 따라 하나의 엘리먼트만 렌더링 가능 */
        const answer = parseInt(prompt('리액트, 재미있나유?\n 1. 재밌어용!🥳 \n 2. 어려워용😵‍💫'));

        /* 1. 렌더링 시 조건 비교 후 조건부 렌더링 */
        const positiveElement = <h1>앞으로 점점 더 재미있을거에유😊</h1>;
        const negativeElement = <h1>천천히 앞의 내용을 복습해보세유😂</h1>;

        ReactClientDOM.render((answer === 1) ? positiveElement : negativeElement);

        /* 2. JSX 내에서 조건부로 엘리먼트 생성 */
        /* 2-1. if문 */
        let element;
        if(answer === 1){
            element = <h1>앞으로 점점 더 재미있을거에유😊</h1>;
        } else {
            element = <h1>천천히 앞의 내용을 복습해보세유😂</h1>;
        }

        /* 2-2. 삼항연산자 */
        const element = (answer === 1) ? (
            <h1>앞으로 점점 더 재미있을거에유😊</h1>
        ) : (
            <h1>천천히 앞의 내용을 복습해보세유😂</h1>
        )

        /* 2-3. && 연산자를 이용한 조건부 엘리먼트 생성 
        => 특정 조건을 만족하는 경우에만 렌더링을 하고 그렇지 않은 경우 
           아무것도 보이지 않게 하고 싶은 경우 사용 */
        const element = answer === 1 && <h1>앞으로 점점 더 재미있을거에유😊</h1>
        => 1이 아닌 경우 아무것도 렌더링을 하지 않음

        /* && 연산자 주의 사항
        false 조건을 가지고 렌더링 하는 경우 조건에 일치하지 않으면 렌더링되는 요소가 없지만
        0과 같이 falsy한 값을 이용해 조건부 엘리먼트를 생성 하면 0을 반환 */
        const number = 0;
        const element = number && <h1>0이 아님다</h1>; // 렌더링 되지 않게 하고자 하는 의도였으나 0이 렌더링 됨

        ReactClientDOM.render(element);

    </script>
    
</body>


👀 Component

1) 클래스형 컴포넌트

💁‍♀️ 클래스형 컴포넌트란?
ES6에서 제공되는 클래스 문법을 이용해 렌더링 될 컴포넌트

  • render() 함수는 반드시 작성되어야 하며 반환 값으로는 렌더링 될 리액트 엘리먼트를 정의(일반적으로 JSX를 사용)
  • 중복 되는 엘리먼트를 추상화해서 컴포넌트로 작성하고 재사용 하는 것이 리액트가 추구하는 방향
  • '함수형 컴포넌트'가 문법적으로 더 간결하고 메모리도 덜 사용하기에 권장 되는 방식. 하지만 유상태 컴포넌트 사용 및 라이프 사이클 기능, 임의의 메소드 정의 등은 '클래스형 컴포넌트'에서만 사용 가능한 방식. 이러한 함수형 컴포넌트의 기능적 한계로 훅스(hooks)를 제공하고 있으며, 이 부분은 뒤에서 다룸
<body>
    
    <div id="root"></div>

    <script type="text/babel">

        /* 클래스형 컴포넌트 : React.Component를 상속받고 render() 함수 정의 */
        class Title extends React.Component {

            render() {
                return (
                    <h1>Class Component 🎃</h1>
                );
            }

        }

        ReactDOM.createRoot(document.getElementById('root')).render(<Title/>); /* 컴포넌트를 rendering할 때 작성 방식 */

    </script>
    
</body>

2) 함수형 컴포넌트

💁‍♀️ 함수형 컴포넌트란?
함수를 사용하여 컴포넌트를 정의하며, 단순한 함수로써 props를 인자로 받아 React 엘리먼트를 반환

  • 함수의 반환 값으로 리액트 엘리먼트만 정의해서 반환하면 됨 (일반적으로 JSX 사용)
  • 유의 사항 : 앞 글자를 대문자로 정의해야한다는 것
  • 사용자 정의 엘리먼트를 이용할 시 <Title/>과 같은 형식으로 사용하는데 앞 글자가 소문자이면 HTML로 인식하게 되고 존재하지 않는 HTML 태그 엘리먼트이기 때문에 에러 발생
<body>
    
    <div id="root"></div>

    <script type="text/babel">

        /* 함수형 컴포넌트 : return에 렌더링 할 JSX 작성 */
        function Title() {

            return(
                <h1>Function Component 👽</h1>
            );
        }
        
        ReactDOM.createRoot(document.getElementById('root')).render(<Title/>); 
        /* 앞 글자를 대문자로 하지 않으면 일반 HTML 태그라고 인식되게 됨 */

    </script>
    
</body>

3) 컴포넌트 합성

💁‍♀️ 컴포넌트 합성이란?
여러 개의 작은 컴포넌트들을 조합하여 하나의 큰 컴포넌트를 만드는 것

  • 컴포넌트는 자신의 출력에 다른 컴포넌트를 참조 가능
  • 컴포넌트는 재사용 가능한 크기로 작게 유지해야하며, 순수 함수(오직 매개변수로 받은 값에만 접근하여 return하는 함수)처럼 작성되어야 함
<body>

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

    <script type="text/babel">

        const user = {
            name : '허치치',
            age : 2,
            phone : '010-7272-7272',
            email : 'cheese@gmail.com'
        };

        function NameCard() {

            return <h1>{ user.name }</h1>
        }

        function AgeCard() {

            return <h2 style={ { color : 'lightblue' } }>{ user.age }</h2>;
        }

        function PhoneCard() {

            return <h2 style={ { color : '#c2e3f9' } }>{ user.phone }</h2>;
        }

        function EmailCard() {

            return <h3 style={ { backgroundColor : '#fcec9b' } }>{ 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>
</body>


👀 Props

💁‍♀️ props란?
properties의 약어로, 컴포넌트의 속성을 설정할 때 사용하는 요소

  • props 값은 해당 컴포넌트를 사용하는 부모 컴포넌트에서 설정 가능
  • props는 읽기 전용이기 때문에 수정하면 안됨
  • 모든 React 컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 동작해야함

1) Props의 사용

<body>

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

    <script type="text/babel">

        function Title(props) {

            return <h1>Hi! { props.myName }, 오랜만이네용 🥳</h1>;
        }

        /* props.myName이 존재하지 않을 경우의 기본 값 설정 */
        Title.defaultProps = {
            myName : '🐶'
        }

        const name1 = "허꼬순";
        const name2 = "허강냉";

        ReactDOM.createRoot(document.getElementById('root')).render(
            [
            <Title myName={ name1 }/>, /* 함수형 컴포넌트에 myName이라는 props에 변수 전달 */
            <Title myName={ name2 }/>,
            <Title myName="허멈머"/>,
            <Title/> // 설정하지 않았으므로, 기본값으로 출력
            ]
        ); 

    </script>
    
</body>

2) Children props

<body>

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

    <script type="text/babel">

        function ChildNodePrinter(props) {

            console.log(props);

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

        ReactDOM.createRoot(document.getElementById('root')).render(
            [
                <ChildNodePrinter name="치즈" phone="010-7272-2727" >텍스트노드</ChildNodePrinter>,
                <ChildNodePrinter><div>자식노드</div></ChildNodePrinter>,
                <ChildNodePrinter><div>1</div><div>2</div><div>3</div></ChildNodePrinter>

            ]
        );

    </script>
    
</body>

3) 구조분해할당과 props

<body>

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

    <script type="text/babel">

        /* ES6에서 제공하는 구조분해할당 문법을 이용하여 props 객체 내부의 값을 바로 추출해서 사용 가능
           (props. 를 생략하고 사용 가능) */

        /* 1. 전달 받은 props 인자를 구조분해할당 하는 방법 */
        function PropsPrinter(props) {

            const { name, children } = props;

            return (
                <>
                    <h1>제 이름은 { name }이에요😋</h1>
                    <h3>제가 가지고 있는 children은 { children }임다!</h3>
                </>
            )
        }

        /* 2. 전달 받는 인자를 구조분해할당 방식으로 선언하는 방법 */
        function PropsPrinter({ name, nickName, children }) {

            return (
                <>
                    <h1>제 이름은 { name }이에요😋</h1>
                    <h2>저를 { nickName }이라고 불러주세용</h2>
                    <h3>제가 가지고 있는 children은 { children }임다!</h3>
                </>
            )
        }

        ReactDOM.createRoot(document.getElementById("root")).render(
            <PropsPrinter name="꼬송" nickName="치즈" >텍스트노드💩</PropsPrinter>
        );

    </script>
    
</body>

👉 전달 받는 인자를 구조분해할당 방식으로 선언하는 방법 Preview

4) propTypes

💁‍♀️ propTypes란?
컴포넌트에 전달되는 props의 타입을 검증하는 데 사용되는 속성

  • props의 타입이 잘못 되었다거나 필수 타입이 처리되지 않았음을 콘솔을 통해 알려줌
  • 렌더링은 되기 때문에 필수적인 설정은 아니나 큰 규모의 프로젝트를 진행하거나 협업하게 되면 해당 컴포넌트에 어떤 props가 필요한지 쉽게 알 수 있어 개발 능률이 좋아질 수 있음
<head>
    ...
    <title>04_props-type-verify</title>
    <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>
    <script src='https://unpkg.com/@babel/standalone/babel.min.js'></script>
    <script src="https://unpkg.com/prop-types@15.8.1/prop-types.js"></script> <!-- 추가 -->
</head>
<body>

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

    <script type="text/babel">

        function PropsVerify({ name, favoriteNumber, children }){

            return (
                <>
                    <h1>내 이름은 { name }!😎</h1>
                    <h2>내가 좋아하는 숫자는 { favoriteNumber }이구 </h2>
                    <h3>내가 가지고 있는 children은 { children }이야! </h3>
                </>
            );
        }

        /* head에 CDN 추가 후 코드 작성 */
        PropsVerify.propTypes = {
            name : PropTypes.string, /* 이름은 String이어야한다는 조건 */
            favoriteNumber : PropTypes.number.isRequired /* 반드시 작성되어야한다는 조건 */
        };

        ReactDOM.createRoot(document.getElementById("root")).render(
            [
                <PropsVerify name="치치" favoriteNumber={ 3 } >바나나🍌랑 당근🥕</PropsVerify>,
                <PropsVerify name={ 3 }>💩</PropsVerify>
            ]
        );

    </script>
    
</body>

5) Props의 활용

<body>

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

    <script type="text/babel">

        /* UserInfo에 user객체를 props로 전달
        NameCard, AgeCard, PhoneCard, EmailCard에 각각의 값을 props로 전달
        +) user 객체를 여러 개 정의해서 여러 UserInfo를 출력 
        */

        const user1 = {
            name : '허치치🍌',
            age : 2,
            phone : '010-7272-7272',
            email : 'cheese@gmail.com'
        };
        
        const user2 = {
            name : '허꼬순🍑',
            age : 3,
            phone : '010-5656-6565',
            email : 'cheese@naver.com'
        };

        const user3 = {
            name : '허소시지🌭',
            age : 4,
            phone : '010-2342-2342',
            email : 'cheese@daum.net'
        };

        function NameCard({ name }) {

            return <h1>{ name }</h1>
        }

        function AgeCard({ age }) {

            return <h2 style={ { color : 'salmon' } }>{ age }</h2>;
        }

        function PhoneCard({ phone }) {

            return <h2 style={ { color : '#c2e3f9' } }>{ phone }</h2>;
        }

        function EmailCard({ email }) {

            return <h3 style={ { backgroundColor : '#fcec9b' } }>{ email }</h3>;
        }

        function UserInfo({ user }) { /* props로 데이터 전달 */
            
            return (
                <div style={ { width : 300, border : '1px solid black' } }>
                    <NameCard name={ user.name }/>
                    <AgeCard age={ user.age }/>
                    <PhoneCard phone={ user.phone }/>
                    <EmailCard email={ user.email } />
                </div>
            );
        }
        
        ReactDOM.createRoot(document.getElementById('root')).render(
            [
            <UserInfo user={ user1 }></UserInfo>,
            <UserInfo user={ user2 }></UserInfo>,
            <UserInfo user={ user3 }></UserInfo>
            ]
        ); 

    </script>


👀 Event

💁‍♀️ React의 이벤트 시스템
웹 브라우저의 HTML 이벤트와 인터페이스가 동일하기 때문에 사용 방법이 유사
단, 몇 가지 유의사항 존재

  • 이벤트 속성의 이름은 카멜 표기법으로 작성 (onclick 👉 onClick)
  • 이벤트에 실행할 자바스크립트의 코드를 전달하는 것이 아닌 함수 형태의 값을 전달
    HTML => <button>클릭</button>
    React => <button onClick={ ()=> alert('hello world') }>클릭</button>
  • DOM 요소에만 이벤트를 설정 가능 (컴포넌트에는 이벤트 설정 불가)
    만약 컴포넌트에 onClick 속성을 두고 이벤트 함수를 전달한다면 이벤트로 전달되는 것이 아니라 onClick이라는 props를 전달하는 것
    물론 props로 전달 된 이벤트를 내부의 DOM 이벤트로 설정할 수는 있음

1) Class형 컴포넌트를 활용한 이벤트 핸들링

<body>

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

    <script type="text/babel">

        /* 여러 개의 input 태그 이벤트 다루기 */
        class LoginComponent extends React.Component {

            state = {
                userid : '',
                userpassword : ''
            };

            onChangeHandler = (e) => {
                /* [e.target.name] : 
                   이벤트가 발생한 name속성 값을 key값으로 가져와서
                   userid와 userpassword를 이 하나의 이벤트 함수로 핸들링
                   (동일한 key 값을 가진 state 객체만 업데이트) 
                   => 대괄호 표기법을 사용하지 않으면, key값으로 인식하지 못하기 때문에 반드시 감싸줘야함
                */
                this.setState({ [e.target.name] : e.target.value });
            };

            onClickHandler = (e) => {
                alert(`유저의 id : ${this.state.userid}\n유저의 password : ${this.state.userpassword}`);
                /* input박스 안에 있는 입력값 초기화 */
                this.setState( { userid : '', userpassword : '' } )
            }

            render() {

                return (
                    <div>
                        <h1>로그인</h1>
                        <label>아이디 : </label>
                        <input
                            type="text"
                            name="userid"    
                            placeholder="아이디 입력 :)"
                            value={ this.state.userid }
                            onChange={ this.onChangeHandler }
                        />
                        <br/>
                        <label>비밀번호 : </label>
                        <input
                            type="password"
                            name="userpassword"
                            placeholder="비밀번호 입력 :)"
                            value={ this.state.userpassword }
                            onChange={ this.onChangeHandler }
                        />
                        <br/>
                        <button
                            onClick={ this.onClickHandler }
                        >
                            Log In
                        </button>
                    </div>
                );

            }
        }

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

    </script>
    
</body>

2) Function형 컴포넌트를 활용한 이벤트 핸들링

<body>

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

    <script type="text/babel">

        const { useState } =  React;

        function LoginComponent() {

            const [userid, setUserid] = useState('');
            const [userpassword, setUserpassword] = useState('');

            const onChangeUserid = e => setUserid(e.target.value);
            const onChangeUserpassword = e => setUserpassword(e.target.value);
            
            const onClickHandler = () => {
                alert(`유저의 id 👉 ${userid}\n유저의 password 👉 ${userpassword}`);
                /* input박스 안에 있는 입력값 초기화 */
                setUserid('');
                setUserpassword('');
            }

            return (
                    <div>
                        <h1>로그인</h1>
                        <label>아이디 : </label>
                        <input
                            type="text"
                            name="userid"    
                            placeholder="아이디 입력 :)"
                            value={ userid }
                            onChange={ onChangeUserid }
                        />
                        <br/>
                        <label>비밀번호 : </label>
                        <input
                            type="password"
                            name="userpassword"
                            placeholder="비밀번호 입력 :)"
                            value={ userpassword }
                            onChange={ onChangeUserpassword }
                        />
                        <br/>
                        <button
                            onClick={ onClickHandler }
                        >
                            Log In
                        </button>
                    </div>
                );
        }

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

    </script>
    
</body>

3) 한번에 다중 input 태그 이벤트 핸들링

<body>

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

    <script type="text/babel">

        const { useState } =  React;

        function LoginComponent() {

            /* 1. 한번에 다루기 위해 객체 단위로 userid, userpassword를 속성에 넣고 form이라는 하나의 단위로 다룸 */
            const [form, setForm] = useState({
                userid : '',
                userpassword: ''
            });

            /* 2. form을 다시 구조분해할당 */
            const { userid, userpassword } = form;

            const onChangeHandler = e => {

                /* userid 또는 userpassword의 값이 변경될 때 동작하므로 form 객체에 전달할 객체에는
                   2개의 값이 모두 존재해야함 */
                const changedForm = {
                    ...form,    // 스프레드 연산자를 이용하여 기존 form 객체 복사
                    [e.target.name] : e.target.value    // 이벤트가 발생한 값만 덮어쓰기
                };

                setForm(changedForm);
            };
            
            const onClickHandler = () => {
                alert(`유저의 id 👉 ${userid}\n유저의 password 👉 ${userpassword}`);
                /* input박스 안에 있는 입력값 초기화 */
                setForm({
                    userid : '',
                    userpassword: ''
                });
            }

            /* 엔터키 입력 시 로그인 버튼을 클릭한 것과 동일한 이벤트 발생하도록 하는 핸들러 작성 */
            const onKeyPressHandler = e => {
                if(e.key === 'Enter') onClickHandler(); // : Enter를 누르면 해당 이벤트 호출
            }
            

            return (
                    <div>
                        <h1>로그인</h1>
                        <label>아이디 : </label>
                        <input
                            type="text"
                            name="userid"    
                            placeholder="아이디 입력 :)"
                            value={ userid } /* 위에서 '2. form을 다시 구조분해할당'을 했으므로 form.userid가 아닌 userid로도 가능해짐 */
                            onChange={ onChangeHandler }
                            onKeyPress={ onKeyPressHandler }
                        />
                        <br/>
                        <label>비밀번호 : </label>
                        <input
                            type="password"
                            name="userpassword"
                            placeholder="비밀번호 입력 :)"
                            value={ userpassword }
                            onChange={ onChangeHandler }
                            onKeyPress={ onKeyPressHandler }
                        />
                        <br/>
                        <button
                            onClick={ onClickHandler }
                        >
                            Log In
                        </button>
                    </div>
                );
        }

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

    </script>
    
</body>


👀 Iteration

💁‍♀️ Key란,
반복적인 컴포넌트 렌더링을 할 때 필요한 식별자 역할의 요소

  • 리액트에서 key는 컴포넌트 배열 렌더링 시 어떤 요소에 변동이 있는지 알아보기 위한 식별자의 역할을 함
  • key가 존재하지 않을 때는 가상 DOM과 원본 DOM 비교 과정에서 리스트를 순차적으로 다 비교하지만, key가 존재하면 어떤 변화가 일어났는지 더 빠르게 감지 가능
  • 인덱스를 이용하여 key값을 설정하는 것은 옳지 못 한 방식
    인덱스는 요소에 변화가 생기더라도 항목의 순서에 따라 재부여 되기 때문에 요소를 식별하지 못 함
    일반적으로는 DB에서 조회한 데이터인 경우 PK컬럼 값을 key로 설정하면 됨

1) 고차함수 map()을 활용한 Iteration

<body>

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

    <script type="text/babel">

        const names = ['치즈', '멈머', '강냉', '꼬순', '치치'];

        function Items({ names }) {

            return (
                <ul>
                    {/* 배열을 map()으로 하나하나 접근하여 name을 <li>형태로 가공해서 반환!! */}
                    {/* li마다 index를 key값으로 부여하면 브라우저 콘솔의 오류 사라짐 */}
                    { names.map((name, index) => <li key={ index }>{ name }</li>) } 
                </ul>
            );

        }

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

    </script>
    
</body>

2) List형 컴포넌트의 활용

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

    <script type="text/babel">

        const { useState } = React;

        function App() {

            /* 관리하고자 하는 값들을 useState()로 관리 */
            const [names, setNames] = useState(
                [
                    { id: 1, name : '허치치' },
                    { id: 2, name : '허소시지' },
                    { id: 3, name : '허멈머' }
                ]
            );
            const [inputText, setInputText] = useState('');
            const [nextId, setNextId] = useState(4);


            /* input박스 내에 변화가 생길 경우 호출될 이벤트 함수 */
            const onChangeHandler = e => setInputText(e.target.value);

            /* ADD 버튼을 클릭 시 아래의 리스트에 추가 될 input 값 */
            const onClickHandler = () => {
                /* 새로운 names 배열을 생성 */
                const changedNames = names.concat({
                    id : nextId,
                    name : inputText
                });
                
                setNextId(nextId + 1);  // nextId는 추가할 때마다 1씩 커져야하므로 핸들링
                setNames(changedNames); // 새로 입력된 input값
                setInputText('');       // input박스 입력값 초기화
            };
            
            /* 리스트에 있는 이름을 더블 클릭 시 삭제되도록 하는 이벤트 함수 */
            const onRemove = id => {
                /* filter(item => item.id !== id) : id를 제외한 배열을 반환받음 */
                const changedNames = names.filter(item => item.id !== id);
                setNames(changedNames); // 
            }

            const nameList = names.map(item => <li key={ item.id } onDoubleClick={ ()=> onRemove(item.id) }>{ item.name }</li>);

            return (
                <>
                    <input value={ inputText } onChange={ onChangeHandler } />
                    <button onClick={ onClickHandler }>ADD</button>
                    <ul>{ nameList }</ul>
                </>
            );
        }


    ReactDOM.createRoot(document.getElementById("root")).render(<App />);

    </script>

</body>

3) To-do List 만들기

map() filter()

[1] way 1. 나의 풀이

<body>
    <div id="root"></div>
    <script type="text/babel">

        const { useState } = React;

        /* [컴포넌트] Header */
        function Header() {

            return (
                <div className="header">
                    <h1>TO DO LIST</h1>
                    <h2> ({ new Date().toLocaleDateString() }) </h2>    
                </div>
            );
        }

        /* [컴포넌트] Body */
        function Content() {

            return (
                <div className="content">
                    <TodoList/>
                </div>
            );

        }

        /* [컴포넌트] To-do List 영역 */
        function TodoList() {

            const [todos, setTodos] = useState([
                {id: 1, description: 'React 복습', isDone: false}
            ]); 
            const [inputText, setInputText] = useState('');
            const [nextId, setNextId] = useState(2);

            /* input박스 내에 변화가 생길 경우 호출될 이벤트 함수 */
            const onChangeHandler = (e) => {
                setInputText(e.target.value)
            }

            /* ADD 버튼을 클릭 시 입력된 값을 기존에 존재하는 todos 뒤에 더하기 */
            const onClickHandler = () => {
                /* 입력된 값과 새로운 id를 todos 배열에 추가 */
                const changedTodos = todos.concat({
                    id : nextId,
                    description : inputText
                });

                setNextId(nextId + 1);  // nextId는 추가할 때마다 1씩 커져야하므로 핸들링
                setTodos(changedTodos); // 새로 입력된 input값
                setInputText('');       // input박스 입력값 초기화
            }

            /* 엔터키 입력 시 ADD 버튼을 클릭한 것과 동일한 이벤트 발생하도록 하는 핸들러 작성 */
            const onKeyPressHandler = e => {
                if(e.key === 'Enter') onClickHandler(); // : Enter를 누르면 해당 이벤트 호출
            }

            return (
                <>
                    <h3>To-Do-List</h3>
                    <div className="todo-list">
                        <TodoItems 
                            todos={ todos } 
                            setTodos={ setTodos }
                        />
                    </div>
                    <div className="append-list">
                        <input 
                            type="text" 
                            value={ inputText } 
                            onChange={ onChangeHandler }
                            onKeyPress={ onKeyPressHandler }
                        />
                        <button onClick={ onClickHandler }>ADD</button>
                    </div>
                </>
            );
        }

        /* [컴포넌트] To-do List 중 list 요소 하나하나 */
        function TodoItems({ todos, setTodos }) {

            /* REMOVE를 클릭 시, 리스트에 있는 할 일이 삭제되도록 하는 이벤트 함수 */
            const removeTodo = (id) => {
                /* filter(item => item.id !== id) : id를 제외한 배열을 반환 받음 */
                const changedTodos = todos.filter(item => item.id !== id);
                setTodos(changedTodos);
            }
            
            /* 체크박스의 상태가 변경되면 label의 isDone과 그에 따른 텍스트 상태를 바꾸는 이벤트 함수 */
            const onChangeHandler = (id) => {
                const changedTodos = todos.map(todo => {
                    if (todo.id === id) {
                        console.log(`todo.id : ${todo.id}`)
                        return { ...todo, isDone: !todo.isDone };
                    } else {
                        return todo;
                    }
                });
                setTodos(changedTodos);
            };
            
            return (
                <>
                    { 
                        todos.map(
                            todo => 
                                /* 하나의 List 조각 */
                                <p key={ todo.id }>
                                    <input
                                        type="checkbox" 
                                        id={ todo.id } 
                                        onChange={ () => onChangeHandler(todo.id) }
                                        checked={ todo.isDone }
                                    />
                                    <label 
                                        htmlFor={ todo.id } 
                                        style={ {textDecoration: todo.isDone? 'line-through': 'none', color: todo.isDone? 'lightgray' : 'black'} }
                                    >
                                        { todo.description }
                                    </label>
                                    <button onClick={ () => removeTodo(todo.id) }>REMOVE</button>
                                </p>
                        ) 
                    }
                </>
            );
        }

        /* [컴포넌트] Footer */
        function Footer() {

            return (
                <div className="footer">
                    <p>Copyright 2023. team-greedy all rights reserved.</p>
                </div>
            );
        }

        /* [컴포넌트] 실행 */
        function TodoApp() {

            return (
                <div className="container">
                    <Header/>
                    <Content/>
                    <Footer/>
                </div>
            );
        }

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

[2] way 2. 선생님 풀이

<body>
    <div id="root"></div>
    <script type="text/babel">

        const { useState } = React;

        function Header() {

            return (
                <div className="header">
                    <h1>오늘의 할일! { new Date().toLocaleDateString() }</h1>    
                </div>
            );
        }

        function TodoItems({ todos, setTodos }) {

            const removeTodo = (id) => {
                const removedList = todos.filter(todo => todo.id !== id);
                
                setTodos(removedList);
            }

            const onChangeHandler = (e) => {
                const changeIsDoneList = todos.map(
                    (todo) => {
                        
                        // if(todo.id === parseInt(e.target.id)) {
                        //     todo.isDone = e.target.checked;
                        // }

                        if(todo.id == e.target.id) {
                            todo.isDone = !todo.isDone;
                        } 

                        return todo;
                    }
                );

                setTodos(changeIsDoneList);
            }
            
            return (
                <>
                    { 
                        todos.map(
                            todo => 
                                <p key={ todo.id }>
                                    <input 
                                        type="checkbox" 
                                        id={ todo.id } 
                                        onChange={ onChangeHandler }
                                    />
                                    <label 
                                        htmlFor={ todo.id } 
                                        style={ {textDecoration: todo.isDone? 'line-through': 'none'} }
                                    >
                                        { todo.description }
                                    </label>
                                    <button onClick={ () => removeTodo(todo.id) }>x</button>
                                </p>
                        ) 
                    }
                </>
            );
        }

        function TodoList() {

            const [todos, setTodos] = useState([
                {id: 1, description: '할일 목록을 추가할 것', isDone: false}
            ]); 
            const [inputText, setInputText] = useState('');
            const [nextId, setNextId] = useState(2);

            const onChangeHandler = (e) => {
                setInputText(e.target.value);
            }

            const onClickHandler = () => {
                const changeTodos = todos.concat({
                    id: nextId,
                    description: inputText,
                    inDone: false
                });

                console.log(changeTodos)
                setInputText('');
                setNextId(nextId + 1);
                setTodos(changeTodos);
            }

            return (
                <>
                    <h3>ToDo-List</h3>
                    <div className="todo-list">
                        <TodoItems 
                            todos={ todos } 
                            setTodos={ setTodos }
                        />
                    </div>
                    <div className="append-list">
                        <input 
                            type="text" 
                            value={ inputText } 
                            onChange={ onChangeHandler }
                        />
                        <button onClick={ onClickHandler }>추가하기</button>
                    </div>
                </>
            );
        }

        function Content() {

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

        function Footer() {

            return (
                <div className="footer">
                    <p>Copyright 2022. team-greedy all rights reserved.</p>
                </div>
            );
        }

        function TodoApp() {

            return (
                <div className="container">
                    <Header/>
                    <Content/>
                    <Footer/>
                </div>
            );
        }

        ReactDOM.createRoot(document.getElementById('root')).render(
            <TodoApp />
        );
    </script>
</body>
profile
Tiny little habits make me

0개의 댓글