ABOUT ME

-

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

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

    🚀 4회차 강의 목표

    • 💻 React 컴포넌트의 상태와 관리
    • 🎯 커뮤니케이션!!
    • 😤 비동기와 제너레이터
      • 타입스크립트는 다음주부터! 🤩

    🚀 React 컴포넌트의 상태와 관리

    React 애플리케이션을 만든다.

    • index.js
      import React from "react";
      import ReactDOM from "react-dom";
      ReactDOM.render(
        <React.StrictMode>
            <App />
        </React.StrictMode>,
        document.getElementById("root")
      );
    • App.js
      import React from "react";
      const App = () => {
        return (
            <div>
                <header>
                    <h1>React and TypeScript</h1>
                </header>
                <ul>
                    <li>1회차: Overview</li>
                    <li>2회차: Redux 만들기</li>
                    <li>3회차: React 만들기</li>
                    <li>4회차: 컴포넌트 디자인 및 비동기</li>
                </ul>
            </div>
        );
      };
      export default App;

    위와 같은 형태를 되어있는 것을 li 태그(상태 값) 데이터를 객체로 만들어 index.js에서 App컴포넌트로 값을 넘겨준다.(아래)

    • index.js
      import React from "react";
      import ReactDOM from "react-dom";
      import App from "./App";   
      const rootElement = document.getElementById("root");
      const sessionList = [
        { title: "1회차: Overview" },
        { title: "2회차: Redux 만들기" },
        { title: "3회차: React 만들기" },
        { title: "4회차: 컴포넌트 디자인 및 비동기" }
      ];
      ReactDOM.render(
        <React.StrictMode>
          <App store={{ sessionList }} />
        </React.StrictMode>,
        rootElement
      );
    • App.js
      import React from "react";
      const App = (props) => {
        const {sessionList} = props.store;
        return (
            <div>
                <header>
                    <h1>React and TypeScript</h1>
                </header>
                <ul>
                   {sessionList.map((session) => (
                        <li>{session.title}</li>
                    ))}
                </ul>
            </div>
        );
      };
      export default App;

    이렇게 li태그를 map을 사용하여 생성할 수 있다.

    • 하지만 이 코드는 좋지 못하다.

      모든 리액트 컴포넌트 안에 리턴되는 JSX들이 있는데 코드가 코드로써 렌더링되는 코드들이 많아지면 readability(가독성) 이 떨어진다. 그렇기 때문에 컴포넌트를 한번 더 감싸서 만드는 것이 좋다.

    • App.js

      import React from "react";
      // li 태그 분리
      const SessionItem = ({ title }) => <li>{title}</li>;
      // App 컴포넌트
      const App = (props) => {
        const {sessionList} = props.store;
        return (
            <div>
                <header>
                    <h1>React and TypeScript</h1>
                </header>
                <ul>
                   {sessionList.map((session) => (
                        <SessionItem title={session.title} />
                    ))}
                </ul>
            </div>
        );
      };
      export default App;

      이렇게 분리를 하면 훨씬 더 좋은 가독성이 생긴다.
      하지만 지금의 컴포넌트는 상태를 가지고 있지 않는 컴포넌트이다.

      버튼(토글형식)을 누르면 오름차순, 내림차순으로 바뀌는 상태를 가진 컴포넌트를 만들어보자!

    • App.js

      import React from "react";
      const App = (props) => {
        let displayOrder = "ASC";
        const { sessionList } = props.store;
        // order라는 이름으로 인덱스를 넣어준다.
        const orderedSessionList = sessionList.map((session, i) => ({
            ...session,
            order: i,
        }));
        const toggleDisplayOrder = () => {
            displayOrder = displayOrder === "ASC" ? "DESC" : "ASC";
        };
      
        return (
            <div>
                <header>
                    <h1>React and TypeScript</h1>
                </header>
                <button onClick={toggleDisplayOrder}>재정렬</button>
                <ul>
                    {orderedSessionList.map((session) => (
                        <SessionItem title={session.title} key={session.id}/>
                    ))}
                </ul>
            </div>
        );
      };
      export default App;
    • 하지만 위와 같은 상태에서는 버튼을 클릭해봤자 displayOrder의 값은 변경되지 않고 다시 렌더링되지도 않는다.

    🌈 함수형 컴포넌트 왜 렌더링되지 않았을까?

    • 위 상황에서 함수 컴포넌트는 onClick={toggleDisplayOrder}를 넣어서 버튼을 클릭하고 값을 바꿔봐도 갱신이 되지않는다.
      리액트 입장에서 이 App이 다시 호출되서 Virtual DOM이 다시 만들어지고 다시 DOM에다가 다시 업데이트를 해야하는데 안에서 다시 호출한다는 무언가의 신호(signal)을 함수 바깥으로 보낼 수가 없다. 왜냐하면 함수는 이미 끝났기 때문이다.

    • 그래서 초창기에는 클래스 컴포넌트를 사용할 수 밖에 없었던 것이다.

    🌈 클래스 컴포넌트는 어떻게 상태가 바뀔까?

    함수형 컴포넌트를 클래스 컴포넌트로 간단하게 바꿔본것이다.

    • App.js
      class ClassApp extends React.Component {
      constructor(props) {
        // this.props 컨텍스트 객체
        super(props); // 컨벤션
        // this.onToggleDisplayOrder = this.onToggleDisplayOrder.bind(this);
        this.state = {
          displayOrder: "ASC"
        };
      }
      // onToggleDisplayOrder(){
      //   // 그냥 함수 실행 컨텍스트를 따른다.
      //   this.setState({
      //     displayOrder:displayOrder ==='ASC' ? 'DESC' : 'ASC'
      //   })
      // }
      toggleDisplayOrder = () => {
        this.setState({
          displayOrder: displayOrder === "ASC" ? "DESC" : "ASC"
        });
      };
      render() {
        return (
          <div>
            여기여기
            <button onClick={this.onToggleDisplayOrder}>정렬</button>
          </div>
        );
      }
      }
    • 여기서 superextends뒤에 있는 것이고 prototype chain의 상위 부모이기 때문에 super한테 그대로 넘겨주면 리액트가 props에다가 넣어준다. (컨텍스트 객체)
    • 기본값을 this.state안에다가 넣어준다.
    • 여기서 일반함수 onToggleDisplayOrder는 실행 컨텍스트를 따라서 this가 바뀔 수 있는데, arrow 함수는 렉시컬 컨텍스트(문맥 컨텍스트)를 따르기 때문에 지금 컴파일 타임의 이 순간(코드흐름)대로 따라가게 된다. 그래서 바인딩도 필요가 없게 된다.

    🌈 그래서 중요한건 어떻게 상태를 가질 수 있을까?

    • 이런 과정의 이후에는 이 컨텍스트 객체만 가지고 만들어진 인스턴스 객체를 가지고 리액트를 핸들링하기 떄문에 없어지지 않는다. 즉, 다시 호출된다는 개념이 아니라 이미 존재하는 객체가 업데이트 되었으면 그 객체 안에 render메소드를 호출하는 개념이다.
    • 그래서 this를 다시 만드는 것이 아니기 때문에 클래스 컴포넌트가 상태를 가질 수 있는 것이다.

    🌈 함수형 컴포넌트 어떻게 상태를 가질까?

    • 애초에 함수형 컴포넌트는 상태를 갖는건 불가능하다.
    • 하지만 react 팀에서 그 함수의 호출과정 언제 호출되는지, 순서등과 같은 것들을 모두 다 제어하고 있기 때문에 그거에 맞춰서 각자의 함수에 상태를 저장하는 것을 제공해주면 함수도 상태를 가질 수 있다라고 나온 것이 react hooks 이다.
    • 결국 클래스 컴포넌트의 render가 다시 불러오는 것과 같은 맥락이다.

    🌈 함수형 컴포넌트를 선호하는 이유

    • App클래스 컴포넌트만 봐도 상태가 등장하는 것에 여러 메소드들이 분산이 되어있다.
    • 함수형 컴포넌트는 이와 다른데 함수하나에 모두 몰려있는 것을 볼 수가 있는데 이런 것을 응집성이라고 하는데 모여있으면 훨씬 보기에 좋고 여러가지 측면에서 장점이 있다고 얘기를 한다. 또한 선언적이다.

    🌈 함수형 컴포넌트의 Hook을 사용한 마무리

    • App.js

      import React from "react";
      const SessionItem = ({ title }) => <li>{title}</li>;
      const App = (props) => {
      const [displayOrder, toggleDisplayOrder] = React.useState("ASC");
      const { sessionList } = props.store;
      const orderedSessionList = sessionList.map((session, i) => ({
        ...session,
        order: i
      }));
      
      const onToggleDisplayOrder = () => {
        toggleDisplayOrder(displayOrder === "ASC" ? "DESC" : "ASC");
      };
      
      return (
        <div>
          <header>
            <h1>React and TypeScript</h1>
          </header>
          <p>전체 세션 갯수: 4개 {displayOrder}</p>
          <button onClick={onToggleDisplayOrder}>재정렬</button>
          <ul>
            {orderedSessionList.map((session) => (
              <SessionItem title={session.title} key={session.id} />
            ))}
          </ul>
        </div>
      );
      };
      export default App;

      이렇게 useState Hook을 사용하여 상태를 관리할 수 있게 되었다.
      전체 소스 코드는 GitHub 참고

    🌈 함수형 컴포넌트의 Hooks는 클로저이다?? 🤔

    • 실제로 Hooks는 클로저가 아닌거 같다! 🙄 하지만 Hooks를 사용할땐 당연히 클로저가 전달된다!
    • 위의 displayOrder의 값의 상태를 변경할려면 당연히 toggleDisplayOrder함수를 호출해야 되고, 이 함수는 반드시 다른 함수 안에 콜백으로 들어갈 수 밖에 없는 구조이다. 😮 이 상황에서 클로저가 잡하는 것이다.
    • 왜나하면 toggleDisplayOrder 함수가 호출되는 시점에 호출되면 안되는 것은 호출을 하면 다시 render가 될 것이고 그렇게 render가 되고 다시 toggleDisplayOrder 함수가 호출되기 때문에 무한루프에 빠지게 되는 것이다.

      그렇기 때문에 Hook 자체가 클로저가 아니라 함수안에서 클로저가 잡히는 것이다! 🚀

    🌈 클로저 같은 것들이 메모리가 만들어졌으면 이걸 언제 해제하거나 제거할 수 있을까?? (메모리 할당/해제)

    • 아래의 예를 보자.

      // ex)
      useEffect(() => {
        document.title = '';
      })

      위를 확인해보면 document.title은 리액트와 무관한 바깥에서 일어나는 일이다.
      이런 것들을 side-effect라고 하는데 제어가 불가능하고 부수효과를 이르킬수 있는 요소들을 말하는 것이다.

    • useEffect안에서 return을 함수로 해주면 실제로 UI에서 사라질 때 이 함수를 호출을 해주는데 정리할 리소스들을 해제할 것들을 하라고 나와있다. (공식문서 참조)

      // ex)
      useEffect(() => {
        // ...
        return () => {
      
        }
      })
    • 하지만 위에서 return을 함수로 호출하는 방법은 정리 대상의 요소들은 실질적인 대상에 불과하고 메모리, 변수, 클로저 공간, 스코프 공간 이런 것들은 대상이 아니다.

    • JavaScript 에서 변수안에 있는 데이터 혹은 객체를 임의로 지우는 방법은 존재하지 않는다. (언어적으로 제공하지 않는다.)
      하지만, 무한히 생성되는 것은 아니고 GC(가비지 컬렉션)이라는 메커니즘이 존재하는데 사용하지 않는 것들은 수집해서 자바스크립트가 Mark-and-sweep이라는 알고리즘을 사용하여 제거를 한다.

    • 그때 컴포넌트가 사라지면 같이 그 안에 존재하던 일반 객체 뿐만 아니라 클로저와 같은 스코프 공간들도 참조가 없어지면 GC가 일어나는 것이다.

    • 자바스크립트에서 그런 종류의 데이터 스토리지들을 명시적으로 제거하는 방법은 제공하지 않기 때문에 react에서 clean up 대상은 클로저 같은 것이 아니기 때문에 굳이 메모리에 대해서 신경을 쓰지 않아도 된다. (알아서 해준다.)

    🚀 커뮤니케이션에 대해서..

    • 커뮤니케이션은 일종의 프로토콜(정보를 주고 받기 위한 약속)과 같다.
    • 이렇듯 일하면서나 면접에서 봐도 이 프로토콜이 안 맞는 경우가 굉장히 많다. 그리고 더욱 더 큰 문제는 프로토콜이 안 맞는다라는 것을 의식조차 못하고 있는 사람들도 꽤 많다는 것이다.
    • 그렇기에 프로토콜이라는 것은 정보를 주고 받는다는 건(이해를 한다는 건) 어느 정도의 맥락(수준)이 일치 되야 한다는 것이다.
      이 수준의 layer라고 생각을 하면 어떤 A를 상대방에게 전달할 때 상대방이 이해할 수 있는 layer를 파악하고 있는 것이 굉장히 중요하다. 즉 이말은 개발자가 개발자가 아닌 사람한테 개발용어를 설명한다는 것과 비슷한 것이다.
      그런 부분을 잘 알고 있어야하고 일방적인 소통을 하면 안된다는 점이다.
    • 학습하는 부분에 있어서도 같은 맥락이라 볼 수 있다.
    • 학습도 나와의 커뮤니케이션이라는 것으로 그 지식이 나와 맞으면 그 지식이 잘 들어온다. 하지만 맞지 않는다면 그 원하고자 하는 지식은 눈에 들어오지 않는 것이다.
    • 그렇기 때문에 어느 layer의 지식이 나와의 프로토콜에 맞을까를 생각해서 스스로 조정이 필요하다.😤
    • 또한 어느 지식에 대해서 그 하나만 몰입하지 않았으면 좋겠고 너무 고급기술만 배울려고 하지 않았으면 좋겠다.

      🙏 신입 개발자 또는 주니어 개발들에게..
      신입분들은 대부분 의욕이 넘쳐나고 충만한 상태인데 그 의욕은 굉장히 좋지만 반대로 매우 위험한 상태일 수도 있다.
      어느 한쪽에 몰입하여 잘 못 빠지게 되면 훅 들어가 버릴 수가 있다.
      그렇기 때문에 항상 자기를 돌아봐보는 시간을 갖는 것이 좋을 거 같다.🌈

    🚀 비동기와 제너레이터

    🌈 제너레이터

    • 제너레이터는 function * 으로 사용된다.

      function* foo() {}
    • 아래 코드를 확인해보자.
      x값이 확정짓기 전에 계산을 수행할 수 없기 때문에 순차적으로 실행될 수 밖에 없다.
      즉, y의 변수에 x의 의존성이 있기 때문에 x가 없이 y가 생길 수 없게 되는 것이다.

      const x = 10;
      const y = x*10;
    • 함수인 경우는??
      함수는 호출을 하면 그냥 함수만 담고 실제로 x값을 확정하는 순간을 지연시킨다.(지연호출)

      const x = () => 10;
      const y = x*10;
    • Promise도 이 지연과 굉장히 비슷하다.

      const p = new Promise(function (resolve, reject) {
      // 함수로 실행해서 지연을 일으킨다.
        setTimeout(() => {
          resolve("1");
        }, 100);
      });
    • 제너레이터는 제너레이터 객체를 반환하는데 제너레이터 객체는 코루틴이라는 함수의 구현체다.

      • 코루틴이란??🤔
        함수는 인자를 받고 무언가 계산을 하고 리턴을 한다. 그리고 한 번 호출하고 결과를 받거나 그 다음 스텝을 실행한다.
        이런 상황을 폭 넓게 넓혀 호출자한테 리턴을 여러 번 할 수 있게 해주는 것이다.
        리턴을 여러 번 한다는 것은 다시 함수가 호출될 때 처음부터 시작되는 것이 아니라 마지막 리턴한 지점부터 다시 시작한다는 컨셉이고 함수를 조금 더 일반화한 컨셉으로 확장해서 기능을 추가를 해 놓은 것이 코루틴이다.
        자바스크립트에서는 그 코루틴의 개념을 일부 차용해서 제너레이터라고 명명하였다.
    • 일반적인 함수인 경우는 make함수의 반환 값을 1이 될 것이다.

      function make(){
        return 1;
      } // 1
      // 제너레이터
      function* make() {
        return 1;
      }
      console.log(make());
    • 아래와 같이 function*를 사용하면 이 제너레이터 객체를 반환한다.

    • 제너레이터는 yield과 같이 사용한다.

      function* makeNumber() {
      let num = 1;
      // 무한 루프이지만 문제가 없다.
      while (true) {
        // 제너레이터 안의 리턴이지만 함수를 끝내지 않고 바깥으로 내보낸다.
        // 기존 상태를 저장하고 있다.
        yield num++;
      }
      }
    • 아래에 makeNumber() 제너레이터를 생성해도 값이 반환되지 않고 객체를 넘겨준다.

      const j = makeNumber();
    • 위의 makeNumber를 호출하면 제너레이터 객체를 넘겨주고 여기에는 next라는 메소드가 있다.

      console.log(j.next());
    • 콘솔에 찍힌 값을 확인해보면 valueyield로 반환된 값이며 donetrue일 경우 제너레이터가 종료된다.
      즉, yieldreturn이 나올 때 까지 실행하는 것이다.

    • 제너레이터는 next는 값을 넣을 수도 있다.

      function* makeNumber() {
        let num = 1;
        while (true) {
          const x = yield num++;
          console.log(x);
        }
      }
      const j = makeNumber();
      console.log(j.next());
      console.log(j.next("a"));
    • 아래 사진과 같이 next에 매개변수로 넘겨준 문자열 ayield의 반환값으로 담겨서 사용할 수도 있는 것을 확인할 수 있다.

    • 제너레이터를 활용해서 비동기함수를 동기적으로 나타낼 수 있다.

      const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
      // 동기 함수처럼 보인다.
      // 바깥쪽에서 상황을 해결한다.
      function* main() {
        console.log("시작");
        yield delay(3000);
        console.log("3초뒤");
      }
      const it = main();
      const { value } = it.next();
      console.log(value);
    • 위의 코드를 실행하면 시작이 출력되고 3초 뒤에 3초 뒤라고 출력된다.

    • async/await를 사용해서 비슷하게 사용할 수 있다.

      // async
      async function main2() {
      console.log("시작");
      await delay(3000);
      console.log("3초뒤");
      }
      main2();
    • async/awaitPromise에 최적화 되어있다.

    • 그렇지만 제너레이터를 사용할 경우 Promise 형태의 값이 반환되지 않아도 사용이 가능하기 때문에 더 다양한 상황에서 응용이 가능하다.

    • 전체 소스는 GitHub를 참고 🙏

    🚀 4회차 후기

    오늘도 역시나 역시 배운게 많았다. 생소한 용어도 많이 있었다. 찾아서 공부를 더 해봐야겠다.😤
    다음 주부터는 typescript에 대해서 배운다는데 집중해서 들어야겠다..📖
    벌써 반이나 와버렸다.. 시간이 너무 빠르다. 블로깅하는 것이 쉽지않은데 끝까지 아자아자! 해야겠다!!!✨

    댓글

Designed by Seungmin Sa.