본문 바로가기
포스코x코딩온

[포스코x코딩온] 풀스택 부트캠프 11주차 정리 -1 (React - Component, State, useState, EventHandling)

by 김선지 2024. 1. 3.

프론트엔드 라이브러리

Angular JS

구글에서 만든 JS기반 오픈 소스 프레임워크, 양방향 데이터 바인딩이다.

 

React JS

동적 사용자 인터페이스를 만들기 위해 페이스북에서 만든 오픈 소스 라이브러리이다.

데이터 변경이 잦고 규모가 큰 라이브러리에 적합하다.

 

Vue.js

화면을 만들기 위한 자바스크립트 프레임워크로로 angular와 react의 자엄을 수용한 프레임워크다.

중국 회사에서 만들어서 중국어 기반이다. 

 

여기서 가장 많이 활용되고 있고 사용자와의 상호 작용이 가능한 동적 UI를 제작하는 React를 공부한다.

이게 리액트다


특징

1) 단방향으로 데이터가 흐른다.

     -바인딩을 통해 양 쪽의 데이터를 동시에 바꿀 수 있는 Vue와 Angular와는 달리, 부모에서 자식으로의 데이터 흐름으로 이루어져있다.

      (양방향 데이터 바인딩?  => -- 자동 동기화 --
- 양방향 데이터 바인딩은 데이터의 변경을 프레임워크에서 감지하고 있다가, 데이터가 변경되는 시점에 DOM 객체에 렌더링을 해주는 것 -
)

2) Component 기반 구조

   - UI를 여러 Component를 쪼개서 만든다. ejs처럼 import 해서 만들 수 있다.

 

3) Virtual DOM

   - DOM Tree와 같은 구조를 Virtual DOM형태로 가지고 있다. 이벤트가 발생할 때마다 Virtual DOM을 만들고 실제 DOM과 다른 변경사항만 실제 DOM에 반영해서 효율성을 개선한다.

 

4) Props and State

  - Prop: 부모 컴포넌트에서 자식 컴포넌트로 전달해주는 데이터 (ejs문법으로  import할 때 {객체 값으로} 같이 주는 것과 동일)

  - State: 컴포넌트 내부에서 선언되고 내부에서 값을 변경할 수 있는 데이터 (class의 property나 지역변수 느낌으로 생각하면 될 듯 하다.)

 

5) JSX

  - JS와 SML의 합성으로서 JS를 확장해서 React Element를 생성할 수 있는 문법이다.

 

react 프로젝트는 아래와 같은 명령어로 CMD에서 만들어주면 된다. 대문자는 사용 불가능하고 단어 여러 개 사용시 -로 구분한다.

npm create-react-app appName

 


Component의 종류

둘 다 PascalCase를 사용한다.

 

함수형 Component 

  - 요즘 쓰는 문법, hooks의 등장으로 함수형 컴포넌트도 state, lifecycle 등을 이용할 수 있게 되었음.

 

import PropTypes from 'prop-types';
// prop-types 라이브러리를 PropTypes 이름으로 import

// function FuncComponent(props) {
function FuncComponent({name}) {
    // 아래 처럼 해도 되고, 위처럼 함수에 인자로 넣고 구조분해할당 해도 된다.
    // const {name} = props;
    return (
        <div>
            <h1>Hi there!</h1>
            <p>새로운 컴포넌트의 이름은 <b>{name}</b></p>
            {/* <p>새로운 컴포넌트의 이름은 <b>{props.name}</b></p> */}
        </div>
    )
}

FuncComponent.defaultProps = {
    name: '홍길동'
}

//  name의 타입을 string으로 제한같은 느낌. 오류는 안나는데 타입이 안맞으면 콘솔로 띄워준다.
FuncComponent.propTypes = {
    name: PropTypes.string
}

export default FuncComponent;

클래스형 Component

 - 예전문법, State와 라이프사이클 기능 사용 가능, Render함수 필요, 기존 Component 클래스에서 상속받아 사용해야함

 

import { Component } from "react";
import { PropTypes } from "prop-types";
// import PropTypes from 'prop-types'; 와 동일하다.
// prop-types 라이브러리를 PropTypes 이름으로 import
// 라이브러리 안에 PropTypes라는 값도 있고, default 값도 PropTypes로 되어있기 때문에

// ClassComponent 라는 이름의 컴포넌트
class ClassComponent extends Component {
  // 클래스형 컴포넌트는 render 함수가 필수
  render() {
    const {name} = this.props;
    return (
      <div>
        <p>
          새로운 컴포넌트의 이름은 <b>{name}</b>
          <br />
          새로운 컴포넌트의 이름은 <b>{this.props.name}</b>
        </p>
      </div>
    );
  }
}

ClassComponent.defaultProps = {
    name: 'coding'
}

ClassComponent.propTypes = {
    name: PropTypes.number
}

export default ClassComponent;

 


JSX 문법

1. 하나의 Component는 반드시 return 이후에 하나의  부모 요소가 전체 요소를 감싸는 형태로 작성해야 한다.

 여러 개의 태그가 있을 경우 return()의 형태로 작성한다.

2. return 이후의 html 요소를 작성하다가 js문법을 사용할 경우에는 {}로 감싸야한다. (if, while 불가, 삼항 연산자 나 논리 연산자를 이용해야함.)

3. input 같은 opening tag도 닫아줘야한다.

 


Props


properties를 줄인 표현으로 컴포넌트 속성을 설정할 때 쓰는 요소

ejs에서 import를 해줄 때 옆에서 전달해주는 객체와 비슷한 느낌이다.

함수형 컴포넌트에서 이용할 때

FuncComponent.js

import PropTypes from 'prop-types';
// prop-types 라이브러리에서 default로 설정되어 있는 PropTypes를 import


// function FuncComponent(props) {
// 구조분해 할당해서 가져오기 (name만)
function FuncComponent({name}) {
    // const {name} = props;
    return (
        <div>
            <h1>Hi there!</h1>
            <p>새로운 컴포넌트의 이름은 <b>{name}</b></p>
            // argument에 props가 들어갔다면 아래처럼 그대로 불러와도 가능
            {/* <p>새로운 컴포넌트의 이름은 <b>{props.name}</b></p> */}
        </div>
    )
}

FuncComponent.defaultProps = {
    name: '홍길동'
}

FuncComponent.propTypes = {
    name: PropTypes.string
}

export default FuncComponent;

 

App.js

function App() {
  
  return (
    <div className="App">
      <FuncComponent name='코딩'/>
      <FuncComponent />
    </div>
  );
}

export default App;

 


State

React에서 앱의 유동적인 데이터를 다루기 위한 개체로 계속해서 변하는 특정 상태

변수와는 달리 State가 변경될 시 자동으로 재랜더링 된다.

axios같은 동적 form 전송 시에 이용하는 용도로 만들어진 것 같다.

 

Props

부모 컴포넌트에서 자식 컴포넌트에 데이터 전달시 사용
(변경 불가능)
State

특정 컴포넌트가 갖는 상태(값)
컴포넌트 내부에서 선언되고 내부에서 값을 변경함
(변경할 때 직접 state값을 변경하는 것이 아닌 setState나 useState 등을 이용해야함)
*** setState는 비동기적으로 동작하므로(),
setState를 해서 state가 바뀌었다고 생각하지 말고, 이전 상태를 기반으로 로직을 짜야한다. 아래 chatGPT참조

이거 주의하자

Function Component (useState사용)

import React, { useState } from "react";

const CounterFunc = () => {
    const [number, setNumber] = useState(0);
    const onClickEnter = () => {
        setNumber(number + 1);
    }

    return (
        <div>
            <h1>CounterFunc {number}</h1>
            <button onClick={function() {
                setNumber(number + 1);
            }}>
                Plus 1
            </button>
            <button onClick={onClickEnter}>
                Plus 1
            </button>
        </div>
    );
}

export default CounterFunc;

 

Class Component사용 (바인딩의 존재를 알기 전 오류해결하겠답시고 this를 야매로 해결해봤다.)

import React, { Component } from 'react';

class Counter extends Component {
    state = {
        // number 초기값: 0 
        number: 0
     } 
    render() { 
        // state 데이터는 this.state로 접근 가능 = this.state.number
        const { number } = this.state;
        // 그냥 야매로 thi라는 변수를 만들었다. 근데 이게 되네,,,
        const thi = this;
        return (
            <div>
                <h1>CounterClass {number}</h1>
                {/* this.setState(): state 값을 바꾸는 함수 */}
                {/* state 값을 직접 변경 불가능 */}
                <button onClick={function () {
                    thi.setState({number: number+1})
                    // 그냥 function을 만들면 this가 해당 함수의 this가 된다.
                    // 그래서 method binding을 하던가 화살표 함수로 만들어서 this가 화살표 함수가 아닌 가장 가까운 함수의 this가 되도록 해야한다.
                    // 이건 야매로 한것.
                    }}>
                    +1
                </button>
            </div>
        );
    }
}
 
export default Counter ;

EventHandler

말 그대로 이벤트 핸들러다.

class형 Component의 경우 class의 this와 eventHandler의 this가 겹치기 때문에 Class의 this를 이용하기 위해 Component 바인딩을 별도로 해주거나 화살표 함수를 이용하여 해줘야한다..

그리고 이벤트를 정의하는 함수를 만들 때 function으로 정의해서 오류가 계속 떴는데, 알고보니 JS의 클래스 문법 자체가 클래스의 메소드를 만들 때는 function으로 정의하지 않고 바로 함수를 정의하는 거였다.

지금까지 클래스랑 메소드 많이 만들었는데... 새삼스럽지만 지금까지 난 뭘 한거지...?

import React, { Component } from "react";

class ClassBind extends Component {
  state = {
    name: "codingon",
  };
  // # 클래스 컴포넌트에서 이벤트 쓰기 - 화살표 함수 사용
  printConsole = () => {
    console.log('this', this);
    console.log('state', this.state);
  }

  // # 클래스 컴포넌트에서 이벤트 쓰기 - bind사용 (render 안에서 bind)

  printConsole2() {
    console.log('this', this);
    console.log('state', this.state);
  }

  // 인자 전달하는 경우
  printConsole3 = (msg) => {
    console.log('msg:', msg);
    console.log('this:', this);
    console.log('state:', this.state);
  }

  render() {
    return <div>
        <h1>Class Component Event</h1>
        <button onClick={this.printConsole}>PrintConsole(인자x)</button>
        <button onClick={this.printConsole2.bind(this)}>PrintConsole2(인자x)</button>
        <button onClick={() => this.printConsole3('안녕')}>PrintConsole3(인자o)</button>
    </div>;
  }
}

export default ClassBind;

 

Function Component의 경우

import React, { useState } from 'react';

function Exercise3() {
    const [btnVal, setBtnVal] = useState(true);
    const [btnText, setBtnText] = useState("사라져라");

    // 비동기로 처리하고 async await,  setTimeOut을 써도 React에서 상태 업데이트는
    //항상 비동기적으로 이루어지며, React는 자체적으로 상태 업데이트를 일괄 처리(batch)합니다.
    // 그래서 set함수를 쓴다고 해도 바뀌었다고 생각하지 말고 나중에 바뀐다고 생각하며
    //코드를 진행하는 것이 정신 건강에 이롭다.
    // 결과적으로 둘의 결과는 척 보면 같아야 하지만 정 반대의 결과가 나온다.
    // why? if 절에서 btnVal의 값은 setBtnVal(!btnVal);가 일어나기 전의 값이니까.
    
    // 주석처리한 함수는 동기적이었다면 맞겠지만, set함수는 batch로 처리하는 비동기함수이다.
    // function disappear() {
    //     setBtnVal(!btnVal);
    //     if (btnVal) {
    //         setBtnText('사라져라');
    //     }else {
    //         setBtnText('보여라');
    //     }
    // }

    function disappear() {
        if (btnVal) {
            setBtnText('보여라');
        } else {
            setBtnText('사라져라');
        }
        setBtnVal(!btnVal);
    }


    return (
        <div>
            <button onClick={disappear}>{btnText}</button>
            {btnVal && <h2>안녕하세요</h2>}
        </div>
    );
}

export default Exercise3;

 

이거 오류 이해하는 데 힘들었다.

async await, setTimeOut도 해보고 콜백도 쓰고 별 수를 다써봤는데 안됐다.

그래서 gpt에게 물어봤다. 답변은 이거였다.

 

결국 하지 말란다. 그러니까 State의 set 함수는 추후 처리된다고 생각하고 (이 로직이 실행되는 동안은 변경되지 않는다고) 로직을 짜면 큰 문제는 되지 않을 것 같다.