우아한 테크러닝 3기(2) - 이문식과 자바스크립트 그리고 리덕스

둘둘·2020년 9월 8일
1

우아한_테크러닝

목록 보기
2/3
post-thumbnail

TS를 쓰는 React를 쓰든 어쨌든 둘 다 JS이고, JS에 대한 지식이 진짜 중요하기 때문에 이날 강의 목표는

  • 자바스크립트를 A부터 Z까지!
  • 거기다 리덕스!

자바스크립트 A to Z

기억해, 이문식

모든 자바스크립트 코드는 크게 으로 구분할 수 있다.
(이문식 드립을 채팅창에 치고 싶었지만 참았다.. 블로그에 쓰려고 아껴두었다)

  • 한 줄 실행했을 때 값이 안 나오는 것 👉
  • 한 줄 실행했을 때 그 결과가 으로 마무리 되는 것 👉
1+10 // 11이라는 값이 나옴 식
foo() // undefined라는 값 호출 -> 식
1+10+foo(); // 역시 식

실행했는데 값이 안 나오면 문임
-> if문, for문, while문..

더 확실하게 구분하자면..

마지막에 세미콜론 안 붙인다 ->
마지막에 세미콜론 붙인다!!!->

자바스크립트 타입과 변수 선언

보통 프로그래밍 언어 배울때 변수나 데이터 타입을 많이 배우는데
js는 크게 원시 타입(primitive type)과 객체타입으로 나눌 수 있다.

function foo() {}

let y = foo;

자바스크립트는 거의 모든 게 값인데, 함수도 값으로 넣을 수 있는 변수가 된다!

함수

이번엔 함수를 알아보자.
자바스크립트의 함수는 반드시 어떠한 값을 반환하게 되어있음

값을 반환 두 가지 방법이 있는데 return 혹은 undefined 반환
(정의문과 정의식이라고 쓰셨는데 나에게 좀 더 친숙한 선언식과 표현식이라는 키워드를 써보았다)

함수 선언식

function foo() {
  return 0;
}

함수를 식으로도 쓸 수 있음

함수 표현식

const bar = function bar () {}
bar()

함수는 값이라고 규정했으니까 함수식에서는 이름을 생략할 수있다.
또한, 함수를 값으로 취급할 땐 이름을 생략할 수 있다.
그게 바로 익명함수

아래의 함수는 만들자마자 즉시 함수를 호출하면 호출이 가능하다는 것을 보여준다.
이것은 바로 IIFE(Immediately Invoked Function Expression)
그치만 이름이 없어서 단 한번만 호출하고 싶을 때 쓸 수 있음
(초기화 코드 등 앱이 실행될 때 단 한번만 실행되어야 하는 코드에 쓰게 된다)

(function(){
})();

//여기서 개념을 조금 더 확장해보자/
//함수를 변수에 넣을수 있었는데, 변수가 우리가 어디에 등장하는지 생각을 해봐야 함.
//변수는 다시 생각해보면 변수를 읽으면 값. 변수도 결국 값!
//값이 등장하는 위치로 확장할 수 있는데..

재귀함수
함수 안에 자기 자신 함수를 호출할 수 있음

const foo = function (x) {
  foo();
}

여기서 한 가지 리빙 포인트!
우리가 함수 식을 만들 때 재귀호출되는 함수가 있다면 이름을 생략하지 말아야 함.
원래대로라면 함수의 이름은 생략 가능하나, 함수 호출을 하려면 이름이 필요.

콜백함수

function foo(x) {
	x();
    return function(){
    };
}

const y = foo(function() {	
})

인자로 함수를 전달하는 것을 보통 콜백함수라고 함.
어떤 함수한테 함수하나 줄테니 대신 호출해달라고 함수 호출을 위임하는것.(심부름 시키는 것!)

소프트웨어 공학적으론 일급함수 혹은 Higher Order Function이라고 함.
HOF... 어디서 많이 들어보지 않았나 싶었는데 입력을 컴포넌트로 넣고 출력도 컴포넌트로 하는 리액트의 HOC와 닮아도 너무 닮았다!

자바스크립트가 가능하기 때문에 리액트에서의 HOC도 가능한 것이다.
결코 리액트 특유의 문법이 아니었던 것!

(왜 때문인지 모르겠으나 적어놓은 띵언)

함수는 코드를 묶고있는 값이라고 이해해야 한다.

화살표 함수

ES6이후 나온 문법쓰. 함수 만들때 화살표를 이용하면 된다!
한줄이면 {}return도 생략 삽가능수.
인자가 하나면 괄호 생략 가능수, 인자 두개면 괄호 필수

const macaron = meringue => meringue*ovenFire;

(민태쓰앵님이 이런 예시를 들진 않았다. 급 단게 땡겨서 내가 대충 만들었다)

화살표함수는 주로 함수가 값을 반환하는데 식에 참여했을때 많이 쓴다.
함수는 어떠한 인자를 입력값으로 받고, 계산한 다음에 그 계산의 결과를 돌려줘야 한다.

const bar = x => x*2;

x를 받아서 곱하기 2한 값을 돌려줄거야. 이러한 함수를 bar에 넣어놓은 것~

🚗 다시 등장한 문식쒸

const x = 10;
const y = () => 10;

console.log(x,y())
  • 결과론적으론 둘다 값을 리턴해서 둘다 식임.
  • 값은 그 자체론 계산을 할 수 없다.
  • 값은 불변(immutable)의 속성을 가지고 있음.

new 연산자와 인스턴스

new 연산자를 쓰면 내부적으로 어떤 메카니즘이 작동될까?
여기서 중요한 키워드는 객체동적바인딩

const x = {};
x.name = 10;

위의 코드대로라면 x 객체 안에 속성 10이 들어감.
이것이 바로 동적바인딩

클래스에서 this.xxx = 10;
이것도 객체의 동적 바인딩을 이용하는 것이다.
리턴이 명시적으로 없어도 새로 만든 this가 리턴된다!

function foo() {
	this.food=10;	
}

const y = new foo();

console.log(y) 찍으면 위에서 만든 객체형태가 나온다.
이렇듯 new 연산자는 새로운 객체를 만들어내는데 내부적으론 프로토타입이라는 매커니즘이고 이걸 생성자함수라고 한다.
이렇게 만들어진 y는 인스턴스 객체라고 한다.

foo 함수가 생성된 것은 instanceof 라는 명령어로 확인할 수 있다.
y instanceof foo 를 콘솔에 찍어보면 true가 나올것이다~

내가 어떤 모양의 객체가 필요한데 자바스크립트는 타입을 느슨하게 검사를 하는 편이다.
그래서 내가 원하는 값을 가지고 있는지 꼭 확인해야 한다. 왜냐, 객체라면 그 안에 객체의 구조가 복잡할 수 있기 때문!

function foo() {
	this.name = 10;
}

class bar {
	constructor() {
    this.name = 10
    }
}

console.log(new bar())

위의 foo보다는 아래의 bar가 좀 더 명시적이라고 한다.
이 시점에서 함수와 클래스가 다른 점은 new 함수를 강제할 수 없다는 것!

제발 new함수로 호출해줘!!! 하는 컨벤션으로 클래스형일땐 대문자로 함수이름을 쓰기로 했다는 전설이..

올 것이 왔다 this

const person = {
  name: '김두리',
  getName() {
    return this.name;
  }
}

console.log(person.getName())

this가 결정되는 방식들이 있는데 보통은 실행 컨텍스트로 실행하는 순간 자바스크립트 엔진이 소유자를 확인한다.
위의 코드에선 누가봐도 person이 소유하고 있으니까 person이 바로 실행 컨텍스트!

(충격!!)문제는 소유자가 사라지는 순간들이 있다!

const person = {
  name: '김두리',
  getName() {
    return this.name;
  }
}

console.log(person.getName())

const man = person.getName;
console.log(man());

호출자가 확인이 안되면 전역 스코프까지 가게 되고.. 그래서 this가 윈도우객체가 되는 것이다.
저기에선 도저히 this를 찾을 수 없기 때문이다!
(getName 입장에서는 소유자가 바뀌는지 안 바뀌는지 모른다.)

button.addEventListener('click', person.getName.bind(person))

이렇게 bind함수를 쓰게되면 소유자가 벗겨져도 이 안의 this가 항상 바인드에 입력된 객체가 될 수 있도록 묶어준다. 헬퍼함수라고 보면 된다!
그 밖에도 call함수나 apply함수도 있다.
리액트에선 리액트 내부의 메소드들을 arrow로 만들기 때문에 this가 바뀔 일이 없다.

😭 이렇게 어려운 this를 만든 이유!
this의 소유자를 이렇게 저렇게 바꿔가면서 활용하라는 자바스크립트 창시자의 뜻...

친해지길 바래, 클로저

function foo(x) {
  return function bar() {
  return x;
  }
}

const two = foo(2);
console.log(two())

foo 함수 안에는 bar함수가 있는데 bar함수는 foo 함수에 인자로 들어간 x를 리턴한다.
foo 함수가 호출되면 foo의 스코프는 8:45~~ 그치만 bar는 x를 기억하쥐..
위의 코드 예시처럼 외부 스코프를 기억하고 있는 것을 클로저라고 한다.

function makePerson() {
  let age = 10;
  return  {
    getAge() {
      return age;
      //클로저에 캡쳐해서 getAge만이 age에 접근할 수 있음.(캡슐화)
    },
    //age 값을 설정할 수 있게 함수 만들어줌
    setAge(x) {
      age = x > 1 && x < 130 ? x : age;
    }
  }
}

p = makePerson();
console.log(p.getAge()); // 10
p.setAge(130);
console.log(p.getAge()); // 10
p.setAge(129);
console.log(p.getAge()); // 129

캡슐화 넘나 신기신기.. 나중에 더 자세히 파봐야지
쓰앵님이 클로저는 이게 끝이라고 하셨다... 와.....bbbb

비동기와 Promise

비동기가 어려운 이유는 인간의 두뇌가 생각을 비동기적으로 하지 않기 때문이다
setTimeout 예시를 보통 많이 쓰는데 setTimeout이나 API 호출할때 비동기적으로 실행된다.

setTimeout(function (x) {
  console.log('배고파!')
  setTimeout(function(y) {
    console.log('배불러~')
  },2000)
},1000);

위의 코드 예시는 깊이가 2인데.. depth가 더 깊어지면 콜백지옥에 빠지게 된다.
그래서 이걸 방지하기 위해 Promise 힘차게 등장!

const p1 = new Promise((resolve, reject) => {
  setTimeout(()=> {
    resolve('응답하라')
  },1000)
})

const p2 = new Promise((res, rej) => {
  setTimeout(() => {
    resolve('응답22')
  }, 1000)
})

new함수로 Promise를 리턴하면 then이라는 함수가 들어가 있다(thenable 함수)
프로미스 객체 안엔 resolve함수reject 함수가 있는데

  • resolve는 성공! 호출하면 then에 입력된 함수 호출
  • reject는 실패~ 호출하면 catch에 입력된 함수 호출
  • Promise는 시간이 걸려도 언젠간 resolve 혹은 reject에 입력된 함수를 호출해줌. 그것이 약.속.이.니.깐
  • async와 await를 이용해 동기를 비동기로 실행시킬수도 있읍니다..

리덕스

리덕스를... 자바스크립트로 먼저 익힐 수 있도록 예시를 들어주셨는데
컨디션 난조로 일단 선끄적 후이해 하려고 했는데..
뭔가 바빠서 제대로 정리하고 넘어가지 못하였다.. 일단 코드 복붙하고 추후 다시 수정하는걸로...!

Store

  • 앱에서 쓰이는 모든 상태를 한 군데에 모아놓은 것
  • 앱 하나에 스토어 하나
  • store는 객체형태 이다.

자바스크립트로 리덕스를 만들어보자.

// 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);
  };

  state = reducer(state, {
    type: 'abc',
  });
  return {
    getState,
    dispatch,
    subscribe,
  };
}
import { createStore } from './redux';

const store = createStore(reducer);

function update() {
  console.log(store.getState());
}

// store.person = {};
// redux의 컨셉 : 컴포넌트는 데이터 받아서 그리기만 해야 함.
// 컴포넌트가 직접적으로 데이터를 바꾸면 안됨.

// 상태를 바꾸려면 리듀서 함수를 이용하고, 이 리듀서를 createStore에 전달해라.
function reducer(state, action) {
  // if (action.type === 'abc') {
  //   state.abc = '200 ok'; // 이렇게 대놓고 바꾸면 안돼. 그래서 약속을 하나 더 추가
  //   // 내가 준 객체와 참조를 끊고 새로운 객체를 리턴하라는 약속
  // }
  // return state; //바꾼 상태리턴
  // 객체 레퍼런스가 바뀌어야 상태변화를 감지해서 UI업데이트를 제대로 할 수 있는걸로 알고있어요
  //새로운 객체레퍼런스가 생겨야 아 이전이랑 새로운 객체가 생겼으니 상태를 업데이트 해야지

  if (action.type === 'abc') {
    return {
      ...state,
      abc: '200 ok',
    };
  }
  return state;
}

store.subscribe(update);

store.dispatch({ type: 'abc' });
console.log(store.getState());
profile
Dooreplay! 안 되면 될 때까지,

0개의 댓글