Jekyll
2020-05-15T01:45:05+00:00
https://min9nim.github.io//
Learn, apply and share
SW 개발에 대한 이야기
[공지] 20년 5월 블로그 이사갑니다
2020-05-15T00:10:00+00:00
2020-05-15T00:10:00+00:00
https://min9nim.github.io/2020/05/notice
<h4 id="gatsby-기반의-새로운-블로그로-이사를-갑니다">Gatsby 기반의 <a href="https://min9nim.now.sh/">새로운 블로그</a>로 이사를 갑니다.</h4>
<p><br /></p>
<h3 id="-httpsmin9nimnowsh">👉 https://min9nim.now.sh</h3>
<p><br /></p>
<p>당분간 이 블로그는 이대로 현장보존 합니다. 😀</p>
Gatsby 기반의 새로운 블로그로 이사를 갑니다.
JavaScript 세미콜론 사용
2020-05-07T00:10:00+00:00
2020-05-07T00:10:00+00:00
https://min9nim.github.io/2020/05/javascript-semicolon
<p>자바스크립트는 문장(statement)의 구분을 위해 세미콜론 <code class="language-plaintext highlighter-rouge">;</code> 을 사용한다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log('11'); console.log('22');
</code></pre></div></div>
<p><br /></p>
<p>일반적으로 코드의 가독성을 위해 한줄에 하나의 문장만 사용한다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log('11');
console.log('22');
</code></pre></div></div>
<p>이런 경우 아래와 같이 세미콜론 생략이 가능하다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">11</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">22</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div></div>
<p>생략이 가능한 이유는 자바스크립트가 처리될 때 내부적으로 <a href="https://tc39.es/ecma262/#sec-automatic-semicolon-insertion">세미콜론을 자동으로 삽입</a>해 주기 때문이다.</p>
<p><br /></p>
<p>그렇다면 굳이 줄바꿈시에는 세미콜론을 넣을 필요가 없다고 생각할 수 있겠지만 문제되는 경우가 있기 때문에 주의 해야 한다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log("에러가 발생합니다.")
[1, 2].forEach(console.log)
</code></pre></div></div>
<p>위 예시는 세미콜론 자동삽입 규칙에 해당하지 않기 때문에 세미콜론 자동삽입이 일어나지 않는다. 결국 자바스크립트 실행 엔진은 아래와 같은 문장을 바라볼 것이고</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log('에러가 발생합니다.')[1, 2].forEach(console.log)
</code></pre></div></div>
<p>코드 실행시 <code class="language-plaintext highlighter-rouge">Uncaught TypeError: Cannot read property '2' of undefined</code> 오류가 발생하게 된다.</p>
<p><br /></p>
<p>그럼 이제 마음의 갈등이 일어나기 시작한다.</p>
<p>보다 안전한 코드 작성을 위해 세미콜론을 넣을 것인가. 코드의 간결함을 유지하기 위해 세미콜론을 사용하지 않는 것이 나을까. 이에 대해서 개발자들 사이에 많은 고민들이 있었던 것 같다. 개인의 취향에 따라 달리 결정된 문제일 것이다.</p>
<p><strong>필자는 세미콜론을 사용하지 않는 것을 선호한다</strong>. 그동안의 경험에 비추어볼 때 세미콜론을 사용하지 않아 어떤 문제를 만난 적이 단 한 번도 없었다. 그도 그럴 것이 일단 IDE 자동포맷 기능이나 린트설정 등이 이에 대한 문제를 알아서 잘 처리해 주기 때문이다.</p>
<p>vscode 에서 세미콜론 관련 자동포맷 옵션을 적용하면 아래 코드는</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log('에러가 발생합니다.')
[1, 2].forEach(console.log)
</code></pre></div></div>
<p>자동으로 아래와 같이 변환이 되기 때문에 쉽게 오류 발생지점을 확인할 수 있다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">에러가 발생합니다.</span><span class="dl">'</span><span class="p">)[(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)].</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">)</span>
</code></pre></div></div>
<p><br /></p>
<blockquote>
<p>“완벽함이란 더 이상 보탤 것이 남아 있지 않을 때가 아니라 더 이상 뺄 것이 없을 때 완성된다.” - 생텍쥐페리</p>
</blockquote>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<ul>
<li>https://ko.javascript.info/structure#semicolon</li>
<li>https://feross.org/never-use-semicolons/</li>
<li>https://bakyeono.net/post/2018-01-19-javascript-use-semicolon-or-not.html</li>
</ul>
자바스크립트는 문장(statement)의 구분을 위해 세미콜론 ; 을 사용한다.
[react] 컴포넌트별 scoped 스타일 사용
2020-04-29T00:10:00+00:00
2020-04-29T00:10:00+00:00
https://min9nim.github.io/2020/04/react-scoped-css
<p>css 는 언제나 전역으로 사용되기 때문에 리액트 컴포넌트별로 css 를 사용하더라도 경우에 따라 실렉터가 충돌날 수 있다. 이를 해결하기 위한 다양한 방법이 있겠지만 이 글에서는 사용방법이 간단한 <a href="https://www.npmjs.com/package/scoped-css-loader">scoped-css-loader</a> 모듈을 이용하는 방법을 소개한다.</p>
<p><br /></p>
<h3 id="사용방법">사용방법</h3>
<p>컴포넌트 파일명이 Excerpt.js 라고 하면 css 정의 파일의 파일명을 Excerpt.<strong><em>scoped</em></strong>.scss 로 하고 컴포넌트에서 import 한다.</p>
<p><img src="/images/scoped-scss-1.png" alt="" /></p>
<p><br /></p>
<p>Excerpt.scoped.scss</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.excerpt</span><span class="p">{</span>
<span class="nc">.title1</span> <span class="p">{</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">3px</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><br /></p>
<p>Excerpt.js</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="dl">'</span><span class="s1">./Excerpt.scoped.scss</span><span class="dl">'</span>
<span class="c1">// 이후 컴포넌트 정의 코드</span>
</code></pre></div></div>
<p>끝~</p>
<p><br /></p>
<p>그러면 아래와 같이 해당 컴포넌트의 dom 에는 data-v-xxx 속성이 추가되고 적용되는 실렉터에도 data-v-xxx 속성 실렉터가 자동으로 적용된다.</p>
<p><img src="/images/scoped-scss-2.png" alt="" /></p>
<p><br /></p>
<h3 id="설정방법">설정방법</h3>
<h4 id="모듈-설치">모듈 설치</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn add babel-plugin-react-scoped-css --dev
yarn add scoped-css-loader --dev
</code></pre></div></div>
<p><br /></p>
<h4 id="webpackconfigjs-설정-수정">webpack.config.js 설정 수정</h4>
<p>css-loader 설정 바로 뒤에 <code class="language-plaintext highlighter-rouge">scoped-css-loader</code> 모듈을 추가한다</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nl">test</span><span class="p">:</span> <span class="sr">/</span><span class="se">\.(</span><span class="sr">sc|c|sa</span><span class="se">)</span><span class="sr">ss$/</span><span class="p">,</span>
<span class="nx">use</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">style-loader</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">css-loader</span><span class="dl">'</span><span class="p">,</span>
<span class="na">options</span><span class="p">:</span> <span class="p">{</span>
<span class="na">sourceMap</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">importLoaders</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="c1">// You have to put in after `css-loader` and before any `pre-precessing loader`</span>
<span class="p">{</span> <span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">scoped-css-loader</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">{</span>
<span class="na">loader</span><span class="p">:</span> <span class="dl">'</span><span class="s1">sass-loader</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">},</span>
</code></pre></div></div>
<p><br /></p>
<h4 id="babelrc-수정">.babelrc 수정</h4>
<p>아래 설정을 추가한다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"plugins": ["babel-plugin-react-scoped-css"]
</code></pre></div></div>
<p>바벨설정이 package.json 에 포함되어 있다면 package.json 의 babel 항목에 plugins 를 추가한다.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">..</span><span class="w">
</span><span class="nl">"babel"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"presets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"react-app"</span><span class="p">,</span><span class="w">
</span><span class="s2">"mobx"</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"plugins"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"babel-plugin-react-scoped-css"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span><span class="err">..</span><span class="w">
</span></code></pre></div></div>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<p>https://www.npmjs.com/package/scoped-css-loader</p>
css 는 언제나 전역으로 사용되기 때문에 리액트 컴포넌트별로 css 를 사용하더라도 경우에 따라 실렉터가 충돌날 수 있다. 이를 해결하기 위한 다양한 방법이 있겠지만 이 글에서는 사용방법이 간단한 scoped-css-loader 모듈을 이용하는 방법을 소개한다.
RxJS 의 이해와 사용
2020-04-24T00:10:00+00:00
2020-04-24T00:10:00+00:00
https://min9nim.github.io/2020/04/rxjs
<p><a href="https://rxjs-dev.firebaseapp.com/">RxJS</a> 는 이벤트 기반 프로그래밍에서 함수형 프로그래밍을 이용해 보다 선언적으로 이벤트를 처리할 수 있도록 도와준다.</p>
<p>RxJS는 모든 이벤트를 observable 로 추상화하여 시간에 따른 스트림으로 간주할 수 있게 한다. 그리고 각 이벤트가 observer(이벤트핸들러)에게 전달되기 전에 map, filter 등의 operator 를 이용해 이벤트를 필요한 형태로 재가공하여 observer 에게 전달할 수 있다.</p>
<p>RxJS 는 observable 에서 발생한 이벤트가 observer 에게 전달되기 전에 여러가지 operator 들을 이용하여 이벤트를 보다 선언적으로(직관적으로) 처리할 수 있도록 해준다.</p>
<p>구체적인 예를 통해서 RxJS 의 개념과 동작방식을 알아보자</p>
<p><br /></p>
<p>일반적인 이벤트 처리 방식 예제이다</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Clicked!</span><span class="dl">'</span><span class="p">))</span>
</code></pre></div></div>
<p><br /></p>
<p>이를 RxJS 를 처리하면 아래와 같다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">fromEvent</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">rxjs</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">observable</span> <span class="o">=</span> <span class="nx">fromEvent</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">observer</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Clicked!</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">observable</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="nx">observer</span><span class="p">)</span>
</code></pre></div></div>
<ol>
<li>fromEvent 를 이용해서 이벤트를 observable 객체로 만든다</li>
<li>observer(이벤트핸들러) 를 정의한다</li>
<li>observer 가 observable 을 구독한다</li>
</ol>
<p>아직까지는 간단한 것을 굳이 왜 이렇게 복잡하게 해야하나 의문이 생긴다. 아직까지는 RxJS 의 능력?을 실감할 수 없다.</p>
<p><br /></p>
<p>요건이 조금 추가되어서 클릭한 횟수를 매번 표시해야 한다고 해보자.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Clicked </span><span class="p">${</span><span class="o">++</span><span class="nx">count</span><span class="p">}</span><span class="s2"> times`</span><span class="p">))</span>
</code></pre></div></div>
<p>핸들러가 count 변수를 참조하면서 순수성을 잃었다. 하지만 RxJS 를 이용하면 아래와 같이 <strong>순수함수만을 이용하여 처리가 가능</strong>하다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">fromEvent</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">rxjs</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">scan</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">rxjs/operators</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">observable</span> <span class="o">=</span> <span class="nx">fromEvent</span><span class="p">(</span><span class="nb">document</span><span class="p">,</span> <span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">).</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">scan</span><span class="p">(</span><span class="nx">count</span> <span class="o">=></span> <span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
<span class="kd">const</span> <span class="nx">observer</span> <span class="o">=</span> <span class="nx">count</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Clicked </span><span class="p">${</span><span class="nx">count</span><span class="p">}</span><span class="s2"> times`</span><span class="p">)</span>
<span class="nx">observable</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="nx">observer</span><span class="p">)</span>
</code></pre></div></div>
<p><br />
Note)</p>
<p>RxJS 를 이용하여 이벤트를 다룰 때는 항상 아래와 같은 절차를 거치게 된다.</p>
<ol>
<li>observable 정의
<ul>
<li>어떤 이벤트들을 하나의 observable 로 묶을 것인가</li>
<li>적절한 <a href="https://rxjs-dev.firebaseapp.com/guide/operators">operator</a> 를 이용하여 obserable 을 가공</li>
</ul>
</li>
<li>observer 정의
<ul>
<li>observable 로 부터 이벤트를 전달받으면 어떻게 처리할 지를 정의</li>
</ul>
</li>
<li>observable 와 observer 를 연결
<ul>
<li>observer 와 observable 을 연결한다(<code class="language-plaintext highlighter-rouge">observable.subscribe()</code>)</li>
</ul>
</li>
</ol>
<p><br /></p>
<h3 id="observable">Observable</h3>
<p>Observable 은 시간 흐름에 따라 발생하는 이벤트들의 집합이다. 단순하게 이벤트 스트림으로서 생각해 볼 수 있다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">Observable</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">rxjs</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">observable</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Observable</span><span class="p">(</span><span class="nx">subscriber</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">subscriber</span><span class="p">.</span><span class="nx">next</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="nx">subscriber</span><span class="p">.</span><span class="nx">next</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="nx">subscriber</span><span class="p">.</span><span class="nx">next</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
<span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">subscriber</span><span class="p">.</span><span class="nx">next</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
<span class="nx">subscriber</span><span class="p">.</span><span class="nx">complete</span><span class="p">()</span>
<span class="p">},</span> <span class="mi">1000</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">just before subscribe</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">observable</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">({</span>
<span class="nx">next</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">got value </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">x</span><span class="p">)</span>
<span class="p">},</span>
<span class="nx">error</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">something wrong occurred: </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">err</span><span class="p">)</span>
<span class="p">},</span>
<span class="nx">complete</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">done</span><span class="dl">'</span><span class="p">)</span>
<span class="p">},</span>
<span class="p">})</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">just after subscribe</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div></div>
<p>위에서 정의된 observable 은 observer 에게 연속으로 1,2,3 을 푸시하고 1초 후 4를 푸시하고 마치는 간단한 observable 이다.</p>
<p>이 observable 에 observer 가 등록되면(observer 가 observable 을 구독하면) observer 는 연속으로 1,2,3 을 전달 받고 1초 후 4를 전달받고 끝이 난다.</p>
<p><br /></p>
<p>Note)</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">observable.subscribe</code> 는 observer 를 인자로 받는다.</li>
<li>observable 구독시 <code class="language-plaintext highlighter-rouge">next</code>, <code class="language-plaintext highlighter-rouge">error</code>, <code class="language-plaintext highlighter-rouge">complete</code> 3가지 핸들러 2가지 형태로 전달할 수 있다.
<ol>
<li>ex1) <code class="language-plaintext highlighter-rouge">observable.subscribe({next, error, complete})</code></li>
<li>ex2) <code class="language-plaintext highlighter-rouge">observable.subscribe(next, error, complete)</code></li>
</ol>
</li>
<li>observer 가 observable 을 구독할 때 1,2,3 을 전달받은 시점이 ‘just after subscribe’ 출력 전에 이루어졌다는 것에 유의한다.
<ul>
<li><strong>observable 은 이벤트를 동기 또는 비동기로 발생시킬 수 있음</strong>을 꼭 기억한다.</li>
</ul>
</li>
</ol>
<p><br /></p>
<h3 id="subscription">Subscription</h3>
<p>observable 를 구독하면(subscribe) subscription 객체가 리턴된다. subscription 객체를 이용해 해당 구독을 취소할 수 있다. subscription 은 구독을 취소하기 위한 용도로서만 사용된다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">interval</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">rxjs</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">observable</span> <span class="o">=</span> <span class="nx">interval</span><span class="p">(</span><span class="mi">1000</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">subscription</span> <span class="o">=</span> <span class="nx">observable</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="nx">x</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">x</span><span class="p">))</span>
<span class="c1">// Later:</span>
<span class="c1">// This cancels the ongoing Observable execution which</span>
<span class="c1">// was started by calling subscribe with an Observer.</span>
<span class="nx">subscription</span><span class="p">.</span><span class="nx">unsubscribe</span><span class="p">()</span>
</code></pre></div></div>
<p><br /></p>
<h3 id="subject">Subject</h3>
<p>Subject 는 멀티캐스트를 위한 특별한 유형의 Observable 이다. 일반적으로는 하나의 observable 에는 하나의 observer 만 등록할 수 있다. 하지만 Subject 를 이용하면 하나의 observable 을 구독하는 여러개의 observer 를 생성하고 등록할 수 있다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Subject</span><span class="p">,</span> <span class="k">from</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">rxjs</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">subject</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Subject</span><span class="o"><</span><span class="nx">number</span><span class="o">></span><span class="p">();</span>
<span class="nx">subject</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">({</span>
<span class="na">next</span><span class="p">:</span> <span class="p">(</span><span class="nx">v</span><span class="p">)</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`observerA: </span><span class="p">${</span><span class="nx">v</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
<span class="p">});</span>
<span class="nx">subject</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">({</span>
<span class="na">next</span><span class="p">:</span> <span class="p">(</span><span class="nx">v</span><span class="p">)</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`observerB: </span><span class="p">${</span><span class="nx">v</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">observable</span> <span class="o">=</span> <span class="k">from</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]);</span>
<span class="nx">observable</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="nx">subject</span><span class="p">);</span> <span class="c1">// You can subscribe providing a Subject</span>
<span class="c1">// Logs:</span>
<span class="c1">// observerA: 1</span>
<span class="c1">// observerB: 1</span>
<span class="c1">// observerA: 2</span>
<span class="c1">// observerB: 2</span>
<span class="c1">// observerA: 3</span>
<span class="c1">// observerB: 3</span>
</code></pre></div></div>
<p>위 예제는 먼저 subject 를 생성하고 해당 subject 를 구독하는 2개의 observer 를 subject 를 등록한다. 그리고 subject 가 observer 로서 observable 을 구독하게 한다. 그러면 observable 의 이벤트가 그대로 subject 에 등록된 2개의 observer 에게 모두 전달된다.</p>
<p><br /></p>
<blockquote>
<p>Subject 는 observable 이지만 observer 로서 또 다른 observable 을 구독할 수 있다</p>
</blockquote>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<p>https://rxjs-dev.firebaseapp.com/guide/overview</p>
RxJS 는 이벤트 기반 프로그래밍에서 함수형 프로그래밍을 이용해 보다 선언적으로 이벤트를 처리할 수 있도록 도와준다.
redux-saga 가 해결하는 문제
2020-04-23T00:10:00+00:00
2020-04-23T00:10:00+00:00
https://min9nim.github.io/2020/04/redux-saga
<p align="center">
<img src="/images/redux-saga.png" />
</p>
<p>본 글에서는 <a href="https://redux-saga.js.org/">redux-saga</a> 를 알고 싶지만 왠지 높아 보이는 진입장벽으로 마음이 불편한 개발자들을 위해 작성되었다. redux-saga 가 필요한 이유와 redux-saga 가 어떤 문제를 해결하는 지에 대한 이해를 돕고자 한다.</p>
<p>redux-saga 를 충분히 이해하려면 redux-saga 가 등장하게 된 배경부터 차근차근 짚어 봐야할 것 같다. 그리고 <a href="https://reactjs.org/">react</a>, <a href="https://redux.js.org/">redux</a>, <a href="https://redux.js.org/advanced/middleware">redux middleware</a>, <a href="https://github.com/reduxjs/redux-thunk">redux-thunk</a>, <a href="http://hacks.mozilla.or.kr/2015/08/es6-in-depth-generators/">ES6 generator</a> 에 대한 기본적인 선행 지식이 필요한데 본 글에서 해당 내용들을 자세히 다루지는 않는다. redux-saga 를 사용하는 구체적인 방법도 따로 설명하지는 않는다. 다만 해당 기술들이 해결하는 문제들이 무엇인지 대해서만 집중하여 이야기를 풀어가고자 한다.</p>
<p><br /></p>
<h3 id="intro">Intro.</h3>
<p>모든 컴퓨터 프로그램은 <a href="https://ko.wikipedia.org/wiki/튜링_기계">상태머신</a>으로 추상화된다. 프로그램이 구동되면 초기상태를 가지며 이후 입력이 들어오면 상태가 변하고 그에 따른 출력을 내보낸다. 프로그래밍이란 초기상태를 정의하고 입력에 따른 상태변이와 출력을 어떻게 제어할 지를 정의하는 작업의 연속이다. 웹애플리케이션도 마찬가지다. 지금부터는 이 관점을 지속적으로 유념해 주기 바란다.</p>
<p align="center">
<img src="/images/turing-machine.gif" alt="튜링머신 이미지" />
<br />
<<a href="http://www.aistudy.co.kr/logic/logic_rucker.htm">그림1: www.aistudy.co.kr</a>>
</p>
<p><br /></p>
<h3 id="react">React</h3>
<p>SPA 웹프로젝트가 유행하면서 컴포넌트 기반의 라이브러리들이 많이 생겨났다. 대표적인 예로 Vue, React 등이 있겠다. 사용방법은 다르지만 이 들의 기본적인 관심은 화면을 컴포넌트 단위로 구조화 하고 애플리케이션의 상태를 효과적으로 화면에 렌더링 하는데에 있다. 리액트를 이용해 UI를 컴포넌트 기반으로 제작하고 애플리케이션의 상태를 화면으로 렌더링하는 것은 어렵지 않게 할 수 있다. 하지만 애플리케이션 개발시에는 화면을 그려내는 것 외에도 외부 입력을 받아서(사용자 입력 등) 이에 따른 적절한 비즈니스 로직을 처리하고 그 결과를 애플리케이션 상태에 반영하며 필요한 사이드이펙트들을 잘 처리하는 작업이 필요한데 리액트는 이에 대한 훌륭한 방법을 제공하지는 않는다.(이는 리액트의 관심사가 아니다.)</p>
<p>우리는 앞으로 리액트가 도움을 주지 않는 2가지, 즉 애플리케이션의 상태관리와 사이드이펙트관리 이렇게 2가지 문제를 리액트 생태계에서 어떻게 해결해 왔는 지를 살펴볼 것이다. 😐</p>
<p><br /></p>
<p>상태관리의 문제를 먼저 살펴 보자.</p>
<p>아래 간단한 카운터 예제에서 Count1, Count2 컴포넌트는 애플리케이션의 상태를 공유하기 위해 부모컴포넌트로부터 상태를 prop으로 전달받고 있다.</p>
<p>App.js</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">Counter1</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./Counter1</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">Counter2</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./Counter2</span><span class="dl">'</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">App</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setCount</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">setCount</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">count</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">setCount</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span>
<span class="na">count</span><span class="p">:</span> <span class="nx">value</span><span class="p">,</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className=</span><span class="s1">'App'</span><span class="p">></span>
<span class="p"><</span><span class="nc">Counter1</span> <span class="na">count=</span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="si">}</span> <span class="na">setCount=</span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setCount</span><span class="si">}</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Counter2</span> <span class="na">count=</span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="si">}</span> <span class="na">setCount=</span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">setCount</span><span class="si">}</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Counter1.js</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Count1</span><span class="p">({</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">})</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className=</span><span class="s1">'App'</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="si">{</span><span class="nx">count</span><span class="si">}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="p">()</span> <span class="o">=></span> <span class="nx">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="si">}</span><span class="p">></span>+1<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="p">()</span> <span class="o">=></span> <span class="nx">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span><span class="si">}</span><span class="p">></span>-1<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Counter2.js</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Count2</span><span class="p">({</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">})</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className=</span><span class="s1">'App'</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="si">{</span><span class="nx">count</span><span class="si">}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="p">()</span> <span class="o">=></span> <span class="nx">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span><span class="si">}</span><span class="p">></span>+2<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="p">()</span> <span class="o">=></span> <span class="nx">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span><span class="si">}</span><span class="p">></span>-2<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<iframe src="https://codesandbox.io/embed/redux-saga-1-f140i?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:300px; border:0; border-radius: 4px; overflow:hidden;" title="redux-saga-1" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p><br /></p>
<p>지금과 같은 간단한 구조에서는 크게 문제가 될 것이 없어 보이지만 컴포넌트의 depth 가 깊어지면 계속해서 상태를 자식컴포넌트의 prop으로 전달해 줘야 하는 불편함이 있다. (Context API 를 사용하면 prop으로 전달하지 않고도 처리가 가능하지만 Context API 사용할 때 따르는 또 다른 불편함에 대한 설명은 생략한다)</p>
<p><br /></p>
<h3 id="redux">Redux</h3>
<p>애플리케이션의 전역 상태를 효과적으로 공유하고 관리하기 위해 redux 가 등장한다. 리덕스가 이야기 하는 리듀서와 액션의 개념을 익혀서 우리는 아름답게(?) 상태를 변화시키고 그에 따른 상태변화를 구독해서 적절한 화면을 렌더링할 수 있게 되었다. 리덕스는 상태를 변화시키는 방법과 상태의 변이를 구독하는 멋진 방법을 우리에게 제공한다.</p>
<p>아래 예제는 위 예제의 App 컴포넌트에 포함되어 있던 전역 상태를 Redux 를 이용해 store.js 로 분리한다.</p>
<p>store.js</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">createStore</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">redux</span><span class="dl">'</span>
<span class="kd">function</span> <span class="nx">reducer</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span> <span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="dl">'</span><span class="s1">ADD</span><span class="dl">'</span><span class="p">:</span>
<span class="k">return</span> <span class="nx">state</span> <span class="o">+</span> <span class="nx">action</span><span class="p">.</span><span class="nx">value</span>
<span class="k">default</span><span class="p">:</span>
<span class="k">return</span> <span class="nx">state</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">addCount</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ADD</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">value</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">createStore</span><span class="p">(</span><span class="nx">reducer</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</code></pre></div></div>
<iframe src="https://codesandbox.io/embed/redux-saga-2-f2e8u?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:300px; border:0; border-radius: 4px; overflow:hidden;" title="redux-saga-2" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p><br /></p>
<p>Redux 덕분에 리액트 컴포넌트와 별개로 애플리케이션의 상태를 관리할 수 있게 되었다.</p>
<p>리덕스는 리듀서와 액션을 통해 상태를 어떻게 변이시킬 것인지에 대해서만 관심이 있다. 최종적으로 상태를 변이시키기 전에 외부 입력(사용자 이벤트)을 어떻게 처리해야할 지 그 중간 과정에 대해서는 관여하지 않는다. 사용자의 입력을 받아서 적절한 처리를 하고 최종적으로 상태변화까지 처리를 하는 것은 대부분 비즈니스 로직의 영역이며 대부분 네트워크 통신등의 여러가지 비동기처리를 포함할 수 있다.</p>
<p>버튼 클릭시 api를 통해 네트워크로부터 랜덤 값을 가지고 온 후 그 값을 더한다고 가정해보자. 어떻게 해야 할까.</p>
<p>고민할 것 없이 자연스럽게 onClick 이벤트 핸들러에서 비동기 로직을 처리하면 된다.</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">store</span><span class="p">,</span> <span class="nx">addCount</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./store</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">fetchNumber</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./api</span><span class="dl">'</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">Counter1</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">loading</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">count</span><span class="p">:</span> <span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">(),</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">componentDidMount</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">unsubscribe</span> <span class="o">=</span> <span class="nx">store</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(()</span> <span class="o">=></span>
<span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span>
<span class="na">count</span><span class="p">:</span> <span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">(),</span>
<span class="p">})</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="nx">componentWillUnmount</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">unsubscribe</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">async</span> <span class="nx">increment</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="na">loading</span><span class="p">:</span> <span class="kc">true</span><span class="p">})</span>
<span class="kd">const</span> <span class="nx">random</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetchNumber</span><span class="p">()</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="na">loading</span><span class="p">:</span> <span class="kc">false</span><span class="p">})</span>
<span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">addCount</span><span class="p">(</span><span class="nx">random</span><span class="p">))</span>
<span class="p">}</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className=</span><span class="s1">'App'</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="si">}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="p">()</span> <span class="o">=></span> <span class="k">this</span><span class="p">.</span><span class="nx">increment</span><span class="p">()</span><span class="si">}</span><span class="p">></span>add random number<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">h3</span><span class="p">></span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">loading</span> <span class="o">&&</span> <span class="dl">'</span><span class="s1">loading..</span><span class="dl">'</span><span class="si">}</span><span class="p"></</span><span class="nt">h3</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<iframe src="https://codesandbox.io/embed/redux-saga-3-6505p?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:300px; border:0; border-radius: 4px; overflow:hidden;" title="redux-saga-3" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p><br /></p>
<p>비동기 로직이 컴포넌트의 클릭이벤트 핸들러에 포함되었다. 지금과 같이 간단한 예제에서는 역시 별 문제가 될 것 같지 않다. 하지만 위와 같은 비동기 로직들(비즈니스로직)은 충분히 복잡해지고 길어 질 수 있다. 그러면 리액트 컴포넌트는 상태에 따른 화면을 정의하는 본래의 의도를 벗어나서 비즈니스 로직이 잔뜩 포함된 모습의 코드가 될 것이다.</p>
<p>원래 리액트가 다루고자 했던 문제는 애플리케이션의 상태를 어떻게 렌더링할 것인가에 대한 영역이었다. <strong>리액트 컴포넌트는 비즈니스 로직의 컨테이너가 아니다.</strong> 😥</p>
<p>그럼 위와 같은 비즈니스 로직들을 어떻게 다루어야 할까. 그래서 등장하는 것이 리덕스 미들웨어 redux-thunk 이다.</p>
<p><br /></p>
<h3 id="redux-thunk">redux-thunk</h3>
<p>redux-thunk 를 이용해 리액트 컴포넌트에 포함된 비동기 로직들을 아래와 같이 분리할 수 있다.</p>
<p>Counter1.js</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">store</span><span class="p">,</span> <span class="nx">addCountAsync</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./store</span><span class="dl">'</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">Counter1</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setLoading</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">setLoading</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">loading</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">count</span><span class="p">:</span> <span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">(),</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">componentDidMount</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">unsubscribe</span> <span class="o">=</span> <span class="nx">store</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(()</span> <span class="o">=></span>
<span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span>
<span class="na">count</span><span class="p">:</span> <span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">(),</span>
<span class="p">})</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="nx">componentWillUnmount</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">unsubscribe</span><span class="p">()</span>
<span class="p">}</span>
<span class="nx">setLoading</span><span class="p">(</span><span class="nx">loading</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">loading</span><span class="p">})</span>
<span class="p">}</span>
<span class="nx">increment</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">addCountAsync</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">setLoading</span><span class="p">))</span>
<span class="p">}</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className=</span><span class="s1">'App'</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="si">}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="p">()</span> <span class="o">=></span> <span class="k">this</span><span class="p">.</span><span class="nx">increment</span><span class="p">()</span><span class="si">}</span><span class="p">></span>add random number<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">h3</span><span class="p">></span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">loading</span> <span class="o">&&</span> <span class="dl">'</span><span class="s1">loading..</span><span class="dl">'</span><span class="si">}</span><span class="p"></</span><span class="nt">h3</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>store.js</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">createStore</span><span class="p">,</span> <span class="nx">applyMiddleware</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">redux</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">thunk</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">redux-thunk</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">fetchNumber</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./api</span><span class="dl">'</span>
<span class="kd">function</span> <span class="nx">reducer</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span> <span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="dl">'</span><span class="s1">ADD</span><span class="dl">'</span><span class="p">:</span>
<span class="k">return</span> <span class="nx">state</span> <span class="o">+</span> <span class="nx">action</span><span class="p">.</span><span class="nx">value</span>
<span class="k">default</span><span class="p">:</span>
<span class="k">return</span> <span class="nx">state</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">addCount</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ADD</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">value</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">addCountAsync</span><span class="p">(</span><span class="nx">setLoading</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">async</span> <span class="nx">dispatch</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">setLoading</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">random</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetchNumber</span><span class="p">()</span>
<span class="nx">setLoading</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
<span class="nx">dispatch</span><span class="p">(</span><span class="nx">addCount</span><span class="p">(</span><span class="nx">random</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">createStore</span><span class="p">(</span><span class="nx">reducer</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">applyMiddleware</span><span class="p">(</span><span class="nx">thunk</span><span class="p">))</span>
</code></pre></div></div>
<p><a href="https://codesandbox.io/s/redux-saga-4-h69ub?fontsize=14&hidenavigation=1&theme=dark"><img src="https://codesandbox.io/static/img/play-codesandbox.svg" alt="Edit redux-saga-4" /></a></p>
<p>onClick 이벤트 핸들러에 포함되었던 비동기 로직이 addCountAsync 액션으로 넘어왔다.</p>
<p>thunk 는 dispatch 를 품은 액션이다. redux-thunk 의 도움으로 우리는 액션을 원하는 시점에 리듀서에게 던질 수 있게 되었다. 이제 비동기처리는 모두 이 thunk 가 처리를 한다. 리액트 컴포넌트를 순수하게 만들 수 있게 되었다(비동기 로직들이 리액트 컴포넌트에 포함되지 않게 되었다)</p>
<p>그런데 기쁨도 잠시.. 😤</p>
<p>thunk 내부에서 여러개의 액션을 디스패치해야할 필요가 생겼다고 가정해 보자.</p>
<p>예로 들자면 랜덤숫자 2개를 차례대로 생성해서 첫번째 랜덤숫자는 더한 값으로 상태에 반영하고 그 다음은 곱한 값으로 상태를 반영해야 하는 요건이 새로 발생했다고 해보자(조금 억지스러운 상황이지만 양해를 바란다)</p>
<p>그럼 아래와 같이 코딩을 하게 될 것이다.</p>
<p>store.js</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">createStore</span><span class="p">,</span> <span class="nx">applyMiddleware</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">redux</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">thunk</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">redux-thunk</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">fetchNumber</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./api</span><span class="dl">'</span>
<span class="kd">function</span> <span class="nx">reducer</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span> <span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="dl">'</span><span class="s1">ADD</span><span class="dl">'</span><span class="p">:</span>
<span class="k">return</span> <span class="nx">state</span> <span class="o">+</span> <span class="nx">action</span><span class="p">.</span><span class="nx">value</span>
<span class="k">case</span> <span class="dl">'</span><span class="s1">MUL</span><span class="dl">'</span><span class="p">:</span>
<span class="k">return</span> <span class="nx">state</span> <span class="o">*</span> <span class="nx">action</span><span class="p">.</span><span class="nx">value</span>
<span class="k">default</span><span class="p">:</span>
<span class="k">return</span> <span class="nx">state</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">addCount</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ADD</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">value</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">mulCount</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">MUL</span><span class="dl">'</span><span class="p">,</span>
<span class="nx">value</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">addCountAsync</span><span class="p">(</span><span class="nx">setLoading</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">async</span> <span class="nx">dispatch</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">setLoading</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">random1</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetchNumber</span><span class="p">()</span>
<span class="nx">dispatch</span><span class="p">(</span><span class="nx">addCount</span><span class="p">(</span><span class="nx">random1</span><span class="p">))</span>
<span class="kd">const</span> <span class="nx">random2</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetchNumber</span><span class="p">()</span>
<span class="nx">dispatch</span><span class="p">(</span><span class="nx">mulCount</span><span class="p">(</span><span class="nx">random2</span><span class="p">))</span>
<span class="nx">setLoading</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">createStore</span><span class="p">(</span><span class="nx">reducer</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">applyMiddleware</span><span class="p">(</span><span class="nx">thunk</span><span class="p">))</span>
</code></pre></div></div>
<iframe src="https://codesandbox.io/embed/redux-saga-5-30bgp?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:300px; border:0; border-radius: 4px; overflow:hidden;" title="redux-saga-5" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p><br /></p>
<p>그런데 뭔가 이상하다. 원래 액션이란 상태를 변경하는 단위로서 하나의 액션은 하나의 상태를 변화시키는 것이어야 하는데 이제는 하나의 액션이 여러가지 액션을 포함한 모양이 되었다. 뭔가 마음이 불편해진다. 이렇게 되면 우리는 앞으로 어떤 액션을 만날 때 이 액션이 하나의 액션인 지 아니면 그 안에 다른 액션들을 품고있는 액션인지 알 수 없게 되었다. 또한 이 액션이 리듀서에게 던져지는 시점이 언제인지 그리고 정말 리듀서까지 잘 도달을 할 수 있을 지 등을 미리 예측하기 어려워진다. 액션 이름만 가지고서는 상태변화를 미리 예측하기가 어렵게 되었다는 이야기다. (이것이 redux-thunk 의 한계)</p>
<p>리액트 컴포넌트처럼 액션도 순수하게 유지할 수 있으면 좋겠다는 바램이 생긴다.</p>
<p>리액트 컴포넌트와 액션을 모두 순수하게 유지하면서 비즈니스로직(비동기처리)만 별도로 관리할 수는 없을까.</p>
<p><br /></p>
<h3 id="redux-saga">redux-saga</h3>
<p>이 지점에서 redux-saga 가 혜성처럼? 등장한다. redux-saga 는 제너레이터를 이용해 액션의 순수성이 보장되도록 해준다. 제너레이터는 싱글쓰레드 기반 자바스크립트에서 별도의 쓰레드를 fork 하는 마법을 부린다(실제로 별도 쓰레드가 생성되는 것은 아니다. 제너레이터도 싱글쓰레드로 수행이 된다. 다만 별도 쓰레드가 생성된다고 상상해 보는 것이 saga 의 동작을 이해하는데에 도움이 된다).</p>
<p>saga 는 특별히 비동기 처리가 필요한 액션이 발생할 때를 기다리다가 해당 액션이 dispatch 되면 새로운 쓰레드를 fork 하고 새로운 쓰레드에서 필요한 비즈니스 로직들을 순서대로 처리해 나간다.</p>
<p>필요한 비동기 처리들은 이렇게 모두 saga 에서 작성되고 수행된다. 드디어 리액트 컴포넌트와 리덕스의 액션을 모두 순수하게 유지하면서 비즈니스로직만 따로 처리가 가능하게 된 것이다. 아래 예시를 통해 사가의 위용을 감상할 수 있다.</p>
<p>Counter1.js</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">store</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./store</span><span class="dl">'</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">Counter1</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span> <span class="p">{</span>
<span class="kd">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setLoading</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">setLoading</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">loading</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">count</span><span class="p">:</span> <span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">(),</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">componentDidMount</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">unsubscribe</span> <span class="o">=</span> <span class="nx">store</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(()</span> <span class="o">=></span>
<span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span>
<span class="na">count</span><span class="p">:</span> <span class="nx">store</span><span class="p">.</span><span class="nx">getState</span><span class="p">(),</span>
<span class="p">})</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="nx">componentWillUnmount</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">unsubscribe</span><span class="p">()</span>
<span class="p">}</span>
<span class="nx">setLoading</span><span class="p">(</span><span class="nx">loading</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">loading</span><span class="p">})</span>
<span class="p">}</span>
<span class="nx">increment</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// COMPUTE 액션을 dispatch 한다</span>
<span class="c1">// COMPUTE 액션이 dispatch 되면 computeCount 제너레이터의 yield 구문들이 순차적으로 실행된다</span>
<span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">({</span><span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">COMPUTE</span><span class="dl">'</span><span class="p">,</span> <span class="na">setLoading</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">setLoading</span><span class="p">})</span>
<span class="p">}</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className=</span><span class="s1">'App'</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="si">}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="p">()</span> <span class="o">=></span> <span class="k">this</span><span class="p">.</span><span class="nx">increment</span><span class="p">()</span><span class="si">}</span><span class="p">></span>add random number<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">h3</span><span class="p">></span><span class="si">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">loading</span> <span class="o">&&</span> <span class="dl">'</span><span class="s1">loading..</span><span class="dl">'</span><span class="si">}</span><span class="p"></</span><span class="nt">h3</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>saga.js</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">fetchNumber</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./api</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">call</span><span class="p">,</span> <span class="nx">put</span><span class="p">,</span> <span class="nx">takeEvery</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">redux-saga/effects</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">addCount</span><span class="p">,</span> <span class="nx">mulCount</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./store</span><span class="dl">'</span>
<span class="kd">function</span><span class="o">*</span> <span class="nx">computeCount</span><span class="p">(</span><span class="nx">action</span><span class="p">)</span> <span class="p">{</span>
<span class="k">yield</span> <span class="nx">call</span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">setLoading</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span> <span class="c1">// true 를 인자로 action.setLoading 호출</span>
<span class="kd">const</span> <span class="nx">random1</span> <span class="o">=</span> <span class="k">yield</span> <span class="nx">call</span><span class="p">(</span><span class="nx">fetchNumber</span><span class="p">)</span> <span class="c1">// fetchNumber 호출</span>
<span class="k">yield</span> <span class="nx">put</span><span class="p">(</span><span class="nx">addCount</span><span class="p">(</span><span class="nx">random1</span><span class="p">))</span> <span class="c1">// addCount(random1) 액션을 dispatch</span>
<span class="kd">const</span> <span class="nx">random2</span> <span class="o">=</span> <span class="k">yield</span> <span class="nx">call</span><span class="p">(</span><span class="nx">fetchNumber</span><span class="p">)</span> <span class="c1">// fetchNumber 호출</span>
<span class="k">yield</span> <span class="nx">put</span><span class="p">(</span><span class="nx">mulCount</span><span class="p">(</span><span class="nx">random2</span><span class="p">))</span> <span class="c1">// mulCount(random2) 액션을 dispatch</span>
<span class="k">yield</span> <span class="nx">call</span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">setLoading</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span> <span class="c1">// false 를 인자로 action.setLoading 호출</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span><span class="o">*</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// COMPUTE 타임 액션이 dispatch 되면 computeCount 제너레이터를 실행한다</span>
<span class="k">yield</span> <span class="nx">takeEvery</span><span class="p">(</span><span class="dl">'</span><span class="s1">COMPUTE</span><span class="dl">'</span><span class="p">,</span> <span class="nx">computeCount</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<iframe src="https://codesandbox.io/embed/redux-saga-6-e3e17?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:300px; border:0; border-radius: 4px; overflow:hidden;" title="redux-saga-6" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p><br /></p>
<p>무슨 일이 일어났는가?</p>
<p>모든 액션은 순수한 자바스크립트 객체가 되었다. 😆 리액트 컴포넌트에서는 특정 이벤트가 발생할 때 단순히 액션을 dispatch 할 뿐이다.</p>
<p>우리들의 비동기 로직들만 saga.js 로 분리되었다.</p>
<p>드디어 다 이루었다! 😀</p>
<p><br /></p>
<p>글의 서두에서 모든 컴퓨터 프로그램은 상태머신으로 추상화 된다고 했었다. 이제 웹애플리케이션에서 상태를 모니터 화면으로 출력하는 것은 온전히 리액트가 담당한다. 애플리케이션의 상태관리는 리덕스가 담당한다. 나머지 외부 입력(사용자 입력 및 기타 이벤트들)을 받아서 어떻게 처리할 지에 대한 모든 복잡한 과정들은(비동기 비즈니스 로직 처리) saga 에서 온전히 담당을 하게 된다.</p>
<p>redux-saga 를 통해서 react 와 redux 의 순수성을 유지하며 각자의 역할을 분명하게 분리할 수 있게 된 것이다. 👍</p>
[react] Context api 사용 방법
2020-04-21T00:10:00+00:00
2020-04-21T00:10:00+00:00
https://min9nim.github.io/2020/04/react-context
<p>Context api 를 이용하면 mobx, redux 없이도 애플리케이션 상태를 전역으로 관리할 수가 있다. 외부 도움없이 전역으로 상태관리를 할 수 있다는 것이 매력적이지만 mobx, redux 와 비교해서 사용방법이 뭔가 좀 직관적이지는 않은 것 같다.</p>
<ul>
<li>React.createContext 로 컨텍스트를 생성할 때 전달하는 상태의 기본값은 실질적으로 별로 쓸데가 없는 것 같다.
<ul>
<li>하지만 컴포넌트를 독립적으로 테스트할 때는 필요하다고 한다.</li>
</ul>
</li>
<li>Context 객체를 각 파일마다 공유하기 하기 위해 <code class="language-plaintext highlighter-rouge">import</code> 로 가져와야 한다</li>
<li>상태 변경 메소드를 함께 공유하기 위해서는 최상위 컴포넌트에서 메소드를 포함한 상태를 context 로 공유해야만 하는 제약이 있다.</li>
<li>컨텍스트의 상태가 변경될 때마다 해당 컨텍스트를 구독?하는 컴포넌트들은 re-render 된다
<ul>
<li>상태의 변경 체크는 얕은 비교를 수행하는데 단순히 <code class="language-plaintext highlighter-rouge">===</code> 연산자가 사용되는 것은 아니고 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is">Object.is</a> 와 동일한 알고리즘이 사용된다.</li>
</ul>
</li>
<li>함수형컴포넌트와 클래스 컴포넌트의 컨텍스트 구독 처리하는 코드가 살짝 다르니 유의한다.</li>
</ul>
<p>아래 예시는 Context api 를 이용하여 컴포넌트간 상태를 공유하는 모습을 시연한다.</p>
<p><br /></p>
<iframe src="https://codesandbox.io/embed/github/min9nim/react-context-sample/tree/master/?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="react-context-api-9" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<p>https://ko.reactjs.org/docs/context.html</p>
Context api 를 이용하면 mobx, redux 없이도 애플리케이션 상태를 전역으로 관리할 수가 있다. 외부 도움없이 전역으로 상태관리를 할 수 있다는 것이 매력적이지만 mobx, redux 와 비교해서 사용방법이 뭔가 좀 직관적이지는 않은 것 같다.
[CRA] create-react-app 프로젝트에 module-alias 적용하기
2020-04-21T00:10:00+00:00
2020-04-21T00:10:00+00:00
https://min9nim.github.io/2020/04/cra-module-alias
<p>프로젝트의 폴더 구조를 개선해 나가다 보면 폴더 구조의 depth 가 깊어짐에 따라 <code class="language-plaintext highlighter-rouge">import xxx from '../../../../utils'</code> 와 같이 보기 싫은 코드가 만들어 질 수 있다.</p>
<p>프로젝트/src 폴더 기준으로 간단하게 <code class="language-plaintext highlighter-rouge">import xxx from '@/utils'</code> 와 같이 접근하여 사용할 수 있으면 참 좋겠다는 바램이 생긴다. (그리고 <a href="https://www.npmjs.com/package/module-alias">module-alias</a> 모듈이 바로 우리의 바램을 만족시킨다)</p>
<p>특별히 <a href="https://create-react-app.dev/">CRA</a>로 생성한 프로젝트에 module-alias 를 이용해 path 별칭을 세팅하는 방법을 공유한다.</p>
<p><br /></p>
<h3 id="cra-앱-추출">CRA 앱 추출</h3>
<p>(뭔가 세부적인 커스터마이징을 하려면 먼저 이렇게 해야함)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn eject
</code></pre></div></div>
<p><br /></p>
<h3 id="module-alias-설치">module-alias 설치</h3>
<p>(웹팩 빌드타임에만 사용될 것이므로 -D 로 설치)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn add -D module-alias
</code></pre></div></div>
<p><br /></p>
<h3 id="packagejson-루트-위치에-별칭-정의">package.json 루트 위치에 별칭 정의</h3>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">...</span><span class="w">
</span><span class="nl">"_moduleAliases"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"@"</span><span class="p">:</span><span class="w"> </span><span class="s2">"src"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="err">...</span><span class="w">
</span></code></pre></div></div>
<p><br /></p>
<h3 id="webpackconfigjs-에-매핑-정보를-등록">webpack.config.js 에 매핑 정보를 등록</h3>
<p>웹팩에서 package.json 에 정의된 매핑 정보를 사용할 수 있도록 또 등록해 줘야 한다</p>
<p>config/webpack.config.js 에 별칭 매핑 정보(<code class="language-plaintext highlighter-rouge">appPackageJson._moduleAliases</code>)를 등록</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>
<span class="nx">alias</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">// Support React Native Web</span>
<span class="c1">// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/</span>
<span class="dl">'</span><span class="s1">react-native</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">react-native-web</span><span class="dl">'</span><span class="p">,</span>
<span class="c1">// Allows for better profiling with ReactDevTools</span>
<span class="p">...(</span><span class="nx">isEnvProductionProfile</span> <span class="o">&&</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">react-dom$</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">react-dom/profiling</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">scheduler/tracing</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">scheduler/tracing-profiling</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}),</span>
<span class="p">...(</span><span class="nx">modules</span><span class="p">.</span><span class="nx">webpackAliases</span> <span class="o">||</span> <span class="p">{}),</span>
<span class="p">...(</span><span class="nx">appPackageJson</span><span class="p">.</span><span class="nx">_moduleAliases</span> <span class="o">||</span> <span class="p">{}),</span>
<span class="p">},</span>
<span class="p">...</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">appPackageJson</code> 변수는 위쪽에 보면 <code class="language-plaintext highlighter-rouge">const appPackageJson = require(paths.appPackageJson)</code> 와 같이 package.json 파일 내용이 담겨 있음을 확인할 수 있다</p>
<p><br /></p>
<h3 id="scriptsstartjs-buildjs-에-module-alias-모듈-등록">/scripts/start.js, build.js 에 module-alias 모듈 등록</h3>
<p>코드 상단에 적절한 곳에 아래 코드를 삽입한다</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// set module-alias</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-alias/register</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div></div>
<p><br /></p>
<p>끝~</p>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<p>https://www.npmjs.com/package/module-alias</p>
프로젝트의 폴더 구조를 개선해 나가다 보면 폴더 구조의 depth 가 깊어짐에 따라 import xxx from '../../../../utils' 와 같이 보기 싫은 코드가 만들어 질 수 있다.
[mobx-react] 리액트 컴포넌트에 상태 주입
2020-04-19T00:10:00+00:00
2020-04-19T00:10:00+00:00
https://min9nim.github.io/2020/04/mobx-inject-store
<p>mobx-react 를 이용해 리액트 컴포넌트에 상태를 주입하는 방법</p>
<p><br /></p>
<p>1. 함수형 컴포넌트</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">observer</span><span class="p">,</span> <span class="nx">inject</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">mobx-react</span><span class="dl">'</span>
<span class="kd">function</span> <span class="nx">Counter</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">counter</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">props</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="si">{</span><span class="nx">counter</span><span class="p">.</span><span class="nx">number</span><span class="si">}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="nx">counter</span><span class="p">.</span><span class="nx">increase</span><span class="si">}</span><span class="p">></span>+1<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="nx">counter</span><span class="p">.</span><span class="nx">decrease</span><span class="si">}</span><span class="p">></span>-1<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">inject</span><span class="p">(</span><span class="dl">'</span><span class="s1">counter</span><span class="dl">'</span><span class="p">)(</span><span class="nx">observer</span><span class="p">(</span><span class="nx">Counter</span><span class="p">))</span>
<span class="c1">// export default observer(inject("counter")(Counter)); // not works</span>
</code></pre></div></div>
<p><br /></p>
<p>2. 클래스 컴포넌트</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">observer</span><span class="p">,</span> <span class="nx">inject</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">mobx-react</span><span class="dl">'</span>
<span class="p">@</span><span class="nd">inject</span><span class="p">(</span><span class="dl">'</span><span class="s1">counter</span><span class="dl">'</span><span class="p">)</span>
<span class="p">@</span><span class="nd">observer</span>
<span class="kd">class</span> <span class="nx">Counter</span> <span class="kd">extends</span> <span class="nx">Component</span> <span class="p">{</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">counter</span> <span class="p">}</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">props</span>
<span class="k">return</span> <span class="p">(</span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="si">{</span><span class="nx">counter</span><span class="p">.</span><span class="nx">number</span><span class="si">}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="nx">counter</span><span class="p">.</span><span class="nx">increase</span><span class="si">}</span><span class="p">></span>+1<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick=</span><span class="si">{</span><span class="nx">counter</span><span class="p">.</span><span class="nx">decrease</span><span class="si">}</span><span class="p">></span>-1<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Counter</span>
</code></pre></div></div>
<p><br /></p>
<iframe src="https://codesandbox.io/embed/mobx-observer-function-p40zk?fontsize=14&hidenavigation=1&theme=dark" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="mobx-observer-function" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
mobx-react 를 이용해 리액트 컴포넌트에 상태를 주입하는 방법
[react] mobx 개발환경 설정
2020-04-19T00:10:00+00:00
2020-04-19T00:10:00+00:00
https://min9nim.github.io/2020/04/create-mobx-app
<p>create-react-app 으로 시작해서 mobx 를 사용하기 위한 기본적인 세팅방법 기록해 둠</p>
<p><br /></p>
<p>1. 앱생성</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx create-react-app toy-mobx
cd toy-mobx
</code></pre></div></div>
<p><br /></p>
<p>2. CRA 기본 앱 설정 추출</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn eject
</code></pre></div></div>
<p><br /></p>
<p>3. mobx, mobx-react 설치</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn add mobx mobx-react
</code></pre></div></div>
<p><br /></p>
<p>4. 데코레이터 사용 설정 preset 설치</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn add -D babel-preset-mobx
</code></pre></div></div>
<p>package.json 에 mobx 프리셋 추가</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"babel"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"presets"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="s2">"react-app"</span><span class="p">,</span><span class="w">
</span><span class="s2">"mobx"</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>
<p><br /></p>
<p>예시) https://github.com/min9nim/toy-mobx</p>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<ul>
<li>https://velog.io/@velopert/MobX-2-리액트-프로젝트에서-MobX-사용하기-oejltas52z</li>
<li>https://www.npmjs.com/package/babel-preset-mobx</li>
</ul>
create-react-app 으로 시작해서 mobx 를 사용하기 위한 기본적인 세팅방법 기록해 둠
[react] render props
2020-04-17T00:10:00+00:00
2020-04-17T00:10:00+00:00
https://min9nim.github.io/2020/04/render-props
<p>react 에서 코드 재사용을 위해 적극적으로 권장하는 방법 <strong>Render Props</strong>.</p>
<p>리액트는 prop 을 통해서 primitive 데이터 뿐 아니라 무엇이든(자바스크립트 객체) 동적으로 전달받을 수 있다. 그러므로 리액트 컴포넌트 내에 필요한 어떤 요소든 간에 prop 을 통해 동적으로 전달받을 수 있음을 기억하자.</p>
<p>아래 예제는 마우스 포인트가 특정영역을 지나갈 때 포인터의 위치를 추적하여 리턴하는 기능을 <code class="language-plaintext highlighter-rouge">Mouse</code> 컴포넌트로 추상화하고 포인터의 위치를 따라 다니는 고양이를 표시할 수 있는 <code class="language-plaintext highlighter-rouge">Cat</code> 컴포넌트를 <code class="language-plaintext highlighter-rouge">Mouse</code> 컴포넌트에게 prop 으로 전달한다.</p>
<iframe style="width: 100%; height: 500px" src="https://stackblitz.com/edit/react-render-props-9?embed=1&file=Mouse.js">
</iframe>
react 에서 코드 재사용을 위해 적극적으로 권장하는 방법 Render Props.
[react] React.memo
2020-04-17T00:10:00+00:00
2020-04-17T00:10:00+00:00
https://min9nim.github.io/2020/04/react-memo
<p>함수형 컴포넌트는 부모 컴포넌트가 렌더링될 때 무조건 함께 같이 렌더링이 됩니다.</p>
<p>하지만 함수컴포넌트의 경우 props 가 다르지 않다면 항상 같은 결과를 리턴하므로 부모 컴포넌트가 re-render 되더라도 함수 컴포넌트의 props 가 변경되지 않는 경우라면 굳이 함수컴포넌트까지 re-render 할 필요는 없을 것입니다</p>
<p><br /></p>
<p>이럴 때 성능 최적화를 위해 사용할 수 있는 것이 <code class="language-plaintext highlighter-rouge">React.memo</code> 입니다.</p>
<p><code class="language-plaintext highlighter-rouge">React.memo</code> 는 <code class="language-plaintext highlighter-rouge">React.PureComponent</code> 의 함수 컴포넌트 버젼이라고 생각해 볼 수 있습니다.</p>
<p><br /></p>
<p>아래 예제는 <code class="language-plaintext highlighter-rouge">React.memo</code> 의 효과를 시연합니다. <code class="language-plaintext highlighter-rouge">setInterval</code> 에 의해서 2초에 한번씩 부모 컴포넌트는 re-render 되지만 자식인 함수 컴포넌트는 <code class="language-plaintext highlighter-rouge">React.memo</code> 에 의하여 전달되는 props 값이 변경될 경우에만 re-render 됩니다. (console 탭을 열고 결과를 확인해 주세요)</p>
<iframe style="width: 100%; height: 500px" src="https://stackblitz.com/edit/react-memo-9?embed=1&file=Hello.js">
</iframe>
<p><br /></p>
<p>Note)</p>
<ul>
<li>React.memo 는 오직 성능 최적화 문제를 해결하기 위한 방법으로 사용됩니다.</li>
<li>props 에 대한 얕은 비교만 수행합니다</li>
<li>props 에 대한 깊은 비교가 필요하다면 React.memo의 2번째 인자를 활용할 수 있습니다.</li>
</ul>
<p>React.memo의 2번째 인자 사용방법</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">MyComponent</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="p">{</span>
<span class="cm">/* props를 사용하여 렌더링 */</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">areEqual</span><span class="p">(</span><span class="nx">prevProps</span><span class="p">,</span> <span class="nx">nextProps</span><span class="p">)</span> <span class="p">{</span>
<span class="cm">/*
nextProp가 prevProps와 동일한 값을 가지면 true를 반환하고, 그렇지 않다면 false를 반환
*/</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">React</span><span class="p">.</span><span class="nx">memo</span><span class="p">(</span><span class="nx">MyComponent</span><span class="p">,</span> <span class="nx">areEqual</span><span class="p">)</span>
</code></pre></div></div>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<p>https://reactjs.org/docs/react-api.html#reactmemo</p>
함수형 컴포넌트는 부모 컴포넌트가 렌더링될 때 무조건 함께 같이 렌더링이 됩니다.
[react] PureComponent
2020-04-17T00:10:00+00:00
2020-04-17T00:10:00+00:00
https://min9nim.github.io/2020/04/pure-component
<p>일반적인 리액트 클래스 컴포넌트는 React.Component 를 확장하여 정의한다.</p>
<p>React.Component 는 <code class="language-plaintext highlighter-rouge">setState</code> 가 호출될 때 항상 <code class="language-plaintext highlighter-rouge">render</code> 함수가 호출된다. 렌더링 여부를 제어하기 위해 <code class="language-plaintext highlighter-rouge">shouldComponentUpdate</code> 함수를 이용할 수 있지만 <code class="language-plaintext highlighter-rouge">shouldComponentUpdate</code> 함수를 정확하게 정의하지 못하는 경우 렌더링 되어야 하는 상황에 렌더링이 되지 않는 버그를 만들어 내는 실수를 하기 쉽다.</p>
<p>이럴 경우 PureComponent 를 이용할 수 있다. <code class="language-plaintext highlighter-rouge">React.PureComponent</code> 를 상속받아 정의된 리액트 컴포넌트는 <code class="language-plaintext highlighter-rouge">props</code> 나 <code class="language-plaintext highlighter-rouge">state</code> 가 변경될 경우에만(얕은 비교) 다시 렌더링을 수행한다.</p>
<p><code class="language-plaintext highlighter-rouge">props</code> 와 <code class="language-plaintext highlighter-rouge">state</code> 가 동일하다면(얕은 비교) 언제나 동일한 UI가 보장되는 컴포넌트인 경우에는 PureComponent 로 정의하여 성능향상을 기대할 수 있다.</p>
<p>아래 예제는 <code class="language-plaintext highlighter-rouge">React.Component</code> 와 <code class="language-plaintext highlighter-rouge">React.PureComponent</code> 의 차이를 시연한다.</p>
<iframe style="width: 100%; height: 500px" src="https://stackblitz.com/edit/react-pure-component-9?embed=1&file=index.js">
</iframe>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<p>https://ko.reactjs.org/docs/react-api.html#reactpurecomponent</p>
일반적인 리액트 클래스 컴포넌트는 React.Component 를 확장하여 정의한다.
[mobx] enforceActions
2020-04-17T00:10:00+00:00
2020-04-17T00:10:00+00:00
https://min9nim.github.io/2020/04/mobx-action
<p>mobx 는 우리를 <code class="language-plaintext highlighter-rouge">setState</code> 의 늪에서 꺼내 주었다. 충분한 자유함과 유연함은 좋지만 그 것이 좋은 것이 되려면 언제나 책임을 전제로 해야한다.</p>
<p>observalbe 로 관리되는 상태는 정말 특별히 관리되어질 필요가 있다. 상태의 변화는 곧 사이드이펙트를 수반하기 때문이다.</p>
<p>obsarvable 상태를 변화시키는 로직들을 특별하게 관리하기 위해 우리는 mobx 의 <code class="language-plaintext highlighter-rouge">action</code> 을 이용할 수 있다.</p>
<p><br /></p>
<p>기본적으로 mobx 에서 상태는 아래와 같이 간단하게 변화를 줄 수 있다.</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">observable</span><span class="p">,</span> <span class="nx">autorun</span><span class="p">,</span> <span class="nx">action</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">mobx</span><span class="dl">'</span>
<span class="kd">class</span> <span class="nx">Store</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">observable</span> <span class="nx">state</span> <span class="o">=</span> <span class="p">{</span><span class="na">num</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
<span class="p">@</span><span class="nd">action</span>
<span class="nx">incNum</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">num</span><span class="o">++</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Store</span><span class="p">()</span>
<span class="nx">autorun</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">store</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">num</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">store</span><span class="p">.</span><span class="nx">incNum</span><span class="p">()</span>
</code></pre></div></div>
<p>하지만 <code class="language-plaintext highlighter-rouge">state.num</code> 의 변화가 가져올 사이드이펙트에 대한 위화감?을 조장하고 싶다.</p>
<p><code class="language-plaintext highlighter-rouge">state.num</code> 이 사이드이펙트를 불러올 수 있다!라는 것을 명시적으로 표현하기 위해 아래와 같이 코드를 작성할 수 있다.</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">observable</span><span class="p">,</span> <span class="nx">autorun</span><span class="p">,</span> <span class="nx">action</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">mobx</span><span class="dl">'</span>
<span class="kd">class</span> <span class="nx">Store</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">observable</span> <span class="nx">state</span> <span class="o">=</span> <span class="p">{</span><span class="na">num</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
<span class="p">@</span><span class="nd">action</span>
<span class="nx">incNum</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">num</span><span class="o">++</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Store</span><span class="p">()</span>
<span class="nx">autorun</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">store</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">num</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">store</span><span class="p">.</span><span class="nx">incNum</span><span class="p">()</span>
</code></pre></div></div>
<p>이제 조금 마음에 편안함이 전해진다.</p>
<p><br /></p>
<p>하지만 코딩 컨벤션에만 의존하기엔 역시나 아직 개운치 않다. 이를 강제하기 위한 방법을 mobx 는 제공한다.</p>
<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">observable</span><span class="p">,</span> <span class="nx">autorun</span><span class="p">,</span> <span class="nx">action</span><span class="p">,</span> <span class="nx">configure</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">mobx</span><span class="dl">'</span>
<span class="nx">configure</span><span class="p">({</span><span class="na">enforceActions</span><span class="p">:</span> <span class="dl">'</span><span class="s1">observed</span><span class="dl">'</span><span class="p">})</span>
<span class="kd">class</span> <span class="nx">Store</span> <span class="p">{</span>
<span class="p">@</span><span class="nd">observable</span> <span class="nx">state</span> <span class="o">=</span> <span class="p">{</span><span class="na">num</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
<span class="p">@</span><span class="nd">action</span>
<span class="nx">incNum</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">num</span><span class="o">++</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">store</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Store</span><span class="p">()</span>
<span class="nx">autorun</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">store</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">num</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">store</span><span class="p">.</span><span class="nx">incNum</span><span class="p">()</span>
</code></pre></div></div>
<p>이제 action 을 사용하지 않고 직접 상태를 변경할 경우에는 아래와 같은 오류를 만나게 될 것이다.</p>
<blockquote>
<p>[mobx] Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an <code class="language-plaintext highlighter-rouge">action</code> if this change is intended</p>
</blockquote>
<p><br /></p>
<p>Note)</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">configuare</code> 를 이용한 설정은 mobx 를 사용하는 프로젝트 전체에 영향을 주기 때문에 전역에서 한번만 호출한다.</li>
<li><code class="language-plaintext highlighter-rouge">action</code> 은 해당 함수내에서 변경이 여러 번 발생할 경우 하나의 트랜잭션으로 처리하며 reaction 이 한번만 발생하도록 처리한다.</li>
<li>여러 개의 action 을 하나의 트랜잭션으로 처리하기 위해서는 mobx의 <code class="language-plaintext highlighter-rouge">transaction</code> 을 사용한다</li>
</ol>
<p><br /></p>
<h4 id="ref">Ref.</h4>
<p>https://mobx.js.org/refguide/api.html#enforceactions</p>
mobx 는 우리를 setState 의 늪에서 꺼내 주었다. 충분한 자유함과 유연함은 좋지만 그 것이 좋은 것이 되려면 언제나 책임을 전제로 해야한다.
bind, apply, call 중복 사용시 this 바인딩의 우선순위
2020-04-09T00:10:00+00:00
2020-04-09T00:10:00+00:00
https://min9nim.github.io/2020/04/bind-apply-call
<p>함수 스코프 안에서 <code class="language-plaintext highlighter-rouge">this</code> 는 해당 함수가 호출되는 형태에 따라서 동적으로 바인딩이 된다. <code class="language-plaintext highlighter-rouge">this</code> 바인딩을 개발자가 직접 제어하고자 할 때는 <code class="language-plaintext highlighter-rouge">bind</code>, <code class="language-plaintext highlighter-rouge">apply</code>, <code class="language-plaintext highlighter-rouge">call</code> 함수를 사용할 수 있다. 해당 함수들의 용법은 <a href="/2018/06/apply-call-bind/">이 글</a>을 참고하기 바란다.</p>
<p>본 포스트에서는 특별히 해당 함수들을 중복으로 사용할 경우 <code class="language-plaintext highlighter-rouge">this</code> 바인딩의 결과(우선순위)를 확인하고자 한다.</p>
<p><br />
아래 예제코드의 퀴즈를 맞춰보자.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">fn</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">num</span>
<span class="p">}</span>
<span class="nx">fn</span> <span class="o">=</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">bind</span><span class="p">({</span> <span class="na">num</span><span class="p">:</span> <span class="mi">1</span> <span class="p">})</span>
<span class="nx">fn</span><span class="p">()</span> <span class="c1">// 1. 리턴값은 ?</span>
<span class="nx">fn</span><span class="p">.</span><span class="nx">apply</span><span class="p">({</span> <span class="na">num</span><span class="p">:</span> <span class="mi">2</span> <span class="p">})</span> <span class="c1">// 2. 리턴값은 ?</span>
<span class="nx">fn</span><span class="p">.</span><span class="nx">call</span><span class="p">({</span> <span class="na">num</span><span class="p">:</span> <span class="mi">2</span> <span class="p">})</span> <span class="c1">// 3. 리턴값은 ?</span>
</code></pre></div></div>
<p><br /></p>
<p>1번의 리턴값은 <code class="language-plaintext highlighter-rouge">1</code> 임을 쉽게 맞출 수 있을 것이다. 하지만 2번, 3번 문제부터는 조금 헤깔릴 수가 있다.</p>
<p><br /></p>
<p><code class="language-plaintext highlighter-rouge">fn</code> 함수의 <code class="language-plaintext highlighter-rouge">this</code>에 <code class="language-plaintext highlighter-rouge">{num: 2}</code> 객체를 새롭게 바인딩하며 호출하므로 <code class="language-plaintext highlighter-rouge">2</code> 가 될 것 같지만 결과는 <code class="language-plaintext highlighter-rouge">1</code> 이 된다. <code class="language-plaintext highlighter-rouge">bind</code> 를 이용한 <code class="language-plaintext highlighter-rouge">this</code> 바인딩이 <code class="language-plaintext highlighter-rouge">apply</code> 나 <code class="language-plaintext highlighter-rouge">call</code> 보다 강함?을 알 수 있다.</p>
<p><br /></p>
<p>그렇다면 다음 퀴즈도 풀어보자</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">fn</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">num</span>
<span class="p">}</span>
<span class="nx">fn</span> <span class="o">=</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">bind</span><span class="p">({</span> <span class="na">num</span><span class="p">:</span> <span class="mi">1</span> <span class="p">})</span>
<span class="nx">fn</span> <span class="o">=</span> <span class="nx">fn</span><span class="p">.</span><span class="nx">bind</span><span class="p">({</span> <span class="na">num</span><span class="p">:</span> <span class="mi">2</span> <span class="p">})</span>
<span class="nx">fn</span><span class="p">()</span> <span class="c1">// 리턴값은 ?</span>
</code></pre></div></div>
<p>센놈 둘이 붙었다. 결과는 <code class="language-plaintext highlighter-rouge">1</code>이 될까 <code class="language-plaintext highlighter-rouge">2</code>가 될까. 모든 것이 동적으로 결정되는 JavaScript 의 특성상 <code class="language-plaintext highlighter-rouge">2</code>가 나올 법 같은데 정답은 1이다.</p>
<p><br /></p>
<blockquote>
<p>“<code class="language-plaintext highlighter-rouge">bind</code> 함수로 한 번 바인딩된 <code class="language-plaintext highlighter-rouge">this</code> 는 결코? 다시 풀어지지 않는다.” 라고 기억하면 되겠다</p>
</blockquote>
<p><br /></p>
<p>(그런데 혹시 아닐지도😅.. 반례가 있다면 제보 바랍니다)</p>
<p><br /></p>
<p>실제 개발시 이런 상황을 만나면 알쏭달쏭 헤깔릴 수 있으니 이렇게 한번 기억해 주고 넘어가면 나중에 고민할 시간을 줄일 수 있다.</p>
함수 스코프 안에서 this 는 해당 함수가 호출되는 형태에 따라서 동적으로 바인딩이 된다. this 바인딩을 개발자가 직접 제어하고자 할 때는 bind, apply, call 함수를 사용할 수 있다. 해당 함수들의 용법은 이 글을 참고하기 바란다.
image lazy load
2020-04-08T00:10:00+00:00
2020-04-08T00:10:00+00:00
https://min9nim.github.io/2020/04/image-lazy-load
<p>image lazy load 란 사용자가 보여지는 화면 영역 안에(viewport) 들어왔을 때에 이미지를 로드하는 방법이다.</p>
<p>과거에는 아래와 같이 문서상의 이미지의 높이값과 스크롤 위치등을 계산해서 이미지가 보여지는 영역 안으로 들어왔는 지 여부를 체크해야만 했었다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">debounce</span><span class="p">(</span><span class="nx">callback</span><span class="p">,</span> <span class="nx">ms</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">timeout</span>
<span class="k">return</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">timeout</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">timeout</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">timeout</span> <span class="o">=</span> <span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">callback</span><span class="p">(...</span><span class="nx">args</span><span class="p">),</span> <span class="nx">ms</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">imageLazyLoadPolyfill</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">lazyload</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">lazyloadImages</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">img.lazy</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">lazyloadImages</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">img</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">img</span><span class="p">.</span><span class="nx">offsetTop</span> <span class="o">>=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span> <span class="o">+</span> <span class="nb">window</span><span class="p">.</span><span class="nx">pageYOffset</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">img</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">src</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">img</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">img</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">src</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">img</span><span class="p">.</span><span class="nx">removeAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">src</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">img</span><span class="p">.</span><span class="nx">removeAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-src</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">img</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">lazy</span><span class="dl">'</span><span class="p">)</span>
<span class="p">})</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">lazyloadImages</span><span class="p">.</span><span class="nx">length</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">scroll</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lazyload</span><span class="p">)</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">resize</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lazyload</span><span class="p">)</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">orientationChange</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lazyload</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">lazyload</span> <span class="o">=</span> <span class="nx">debounce</span><span class="p">(</span><span class="nx">lazyload</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span> <span class="c1">// 성능문제도 고려해 줘야 함</span>
<span class="nx">lazyload</span><span class="p">()</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">scroll</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lazyload</span><span class="p">)</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">resize</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lazyload</span><span class="p">)</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">orientationChange</span><span class="dl">'</span><span class="p">,</span> <span class="nx">lazyload</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p><br /></p>
<p>최근에는 브라우져의 <a href="https://developer.mozilla.org/ko/docs/Web/API/IntersectionObserver/IntersectionObserver">IntersectionObserver</a> api 를 이용하여 보다 간단히 구현할 수 있다.</p>
<p>IntersectionObserver api 는 어떤 dom 요소가 화면에 노출되었는 지 여부를 보다 쉽게 그리고 정교하게 확인할 수 있게 해준다. IntersectionObserver 를 이용해 아래와 같이 특정 dom 이 화면에서 보여졌을 때 어떤 처리를 수행할 수 있는 함수 observeDom 을 만들 수 있다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">observeDom</span><span class="p">(</span><span class="nx">dom</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">observer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">IntersectionObserver</span><span class="p">((</span><span class="nx">entries</span><span class="p">,</span> <span class="nx">observer</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">entries</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">entry</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">entry</span><span class="p">.</span><span class="nx">isIntersecting</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="nx">callback</span><span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">target</span><span class="p">)</span>
<span class="nx">observer</span><span class="p">.</span><span class="nx">unobserve</span><span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">target</span><span class="p">)</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="nx">observer</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">dom</span><span class="p">)</span>
<span class="k">return</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">observer</span><span class="p">.</span><span class="nx">unobserve</span><span class="p">(</span><span class="nx">dom</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><br /></p>
<p>이제 위 함수를 이용해 아래와 같이 image lazy load 를 구현할 수 있다.</p>
<p><br /></p>
<p>최초 image 는 아래와 같이 렌더링한다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><img</span> <span class="na">class=</span><span class="s">"lazy"</span> <span class="na">src=</span><span class="s">"some-loading-image"</span> <span class="na">data-src=</span><span class="s">"~~"</span> <span class="nt">/></span>
</code></pre></div></div>
<p><br /></p>
<p>그리고 dom 이 모두 렌더링되었을 때 아래 함수를 호출한다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">imageLazyLoad</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">loadImage</span> <span class="o">=</span> <span class="nx">img</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">img</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">src</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">img</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">img</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">src</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">img</span><span class="p">.</span><span class="nx">removeAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">src</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">img</span><span class="p">.</span><span class="nx">removeAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-src</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">img</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">lazy</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">lazyloadImages</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">.lazy</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">lazyloadImages</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">item</span> <span class="o">=></span> <span class="nx">observeDom</span><span class="p">(</span><span class="nx">item</span><span class="p">,</span> <span class="nx">loadImage</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>
<p><br /></p>
<p>Note) 무한 스크롤 구현시 에도 <code class="language-plaintext highlighter-rouge">observeDom</code> 함수를 이용할 수 있다. 리스트의 마지막 요소가 눈에 보여질 때 이후 목록을 로딩하는 함수를 호출하면 되겠다.</p>
image lazy load 란 사용자가 보여지는 화면 영역 안에(viewport) 들어왔을 때에 이미지를 로드하는 방법이다.
play sound with JavaScript
2020-02-28T00:10:00+00:00
2020-02-28T00:10:00+00:00
https://min9nim.github.io/2020/02/js-sound
<p>안 되는 줄 알았는데.. 찾아보니 간단히 되는구나!</p>
<p>요즘 웹으로 정말 안되는 게 없는 듯~</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">play</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">audio</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Audio</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">https://interactive-examples.mdn.mozilla.net/media/examples/t-rex-roar.mp3</span><span class="dl">'</span><span class="p">,</span>
<span class="p">)</span>
<span class="nx">audio</span><span class="p">.</span><span class="nx">play</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<p>https://stackoverflow.com/questions/9419263/playing-audio-with-javascript</p>
안 되는 줄 알았는데.. 찾아보니 간단히 되는구나!
URL에 포함된 id/pw 를 fetch 함수로 보내기
2020-02-18T00:10:00+00:00
2020-02-18T00:10:00+00:00
https://min9nim.github.io/2020/02/http-auth
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://user01:password01@somesite.com
</code></pre></div></div>
<p><br /></p>
<p>과 같이 url에 포함된 id/pw 를 fetch 함수로 요청하는 방법</p>
<p><br /></p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">https://somesite.com</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">use01</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">password</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">password01</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">headers</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Headers</span><span class="p">()</span>
<span class="nx">headers</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="dl">'</span><span class="s1">Authorization</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Basic </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">btoa</span><span class="p">(</span><span class="nx">user</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">:</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">password</span><span class="p">))</span>
<span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span><span class="nx">headers</span><span class="p">}).</span><span class="nx">then</span><span class="p">(</span><span class="nx">res</span> <span class="o">=></span> <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
</code></pre></div></div>
https://user01:password01@somesite.com
npm 버젼의 캐럿(^) 의미
2020-01-29T00:10:00+00:00
2020-01-29T00:10:00+00:00
https://min9nim.github.io/2020/01/npm-version
<p><a href="https://blog.outsider.ne.kr/1041">아웃사이더님이 잘 정리해주신 글</a> 보고 배운 내용 요약</p>
<p><br /></p>
<h3 id="npm-버젼-의미">npm 버젼 의미</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{MAJOR}.{MINOR}.{PATCH}
</code></pre></div></div>
<ol>
<li>MAJOR: 하위호환성이 보장되지 않는 변경사항 발생시</li>
<li>MINOR: 하위호환성 보장 하면서 기능추가</li>
<li>PATCH: 하위호환성 보장 하면서 버그수정</li>
</ol>
<p><br /></p>
<h3 id="npm-버젼의-틸드-캐럿-의미">npm 버젼의 ~(틸드), ^(캐럿) 의미</h3>
<ul>
<li>틸드(<code class="language-plaintext highlighter-rouge">~</code>) 는 요즘 잘 안 사용</li>
<li>캐럿(<code class="language-plaintext highlighter-rouge">^</code>)은 Node.js 모듈이 위 규약을 따른다는 것을 신뢰한다는 가정하에서 동작
<ul>
<li>그래서 MINOR나 PATCH버전은 하위호환성이 보장되어야 하므로 업데이트를 한다</li>
</ul>
</li>
</ul>
<p><br /></p>
<h3 id="캐럿-의-동작">캐럿(^) 의 동작</h3>
<p>최신 마이너 버젼으로 설치</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>^1.0.2 : >=1.0.2 <2.0
^1.0 : >=1.0.0 <2.0
^1 : >=1.0.0 <2.0
</code></pre></div></div>
<p><br /></p>
<p>단, 1.0 미만 버젼의 경우는 자릿수까지 체크</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>^0.1.2 : >=0.1.2 <0.2.0
^0.1 : >=0.1.0 <0.2.0
^0 : >=0.0.0 <1.0.0
^0.0.1 : ==0.0.1
</code></pre></div></div>
<p><br /></p>
<h3 id="버젼-고정">버젼 고정</h3>
<p>그냥 간단하게 버젼을 고정하고 싶으면 특수기호 없이 그냥 버젼만 명시</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// package.json</span>
<span class="dl">"</span><span class="s2">dependencies</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">if-logger</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">0.4.2</span><span class="dl">"</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<p>https://blog.outsider.ne.kr/1041</p>
아웃사이더님이 잘 정리해주신 글 보고 배운 내용 요약
electron 시작하기
2020-01-17T00:10:00+00:00
2020-01-17T00:10:00+00:00
https://min9nim.github.io/2020/01/electron-note
<h3 id="엘렉트론-소개">엘렉트론 소개</h3>
<ul>
<li>js 로 데스크탑 애플리케이션을 만들자!</li>
<li>Nodejs + Chromium
<ul>
<li>일렉트론은 기본적으로 Nodejs 프로젝트</li>
</ul>
</li>
<li><a href="https://electronjs.org/docs/tutorial/about#about-electron">2014년 github 에서 Atom 개발을 하다가 탄생</a></li>
</ul>
<p><br /></p>
<h3 id="일렉트론-설치">일렉트론 설치</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install --save-dev electron
</code></pre></div></div>
<p><br /></p>
<h3 id="프로젝트-시작">프로젝트 시작</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm init
</code></pre></div></div>
<p><br /></p>
<h3 id="프로젝트-기본-구조">프로젝트 기본 구조</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>your-app/
├── package.json
├── main.js
└── index.html
</code></pre></div></div>
<p><br />
<code class="language-plaintext highlighter-rouge">package.json</code></p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"your-app"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.1.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"main.js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"electron ."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p><br /></p>
<p><code class="language-plaintext highlighter-rouge">main.js</code></p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span> <span class="nx">app</span><span class="p">,</span> <span class="nx">BrowserWindow</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">electron</span><span class="dl">"</span><span class="p">)</span>
<span class="c1">// window 객체는 전역 변수로 유지. 이렇게 하지 않으면,</span>
<span class="c1">// 자바스크립트 객체가 가비지 콜렉트될 때 자동으로 창이 닫힐 것입니다.</span>
<span class="kd">let</span> <span class="nx">win</span>
<span class="kd">function</span> <span class="nx">createWindow</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// 브라우저 창을 생성합니다.</span>
<span class="nx">win</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BrowserWindow</span><span class="p">({</span>
<span class="na">width</span><span class="p">:</span> <span class="mi">800</span><span class="p">,</span>
<span class="na">height</span><span class="p">:</span> <span class="mi">600</span><span class="p">,</span>
<span class="na">webPreferences</span><span class="p">:</span> <span class="p">{</span>
<span class="na">nodeIntegration</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">})</span>
<span class="c1">// 그리고 앱의 index.html를 로드합니다.</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">loadFile</span><span class="p">(</span><span class="dl">"</span><span class="s2">index.html</span><span class="dl">"</span><span class="p">)</span>
<span class="c1">// 개발자 도구를 엽니다.</span>
<span class="c1">// win.webContents.openDevTools()</span>
<span class="c1">// 창이 닫힐 때 발생합니다</span>
<span class="nx">win</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">closed</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// window 객체에 대한 참조해제. 여러 개의 창을 지원하는 앱이라면</span>
<span class="c1">// 창을 배열에 저장할 수 있습니다. 이곳은 관련 요소를 삭제하기에 좋은 장소입니다.</span>
<span class="nx">win</span> <span class="o">=</span> <span class="kc">null</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="c1">// 이 메서드는 Electron이 초기화를 마치고</span>
<span class="c1">// 브라우저 창을 생성할 준비가 되었을 때 호출될 것입니다.</span>
<span class="c1">// 어떤 API는 이 이벤트가 나타난 이후에만 사용할 수 있습니다.</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">ready</span><span class="dl">"</span><span class="p">,</span> <span class="nx">createWindow</span><span class="p">)</span>
<span class="c1">// 모든 창이 닫혔을 때 종료.</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">window-all-closed</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// macOS에서는 사용자가 명확하게 Cmd + Q를 누르기 전까지는</span>
<span class="c1">// 애플리케이션이나 메뉴 바가 활성화된 상태로 머물러 있는 것이 일반적입니다.</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">platform</span> <span class="o">!==</span> <span class="dl">"</span><span class="s2">darwin</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">quit</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">activate</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// macOS에서는 dock 아이콘이 클릭되고 다른 윈도우가 열려있지 않았다면</span>
<span class="c1">// 앱에서 새로운 창을 다시 여는 것이 일반적입니다.</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">win</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">createWindow</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="c1">// 이 파일 안에 당신 앱 특유의 메인 프로세스 코드를 추가할 수 있습니다. 별도의 파일에 추가할 수도 있으며 이 경우 require 구문이 필요합니다.</span>
</code></pre></div></div>
<p><br /></p>
<p><code class="language-plaintext highlighter-rouge">index.html</code></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span> <span class="nt">/></span>
<span class="nt"><title></span>Hello World!<span class="nt"></title></span>
<span class="c"><!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag --></span>
<span class="nt"><meta</span>
<span class="na">http-equiv=</span><span class="s">"Content-Security-Policy"</span>
<span class="na">content=</span><span class="s">"script-src 'self' 'unsafe-inline';"</span>
<span class="nt">/></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><h1></span>Hello World!<span class="nt"></h1></span>
We are using node
<span class="nt"><script></span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">versions</span><span class="p">.</span><span class="nx">node</span><span class="p">)</span>
<span class="nt"></script></span>
, Chrome
<span class="nt"><script></span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">versions</span><span class="p">.</span><span class="nx">chrome</span><span class="p">)</span>
<span class="nt"></script></span>
, and Electron
<span class="nt"><script></span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">versions</span><span class="p">.</span><span class="nx">electron</span><span class="p">)</span>
<span class="nt"></script></span>
.
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p><br /></p>
<h3 id="애플리케이션-시작">애플리케이션 시작</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm start
</code></pre></div></div>
<p><br /></p>
<p><img src="/images/electron-hello-world.png" alt="애플리케이션 UI" /></p>
<p><br /></p>
<h3 id="ref">Ref.</h3>
<p>https://electronjs.org/docs/tutorial/first-app</p>
엘렉트론 소개
0부터 99까지 배열을 만드는 가장 간단한 방법
2020-01-16T00:10:00+00:00
2020-01-16T00:10:00+00:00
https://min9nim.github.io/2020/01/n-children-array
<p>0부터 99까지 배열을 만드는 방법(내가 아는 가장 간단한 방법)</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nb">Array</span><span class="p">(</span><span class="mi">100</span><span class="p">)).</span><span class="nx">map</span><span class="p">((</span><span class="nx">v</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="o">=></span> <span class="nx">i</span><span class="p">)</span>
<span class="cm">/*
[0, 1, 2, 3, 4, ... , 99]
*/</span>
</code></pre></div></div>
0부터 99까지 배열을 만드는 방법(내가 아는 가장 간단한 방법)