쌩로그

리액트를 다루는 기술 - 03. 컴포넌트 본문

Front/리액트를 다루는 기술

리액트를 다루는 기술 - 03. 컴포넌트

.쌩수. 2024. 4. 2. 12:58
반응형

목록

  1. 포스팅 개요
  2. 본론
      2-1. 클래스형 컴포넌트
      2-2. 첫 컴포넌트 생성
      2-3. props
      2-4. state
      2-5. state를 사용할 때 주의 사항
  3. 요약

1. 포스팅 개요

해당 포스팅은 리액트를 다루는 기술03장 컴포넌트 를 학습하며 정리한 포스팅이다.

2. 본론

  • 컴포넌트의 기능은 단순한 템플릿 이상이다.

  • 데이터가 주어졌을 때 이에 맞추어 UI를 만들어 준다.

  • 라이프 사이클 API를 이용하여 컴포넌트가 화면에서 나타날 때, 사라질 때 등 변화가 일어날 때 주어진 작업들을 처리할 수 있다.

  • 임의 메서드를 만들어 특별한 기능을 붙여줄 수 있다.

    2-1. 클래스형 컴포넌트

  • 컴포넌트를 선언하는 방식은 두 가지이다.

    • 함수형 컴포넌트.
    • 클래스형 컴포넌트
import '../ClassInsteaedClassNameApp.css'  
import {Component} from "react";  


class ClassionalComponent extends Component {  
    render() {  
        const name = 'react';  
        return <div className="react">{name}</div>  
    }  
}  
export default ClassionalComponent;

클래스형 컴포넌트와 함수형 컴포넌트의 차이점

  • 클래스형 컴포넌트의 경우 state 기능 및 라이프사이클 기능을 사용할 수 있다.
  • 임의 메서드를 정의할 수 있다.
  • 클래스형 컴포넌트에서는 render 함수가 꼭 있어야 한다.
    • 이 안에서는 보여주어야할 JSX를 반환해야 한다.

함수형 컴포넌트의 장점

  • 클래스형 컴포넌트보다 선언하기 훨씬 편하다.
  • 메모리 자원 사용이 클래스형 컴포넌트보다 덜 하다.
  • 프로젝트를 완성하여 빌드한 후 배포할 때 함수형 컴포넌트를 사용하는 것이 결과물의 파일 크기가 더 작다.
  • (번외) 성능과 파일 크기 면에서는 사실상 별 차이가 없다.

함수형 컴포넌트의 단점

  • state와 라이프 사이클 API의 사용이 불가능하다.
    • 리액트 v16.8 업데이트 이후 Hooks라는 기능이 도입되면서 해결되었다.
    • Hook은 8장에서 나온다.
    • 리액트 공식 매뉴얼에서는 컴포넌트를 새로 작성할 때 함수형 컴포넌트와 Hook를 사용하도록 권장하고 있다.

2-2. 첫 컴포넌트 생성

  • ES6의 화살표 함수

    • ES6 문법에서 함수를 표현하는 새로운 방식

    • 주로 함수를 파라미터로 전달할 때 유용하다.

    • 기존의 function 문법과 대체할 수 없는 것은 용도가 다르다.

      • 서로 가리키는 this 값이 다르다.
    • 일반함수(function)은 자신이 종속된 객체를 this로 가리킨다.

    • 화살표 함수는 자신이 종속된 인스턴스를 가리킨다.

    • 화살표 함수는 값을 연산하여 바로 반환해야 할 때 사용하면 가독성을 높일 수 있다.

      function twice(value) {
        return value * 2;
      }
      const triple = (value) => value * 3;*
  • 함수형 컴포넌트를 선언할 때 function 키워드를 사용하는 것과 화살표 함수 문법을 사용하는 것 간에는 큰 차이가 없다.

    • 화살표 함수를 사용하는 것이 좀 더 간결하다.
      • 때문에 책에서는 함수형 컴포넌트를 만들 때 화살표 함수 문법을 사용한다.
  • 모듈 내보내기(export)

    • 모듈 내보내는 방법은 다음과 같다.
export default MyComponent;
  • 모듈 불러오기(import)
    • 모듈을 불러오는 방법은 다음과 같다.
import React from 'react';

...

2-3. props

  • propsproperties를 줄인 표현으로 컴포넌트 속성을 설정할 때 사용하는 요소이다.

  • props는 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있다.

  • props 값은 컴포넌트 함수의 파라미터로 받아 와서 사용할 수 있다.

    • 렌더링시에는 JSX 내부에서 { } 기호로 감싸주면 된다.

props 기본값 설정 : defaultProps

import '../ClassInsteaedClassNameApp.css'   // 모듈 불러오기  

const PropsFunctionalComponent = props => {  
    return <div>안녕하세요 제 이름은 {props.name}입니다.</div>;  
};  

PropsFunctionalComponent.defaultProps = {  
    name: '기본 이름'  
}  


export default PropsFunctionalComponent; // 모듈 내보내기

태그 사이의 내용을 보여주는 children

const root = ReactDOM.createRoot(document.getElementById('root'));  
root.render(  
  <React.StrictMode>  
      <ChildrenComponent>리액트</ChildrenComponent>  
  </React.StrictMode>);
const root = ReactDOM.createRoot(document.getElementById('root'));  
root.render(  
  <React.StrictMode>  
      <ChildrenComponent>리액트</ChildrenComponent>  
  </React.StrictMode>);

비구조화 할당 문법을 통해 props 내부 값 추출하기

import React from "react";  

const DestructuringAssignmentComponent = props => {  
    const {name, children} = props;  
    return (  
        <div>  
            안녕하세요, 제 이름은 {name}입니다. <br/>  
            children 값은 {children}  
            입니다.  
        </div>  
    );  
};  

DestructuringAssignmentComponent.defaultProps = {  
    name: '기본 이름',  
}  


export default DestructuringAssignmentComponent;

위의 코드를 더 간단히

import React from "react";  

// 이 부분이 바뀜.
// const DestructuringAssignmentComponent = props => {  
//     const {name, children} = props;  
const DestructuringAssignmentComponent = ({name, children}) => {  
    return (  
        <div>  
            안녕하세요, 제 이름은 {name}입니다. <br/>  
            children 값은 {children}  
            입니다.  
        </div>  
    );  
};  

DestructuringAssignmentComponent.defaultProps = {  
    name: '기본 이름',  
}  


export default DestructuringAssignmentComponent;

propTypes를 통한 props 검증

  • 컴포넌트의 필수 props를 지정하거나 props의 타입(type)을 지정할 때는 propTypes를 사용한다.
  • 지정하는 방법은 defaultProps를 설정하는 것과 비슷하다.
  • 사용하려면 상단에 import로 불러와야 한다.
import React from "react";  
import PropTypes from "prop-types";  

const PropTypesComponent = ({name, children}) => {  
    return (...);  
}

사용 예시

import React from "react";  
import PropTypes from "prop-types";  

const PropTypesComponent = ({name, children}) => {  
    return (  
        <div>  
            안녕하세요, 제 이름은 {name}입니다. <br/>  
            children 값은 {children}  
            입니다.  
        </div>  
    );  
};  

PropTypesComponent.defaultProps = {  
    name: '기본 이름',  
}  

PropTypesComponent.propTypes = {  
    name: PropTypes.string,  
}  

export default PropTypesComponent;
  • 위와 같이 설정해주면 name 값은 무조건 문자열(string) 형태로 전달해야 된다.
const root = ReactDOM.createRoot(document.getElementById('root'));  
root.render(  
  <React.StrictMode>  
      <PropTypesComponent name={3}>리액트</PropTypesComponent>  
  </React.StrictMode>);

이렇게 해서 name에 숫자를 주면 다음과 같은 에러가 발생한다.

name을 아래와 같이 주면 오류가 나오지 않는다.

name={"3"} // or
name="React"

isRequired를 사용하여 필수 propTypes 설정

  • propTypes를 지정하지 않았을 때 isRequired를 사용하여 경고 메세지를 띄워줄 수 있다.
const root = ReactDOM.createRoot(document.getElementById('root'));  
root.render(  
  <React.StrictMode>  
      <IsRequiredComponent name={"3"}>리액트</IsRequiredComponent>  
  </React.StrictMode>);
import PropTypes from "prop-types";  

const IsRequiredComponent = ({name, favoriteNumber, children}) => {  
    return (  
        <div>  
            안녕하세요, 제 이름은 {name}입니다. <br/>  
            children 값은 {children}  
            입니다.  
            <br/>  
            제가 좋아하는 숫자는 {favoriteNumber}입니다.  
        </div>  
    );  
};  

IsRequiredComponent.defaultProps = {  
    name: '기본 이름',  
}  

IsRequiredComponent.propTypes = {  
    name: PropTypes.string,  
    favoriteNumber: PropTypes.number.isRequired,  
}  

export default IsRequiredComponent;

현재 favoriteNumber를 넘기지 않고 있으므로 다음과 같은 에러를 발견할 수 있다.

따라서 다음과 같이 props를 넘겨주면 된다.

<IsRequiredComponent name="React" favoriteNumber={1}>  
    리액트  
</IsRequiredComponent>

오류 사라짐.

PropTypes 종류(참고용)

페이스북 깃허브 prop-types에서 확인 가능하다.

클래스형 컴포넌트에서 props 사용하기

import React, { Component } from "react";  
import PropTypes from "prop-types";  

class UserPropsByComponent extends Component {  
    render() {  
        const { name, favoriteNumber, children } = this.props; // 비구조화 할당  
        return (  
            <div>  
                안녕하세요, 제 이름은 {name}입니다. <br/>  
                children 값은 {children}  
                입니다.  
                <br/>  
                제가 좋아하는 숫자는 {favoriteNumber}입니다.  
            </div>  
        );  
    }  
}  

UserPropsByComponent.defaultProps = {  
    name: '기본 이름',  
};  

UserPropsByComponent.propTypes = {  
    name: PropTypes.string,  
    favoriteNumber: PropTypes.number.isRequired  
};  

export default UserPropsByComponent;

class 내부에서 설정하는 방법은 다음과 같다.

import React, { Component } from "react";  
import PropTypes from "prop-types";  

class InnerUserPropsByComponent extends Component {  
    static defaultProps = {  
        name: '기본 이름',  
    }  
    static propTypes = {  
        name: PropTypes.string,  
        favoriteNumber: PropTypes.number.isRequired  
    };  
    render() {  
        const { name, favoriteNumber, children } = this.props; // 비구조화 할당  
        return (  
            <div>  
                안녕하세요, 제 이름은 {name}입니다. <br/>  
                children 값은 {children}  
                입니다.  
                <br/>  
                제가 좋아하는 숫자는 {favoriteNumber}입니다.  
            </div>  
        );  
    }  
}  

export default InnerUserPropsByComponent;

2-4. state

  • 리액트에서 state는 컴포넌트 내부에서 바뀔수 있는 값을 의미한다.

    • props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이다.
    • 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있다.
    • props를 바꾸려면 부모 컴포넌트에서 바꾸어 주어야 한다.
      • props를 전달받은 컴포넌트에서는 값을 직접 바꿀 수 없다.
  • 리액트에는 두 종류의 state가 있다.

    • 클래스형 컴포넌트가 지니고 있는 state
    • 함수형 컴포넌트에서 useState라는 함수를 통해 사용하는 state

클래스형 컴포넌트의 state

import {Component} from "react";  

class ClassionalStateComponent extends Component {  
    constructor(props) {  
        super(props);  
        // state의 초깃갑 설정  
        this.state = {  
            number: 0  
        };  
    }  
    render() {  
        const {number} = this.state; // state를 조회할 때는 this.state로 조회한다.  
        return (  
            <div>  
                <h1>{number}</h1>  
                <button                    // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.  
                    onClick={() => {  
                        // this.setState를 사용하여 state에 새로운 값을 넣을 수 있다.  
                        this.setState({ number: number + 1})  
                    }}  
                >  
                    +1  
                </button>  
            </div>        )  
    }  
}  


export default ClassionalStateComponent;
  • 컴포넌트에 state를 설정할 때는 constructor 메서드를 작성하여 설정한다.
    • constructor 메서드는 컴포넌트의 생성자 메서드이다.
      • 클래스형 컴포넌트에서 constructor 메서드를 작성할 때는 반드시 super(props)를 호출해야 한다.
        • 이 함수가 호출되면 현재 클래스형 컴포넌트가 상속하고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해준다.
    • 이후 this.state 값에 초깃값을 설정해주었다.
    • 컴포넌트의 state는 객체 형식이어야 한다.
constructor(props) {  
        super(props);  
        // state의 초깃갑 설정  
        this.state = {  
            number: 0  
        };  
    }  

다음은 render() 함수다.

render() {  
    const {number} = this.state; // state를 조회할 때는 this.state로 조회한다.  
    return (  
        <div>  
            <h1>{number}</h1>  
            <button                // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.  
                onClick={() => {  
                    // this.setState를 사용하여 state에 새로운 값을 넣을 수 있다.  
                    this.setState({ number: number + 1})  
                }}  
            >  
                +1  
            </button>  
        </div>    )  
}
  • render 함수에서 현재 state를 조회할 때는 this.state를 조회하면 된다.
  • button안에 onClick이라는 값을 props로 넣어주었다.
    • 버튼이 클랙될 때 호출시킬 함수를 설정할 수 있게 해준다.
    • 이를 "이벤트를 설정한다"고 한다.
#### state 객체 안에 여러 값이 있을 때
import {Component} from "react";  

class ClassionalStateComponent extends Component {  
    constructor(props) {  
        super(props);  
        // state의 초깃갑 설정  
        this.state = {  
            number: 0,  
            fixedNumber: 0  
        };  
    }  
    render() {  
        const {number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회한다.  
        return (  
            <div>  
                <h1>{number}</h1>  
                <h2>바뀌지 않는 값 : {fixedNumber}</h2>  
                <button                    // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.  
                    onClick={() => {  
                        // this.setState를 사용하여 state에 새로운 값을 넣을 수 있다.  
                        this.setState({ number: number + 1})  
                    }}  
                >  
                    +1  
                </button>  
            </div>        )  
    }  
}  

export default ClassionalStateComponent;

onClick 함수에 fixedNumber 를 주진 않았다.

this.setState 함수는 인자로 전달된 객체 안에 들어있는 값만 바꿔준다.

state를 constructor에서 꺼내기

이전에 초깃값을 지정하기 위해 constructor 메서드를 선언했다.
다른 방식으로도 초깃값을 설정할 수 있다.

import {Component} from "react";  

class ConstructorComponent extends Component {  

    state = {  
        number: 0,  
        fixedNumber: 0  
    };  
    render() {  
        const {number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회한다.  
        return (  
            <div>  
                <h1>{number}</h1>  
                <h2>바뀌지 않는 값 : {fixedNumber}</h2>  
                <button                    // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.  
                    onClick={() => {  
                        // this.setState를 사용하여 state에 새로운 값을 넣을 수 있다.  
                        this.setState({ number: number + 1})  
                    }}  
                >  
                    +1  
                </button>  
            </div>        )  
    }  
}  

export default ConstructorComponent;

(결과는 동일하다.)
앞으론 constructor가 아닌 state를 사용할 때 이 방식을 이용한다.

this.setState에 객체 대신 함수 인자 전달하기

  • this.setState를 사용하여 state 값을 업데이트할 때는 상태가 비동기적으로 업데이트된다.

그래서 다음과 같이 onClick() 내부에 코드를 this.setState를 두 번 호출하더라도 숫자는 1씩 더해진다.

onClick={() => {  
    this.setState({ number: number + 1})  
    this.setState({number: this.state.number + 1})  
}}

this.setState를 사용한다고 해서 state 값이 바로 바뀌지 않는다.

해결책this.setState를 사용할 때 객체 대신에 함수를 인자로 넣어주는 것이다.

this.setState((prevState, props) => {
    return {
        // 업데이트하고 싶은 내용
    }
})
  • prevState : 기존상태
  • props : 현재 지니고 있는 props

업데이트 과정에서 props가 필요하지 않다면 생략해도 된다.
다음과 같이 사용할 수 있다.

onClick={() => {  
    this.setState(prevState => {  
        return {  
            number: prevState.number + 1  
        };  
    });  
    // 위의 코드와 아래 코드는 완전히 똑같은 기능을 한다.  
    // 아래 코드는 함수에서 바로 객체를 반환한다는 의미(return 문 생략) 
    this.setState(prevState => ({  
        number: prevState.number + 1  
    }));  
}}
  • 화살표 함수에서 값을 바로 반환하고 싶다면 코드 블록 { }을 생략하면 된다.
  • onClick에서 두 번째로 this.setState 함수를 사용할 때는 화살표 함수에서 바로 객체를 반환하도록 했기 때문에 prevState => ({ }) 와 같은 형태로 코드가 이루어진다.

this.setState가 끝난 후 특정 작업 실행하기

setState를 사용하여 값을 업데이트하고 난 다음에 특정 작업을 하고 싶을 때setState의 두 번째 파라미터로 콜백(callback) 함수를 등록하여 작업을 처리할 수 있다.

import {Component} from "react";  

class CallbackComponent extends Component {  

    state = {  
        number: 0,  
        fixedNumber: 0  
    };  
    render() {  
        const {number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회한다.  
        return (  
            <div>  
                <h1>{number}</h1>  
                <h2>바뀌지 않는 값 : {fixedNumber}</h2>  
                <button                    onClick={() => {  
                        this.setState(  
                            {  
                                number: number + 1  
                            },  
                            () => {  
                                console.log('방금 setState가 호출되었습니다.');  
                                console.log(this.state);  
                            }  
                        )  
                    }}  
                >  
                    +1  
                </button>  
            </div>        )  
    }  
}  

export default CallbackComponent;

함수형 컴포넌트에서 useState 사용하기

  • 리액트 16.8 이전에는 함수형 컴포넌트에서 state를 사용할 수 없었다.
  • 16.8 이후부터 useState 함수를 사용하여 함수형 컴포넌트에서 state를 사용할 수 있게 되었다.
    • 해당 장에서는 useState만 배워본다.

배열 비구조화 할당

const array = [1, 2];
const one = array[0];
const two = array[1];

위의 코드는 아래와 같이 할 수 있다.

const array = [1, 2];
const [one, two] = array;

useState 사용하기

import {useState} from "react";  

const SayComponent = () => {  
    const [message, setMessage] = useState('');  
    const onClickEnter = () => setMessage('안녕하세요!');  
    const onClickLeave = () => setMessage('안녕히 가세요!');  

    return (  
        <div>  
            <button onClick={onClickEnter}>입장</button>  
            <button onClick={onClickLeave}>퇴장</button>  
            <h1>{message}</h1>  
        </div>    )  

}  

export default SayComponent;
  • useState 함수의 인자에는 상태의 초깃값을 넣어준다.

    • 클래스형 컴포넌트에서의 state 초깃값은 객체 형태를 넣어주어야 했다.
    • useState에는 반드시 객체가 아니어도 상관없다.
      • 즉, 값의 형태는 자유다.
  • 함수를 호출하면 배열이 반환된다.

    • 배열의 첫 번째 원소는 현재 상태
    • 배열의 두 번째 원소는 상태를 바꾸어주는 함수
      • 함수를 세터(setter) 함수라고 한다.
  • message, setMessage라고 이름을 설정했지만, test, setText라고 이름을 자유롭게 바꾸어도 상관없다.

import SayComponent from "./SayComponent";  

const AppComponent = () => {  
    return <SayComponent/>  
}  

export default AppComponent;
  • 입장
  • 퇴장

한 컴포넌트에서 useState 여러 번 사용하기

  • useState는 한 컴포넌트에서 여러 번 사용해도 상관없다.
import React, { useState } from "react";  

const MauyUsingUseState = () => {  
    const [message, setMessage] = useState('');  
    const onClickEnter = () => setMessage('안녕하세요!');  
    const onClickLeave = () => setMessage('안녕히 가세요!');  

    const [color, setColor] = useState('black');  

    return (  
        <div>  
            <button onClick={onClickEnter}>입장</button>  
            <button onClick={onClickLeave}>퇴장</button>  
            <h1 style={{color}}>{message}</h1>  
            <button style={{color: 'red'}} onClick={() => setColor('red')}>  
                빨간색  
            </button>  
            <button style={{color: 'green'}} onClick={() => setColor('green')}>  
                초록색  
            </button>  
            <button style={{color: 'blue'}} onClick={() => setColor('blue')}>  
                파란색  
            </button>  
        </div>    );  
}  

export default MauyUsingUseState; 

2-5. state를 사용할 때 주의 사항

  • 클래스형 컴포넌트든 함수형 컴포넌트든 state를 사용해야 할 때 setState 혹은 useState를 통해 전달받은 세터 함수를 사용해야 한다.
  • 다음은 잘못된 코드 예시다.
// 클래스형 컴포넌트에서
this.state.number = this.state.number + 1;
this.state.array = this.array.push(2);
this.state.object.value = 5;

// 함수형 컴포넌트에서
const [object, setObject] = useState({ a: 1, b: 1 });
object.b = 2

배열이나 객체를 업데이트 해야할 때배열이나 객체 사본을 만들고, 그 사본에 값을 업데이트한 후, 그 사본의 상태를 setState 혹은 세터 함수를 통해 업데이트해야 한다.

사본을 만들어서 업데이트하는 예시는 다음과 같다.

// 객체
const object = {a: 1, b: 2, c: 3};
const nextObject = { ...object, b: 2}; // 사본을 만들어서 b값만 덮어 쓰기

// 배열
const array = [
    { id: 1, value: true },
    { id: 2, value: true },
    { id: 3, value: false }
];
let nextArray = array.concat({ id: 4 }); // 새 항목 추가
nextArray.filter(item => item.id !== 2); // id가 2인 항목 제거
nextArray.map(item => (item.id ===1 ? { ...item, value: false } : item)); // id가 1인 항목의 value를 false로 설정
  • 객체에 대한 사본을 만들 때는 spread 연산자라 불리는 ...을 사용하여 처리한다.
  • 배열에 대한 사본을 만들 때는 배열의 내장 함수들을 활용한다.

3. 요약

  • 컴포넌트를 만들어서 내보내고 불러오는 방법을 배웠다.

  • props 및, state를 사용하는 방법을 배웠다.

  • props는 부모 컴포넌트가 설정하고,

  • state는 컴포넌트 자체적으로 지닌 값으로 컴포넌트 내부에서 업데이트 할 수 있다.

  • props를 사용한다고 해서 무조건 값이 고정적인 것은 아니다.

    • 부모 컴포넌트의 state를 자식 컴포넌트의 props로 전달하고, 자식 컴포넌트에서 특정 이벤트가 발생할 때 부모 컴포넌트의 메서드를 호출하면 props도 유동적으로 사용할 수 있다.
728x90
Comments