ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ✔ 우아한 테크러닝 3기: React & TypeScript 3회차
    우아한 테크러닝 3기 2020. 9. 9. 22:52

    ✌ 9월08일 (화) 우아한 테크러닝 3기 3회차 강의 정리

    🚀 3회차 강의 목표

    • 리액트 만들어보기
    • 리액트의 기본적인 부분들과 리액트를 구현해보자! 😤

      📌 리액트를 구현해보는 이유!
      개발자가 사용자입장에서만 사용하지 않았으면 좋겠다.
      이렇게 구현해보면서 더 좋은 개발자가 되었으면 좋겠다.

    🚀 React를 만들기 전에..

    • list와 같은 데이터를 기반으로 JavaScript를 사용할 땐 아래와 같이 rootElement.innerHTML사용하여 화면에 표시할 수 있다.

      // index.js
      const list = [
        { title: "React에 대해 알아봅시다." },
        { title: "Redux에 대해 알아봅시다." },
        { title: "TypeScript에 대해 알아봅시다." }
      ];
      const rootElement = document.getElementById("root");
      function app() {
        rootElement.innerHTML = `
            <ul>
                  ${list.map((item) => `<li>${item.title}</li>`).join("")}
            </ul>
        `;
      }
      app();
    • 위의 app()함수는 list를 바깥에서 직접 가져다 쓰기 때문에 좋지 못하다.

    • 바깥의 영향을 안 받게 순수 함수로 만들어 준다.

      // 생략..
      function app(items) {
        rootElement.innerHTML = `
            <ul>
                  ${items.map((item) => `<li>${item.title}</li>`).join("")}
            </ul>
        `;
      }
      app(list);

      🌈 React가 탄생한 이유

    • 위와 같이 rootElement.innerHTML의 DOM을 직접 사용하여 작성한다.

    • 이럴때 데이터가 많아지고 UI도 많아지고, UI가 많아지다보니 어떨땐 A라는 UI를 사용자에게 보여주고, 어떨땐 B라는 UI를 사용자에게 보여주고, C라는 이미지를 보여주게 된다. 이런것을 라우팅이라고 한다.

    • 데이터도 위와 같이 코드에 박혀 있는 것이 아니고 서버에 있다보니 API의 호출을 통해서 비동기적으로 필요한 상황에 적시적소 원하는 타이밍에 데이터를 가져온다. 이렇듯 끊임없이 변화한다.

    • 이렇게 변하다보니 한달 뒤에 또는 두달 세달 뒤에 코드를 고칠려고 할 때 생각이 잘 안난다...

    • 빨리 생각이 날 수 있는 구조가 있는데 여기에 큰 원칙으로 좋은 아키텍처는 같은 것끼리 묶어주고, 다른 것끼리 분리하는 것이다.(대원칙)

    • 이 원칙의 가장 중요한 첫 번째 할 수 있는 일은 네이밍을 잘 지어주는 것으로 네이밍만 잘해줘도 70%는 해결된다.

    • 위 코드처럼 Real DOM을 가지고 HTML UI를 조작하는 것은 안전성이 매우 떨어진다.

    • 그 이유는 Real DOM은 너무 Low Level API이기 때문이여서 추상화 수준이 높지 못하다.

    • 이것으로 조작하다보면 필연적으로 복잡도가 올라가게 되는 것이다. 그 말은 즉, 수정하기가 매우 까다롭다.

    • 이런 상황을 해결하기 위해 react와 같은 것들이 나오게 되었다!🤩

    • 브라우저는 HTML같은 것을 DOM이라는 트리 구조의 데이터로 변환해서 관리한다.

    • 이처럼 자바스크립트 자체로 직접 접근하는 것 또한 까다로우니까 Virtual DOM을 사용한 React가 나왔다.

    • 사실상 HTML 문자열을 브라우저가 DOM이라는 트리 구조의 객체 형태로 만드는 것이나 DOM을 Virtual DOM으로 만드는 것이다 결국 똑같은 컨셉이다.

    🚀 React의 기본 이해와 Virtual DOM

    • 기본 구조

      import React from "react";
      import ReactDOM from "react-dom";
      function App() {
        return (
            <div>
                <h1>Hello?</h1>
                <ul>
                    <li>React</li>
                    <li>Redux</li>
                    <li>MobX</li>
                    <li>Typescript</li>
                </ul>
            </div>
        );
      }
      ReactDOM.render(<App />, document.getElementById("root"));
    • JSX문법이며 Babel이 변환해준다.

    • Babel의 react plugin이 리액트 테그를 변환해주는 것이다. 그렇기 때문에 실제로는 결국 그냥 함수 호출에 불과하다.

    • ReactDOMrender 정적 메소드를 갖는데 2개의 인자를 받는다.

    • 첫 번째 인자는 화면에 렌더링할 컴포넌트이며 두 번째 인자는 컴포넌트를 렌더링할 HTML 요소이다.

      // Babel에서 jsx를 javasript로 변환
      "use strict";
      var _react = _interopRequireDefault(require("react"));
      var _reactDom = _interopRequireDefault(require("react-dom"));
      function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
      function App() {
          return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement("h1", null, "Hello?"), /*#__PURE__*/_react.default.createElement("ul", null, /*#__PURE__*/_react.default.createElement("li", null, "React"), /*#__PURE__*/_react.default.createElement("li", null, "Redux"), /*#__PURE__*/_react.default.createElement("li", null, "MobX"), /*#__PURE__*/_react.default.createElement("li", null, "Typescript")));
      }
      _reactDom.default.render( /*#__PURE__*/_react.default.createElement(App, null), document.getElementById("root"));

      react의 createElement는 Virtual DOM을 만들게 된다.
      자바스크립트 사이드에서는 createElement라는 함수를 이용해서 Virtual DOM이 만들어지고 리액트는 자신이 만든 것이라 잘 알기 때문에 Virtual DOM을 만든 다음에 그것을 그대로 Real DOM으로 변환해주는 것이다.
      그리고 개발자들에게는 createElement를 안쓰기때문에 컴포넌트로 좀 더 쉽게 사용하기 위해서 JSX를 제공하는 것이다.

      import React from "react";
      import ReactDOM from "react-dom";
      // StudyList 이름을 가질 수 있다.
      function StudyList() {
        return (
            <ul>
                <li>React</li>
                <li>Redux</li>
                <li>MobX</li>
                <li>Typescript</li>
            </ul>
        );
      }
      function App() {
        return (
            <div>
                <h1>Hello?</h1>
                <StudyList />
            </div>
        );
      }
      ReactDOM.render(<App />, document.getElementById("root"));
    • 위와 같이 분리를 해서 컴포넌트 이름을 지정해서 사용할 수 있다.

    • 이렇게 이름을 줄 수 있는 구조는 더 이상 MarkUp을 보고서는 우리가 어떤 데이터가 어디에 두었는지를 고민하지 않고 컴포턴트 이름만(잘 지어졌을 경우) 보고서 확인이 가능함으로써 읽기 쉬워지고, 읽기 쉬어짐에 따라 고치기도 쉬워진다.

    • 위의 StudyList 함수에서 반환하는 JSX 엘리먼트들을 Babel로 변환되면 아래와 같은 형태이다.

      function StudyList() {
        return /*#__PURE__*/_react.default.createElement("ul", null, /*#__PURE__*/_react.default.createElement("li", {
          className: "item"
        }, "React"), /*#__PURE__*/_react.default.createElement("li", {
          className: "item"
        }, "Redux"), /*#__PURE__*/_react.default.createElement("li", {
          className: "item"
        }, "MobX"), /*#__PURE__*/_react.default.createElement("li", {
          className: "item"
        }, "Typescript"));
      }
    • 변환된 상태를 참고하여 객체로 바꾸어보면 아래의 vdom과 같은 형태이다.

      const vdom = {
        type: "ul",
        props: {},
        children: [
            { type: "li", props: { className: "item" }, children: "React" },
            { type: "li", props: { className: "item" }, children: "Redux" },
            { type: "li", props: { className: "item" }, children: "Typescript" },
            { type: "li", props: { className: "item" }, children: "MobX" },
        ],
      };
    • 이렇게 확인해보면 왜 React가 결국 맨 상위 단 하나의 최상위 부모 컨포넌트가 있을 수 밖에 없는지 알 수 있는 구조이다.

    • 이렇듯 react는 Virtual DOM이 단 하나인 이유이다.

    🚀 React 만들어보기!

    • 위의 JSX가 변환된 모습을 보면 createElement는 아래와 같은 형태로 이루어져있다.

      // props는 없으면 빈 객체, children는 여러 개의 배열
      function createElement(type, props = {}, ...children) {
      return { type, props, children };
      }
    • 위에서 만들어본 vdomcreateElement를 이용하여 만들 수 있다.

      // 예시
      const vdom = createElement("ul", {}, createElement("li", {}, "React"));

    • createElement는 vdom 객체를 만드는 함수고 render할 때, render 객체는 계속 쫒아가면서 realdom으로 변환한다.

    • 최종적으로 다 만들면 root 컴포넌트 컨테이너한테 append child를 하는 방식이다.

    • 바벨의 옵션을 이용해서 맨 상단에 주석을 넣어준다.

    • @jsx의 지시어를 이용해서 내가 원하는 이름을 주면 createElement가 원하는 이름으로 변경이 된다.

      /* @jsx createElement */
      function StudyList(props) {
        return (
            <ul>
                <li className="item">React</li>
                <li className="item">Redux</li>
                <li className="item">MobX</li>
                <li className="item">Typescript</li>
            </ul>
        );
      }
    • 이 말을 즉, 바벨만 가지고 Virtual DOM을 만들 메소드(트랜스파일될 메소드)를 임의로 바꿔서 내가 만든 다른 것으로 교체가 가능하다.

    • 이 과정은 컴파일 타임이다.

      // createElement의 이름으로 변환되었다.
      "use strict";
      /* @jsx createElement */
      function StudyList(props) {
      return createElement("ul", null, createElement("li", {
        className: "item"
      }, "React"), createElement("li", {
        className: "item"
      }, "Redux"), createElement("li", {
        className: "item"
      }, "MobX"), createElement("li", {
        className: "item"
      }, "Typescript"));
      }
    • @jsx지시어를 사용하면 js문법을 찾아서 자동으로 변환을 해준다.

    • 이렇게 createElement로 변하게 되고 우리가 만든 createElement함수로 들어가게 된다.

      /* @jsx createElement */
      function createElement(type, props = {}, ...children) {
      return { type, props, children };
      }
      function StudyList(props) {
      return (
        <ul>
          <li className="item" label="haha"> React </li>
          <li className="item">Redux</li>
          <li className="item">TypeScript</li>
          <li className="item">Mobx</li>
        </ul>
      );
      }
      function App() {
      return (
        <div>
          <h1>Hello?</h1>
          <StudyList item="abcd" id="hoho" />
        </div>
      );
      }
      //  변환되면 console.log(createElement(App, null));
      console.log(<App />); 
    • 이렇게 console.log(<App />); 해보게 되면 아래 사진처럼 App함수가 들어간다.

    • type의 타입을 비교하는 로직을 추가해준다.

      function createElement(type, props = {}, ...children) {
      if (typeof type === "function") {
          // 호출해준다.
        return type.apply(null, [props, ...children]);
      }
      return { type, props, children };
      }

    🌈 리액트에서 사용자 컴포넌트가 대문자로 시작하는 이유

    • 변환된 App안의 태그들을 확인해보면 문자열로 들어가는 것을 확인할 수 있다.
      function app() {
      return (
        <div>
          <h1>Hello?</h1>
          <StudyList item="abcd" id="hoho" />
        </div>
      );
      }
      // 변환
      function app() {
      return createElement("div", null, createElement("h1", null, "Hello?"), createElement(StudyList, {
        item: "abcd",
        id: "hoho"
      }));
      }
    • 하지만 대문자로 시작한 App은 함수 그 자체로 넣어버린다. (Type이 function)
      // 대문자일 때
      console.log(<App />);
      // 변환
      console.log(createElement(App, null));
      // 소문자일 때
      console.log(<app />);
      // 변환
      console.log(createElement("app", null));
    • 이처럼 리액트에서는 사용자 컴포넌트는 무조건 대문자로 시작해서 사용해야 한다.
    • createElement로 생성된 객체를 실제 DOM으로 구성하는 render 함수를 작성한다.

    • createElement로 생성된 객체를 보면 같은 형태로 계속 이루어져있다.

    • 그리고 마지막에는 전부 appendChild를 해준다.

      // 재귀함수
      function renderElement(node) {
      if (typeof node === "string") {
        return document.createTextNode(node);
      }
      const el = document.createElement(node.type);
      node.children.map(renderElement).forEach((element) => {
        el.appendChild(element);
      });
      return el;
      }
      function render(vdom, container) {
      container.appendChild(renderElement(vdom));
      }
    • props를 넘겨받는 컴포넌트의 경우에도 같은 형식으로 적용할 수 있다.

      function Row(props) {
      return <li>{props.label}</li>;
      }
      function StudyList(props) {
      return (
        <ul>
          <Row label="하하하" />
          <li className="item" label="haha">React</li>
          <li className="item">Redux</li>
          <li className="item">TypeScript</li>
          <li className="item">Mobx</li>
        </ul>
      );
      }
    • render 함수를 사용하여 real DOM의 HTML에 붙여 넣을 수 있다.

      render(<App />, document.getElementById("root"));

    📌 전체 소스는 GitHub 확인해주세요! 🙏

    🚀 React의 클래스 컴포넌트와 함수 컴포넌트

    🌈 클래스 컴포넌트

    • 클래스 컴포넌트는 라이프사이클 API를 사용하여 state를 생성자에서 초기화할 수 있다.
      class Hello extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 1
        };
      }
      componentDidMount() {
        this.setState({ count: this.state.count + 1 });
      }
      render() {
        return <p>안녕하세요!</p>;
      }
      }
    • react 공식문서 확인하기 ❗❗

    🌈 함수 컴포넌트(Hook)

    • 함수 컴포넌트는 초창기에는 상태는 함수가 호출될 때 마다 생성되기 때문에 유지될 수 없었기 때문에 상태를 관리하지 못한다고 여겨져 사용하지 않았다.
    • 아래처럼 안에 변수는 함수 내부의 상태처럼 여겨졌다.
      function App() {
        let x = 10;
        return (
            <div>
                <h1>상태</h1>
                <Hello />
            </div>
        );
      }
    • 하지만 함수 컴포넌트도 Hooks가 나온 뒤 상태 관리를 할 수 있게 되었다.
      function App() {
      // 반환하는 값은 배열
      const [counter, setCounter] = useState(1);
      // const counter = result[0];
      // const setCounter = result[2];
      return (
        <div>
          <h1 onClick={() => setCounter(counter + 1)}>상태{counter}</h1>
          <Hello />
        </div>
      );
      }
    • h1 태그를 클릭하면 counter 상태가 1씩 증가한다.
    • hook은 항상 최상위(at the Top Level)에서만 Hook을 호출해야 해야한다. react Hooks 규칙 참고
    • 최상위가 아닌 부분에서 호출 될 경우 전역 배열에 원하지 않는 값을 반환하여 문제가 발생 할 경우가 생길 수 있다.
    • 공식 문서를 참고하자! 😤

    🚀 우아한 테크러닝 3기 3회차 후기

    이번 시간은 리액트를 직접 만들어보았다. 😀
    만들어보면서 계속 파보면서 여태까지 아무것도 모르고 리액트를 사용했다고 생각했다..
    이번 계기로 왜? 라는 질문을 계속 스스로에게 해야할 거 같다.
    그냥 표면적으로 보이는 것만 보지 말고 원리를 이해하도록 노력해야 할 거 같다.
    3회차까지 들으면서 김민태 강사님의 열정과 정성이 느껴져서 너무 좋았다.(2회차는 10시 반까지.. 3회차도 7시부터 QnA 시간..)
    내일이면 반을 했는데 끝나면 아쉬울거 같다. 열심히 듣자.😤

    댓글

Designed by Seungmin Sa.