본문 바로가기
프로젝트 & 강의

react (1)

by Chars4785 2020. 11. 16.

React + TypeScript 강의

도구를 사용하게 되면 도구 사용법에만 집중하게 되면 마법처럼 느껴질때가 많다. 그 안에 있는 숨은 논리나 원리를 알면 복잡한 새로운 도구가 나와도 어떻게 개발 되었구나라는 걸 짐작할수 있는데, 도구 사용에만 급급하다.

방법) 초기 깃헙을 보면서 공부하면 좋다.

 

이 강의의 목적: 원리, 숨은 논리 알려주는 것

 

서양에서 만들어 진것이기 때문에 철학을 이해 하는게 중요, 여기서 이야기하는 철학이란 그들의 논리 세계를 이해 하면 어떤 큰 도구더라도 그 밑 바닥에는 논리세계가 쌓여 있다.

 

왜 이게 필요하게 되었는지 이해하고 공부하는게 중요하다.

예) 왜 react를 만들었을까? 왜 redux가 있을까? 등...


React

개념

 

- JSX를 통해서 만든 VDOM은 이전 VDOM과 비교해서 변경된 것만 render 한다.

 

0. 기본

- 함수란? 뭐든 반환한다. ( return이 없어도 undefined 을 반환한다. )

- 식과 문으로 구성되어 있다. ( 구분은 ; 가 있냐 없냐 )

- 값은 변수에 들어간다. 모든 것이 값이다. ( 함수도 값이다 )

1. 실행 컨텍스트

- 자바스크립트 작동 원리

- 실행 컨텍스트는 쉽게 말해 코드가 실행되고 있는 구역, 범위에 대한 개념이다.

 

실행 컨텍스트 종류

a. 전역 컨텍스트 ( 처음만들어 지는것 )

b. 함수 컨텍스트 ( 함수 호출시 )

 

자바스크립트 엔진은 코드를 실행하기 위하여 실행에 필요한 여러가지 정보를 알고 있어야 한다. 실행에 필요한 여러가지 정보란 아래와 같은 것들이 있다.

  • 변수 : 전역변수, 지역변수, 매개변수, 객체의 프로퍼티
  • 함수 선언
  • 변수의 유효범위(Scope)
  • this

컨텍스트는 3가지 객체로 구성

- 변수 객체 : 인수 정보 , 함수 이름, 변수 등 ..

- 스코프 체인: 실행 컨텍스트가 참조할 수 있는 변수, 함수 선언 등의 정보를 담고 있는 리스트를 가르킨다

- this binding: 전역에서는 this 가 global object, 함수에서는 어떻게 호출 되었냐에 따라 다르다.

object reference 로 호출 되면 해당 객체의 this 또는 호출자가 확인이 안되면  전역 스코프의 전역 객체에 연결된다.

var x = 'xxx';

function foo () {
  var y = 'yyy';

  function bar () {
    var z = 'zzz';
    console.log(x + y + z);
  }
  bar();
}
foo();

호출 스택

자바스크트립 엔진 script 를 만나게 되면 전역컨텍스트를 만들고 스택에 함수 컨텍스트를 쌓는 방식으로 실행된다.

const person ={
	name:'이종민',
    getName(){
     return this.name;
    }
}

console.log(person.getName())
// 이종민

const man = person.getName();
console.log(man());
// error window 가 없기 때문에

이렇게 되면 const man = person.getName() 처럼 소유자가 벗겨진다. 그래서 에러

생각보다 소유자가 벗겨지는 일은 많이 일어난다.

 

예)

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

UI 버튼을 만들때 click 을 하게 되면 언제 사용자가 클릭을 할지 모르니까 잠시 저장해 두고 클릭을 할때 마다 보내줘야 한다. 

> 콜백 함수가 된다. 그래서 브라우저에서는 이벤트 등록이 있다.

 

Q) 이렇게 만들면 this가 많이 벗겨지는데...

이렇게 만든이유 this를 바꿔서 마음대로 이용해라. > react 를 만들때 this 를 많이 바꿔야 한다.

하지만 비즈니스 개발을 할때는 그렇게.. 많이 사용하지 않고 this 가 고정 하길 원한다. 

 

 

this, binding 방법

1. bind

button.addEventListener('click',person.getName().bind(person)); // 입력값을 this로

2. arrow function 

3. 함수 호출시

person.getName();

person.getName.call(person, any) // this 를 넣을수 있다.

person.getName.apply(person, 배열); // 해당 함수에 parameter를 넣을때 배열로 넣어 줘야 한다.

 

react는 이제 function 컴퍼넌트로 많이 사용하기 때문에 이제는 위에 개념이 그렇게 중요하지 않게 되었다. react 가 위에 내용을 잘 사용하지 않는 framework 이기 때문에 잘 모른다. 하지만 알아두면 좋다.

 

2. 클로저

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

const f = foo(10)

console.log(f());

return 10

함수를 만들면 scope 가 생긴다 es6 가 되면서 brace로 인해서 더욱 촘촘해 졌다.

- return 이 될때 foo는 사라진다.

- 안에 bar 함수가 scope를 만들면서 밖에 있는 변수를 갖고 있는데 그 영역을 클로저 라고 한다.

- bar 함수는 클로저에 x 를 저장하고 있기 때문에 return 가능하고 bar 만이 x에 접근 할수 있다.

 

- 특히 값을 보호 할때 많이 사용한다.

- 클로저를 활용한 모듈 패턴

const person ={
	age: 10,
}

person.age = 500; // 못막는다.

function makePerson(){
 let age = 10;
 return {
 	getAge(){
    	return age;
    },
    setAge(x){
    	age = x > 1 && x < 130 ? x : age;
    }
 }
}

let p = makePerson();
console.log(p.getAge()) 

1.  age를 보호할수 있다.

2. 클로저가 아니면 x를 막는 방어 코드가 많아진다. 코드가 길어 진다.

3. 프라미스

비동기적인 코드를 동기적으로 만들기 위해서 노력했다.

콜백 지옥에서 -> promise -> async/await

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

const p2 = new Promise((resolve,reject) =>{
	setTimeout(()=>{
        reject('EE')
    },1000);
});
//클로저 사용

p1.then((t)=>{
    console.log(t)
    return p2
}).then((t)=>{
    console.log(t)
}).catch((e)=>{
    console.log(e)
})

await 는 단순히 비동기 처리를 동기 처리 처럼 보이게 하기 위한 장치이다. 그안에는 Promise 로 구성되어 있다.

4. 커링

인자를 나누는것

> 클로저가 커링이 가능하게한 기술중 하나

// 1
// 표현 방법1
function yourMiddleware(store){
	return function(dispatch){
    	return function(action){
        	dispatch(action)
        }
    }
}
// 2
// 표현 방법2
const myMiddleware = store => dispatch => action =>{
	dispatch(action)	
}

// 3
// 인자 3개 받는 함수
const ourMiddleware = ( store, dispatch, action ) =>{
	disatch(action)
}

1,2번은 커링이고 3번은 보통 볼수 있는 함수 입니다.

인자가 N개인 함수를 각각 인자를 쪼갠 함수로 분리하는 걸 함수형 프로그램에서 커링이라고 합니다. 그리고 클로저가 있어야 가능한 것이다.

그럼 왜? 쪼개나

호출 방법

myMiddleware(store)(store.dispatch)({ type: "increase" })
ourMiddleware(store, store.dispatch, { type: "increase" })

재사용성이 좋아진다.

let printInfo = function(group, name){
    console.log(group + ', ' + name);
};
printInfo('dev-momo', 'haegul');    // dev-momo, haegul
printInfo('dev-momo', 'jiwon');     // dev-momo, jiwon
printInfo('dev-momo', 'sungcheon'); // dev-momo, sungcheon
// currying
let printInfo = group => name => console.log(group + ', ' + name);

let momoGroup = printInfo('dev-momo');
momoGroup('haegul');    // dev-momo, haegul
momoGroup('jiwon');     // dev-momo, jiwon
momoGroup('sungcheon'); // dev-momo, sungcheon

더 자세한 내용은 미들웨어 할때 다루겠습니다.

 


- redux, vdom, Hook

Redux

redux, mobx, context-api ...

const INCREMENT = 'increment' // action type
const RESET = 'reset'
function createStore(reducer){
    let state;
    const listeners = [];
    const getState = () => ({ ...state }) 
    //새로운 객체를 만들어서 리턴
    // 리덕스에서 액션을 던지는게 아니고 dispatch 에서 던진다.
    const dispatch = (action) =>{
        state = reducer(state, action);
        listeners.forEach((fn) => fn());
    }
    // 바뀌었다는 걸 밖으로 통지 해줘야 한다.
    // 여러 컴퍼넌트에서 받을수도 있으니까 배열로
    const subscribe = (fn) =>{
        listeners.push(fn)
    }

    return {
        getState,
        dispatch,
        subscribe
    }
}

// reducer
// 상태를 바꾸는걸 아는 사람은 개발자만 알기 때문에 상태를 바꾸기 위한 함수
function reducer(state = {}, action ) {
    switch(action.type){
        case INCREMENT:
            return {
                ...state,
                count: state.count ? state.count + 1 : 1
            };
        case RESET:
            return{
                ...state,
                count: action.resetCount,
            }
    }
    //  state.count  = state.count + 1
    // reducer는 항상 새로 값을 리턴해야 한다. 받은 state 를 변경 할 수도
    //  있으니까 순수 함수
}

const store = createStore(reducer);
// store.person = {} 아무나 막 바꿀수 있다. 
// 컨셉이 컴퍼넌트가 막 직접 바꾸지 못하게 해야 한다.
// 여기 저기 컴퍼넌트가 바꾸면 안되기 때문

// 이걸 받으면 업데이트 되었다는 걸 알게 된다.
function update(){
    console.log(store.getState())
}

function actionCreator(type,data){
    return{
        ...data,
        type: type
    }
}

function increment(){
    store.dispatch(actionCreator(INCREMENT));
}

function reset(){
    store.dispatch(actionCreator(RESET, { resetCount: 10 }))
}

store.subscribe(update);
store.dispatch({ type: 'increment' }); // 리덕스 밖에서 변경 할수 있다.
store.dispatch({ type: INCREMENT }); 
store.dispatch(actionCreator(INCREMENT)); 
store.dispatch(actionCreator(INCREMENT)); 
increment();
reset();
increment();

 

VDOM

function App() {
  return (
    <div>
      <h1>hello~</h1>
    </div>
  );
}
ReactDOM.render(<App/>, document.getElementById('root'));
// real Dom 으로 만들어 주는 놈

// babel 안에서

function App() {
  return React.createElement("div", null, 
  			React.createElement("h1", null, "hello~")
	    );
}

vdom 만들어 보기

/* @jsx createElement */

// babel 에게 태그(<ul> <h1> 와 같은)를 createElement 함수로 변경 해줘 라는 뜻

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" }
  ]
};

function createElement(type, props = {}, ...children) {
  return { type, props, children };
}

function App() {
  return (
    <div>
      <h1>hello</h1>
    </div>
  );
}

console.log(<App />);

 

JS 있을때는 런타임이 없었다. 하지만 ES6, react, 프레임 워크로 개발하는 시대가 오면서 스크립트 언어 임에도 불구하고 컴파일타임인지, 런타임인지 인지 하고 있어야 한다.

 

- 상위 코드들은 컴파일 타임에 넣어지는 것

- 콘솔에 찍히는 값이 런타임에 되는 것

 

그러면 <div> 랑 컴퍼넌트 <App> 은 어떻게 구분할까, div, ul는 createElement 를 사용하고 컴퍼넌트는 어떻게 구분 할까. 대문자 소문자로 구분한다. 대문자면 사용자 컴퍼넌트로, 소문자면 빌트인 컴퍼넌트 라고 구분한다.

 

/* @jsx createElement */

// babel 에게 태그를 createElement 함수로 변경 해줘 라는 뜻

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" }
  ]
};

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); //자식 추가
  });
  console.log("e1", el);
  return el;
}

// const ul = document.createElement('ul')
// document.body.appendChild(ul)
// 랑 같은 원리

function render(vdom, container) {
  container.appendChild(renderElement(vdom)); // 선택한 요소 안에 자식을 넣는다.
}

function createElement(type, props = {}, ...children) {
  if (typeof type === "function") {
    return type.apply(null, [props, ...children]);
  }
  return { type, props, children };
}

function StudyList(props) {
  return (
    <ul>
      <li className="item" label="haha">
        React
      </li>
      <li className="item" label="haha">
        TypeScript
      </li>
      <li className="item" label="haha">
        Redux
      </li>
    </ul>
  );
}

function App() {
  return (
    <div>
      <h1>hello</h1>
      <StudyList item="abcd" id="haha2" />
    </div>
  );
}
console.log(<App />);
render(<App />, document.getElementById("root"));

React 는 Dom을 다루기 쉬운 object 로 바꾸는 일

Hook

import React from 'react';
import ReactDOM from 'react-dom';

class Hello extends React.Component {
	constructor(props){
    	super(props)
        this.state = {
        	count: 1
        }
    }
    componentDidMount(){
    	this.setState({ count: this.state.count + 1 });
    }
   
    render(){
    	return <p>안녕하세요{count}</p>
    }
}
// render를 react에서 호출해서 사용하는데 언제 호출하는지 알수가 없다 
// 그래서 setState라는 함수를 하나 더 제공해 줘서 상태 값을 변경할수 있도록 했다.
// class가 없어지지 않는 이상 상태는 초기화 되지 않는다.

function App(){
	let x = 10; // 함수형 컴퍼넌트는 변수를 못갖는다.
	return (
    	<div>
        	<h1>상태</h1>
            <Hello />
        </div>
    );
}

 상태란 변해야 하는것이다. let x = 10; 값을 못바꾸니까 변수를 만듬 그럼 함수안에 x 는 호출 할때 마다 초기화 된다. 

그래서 상태가 필요할때는 class 컴퍼넌트를 사용했다. 

function App(){
	const [ counter, setCounter ] = useState(1);

    return(
    	<div>
        	<h1 onClick={ () => setCounter(counter + 1) }>상태 {counter} </h1>
        </div>
    )
 }

ko.reactjs.org/docs/hooks-rules.html

 

Hook의 규칙 – React

A JavaScript library for building user interfaces

ko.reactjs.org

hook 방식

- react가 createElement 하나 하나 만든다.

- 함수면 호출

 

- 전역 배열에 넣어 두고 컴퍼넌트라는 키를 통해서 컴퍼넌트 생성된 순서 대로  넣어둔다.

- 만약 컴퍼넌트가 있다면 한번 만들어 진거고 없으면 두번째이다.

- 위에 규칙대로 하는 이유는 컴퍼넌트 순서대로 배열에 넣어뒀기 때문에, 

- if, for 안에 없는 이유는 어느때는 훅을 만들고 안만들고 하니까 순서가 망가진다.

- 그리고 일반 함수에서 호출을 못한다. 리엑트 컴퍼넌트 안에서만 가능

- 이해하기 위해서는 함수 호출이구나 라는걸 알아야 한다.

 

성능 보기 

medium.com/wantedjobs/react-profiler%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%84%B1%EB%8A%A5-%EC%B8%A1%EC%A0%95%ED%95%98%EA%B8%B0-5981dfb3d934

 

React Profiler를 사용하여 성능 측정하기

Profiler 도구를 사용하여 React 성능을 측정하고, 어떻게 하면 성능을 높일 수 있는지 알아보겠습니다.

medium.com

5. 제너레이터

지연

(1)
const x = 10;
cosnt y = x + 10;
// 두 줄은 동시에 실행 될수 없다. 디펜던시 때문에

(2)
const x = () => 10;
const y = x() + 10;
// x()가 확정되는 순간은 두번쨰 라인때 그렇다 -> 지연 호출
// rxjs, mobx observe 등.. 지연을 이용해서 개발 되었다.
// 프라미스 경우

const p = new Promise(( resolve, reject ) =>{
	setTimeout(()=>{
    	resolve("1")
    },1000)
})
// 안에 있는 함수는 이미 리턴으로 끝났지만
// setTimeout안에 함수는 클로저로 resolve를 받아서 살아있다. 
// 하지만 지연된 것이다.

p.then((r) =>{
	console.log(r)
})

// 제너레이터
// 코루틴 
function* make(){
	return 1;
}

const i = make(); 
console.log(i); //객체 하나를 준다. 이터레이터

코루틴의 개념을 가져와서 만든게 제너레이터

코루틴이란 

함수는 인자를 받아서 return 하는데 만약 return 이 없는 함수를  "프로시저" 라고 부른다 ( 사실 js에서는 무조건 undefined 을 return 한다). 이 개념을 약간 넓게 바꿔서 return 을 여러번 하면 어떨까?

-> 함수를 다시 처음 부터 하는게 아니고 return을 여러번하자 가 코루틴이다 이 개념은 처음 만들어진것이 아닌 옛날에 있던 개념이다.

Generator (발전기, 발생하는것) : 계속 값을 생성한다.

function* makeNumber(){
	let num = 1;
    while(true){
    	yield num++; //제너레이터에서 return을 yield로 표현
    }
 }
 const i = makeNumber(); 
 // 제너레이터 객체를 넘겨준다. 
 // 실행될 준비만 하고 객체를 넘겨준다.
 // 실행 시키는 도구는 next();
 
console.log(i.next())
// {value: 1, done: false} 객체를 준다.
// done이 true 이면 더 리턴할게 없다는 뜻
console.log(i.next())
// {value: 2, done: false}
// 값이 죽지 않고 계속 유지하면서 return해준다.

제너레이터 이용

제너레이터로 비동기를 처리 할수도 있고 async/await 보다 더 광범위한 개념이다. await는 오른쪽에 꼭 Promise가 있어야 하지만 제너레이터는 아니다.

function* makeNumber() {
  let num = 1;

  const value1 = yield num++;
  console.log("value1", value1);
  const value2 = yield num + 2;
  console.log("value2", value2);
}

const a = makeNumber(); // a를 이터레이터라고 한다. 계속 실행 할수 있다는 뜻으로 Iterator
console.log(a.next(1));
console.log(a.next(2));
console.log(a.next(3));
const delay = (ms) => new Promise((resolve)=> setTimeout(resolve,ms));
const delay2 = ms => ms

function* main(){
	console.log("시작");
    yield delay(3000)
    console.log("3초 뒤입니다.");
}

async function main2(){
	console.log("시작");
    await delay(3000)
    console.log("3초 뒤입니다.");
}

main2();
const it = main();
const { value } = it.next();
value.then(()=>{
	it.next();
})

async/await도 제너레이터로 개발되었다. 

const delay = (ms) => new Promise((resolve)=> setTimeout(resolve,ms));
const delay2 = ms => ms

function* main(){
	console.log("시작");
    yield delay(3000)
    console.log("3초 뒤입니다.");
}

const it = main();
const { value } = it.next();

if( value instanceof Promise ){
	value.then(()=>{
		it.next();
	})	
}else {
	setTimeout(()=>{
    	it.next();
    },value)
}

 


middleWare

Redux ( 공식문서 )

ko.redux.js.org/advanced/middleware

 

미들웨어 | Redux

심화 강좌 > 미들웨어: How middleware enable adding additional capabilities to the Redux store

ko.redux.js.org

reducer가 순수함수 이어야 하는 이유는 reducer 자체가 동기 함수이기 때문에 순수 함수여야 한다.

순수 함수: 외부와 아무런 dependency 없이, 그리고 side effect 없이 동작하는 함수, 인풋이 똑같으면 아웃풋이 동일하다.

순수 하지 않는 함수: 실행 될때 마다 결과가 일정하지 않은 작업 ( 비동기 )

const api= (url, db) =>{
  setTimeout(()=>{
    db({ type: '응답', data: [] })
  },2000)
}
const reduecer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case "inc":
      return {
        ...state,
        count: state.count + 1
      };
    case 'fetch-user':
      api('user/v1/1',( data )=>{
        return { ...state, ...data }
      })
    break;
    default:
      return { ...state };
  }
};

const store = createStore(reduecer);

store.subScribe(() => {
  console.log(store.getStore());
});

store.dispatch()

이렇게 되면 state 는 클로저 영역에 잡히게 되면서 2초동안 변경 될수도 있다. 

그래서 redux는 이문제를 미들웨어로 통해서 해결한다.

 

미들웨어란?

middleware

데이터 흐름 중간에 들어가서, 데이터 흐름이 미들웨어 쪽으로 흐르게 된다. 

즉, 모든 데이터가 모든 미들웨어를 꽂힌 순서대로 들어간다.

( 그래서 어떤 미들웨어가 다른 것 보다 먼저 들어가야 하는 규칙이 있는 미들웨어도 있다. )

Plugin

경우에 따라 사용하는 것

미들웨어로 조금씩 다가가기!!

 Loging

함수 감싸기

let action = addTodo('Use Redux')

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

- 이쁘지 않아서 함수 감싸기 많이 한다. ( 많이 이용한다. )

function dispatchAndLog(store, action) {
  console.log('dispatching', action)
  store.dispatch(action)
  console.log('next state', store.getState())
}
dispatchAndLog(store, addTodo('Use Redux'))

그러나..

- 아름답지 않다

- 로그를 지우고 싶다 그러면 console.log()를 없애야 한다.

- 그럼 코드 지워야 한다.

- 빌드 다시 

- 배포 다시 

- qa 다시

...

버그 생기면 x 됨

> 결론: 코드를 바꾸는 행위는 비싼 행위이다.

"가능하면 코드를 수정하지 않고 코드의 행위를 바꿀수 있는 테크닉들을 연구 하는 것이다."

그래서 위의 코드는 안좋다.

 

몽키패칭

우리가 저장소 인스턴스에 있는 dispatch 함수를 대체한다면 어떨까요? Redux의 저장소는 몇개의 메서드를 가진 평범한 오브젝트일 뿐이고, 우리는 자바스크립트로 작성하고 있으니 dispatch구현을 몽키패칭할 수 있습니다.

> 원래꺼를 다른걸로 바꾸겠다.

let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action)
  
  let result = next(action) //원래 dispatch() 꺼를 사용하고 있다.
  
  console.log('next state', store.getState())
  return result
}

2개 이상

우리가 dispatch에 이런 변환을 두 개 이상 적용하고 싶다면 어떨까요?

 

로깅 + error로깅

function patchStoreToAddLogging(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndLog(action) {
    // ...some
  }
}

function patchStoreToAddCrashReporting(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndReportErrors(action) {
    //..some
  }
}

patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)

내가 할일만 하고 다시 바꿔놔야 한다. 그래서 여기서 나온 것이 무엇일까요?

 

function add(a,b){
	return a + b;
}

function add2(a){
	return function(b){
    	return a + b;
    }
}

add(10,20)
// 사용자가 최종 계산에 개입할 여지가 없다.
add(10)(20)
// 10과 20이 더해지는 사이에 사용자가 개입 할 수있다.

const next = add(10) // 함수 합성, 함수 조합..등 이름이다.
// do something
// 지연 평가
next(20);
// 즉 몽키패칭 할 수 있다. 사용자가 뭔가를 할 수 있다.
// 즉 커링이란 사용자에게 인자와 인자 사이에 개입할수 있는 여지를 열어 줄수 있는 테크닉

몽키패칭 숨기기

function logger(store) {
  let next = store.dispatch

  // 앞에서:
  store.dispatch = function dispatchAndLog(action) {

  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

이 코드를 사용하는 주체는 누구일까?

 

____ 가 let next 이 사이에 본인들이 사용한다는 뜻이다.

add(10)(20)
const next = add(10) 

// 원래 작업 실행

next(20);

최종적으로

function logger(store) {
  return function wrapDispatchToAddLogging(next) {
    return function dispatchAndLog(action) {
      console.log('dispatching', action)
      let result = next(action)
      // 다음으로 넘겨준다.
      
      console.log('next state', store.getState())
      return result
    }
  }
}

미들웨어는 이렇게 생겨야 한다. next 는 미들웨어끼리 함수를 부른것이 아니다 첫번째 미들웨어 실행후 다음 미들웨어가 실행 되어야 하는데 그걸 부르기 위해서 next가 있는것이다. 이건 redux에 몽키패칭을 돕는 헬퍼가 있다. 거기서 알아서 넘겨준다.

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()

  // 각각의 미들웨어로 디스패치 함수를 변환합니다.
  middlewares.forEach(middleware => (store.dispatch = middleware(store)))
}

es6 표현식

const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

등록된 미들웨어를 순서대로 연결시놓은 구조를 redux에서 할수 있도록 열어둔 커링 테크닉

gist.github.com/ibare/0eb8597551070bf1ebf8e797439913a3

 

middlewares 가 들어간다.

export function createStore(reducer, middlewares = []) {
  let state;
  const listeners = [];
  const publish = () => {
    listeners.forEach(({ subscriber, context }) => {
      subscriber.call(context);
    });
  };

  const dispatch = (action) => {
    state = reducer(state, action);
    publish();
  };

  const subscribe = (subscriber, context = null) => {
    listeners.push({
      subscriber,
      context
    });
  };

  const getState = () => ({ ...state });
  const store = {
    dispatch,
    getState,
    subscribe
  };

  middlewares = Array.from(middlewares).reverse();
  let lastDispatch = store.dispatch;

//예시
const mymid = store => next => action => { }

  middlewares.forEach((middleware) => {
    lastDispatch = middleware(store)(lastDispatch);
  });

  return { ...store, dispatch: lastDispatch };
}

export const actionCreator = (type, payload = {}) => ({
  type,
  payload: { ...payload }
});


왜 reverse() 를 했을까?

> 마지막 lastDispatch() 를 넘겨주니까

[ 1, 2, 3 ] 미들웨어면 => [ 3, 2, 1] 실행하고 1의 action을 return 하기 때문에

 

const logger = (store) => (next) => (action) => {
  console.log("logger: ", action.type);
  next(action);
};

const monitor = (store) => (next) => (action) => {
  setTimeout(() => {
    console.log("monitor: ", action.type);
    next(action);
  }, 2000);
};

const store = createStore(reducer, [logger, monitor]);

만약 api 작업을 하고 싶다면

 

next(action) 을 하기전에 비동기 작업후 값이 오면 next(action) 을 하면 됩니다.

const logger = (store) => (next) => (action) => {
  console.log("logger: ", action.type);
  if( action.type === 'get user info'){
  	api.call('/user').then(res =>{
    	next({
        	type:'res user info',
            data: res
        });
    });
  }else{
  	next(action);
  }
};

const monitor = (store) => (next) => (action) => {
  setTimeout(() => {
    console.log("monitor: ", action.type);
    next(action);
  }, 2000);
};

const store = createStore(reducer, [logger, monitor]);

store는 그럼 왜 있나?

예) 로그인 여부 검사하고 로그인 되어있으면 유저정보 저장하는 액션 이런거? 사용가능

 

우리 서비스에서 사용하는 

1. react-zero 는 리듀서가 없습니다.

2. 용량이 적습니다.

'프로젝트 & 강의' 카테고리의 다른 글

Next js (1) 기초지식  (0) 2021.12.10
react 웹앱 서비스 개발  (0) 2021.12.08
react(2)  (0) 2020.11.20
[follow] 2020.03.13  (0) 2020.03.13
[Follow] 프로젝트 일기  (0) 2020.03.11

댓글