✔ 우아한 테크러닝 3기: React & TypeScript 2회차
✌ 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; }
📌 값을 반환하는 두가지 방법
명시적으로 리턴하는 방법.(
return
이 없으면undefinded
반환)호출할 때 앞에
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
라고 명명하는 것이다. - 그래서 이 함수 안에
this
는new
연산자를 호출했을 때 생성된 빈 객체이다. (동적 바인딩)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()); // 사승민
person
의name
은 실행 컨텍스트라고 한다.- 실행 컨텍스트는 실행하는 순간 소유자를 자바스크립트 엔진이 실행할 때 확인한다. 그래서 함수 안에서
this
를 사용했을 때 호출하는 순간에 소유자와 연결된다. - ❗ 여기서 문제는 소유자가 사라지는 순간이 있다.
const man = person.getName; console.log(man()); // 값없음
this
를 선언하지 않았기 때문에 실행하는 순간 호출자가 확인이 안되서 전역 스코프의 전역 객체에 연결된다는 룰을 가지고 있다.(브라우저의 전역객체는window
객체)
📌 this를 이렇게 만든 이유
this
를 이렇게 저렇게 바꾸어가며 활용해라.- 🔸
this
를 고정하는 방법
- 모든 함수가 제공하는
bind()
const man1 = person.getName.bind(person); console.log(man1()); // 사승민
call()
:this
로 선택한 값과 매개변수로 함수를 호출한다.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
배열에 저장한다.listeners
에subscribe
함수에서 받은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회차 후기
이번 시간은 정말 많은 것을 배운 시간이였다!! ✨
리액트를 사용하고 공부를 하였지만, 직접 리덕스를 만들어보고 그 안에 어떤식으로 흘러가고 어떤 기술이 쓰였는지에 대해서 한번도 생각해보지 못했던 내 자신한테 부끄러워지만 이번 강의로 확실히 알고가서 너무 좋았다. 😀🚀
앞으로 표면적으로 보이는 것만 사용하지 않고, 한 번더 생각하고 이건 왜 이렇게 되는 걸까? 라는 고민을 해보도록 노력해야겠라는 생각을 많이 했다. 😥
또한, 자바스크립트의 단순하고 당연히 이정도는 알고있지고 생각했던 건 오산이였다..
자바스크립트의 기본적인 개념이더라도 이런 과정이 있어서 이렇게 생겼났다 라는 것을 명확하게 설명해주셔서 간단하다고 생각했던 개념들도 다른관점에서 볼 수 있던 계기가 되었다. 😤
앞으로의 강의가 더욱 더 기대가 되고 이번에 운 좋게 강의를 들어서 너무 좋은 기회를 잡았다고 생각이 들었다!! 🎊🎉