리액트

#3. 순수 리액트

YJH3968 2021. 3. 26. 22:14
728x90

1. 페이지 설정

  • 리액트를 브라우저에서 다루려면 React와 ReactDOM 라이브러리를 불러와야 한다.
  • React는 뷰를 만들기 위한 라이브러리고 ReactDOM은 UI를 실제로 브라우저에 렌더링할 때 사용하는 라이브러리이다.
  • ReactDOM을 UI에 렌더링하기 위해 사용할 HTML element가 필요하다.
  • script와 HTML element를 추가하는 방법은 다음과 같다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>순수 리액트 예제</title>
</head>
<body>

    <!-- target container -->
    <div class="react-container"></div>
    
    <!-- React와 ReactDOM library -->
    <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
    
    <script>
    	// 순수 리액트와 자바스크립트 코드
    </script>
</body>
</html>

2. 가상 DOM

  • HTML은 브라우저가 문서 객체 모델(DOM)을 구성하기 위해 따라야 하는 절차로, HTML 문서를 이루는 element는 브라우저가 HTML 문서를 읽어 들이면 DOM element가 되고, 이 DOM이 화면에 UI를 표시한다.
  • 전통적으로 웹사이트는 독립적인 HTML 페이지들로 만들어져 사용자가 페이지를 navigate할 때마다 브라우저는 매번 다른 HTML 문서를 요청해서 로딩했다. 그러나 AJAX가 생기면서 단일 페이지 앱(SPA)이 생겼다. 이제는 전체 웹 애플리케이션이 한 페이지로 실행되면서 javascript에 의존해 UI를 갱신할 수 있게 되었다.
  • DOM API는 브라우저의 DOM을 변경하기 위해 javascript가 사용할 수 있는 객체의 모음이다. document.createElement, document.appendChild가 그 예시이다. 화면에 표시된 DOM element를 변경하는 것은 쉽지만, 새 element를 추가하는 것은 시간이 매우 오래 걸린다.
  • javascript를 사용해 DOM 변경을 효율적으로 처리하는 일은 아주 복잡하고 시간이 오래 걸리는데, 리액트는 이를 쉽게 해결해준다. 리액트는 브라우저 DOM을 갱신하기 위해 만든 라이브러리로 모든 처리를 대신 해 준다.
  • DOM API를 직접 조작하지 않고 가상 DOM을 다루거나 리액트가 UI를 생성하고 브라우저와 상호작용하기 위해 사용하는 몇 가지 명령을 다룬다.
  • 가상 DOM은 리액트 element로 이루어진다. 리액트 element는 HTML element와 유사하지만 실제로는 javascript 객체다. DOM API를 직접 다루는 것보다 javascript 객체인 가상 DOM을 다루는 편이 훨씬 빠르다. 가상 DOM을 변경하면 리액트는 DOM API를 사용해 변경사항을 가장 효율적으로 렌더링해준다.

3. 리액트 element

  • 리액트 element는 DOM element가 어떻게 생겨야 하는지 기술한다. 즉, 브라우저 DOM을 만드는 방법을 알려주는 명령이다.
// React.createElement를 사용해 리액트 element를 생성할 수 있다.
// 첫 번째 인자는 element의 type, 두 번째 인자는 element의 property, 세 번째 인자는 element의 자식 노드를 표현한다.
// DOM element에 있는 attribute를 리액트 element의 property로 표현할 수 있다.
React.createElement("h1", {id: "recipe-0", data-type: "title"}, "구운 연어");

  • 즉, 리액트 element는 리액트가 DOM element를 구성하는 방법을 알려주는 javascript literal일 뿐이다. 다음은 createElement가 실제로 만들어낸 element이다.
{
    $$typeof: Symbol(React.element),
    "type": "h1",
    "key": null,
    "ref": null,
    "props": {"children": "구운 연어"},
    "_owner": null,
    "_store": {}
}
  • type 프로퍼티는 만들려는 HTML이나 SVG(2차원 벡터 그래픽을 표현하기 위한 XML 기반의 파일) element의 type을 지정한다.
  • props 프로퍼티는 DOM element를 만들기 위해 필요한 데이터나 자식 element를 표현한다.
  • children 프로퍼티는 텍스트 형태로 표시할 다른 내부 element이다.

4. ReactDOM

  • ReactDOM에는 리액트 element를 브라우저에 렌더링하는데 필요한 모든 도구가 들어 있다. 즉, 가상 DOM에서 HTML을 생성하는데 필요한 모든 도구가 들어있다.
  • render method, 서버에서 사용하기 위한 renderToString과 renderToStaticMarkup method 등이 들어있다.
  • ReactDOM.render는 리액트 element와 그 모든 자식 element를 함께 렌더링하기 위해 사용한다.
// ReactDOM.render
// 첫 번째 인자는 렌더링할 리액트 element이고, 두 번째 인자는 렌더링이 일어날 대상 DOM 노드이다.
var dish = React.createElement("h1", null, "구운 연어");
ReactDOM.render(dish, document.getElementById('react-container'));
<!-- 위 코드의 결과는 다음과 같다.-->
<body>
    <div id="react-container">
        <h1>구운 연어</h1>
    </div>
</body>

 

5. Children

  • ReactDOM은 항상 한 element만 DOM으로 렌더링할 수 있다.
  • 리액트는 렌더링할 element에 data-reactroot라는 꼬리표를 달고 모든 다른 리액트 element는 이 root element 아래로 내포된다.
  • 리액트는 props.children을 사용해 자식 element를 렌더링한다. 리액트 element를 자식으로 렌더링하면서 element의 tree가 생기는데, 이를 component tree라고 한다. tree에는 root component가 하나 존재하고, root 아래로 많은 가지가 자란다.
// HTML의 unordered list를 react element로 표현하는 예제
React.createElement(
    "ul",
    { className: "ingredients" },
    React.createElement("li", null, "연어 500그램"),
    React.createElement("li", null, "잣 1컵"),
    React.createElement("li", null, "버터 상추 2컵"),
    React.createElement("li", null, "옐로 스쿼시 1개"),
    React.createElement("li", null, "올리브 오일 1/2컵"),
    React.createElement("li", null, "마늘 3쪽")
);
  • createElement의 4번째 이후에 추가된 인자는 다른 자식 element로 취급된다. 리액트는 이런 자식 element의 배열을 만들고 props.children의 값을 그 배열에 설정한다.
// 위 코드로 생성된 리액트 element는 다음과 같다.
{
    "type": "ul",
    "props": {
        "children": [
            { "type": "li", "props": { "children": "연어 500그램" } ... },
            { "type": "li", "props": { "children": "잣 1컵" } ... },
            { "type": "li", "props": { "children": "버터 상추 2컵" } ... },
            { "type": "li", "props": { "children": "옐로 스쿼시 1개" } ... },
            { "type": "li", "props": { "children": "올리브 오일 1/2컵" } ... },
            { "type": "li", "props": { "children": "마늘 3쪽" } ... }
        ]
    }
    ...
};
            
  • HTML element 중 class attribute가 있는 element는 리액트에서 class 대신 className이라는 이름의 프로퍼티를 사용해야 한다.

6. 데이터로 element 만들기

  • 리액트 사용의 가장 큰 장점은 UI element와 데이터를 분리할 수 있다는 것이다.
  • 리액트는 단순한 javascript이기 때문에 리액트 component tree를 더 편하게 구성하기 위한 javascript 로직을 얼마든지 추가할 수 있다.
// 위 예제를 javascript 배열로 간단하게 표현할 수 있다.
var items = [
    "연어 500그램",
    "잣 1컵",
    "버터 상추 2컵",
    "옐로 스쿼시 1개",
    "올리브 오일 1/2컵",
    "마늘 3쪽"
];

React.createElement(
    "ul",
    { className: "ingredients" },
    items.map(ingredient =>
        React.createElement("li", null, ingredient);
);
  • 이 때, 배열을 iteration해서 자식 element의 list를 만드는 경우 리액트에서는 각 자식 element에 key 프로퍼티를 넣을 것을 권장한다. 리액트는 key를 사용해 DOM을 더 효율적으로 갱신할 수 있다.
// key 프로퍼티를 추가한다.
React.createElement(
    "ul",
    { className: "ingredients" },
    items.map((ingredient, i) =>
        React.createElement("li", { key: i }, ingredient);
);

 

7. 리액트 component

  • component를 만드는 방법에는 크게 3가지 방법이 있는데, createClass를 사용하는 방법, ES6 class를 사용하는 방법, 상태가 없는 함수형 component를 사용하는 방법이다.
  • React.createClass를 사용해 component를 만드는 방법은 다음과 같다.
// React.createClass를 사용해 리액트 component를 만드는 예제
const IngredientsList = React.createClass({
    displayName: "IngredientsList",
    render() {
        return React.createElement("ul", {"className": "ingredients"},
            React.createElement("li", null, "연어 500그램"),
            React.createElement("li", null, "잣 1컵"),
            React.createElement("li", null, "버터 상추 2컵"),
            React.createElement("li", null, "옐로 스쿼스 1개"),
            React.createElement("li", null, "올리브 오일 1/2컵"),
            React.createElement("li", null, "마늘 3쪽")
        )
    }
});

const list = React.createElement(IngredientsList, null, null);

ReactDOM.render(
    list,
    document.getElementById('react-container')
);
            
<!-- 위 코드의 결과는 다음과 같다. -->
<IngredientsList>
    <ul className="ingredients">
        <li>연어 500그램</li>
        <li>잣 1컵</li>
        <li>버터 상추 2컵</li>
        <li>옐로 스쿼시 1개</li>
        <li>올리브 오일 1/2컵</li>
        <li>마늘 3쪽</li>
    </ul>
</IngredientsList>
// 리액트 component에 데이터를 넘길 때는 프로퍼티로 넘긴다. 
// 재사용 가능한 재료 리스트를 만들기 위해 재료들이 들어 있는 배열을 이 component에 넘길 수 있다.
const IngredientsList = React.createClass({
    displayName: "IngredientsList",
    render() {
        return React.createElement("ul", {className: "ingredients"},
            this.props.items.map((ingredients, i) =>
                React.createElement("li", { key: i }, ingredient)
            )
        )
    }
});

const items = [
    "연어 500그램",
    "잣 1컵",
    "버터 상추 2컵",
    "옐로 스쿼시 1개",
    "올리브 오일 1/2컵",
    "마늘 3쪽"
];

ReactDOM.render(
    React.createElement(IngredientsList, {items}, null),
    document.getElementById('react-container')
);
// component는 객체이므로 내부에 코드를 캡슐화할 수 있다.
// 그러므로 list 원소를 하나 생성하는 method를 만들어서 list를 만들 때 사용할 수 있다.
const IngredientsList = React.createClass({
    displayName: "IngredientsList",
    renderListItem(ingredient, i) {
        return React.createElement("li", { key: i }, ingredient)
    },
    render() {
        return React.createElement("ul", {className: "ingredients"},
            this.props.items.map(this.renderListItem)
        )
    }
});
  • 위의 IngredientsList에서 UI와 관계있는 부분은 모두 한 component 안에 캡슐화되어 UI 표시에 다른 아무것도 필요없게 된다.
  • 이런 방식으로 component를 사용해 리액트 element를 만들 수 있고, 그 component에 재료 list를 프로퍼티로 전달할 수 있다. 
  • component를 사용해 만들어진 element를 쓸 때는 문자열이 아니라 component class를 직접 쓴다는 점을 유의해야 한다.
  • 예를 들어, React.createElement를 호출할 때 IngredientsList 주위를 인용부호로 둘러쌀 필요가 없다. IngredientsList가 component이기 때문에 직접 그 클래스를 createElement에 넘기면 리액트는 이 클래스로 component의 인스턴스를 생성하고 직접 관리한다.
  • ES6 class를 사용해 component를 만들 수도 있는데, React.Component를 추상 클래스로 사용해 이 추상 클래스를 상속하면 custom component를 만들 수 있다.
// React.Component 추상 클래스를 상속해서 component를 만드는 예제
class IngredientsList extends React.Component {
    
    renderListItem(ingredient, i) {
        return React.createElement("li", { key: i }, ingredient)
    }
    
    render() {
        return React.createElement("ul", { className: "ingredients" },
            this.props.items.map(this.renderListItem)
        )
    }
}

 

  • 상태가 없는 함수형 component는 객체가 아니라 함수다. 그래서 그런 component 영역에는 this가 없다. 
  • 간단한 순수 함수이므로 애플리케이션에서는 가능하면 함수형 component를 사용해야 한다.
  • 상태가 없는 함수형 component는 프로퍼티를 인자로 받아서 DOM element를 반환하는 함수다.
  • 애플리케이션 architecture를 단순하게 유지할 수 있지만 기능을 캡슐화해야 하거나 this가 필요하면 이를 사용할 수 없다.
// 상태가 없는 함수형 component를 만드는 예제
const IngredientsList = props =>
    React.createElement("ul", { className: "ingredients" },
        props.items.map((ingredient, i) =>
            React.createElement("li", { key: i }, ingredient)
        )
    );

// props를 구조 분해해서 점(.) 구문을 사용해 프로퍼티에 접근하지 않아 더 간단해진 예제
const IngredientsList = ({items}) =>
    React.createElement("ul", { className: "ingredients" },
        items.map((ingredient, i) =>
            React.createElement("li", { key: i }, ingredient)
        )
    );

8. DOM 렌더링

  • 데이터를 component에 프로퍼티로 넘길 수 있으므로 UI를 만들 때 사용하는 로직과 데이터를 분리할 수 있다. 이렇게 따로 분리된 데이터는 DOM보다 훨씬 더 쉽게 다룰 수 있다. 독립된 데이터 집합의 값에 따라 애플리케이션의 상태가 바뀐다.
  • 애플리케이션에 있는 모든 데이터를 한 javascript 객체에 저장했다고 했을 떄, 이 객체를 변경할 때마다 그 객체를 component에 프로퍼티로 전달하고 UI를 렌더링해야 한다. 이러한 변경이 있을 때 ReactDOM.render는 전체 DOM을 없애고 재구축한다면 매우 비효율적일 것이다.
  • 그래서 ReactDOM.render는 현재 DOM을 그대로 두고 갱신이 필요한 DOM element만 변경해 효율적으로 DOM element를 갱신한다.

9. factory

  • factory는 객체를 인스턴스화하는 자세한 과정을 감추고 객체 생성 과정을 추상화해주는 특별한 객체다.
  • 리액트에서는 리액트 element 인스턴스를 만들 때 사용한다.
  • React.createFactory 함수를 사용하면 component를 만들어주는 factory를 만들 수 있고, 내장 factory를 사용해 리액트 element를 만들 수도 있다.
// ReactDOMFactories를 이용해 리액트 element를 만드는 예제
// 첫 번째 인자는 프로퍼티, 두 번째 인자는 자식 노드다.
ReactDOMFactories.h1(null, "Baked Salmon");

// factory와 map을 함께 사용하는 예제
var items = [
    "연어 500그램",
    "잣 1컵",
    "버터 상추 2컵",
    "옐로 스쿼시 1개",
    "올리브 오일 1/2컵",
    "마늘 3쪽"
];

var list = ReactDOMFactories.ul(
    { className: "ingredients" },
    items.map((ingredient, key) =>
        React.createElement("li", {key}, ingredient)
    )
)

ReactDOM.render(
    list,
    document.getElementById('react-container')
);

 

  • component를 함수로 만들어 코드를 단순화하고 싶다면 factory를 만들어야 한다.
const { render } = ReactDOM;

const IngredientsList = ({ list }) =>
    React.createElement('ul', null, 
        list.map((ingredient, i) =>
            React.createElement('li', {key: i}, ingredient)
        )
    );

const Ingredients = React.createFactory(IngredientsList)

const list = [
    "연어 500그램",
    "잣 1컵",
    "버터 상추 2컵",
    "옐로 스쿼시 1개",
    "올리브 오일 1/2컵",
    "마늘 3쪽"
];

render(
    Ingredients({list}),
    document.getElementById('react-container')
);

 

출처 : learning react(2018)

728x90