이 글에선 컴포넌트의 개념에 대해 알아보며, 더 자세한 내용에 관해서는 Detailed component API reference 를 참고하자.
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의 문법 또한 지원하기에 화살표함수 방식의 함수형 컴포넌트 선언도 가능하다.
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 지원으로 함수형 컴포넌트가 선호된다.
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
props와 state는 React에서 데이터를 다루는 두가지 개념이다.
props
:
부분적인 특징으로만 보면 전역변수와 비슷하지만, 컴포넌트 내부에서 수정이 불가능하다(읽기 전용)는 추가적인 특징을 가진다.
state
:
클래스 컴포넌트
를 사용해야 했으며, constructor
를 선언해주는 길고 불편한 과정이 필요했다.Hook (useState)
이라는 개념을 도입하였으며, 이 useState
라는 함수를 사용해 함수형 컴포넌트
에서 아무런 하자없이 state를 관리할 수 있게 되었다.함수의 지역변수와 유사한 특성이라고 볼 수 있다.
밑의 본문에선,
컴포넌트&Props의 개념에 대해 React 공식 튜토리얼 페이지의 예제와 함께 번역하고, 사견을 더해 조금 길게 풀어쓴다.
Components
는 우리의 UI를 비의존적이고 재사용 가능한 조각들로 분리하는 것을 가능하게 만들어 주며, 분할된 각 조각들을 독립적인 요소로 생각하는 것을 가능하게 한다.
개념적으로, 리액트 Component
는 자바스크립트 function
과 닮아있다.
props
라고 불리는 input
값을 허용하며,elements
를 return
값으로 반환한다. 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 에서 알아본다.
우린 앞서 아래와 같이 DOM tags
만 표현하는 리액트 Elements
를 마주했다.
React Element : DOM Tags
const element = <div />;
하지만 React Element는 기본 DOM Tag
뿐 아니라 사용자 정의 컴포넌트
를 표현할 수도 있다.
React Element : User-Defined Components
.
React는사용자 정의 컴포넌트
를 표현한Element
를 볼 때JSX Attributes
와children
을 이 컴포넌트에Single Object : 단일 객체
로 전달하며, 이 때 전달되는 단일 객체를props
이라고 부른다.
.const element = <Welcome name="Sara" />;
.
props
:{ name : 'sara' }
위의 컴포넌트가 페이지에서 어떻게 렌더링되는지 자세한 절차를 확인해보자.
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을 업데이트 한다.
React는 원칙적으로 소문자로 시작하는
컴포넌트
를DOM태그
로 취급하므로 반드시 컴포넌트명의 첫글자는 대문자로 작성하여 DOM TAG와 구분해준다.
<domTag />
: 첫글자 소문자이므로돔 태그
<Component />
: 첫글자 대문자이므로컴포넌트
예를 들어, <div />
는 소문자로 시작하므로 리액트는 이를 HTML div TAG로 인식하지만, 만일 이를 <Div />
라고 작성할 시, 리액트는 원칙적으로 이를 컴포넌트로 인식 할 것이다. 이를 반드시 명심하고 구분하자.
자세히
컴포넌트는 그들의 출력에서 다른 컴포넌트를 참조할 수 있다(?). 이런 특징은 동일한 컴포넌트에 대해 어느 수준의 디테일 레벨에서든지 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계층의 최상단을 향해 점진적으로 나아가야 할 것이다.
컴포넌트를 더 작은 컴포넌트로 분할하는 것을 두려워하지 말자
컴포넌트 추출 예제
위의 페이지는 아래의 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의 값은 컴포넌트 자신 내부의 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의 값을 내부에서 수정했다.
직전에서 설명한 리액트 컴포넌트의 pure
한 특성은 우리가 dynamic한 앱 UI를 설계하는데에 제약을 줄 것 처럼 보인다.
하지만 다음 섹션에서 알아볼 [state](https://reactjs.org/docs/state-and-lifecycle.html) 라는 새로운 개념은 위의 pure
규칙에 대한 위배 없이, 리액트 컴포넌트가 사용자의 동작, 네트워크 반응, 그리고 그 밖의 이벤트에 따라 리액트 컴포넌트 자신의 output을 변경하는 것을 가능하게 만들어 준다.
https://reactjs.org/docs/components-and-props.html