Component & Props

김동현·2021년 12월 8일
0

React

목록 보기
3/27
post-thumbnail

Component

리액트는 사용자 인터페이스를 만들기 위한 자바스크립트 라이브러리입니다. 리액트 같은 라이브러리를 사용하는 이유는 사용자 인터페이스를 만드는 과정을 "단순화"해주기 때문입니다.

리액트에는 "컴포넌트"라는 개념이 있는데, 리액트는 모든 사용자 인터페이스를 컴포넌트로 구성합니다.

리액트가 컴포넌트를 사용하는 이유는 "재사용이 가능하다는 점""작은 단위로 작성하여 우려 사항을 분리할 수 있다는 점" 입니다.

재사용을 할 수 있다는 것은 반복을 피할 수 있게 해주며, 코드를 작은 단위로 작성하면 코드를 관리하기에 용이하며 하나의 사항에 대해서만 집중할 수 있게 됩니다.


import React from 'react';

// 1. 컴포넌트 함수 정의
const Hello = () => <h1>Hello, world</h1>;

// 2. 컴포넌트 생성(객체화)
<Hello />;

컴포넌트 함수는 일반 함수처럼 정의하지만 반드시 "하나의 리액트 엘리먼트를 반환"해야 하는 함수여야 리액트 컴포넌트의 자격을 가질 수 있습니다.

또한 컴포넌트 함수의 이름은 반드시 "파스칼 케이스"로 작성해야 합니다. JSX 문법을 사용할 때 소문자를 사용한 경우 리액트는 이를 html 태그 이름으로 인식하게 되고, 파스칼 케이스로 사용한 경우에 사용자 정의 컴포넌트로 인식합니다.

컴포넌트 함수로 JSX 문법을 사용하게 되면 바벨에 의해 React.createElement 호출문으로 변환되어 리액트 엘리먼트가 생성되며, 생성된 리액트 엘리먼트의 type 프로퍼티 값이 컴포넌트 함수이므로 이를 다시 호출합니다. 호출될 때 인수로 props 프로퍼티에 바인딩된 객체를 전달받아 호출됩니다.

이렇게 type 프로퍼티가 문자열이 될 때까지 이를 반복하여 최종적으로는 type 프로퍼티 값이 문자열인 리액트 엘리먼트를 반환합니다.

즉, 컴포넌트 함수로 JSX 문법을 사용하는 것은 해당 컴포넌트 함수를 호출하는 의미와 동일하게 볼 수 있습니다.

const Component () => {
    return <p>My Component</p>;
}

const myComponent = <MyComponent />; // -> MyComponent 함수 호출
console.log(myComponent); // { type: 'p', key: null, ref: null, props: { children: 'My Component' } }

생성된 컴포넌트는 개별적인 상태를 갖고있는 "독립적인 컴포넌트"입니다.

const Parent = () => {
    return (
        <>
            <Child />
            <Child />
            <Child />
        </>
    );
}

위 코드에서 생성된 각 Child 컴포넌트들은 각각 "개별적인 상태"를 갖고 있는고, "독립적인 컴포넌트"로서 서로에게 영향을 주지 않습니다.


우리는 "props"라는 개념을 통해서 컴포넌트를 "재활용 측면"에서도 더 유연하게 사용할 수 있습니다. 컴포넌트 함수로 JSX 문법 사용시 생성된 리액트 엘리먼트의 type 프로퍼티는 컴포넌트 함수를 가리키고 있습니다.

type 프로퍼티가 함수이므로 이를 다시 호출하는데, 호출할 때 리액트 엘리먼트의 props 프로퍼티가 가리키고 있는 객체가 type 프로퍼티가 가리키는 함수의 인수로 전달되면서 호출됩니다.

즉, 컴포넌트 함수로 JSX 문법 사용시 작성한 어트리뷰트들은 모두 props 객체의 프로퍼티가 되고, props 객체는 암묵적으로 컴포넌트 함수의 인수로 전달됩니다.
컴포넌트 함수 내부에서는 인수로 전달되는 props객체의 프로퍼티를 참조하여 컴포넌트를 설정(Configuration)할 수 있습니다.

import React from 'react';

const Title = props => <h1>{props.title}</h1>;

const headerTitle = <Title title="header title" />;
const mainTitle = <Title title="main title" />;
const footerTitle = <Title title="footer title" />;

Title 컴포넌트 함수에서 props.title을 통해 컴포넌트를 재활용하였습니다. 이때 생성된 각 컴포넌트는 개별적인 상태를 갖고 동작합니다. 즉, 생성된 컴포넌트는 독립적인 객체(컴포넌트)입니다.

Props

사용자 컴포넌트 측면에서 props컴포넌트 함수 "외부(상위 컴포넌트)"에서 들어와 컴포넌트의 내부 UI에 영향을 줄 수 있는 객체입니다. 우리는 props을 통해 컴포넌트 함수에게 "데이터를 전달하여 생성될 컴포넌트를 설정"하고, "재사용"할 수 있게 해줍니다.

컴포넌트 함수는 JSX 문법으로 컴포넌트화합니다. 컴포넌트 함수에게는 언제나 하나의 인수가 전달되고, 전달되는 인수가 바로 props 객체이며 컴포넌트 UI 데이터를 "설정(Configurable)"할 수 있습니다.

즉, 사용자 컴포넌트가 외부와 통신할 수 있는 "유일한 통로는 props"입니다.


// Parent Component
const Parent = () => {
    return (
        // Child 컴포넌트에 title 어트리뷰트 작성
        // title 어트리뷰트는 props 객체의 title 프로퍼티로 Child 컴포넌트 함수에게 전달
        <Child title="first child" />
    );
};

// Child Component
const Child = (props) => {

    // 컴포넌트 함수는 언제나 하나의 인수 전달받아 호출됨
    // 인수로 전달받는 props 객체에는 JSX 문법에 작성된 어트리뷰트가 존재
    console.log(props); // { title: 'first child' }
    
    return (
        <div>
            // 전달받은 props 객체로 컴포넌트를 설정할 수 있음
            <h1>{props.title}</h1>
        </div>
    );
};

위 코드에서 Parent 함수의 반환값에 작성된 Child 컴포넌트에 작성된 title은 Child 함수의 첫 번째 인수로 전달되는 props 객체의 프로퍼티로 전달됩니다.
즉, Child 함수 내부에서 props.title을 참조하면 'first child'라는 문자열값을 취득할 수 있습니다.

여기서 알 수 있는 점으로 두 가지가 있습니다.

  1. 암묵적으로 리액트가 "컴포넌트 함수의 첫 번째 인수로 객체(props)를 전달"하면서 호출한다.

  2. 컴포넌트로 JSX 문법 사용시 작성한 "어트리뷰트는 모두 일반적인 데이터를 전달"하기 위한 props 객체의 프로퍼티"로 컴포넌트 함수에게 전달된다.
    만약 html 어트리뷰트를 작성하더라도 이는 데이터를 전달하기 위한 프로퍼티로서 동작한다.

1. HTML 태그 이름으로 JSX 문법 사용

이전에 살펴보았듯이 "기본 내장 html 태그(div, p,,,)"로 JSX문법을 사용한 경우에만 html 어트리뷰트를 카멜 케이스(onClick, className, htmlFor 등,,)로 작성하여 리액트 엘리먼트를 설정할 수 있습니다.

2. Component로 JSX 문법 사용

"사용자 정의 컴포넌트"에 작성한 어트리뷰트의 경우에는 컴포넌트 함수에게 데이터를 전달하기 위한 props 객체의 프로퍼티로 전달됩니다. 즉, html 어트리뷰트와 대응하는 어트리뷰트를 카멜 케이스로 작성하여도 단지 컴포넌트 함수 내부에서 컴포넌트를 설정하기 위한 props 객체의 프로퍼티로 사용됩니다.

예외적으로 key, ref, children은 빌트인 어트리뷰트로 해당 이름의 어트리뷰트를 컴포넌트에게 데이터를 전달하는 용도로 사용이 불가능합니다. 이는 특별한 의미(목적)를 갖는 어트리뷰트로 리액트에 빌트인 되어 있습니다. 이는 추후에 설명하도록 하겠습니다.

정리하자면 props 객체는 컴포넌트 함수로 JSX 문법을 통해 컴포넌트를 생성할 때 컴포넌트 함수에게 첫 번째 인수로 전달되는 객체이며, 어트리뷰트로 작성한 것이 props 객체의 프로퍼티 키로, 어트리뷰트 값이 프로퍼티 값으로 전달되어 컴포넌트 함수 내부에서 props 객체의 프로퍼티를 참조하여 값을 사용할 수 있습니다.

props는 불변 객체

props는 "불변 객체"입니다. props는 외부(상위 컴포넌트)에서 전달받은 값으로 값을 읽는 것은 가능하지만, 변경하거나 삭제, 추가하는 것은 불가능합니다. 즉, get 엑세스는 가능하지만 set 엑세스는 불가능합니다.

props를 변경하려면 props를 전달하는 "상위 컴포넌트에서 변경"해야 합니다.


import React from 'react';

const Child = props => {
    return <h1>{props.name}</h1>;
};

cosnt Parent = () => {
    return <Child name='child component' />;
};

Child 컴포넌트 함수에게 전달되는 props 객체에는 name 프로퍼티가 존재하고 값으로 'child component' 문자열 값이 존재합니다. Child 컴포넌트 함수 내부에서는 props.name으로 Parent 컴포넌트에서 전달한 'child component'값을 참조할 수 있습니다.

단, Child 컴포넌트에서 props 객체는 불변객체로 props.name에 다른 값을 재할당하는 동작은 불가능합니다. 단지 props 객체의 프로퍼티 값을 읽는 동작만이 가능합니다.

만약 props.name 값을 변경하고 싶다면 props 객체를 전달한 상위 컴포넌트인 Parent 컴포넌트에서 변경해야 합니다.

props.children

html 태그 이름으로 JSX 문법 사용시 Content 영역에 작성한 내용들은 화면에 그대로 렌더링됩니다.

사용자 정의 컴포넌트로 JSX 문법 사용시 Content 영역에 작성한 내용(태그와 태그 사이의 내용)들은 화면에 표시되지 않습니다.
컴포넌트 함수로 JSX 문법을 사용하면 "컴포넌트 함수의 반환값에 작성된 내용들만 화면에 표시"됩니다.

Content 영역에 작성한 내용들은 "props 객체의 children 프로퍼티"에 담겨 컴포넌트 함수에게 전달되므로, 이를 화면에 표시해주기 위해서는 컴포넌트 함수의 반환값에 props.children을 우리가 명시적으로 작성해주어야 합니다.

즉, props 객체의 chilren 프로퍼티는 리액트에 빌트인되어 태그 사이의 내용을 갖는다는 특수한 목적을 갖고 있습니다. 따라서 컴포넌트 함수에게 일반적인 데이터를 전달하기 위한 용도로는 사용할 수 없습니다.

Component Composition(합성)

props.children을 통해서 우리는 컴포넌트를 "합성(Composition)" 할 수 있습니다. 컴포넌트 함수 내부에서 props.children을 이용하여 래퍼 컴포넌트로 사용할 수 있습니다.

import React from 'react';

const Parent = props => {
    return (
        <>
            <p>parent component</p>
            {props.children}  // -> <Child />가 전달됨
        </>
    );
};

const Child = () => {
    return <p>child component</p>;
};

const composition = (
    <Parent>
        <Child />  //->Parent 함수에게 전달되는 prosp 객체의 children 프로퍼티에 바인딩될 값
    </Parent>
);

위 코드에서 composition이라는 변수가 할당받는 컴포넌트를 보면, Parent 컴포넌트의 Content 영역에 Child 컴포넌트를 중첩하여 작성했습니다. 이 Child 컴포넌트는 Parent 함수에게 전달되는 props 객체의 Children 프로퍼티에 바인딩되어 전달됩니다. 따라서 Praent 컴포넌트는 props.children을 참조하여 Child 컴포넌트를 렌더링되도록 Parent 컴포넌트 함수 반환값에 작성해주어야 합니다.

즉, 컴포넌트를 래퍼 컴포넌트로 사용하기 위해서는 상위 컴포넌트 함수 내부에서 props.chilren을 참조하여 중첩된(Content 영역)의 내용들을 참조하여 렌더링되도록 작업해야 합니다.

Composition은 "구조가 동일(공통 레이아웃)"하거나, "중복되는 CSS 코드"가 존재하는 등 공통적인 부분을 하나의 상위 컴포넌트에 작성하고, 상위 컴포넌트의 Content 영역에 개별적인 부분을 담당하는 컴포넌트를 작성하는 방식으로 컴포넌트를 합성(Composition)할 수 있습니다.

상향식 데이터 흐름

일반적으로 props를 이용하여 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달되는 "하양식 데이터 흐름"을 생성합니다. 하지만 자식 컴포넌트의 데이터를 부모 컴포넌트로 전달(자식 -> 부모)되는 상향시 데이터 흐름을 만들기 위해서는 어떻게 해야하는지 알아보겠습니다.

컴포넌트는 외부 컴포넌트와 소통할 수 있는 유일한 통로는 오직 props뿐입니다. 하지만 데이터의 흐름은 상위에서 하위로 흐르는 것이 일반적입니다. 이를 상향식으로 역전시키는 방법은 props의 프로퍼티에 일반적인 데이터가 아닌 "함수"를 전달하는 방법을 사용하는 것입니다.

  1. 하위 컴포넌트의 데이터를 전달받을 상위 컴포넌트에서 함수를 정의하고, 정의한 함수를 props 객체를 통해 하위 컴포넌트에게 전달

  2. 하위 컴포넌트에서는 전달받은 props 객체의 프로퍼티를 참조하여 전달받은 함수를 호출할 때 상위에게 전달하고자하는 데이터를 인수로 전달해줍니다. 그러면 상위 컴포넌트에서 정의한 함수가 하위 컴포넌트의 데이터를 인수로 전달받아 함수를 실행합니다.
    즉, 함수를 호출할 때 인수로 전달한 데이터가 상위 컴포넌트에 정의한 함수에게 전달되는 방식으로 상향식 데이터 흐름을 만들 수 있습니다.

정리하자면 상위 컴포넌트에서 정의한 "함수"를 props객체의 프로퍼티로 전달하고, 하위 컴포넌트에서는 props로 전달받은 상위 컴포넌트에서 정의한 함수를 상위 컴포넌트로 전달할 데이터를 인수로 전달하면서 호출한다면 하위 컴포넌트에서 상위 컴포넌트로 데이터의 흐름이 역전될 것입니다.

주의할 점으로 props는 부모에서 자식으로 전달됩니다. props을 통해 데이터를 전달할 때 중간에 컴포넌트를 중간에 뛰어 넘을 수 없습니다. 반드시 부모 자식 관계로 이어지는 "모든 컴포넌트를 통해 전달"해야 합니다.


// Parent.js
import react, { useState } from react;
import Child from './Child';

const Parent = () => {
    const [text, setText] = useState('');

    const update = enteredData => {
        setData(enteredData);
    }

    return (
        <div>
            <span>{text}</span>
            <Child onUpdateText={update}/>
        </div>
    );
};

Parent 컴포넌트에서 update 함수를 props.onUpdateText에 바인딩하여 Child 컴포넌트에게 전달합니다.

// Child.js
import react, { useState } from react;

const Child = props => {
    const [enteredInput, setEnteredInput ] = useState('');
    
    const sumbitTextHandler = event => {
        event.preventDefault();
        props.onUpdateText(enteredInput);
    };
    
    const changeTextHandler = event => {
        setEnteredInput(event.target.value);
    };

    return (
        <form onSubmit={sumbitTextHandler}>
            <input type="text" onChange={changeTextHandler} value={enteredInput} />
        </from>
    );
}

Child 컴포넌트에서는 submit 이벤트가 발생하면 submitTextHandler 함수가 호출되고, submitTextHandler 함수 내부에서는 전달된 props 객체의 onUpdateData 프로퍼티에 바인딩된 함수를 호출하는데 input 요소의 value 값인 enteredInput을 인수로 전달하면서 호출합니다.

그러면 Child 컴포넌트의 enteredInput 상태값을 Parent 컴포넌트로 전달됩니다.

profile
Frontend Dev

0개의 댓글