All Articles

모양새를 갖춘 리액트 컴포넌트 만들기

클릭하면 1씩 증가하고 우클릭하면 1씩 감소하는, 그러면서 0 아래로는 떨어지지 않는 컴포넌트 3개를 만들어봤다.

만든 컴포넌트

들어가기 전에 리액트 컴포넌트 간단 정리

  • 리액트가 변경된 속성값, 상태값을 기반으로 UI 자동 갱신
  • 가상 돔(Virtual DOM)을 통한 빠른 UI 갱신 리액트가 가상 돔을 이용하는 방식

    • DOM 계산 비용이 비싸기 때문
    • 가상 돔을 이용해 이전 UI 상태를 메모리에 유지하고 변경될 UI 최소 집합을 계산
  • props: 속성값. 읽기 전용이며 불변 객체

    • 컴포넌트 생성 시, 부모 컴포넌트로부터 전달받음
    • 부모가 props를 변경하면(부모 입장에서는 state), 변경된 props 값을 참조하는 UI가 자동으로 업데이트
  • state: 상태값

    • 각 컴포넌트가 개별로 생성/유지하는 상태값들
    • 주로 컴포넌트 단위로 UI에 반영할 값을 저장할 목적
    • 불변 객체로 처리해야 함. immer 패키지를 이용해 보다 편리하게 처리할 수 있음
    • 상태값은 setter 함수로 변경해야 함. 직접 변경은 가급적 지양
  • Element Tree
  • Component Tree

만든 컴포넌트 소스코드

// App.js

import React from 'react';
import 'App.css';
import PropTypes from "prop-types";
import Counter from "Counter";

class App extends React.Component {
    render() {
        // defaultProps로 color: 'red'를 지정했기 때문에
        // 컬러를 지정하지 않은 컴포넌트는 자동으로 빨간색이 된다.
        return (
            <div>
                <Counter />
                <Counter color="green" />
                <Counter color="blue" />
            </div>
        )
    } 
}

export default App;
// Counter.js

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

class Counter extends React.Component {
    static defaultProps = {
        color: 'red',
    }

    // defaultProps를 지정해주었으므로
    // (= props가 있다면) type을 지정해주는 편이 좋음
    static propTypes = {
        color: PropTypes.string,
    }

    state = {
        color: this.props.color,
        value: 0,
    }

    onClick = () => {
        this.setState(prevState => ({
            value: prevState.value + 1
        }))
    }

    // 마우스 우클릭 시 이벤트 onContextMenu
    onContextMenu = (e) => {
        // 원래 이벤트 동작을 막겠다는 의미
        // onContextMenu(마우스 우클릭)에서 원래 이벤트 동작은 context 메뉴가 뜨는 것
        e.preventDefault();
        this.setState(prevState => ({
            // 3항 연산자를 사용해 숫자가 0 아래로는 떨어지지 않게 처리
            value: (prevState.value >= 1 ? prevState.value - 1 : 0),
        }))
    }

    // backgroundColor: color 속성 추가를 위해 style 객체를 펼쳐서(...) 사용
    // 다른 속성을 추가해줄 게 아니라면 style = { style }로만 써도 됨
    render() {
        const { color, value } = this.state;
        return (
            <div onClick={this.onClick}
                 style={ {...style, backgroundColor: color} }
                 onContextMenu={this.onContextMenu}>
                { value }
            </div>
        )
    }
}

const style = {
    width: '100px',
    height: '100px',
    display: 'inline-block',
    borderRadius: '50px',  // 리액트에서는 언더스코어가 허용되지 않기 때문에 카멜케이스로 작성
    textAlign: 'center',
    lineHeight: '100px',  // 원지름이 줄높이가 되도록
    userSelect: 'none',  // 클릭했을 때 음영으로 영역선택되는 것 막기
    fontSize: '3rem',
    color: 'white',
    margin: '1rem',
};

export default Counter;

onContextMenu에서 기본 동작을 막지 않으면(e.preventDefault();) 우클릭할 때마다 익히 아는 이 메뉴가 뜬다.

onContextMenu 이벤트 기본 동작

참고) 이벤트 핸들러 여러 방식으로 표현하기

전부 같은 동작을 한다.

onClick = () => {
    this.setState({ value: value + 1 });
}
onClick = () => {
    this.setState(({ value }) => ({
        value: value + 1
    }))
}
onClick = () => {
    this.setState(({ value: prevState }) => ({
        value: prevState + 1
    }))
}
onClick = () => {
    this.setState(prevState => ({
        value: prevState.value + 1
    }))
}