리액트 컴포넌트 ( 함수, 클래스 ) : React Component & Props

horiz.d·2021년 12월 18일
0

리액트 꿀단지

목록 보기
7/41
post-thumbnail



이 글에선 컴포넌트의 개념에 대해 알아보며, 더 자세한 내용에 관해서는 Detailed component API reference 를 참고하자.



핵심 요약


1. React 컴포넌트의 종류와 기본 형태

  1. 함수형 컴포넌트
import React from 'react';
import './App.css';

function App(){
  const name = '리액트'
  return <div className="react">{name}</div>
}

export default App;

함수형 컴포넌트는 JS함수를 만드는 방식과 거의 유사하며, 리액트의 JSX라는 특수한 문법을 사용하여, <HTML태그 />를 내장할 수 있는 등의 특징을 가진다. hook을 사용한 state조작이 가능해졌기에 효율&성능면에서 우수한 함수형 컴포넌트의 활용히 일반적이다. es6의 문법 또한 지원하기에 화살표함수 방식의 함수형 컴포넌트 선언도 가능하다.


  1. 클래스형 컴포넌트
import React, { Component } from 'react';
import './App.css';

class App extends Component{
  render(){
    const name = '리액트';
    return <div className="react">{name}</div>;
  }
}

export default App;

클래스형 컴포넌트는 state 기능 및 라이프사이클 기능을 사용할 수 있고, 임의의 메서드를 정의할 수 있다는 점이 함수형 컴포넌트와 다르다. 위의 함수형 컴포넌트와 100프로 일치하는 기능을 수행한다. 기존의 리액트는 state의 활용을 위해선 번거로운 class형 컴포넌트와 몇가지 패턴을 응용해야 했으나, 현재는 리액트의 hook 지원으로 함수형 컴포넌트가 선호된다.


2. React Props

props란 properties의 준말로 컴포넌트의 속성을 의미하며, 말 그대로 컴포넌트 내에서 사용할 properties를 가지는 단일객체이다.

기본적으로 props는 전달받은 자식 컴포넌트 입장에서는 읽기전용이므로, 해당 컴포넌트(자식)에 props를 전달하는 부모 컴포넌트에서 설정하며 자식 컴포넌트(함수형/클래스형)는 이를 본인의 내부에서 절대 수정해서는 안된다.

props의 사용1
부모로부터 전달받은 단일객체인 props의 children을 사용한다.

자식 컴포넌트 JSX내에서 props를 사용하기 위해서는 {props.property} 형식으로{} 중괄호 내에 부모 컴포넌트로부터 넘겨받은 props 중 사용하고자 하는 children property를 담아준다.

import React from 'react';

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

export default PropsPractice; // 함수형 컴포넌트 ProsPractice를 내보낸다.

props의 전달 [부모=>자식]
부모 컴포넌트에서 자식 컴포넌트를 호출할 때, 자식 컴포넌트가 사용할 children properties ( which is in 단일객체 props )를 결정하여 전달한다.

<Component property1=value property2=value /> 와 같이 컴포넌트의 호출부 에서 properties를 전달한다.

import React from 'react';
import PropsPractice from './PropsPractice';

const App = () => {
  return <PropsPractice name="마이클" age="23" />
};

export default App;

+ 비구조화 할당

ref : https://velog.io/@dlrbwls0302/TIL-React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-Props-State-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%99%80-Key


3. props와 state

props와 state는 React에서 데이터를 다루는 두가지 개념이다.

props :

  • 부모에서 설정하는 값으로, 부모에서 자식 컴포넌트로 매개변수로서 전달하며, 자식 컴포넌트 내부에서 수정이 불가능하다(읽기 전용이다).
  • 사용자에게 보여지는, 사용자에게 중요한 데이터를 다룬다고 볼 수 있다. ( 클라이언트와 유사 )

부분적인 특징으로만 보면 전역변수와 비슷하지만, 컴포넌트 내부에서 수정이 불가능하다(읽기 전용)는 추가적인 특징을 가진다.


state :

  • 컴포넌트 내부에서 선언되며, 컴포넌트 내에서 제어 가능하다.
  • 컴포넌트 자신이 가지고 있는 값을 말하며, 컴포넌트 내에서 수정이 가능하다. 사실 컴포넌트 내에서의 수정이 state 사용의 근본적 목적이다.
  • 컴포넌트 내부의 처리에서 중요한 데이터를 다룬다고 볼 수 있다. ( 서버와 유사 )
  • 내부적인 정보를 불필요하게 공개하지 않으며, 내부 기밀성을 제고해준다.
  • 초기의 React에선 state의 사용을 위해 함수형 컴포넌트가 아닌 클래스 컴포넌트를 사용해야 했으며, constructor를 선언해주는 길고 불편한 과정이 필요했다.
  • 현재의 React는 state의 간편한 사용을 위해 Hook (useState)이라는 개념을 도입하였으며, 이 useState라는 함수를 사용해 함수형 컴포넌트에서 아무런 하자없이 state를 관리할 수 있게 되었다.
  • state는 일반 변수와는 다르게, 절대 직접 수정해서는 안된다. 리액트가 이 상태를 참조하고 바꿀 수 있도록 관련 함수를 사용하여 수정해준다.

함수의 지역변수와 유사한 특성이라고 볼 수 있다.








본문

밑의 본문에선,
컴포넌트&Props의 개념에 대해 React 공식 튜토리얼 페이지의 예제와 함께 번역하고, 사견을 더해 조금 길게 풀어쓴다.


리액트 컴포넌트

Components는 우리의 UI를 비의존적이고 재사용 가능한 조각들로 분리하는 것을 가능하게 만들어 주며, 분할된 각 조각들을 독립적인 요소로 생각하는 것을 가능하게 한다.

Components & Props

개념적으로, 리액트 Component자바스크립트 function과 닮아있다.

  • 리액트 컴포넌트는 props 라고 불리는 input값을 허용하며,
  • 화면에 무엇이 표시될지를 기술하는 elementsreturn값으로 반환한다.




컴포넌트 정의 : Function & Class

Function Components : 함수형 컴포넌트 정의
Class Components : 클래스형 컴포넌트 정의

리액트의 컴포넌트의 정의방식은 크게 위의 두가지가 존재한다.

Function Component
리액트의 함수형 컴포넌트는 javascript의 함수와 유사한 모습을 가진다.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>; //props의 하위 속성을 사용가능
}
  • input : props라고 불리는 input값을 받을 수 있다.
  • return : 렌더링되어 앱의 화면에 그려질 elements를 반환값으로 설정한다.

Class Component
클래스식으로 컴포넌트를 정의하고자 하면 ES6 Class 방식을 사용한다.

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

클래스형 컴포넌트 자세히 알아보기 <-


함수형, 클래스형 컴포넌트 정의는 각자 추가적인 특징을 가진다.
이는 Next section : state & LifeCycle 에서 알아본다.




컴포넌트의 렌더링 & Props

우린 앞서 아래와 같이 DOM tags만 표현하는 리액트 Elements를 마주했다.

React Element : DOM Tags

const element = <div />;



하지만 React Element는 기본 DOM Tag뿐 아니라 사용자 정의 컴포넌트를 표현할 수도 있다.

React Element : User-Defined Components
.
React는 사용자 정의 컴포넌트를 표현한 Element를 볼 때 JSX Attributeschildren을 이 컴포넌트에 Single Object : 단일 객체로 전달하며, 이 때 전달되는 단일 객체를 props이라고 부른다.
.

const element = <Welcome name="Sara" />;

.
props : { name : 'sara' }


사용자 정의 컴포넌트 Process

위의 컴포넌트가 페이지에서 어떻게 렌더링되는지 자세한 절차를 확인해보자.

Ex

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

Process
1. ReactDOM.render()리액트 element : <Welcome name="Sara" />를 호출한다.
.
2. React는 Welcome 컴포넌트 : 상단 정의된 함수형 컴포넌트를 호출하며 이 때, element로부터 컴포넌트에 전달되는 props{name: 'Sara'} 이다.
.
3. Welcome 컴포넌트는 호출의 return 값으로 element : <h1>Hello, Sara</h1>을 반환한다.
.
4. React DOM은 기존의 것을 <h1>Hello, Sara</h1>에 매치시키기 위해 효율적으로 DOM을 업데이트 한다.



Component의 첫글자는 대문자

React는 원칙적으로 소문자로 시작하는 컴포넌트DOM태그로 취급하므로 반드시 컴포넌트명의 첫글자는 대문자로 작성하여 DOM TAG와 구분해준다.

  • <domTag /> : 첫글자 소문자이므로 돔 태그
  • <Component /> : 첫글자 대문자이므로 컴포넌트

예를 들어, <div />는 소문자로 시작하므로 리액트는 이를 HTML div TAG로 인식하지만, 만일 이를 <Div /> 라고 작성할 시, 리액트는 원칙적으로 이를 컴포넌트로 인식 할 것이다. 이를 반드시 명심하고 구분하자.
자세히



Composing 컴포넌트

컴포넌트는 그들의 출력에서 다른 컴포넌트를 참조할 수 있다(?). 이런 특징은 동일한 컴포넌트에 대해 어느 수준의 디테일 레벨에서든지 Abstraction을 가능하도록 한다.

원문 : Components can refer to other components in their output. This lets us use the same component abstraction for any level of detail.

리액트 앱 내에선button,form,dialog,screen 등의 모든것들을 매우 흔하게 Component로 표현한다.


EX : APP 컴포넌트

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
.
function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}
.
ReactDOM.render(
  <App />,
  document.getElementById('root')
);`

일반적으로, 새로운 리액트 앱들은 이처럼 최상단에 단 하나의 App 컴포넌트를 가지고 있다. ( Top-down 접근 )
.
하지만 만일 리액트 앱을 기존의 앱으로 통합하고자 하는 경우엔, Bottom-up 접근법을 사용해야 할 것이며, 이 경우 Button과 같은 하나의 작은 컴포넌트로부터 시작해서 점진적으로 view계층의 최상단을 향해 점진적으로 나아가야 할 것이다.




컴포넌트 추출 : Extracting

컴포넌트를 더 작은 컴포넌트로 분할하는 것을 두려워하지 말자

컴포넌트 추출 예제


위의 페이지는 아래의 JSX코드로 만들어졌다.
컴포넌트 하나가 과하게 많은 기능을 가지고 있다.

function formatDate(date) { // Comment 함수형 컴포넌트 내에서 date prop을 format할 에정
  return date.toLocaleDateString();
}
.
function Comment(props) { // 메인 컴포넌트인 Comment : input으로 받는 단일 객체 props는 해당 컴포넌트 하단에 정의함
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img
          className="Avatar"
          src={props.author.avatarUrl} // 인자로 받은 단일객체 props의 child property인 이미지 
          alt={props.author.name}		// img태그의 필수 인자인 alt를 동적 설정 : 인자로 받은 단일객체 props의 child property인 name
        />
        <div className="UserInfo-name">
          {props.author.name} 		//인자로 받은 단일객체 props의 child property인 name
        </div>
      </div>
      <div className="Comment-text">{props.text}</div> // 인자로 받은 단일객체 props의 property text 
      <div className="Comment-date">
        {formatDate(props.date)} // 인자로 받은 단일객체 props의 property date를 상단에서 정의한 함수로 format
      </div>
    </div>
  );
}
.
const otherComment = { // 상단에서 정의한 comment 컴포넌트의 props로 사용될 단일 객체 정의
  date: new Date(),
  text: '리액트는 쉽다',
  author: {
    name: 'Horiz.d',
    avatarUrl:
    'https://blogpfthumb-phinf.pstatic.net/MjAyMTA5MjZfMTYz/MDAxNjMyNjAyNzY4NTYx.PDwWl2KxsSo8_BV1-aTnU7Eex5EDe8gMTtyq6qqopBIg.Dl04wLkj7JJtm1rxl2A_EJUXc6U4asmuYG-HRMFsfGgg.PNG.he12569/%25EC%258A%25A4%25EB%25A7%2588%25EC%259D%25BC_%25EB%25B0%2591%25EB%258F%2599%25EC%259E%2590%25EB%25A6%2584.png?type=w161'
  },
}
ReactDOM.render( // ReactDom.render() 를 사용해 화면에 표시될 react Elements 렌더링
  <Comment			//comment 컴포넌트 호출
    date={otherComment.date}	// comment 컴포넌트의 단일객체props.child
    text={otherComment.text}	// comment 컴포넌트의 단일객체props.child
    author={otherComment.author}// comment 컴포넌트의 단일객체props.child
  />,
  document.getElementById('root')
);

궁금증 : 컴포넌트는 단일객체를 props로 받는다고 했는데 상단의 코드에서 돔 내에 호출한 <Comment /> 컴포넌트는 date,text,author 로 단일객체 otherComment의 개별 property 세개를 받았다. 내가 개념을 잘못이해한걸까?
.
컴포넌트의 정의에선 인자를 props 하나로 설정했는데 어떻게 받는 props가 세개일 수 있지 싶다.
.
: 현업의 친구에게 물어보고 답을 받았다. 이는 자바스크립트의 특성으로 따로 매개변수를 설정하지 않아도. props로 받는 객체의 property명을 컴포넌트의 매개변수 명으로 자동설정 한다고 한다. 이런 특성 때문에 발생하는 문제가 많기 때문에, typescript가 등장한 것이며 이에 대한 예시코드 또한 확인해봤다. 내가 기대한 props의 사전 설정 방식이 typescript를 활용하면 가능해진 것을 볼 수 있었다.

컴포넌트 추출 예제 Solution

위에서 확인해본 과도하게 큰 Comment 컴포넌트를 아래와 같이 추출/분할 할 수 있으며, 위에서 내가 가졌던 1-component : 1-props 에 대한 문제 또한 해결해 준다.
.
다만 남아있는 의문은 리액트 자체적으로 1props 인자를 강제하지는 않는지에 대한 것에 더해, 상단 예제의 Comment컴포넌트의 매개변수로 존재하는 date,text,author 매개변수 명칭은 어떻게 설정되어 사용되고 있는 지 또한 여전히 의문이다.
.
어쨌든 정갈하게 추출된 아래의 solution을 보자,
단일 컴포넌트였던 Comment가 아래의 솔루션에선 Avatar, UserInfo, Comment 총 세개로 분할되었으며, 각각의 컴포넌트는 props를 하나씩만 받으며 [ Avatar => UserInfo => Comment ] 로 연결되어 위의 Comment 컴포넌트와 동일한 기능을 수행하게 된다.

function formatDate(date) {
  return date.toLocaleDateString();
}
.
function Avatar(props) {
  return (
    <img
      className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}
.
function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">{props.user.name}</div>
    </div>
  );
}
.
function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}
.
const comment = {
  date: new Date(),
  text: '리액트는 어렵다',
  author: {
    name: 'Horiz.d',
    avatarUrl: 'https://placekitten.com/g/64/64',
  },
};
ReactDOM.render(
  <Comment
    date={comment.date}
    text={comment.text}
    author={comment.author}
  />,
  document.getElementById('root')
);



Props는 읽기전용(수정X)

컴포넌트를 함수형으로 정의하든, 클래스형으로 정의하든, 컴포넌트 자신의 props의 값은 컴포넌트 자신 내부의 expression에서 절대 수정될 수 없다. 마치 아래의 pure한 함수와 같다고 할 수 있다.

pure함수를 보자

function sum(a, b) {
  return a + b;
}

이런 함수는 pure라고 불린다. pure은 그 자신의 inputs로 들어온 값을 자신의 내부에서 바꾸는 시도를 절대 하지 않으며, 언제나 동일한 inputs로 동일한 결과를 반환한다. 우리의 리액트 컴포넌트는 이러한 pure함수처럼, 내부에서 inputs:props를 바꾸는 시도를 하지 않는다.

impure함수를 보자

이와는 대조적으로, 아래의 함수는 그 자신의 input의 값을 내부에서 수정하기 때문에 impure하다고 말한다.

function withdraw(account, amount) {
  account.total -= amount;

리액트의 컴포넌트 특성인 pure과는 대조되는 예시이다. inputs로 들어온 account의 children property의 값을 내부에서 수정했다.


REACT 컴포넌트는 반드시 Pure function처럼 동작하며, 자신의 input으로 전달받은 props를 존중하여 절대 이를 내부에서 수정하지 않는다.



However, APP UIs are dynamic and changeable : state

직전에서 설명한 리액트 컴포넌트의 pure한 특성은 우리가 dynamic한 앱 UI를 설계하는데에 제약을 줄 것 처럼 보인다.
하지만 다음 섹션에서 알아볼 [state](https://reactjs.org/docs/state-and-lifecycle.html) 라는 새로운 개념은 위의 pure규칙에 대한 위배 없이, 리액트 컴포넌트가 사용자의 동작, 네트워크 반응, 그리고 그 밖의 이벤트에 따라 리액트 컴포넌트 자신의 output을 변경하는 것을 가능하게 만들어 준다.




REF :

REACT Official Concepts - Components and Props :

https://reactjs.org/docs/components-and-props.html

강추 : https://velog.io/@dlrbwls0302/TIL-React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-Props-State-%EB%A6%AC%EC%8A%A4%ED%8A%B8%EC%99%80-Key

profile
가용한 시간은 한정적이고, 배울건 넘쳐난다.

0개의 댓글