React.js - component와 props

Gyu·2022년 2월 3일
1

React.js

목록 보기
4/20
post-thumbnail

React에서 컴포넌트란?

  • JS에서 함수는 코드를 더 명확하고 재사용 가능하게 해준다. 함수는 함수 안에 다른 함수를 호출 할 수 있는데, 이는 함수가 해야 할 일을 명확히 분리할 수 있게 해준다. 즉 모든 명령을 하나의 함수 안에 다 작성할 필요가 없고, 각 작업에 특화된 여러 함수를 만들어 기능을 분리할 수 있다.
  • 리액트의 컴포넌트는 함수의 이러한 특징을 웹 앱의 비주얼에 적용한 것이라고 할 수 있다. 컴포넌트를 통해 UI를 재사용 가능한 개별적인 여러 조각으로 나누고, 각 조각을 개별적으로 살펴볼 수 있다. 또한 컴포넌트 안에 컴포넌트를 포함할 수도 있다.
  • 개념적으로 컴포넌트는 JavaScript 함수와 유사하다. 컴포넌트는 “props”라고 하는 임의의 입력을 받은 후, 화면에 어떻게 표시되는지를 기술하는 React 엘리먼트를 반환한다.
  • 리액트에서 컴포넌트는 JSX를 통해 HTML 엘리먼트를 출력할 수 있는, 재사용 가능한 자바스크립트 덩어리다.

함수 컴포넌트와 클래스 컴포넌트

  • 리액트에서 컴포넌트를 만드는 방식은 전통적인 함수를 이용한 방식과 ES6에서 제공하는 class를 이용하는 방식이 있다. JS에서 class는 결국 함수이기 때문에 React의 관점에서 볼 때 이 두 가지 유형의 컴포넌트는 동일하다.

함수 컴포넌트

  • 리액트에서 컴포넌트를 정의하는 가장 간단한 방법으로, 이 함수는 데이터를 가진 하나의 “props” (props는 속성을 나타내는 데이터) 객체를 매개변수로 받은 후 이를 이용하여 React 엘리먼트를 반환하므로 유효한 React 컴포넌트다. 이러한 컴포넌트는 JavaScript 함수이기 때문에 말 그대로 “함수 컴포넌트”라고 칭한다.
    function Welcome(props) { // 컴포넌트이름은 무조건 PascalCase로 작성한다.
      return <h1>Hello, {props.name}</h1>;
    }
  • 장점
    • 클래스 컴포넌트보다 후에 나왔으며 컴포넌트 선언이 편하다.
    • 메모리 자원을 클래스 컴포넌트보다 덜 사용한다.
  • 단점
    • state, lifeCycle 관련 기능사용 불가능하다. (하지만 이는 Hook을 통해 해결 가능하다.)

클래스 컴포넌트

  • 리액트에서 클래스 컴포넌트는 React.Component를 상속 받아 작성한다. 클래스 컴포넌트는 props라는 매개변수를 받아오고 render 함수를 통해 표시할 뷰 계층 구조를 반환한다.

    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
  • 장점

    • state, lifeCycle 관련 기능 사용이 가능하다.
    • 임의 메서드를 정의할 수 있다.
  • 단점

    • 메모리 자원을 함수형 컴포넌트보다 조금 더 사용한다.
  • JavaScript Class 참조 문서

💡 render() 함수

render 함수는 화면에서 보고자 하는 내용을 반환한다. React는 설명을 전달받고 결과를 표시한다. 특히 render는 렌더링할 내용을 경량화한 React 엘리먼트를 반환한다. 다수의 React 개발자는 “JSX”라는 특수한 문법을 사용하여 React의 구조를 보다 쉽게 작성한다.

구문은 빌드하는 시점에서 React.createElement('div')로 변환된다.

// 작성 코드
ReactDOM.render(
	<div>
		<h1>Hello World</h1>
	</div>
);

// 트랜스파일링 된 코드
ReactDOM.render(
	React.creatElement("div", null,
		React.creatElement("h1", null, "Hello World")
	)
)

컴포넌트로 리액트 엘리먼트 작성하기

  • 리액트에서는 HTML 태그로 리액트 엘리먼트(JSX)를 작성했다. 하지만 리액트 컴포넌트를 사용한다면 사용자 정의 컴포넌트로 리액트 엘리먼트를 작성 할 수 있다.
    // 기존 HTML 태그로 리액트 엘리먼트 작성
    const element = <h1>Hello, World</h1>;
    
    // 사용자 정의 컴포넌트를 이용해 리액트 엘리먼트 작성
    function Welcome(props) {
      return <h1>Hello, World</h1>;
    }
    
    const element = <Welcome/>; 
    // 컴포넌트 이름을 태그 형식으로 작성하여 리액트 엘리먼트를 표현한다.
  • 컴포넌트의 이름은 항상 대문자로 시작해야 한다. 왜냐하면 React는 소문자로 시작하는 컴포넌트를 DOM 태그로 처리하기 때문이다. 예를 들어 <div/>는 HTML div 태그를 나타내지만, <Welcome />은 컴포넌트를 나타내며 범위 안에 Welcome이 있어야한다.

컴포넌트 렌더링 및 컴포넌트 속성(props)

컴포넌트 속성

  • 함수에 매개변수를 사용하면 매개변수에 따라 다른 결과값을 반환하여 동적으로 프로그램을 실행시킬 수 있다. 리액트 컴포넌트에서는 함수의 매개변수처럼 속성(property)를 사용하여 동적으로 화면을 만들 수 있다.
  • 컴포넌트에서 속성 사용하는 방법
    // 1. 컴포넌트 내부에서 props 사용
    
    // 함수 컴포넌트에서 props 사용하기
    function Welcome(props) {
      // JSX에서 JS 문법을 사용하므로 중괄호를 사용한다.
      return <h1>Hello, {props.name}</h1>;
    }
    
    // 클래스 컴포넌트에서 props 사용하기
    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
        // 클래스 컴포넌트에서 props 사용 시 this 키워드는 필수
      }
    }
    
    // 2. 리액트 엘리먼트에 props 추가하기 - 공통
    ReactDom.render( // 컴포넌트에서 작성한 동일한 이름의 엘리먼트 속성 추가
    	<div>
    		<Welcome name="stranger"/>
    	</div>,
    	document.querySelector('#container')
    );
    // 속성을 추가하는 방식은 기존의 HTML 엘리먼트에 속성(attribute)를 추가하는 방식과 같다.
  • 컴포넌트에 속성은 여러 개를 사용할 수 있으며, 리액트 엘리먼트에 속성 정의 시 따옴표를 이용하여 문자열을 값으로 지정하는 방법 혹은 중괄호를 이용하여 JS표현식을 이용하는 방법 중 하나만의 방법을 사용하여야 한다.
  • props는 읽기 전용이다. 리액트에 엘리먼트에 속성 정의 시 할당한 초기값을 변경하지 않고 있는 그대로 사용해야 한다.

컴포넌트 렌더링

함수 컴포넌트 렌더링 순서

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

ReactDom.render(
	<div>
		<Welcome name="stranger"/>
	</div>,
	document.querySelector('#container')
);
  1. <Welcome name="stranger"/> 이라는 리액트 엘리먼트를 매개변수에 담아 ReactDom.render() 함수를 호출한다.
  2. ReactDom.render() 함수 호출 시 리액트는 {name: 'stranger'}를 props로 하여 Welcome 컴포넌트를 호출한다.
  3. Welcome 컴포넌트는 함수이므로 <h1>Hello, stranger</h1>를 반환한다.
  4. 리액트 DOM은 실제 DOM이 Welcome 컴포넌트가 반환한 <h1>Hello, stranger</h1> 와 같도록, DOM을 효율적으로 업데이트 한다.

클래스 컴포넌트 렌더링

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

ReactDom.render(
	<div>
		<Welcome name="stranger"/>
	</div>,
	document.querySelector('#container')
);
  1. <Welcome name="stranger"/> 이라는 리액트 엘리먼트를 매개변수에 담아 ReactDom.render() 함수를 호출한다.
  2. ReactDom.render() 함수 호출 시 리액트는 Welcome 클래스의 생성자(Constructor)를 호출한다.
  3. 리액트가 Welcome 클래스의 render() 함수를 호출한다. render() 함수에서는 this.props.prop이름 으로 props에 접근 할 수 있다.
  4. render() 함수는 화면에 표시할 내용으로 JSX를 리턴한다.
  5. 리액트 DOM은 실제 DOM이 Welcome 클래스의 render() 함수가 반환한 <h1>Hello, stranger</h1> 와 같도록, DOM을 효율적으로 업데이트 한다.

컴포넌트에서 props로 자식 다루기

  • 리액트에서 컴포넌트는 최종적으로 리액트 엘리먼트를 반환하고, 리액트 엘리먼트는 규칙에 맞게 JSX로 작성해야 한다.
  • 리액트 엘리먼트는 컴포넌트 이름을 태그 형식으로 작성하여 사용할 수 있으며, 최종적으로 브라우저에게는 브라우저가 이해할 수 있는 HTML, CSS, JS로 전달된다.
  • 즉 리액트 엘리먼트는 기존의 HTML 엘리먼트와 매우 유사하기 때문에 다른 엘리먼트로 감싸거나 엘리먼트 속성을 지정할 수 있다. 또한 다른 HTML 엘리먼트와 동일하게 자식 노드(텍스트, HTML 엘리먼트, 리액트 엘리먼트)를 가질 수 있다.
    // 다른 엘리먼트 안에 포함 된 리액트 엘리먼트
    ReactDom.render(
    	<div>
    		<Welcome/>
    	</div>,
    	document.querySelector('#container')
    );
    
    // 다른 엘리먼트를 포함하고 있는 리액트 엘리먼트
    ReactDom.render(
    	<Welcome>
    		<p>Hello World</p>
    	</Welcome>,
    	document.querySelector('#container')
    );
  • 리액트 엘리먼트는 텍스트, 기존 HTML 엘리먼트, 리액트 엘리먼트(컴포넌트) 모두에 포함 될 수도, 모두를 포함 할 수도 있다.

컴포넌트에서 props를 사용해 자식에 접근하는 방법

  • 컴포넌트 내부에서 this.props.children 으로 자식 요소에 접근 가능하다.
// 클래스형 컴포넌트
class Welcome extends React.Component {
	render () {

		const style = {
			color: {this.props.textColor} 
			// 리액트 엘리먼트에 작성한 textColor 이름의 속성이 전달
		};

		return(
			<h1 style={style}>Welcole {this.props.children}</h1>
			// 리액트 엘리먼트 내부에 작성한 텍스트 노드가 전달
		);
	}
}

// 함수헝 컴포넌트
const Welcome = (props) => {
	const style = {
		color: {props.textColor} 
	};
	return (
		<h1 style={style}>Welcole {props.children}</h1>
	);
}

ReactDom.render(
	<Welcome textColor="green">World<Welcome/>,
	document.querySelector('#container')
);
  • 자식 노드가 텍스트라면 this.props.children 은 문자열을 반환하고, 자식 노드가 단일 엘리먼트라면 배열로 감싸지 않은 단일 컴포넌트를 리턴한다.

props type 검증 및 default props 설정

  • 리액트에서 props는 Immutable Data 즉, 변하지 않는 데이터이며, 상위(부모) 컴포넌트에서 하위(자식) 컴포넌트로 데이터를 넘겨줄 때 사용한다. 간단한 컴포넌트의 경우 전달받은 props의 값을 쉽게 알 수 있지만 복잡한 구조의 컴포넌트의 경우 props의 값을 알기가 쉽지 않다.
  • 복잡한 구조의 컴포넌트에서 props를 전달할 경우 값이 없어서 오류가 생기거나 자유로운 JS의 특성 때문에 props 타입이 맞지 않아 오류가 생기는 경우가 있다. 이러한 오류를 막기 위해 리액트에서는 전달받은 props의 타입을 검증하는 방법과 props의 기본값을 지정하는 방법을 제공한다.
  • default props를 설정하면 undefined로 인해 생기는 오류를 방지할 수 있다. props type 검증을 통해 props의 값이 필수인지 아닌지, 지정한 타입과 맞는지 아닌지를 검증하여 지정한 type과 틀리다면 console에서 경고 메시지를 띄우는 등의 작업을 할 수 있다. 이러한 작업들은 유지보수에도 좋고 다른 개발자가 코드를 봤을 때 쉽게 이해할 수 있도록 돕는다.
  • props type 검증과 default props 설정은 모두 컴포넌트 선언이 끝난 후에 한다.
  • props type 검증 형식
    Component.propTypes = {
    	propName: PropTypes.dataType[.isRequired] // isRequired의 경우 필수 속성
    }
    
    // 어떤 타입이든 허용할 때는 any, 리액트 엘리먼트일 경우 element
  • default props 설정 형식
    Component.defaultProps = {
    	propName: defaultValue
    }
  • props type 검증, default props 설정 예
    class User extends React.Component {
    	render(
    		return (
    			<div>
    				<p>name : {this.props.name}</p>
    				<p>age : {this.props.age}</p>
    				<p>profile : {this.props.children}</p>
    			</div>
    		);
    	);
    }
    
    // 타입 검증
    User.propTypes = {
    	name: PropTypes.string, // string 타입
    	age: PropTypes.number.isRequired // number 타입, 필수값
    }
    
    // 기본값 설정
    User.defaultProps = {
    	name: 'Unknown' // 전달받은 name이라는 이름을 가진 props가 없을 경우 기본값 설정
    }
    
    ReactDom.render(
    	<User age="25">Hi. It's me<User/>,
    	document.querySelector('#app')
    );
  • 함수형 컴포넌트에서 default props를 설정하는 방법은 클래스형 컴포넌트와 동일하지만, props type 검증은 조금 다르다. 함수형 컴포넌트에서 props type 검증을 하기 위해서는 prop-types 라는 모듈을 설치 해야한다.
    • 설치 명령어 : npm i prop-types
  • 함수형 컴포넌트에서 props type 검증 및 default props 설정 예시
    import React from "react";
    import PropTypes from "prop-types"; // prop-types 모듈 import
    
    const User = (props) => {
      return (
    		<div>
    			<p>name : {props.name}</p>
    			<p>age : {props.age}</p>
    			<p>profile : {props.children}</p>
    		</div>
    	);
    };
    
    // 타입 검증
    User.propTypes = {
    	name: PropTypes.string, // string 타입
    	age: PropTypes.number.isRequired // number 타입, 필수값
      children: PropTypes.node.isRequired // 자녀노드 필수
    }
    
    // 기본값 설정
    User.defaultProps = {
    	name: 'Unknown' // 전달받은 name이라는 이름을 가진 props가 없을 경우 기본값 설정
    }
    export default User;
    *PropTypes 종류 더 알아보기

스프레드 연산자를 사용하여 props 전달

  • 여러 계층에 걸쳐 컴포넌트 사이에 props를 전달해야 하는 경우, props를 전달하는 과정은 복잡하다. props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방식이기 때문에 중간 계층을 건너뛰고 원하는 컴포넌트로 데이터를 전달할 수 없다. 또한 반대로 자식 컴포넌트에서 props를 이용해 부모 컴포넌트로 데이어를 보낼 수 없다. props는 부모 컴포넌트에서 직계 자식 컴포넌트에게 일방적인 데이터 전달하는 방식으로만 이뤄진다.
  • 여러 계층을 통해 props를 전달하는 예
    class RootComp extends React.Component {
        render() {
            return (
                <Parent color={this.props.color}
                        num={this.props.num}
                        size={this.props.size}
                />
            );
        }
    }
    
    class Parent extends React.Component {
        render() {
            return (
                <Child color={this.props.color}
                        num={this.props.num}
                        size={this.props.size}
                />
            );
        }
    }
    
    class Child extends React.Component {
        render() {
            return (
                <div>
                        <p>{this.props.color}</p>
                        <p>{this.props.num}</p>
                        <p>{this.props.size}</p>
                </div>
            )
        }
    }
    
    ReactDOM.render(
        <div>
            <RootComp color='blue' num='12' size='medium'/>
        </div>,
        document.getElementById('app')
    );
    
    // RootComp -> Parent -> Child로 props를 전달
    // Child에서는 전달받은 props를 출력
    // Parent는 Child로 전달만 할 뿐 props를 실제로 사용하지 않음
  • props는 JS 객체다. props를 자식 컴포넌트에게 전달하려면 props 객체의 각 속성에 일일이 접근해야 한다.
    // props object
    var props = {
    	color: 'blue',
    	num: 12
    	size: 'medium'
    }
    
    // props를 전달하기 위해서는 props 객체의 모든 속성에 하나하나 접근 해야 한다.
    <Parent color={this.props.color}
            num={this.props.num}
            size={this.props.size}
    />
  • 스프레드 연산자를 사용하면 배열이나 객체에서 모든 요소에 접근 할 수 있는 것처럼, props도 스프레드 연산자를 사용하여 모든 props 속성에 접근하여 자식 컴포넌드에게 props를 전달할 수 있다.
    class RootComp extends React.Component {
        render() {
            return (
                <Parent {...this.props}/>
            );
        }
    }
    
    class Parent extends React.Component {
        render() {
            return (
                <Child {...this.props}/>
            );
        }
    }
    
    class Child extends React.Component {
        render() {
            return (
                <div>
                        <p>{this.props.color}</p>
                        <p>{this.props.num}</p>
                        <p>{this.props.size}</p>
                </div>
            )
        }
    }
    
    ReactDOM.render(
        <div>
            <RootComp color='blue' num='12' size='medium'/>
        </div>,
        document.getElementById('app')
    );
profile
애기 프론트 엔드 개발자

0개의 댓글