ABOUT ME

-

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

    ✌ 9월 3일 우아한 테크러닝 3기 2회차 정리

    🚀 2회차 강의 목표

    • JavaScript에 대한 지식을 A~Z까지 할 수 있는 만큼 해보기! (70%)
    • JavaScript의 아주 기초적인 내용보단 전체적인 흐름을 타면서 스펙들을 쭉 훑어보기
    • 상태관리자인 Redux를 직접 만들어보기! 만들어보면서 어떤 기술로 이루어져있는지! (30%)

    🚀 JavaScript 훑어보기

    • 변수에 이 이라는 것을 넣을 수 있다.

      var x = 10; //x: 변수 10: 값
      let y = 10;
      const z = 10;

    🌈 값에 대해서

    📌 값에 대해서 굉장히 강조를 하셨다.

    • 아주 바닥의 절대불변의 원칙같은 것들을 세워놓고 (ex. 1=1, 1+1=2 불변(공리)) 이런 원칙을 충분히 이해하면 아무리 복잡한 것이라도 그 원칙으로 하나씩 벗겨나가면 굳이 안 배우고도 이해하고 그것들을 파악해 나가는 장점이 있는데 프로그래밍 언어도 마찬가지이다.

    🌈 JavaScript에서의 값

    • 자바스크립트에서는 값이라고 정의한 녀석들은 변수에 넣을 수 있다.
    • 그렇다면, 문자열도 값이라고 하니까 변수에 넣을 수 있고, 숫자, 객채도 넣을 수 있다.
    • 함수도 자바스크립트에서 값이라고 규정했기 때문에 당연히 함수도 값으로써 변수에 넣을 수 있다. 그렇기 때문에 자바스크립트에서는 거의 모든 값이 값이다.

    🌈 JavaScript에서의 함수

    • 자바스크립트의 함수는 반드시 값을 반환하게 되어있다. (undefinded라도..)

      function foo(){
        return 0;
      }

      📌 값을 반환하는 두가지 방법

    1. 명시적으로 리턴하는 방법.(return이 없으면 undefinded 반환)

    2. 호출할 때 앞에 new 연산자로 호출하면 이 함수는 new 연산자의 작용으로 명시적 리턴이 없어도 객체를 반환하는 인스턴스 객체

      new foo();
      let y = foo;

      🌊 JavaScript 함수가 굉장히 유연함(어려운) 점은?

    • 함수 선언문(function declaration) (세미콜론이 없다.)

    • 함수 표현식(function expression)(세미콜론이 붙는다.)

      // 함수 선언문(함수 정의문)
      function foo(){}
    • 함수 표현식은 값이라고 취급했기(변수에 넣을 수 있다.) 때문에 세미콜론이 붙을 수 있다.

      // 함수 표현식
      const bar = function bar() {};
      // bar안에 함수가 존재하니까 호출이 가능하다.
      bar();
    • 이때 이 함수가 이름을 가져도 함수 바깥쪽에서는 이 안 쪽 bar를 모른다. 그래서 const bar변수가 함수 이름을 대신한다.

    • 그러힉 떄문에 안쪽 선언된 bar는 의미가 없어서 보통 이름 없는 함수로 사용하여 생략이 가능하다.

    • 표면적으로는 이렇게 생각할 수 있지만 정확히는 함수를 값으로 취급할 땐 이름을 생략할 수 있다라고 말한다.

    • 함수가 값으로 취급해서 변수에 넣고 있기 때문에 이름을 생략할 수 있는 것이다.

      const bar = function () {};
    • 만약 단독으로 이름 없는 함수를 사용한다면 에러가 발생한다.

      function () {}
    • 이 상황은 함수를 값으로 취급하는 상황이 아니기 때문이다.

    • 그렇다면 내가 이 함수를 값으로 취급하고 싶을 땐 명시적으로 자바스크립트 엔진에게 알려줘야 한다.

    • 보통 자바스크립트는 값을 만들 때 괄호를 쓰게 되어있다.

      (function () {})
    • 하지만 이 상황은 함수를 만들기만 하고 넣지 않은 상태이다. 이유는 함수는 이름이 있어야 호출할 수 있기 때문이다.

    • 단 한가지 방법은 만들자마자 즉시 호출하는 방법이다.

      (function () {})();
    • 이것이 _Immediate Function Call_이라고 해서 만들자마자 호출이 되고 호출이 끝나고 리턴이 되면 이름이 없기 때문에 다시 호출할 수 없는 것이다.

    • 프로그래밍 상에서 단 한 번만 호출하고 싶은 상황이 플요할 때 이런 방법을 사용한다.(초기화 코드)

    🌊 콜백함수와 합성 함수(Higher-Order Function, 일급 함수)

    • 위에서의 개념을 확장하여 변수가 어디 등장하는지 생각해보자.

    • 변수는 다시 생각해보면 결국 변수를 읽으면 이다. 그렇기 때문에 결국 변수도 값이되는 것이다.

    • 그러면 변수는 값이 등장하는 위치정도라고 생각할 수 있다.

      function foo(x){
        return 0;
      }
    • 인자로 x를 넣어주면 이 x도 결국 변수이자 값이 되는 것이다.

    • 리턴할 때도 값이니까 함수를 리턴할 수 있다.

    • 함수에서도 값이 위치할 수 있다는 소리로 foo함수를 호출할 때 값으로써 x로 전달한 값을 함수도 전달할 수 있다는 얘기이다.

      function foo(x) {
      x();
      return function () {};
      }
      const y = foo(function () {});
    • 다시 foo함수가 리턴하는 값은 함수이기 떄문에 다시 함수를 받을 수 있다.

    • 이 인자로 함수를 전달하는 방법을 콜백함수라고 한다.

    • 또한, 값이 함수를 리턴되는 겂을 소프트웨어 공학에서는 보통 함수 합성이라고 한다.

    • 실행되는 순간 만들어내는 테크닉일 때 많이 쓰인다.

    • 함수를 인자로 받고, 함수를 리턴하는 함수, 이런 함수를 일급함수(Higher-Order Function) 라고 한다. 리액트에서는 같은 개념으로 (HOC, Higher-Oder Component)가 있다.

      함수를 이해할 때 함수를 어떤 값을 입력으로 주면 계산해서 출력하는 애라고 이해하지말고, 코드를 묶고 있는 값으로 이해하면 훨씬 쉽다.
      함수를 리턴하는 함수는 어떤 계산 결과를 즉시 확정해서 내보낼 수 없는 것이다.
      완벽하게 확정할 수 없는 것을 확정할 수 있는 코드를 줘서 리턴하는 것과 비슷한 컨셉이다.

    📌 JavaScript의 문법은 크게 보면 두가지로 나눌 수 있다.

    • 식: 코드를 실행하면 실행의 결과가 값으로 마무리가 되면 그건 전부다 식이다.

      // ex)
      0, 1+10, foo(), 1+10+foo()
    • 문: 실행했을 때 실행의 결과가 안나온다.

      조건문(conditional statement) : if, switch, (삼항연산자는 식)
      반복문: while, do while, for.. (foreach, map, reduce등은 함수 호출 식)

    • 식과 문의 차이는 세미콜론이 있냐 없냐 차이로 결국 세미콜론은 식의 마무리라 할 수 있다.

    🌊 Arrow Function(화살표 함수)

    • return의 생략은 이 함수가 한 줄이라는 것 자체가 바로 return하겠다라고 하는 것을 함유하고 있다.
    • 화살표 함수는 이름이 없기 때문에 재귀호출이 불가능하다.
      const bar = x => x*2;
      console.log(bar(2)); // 4
      //
      const myX = 10;
      const myY = (x) => x * 10;
      console.log(myX, myY(1)); // 10, 10
    • 화살표 함수는 자신의 this가 없다.
    • 화살표 함수는 바로 바깥 범위에서 this를 찾는 것으로 검색을 끝내게 된다.
    • 화살표 함수는 arguments 객체를 바인드 하지 않는다.
    • 화살표 함수는 생성자로서 사용될 수 없으며 new와 함께 사용하면 오류가 발생한다.
      const Foo = () => {};
      const foo = new Foo(); // TypeError: Foo is not a constructor
    • 화살표 함수는 prototype 속성이 없다.
      const Foo = () => {};
      console.log(Foo.prototype); // undefined

    📌 new연산자를 호출하면 내부적으로 어떤 메커니즘이 작동하는가?

    • 빈 객체를 만들고 함수한테 전달하는데 이 함수한테 빈 객체가 무엇인지를 알려줄 방법으로 this라고 명명하는 것이다.
    • 그래서 이 함수 안에 thisnew연산자를 호출했을 때 생성된 빈 객체이다. (동적 바인딩)
      function my() {
      //자바스크립트가 생성한 빈객체
      // 동적 바인딩을 이용하는 것
      this.name = 10;
      }
    • new연산자로 호출한 함수는 return이 명시적으로 없어도 반환 값으로 새로 만든 this가 리턴이 된다.
    • 이런 과정이 내부적인 메커니즘은 생성자가 동작된 것이다.
    • new연산자는 이렇게 새로운 객체를 호출할 때마다 만들어 낸다.
    • 함수가 그런 객체를 구조적으로 형태를 만들어 내는 의미에서 생성자라고 부른다. 내부적인 메커니즘으로는 prototype이라는 메커니즘을 사용한다.(다음시간에..)
    • 이렇게 만들어진 객체를 인스턴스 객체라고 한다.
      // 인스턴스 객체
      const g = new my();
      if (g instanceof my) {
        console.log('true'); //  true
      }
      console.log(g); // my {name: 10}
    • 그래서 es6에서 좀 더 명시적으로 나타내기 위해서 class가 등장한다.

    🌈 Class

    • Class
      class bar {
        // 생성자
        constructor() {
          this.age = 10;
        }
      }
      console.log(new bar()); // bar {age: 10}
      📌 함수와 클래스가 다른 점
    • constructor가 명확하게 들어나있다.(명시적)
    • new연산자를 앞에다 붙여서 호출을 했을 때 함수new 연산자와 new 연산자 없이 호출이 가능하기 때문에 사용자 입장에서는 new 함수를 감지할 수 없다. (명시적이지 못하다.)
      // 앞에 대문자로 생성자 함수를 구분하는 처절함..
      const Bar(){
        this.age = 10;
      }
      const bar = new Bar();
    • class로 만들면 new호출하지 않으면 호출이 불가능하다.
    • 여기서 this실행 컨텍스트 지시어라고 한다.

    🌈 this와 클로저(closure)

    🌊 this

    • person 객체
      const person = {
      name: "사승민",
      // arrow는 this가 변하지 않는다.
      getName() {
        // this는 person
        // 실행 컨텍스트
        return this.name;
      }
      };
      console.log(person.getName()); // 사승민
    • personname실행 컨텍스트라고 한다.
    • 실행 컨텍스트는 실행하는 순간 소유자를 자바스크립트 엔진이 실행할 때 확인한다. 그래서 함수 안에서 this를 사용했을 때 호출하는 순간에 소유자와 연결된다.
    • ❗ 여기서 문제는 소유자가 사라지는 순간이 있다.
      const man = person.getName;
      console.log(man()); // 값없음
    • this를 선언하지 않았기 때문에 실행하는 순간 호출자가 확인이 안되서 전역 스코프의 전역 객체에 연결된다는 룰을 가지고 있다.(브라우저의 전역객체는 window 객체)

    📌 this를 이렇게 만든 이유

    • this를 이렇게 저렇게 바꾸어가며 활용해라.
    • 🔸 this를 고정하는 방법
    1. 모든 함수가 제공하는 bind()
      const man1 = person.getName.bind(person);
      console.log(man1()); // 사승민
    2. call() : this로 선택한 값과 매개변수로 함수를 호출한다.
    3. apply() : this로 선택한 값과 매개변수로 함수를 호출한다.
      person.getName.call(person);
      // "사승민"
      person.getName.apply(person);
      // "사승민"
    • call()apply()의 동작은 같고, 차이점은 함수에 매개변수를 넘기는 방법으로 apply의 매개변수는 배열이고 call은 문자열을 나열한 것이다.

    🌊 클로저(closure)

    • 함수가 호출되면 함수의 스코프가 생성된다.
      function foo(x) {
      // 스코프
      return function(){
        return x;
      }
      }
      const result = foo(10);
      console.log(result()); // 10
    • 이렇게 안쪽 function은 그 스코프 안에 만들어진다.
    • 이때 foo 함수 안에서 접근하는 어떤 변수가 자기 스코프 체인 바깥 쪽 자신을 감싸고 있는 스코프 체인에 있는 변수라면 그걸 함께 들고 있는 영역을 클로저라고 한다.
    • foo함수가 리턴이 될 때 foo함수의 스코프는 같이 사라지는데 받는 인자 x는 없어질 거 같지만 실제 스코프에 있는 x는 없어지고 그걸 따로 저장하고 있는 안쪽 function 함수는 x클로저 영역에 따로 저장하고 있기 때문에 이 때 호출될 때 여전히 x가 존재한다.
    • 이렇게 값을 따로 저장하고 있기 때문에 값을 보호할 용도로 많이 사용한다.

    📌 클로저를 사용한 모듈 패턴

    const person = {
      age: 10,
    }
    person.age = 500;
    // 클로저이요ㅕㅇ해서 막았따
    function makePerson(){
      let age = 10;
      // 모듈패턴
      // 값을 보호해야할때 이렇게 사용했다.
      return {
        getAge(){
          return age;
        },
        setAge(x){
          // 1보다 작고 130보다 크면 age
          age = x > 1 && x < 130 ? x: age;
        }
      }
    }
    let p = makePerson();
    console.log(p.getAge()); // 10
    p.setAge(200);
    console.log(p.getAge()); // 10 200은 130보다 크니 기본 age
    p.setAge(30);
    console.log(p.getAge()); // 30

    🌈 비동기

    • setTimeout을 사용한 비동기
      setTimeout(function foo(x) {
          console.log("아싸");
        setTimeout(function (y) {
          console.log("우싸");
        }, 2000);
      }, 1000);
    • setTimeout을 사용하여 비동기적으로 실행하였다.
    • 1초가 지난 뒤 아싸가 콘솔에 찍히고, 2초가 지난 뒤 우싸가 찍힌다.
    • 이렇게 여러 단계로 함수를 실행하게 되면 함수에 함수에 함수.. 계속 이렇게 되어 복잡한 구조로 나아간다.
    • 이러한 복잡한 구조를 Callback hell또는 콜백 지옥이라고 한다.

    🌊 Promise(프로미스)

    • 위와 같이 콜백 지옥을 해결하기 위해 나온 것이 Promise이다.
      // promise1
      const p1 = new Promise((resolve, reject) => {
          // 이것 또한 클로저
        setTimeout(() => {
          // 이 안에서만 호출
          resolve("응답1");
        }, 1000);
      });
      // promise2
      const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("응답2");
        }, 1000);
      });
      // 메소드 체이닝
      p1.then(
        p2.then((r) => console.log(r)))
        .catch(function(){});
      // 응답2
      // Promise {<fulfilled>: "응답1"}
    • new Promise는 인스턴스 객체를 리턴하는데 이 인스턴스 객체 안에는 then이라고 하는 속성(메소드) 함수가 들어가 있다.
    • 이 비동기 작업이 성공하면 then을 사용하며 then은 resolve를 호출한다.
    • reject함수를 호출하면 catch안에 기술된 함수를 호출해준다.

    🌊 비동기 함수(Async/await)

    • 비동기 함수의 background는 결국 Promise이다.
    • 비동기함수를 사용하면 동기적인 코드로 작성할 수 있다.
      const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
      async function main(){
      console.log('1');
      try {
        const x = await delay(2000);
      } catch (error) {
        //reject
        console.log(error);
      }
      console.log('2');
      }
      main();
    • 1이 찍히고 delay 함수가 실행되고 2초 뒤 2가 실행된다.
    • await연산자는 Promise 객체 즉, thenable 객체가 있으면 안에서 resolve가 호출됬을 때 resolve 함수가 반환하는 값을 마치 리턴 값으로 넘겨주는 식으로 된다.
    • 여기서 javascript에서 많이 사용하지 않던 try/catch문을 사용하여 reject가 에러로 잡히게 된다.

    📌 결국 콜백의 깊은 depth를 일자로 펼치기 위해서 Promise가 만들어졌고 그 Promise의 backgroud 위에 진짜 일자 동기 코드를 쓰기 위해서 async 함수가 만들어졌다.

    🌊 제너레이터

    • redux-saga할 때 진행..

    📌 커링이란?

    • 인자가 여러 개 있을 때 인자 하나하나가 함수 하나씩 하나씩 배분해서 쪼개는 것을 커링이라고 부른다.
    • 클로저는 항상 커링이 된다.
    • 클로저는 커링을 위한 기술중 하나이다.
      function foo(a){
      return function(b){
        return function(c){
          return a + b + c;
        }
      }
      }

    🚀 Redux 만들어보기

    🌈 리덕스의 store 생성

    • 중앙에 스토어 하나를 생성하는 개념이다.(객체이다.)
    • 객체를 만드는 함수를 제공하는데 그것이 리덕스에서 createStore이다.
      // redux.js
      export const createStore = () => {
        const state = {};
        return state;
      }
    • 리덕스의 컨셉은 밑으로 흘러가면서 데이터를 절대 컴포넌트가 직접적으로 변경 불가하다. 즉, immutable하고 컴포넌트는 데이터를 받아서 그리기만 해준다.
    • 그렇기 때문에 클로저로 상태를 숨겨주어야 한다.
      export const createStore = () => {
        const state = {};
        // 새로운 객체를 내보낸다.
          const getState = () => ({ ...state });
        return {getState};
      }
    • 바깥에서 상태를 알고싶으면 store객체의 getState를 호출해야만 볼 수 있다.

    🌈 상태를 변경하기

    • 그 상태를 바꾸는 코드는 리덕스한테 상태를 바꾸는 함수로 전달해준다.
    • 이것을 리듀서라고 부른다.
      // index.js
      function reducer(state = {}, action) {
      if (action.type === "increment") {
        return {
          ...state,
          count: state.count ? state.count + 1 : 1
        };
        //state.abc = 'OK';
      } else if (action.type === "reset") {
        return {
          ...state,
          count: action.resetCount
        };
      }
      return state;
      }
    • createStore한테 리듀서 함수를 전달해주어야 한다.
    • 그러기 위해서는 약속이 필요한데 리듀서 함수의 상태가 변경될 때 이 리듀서 함수를 호출하면 현재 상태를 넘겨준다.
    • 무엇을 바꾸는지 모르기 때문에 간단하게 이 문제를 해결할 수 있는데 두번째 인자에는 객체 하나를 풀고, 이 객체는 type이라는 문자열을 갖고있는 속성(action)을 현재 state 상태로 업데이트 시켜준다.(새로운 객체를 반환)
      // createStore 한테 리듀서 함수 전달.
      // redux.js
      export function createStore(reducer) {
        let state;
        const getState = () => ({ ...state });
        const dispatch = (action) => {
          state = reducer(state, action);
        };
        return {
          getState,
          dispatch
        };
      }
      // index.js
      import { createStore } from "./redux";
      function reducer(state = {}, action) {
        if (action.type === "increment") {
            return {
              ...state,
              count: state.count ? state.count + 1 : 1
            };
              //state.abc = 'OK';
        } else if (action.type === "reset") {
            return {
              ...state,
              count: action.resetCount
            };
        }
        return state;
      }
      const store = createStore(reducer);
      function actionCreator(type, data) {
        return {
          ...data,
          type: type
        };
      }
      store.dispatch(actionCreator("increment")); // Object {count: 1}
      store.dispatch(actionCreator("increment")); // Object {count: 2}
      store.dispatch(actionCreator("increment")); // Object {count: 3}
      store.dispatch(actionCreator("increment")); // Object {count: 4}
      store.dispatch(actionCreator("increment")); // Object {count: 5}
      store.dispatch(actionCreator("reset"));     // Object {count: undefined}
      store.dispatch(actionCreator("increment")); // Object {count: 1}
    • action 문자열을 상수처리와 store.dispatch(actionCreator("increment"));이렇게 계속 쓸 수는 없다. increment함수와 reset함수를 생성해준다.
      // index.js
      import { createStore } from "./redux";
      const INCREMENT = "increment";
      const RESET = "reset";
      function reducer(state = {}, action) {
        if (action.type === INCREMENT) {
            return {
              ...state,
              count: state.count ? state.count + 1 : 1
            };
              //state.abc = 'OK';
        } else if (action.type === RESET) {
            return {
              ...state,
              count: action.resetCount
            };
        }
        return state;
      }
      const store = createStore(reducer);
      function actionCreator(type, data) {
        return {
          ...data,
          type: type
        };
      }
      // 증가
      function increment() {
          store.dispatch(actionCreator(INCREMENT));
      }
      // 리셋
      function reset() {
          store.dispatch(actionCreator(RESET, { resetCount: 0 }));
      }
      increment(); // Object {count: 1}
      increment(); // Object {count: 2}
      increment(); // Object {count: 3}
      increment(); // Object {count: 4}
      increment(); // Object {count: 5}
      reset();     // Object {count: 0}
      increment(); // Object {count: 1}

    🌈 변경된 상태를 확인하기

    📌 스토어에 바뀐 상태를 어떻게 알까?

    • 직접 getState를 호출해서 알 수 있다.

    • 하지만 UI에서 언제 store가 바뀔지 알아서 코드로 적시할 수 없다.

    • 리듀서에서 리덕스가 dispatch가 불러서 리듀서 함수를 호출해서 UI가 쓴 코드로 state를 바꾼 것을 방금 내부의 클로저에 잡혀있는 내 store에다가 변경됬다는 것을 바깥 쪽으로 통지를 해주어야 한다. 즉, 함수를 호출해서 사용한다.

    • 통지함수 update함수 생성

    • subscribe 함수는 인자를 하나 받아서 listeners배열에 저장한다.

    • listenerssubscribe함수에서 받은 function을 넣는다.

      // redux.js
      export function createStore(reducer) {
        let state;
        const listeners = [];
        const getState = () => ({ ...state });
      
        const dispatch = (action) => {
          state = reducer(state, action);
          listeners.forEach((fn) => fn());
        };
      
        const subscribe = (fn) => {
          listeners.push(fn);
        };
      
        return {
          getState,
          dispatch,
          subscribe
        };
      }
      //index.js
      // 생략..
      const store = createStore(reducer);
      function update() {
          console.log(store.getState());
      }
      store.subscribe(update);
      // 생략..

    📌 리덕스 만들기 전체 소스는 GitHub 참조 ✌

    🚀 2회차 후기

    이번 시간은 정말 많은 것을 배운 시간이였다!! ✨
    리액트를 사용하고 공부를 하였지만, 직접 리덕스를 만들어보고 그 안에 어떤식으로 흘러가고 어떤 기술이 쓰였는지에 대해서 한번도 생각해보지 못했던 내 자신한테 부끄러워지만 이번 강의로 확실히 알고가서 너무 좋았다. 😀🚀
    앞으로 표면적으로 보이는 것만 사용하지 않고, 한 번더 생각하고 이건 왜 이렇게 되는 걸까? 라는 고민을 해보도록 노력해야겠라는 생각을 많이 했다. 😥
    또한, 자바스크립트의 단순하고 당연히 이정도는 알고있지고 생각했던 건 오산이였다..
    자바스크립트의 기본적인 개념이더라도 이런 과정이 있어서 이렇게 생겼났다 라는 것을 명확하게 설명해주셔서 간단하다고 생각했던 개념들도 다른관점에서 볼 수 있던 계기가 되었다. 😤
    앞으로의 강의가 더욱 더 기대가 되고 이번에 운 좋게 강의를 들어서 너무 좋은 기회를 잡았다고 생각이 들었다!! 🎊🎉

    댓글

Designed by Seungmin Sa.