반응형

 Native Event? 

 

Native! react에서는 참 많이 들어본 말이지만 vue에서의 native는 생소하다. 실제로 검색을 해봐도 react에 관련된 내용만 잔뜩 나오지 vue.js native에 대해서는 자료가 적다. 공식문서를 살펴보면 커스텀이벤트 페이지에서 다루고 있으며, 컴포넌트에 이벤트를 바인딩할 때 사용한다.

 

 

공식사이트의 문구를 살펴보면 컴포넌트에서 루트엘리먼트의 네이티브 이벤트를 직접 감지하고 싶은 경우, v-on에 사용한다고 설명되어 있다. 해당 문구만으로는 네이티브가 정확히 어떤일을 하는지 파악하기 어렵다. 기능과 함께 vue에서 이야기하는 native가 무엇인지에 대한 고찰을 정리하려한다. 개인적으로 찾아본 자료들을 조합한 고찰이니 완전히 정답이라고 할 수 없다. 기능의 대한 설명을 제외하곤 정확한 정보가 아닐수도 있다는 점 참고!

 

 

 


 

 v-on의 native는 어떤 기능을 할까? 

 

네이티브는 v-on 이벤트 장식자로 취급되는데 컴포넌트의 루트 요소 위에 있는 네이티브 이벤트를 호출한다. 선언법은 아래와 같다.

<컴포넌트명 @이벤트핸들러.native="이벤트함수명"></컴포넌트명>

 

컴포넌트에서 이벤트를 직접 호출하고 싶은 경우에 사용한다. 아래와 같이 하위 컴포넌트에 버튼을 두고 상위컴포넌트로 감싸고 있는 형태에, 하위컴포넌트의 이벤트를 전달하기 위해서는 $emit을 이용하는것이 보통이다. 

하지만 상위컴포넌트의 이벤트를 호출하고 싶을때는 어떻게 해야할까? 하위컴포넌트에 해당 이벤트를 작성해도 이벤트는 작동하지 않는다. 그 이유는 vue 구성요소는 기본 요소에 바인딩하려고 하기때문이다. 이 문제를 해결하기 위해 존재하는 장식자가 바로 네이티브다. 네이티브를 붙인다면 기본요소를 무시하고 상위컴포넌트에 있는 이벤트를 실행시킨다. 코드로 결과를 확인해보자!

 

<!-- app -->

<template>
  <div id="app">
  <h5>app 이벤트</h5>
  <button @click="clickTest">appBtn</button>
  <h5>클릭 이벤트</h5>
  <Test @click="clickTest"></Test> <!-- 하위컴포넌트 -->
  <h5>클릭 native 이벤트</h5>
  <Test @click.native="clickTest"></Test> <!-- 하위컴포넌트 -->
  </div>
</template>

<script>
import Test from '../src/components/test.vue'

export default {
  name: 'App',
  methods:{
  	clickTest(){
      alert("Clicked");
    }
  },
  components: {
    'Test' : Test
  }
}
</script>
<!-- test(하위컴포넌트) -->

<template>
    <div>
        <button>componentBtn</button>
    </div>
</template>

이미지를 누르면 gif가 실행됩니다


 vue의 Native event가 무엇일까? (개인적고찰) 

 

Native의 단어 뜻은 본래의, 고유의, 토착의 라는 뜻을 가졌으며 vue에서의 네이티브는 Native DOM event(네이티브 돔 이벤트)를 의미한다. 네이티브 돔 이벤트을 검색해보면 제이쿼리가 많이 나오는데, 제이쿼리에서 사용하는 대부분의 메소드에 해당하기 때문이다. 제이쿼리의 이벤트 방법에 대해서는 해당링크를 통해 읽어보는걸 추천한다. 제이쿼리는 dom 요소를 직접적으로 참조하여 가져온 후 이벤트헨들러로 dom 이벤트를 처리한다. 이런 내용을 통해 생각을 정리해보았을때, 네이티브 이벤트라는것은 dom 요소를 직접 참조하여 이벤트를 실행시키는것이 라는 뜻이지 않을까? 이 내용을 착안해서 vue는 루트엘리먼트(상위컴포넌트)의 요소를 직접 참조하여 이벤트한다 라는 느낌으로 사용하는 것 같다. 몰론 개인적인 생각이므로 맞는지는 정확하게 확인하지 못했다.😂 vue는 검색해도 내용이 너무 안나온다.

 

왜 html 리스너를 사용하는가

 

왜 내가 제이쿼리를 보고 네이티브를 이해했냐면, vue는 html 리스너를 사용하기 때문이다. 기존에 있는 일반 이벤트 처리와 방식이 크게 다르지 않는 이유도 여기에 있다. 어림짐작으로 v-on은 js의 addEventListener 역할을 하는게 아닐까 싶다. vue는 새로운 프레임워크지만 기존의 소비자가 어렵지않게 사용할 수 있도록 기존의 사용법을 많이 착안해서 제작한것같다. 정말 좋은쪽으로 발전한 프레임워크다😍

 

 

※ addEventListener : 특정 함수에 이벤트핸들러를 부착시키는 기능. 하나의 요소에 다양한 이벤트 핸들러를 추가할 수 있다. click, keyup등 이벤트등 많은 이벤트를 해당 기능으로 등록할 수 있다.

element.addEventListener('이벤트명(click,select 등)', 지정할 콜백함수, 이벤트버블링, 캡쳐링 실행여부(true, false));

 

※  dom에 관련되서는 해당링크를, 이벤트객체에 대해서는 해당링크를 더 읽어보자!

 

 

 

 

 

 

해당문서에 대해 오류가 있다면 댓글로 지적부탁드립니다 :)

 

 

 

 

참고문서 : http://www.tcpschool.com/javascript/js_event_eventListenerCall

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

참고문서 : https://kr.vuejs.org/v2/guide/components-custom-events.html

 

커스텀 이벤트 — Vue.js

Vue.js - 프로그레시브 자바스크립트 프레임워크

kr.vuejs.org

참고문서 : https://chlolisher.tistory.com/10

 

[ JavaScript ] Event의 종류 : addEventListener, removeEventListener 메서드

addEventListener : 특정 이벤트가 발생했을 시, 특정 함수를 실행할 수 있게 해주는 기능.  addEventListener으로 등록할 수 있는 이벤트 중 자주 쓰이는 이벤트 목록 이벤트 명 설명 change 변동이 있을 때

chlolisher.tistory.com

참고문서 : https://codepen.io/gambo/pen/JrPeqN

 

Vue.js Native Events

...

codepen.io

 

읽으면 좋은 문서 : https://programmer-seva.tistory.com/63

 

[스터디_자바스크립트] 50. DOM 심화 - DOM 이벤트

1. DOM 이벤트 개요 DOM의 이벤트는 DOM 내의 element, document 개체, window 개체와 관련되어 발생하는 사전 정의된 시점이나 사용자 정의 시점을 말한다. 이 시점이 발생할 때 실행될 기능(핸들러/콜백)

반응형
반응형

docs(KO)
docs(EN)
docs/api(EN)
redux-saga로 비동기처리와 분투하다
Redux-saga: 제너레이터와 이펙트

redux-saga

redux-saga는 애플리케이션의 side effect 를 보다 쉽게 관리하고, 실행하기 쉽고, 테스트하기 쉬우 며, 오류를 더 잘 처리하는 것을 목표로 하는 라이브러리입니다.

Mental model은 saga가 side effect에 대한 책임이 있는 애플리케이션의 별도 스레드와 같다는 것입니다. redux-saga는 redux 미들웨어이기 때문에,이 스레드는 정상적인 redux 작업으로 메인 애플리케이션에서 시작, 일시 중지 및 취소 할 수 있으며 전체 redux 애플리케이션 상태에 액세스 할 수 있으며 redux 작업도 전달(dispatch)할 수 있습니다.

Generators라는 ES6 기능을 사용하여 이러한 비동기 흐름을 쉽게 읽고, 쓰고, 테스트 할 수 있습니다. 이렇게하면 비동기 흐름이 표준 동기 JavaScript 코드처럼 보입니다. (async/await와 비슷하지만 Generator는 우리가 필요로하는 몇 가지 멋진 기능을 더 가지고 있습니다)

비동기 작업을 처리하기 위해 이전에 redux-thunk를 사용했을 수 있습니다. redux thunk와는 달리 콜백 지옥으로 끝나지 않고 비동기 흐름을 쉽게 테스트 할 수 있으며 작업은 순수하게 유지됩니다.

Side effect: data fetching와 같은 비동기식 작업 또는 브라우저 캐시 액세스와 같은 순수하지 않은 동작

Quick Start

프로젝트가 이미 React, Redux로 이루어져있고, redux-saga 를 추가한다고 가정하겠습니다.

폴더 구조는 이렇습니다.

...
/store
  /reducer
  /saga
    |index.js
    |exampleSaga.js
  |index.js
App.jsx

/store/saga/exampleSaga.js

import { delay } from 'redux-saga'
import { put, takeEvery } from 'redux-saga/effects'

// worker Saga: 비동기 증가 태스크를 수행할겁니다.
export function* incrementAsync() {
  yield delay(1000)
  yield put({ type: 'INCREMENT' })
}

// watcher Saga: 각각의 INCREMENT_ASYNC 에 incrementAsync 태스크를 생성할겁니다.
export function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}

/store/saga/index.js

// 모든 Saga들을 한번에 시작하기 위한 단일 entry point 입니다.
export default function* rootSaga() {
  yield all([
    helloSaga(),
    watchIncrementAsync()
  ])
}

/store/index.js

// ...
import rootSaga from './sagas'

const sagaMiddleware = createSagaMiddleware()
const store = ...
sagaMiddleware.run(rootSaga)

// ...

들어가며 (Generator and Effect)

reference

들어가기에 앞서서 어려운 개념을 먼저 짚어보자.

Generator

앞서 말했듯이 redux-saga 에서는 Generator 문법을 사용한다. 그리고 redux-saga 에서 말하는 saga는 바로 Generator function이다. (Generator가 아니다) saga를 만들어 redux-saga 미들웨어에 등록하면 이 미들웨어가 generator function 으로부터 만들어진 generator를 계속 실행하여 산출된 모든 effect를 실행한다. 즉, saga는 yield 값만을 반환하기만 하고 미들웨어가 이 값(effect)를 받아서 실행하는 역할을 맡는다. saga는 단순히 반환만 하기 때문에 (직접 비동기 처리를 하지 않음) 테스트가 훨씬 용이해진다.

Effect

그럼 Effect는 무엇인가? saga가 반환하는 값이자 미들웨어가 실행할 명령을 담고 있는 자바스크립트 객체라고 생각하면 된다. 이를 통해 saga가 비동기 처리를 하지 않고 미들웨어에게 실행의 책임을 떠넘길 수 있는 것이다. 정리하자면, Saga는 명령을 담고 있는, 이펙트라 부르는 순수한 객체를 yield 할 것이고, 미들웨어는 이런 명령들을 해석해 처리하고, 그 결과를 다시 Saga에 돌려준다. 바로 위 reference 에서 가져온 내용이며 정리가 훨씬 잘되어있으니 꼭 읽어보길 바란다.

Task

하나의 saga가 실행되는 것을 task라고 부른다.

기본 개념

헬퍼 함수 (effect creators)

redux-saga에서는 Task를 만들기 위해 내부 함수를 감싸는 몇몇 helper 함수를 제공한다. (effect creators라고도 부른다.)

functiondescription
takeEvery(action, sagaFn) action이 발생할 때마다 Task(sagaFn)가 실행되게 한다. 여러 개의 Task를 동시에 시작할 수 있다. redux-thunk와 비슷한 기능이다.
takeLast(action, sagaFn) 마지막으로 발생한 하나의 action에만 Task(sagaFn)가 실행되게 한다. 실행중이던 Task는 action이 발생하면 취소되고 새로운 Task가 실행된다.

서술적 이벤트

Saga 로직을 표현하기 위해서 saga는 순수 Javascript object를 yield한다. 이런 object를 effect라고 부른다. effect란 미들웨어에 의해 해석되는 몇몇 정보들을 담고있는 간단한 객체이다. 어떤 기능을 수행하기 위해 미들웨어에 전해지는 명령(스토어에 액션을 dispatch하는 행위나 비동기 함수를 호출하는 등)이라고 볼 수 있다.

effect는 redux-saga/effects 에서 제공하는 함수(effect creator)로 만들어진다. 대표적인 effect creator는 아래와 같다.

functiondescription
select state에서 필요한 데이터를 꺼낸다.
put Action을 dispatch한다.
take Action/이벤트 발생을 기다린다.
call(fn, ...args) Promise의 완료를 기다린다. apply 함수와 동일하다.
fork 다른 Task를 시작한다.
join 다른 Tack의 종료를 기다린다.

3. Advanced Concepts

3.1 Pulling future actions (take)

위에서 살펴봤던 takeEvery 함수를 사용하는 것은 redux-thunk와 유사하다. 사실, takeEvery는 take  fork 함수를 사용한 high-level API에 불과하다.

const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() {
  while (true) {
    const action = yield take(patternOrChannel)
    yield fork(saga, ...args.concat(action))
  }
})

take는 특정한 액션이 dispatch될 때까지 기다린다. takeEvery의 경우, 실행된 태스크는 그들이 다시 실행될 때에 대한 관리 방법이 없다. take의 경우 액션이 푸시(push)되는 대신, saga 스스로 액션을 풀링(pulling)하기 때문에 특별한 컨트롤 프로우를 수행할 수 있게 한다. 전통적인 액션의 푸시 접근법을 해결하는 것이다.

이 pulling 접근법은 동기적(synchronous) 스타일로 컨트롤 플로우를 표현할 수 있게 한다. 예를 들어 LOGIN, LOGOUT 액션을 이용하여 로그인 플로우를 만들고 싶을 때 takeEvery를 이용하면 두 개의 테스크(saga)를 작성해야 했을 것이다. 하지만 take를 사용해 하나의 태스크로 만들 수 있다.

function* loginFlow() {
  while (true) {
    yield take('LOGIN')
    // ... perform the login logic
    yield take('LOGOUT')
    // ... perform the logout logic
  }
}

3.2 non-blocking calls (fork)

saga는 effect creators 함수를 통해 blocking 하게 작동한다. 이 때문에 발생하는 몇 가지 문제점이 있어 non-blocking을 지원하기 위한 fork effect creator를 제공한다. Task를 fork한다면 그 테스크는 백그라운드에서 시작되고, 호출자는 fork된 테스크가 종료될 때까지 기다리지 않고 플로우를 계속해서 진행한다. yield fork는 task object 를 반환하기 때문에 후에 테스크 취소가 가능하다.

3.3 Running tasks in parallel (all)

병렬 처리가 필요한 경우 all 을 사용한다.

import { all, call } from 'redux-saga/effects'

// correct, effects will get executed in parallel
const [users, repos]  = yield all([
  call(fetch, '/users'),
  call(fetch, '/repos')
])

위와 같이 effectdml 배열을 yield하면, 제너레이터는 모든 effect들이 resolve되거나, 어느 하나라도 reject될 때까지 봉쇄(blocked)된다.

3.4 Starting a race between multiple Effects (race)

여러 task를 병렬로 시작하지만, 그 task를 전부 기다리고 싶지 않을 때가 있다. 이 경우는 race 를 사용한다. race는 첫 번째로 resolve(or reject)된 task가 나오면 나머지 task를 자동으로 취소시킨다.

3.11 Connecting Sagas to external Input/Output

take 는 스토어에 dispatch될 액션이 들어오면 resolve 되었다. 그리고 put은 액션을 인자로 dispatch함으로써 resolve된다. saga가 시작될 때 미들웨어는 자동으로 take/put을 스토어와 연결한다. 이 두 이펙트는 saga의 입력/출력처럼 보일 수 있다.

redux-saga는 리덕스 미들웨어 환경 바깥에서 사가를 실행하고 커스텀 입/출력에 연결할 수 있는 방법을 제공한다.

import { runSaga } from 'redux-saga'

function* saga() { ... }

const myIO = {
  subscribe: ..., // this will be used to resolve take Effects
  dispatch: ...,  // this will be used to resolve put Effects
  getState: ...,  // this will be used to resolve select Effects
}

runSaga(
  saga(),
  myIO
)

3.12 Using Channels

채널은 외부의 이벤트 소스 또는 사가 간의 통신을 위해 해당 이펙트를 일반화한다. 또한 스토어에서 특정 작업을 대기열에 넣을 때도 사용할 수 있다.

이 장에서, 다음 내용을 살펴볼 것이다.

  • yield actionChannel 이펙트를 이용해 스토어의 특정 액션을 버퍼링하는 방법
  • eventChannel 팩토리함수를 사용하여 take 이펙트를 외부 이벤트 소스에 연결하는 방법
  • 일반 channel 팩토리 함수를 이용하여 채널을 만드는 방법과 사가 간의 통신을 위해 take/put 이펙트에 이를 사용하는 방법

actionChannel effect 사용하기

import { take, fork, ... } from 'redux-saga/effects'

function* watchRequests() {
  while (true) {
    const {payload} = yield take('REQUEST')
    yield fork(handleRequest, payload)
  }
}

function* handleRequest(payload) { ... }

위 예제는 전형적인 watch와 fork 패턴이다. 짧은 시간에 많은 액션이 들어온다면 동시에 많은 haneldRequest 테스크가 실행될 것이다. 근데 우리는 이 테스크를 순차적으로 처리하고 싶다. 그래서 우리는 아직 처리되지 않은 액션을 대기열에 집어넣고, 현재 요청을 모두 처리했다면 대기열에서 다음 것을 가져올 것이다. 이를 actionChannel을 사용해 구현할 수 있다.

import { take, actionChannel, call, ... } from 'redux-saga/effects'

function* watchRequests() {
  // 1- Create a channel for request actions
  const requestChan = yield actionChannel('REQUEST')
  while (true) {
    // 2- take from the channel
    const {payload} = yield take(requestChan)
    // 3- Note that we're using a blocking call
    yield call(handleRequest, payload)
  }
}

function* handleRequest(payload) { ... }
  1. actionChannel을 생성한다. take와의 차이점은, saga가 아직 그 액션을 처리할 준비가 되지 않았다면 actionChannel은 들어오는 메시지(액션)을 버퍼링할 수 있다는 것이다.
  2. 스토어에서 특정 액션을 받기 위해 take(pattern) 에 넣을 패턴을 사용한 것처럼, take(channel) 도 가능하다. take는 메시지를 받을 수 있을 때만 saga를 봉쇄할 것이다. 채널 버퍼에 메시지가 저장되어 있을 경우에만 봉쇄되지 않고 진행할 것이다.
  3. saga는 call(handleRequest)가 반환될 때까지 봉쇄를 유지할 것이다. 하지만 봉쇄되어 있는 중에 다른 REQUEST 액션이 dispatch된다면, 그것은 채널의 버퍼에 저장될 것이다. saga가 call(handleRequest)에 의해 재개되고 다음 yield take(requestChan)이 실행될 때, take는 대기열에 저장된 메시지를 resolve할 것이다.

기본적으로 actionChannel은 제한 없이 버퍼링이 가능하나 버퍼 인자를 주어 제한할 수도 있다.

eventChannel 팩토리를 사용해 외부 이벤트에 연결하기

eventChannel(effect creator가 아닌 팩토리 함수)는 리덕스 스토어가 아닌 외부 이벤트를 위한 채널을 생성한다.

이 예제는 일정한 간격마다 채널을 생성한다.

import { eventChannel, END } from 'redux-saga'

function countdown(secs) {
  return eventChannel(emitter => {
      const iv = setInterval(() => {
        secs -= 1
        if (secs > 0) {
          emitter(secs)
        } else {
          // this causes the channel to close
          emitter(END)
        }
      }, 1000);
      // The subscriber must return an unsubscribe function
      return () => {
        clearInterval(iv)
      }
    }
  )
}

eventChannel의 첫 번째 인자는 구독자(subscriber) 함수이다. 구독자의 역할은 외부의 이벤트 소스를 초기화하고 (위의 setInterval 사용), 제공된 emitter를 실행하여 소스에서 채널로 들어오는 모든 이벤트를 라우팅한다. 위의 예제에서 우리는 매 초마다 emitter를 호출한다.

주의: 이벤트 채널을 통해 null 또는 undefined를 전달하지 않도록 해야한다. 숫자를 전달하는 것이 좋지만, 이벤트 채널 데이터를 리덕스 액션처럼 구조화하는 것을 추천한다. number를 { number }로 바꾸는 것과 같이.

emitter(END) 호출에도 주의해야 한다. 우리는 채널 소비자에게 채널이 폐쇄되었다는 것을 알리기 위해 사용한다. 이는 더 이상 다른 메시지가 이 채널을 통해 올 수 없다는 것을 의미한다.

우리의 사가에서 이 채널을 어떻게 쓰는지 보자 (이 예제는 저장소(repository)의 cancellable-counter 예제에서 가져왔습니다.)

import { take, put, call } from 'redux-saga/effects'
import { eventChannel, END } from 'redux-saga'

// creates an event Channel from an interval of seconds
function countdown(seconds) { ... }

export function* saga() {
  const chan = yield call(countdown, value)
  try {    
    while (true) {
      // take(END) will cause the saga to terminate by jumping to the finally block
      let seconds = yield take(chan)
      console.log(`countdown: ${seconds}`)
    }
  } finally {
    console.log('countdown terminated')
  }
}

사가는 take(chan)를 yield하고 있다. 메시지가 채널에 들어가기 전까지 사가는 봉쇄된다. 위의 예제에서, 이는 emitter(secs)를 호출할 때와 일치한다. 우리가 try/finally 구역 내에서 전체 while(true) {...}를 실행하고 있는 것에 주목해보자. countdown의 interval이 종료되면, countdown 함수는 emitter(END)를 호출함으로써 이벤트 채널을 폐쇄한다. 채널을 닫으면 그 채널에서 take에 봉쇄된 모든 사가들을 종료시키는 효과가 있다. 예제에서, 사가를 종료하면 finally 구간으로 점프하게 된다 (finally 구간이 없으면 그냥 종료된다).

구독자는 unsubscribe 함수를 반환한다. 이것은 이벤트 소스가 완료되기 전에 채널 구독을 취소하는 데에 사용된다. 이벤트 채널의 메시지를 소비하는 사가 내에서 이벤트 소스가 완료되기 전에 일찍 나가기를 원한다면 (예로, 사가가 취소됨) chan.close()를 호출해 채널을 폐쇄하고 구독을 취소할 수 있다.

여기 웹 소켓 이벤트를 사가에 전달하여 이벤트 채널을 사용하는 또 다른 예제가 있다. ping이라는 서버 메시지를 기다리고 있고, 조금 뒤에 pong이라는 메시지로 답한다고 가정해보자.

import { take, put, call, apply } from 'redux-saga/effects'
import { eventChannel, delay } from 'redux-saga'
import { createWebSocketConnection } from './socketConnection'

// this function creates an event channel from a given socket
// Setup subscription to incoming `ping` events
function createSocketChannel(socket) {
  // `eventChannel` takes a subscriber function
  // the subscriber function takes an `emit` argument to put messages onto the channel
  return eventChannel(emit => {

    const pingHandler = (event) => {
      // puts event payload into the channel
      // this allows a Saga to take this payload from the returned channel
      emit(event.payload)
    }

    // setup the subscription
    socket.on('ping', pingHandler)

    // the subscriber must return an unsubscribe function
    // this will be invoked when the saga calls `channel.close` method
    const unsubscribe = () => {
      socket.off('ping', pingHandler)
    }

    return unsubscribe
  })
}

// reply with a `pong` message by invoking `socket.emit('pong')`
function* pong(socket) {
  yield call(delay, 5000)
  yield apply(socket, socket.emit, ['pong']) // call `emit` as a method with `socket` as context
}

export function* watchOnPings() {
  const socket = yield call(createWebSocketConnection)
  const socketChannel = yield call(createSocketChannel, socket)

  while (true) {
    const payload = yield take(socketChannel)
    yield put({ type: INCOMING_PONG_PAYLOAD, payload })
    yield fork(pong, socket)
  }
}

주의: eventChannel의 메시지는 기본적으로 버퍼링되지 않는다. 채널의 버퍼링 전략을 지정하려면 eventChannel 팩토리에 버퍼를 인수로 넣어줘야 한다 (예: eventChannel(subscriber, buffer)).

saga간 통신에 채널 사용하기

기본적으로 어떤 소스에도 연결되지 않은 채널을 직접 생성할 수 있다. 그런 다음 채널에 수동으로 put 할 수 있다. 이는 사가 간에 통신을 하기 위해 채널을 사용할 때 유용하다.

3.13 Root Saga Patterns

root Saga는 sagaMiddleware가 실행될 수 있도록 여러 Sagas를 단일 entry point으로 합친다. 초급 튜토리얼에서 루트 사가는 다음과 같이 보일 것입니다.

export default function* rootSaga() {
  yield all([
    helloSaga(),
    watchIncrementAsync()
  ])
  // code after all-effect
}

이것은 루트를 구현하는 몇 가지 방법 중 하나입니다. 여기서 all effect는 배열과 함께 사용되며 sagas는 병렬로 실행됩니다. 다른 루트 구현은 오류 및 더 복잡한 데이터 흐름을 더 잘 처리하는 데 도움이 될 수 있습니다.

Non-blocking fork effects

contributor @slorber가 issue #760에서 몇 가지 다른 일반적인 루트 구현을 언급했습니다. 시작하려면 위 예제와 유사하게 작동하는 인기있는 구현이 하나 있습니다.

export default function* rootSaga() {
  yield fork(saga1)
  yield fork(saga2)
  yield fork(saga3)
  // code after fork-effect
}

세 개의 고유한 yield fork를 사용하면 task descriptor가 세 번 반환됩니다. 앱의 결과 동작은 모든 하위 사가가 동일한 순서로 시작되고 실행된다는 것입니다. 포크가 차단되지 않기 때문에 rootSaga는 자식 무용담이 계속 실행되고 내부 효과에 의해 차단되는 동안 완료 될 수 있습니다.

하나의 큰 모든 effect와 여러 개의 포크 effect의 차이점은 all effect가 blocking이라는 것이다. 따라서 all-effect 이후의 코드는 모든 자식 sagas가 완료 될 때 실행되는 반면, 포크 효과는 차단되지 않으므로 이후 코드 포크 효과는 포크 효과를 산출 한 직후에 실행됩니다. 또 다른 차이점은 포크 효과를 사용할 때 작업 설명자를 얻을 수 있다는 것입니다. 따라서 후속 코드에서 작업 설명자를 통해 분기 된 작업을 취소/결합 할 수 있습니다.

Nesting fork effects in all effect

const [task1, task2, task3] = yield all([ fork(saga1), fork(saga2), fork(saga3) ])

루트 사가를 디자인 할 때 또 다른 인기있는 패턴이 있습니다. 모든 효과의 중첩 포크 효과입니다. 이렇게하면 작업 설명 자의 배열을 얻을 수 있으며 각 포크 효과가 non-blocking이고 동기적으로 작업 설명자를 반환하기 때문에 모든 effect 이후의 코드가 즉시 실행됩니다.

fork 효과는 all 효과에 중첩되지만 항상 기본 forkQueue를 통해 상위 작업에 연결됩니다. 분기 된 작업에서 포착되지 않은 오류는 상위 작업으로 버블링되므로 중단(및 모든 하위 작업)-상위 작업에서 포착 할 수 없습니다.

Avoid nesting fork effects in race effect

// DO NOT DO THIS. The fork effect always win the race immediately.
yield race([
  fork(someSaga),
  take('SOME-ACTION'),
  somePromise,
])

반면에 레이스 효과의 포크 효과는 버그 일 가능성이 높습니다. 위의 코드에서 포크 효과는 차단되지 않기 때문에 항상 즉시 레이스에서 승리합니다.

Keeping the root alive

실제로 rootSaga가 개별 하위 효과 또는 saga의 첫 번째 오류에서 종료되고 전체 앱이 충돌하므로 이러한 구현은 그다지 실용적이지 않습니다! 특히 Ajax 요청은 앱이 요청하는 모든 엔드 포인트의 상태에 따라 앱을 결정합니다.

spawn은 부모와 자식 사가의 연결을 끊는 효과로 부모와 충돌하지 않고 실패 할 수 있습니다. 분명히 이것은 오류가 발생할 때에도 여전히 처리해야하는 개발자로서의 우리의 책임에서 벗어나지 않습니다. 실제로 이로 인해 개발자의 관점에서 특정 실패가 가려지고 향후 문제가 발생할 수 있습니다.

스폰 효과는 React의 Error Boundaries와 유사한 것으로 간주 될 수 있는데, 이는 saga 트리의 특정 수준에서 추가 안전 조치로 사용되어 실패한 기능을 차단하고 전체 앱이 충돌하지 않도록 할 수 있다는 점입니다. 차이점은 React Error Boundaries에 대해 존재하는 componentDidCatch와 같은 특별한 구문이 없다는 것입니다. 여전히 자체 오류 처리 및 복구 코드를 작성해야합니다.

export default function* rootSaga() {
  yield spawn(saga1)
  yield spawn(saga2)
  yield spawn(saga3)
}

이 구현에서는 한 사가가 실패하더라도 rootSaga와 다른 사가가 죽지 않습니다. 그러나 실패한 사가는 앱의 수명 동안 사용할 수 없기 때문에 문제가 될 수도 있습니다.

Keeping everything alive

어떤 경우에는 실패시 sagas를 다시 시작할 수있는 것이 바람직 할 수 있습니다. 장점은 앱과 sagas가 실패한 후에도 계속 작동 할 수 있다는 것입니다 (예 : takeEvery (myActionType)을 생성하는 saga). 그러나 우리는 이것을 모든 사가를 살리기위한 포괄적 인 해결책으로 권장하지 않습니다. 무용담이 정확하고 예측 가능하게 실패하고 오류를 처리 / 기록하는 것이 더 합리적 일 가능성이 높습니다.

예를 들어 @ajwhite는이 시나리오를 사가를 살아있게 유지하면 해결하는 것보다 더 많은 문제가 발생하는 경우를 제공했습니다.

function* sagaThatMayCrash () {
  // wait for something that happens _during app startup_
  yield take(APP_INITIALIZED)

  // assume it dies here
  yield call(doSomethingThatMayCrash)
}

sagaThatMayCrash가 다시 시작되면 다시 시작되고 응용 프로그램이 시작될 때 한 번만 발생하는 작업을 기다립니다. 이 시나리오에서는 다시 시작되지만 복구되지 않습니다.

그러나 시작으로 이익을 얻을 수있는 특정 상황에 대해 user @granmoe는 issue #570에서 다음과 같은 구현을 제안했습니다.

function* rootSaga () {
  const sagas = [
    saga1,
    saga2,
    saga3,
  ];

  yield all(sagas.map(saga =>
    spawn(function* () {
      while (true) {
        try {
          yield call(saga)
          break
        } catch (e) {
          console.log(e)
        }
      }
    }))
  );
}

이 전략은 우리의 sagas를 try 블록의 하위 작업으로 시작하는 생성 된 생성기 (루트 부모에서 분리)에 자식 sagas를 매핑합니다. 우리의 saga는 종료 될 때까지 실행 된 다음 자동으로 다시 시작됩니다. catch 블록은 우리의 사가에서 던지고 종료되었을 수있는 모든 오류를 무해하게 처리합니다.

반응형
반응형

Node.js 템플릿 엔진

Node.js 에서 사용하는 템플릿 엔진은 굉장히 많이 있다. (pug(jade), ejs, handlebars, mustache 등)

그 중에 자주 사용되는 pug 문법을 정리한다.

https://pugjs.org/api/getting-started.html

여기서 language reference를 보면 pug 문법을 더 명확하게 이해할 수 있다.

* pug를 사용하는 이유

- 사실 어떤 템플릿 엔진을 사용해도 상관없다.

  예를들어 ejs는 html 태그에 부분적인 입력,수정을 하기 때문에  html문법에 이미 적응한 사람이 보기에 쉬울 것이다. 하지만 pug는 코드를 간소화시켰고, 컴파일한 후에 html문서를 렌더링하는 형식이라 생산성이 높아지기 때문에 새로 문법을 익히는데 시간은 조금 걸리지만 장기적으로 봤을 때 이득이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
doctype html
html(lang='en')
  head
    title Pug
    script(type='text/javascript').
      foo = true;
      bar = function () {};
      if (foo) {
      bar(1 + 5)
      }
  body
    h1 Pug - node template engine
    #container.col
      p You are amazing
      p
        | Pug is a terse and simple
        | templating language with a
        | strong focus on performance
        | and powerful features.
 

[pug파일]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Pug</title>
    <script type="text/javascript">
      foo = true;
      bar = function () {};
      if (foo) {
      bar(1 + 5)
      }
    </script>
  </head>
  <body>
    <h1>Pug - node template engine</h1>
    <div id="container" class="col">
      <p>You are amazing</p>
      <p>
        Pug is a terse and simple
        templating language with a
        strong focus on performance
        and powerful features.
      </p>
    </div>
  </body>
</html>

[pug에서 렌더링 된 html파일]

* 기본예제에서 보다시피 Pug에서 가장 중요한 것은 들여쓰기다.

아주 예민해서 Tab으로 들여쓰는 코드를 작성했으면 계속 Tab으로 들여쓰도록 통일해야하고, 띄어쓰기(Space) 두 번을 사용한다면 계속해서 띄어쓰기 두 번으로 통일해야 한다.

특징적인 것을 살펴보면

1. 일단 html에서 닫는 태그가 없다 (</head>, </body>, </title> 등)

들여쓰기로 어디서 부터 어디까지인지 구분하기 때문에 닫는 태그를 적을 필요가 없다.

2. 들여쓰기한 이후에 공백까지가 태그로 된다.

title Pug 로 쓰여있으면 title태그안에 Pug가 들어간 것을 확인할 수 있다. 마찬가지로 h1 pug~~, p You are ~~ 를 봐도 다 첫번째 문자열이 태그로 들어간다.

내가 원하는 태그를 넣고 싶으면 'content 123' 이런식으로 쓰면 '<content>123</content>' 가 만들어진다.

3. 태그 사이가 아닌 태그의 속성으로 넣으려면 ()괄호 사용.

html(lang='en') , script(type='text/javascript') 

4. 여러줄 입력할 때 ( | ) 로 작성.

5. id입력할 때 #container 처럼 #입력 (태그없을 때는 div가 자동적으로 들어가나보다)

6. 클래스는 .col 처럼 .입력

 

 

Pug(Jade) 문법 정리 요약 

Tags(태그)

- 일반적인 태그 입력

- 태그에 클래스 입력

div.user => <div class="user"></div>

div.user.btn => <div class="user btn"></div>

- 태그에 아이디 입력

div#user => <div id="user"></div>

Plain Text(평문)

- 일반 문장 (태그에서 인라인 가능)

p hello <em>world</em> => <p>hello <em>world</em></p>

- 긴 문장 (pip이용 '|' )

1
2
3
4
5
p
  | The pipe always goes at the beginning of its own line,
  | not counting indentation.
<렌더링 >
<p>The pipe always goes at the beginning of its own line, not counting indentation.</p>

- 태그 안에 블럭처리 (ex css, javasciprt) (.) 온점으로 처리함.

(긴 문장이지만 파이프 안씀 script, style, textarea등..)

- 변수 받아 동적 삽입하기 (인터폴레이션)

.txt #{name} and #{email} , { name : 'jeong-pro', email : 'jeong123@naver.com' }

=> <div class="txt">jeong-pro and jeong123@naver.com</div>

* 노드에서 컨텍스트(?)를 넘겨줄 때 사용

Comments(주석)

- 한줄 주석

// 주석입니다

- 여러 줄 주석

들여쓰기로 여러 줄을 주석처리하고 pug에서만 주석을 보고싶으면 //- 를 입력하면 됨.

Attributes(속성)

- 속성 하나 넣기

a(href='google.com') Google => <a href="google.com">Google</a>

- 속성 여러개 넣기

1
2
3
4
5
6
7
input(
  type='checkbox'
  name='agreement'
  checked
)
<렌더링 >
<input type="checkbox" name="agreement" checked="checked" />

- 속성에 자바스크립트 문법을 방해할 수 있는 문자 처리 ("", '')

1
2
3
4
5
6
div(class='div-class' (click)='play()') // 에러남
 
div(class='div-class', (click)='play()') // ,로 처리
div(class='div-class' '(click)'='play()') // '로 처리
<렌더링 >
<div class="div-class" (click)="play()"></div>

- 속성 동적 삽입 하기 (인터폴레이션)

a(href="/#{url}") Link

- Boolean 속성 처리 (boolean속성은 true일 때만 표시)

Template inheritance(템플릿 상속)

위와 같은 layout.pug 파일이 있다면 이것을 상속받을 수 있다.

extends layout.pug를 통해서 템플릿을 상속받으면 해당 'block' 에 입력된다.

이것을 통해 header, content, footer로 나눠서 미리 header, footer는 작성해두고 content부분만 코딩하는 방식으로 사용 가능하다.

(include는 밑에서 설명.)

- append/prepend

위에서 처럼 그냥 extends 하고 block을 쓰면 부모인 layout.pug파일의 block을 덮어씌워 버리는데,

append/prepend를 이용하면 부모block을 유지한 채 붙일 수 있다.

Case(case조건문)

Switch문 처럼 사용가능

 

Code(코드)

Pug 템플릿 파일 안에서 자바스크립트 코드를 사용할 수 있다.

방법 3가지 중 하나는 ( - ) 를 사용 하는 것이다. - var friends = 0 처럼!

'-'(하이픈) 을 이용하면 조건문 반복문등을 사용할 수 있다.

두 번째 방법은 '='을 이용해서 이스케이프된 버퍼 코드를 쓸 수 있다.

- var foo = 'bar'

h1 =foo

를 쓰면 <h1>bar</h1> 이런식으로 사용 가능하다.

* 위 그림처럼 이스케이프되는 이유는 보안상의 이유다.

이스케이프되지 않은 자바스크립트코드를 이용하고 싶으면 '!=' 를 이용하면 된다.

그러면 'This code is <escaped>!' 라고 나올 것이다.

 

Conditionals(조건절)

일급 조건문에서 - 선택적 괄호를 허용한다. (아래와 같이 사용이 가능하다 정도로 이해...)

Filters(필터)

- Makedown이나 scss 같은 필터를 미리 만들어 놓고 ( : ) 로 필터를 적용하는 문법이다.

 커스텀필터를 만들어서 사용할 수 있다. 자세한 내용은 Pug페이지 참조.

Includes(인클루드)

위 처럼 파일을 분할해서 불러오는 역할을 하는 기능이다. pug파일 뿐만아니라 평범한 text 파일도 include를 이용해서 불러 올 수 있다.

Iteration(반복)

- Pug 에서는 each 와 while 두가지 반복문을 지원한다.

else문, 삼항연산자도 사용 가능

 

Mixins(믹스인)

- 믹스인은 재사용가능한 블럭을 만드는 것이라 할 수 있다. 그래서 그것을 재사용한다.

함수처럼 선언(mixin)하고 사용(+)하면 된다. argument를 받아서 사용하는 것도 볼 수 있다.

block과 함께 사용해서 사용하면 더 다양하게 사용 가능하다.

믹스인 안에 block을 두고 추가적으로 p This is my ~~ 이런식으로 추가한 것을 볼  수 있다.

믹스인에서 속성을 적용할 수도 있고

...items 처럼 유동적인 매개변수를 받을 수도 있다.

Mixin의 활용이 무궁무진하다.



출처: https://jeong-pro.tistory.com/65 [기본기를 쌓는 정아마추어 코딩블로그]

반응형
반응형

새 프로젝트에서 redux-saga를 써보려고하는데 헷갈려서 주로 쓰는 함수를 정리해보았다.

redux-saga docs 링크(한글번역)

 

읽어주세요 · GitBook

No results matching ""

mskims.github.io

 

delay

설정된 시간 이후에 resolve하는 Promise객체를 리턴한다.
예시: delay(1000)
→ 1초 기다리기

 

put

특정 액션을 dispatch하도록 한다.
예시: put({type: 'INCREMENT]})
→INCREAMENT action을 dispatch한다.

 

takeEvery

들어오는 모든 액션에 대해 특정 작업을 처리해 준다.
예시: takeEvery(INCREASE_ASYNC, increaseSaga)
→들어오는 모든 INCREASE_ASYNC액션에 대해 increaseSaga 함수 실행

 

takeLatest

기존에 진행 중이던 작업이 있다면 취소 처리하고 가장 마지막으로 실행된 작업만 수행한다.
예시: takeLatest(DECREASE_ASYNC, decreaseSaga)
→DECREASE_ASYNC액션에 대해서 기존에 진행 중이던 작업이 있다면 취소 처리하고 가장 마지막으로 실행된 작업에 대해서만 decreaseSaga함수를 실행한다.

 

call

함수의 첫 번째 파라미터는 함수, 나머지 파라미터는 해당 함수에 넣을 인수이다.
예시: call(delay, 1000)
→delay(1000)함수를 call함수를 사용해서 이렇게 쓸 수도 있다.

call과 put의 다른 점은 put은 스토어에 인자로 들어온 action을 dispatch하고, call인 경우에는 주어진 함수를 실행하게 되는 것이다.

 

all

all함수를 사용해서 제너레이터 함수를 배열의 형태로 인자로 넣어주면, 제너레이터 함수들이 병행적으로 동시에 실행되고, 전부 resolve될때까지 기다린다. Promise.all과 비슷하다고 보면된다.
예시: yield all([testSaga1(), testSaga2()])
→ testSaga1()과 testSaga2()가 동시에 실행되고, 모두 resolve될 때까지 기다린다.

 

출처:https://sustainable-dev.tistory.com/94

반응형

+ Recent posts