티스토리 뷰

WEB/React

[React] Component

춘햄 2023. 3. 23. 15:04

 리액트에서 컴포넌트를 선언하는 방식은 크게 함수형 컴포넌트와 클래스형 컴포넌트로 나뉜다.

 

첫 리액트 프로젝트에 있던 App.js 에는 function App()으로 시작하는 함수형 컴포넌트가 있었는데, 이를 클래스형 컴포넌트로 변경할 수도 있고, 새로운 컴포넌트를 추가할 수도 있다.

 

바로 한번 리액트의 컴포넌트 개념에 대하여 알아보자.


함수형, 클래스형 컴포넌트

 

◎App.js

import logo from './logo.svg';
import './App.css';

function App() {
   const name = 'React';

   return (
       <>
          <div className="react">{name}</div>
          <input />
       </>
   );
}

export default App;

위와 같이 함수형 컴포넌트로 선언되어 있는 App() 컴포넌트를 클래스형으로 바꿔보면,

 

import logo from './logo.svg';
import './App.css';
import { Component } from "react";

class App extends Component {
    render() {
        const name = 'React';

        return (
            <>
                <div className="react">{name}</div>
                <input />
            </>
        );
    }
}

export default App;

리액트의 Component 클래스를 임포트 하고, 이를 상속받는 App이라는 클래스를 만든 뒤에, render() 함수 내부에 기존 JSX 코드를 삽입했다. 

 

클래스형 컴포넌트에서는 render 함수가 꼭 있어야 한다.

 

클래스형 컴포넌트로 바꿨지만, 그 역할은 위에 있는 함수 컴포넌트와 동일하다. 

 

차이점은 클래스형 컴포넌트의 경우, 이후 다룰 state 기능이나 LifeCycle 기능을 사용할 수 있다는 것과 임의 메서드를 정의할 수 있다는 것이다.


컴포넌트 선언에 2 가지 방법이 있다는 건 알았다. 그럼, 컴포넌트를 선언할 수 있는 두 가지 방법 중 어느 상황에 어떤 방식을 사용해야 할까?

 

우선, 함수 컴포넌트의 장점은 클래스형보다 선언하기가 훨씬 편하며, 메모리 자원도 훨씬 덜 사용한다. 또한, 프로젝트를 완성하여 빌드한 후 배포할 때도 함수 컴포넌트를 사용하는 것이 결과물의 파일 크기가 더 작다.

(성능의 차이는 거의 없다.)

 

함수 컴포넌트의 주요한 단점은 state와 LifeCycle API 의 사용이 불가능하다는 점인데, 이 단점 또한 리액트 v16.8 업데이트 이후 Hooks라는 기능이 도입되면서 해결되었다.

 

리액트 공식 매뉴얼에서는 컴포넌트를 새로 작성할 때 함수 컴포넌트와 Hooks를 사용하도록 권장하고 있다. 

 

우선, Hooks의 기능을 완벽히 이해하기 전에는 책에서 클래스 컴포넌트를 위주로 사용하여 학습을 진행하고, Hooks에 대하여 다룬 뒤에 Hooks를 기반으로 컴포넌트를 생성하려고 한다.


첫 컴포넌트 생성

 

src 디렉토리에 MyComponent.js를 생성한다.

 

◎MyComponent.js

const MyComponent = () => {
    return <div>The new, cool Component</div>
};

export default MyComponent;

 

하단에 작성한 

export default MyComponent;

를 사용하여 다른 파일에서 이 파일을 import할 때, 위에서 선언한 MyComponent 클래스를 불러오도록 설정한다.

 

이제, App 컴포넌트에서 MyComponent를 불러와서 사용해 보자. 

 

◎App.js

import MyComponent  from "./MyComponent";

const App = () => {
    return <MyComponent />
};

export default App;


Props

 properties를 사용하여 컴포넌트 속성을 설정할 수 있다. props 값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있다.

 

즉, 부모 컴포넌트에서 아래와 같이 props 값을 지정하고,

 

◎App.js

import MyComponent  from "./MyComponent";

const App = () => {
    return <MyComponent name="React" />
};

export default App;

 

자식 컴포넌트가 이를 사용할 수 있다는 것이다. 대신, 인자로 먼저 props를 받아와야 한다.

 

◎MyComponent.js

const MyComponent = props => {
    return <div>Hi, there! My name is  {props.name}</div>
};

export default MyComponent;

 

App.js 에서 props의 값을 지정해주지 않을 때, 보여줄 기본 값을 defaultProps를 사용하여 지정할 수도 있다.

 

◎MyComponent.js

const MyComponent = props => {
    return <div>Hi, there! My name is  {props.name}</div>
};

MyComponent.defaultProps  = {
    name: 'Default Name'
};

export default MyComponent;

또한 children props를 사용하여 컴포넌트 태그 사이의 내용을 확인할 수도 있다.

 

◎App.js

import MyComponent  from "./MyComponent";

const App = () => {
    return <MyComponent>Child Contents</MyComponent>
};

export default App;

 

◎MyComponent.js

const MyComponent = props => {
    return (
        <div>
            Hi, there! My name is  {props.name}. <br/>
            my child is {props.children}.
        </div>
    )
};

MyComponent.defaultProps  = {
    name: 'Default Name'
};

export default MyComponent;


props 객체에서 값을 추출하는 문법인 비구조화 할당(구조 분해 문법)을 사용하여 좀 더 편하게 props를 사용할 수 있다.

 

◎MyComponent.js

const MyComponent = props => {
    const {name, children} = props;
    return (
        <div>
            Hi, there! My name is  {name}. <br/>
            my child is {children}.
        </div>
    )
};

MyComponent.defaultProps  = {
    name: 'Default Name'
};

export default MyComponent;

위와 같이 props 내부에 있는 객체를 따로 추출하여 저장한 뒤에 호출하여 사용할 수도 있다.


컴포넌트의 필수 props 를 지정하거나 props 타입을 지정할 때는 propsType을 사용한다. 

 

prop-types 패키지에 있는 PropTypes를 불러와서 아래와 같이 작성해 주자.

 

◎MyComponent.js

import PropsTypes from 'prop-types'

const MyComponent = props => {
    const {name, children} = props;
    return (
        <div>
            Hi, there! My name is  {name}. <br/>
            my child is {children}.
        </div>
    )
};

MyComponent.defaultProps  = {
    name: 'Default Name'
};

MyComponent.propTypes = {
    name: PropsTypes.string
};

export default MyComponent;

타입 힌트와 비슷하게 propTypes을 지정하면 해당 속성은 지정한 타입이 아닌 타입을 할당할 경우, 경고가 나타난다.

 

또한 isRequired 키워드를 사용하여 해당 속성을 필수로 지정하도록 설정할 수 있다.

import PropsTypes from 'prop-types'

const MyComponent = props => {
    const {name, children, favoriteNumber} = props;
    return (
        <div>
            Hi, there! My name is  {name}. <br/>
            my child is {children}. <br/>
            my favoriteNumber is {favoriteNumber}
        </div>
    )
};

MyComponent.defaultProps  = {
    name: 'Default Name'
};

MyComponent.propTypes = {
    name: PropsTypes.string,
    favoriteNumber: PropsTypes.number.isRequired
};

export default MyComponent;

 

isRequired를 설정하면 부모 컴포넌트에서 반드시 해당 속성을 지정해줘야 한다. 그렇지 않으면 경고가 나타난다.

 

◎App.js

import MyComponent  from "./MyComponent";

const App = () => {
    return <MyComponent favoriteNumber={1}>Child Contents</MyComponent>
};

export default App;

클래스형 컴포넌트에서도 동일하게 props를 사용할 수 있다.

 

◎MyComponent.js

import PropsTypes from 'prop-types'
import {Component} from "react";

class MyComponent extends Component {
    render() {
        const {name, children, favoriteNumber} = this.props;

        return (
            <div>
                Hi, there! My name is  {name}. <br/>
                my child is {children}. <br/>
                my favoriteNumber is {favoriteNumber}
            </div>
        )
    }
}

MyComponent.defaultProps  = {
    name: 'Default Name'
};

MyComponent.propTypes = {
    name: PropsTypes.string,
    favoriteNumber: PropsTypes.number.isRequired
};

export default MyComponent;

State

 리액트에서 State는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있다.

 

즉, props를 바꾸려면 부모 컴포넌트에서 바꿔 줘야 한다.

 

리액트에는 두 가지 종류의 state가 있다. 하나는 클래스형 컴포넌트가 지니고 있는 state이고, 다른 하나는 함수 컴포넌트에서 useState라는 함수를 통해 사용하는 state이다.

 

바로 한번 확인해 보자. Counter.js를 생성하여 아래와 같이 작성한다.

 

◎Counter.js

import {Component} from "react";

class Counter extends Component {
    constructor(props) {
        super(props);
        // state의 초깃값 설정
        this.state = {
            number: 0
        };
    }

    render() {
        const {number} = this.state; // this.state로 state를 조회

        return (
            <div>
                <h1>{number}</h1>
                <button
                    onClick={() => {
                        // setState 메서드를 사용하여 새로운 값 할당 가능
                        this.setState({number: number + 1})
                    }}
                >
                    +1
                </button>
            </div>
        )
    }
}

export default Counter;

컴포넌트에 state를 설정할 때는 위와 같이 contructor 메서드를 사용하여 설정한다.

 

◎App.js

import Counter from "./Counter";

const App = () => {
    return <Counter/>
};

export default App;


아래와 같이 state 객체 안에는 2개 이상의 여러 값이 있을 수도 있다.

 

◎Counter.js

import {Component} from "react";

class Counter extends Component {
    constructor(props) {
        super(props);
        // state의 초깃값 설정
        this.state = {
            number: 0,
            fixedNumber: 0
        };
    }

    render() {
        const {number, fixedNumber} = this.state; // this.state로 state를 조회

        return (
            <div>
                <h1>{number}</h1>
                <h2>바뀌지 않는 값: {fixedNumber}</h2>
                <button
                    onClick={() => {
                        // setState 메서드를 사용하여 새로운 값 할당 가능
                        this.setState({number: number + 1})
                    }}
                >
                    +1
                </button>
            </div>
        )
    }
}

export default Counter;

동일하게 this.state에서 속성을 추출하여 사용하면 된다.


또한 굳이 contructor를 사용하지 않고도 클래스 내부에서 바로 state를 지정해 줄 수 있다.

import {Component} from "react";

class Counter extends Component {
    state = {
        number: 0, 
        fixedNumber: 0
    };

    render() {
        const {number, fixedNumber} = this.state; // this.state로 state를 조회

        return (
            <div>
                <h1>{number}</h1>
                <h2>바뀌지 않는 값: {fixedNumber}</h2>
                <button
                    onClick={() => {
                        // setState 메서드를 사용하여 새로운 값 할당 가능
                        this.setState({number: number + 1})
                    }}
                >
                    +1
                </button>
            </div>
        )
    }
}

export default Counter;

 


setState에 객체 대신 함수 인자를 전달할 수도 있다.

 

위 코드에서 버튼을 눌렀을 때, 아래와 같이 setState를 두 번 호출하면 어떻게 될까?

 <button
    onClick={() => {
        this.setState({number: number + 1})
        this.setState({number: number + 1})
    }}
>
    +1
</button>

setState를 사용한다고 state의 값이 바로 바뀌는 것이 아니기 때문에 this.setState를 두 번 호출한다고 값이 2씩 증가하지는 않는다. 

 

그렇기 때문에 setState에 객체가 아닌 함수를 인자로 넣어 2씩 증가하도록 구현할 수 있다.

this.setState((prevState, props) => {
    return {
        // something to update
    }
}

여기서 prevState는 기존 상태이고, props는 현재 지니고 있는 props를 가리킨다. 두 번째 인자인 props는 생략 가능하다.

 

import {Component} from "react";

class Counter extends Component {
    state = {
        number: 0,
        fixedNumber: 0
    };

    render() {
        const {number, fixedNumber} = this.state; // this.state로 state를 조회

        return (
            <div>
                <h1>{number}</h1>
                <h2>바뀌지 않는 값: {fixedNumber}</h2>
                <button
                    onClick={() => {
                        this.setState(prevState => {
                            return {
                                number: prevState.number + 1
                            }
                        });

                        this.setState(prevState => {
                            return {
                                number: prevState.number + 1
                            }
                        });
                    }}
                >
                    +1
                </button>
            </div>
        )
    }
}

export default Counter;

이렇게 하면 증가된 상태의 prevState를 한번 더 불러 1을 더하는 게 되기 때문에 2씩 값이 증가하도록 구현이 가능하다.


setState의 두번째 인자로 callback 함수를 지정하여 setState로 값을 업데이트한 뒤에 행해질 작업을 설정할 수 있다.

 this.setState(prevState => {
    return {
        number: prevState.number + 1
    }
},
() => {
    console.log("setState가 호출되었습니다.");
    console.log(this.state);
});


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

 함수형 컴포넌트에서도 Hooks를 사용하여 state를 사용할 수 있다고 했다. 

 

Hooks를 본격적으로 다루기 전에 useState기능으로 함수형 컴포넌트에서 어떻게 state를 다루는지만 확인해 보자.

 

위에서 this.state 내부의 속성을 변수에 추출하여 사용하는 객체 비구조화 할당을 사용했는데, useState를 사용하기 위해서는 배열 내부에 있는 값을 변수에 쉽게 할당하는 배열 비구조화 할당을 알고 있어야 한다.

 

그렇게 어려운 개념은 아니라, 아래 코드만 봐도 어떤 느낌인지 알 수 있다.

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

 

배열 비구조화 할당을 알면, useState는 쉽게 사용할 수 있다. useState 함수의 리턴 값을 배열 비구조화 할당으로 담은 뒤에 그냥 아래와 같이 사용하면 된다.

 

◎Counter.js

import {useState} from 'react';

const Say = () => {
    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 Say;

useState 함수의 인자에는 state의 초깃값을 넣어준다. 함수의 리턴 값의 첫 번째 원소는 현재 상태이고, 두 번째 원소는 상태를 바꿔주는 함수이다.

 

◎App.js

import Say from './Say'

const App = () => {
    return <Say/>
};

export default App;


또한 한 컴포넌트에서 여러 번의 useState() 함수를 호출하여 상태를 여러 개 사용할 수도 있다.

import {useState} from 'react';

const Say = () => {
    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 Say;


이번 포스팅에서는 컴포넌트를 만들어서 내보내고 불러오는 방법과 props 및 state를 사용하는 방법을 다뤘다. 

 

props와 state는 둘 다 컴포넌트에서 사용하거나 렌더링 할 데이터를 담고 있으므로 비슷해 보일 수 있겠지만, 그 역할은 매우 다르다. props는 부모 컴포넌트가 설정하고, state는 컴포넌트 자체적으로 지닌 값으로 컴포넌트 내부에서 값을 업데이트할 수 있다.

 

또한 함수형이던, 클래스형이던 state객체를 직접 호출하여 변경하는 것은 옳은 방식이 아니다. 반드시 setter 함수를 사용하여 값을 변경해야 하며, state 내부에 존재하는 배열이나 객체를 업데이트하려고 한다면, 해당 배열이나 객체의 사본을 만들고, 그 사본의 상태를 setter 함수를 통해 업데이트해야 한다.

 

 

 

끝!

'WEB > React' 카테고리의 다른 글

[React] 컴포넌트 반복  (0) 2023.03.24
[React] ref: DOM에 이름 달기  (0) 2023.03.24
[React] 이벤트 핸들링  (0) 2023.03.23
[React] JSX 문법  (0) 2023.03.23
[React] Hello, React!  (0) 2023.03.22
Comments