<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Archive</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 04 Nov 2021 14:04:38 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Archive</title>
            <url>https://images.velog.io/images/dongwu-kim/profile/fa7606c3-4896-4eee-9996-b3e4899324e8/KakaoTalk_Photo_2021-10-07-13-25-22.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Archive. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dongwu-kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[(TIL) web.dev : Fast load times - intro -]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-web.dev-Fast-load-times-intro-</link>
            <guid>https://velog.io/@dongwu-kim/TIL-web.dev-Fast-load-times-intro-</guid>
            <pubDate>Thu, 04 Nov 2021 14:04:38 GMT</pubDate>
            <description><![CDATA[<h1 id="fast-load-times---intro">Fast load times - Intro</h1>
<p><a href="https://web.dev/fast/">참고자료</a></p>
<h2 id="intro">Intro</h2>
<h3 id="페이지의-전환이-빨라야-한다">페이지의 전환이 빨라야 한다.</h3>
<h3 id="로딩이-빨라야-한다">로딩이 빨라야 한다.</h3>
<ul>
<li><p>Modern Web 로딩은 과거와 달리 단순히 웹이 켜지는 한 순간만을 의미하는게 아니다.</p>
<p>  큰 단계는 총 4가지로, </p>
</li>
</ul>
<h4 id="1-is-it-happening">1. Is it happening?</h4>
<blockquote>
<p>First Paint, First Contentful Paint = Navigation start, call responded 상태 metric</p>
</blockquote>
<h4 id="2-is-it-useful">2. Is it useful?</h4>
<blockquote>
<p>First Meaningful Paint = Enough content rendered metric</p>
</blockquote>
<h4 id="3-is-it-usable">3. Is it usable?</h4>
<blockquote>
<p>Time to Interactive = Can users interact with the page metric</p>
</blockquote>
<h4 id="4-is-it-delightful">4. Is it delightful?</h4>
<p>등의 상태로 구분할 수 있다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/9bc95467-150e-426b-8eaa-329be24551ba/image.png" alt="">
Reference : <a href="https://web.dev/lighthouse-performance/#metrics">LightHouse performance metrics</a></p>
<h3 id="속도는-어떻게-측정할-수-있는가">속도는 어떻게 측정할 수 있는가?</h3>
<br/>

<h4 id="data">Data</h4>
<br/>

<ul>
<li>Lab Data : 사전에 설정된 기기와 특정 네트워크 세팅을 통해 실험하듯 수집할 수 있다.<br/></li>
<li>Field Data : real-world loading을 통해 수집할 수 있다. =&gt; CX 와 밀접하다.<br/>

</li>
</ul>
<h4 id="tool-from-google">Tool (from Google)</h4>
<br/>

<ol>
<li>Lab Data =&gt; LightHouse 등<br/></li>
<li>Field Data =&gt; CrUX(Chrome User Experience Report) 등<br/>

</li>
</ol>
<h4 id="performance-budget--load-speed-metrics">Performance Budget : Load Speed Metrics</h4>
<br/>

<ul>
<li><p>Set Examples</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/d08eba83-9f4e-497e-be95-3a55c03d4a1b/image.png" alt=""></p>
<p>Reference : <a href="https://web.dev/how-to-stay-fast/">How to stay fast</a></p>
</li>
</ul>
<br/>

<h3 id="rail">RAIL</h3>
<br/>

<ul>
<li><p>Web App life-cycle four aspects</p>
</li>
<li><p><img src="https://images.velog.io/images/dongwu-kim/post/9796a50d-b8a8-4460-906e-81162e456364/image.png" alt=""></p>
<p>Reference : <a href="https://web.dev/rail/">RAIL</a></p>
</li>
<li><p>전반적으로 100ms 이내의 reaction 속도를 가질 경우, 유저는 상당히 만족할 수 있습니다.<br/>
100 ms 이후에는 특정 interaction이 깨질 수 있으나, 대부분의 Web은 100 ms ~ 1 sec 정도의 속도로 움직입니다.<br/>
1 sec가 넘어갈 경우, 유저의 집중이 분산될 수 있는 상태가 되며, 10 sec가 넘어가면 특정 task에 대해 낙담하거나, 불쾌해할 수 있습니다.</p>
<br/></li>
<li><p>그러나 유저가 스스로 환경에 대한 인지를 하고 있다면 약간 다른 문제가 됩니다.<br/>
예를 들어, 5G or 4G, 아주 빠른 인터넷을 사용하고 있고, 하이 퀄리티의 device를 사용한다면 나열된 시간대와 일치하는 user focus를 관찰할 수 있습니다.<br/>
반면 3G 환경에 모바일 기기를 사용하는 유저라면 약간 더 인내심이 생길 수 밖에 없겠죠. <br/>
이 때는 보통 5 sec 정도의 conversion에도 만족할 수 있을겁니다.</p>
<br/>

</li>
</ul>
<p><strong>그래서 위 RAIL 각 모델별로 목표하는 최선을 살펴보면</strong>
<br/></p>
<h4 id="response--under-50-ms">Response : under 50 ms</h4>
<br/>

<p>현실적으로 불가능하면, Response의 경우 User를 방해하지 않는 선에서 100ms까지는 괜찮을 수 있습니다.<br/>
Input handling이 예시입니다. 단순히 생각했을 때, 실제로 input handling은 50 ms 정도의 속도를 가집니다.<br/>
다만, 다음 입력을 queue에 저장함으로 유저를 방해하지 않는 선에서 100 ms 정도의 속도로 real-world에서 동작합니다.</p>
<h4 id="animation--under-10-ms">Animation : under 10 ms</h4>
<br/>

<p>단순합니다. 60 frames 정도 되는 Animation을 구현하면 16ms 정도인데, 그 이하로 떨어뜨릴 경우 더 좋다는 말이 됩니다.<br/>
위 Animation은 모든 Animation에 해당합니다. Visual 뿐 아니라, Scroll, Dragging, etc. 전부 말입니다.<br/>
그러나 목표와는 달리, 60 frames 정도의 퀄리티로 구현하는 것을 최우선으로 하면 됩니다.<br/></p>
<h4 id="idle--maximize-idle-time">Idle : maximize idle time</h4>
<br/>

<p>응답 유효시간을 최대로 하여 user input을 50 ms 이내로 처리하는 것이 목표입니다.<br/>
Conversion, first load 당시 전부를 Load 하는게 아니라, lazy를 걸어 순차적으로 50ms 이내에 load하게 하는 것 또한 maximaize idle time을 준수한 하나의 방법입니다.</p>
<h4 id="load--그래도-under-5-sec--target--3g-network--mid-range-mobile-device">Load : 그래도 under 5 sec ( target : 3G network &amp; mid range mobile device)</h4>
<br/>

<p>사실 첫인상과도 같습니다. 그러니 optimize code를 최대한 적용하든, image를 작게 하든, 어떤 방법을 사용해서든 first loads time을 줄이는게 좋습니다.<br/>
이는 Conversion 또한 예외는 아닙니다. <br/>
해당 target의 추가적인 load는 2 sec under speed를 준수하는 것이 좋습니다.<br/>
<br/></p>
<ul>
<li>추가 정보
<img src="https://images.velog.io/images/dongwu-kim/post/a2a70ad0-e369-4e98-8755-a7ecd03eff29/image.png" alt="">
Reference : <a href="https://web.dev/rail/">RAIL</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) Node.js : 이미지 합성 스크립트]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-Node.js-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%95%A9%EC%84%B1-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</link>
            <guid>https://velog.io/@dongwu-kim/TIL-Node.js-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%95%A9%EC%84%B1-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</guid>
            <pubDate>Wed, 03 Nov 2021 06:35:57 GMT</pubDate>
            <description><![CDATA[<h1 id="이미지-합성">이미지 합성</h1>
<p>Node에도 python의 openCV와 같이 이미지를 바이너리 코드로 변경해서 합성해주는 라이브러리가 있습니다.</p>
<p>정확하게는 배경이 되는 이미지에 특정 이미지를 쌓는 형태로, 덮어쓰는 것과 같이 동작하는 것 같습니다.</p>
<p>예시 )</p>
<p><strong>from</strong>
<img src="https://images.velog.io/images/dongwu-kim/post/7a00474d-f7cb-4d44-a6bc-11d7f221c18e/image.png" alt=""></p>
<p><strong>to</strong>
<img src="https://images.velog.io/images/dongwu-kim/post/9b5a8e39-59fa-4313-9d88-f28ac210ff97/image.png" alt=""></p>
<h2 id="mergeimages">mergeImages</h2>
<p>대표적인 Image merge library인데, <code>저는 merge-images</code> 라는 라이브러리를 활용해서 여러 이미지를 한 장에 합치는 스크립트를 작성했습니다.</p>
<h2 id="install">install</h2>
<ol>
<li><code>npm i merge-images</code></li>
<li><code>npm i canvas</code></li>
</ol>
<p>Node, npm(or yarn)이 있다면 두 라이브러리를 설치하면 됩니다.</p>
<p><a href="https://www.npmjs.com/package/merge-images">참고링크</a></p>
<h2 id="logic">logic</h2>
<p>로직은 생각보다 간단합니다.</p>
<ol>
<li>사진파일이 들어있는 경로의 파일명을 가져와 저장한다.</li>
<li>파일 이름을 라이브러리가 원하는 구조로 변경한다.</li>
<li>2에서 정제한 Array를 정제해 output 파일명을 결정합니다.</li>
<li>합성재료로 2, 3을 사용해 합성한다.</li>
</ol>
<p>의 flow로 진행됩니다.</p>
<p>그럼 코드를 볼까요?</p>
<pre><code class="language-javascript">const { writeFile } = require(&quot;fs&quot;);

const mergeImages = require(&quot;merge-images&quot;);
const { Canvas, Image } = require(&quot;canvas&quot;);

// 1. 파일명을 가져온 뒤, 배열에 저장한다.
const readFiles = () =&gt; {
  const fs = require(&quot;fs&quot;);

  // 5장의 사진을 합치기 위해 폴더(카테고리)로 나눈 뒤, 하나씩 다섯개의 폴더를 리스트화합니다.
  const IMG_FOLDER_LIST = [
    &quot;dir1&quot;, // 파일에서 이미지에 접근할 상대경로
    &quot;dir2&quot;,
    &quot;dir3&quot;,
    &quot;dir4&quot;,
    &quot;dir5&quot;
  ];

  // 각 5개 경로의 파일을 읽어옵니다. (2차원 Array)
  const fileReader = (dirArr) =&gt; {
    return dirArr.map((dir) =&gt; fs.readdirSync(dir).map((fileName) =&gt; dir + &quot;/&quot; + fileName));
  };

  // 불러온 파일명 Array를 반환
  return fileReader(IMG_FOLDER_LIST);
};


// 2. 라이브러리가 원하는 자료구조 형태로 변경합니다.
const fileNameGenerator = (prevFileNameArr) =&gt; {
  const fileNames = [];

  // 입력받은 2개의 Array에 대해 모든 경우의 수에 대해 합성합니다.
  // 최종적으로 5개 카테고리별로 각각 2장의 사진이라면 2^5 개의 element를 갖는 array가 나오게 됩니다.

  // input : [string[], string[]] (파일명 Array tuple)
  // =&gt; [[1,1], [2,2]]
  // output : [[1,2], [1,2], [1,2], [1,2]]

  // 만약 3개의 카테고리라면,
  // input: [[string[]], string[]]
  // =&gt; [[[1,2], [1,2]...], [3,3]]
  // output : [[1,2,3], [1,2,3], ...]
  for (let i = 0; i &lt; prevFileNameArr[0].length; i++) {
    for (let j = 0; j &lt; prevFileNameArr[1].length; j++) {
      if (typeof prevFileNameArr[0][i] === &quot;string&quot;) {
        fileNames.push([prevFileNameArr[0][i], prevFileNameArr[1][j]]);
      } else {
        fileNames.push([...prevFileNameArr[0][i], prevFileNameArr[1][j]]);
      }
    }
  }

  return fileNames;
};

// 3, 4.
// 3은 내부함수로 구현했고,
// 4는 2, 3을 가지고 하나의 아웃풋(합성된 사진)을 만들어냅니다.
const mergeImage = (fileNamesArr) =&gt; {
  // 초기에는 2개의 카테고리, 2장의 이미지를 먼저 합성하기 위해 구조를 변경합니다.
  // input : [[1,1], [2,2]] 
  // ouput : [[1,2], [1,2], [1,2], [1,2]]
  let generatedArr = fileNameGenerator(fileNamesArr.slice(0, 2));

  // 이후 과정은 사진을 한장씩 추가하는 형태의 반복문입니다.
  // 3장이라면, [[1,2,3], [1,2,3], ...] 으로요
  for (let i = 2; i &lt; fileNamesArr.length; i++) {
    let newArr = fileNameGenerator([generatedArr, fileNamesArr[i]]);
    generatedArr = newArr;
  }

  // 반복 이후 모든 파일명이 담긴 배열을 정제합니다.
  // 원하는 output naming을 위한 로직이니, 마음대로 custom할 수 있습니다.
  const outputNameArr = generatedArr.map((el) =&gt; {
    return el.map((el2) =&gt; {
      return el2.split(&quot;_&quot;)[4].split(&quot;.&quot;)[0];
    });
  });

  // 2의 과정을 마친 Array 내부의 원소들을 순회하며 라이브러리 메서드를 적용합니다.
  // Array elem (img files read, merge) =&gt; b64
  // b64 =&gt; png write
  // generatedArr를 기준으로 파일명을 분리했기 때문에 outputNameArr는 동일 조합에 대한 mapping이 이루어져 있습니다.
  generatedArr.forEach((imgs, idx) =&gt; {
    console.log(outputNameArr[idx]);
    mergeImages(imgs, {
      Canvas: Canvas,
      Image: Image,
    }).then((b64) =&gt; {
      const b64Data = b64.replace(/^data:image\/png;base64,/, &quot;&quot;);
      writeFile(`./data/_OUTPUT/merged/${outputNameArr[idx].join(&quot;_&quot;)}.png`, b64Data, &quot;base64&quot;, (err) =&gt; {
        console.log(err);
      });
    });
  });
};</code></pre>
<p>코드는 생각보다 길지 않습니다.</p>
<p>정상적으로 동작하는 것도 잘 확인했고, 결과물도 나쁘지 않았습니다.</p>
<h1 id="refactoring">Refactoring</h1>
<ol>
<li>성능 개선을 위해 초기 코드에서 정규표현식을 최대한 제거하고, 문자열을 <code>split</code>으로 처리하기로 했습니다.</li>
</ol>
<blockquote>
<p>split.join.replace =&gt; split[] 로 변경</p>
</blockquote>
<p>Js Array search 속도를 고려해 index 접근 방식으로 복잡도를 줄일 수 있을까 시도했으나, 근본적인 문제는 그게 아니었습니다.</p>
<ol start="2">
<li>라이브러리 메소드 자체가 느리다.</li>
</ol>
<p>라이브러리를 사용하는 입장에서 이런 얘기를 하면 안되는데, 속도가 조금 늦은 감이 있습니다.</p>
<p>물론, 2장의 이미지를 합성할 때는 상당히 빠릅니다.</p>
<p>문제는 제가 5장의 이미지를 합성하기에 기하급수적으로 복잡도가 올라갑니다.</p>
<p>한번에 다수의 이미지를 합성하는게 문제인 것으로 판단되는데, 추후 generate 방식과 마찬가지로 2장씩 합성하는 loop로 해결해보는건 어떨지 고민하고 있습니다.</p>
<p>이상입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) FEConf 2021 : 프론트엔드 딥러닝]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-FEConf-2021-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%94%A5%EB%9F%AC%EB%8B%9D</link>
            <guid>https://velog.io/@dongwu-kim/TIL-FEConf-2021-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EB%94%A5%EB%9F%AC%EB%8B%9D</guid>
            <pubDate>Mon, 01 Nov 2021 12:36:23 GMT</pubDate>
            <description><![CDATA[<h1 id="feconf-2021">FEConf 2021</h1>
<p>해당 문서는 <a href="https://www.youtube.com/watch?v=cYm4fGQC1eE">B6, 딥러닝으로 확장하는 프론트엔드 현주소</a> 영상을 보고 정리한 글입니다.</p>
<h1 id="도입부">도입부</h1>
<p>먼저, FE deep learning의 경우 SIFT, SURF 등 영상처리 algorithm에 대해 알 필요는 없습니다.</p>
<blockquote>
<p>Keypoint Detector -&gt; Feature Detector -&gt; Feature Matching 의 과정의 <strong>결과</strong>가 어떤지 집중해야 합니다.</p>
</blockquote>
<p>고로, 우린 적당한 모델을 찾아 input shape을 정하고, output feature 결과를 잘 가져다 쓰는게 맞다고 생각했습니다.
그러기 위해서 서치해본 결과, pre-trained model을 제공하는 WebGL을 활용하기로 했습니다.</p>
<p>그렇게 만들어진 것은 사진을 입력받아 닮은 포켓몬을 반환해주는 하나의 Web-App 입니다.</p>
<p>결과물 : <a href="https://pokemon-ai.com/">pokemon-ai.com</a></p>
<h2 id="idea">IDEA</h2>
<ol>
<li><code>feature model</code> 선별, 약 700 마리의 Poketmon <code>feature</code></li>
<li>비교적 가벼운 모델, <code>MobileNet v2</code> 선택</li>
<li>모델을 <code>ts.js</code> 포맷으로 추출하고, Client에서 <code>ts.js</code>로 <code>model</code> 로딩</li>
<li><code>kNN query</code>를 위해 <code>feature indexing</code></li>
<li>선택 기능을 구현, 모델을 추론해 해당 이미지 <code>feature</code> 추출</li>
</ol>
<h2 id="webgl">WebGL</h2>
<ul>
<li>GPU 가속을 활용한 자바스크립트 딥러닝 라이브러리</li>
<li>문서화가 잘 되어있으나, 처음 딥러닝 프레임워크를 사용한다면 러닝커브가 있음</li>
<li>직접 학습이 아닌 <code>pre-trained model</code>을 제공하고 있어 좋다.</li>
<li><code>Python keras model load</code> 가능 -&gt; <code>ts.js model</code> 추출이 가능한 이유</li>
</ul>
<h1 id="tfjs">TF.js</h1>
<h2 id="backend">Backend</h2>
<ul>
<li>CPU Backend : <code>Pure-JS(node)</code></li>
<li>WebGL Backend : 다른 Backend에 비해 지원하는 항목이 많다.</li>
<li>WASM Backend : 자료 부족 / 지원 범위가 좁다</li>
<li>WebGPU Backend : 자료 부족 / 지원 범위가 좁다</li>
<li>Node Backend </li>
<li>React Native : <code>Expo</code></li>
</ul>
<h2 id="model-추출">model 추출</h2>
<ol>
<li><code>Python code</code>로 model 추출 : <code>inputShape</code> 지정</li>
<li><code>json</code> 형태 model <code>async load</code></li>
<li>image (HTML elements) -&gt; <code>crop()</code>으로 inpuShape 일치</li>
<li>(H, W, C) 형태의 <code>inputShape</code> 에 대해 <code>squeeze -&gt; GlobalAveragePooling2D()</code> 사용</li>
</ol>
<h2 id="추론-결과">추론 결과</h2>
<h4 id="첫-회고">첫 회고</h4>
<ol>
<li>배경이 있어 원하는 결과가 나오지 않았다.</li>
<li>얼굴만 crop해보는건 어떨까?<ul>
<li>Smartcrop.js, Pico.js, etc...</li>
</ul>
</li>
<li>얼굴 segmentation도 TF.js가 해주는건 어떨까? <ul>
<li>BodyPix Segmentation 모델을 사용해보자!</li>
</ul>
</li>
</ol>
<blockquote>
<p>어차피 <code>tf.js</code> 쓸거면 얼굴 seg도 <code>tf.js</code>보고 맡기자.
마찬가지로 성능보다 빠른 속도가 우선이니, 경량화된 <code>Model(MobileNet V1)</code>을 사용하자.</p>
</blockquote>
<p>24가지 신체 부위에 대한 Seg 중, 왼쪽 얼굴과 오른쪽 얼굴만 분할하기로 결정했다.</p>
<h2 id="person-segmentation-with-body-pix">Person Segmentation with Body-Pix</h2>
<blockquote>
<p>얼굴만 뽑아서 했더니 비교적 덜 칙칙한 포켓몬이 나와 만족스러웠다.</p>
</blockquote>
<h2 id="deployment">Deployment</h2>
<h3 id="architecture">Architecture</h3>
<ul>
<li>AWS S3</li>
<li>AWS Cloudfront</li>
<li>AWS ACM</li>
<li>AWS Route53</li>
</ul>
<p>결과적으로 <code>Realtime Crop</code>이 가능했고, 속도도 꽤나 잘 나왔다.
닮은 포켓몬을 미리 load했던 시연 영상이지만, 실제 loading도 1~2초 정도 지연시간만을 가진다.</p>
<p><code>WebGL</code>이 아닌 CPU 가속 러닝이었다면 10초 정도 걸린다.</p>
<h3 id="얼굴이-여러개라면">얼굴이 여러개라면</h3>
<ul>
<li><p>실제로 얼굴이 2개인 포켓몬이 나타날 수도 있다.</p>
<ul>
<li>Feature vector에서 형태를 잘 찾는 것 같다.</li>
</ul>
</li>
<li><p>사진 내 얼굴이 여러개일 경우 전부 feature로 잡아 또가스 같은 포켓몬이 나오기도 한다.</p>
</li>
</ul>
<h3 id="모바일-지원">모바일 지원</h3>
<h4 id="문제">문제</h4>
<ul>
<li>데스크탑에서는 잘 지원되나, 모바일에서는 빈번하게 죽었다.</li>
<li>GPU memory를 많이 잡아먹는게 아닐까?<ol>
<li>이미지 선택 </li>
<li>얼굴 Seg</li>
<li>(224, 224, 3) resize</li>
<li>feature vector 추출</li>
</ol>
</li>
</ul>
<p>flow 디버깅 결과 이미지를 선택한 후 얼굴 Seg할 때 메모리가 터지는 현상이 빈번했다.</p>
<p>해상도가 큰 이미지를 <code>Seg</code>할 때 <code>Body-Pix model running</code> 중에 터졌다.</p>
<ul>
<li>해상도 변경 없어 메모리 이슈 발생, 입력 이미지의 해상도 변경.</li>
<li>이후 안정성은 향상되었으나, memory 누수가 존재.</li>
</ul>
<p>tf.Tensor를 활용할 때는 메모리 관리를 명시적으로 해줘야한다는 docs 내용을 발견했다.</p>
<ul>
<li>tf.memory(), tf.dispose()를 사용해 메모리를 명시적으로 관리하기로 했다.</li>
<li>GPU memory Ran out 발생 시, CPU Backend로 변경하도록 수정했다.</li>
</ul>
<p>test case는 80명 정도로, 문제가 재발할 수도 있다.</p>
<h3 id="개선사항">개선사항</h3>
<ol>
<li>Imagenet pre-trained model이라 feature를 잘 추출하지는 못한다.</li>
<li>kNN query를 활용해서 5개 중 가장 귀여운 포켓몬을 보여주는 방식을 선택했다.</li>
</ol>
<h2 id="medical-ai-with-tfjs-응용사례">Medical AI with tf.js (응용사례)</h2>
<h3 id="안저이미지-분류">안저이미지 분류</h3>
<p>Vuno(발표자 회사)에는 눈의 이상소견을 찾아주는 <code>deep learning classification</code> 제품이 있다.
제품 모델은 <code>GPU Server</code>에 두고, 좌안, 우안 구분을 업로드 시점에 <code>FE</code>에서 처리해보는건 어떨까</p>
<p>결과적으로 꽤나 괜찮았다.</p>
<p>그러나 100%는 아니었고, 해당 기능 배포를 위해서는 전용 UX가 필요하겠다는 결과를 얻었다.</p>
<h3 id="모달리티-분류">모달리티 분류</h3>
<p>의료영상에는 <code>Pixel Array</code> 뿐 아니라, 환자정보, 영상 메타정보 등이 있다.</p>
<blockquote>
<p>해당 영상이 어느 부위의 어느 축을 기준으로 촬영한 영상이다 등의 정보 = 모달리티</p>
</blockquote>
<p>비표준, 누락, 익명화 등에 의해 메타정보 신뢰도가 떨어진다.</p>
<p> 회사 프로젝트에 실제로 적용해보도록 했다.</p>
<h2 id="fe-deep-learning">FE deep learning</h2>
<h3 id="장점">장점</h3>
<ul>
<li>서버와 통신할 필요 없어 dektop Web App의 경우 FE에서만 추출이 가능해서 빠르다.</li>
<li>Client GPU resource 사용이 가능하다.</li>
<li>기존 백엔 모델 포팅이 간편하다.</li>
<li>간편한 모델이 많이 있다. (Ex. Body-Pix)</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>딥러닝 모델이 노출되어 회사 자산이 외부에 공개된다.</li>
<li>tfjs 의 경우 고수준의 API가 아닌 webgl tensor를 만든 경우 gc가 돌지 않는다.</li>
<li>모바일의 경우 적은 리소스로 메모리 터지는 경우가 빈번하다.</li>
<li>tfjs의 경우 webgpu, wasm backend가 안정적이지 않다.</li>
</ul>
<h3 id="결론">결론</h3>
<p>FE에서 딥러닝을 한다는 것은 해결할 문제가 더 많아진다는 것이며,
수동 작업을 반자동으로 향상시킬 수 있었습니다.
경우에 따라 서버 리소스 사용이 줄어든다.
딥러닝 모델이 노출되고, 클라이언트 사양과 서버 리소스 트레이드 오프가 필요하다.</p>
<h2 id="작성자-결론">작성자 결론</h2>
<p>기존 영상처리 및 Deep learning 실무에서 사용하던 성능 높은 모델은 아직 FE에서 사용하기에는 부담이 되는 것 같다.
Client의 GPU memory를 사용하기에 memory 최적화가 어느 수준으로 이루어져야 할지도 난관이라고 생각한다.</p>
<p>그러나 python으로만 가능하던 기존의 deep learning을 javascript에서도 활용할 수 있다는 점이 보다 javascript community의 힘을 강하게 해줄 수 있다고 생각한다.</p>
<p>한줄평 : 시간이 된다면 tf.js도 한 번 해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) Redux - RTK : 아, 서순..]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-Redux-RTK-%EC%95%84-%EC%84%9C%EC%88%9C</link>
            <guid>https://velog.io/@dongwu-kim/TIL-Redux-RTK-%EC%95%84-%EC%84%9C%EC%88%9C</guid>
            <pubDate>Thu, 28 Oct 2021 07:05:36 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>반갑습니다.</p>
<p>어제는 <code>Redux -&gt; RTK</code>의 순서가 아닌 <code>RTK -&gt; Redux</code> 순서로 공부하는 글이었습니다.</p>
<p>그렇기에 오늘은 약간의 코드와 다량의 개념들로 글이 이루어질 것 같습니다.</p>
<p>지난 글에서는 코드를 보며 </p>
<ul>
<li>Provider component</li>
<li>Store<ul>
<li>configureStore()</li>
</ul>
</li>
<li>Action</li>
<li>Reducer</li>
<li>dispatch</li>
</ul>
<p>등에 대한 개념을 <code>RTK</code>를 통해 알아보았습니다.</p>
<p>그런데 해당 방식은 서순이 맞지 않기도 하고, 놓치는 부분이 약간씩 생기는 것 같아 <code>Redux</code>의 개념을 <code>RTK</code>로 이해해보는 식으로 변경해볼까 합니다.</p>
<p>이번 글은 기존의 <code>Redux</code>에서 왜 <code>RTK</code>를 사용하는지? 그리고 어떤 개념들이 <code>RTK</code>를 사용하게 만들었는지 체크해보겠습니다.</p>
<p><img src="https://w.namu.la/s/8907e9c946d890a08275f74253375efdf8dc6f633361e164ca31cf5437a1da8e440ea7c13a1380f7ccc1d1c17c1216f0210805ac28426df9ec8c8699092562904f7b68af073a0cd7f40f7aa97009cf7f" alt=""></p>
<p>그럼 시작해보겠습니다.</p>
<h1 id="2-redux">2. Redux</h1>
<p>보통 가장 처음 생각하고 넘어갔어야 할 3가지 원칙을 어제 글에서는 적지 않았었습니다.</p>
<p>그런데 공부하다보니 대부분의 포스팅, 도서, 강의에서 강조하는 내용이 3가지가 있어 적고 시작하겠습니다.</p>
<p>약간 복무신조 느낌이네요.</p>
<ol>
<li>Single source of truth : 스토어는 단 하나만 존재해야 한다.</li>
<li>Read-only state : 상태는 불변 그 자체다.</li>
<li>Changes from pure functions : 순수 함수로만 변경해야 한다.</li>
</ol>
<p>하나씩 생각해보죠.</p>
<h2 id="ssotsingle-source-of-truth">SSOT(Single source of truth)</h2>
<p><code>Redux</code>는 단 하나의 상태 객체, <code>Store</code>를 구성할 것을 강조합니다.</p>
<ol>
<li><p>먼저 앱의 확장성을 고려했을 때, 하나의 자바스크립트 객체를 활용하는 것이 <code>Client - Server</code> 요청 처리를 단순화하기 때문입니다.</p>
</li>
<li><p>그리고 이전의 객체가 저장되어 있다면 <code>redo - undo</code>를 쉽게 구현할 수 있기 때문입니다.</p>
</li>
<li><p>디버깅에 용이하기 때문입니다.</p>
</li>
</ol>
<p>자, 보통의 개발자가 좋아하는 키워드 하나로 표현할 수 있겠죠?</p>
<p><strong>분명 &quot;유지보수&quot;에 용이할 수 있다.</strong> 가 포인트입니다.</p>
<h2 id="read-only-immutable">Read-only, Immutable</h2>
<p>변경에 있어 하나의 가능성만을 남겨두고, 단방향 플로우를 구현해야 하는 이유.</p>
<blockquote>
<p><strong>디버깅에 용이하다.</strong> <strong>유지보수에 도움이 된다.</strong> <strong>프로젝트 안정성을 보장한다.</strong></p>
</blockquote>
<p>하나의 컨벤션을 준수하는 것으로 수많은 오류를 회피할 수 있습니다.</p>
<p>그런데 이런 이유 말고 왜 <strong>불변</strong>이라는 표현을 사용해야만 할까요?</p>
<p>슬슬 감이 오시듯,</p>
<p>상태는 <code>Redux</code> 뿐 아니라, <code>React</code>도 강조하고 있는 개념인 불변성을 가진 객체로 관리되어야 합니다.</p>
<p>그건 바로 변경 탐지의 이유때문입니다.</p>
<p><code>React</code>가 상태, props의 변경을 어떻게 체크하는지 생각해보면 왜 불변성을 유지해야 하는지도 알 수 있습니다.</p>
<blockquote>
<p><code>React</code>는 <code>Shallow compare</code>를 통해 <code>Rendering 최적화</code>를 이뤄냈으니, 한 번 해당 키워드 글을 읽어보면 좋습니다.</p>
</blockquote>
<h2 id="change-from-pure-function">Change from pure function</h2>
<p>순수함수? 쉽게 볼 수 있는 키워드는 아닙니다.</p>
<p>그런데 크게 신경써야 할 부분은 바로 이 부분입니다.</p>
<p>하나의 스토어, 불변객체인 <code>State</code> 관리, 그보다 난해한 것은 내가 작성한 <code>Reducer</code>가 순수함수가 맞을까? 하는 고민이 가장 어려울 것 같습니다.</p>
<p>순수함수는 <code>I-O(Input - Output)</code>이 명확해야 합니다.</p>
<p>어떠한 <code>Side-Effect</code>도 존재하지 않는 함수를 순수함수라 명합니다.</p>
<p>예를 들어보면, </p>
<p>지난 프로젝트에서 다루었던 <code>dayjs()</code> 의 유틸함수 중 일부는 순수함수가 아닙니다.</p>
<p>현재 시점의 <code>Date()</code> 객체의 <code>data</code>를 다루는 함수의 경우 다양한 상황에서 버그가 발생할 수 있습니다.</p>
<p>대표적 예시로 <code>Github Actions</code>에서의 <code>test</code> 환경은 <code>KST</code> 환경과 다른 <code>UTC</code> 환경입니다.</p>
<p>그럴 경우 <code>Date()</code> 객체의 <code>data</code>는 한국 시각이 아니니, <code>I-O</code> 자체가 틀어져버리는 이슈가 발생하게 됩니다.</p>
<p>그러나 반대로 </p>
<p>특정 <code>date String</code>을 입력으로 받아 <code>milliSec</code>로 변경해주는 로직의 경우 <code>Side-Effect</code>가 존재하지 않습니다.</p>
<blockquote>
<p>변경(&quot;2021-10-28&quot;); // return 대충 밀리초(12321313213)</p>
</blockquote>
<p>이런 함수가 바로 순수함수입니다.</p>
<p>즉, 앞으로의 상태변경 로직은 순수함수로만 작성되어야 한다. 정도만 이해하면 될 것 같습니다.</p>
<h2 id="why-redux">Why Redux?</h2>
<p><code>Redux</code>를 알아보았을 때, <code>Store</code> 하나로 프로젝트 전체의 상태를 관리한다는 것은 때로 매우 까다로울 수 있으며, 때로는 프로젝트 내부의 <code>Cost</code>가 높아질 수도 있습니다.</p>
<p>거기다 불변객체가 비교에는 용이하지만, <code>re-Render</code>가 적은 경우 객체 단순 변경이 어쩌면 더 좋은 퍼포먼스를 자랑할수도 있습니다.</p>
<p>순수함수를 고려하는 것 또한 개발자의 입장으로써 쉬운 일은 아닐 수 있죠.</p>
<p>그럼에도 <code>Redux</code>는 확장을 고려할 때 사용해야 하는 하나의 솔루션으로 인정받았습니다.</p>
<p>실제로 인턴십 중에도 <code>Redux</code>를 실무에 사용하고 있다는 것을 듣기도 했고, 지금의 회사도 <code>Redux</code>를 사용하고 있습니다.</p>
<p>그래서인지 <code>Redux</code>를 사용할 때는 모두 입을 모아 &quot;더 고민해보고, 다시 고민해라.&quot; 라는 말을 하기도 합니다.</p>
<p>하지만 상태관리 이슈에 부딪혀본 저와 같은 사람들이라면 이제는 도전해볼 때가 되었습니다.</p>
<p>가보죠.</p>
<h2 id="flow">Flow</h2>
<p>먼저, Redux의 동작흐름은 어제 말씀드린것처럼</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/f77e6c91-f436-44da-8299-38b8d1935814/image.png" alt=""></p>
<p>이런 식으로 구성되어 있습니다.</p>
<p>이를 하나씩 생각해보면</p>
<h3 id="action">Action</h3>
<p><code>Action</code>은 특정 상태에 대한 하나의 정의입니다.</p>
<p>통상적으로 객체 형태이며, <code>type</code> 프로퍼티를 필수적으로 갖는 <code>JS Plain Object</code>입니다.</p>
<h3 id="middleware">Middleware</h3>
<p><code>Reducer</code> 실행 전, 거쳐가는 하나의 레이어 개념입니다.</p>
<p><code>Middleware</code>에는 보통 <code>Reducer</code>의 예외처리를 하거나, 상태값을 디버깅하는 로직을 추가할 수 있습니다.</p>
<p>그 외에도 <code>Action</code>에 대한 비동기 처리를 가능하게 한다던가(<code>redux-thunk</code>), 특정 <code>Action</code>을 기반으로 동작하는 하나의 chaining(<code>redux-saga</code>)을 구현할수도 있습니다.</p>
<p>라이브러리를 주로 활용하는데, 위 두 예시가 자주 사용되는 라이브러리입니다.</p>
<p>Middleware의 경우 function compose로 구현되는데, 이는 클로저 개념을 응용하는 방식입니다.</p>
<p>주로 </p>
<p><code>const middleware = store =&gt; next =&gt; action =&gt; next(action)</code></p>
<p>의 형태를 가집니다.</p>
<p>어제 까보았던 RTK의 store API를 적용해서 생각하면,</p>
<p><code>const middleware = ({getState, dispatch}) =&gt; next =&gt; action =&gt; next(action)</code></p>
<p>으로 디테일하게 표현할 수 있습니다.</p>
<h3 id="reducer">Reducer</h3>
<p><code>(prevState, action)</code> 둘을 전달받아 <code>newState</code>를 반환하는 하나의 function입니다.</p>
<p><code>const reducer = (prev, action) =&gt; new</code></p>
<p>의 구조로 이루어져 있다고 생각할 수 있습니다.</p>
<p>Redux는 Reducer를 위한 라이브러리입니다.</p>
<p>그러니, Reducer를 얼마나 잘 작성하고, 잘 다룰 수 있느냐가 사실상 관건이 아닐까 생각합니다.</p>
<h3 id="store">Store</h3>
<p>단일 <code>JS object</code>입니다.</p>
<p>프로젝트 전체의 상태를 보유하기도 하고, 경우에 따라 일부만 보유할 수도 있습니다.</p>
<p>어제 보았던 <code>store</code>의 내부에는 다양한 <code>property</code>가 존재하는데, 이를 <code>store API</code>라고 표현하기도 합니다.</p>
<p>모든 상태의 집합체라고 생각할 수 있습니다.</p>
<p>또한 값을 직접 수정할 수 없기에 <code>dispatch</code> 메서드를 지원합니다.</p>
<p><code>dispatch</code>를 <code>reducer</code> 내부 등에서 호출할 경우 <code>store</code>는 쌓인 순서, <code>queue</code>에 담아두는 것과 같은 순서로 처리합니다.</p>
<h3 id="view">View</h3>
<p>UI단, interaction을 접수받는 유일무이한 창구입니다.</p>
<p>저는 항상 View도 단방향 통신을 한다고 말하는데, 유저의 눈과 손을 분리해서 생각하기에  View도 하나의 순환구조가 형성된다고 생각합니다.</p>
<p>눈에 영향을 끼치고, 손에게 영향을 받으며, 흐름에 맞게 다른 시스템 구조의 누군가에게 영향을 끼치게 됩니다.</p>
<p>특정 Action의 Trigger이기도 합니다.</p>
<h2 id="rtk-reduxtoolkit">RTK, ReduxToolKit</h2>
<p>이제 <code>Redux</code>를 어느정도 알아봤으니, RTK에 대해 얘기해봅시다.</p>
<p><code>Redux Toolkit</code>은 <code>Redux</code>의 다양한 기능을 보다 쉽게 사용할 수 있는 메서드를 지원합니다.</p>
<p>공식문서에도 잔뜩 있기도 하고, 구체적인 내용은 적지 않겠습니다.</p>
<p>그런데 새로운 개념 하나 둘 정도는 확실히 짚고 넘어가봅시다.</p>
<h3 id="slice-asyncthunk-what-the">Slice? AsyncThunk? what the...</h3>
<p>분명 template을 하나 만들면 이상한 키워드들이 좀 있습니다.</p>
<p><code>AsyncThunk</code>라던가 혹은 <code>Slice</code>가 그 예시가 될겁니다.</p>
<p>어쩌면 어제 적었던 <code>configureStore()</code>도 그 예시중에 포함됩니다.</p>
<p>그런데 <code>AsyncThunk</code>나 <code>configureStore</code> 정도는 이름만 봐도 느낌이 있는데, <code>Slice</code>? 이건 정말 모르겠습니다.</p>
<p>그러니 한 번 짚고 넘어가봅시다.</p>
<h3 id="slice">Slice</h3>
<p><code>Slice</code>는 <code>Reducer + Action</code> 입니다.</p>
<blockquote>
<p>?</p>
</blockquote>
<p>왜 이렇게 되어있는지 의문이시겠지만, 분명 그런 개념이라고 명시되어 있습니다.</p>
<p>이 <code>Slice</code>를 통해 어제 <code>Redux</code> 개념을 공부하려다보니 분명 구멍이 생겼을 정도로 가히 파격적인 개념입니다.</p>
<p>다시 어제의 코드를 가져와봅시다.</p>
<pre><code class="language-javascript">const initialState: CounterState = {
  value: 0,
  status: &quot;idle&quot;,
};

export const counterSlice = createSlice({
  name: &quot;counter&quot;,
  initialState,
  reducers: {
    increment: (state) =&gt; {
      state.value += 1;
    },
    decrement: (state) =&gt; {
      state.value -= 1;
    },
    incrementByAmount: (state, action: PayloadAction&lt;number&gt;) =&gt; {
      state.value += action.payload;
    },
  },
  extraReducers: (builder) =&gt; {
    builder
      .addCase(incrementAsync.pending, (state) =&gt; {
        state.status = &quot;loading&quot;;
      })
      .addCase(incrementAsync.fulfilled, (state, action) =&gt; {
        state.status = &quot;idle&quot;;
        state.value += action.payload;
      });
  },
});</code></pre>
<p>이제 눈에 확 들어오는 친구들이 몇 있습니다.</p>
<ul>
<li><p><code>initialState</code> : 초기 상태값입니다. 따로 할당된 값은 블럭 위에 있습니다.</p>
</li>
<li><p><code>reducers</code> : Action type을 key로 갖는 reducer function의 집합체입니다.</p>
</li>
<li><p><code>extraReducers</code> : pending, fulfilled, reject 등의 비동기 Promise 상태에 따라 변경되는 새로운 State를 반환하는 reducer 입니다. <strong>예외처리기</strong>입니다.</p>
</li>
</ul>
<p>이정도면 Slice는 참 간단히 이해할 수 있겠습니다.</p>
<p>근데 <code>incrementAsync</code>는 어디에 있을까요?</p>
<h3 id="createasyncthunk">createAsyncThunk</h3>
<p>여기 있습니다.</p>
<pre><code class="language-javascript">// counterAPI.ts
export function fetchCount(amount = 1) {
  return new Promise&lt;{ data: number }&gt;((resolve) =&gt;
    setTimeout(() =&gt; resolve({ data: amount }), 500)
  );
}

...

// counterSlice.ts
export const incrementAsync = createAsyncThunk(&quot;counter/fetchCount&quot;, async (amount: number) =&gt; {
  const response = await fetchCount(amount);
  return response.data;
});</code></pre>
<p>이게 뭐시여... 싶으시겠지만, <code>RTK</code>를 활용한 비동기 처리 로직 사용법의 예시입니다.</p>
<p><code>axios</code>를 쓰면 <code>fetch</code>가 어색하듯, <code>createAsyncThunk</code>도 어색합니다.</p>
<p>그러나 해당 요청결과를 <code>extraReducer</code> 내부에서 당연히 필터할 수 있고, 기존 <code>custom hooks</code> 를 활용한 로직 결과 반환을 <code>Slice</code> 파일 내에서 처리할 수 있다는 소소한 장점도 생깁니다.</p>
<p>그러니 thunkAction도 유용하게 사용해봅시다.</p>
<h3 id="rtk-middleware">RTK middleware?</h3>
<p>Middleware layer의 예시를 RTK식으로 표현할 수도 있습니다.</p>
<pre><code class="language-javascript">export const incrementIfOdd = 
  (amount: number): AppThunk =&gt;
    (dispatch, getState) =&gt; {
      const currentValue = selectCount(getState());
      if (currentValue % 2 === 1) {
      dispatch(incrementByAmount(amount));
      }
  };</code></pre>
<p>홀수일 때 더하는 로직인데, 익숙한 표현이 하나 보입니다.</p>
<p>그 전에 <code>AppThunk</code>는 <code>custom type</code> 입니다. </p>
<p>실제로는 <code>TunkAction type</code>을 대입한 것과 같은 구조기에 <code>return type</code>은 <code>(dispatch, getState) =&gt; {}</code> 와 같습니다.</p>
<p><code>(dispatch, getState) =&gt; {}</code></p>
<p>확실히 <code>Redux middleware</code>와 유사한 구성이죠?</p>
<p>또한 <code>currentValue</code>의 경우 <code>prevState</code>에 해당합니다.</p>
<p><code>dispatch(action())</code>도 보이네요.</p>
<p>생각보다 이제 보이는게 좀 있는 것 같습니다.</p>
<h2 id="마치며">마치며</h2>
<p>Redux -&gt; RTK 구조로 공부하는게 어제보다 효율도 잘 나오고, 개념 이해도 쉬웠던 것 같습니다.</p>
<p>둘 다 사용할거니까 어차피 둘 다 하긴 했어야 합니다.</p>
<p>그런데 로드맵이 있다면 Redux 개념 -&gt; RTK 순서가 훨씬 도움이 되었던 것 같네요.</p>
<p>그럼 오늘은 이정도로 마치겠습니다. 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) Redux : RTK 열어보기]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-Redux-RTK-%EC%97%B4%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@dongwu-kim/TIL-Redux-RTK-%EC%97%B4%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 27 Oct 2021 15:11:44 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>반갑습니다. 오랜만입니다.</p>
<p>취업 후 일주일 정도 여유를 가지고 있습니다.</p>
<p>매일 알고리즘 풀이도 조금씩 하며 시간을 보내느라 오랜만에 글을 적게 되네요.</p>
<p>11월 1일 출근 전까지 그래도 회사 스택에 대한 공부를 하며 시간을 보내볼까 합니다.</p>
<p>많은 것들을 공부하기에는 현재 고향에 내려와있어 조금 어려울 것 같지만, 그래도 Redux, Redux toolkit / saga, React query 정도는 한 번 사용해볼까 합니다.</p>
<p>오늘은 <code>Counter</code>를 제공하는 기본 template을 활용해 Redux를 뜯어보며 이해해보겠습니다.</p>
<h1 id="2-template-뜯어보기">2. template 뜯어보기</h1>
<p>먼저, RTK, Redux Toolkit Quick Start 방법 중 하나인 CRA template을 생성해야 합니다.</p>
<p><code>npx create-react-app my-app --template redux-typescript</code></p>
<p>명령어를 터미널에 입력해 typescript template를 생성할 수 있습니다.</p>
<p>해당 프로젝트의 기본 구성은 4가지 카운터 기능입니다.</p>
<p>index 파일부터 한 번 열어보며 생각해보겠습니다.</p>
<blockquote>
<p>Redux를 사용해본 적 없고, 기본 개념 정도만 알고 있는 상태로 RTK를 공부하는 글입니다. 따라서 깊은 분석이 아닌 유추와 적당한 검색을 통해 해결해나가는 과정을 기록하겠습니다.</p>
</blockquote>
<h2 id="21-indextsx">2.1 index.tsx</h2>
<p>Index 파일에는 우리가 쉽게 볼 수 있는 <code>ReactDOM.render</code> 메서드 호출이 있습니다.</p>
<pre><code class="language-javascript">import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom&quot;;
import &quot;./index.css&quot;;
import App from &quot;./App&quot;;
import { store } from &quot;./app/store&quot;;
import { Provider } from &quot;react-redux&quot;;

ReactDOM.render(
  &lt;React.StrictMode&gt;
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;
  &lt;/React.StrictMode&gt;,
  document.getElementById(&quot;root&quot;)
);</code></pre>
<p>주목해야 할 부분은 <code>HOC</code>, <code>Provider</code>가 하나 있고, <code>store</code> 라는 것을 불러와 <code>Provider</code>의 <code>props</code>로 사용한다는 점입니다.</p>
<p>대충 생각해봤을 때, <code>RN</code>의 <code>Navigation</code>, <code>React</code>의 <code>Router</code>와 같이 <code>HOC</code>로 다른 컴포넌트를 감싸 무언가 전달하려고 하는구나 정도로 생각이 됩니다.</p>
<p>그렇다면 Provider 먼저 확인해봅시다.</p>
<h3 id="provider">Provider</h3>
<p><code>Provider</code>는 어떤 컴포넌트일까요?</p>
<p>먼저 <code>index.d.ts</code> 파일을 확인해보면, </p>
<pre><code class="language-javascript">export interface ProviderProps&lt;A extends Action = AnyAction&gt; {
    store: Store&lt;any, A&gt;;
    context?: Context&lt;ReactReduxContextValue&gt; | undefined;
    children?: ReactNode;
}

export class Provider&lt;A extends Action = AnyAction&gt; extends Component&lt;ProviderProps&lt;A&gt;&gt; { }</code></pre>
<p><code>Provider</code>라는 컴포넌트는 Context API에서의 개념과 거의 동일하게 보입니다.</p>
<p><code>ProviderProps</code>의 interface 내부에는 <code>store</code>, <code>context</code>, <code>children</code> 등의 개념들이 자리하고 있습니다.</p>
<p><code>context</code>와 <code>children</code>은 이름만 봐도 React 유저라면 어라? 할 수 있을 정도의 친숙한 이름이지만, Redux만의 특별한 개념은 <code>store</code>에 있는 것 같습니다.</p>
<p>다른 2개의 프로퍼티, <code>context, child</code>는 옵션이지만, <code>store</code>는 필수로 구현해야 합니다.</p>
<p>그렇다면 <code>store</code>는 대체 뭘까요? </p>
<p>천천히 열어보며 어떤 개념일지 유추해봅시다.</p>
<h2 id="22-storets">2.2 store.ts</h2>
<p>그럼 자연스럽게 <code>store.ts</code> 파일을 확인해봅시다.</p>
<pre><code class="language-javascript">import { configureStore, ThunkAction, Action } from &#39;@reduxjs/toolkit&#39;;
import counterReducer from &#39;../features/counter/counterSlice&#39;;

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType&lt;typeof store.getState&gt;;
export type AppThunk&lt;ReturnType = void&gt; = ThunkAction&lt;
  ReturnType,
  RootState,
  unknown,
  Action&lt;string&gt;
&gt;;</code></pre>
<p>결과적으로 <code>index.tsx</code>에서 사용하는 <code>store</code> 객체는 <code>configureStore()</code> 메서드의 <code>return</code> 값입니다.</p>
<p>인자로는 <code>{reducer : {counter : counterReducer}}</code>를 전달받고 있네요.</p>
<p>아래로는 <code>AppDispatch</code>, <code>RootState</code>, <code>AppThunk</code> 등의 타입이 정의되어 있습니다.</p>
<p>여기서 새로운 typescript 활용이 나오는데, <code>ReturnType&lt;T&gt;</code> 입니다.</p>
<p><code>RootState</code>의 경우 <code>&lt;typeof store.getState&gt;</code>를 <code>ReturnType</code> 제네릭에 넣어줌으로써<code>store.getState</code>의 <code>return</code> 값 타입으로 추론하라는 말이 됩니다.</p>
<p>그렇다면 <code>store</code> 객체는 어떻게 구성되었길래, <code>dispatch, getState</code> 등의 내부 메서드를 보유하고 있는걸까요?</p>
<p>그리고 <code>ThunkAction</code>은 대체 뭘까요?</p>
<p>하나씩 파헤쳐봅시다.</p>
<h3 id="action-store-view">Action, Store, View</h3>
<p>잠시 개념을 보자면, <code>Redux</code>는 <code>store</code>라는 하나의 객체에서 앱 전체의 상태를 관리한다고 합니다.</p>
<p>나아가 <code>Redux</code>는 <code>Action =&gt; Store =&gt; View</code> 구조로 동작하고 있습니다.</p>
<p>단, <code>View</code>는 <code>user interaction</code>을 담당하기 때문에 <code>View</code>는 <code>Action</code>의 Trigger가 될 수 있습니다.</p>
<p>그렇기에 <code>View =&gt; Action =&gt; Store =&gt; View</code> 의 순환구조로 이해할 수 있습니다.</p>
<p>그 외에도 다양한 개념이 있는데, 튜토리얼 단계에서 이해해야 할 것은 <code>Action, Store, View</code>의 <code>circulation</code> 정도인 것 같습니다.</p>
<p>또한 실제 순환이라는 개념에 맞도록 <code>Simplex</code>, 단방향으로 통신하는 구조를 갖습니다.</p>
<p><strong>&quot;영향을 끼칠 수 있는 존재는 정해져있다.&quot;</strong> 가 대부분 순환 시스템의 핵심이라는 생각이 강해서인지 미리 생각해두면 확 와닿는 것 같습니다.</p>
<p>이제 개념은 대충 정리되어 있으니, 관계별로 필요한 내용들을 조금씩 끼얹으며 코드 분석을 해봅시다.</p>
<h3 id="configurestore">configureStore</h3>
<p>앞서, <code>Store</code>는 <code>View</code>에 영향을 끼치는 존재인 것을 확인했습니다.</p>
<p>우선은 RTK의 <code>configureStore()</code> 메서드의 선언을 보며 <code>store</code> 객체가 어떤 항목들을 포함하고 있는지 확인할 수 있기는 한데...</p>
<pre><code class="language-javascript">export declare function configureStore&lt;S = any, A extends Action = AnyAction, M extends Middlewares&lt;S&gt; = 
    [ThunkMiddlewareFor&lt;S&gt;]&gt;(options: ConfigureStoreOptions&lt;S, A, M&gt;): EnhancedStore&lt;S, A, M&gt;;</code></pre>
<p>그런데 사실 선언을 봐도 잘 모르겠습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/4d53560f-1b4d-4769-99d5-d3c677f6e698/image.png" alt=""></p>
<p>간단하게 <code>console.log()</code>를 찍어보면,</p>
<p><code>store</code>는 이런 구성으로 이루어져 있고, <code>configureStore</code>의 <code>return</code> 결과는 해당 메서드들로 구성된 객체라는 것을 알 수 있습니다.</p>
<blockquote>
<p>type 선언으로 유추하고자 한다면 <code>EnhancedStore</code> =&gt; <code>Store</code> 의 방법으로도 확인할 수 있습니다.</p>
</blockquote>
<p>또한 <code>store</code> 내부에는 <code>reducer</code>를 포함하는 등의 내용은 존재하지 않습니다. </p>
<p>이유는 간단합니다. 다른 개념이니까요.</p>
<blockquote>
<p>Reducer의 개념은 잠깐 아래 항목에서 다루겠습니다.</p>
</blockquote>
<p>그러나 store를 생성하는 시점에 store가 프로젝트의 Reducer를 인식할 수 있는 이유는</p>
<p><code>configureStore</code> 메서드는 기존 <code>createStore()</code> 메서드의 결과와 달리 <code>parameter</code>로 넘겨준 객체의 <code>reducer {Object}</code>에 대해 자동으로<code>combineReducer()</code> 메서드를 실행하기 때문입니다.</p>
<p>원리는 <code>store</code>를 정의하는 시점에서 <code>root reducer</code>에 해당 <code>reducer</code>를 할당하는 구조인 것 같은데, 기존 <code>Redux</code>에 비해 얼마나 편해진것인지 체감하기엔 전례가 없어 모르겠습니다.</p>
<p>parameter를 관찰하는 방법은 </p>
<p><a href="https://redux-toolkit.js.org/api/configureStore">RTK 공식문서 configureStore</a></p>
<p>또는 type 선언 중 <code>ConfigureStoreOptions</code>를 참고하는 방법이 있습니다.</p>
<p>요약하자면, </p>
<ol>
<li>configureStore 는 dispatch, subscribe, getState 등의 메서드를 포함하는 객체를 반환한다.</li>
</ol>
<blockquote>
<p>아직은 어디다 쓰는지 모른다.</p>
</blockquote>
<ol start="2">
<li>store를 configureStore로 정의하는 시점에 전달되는 reducer 객체는 combinedReducer 메서드를 통해 rootReducer에 할당되는 것처럼 보인다.</li>
</ol>
<blockquote>
<p>rootReducer는 또 뭘까.</p>
</blockquote>
<p>정도로 요약할 수 있습니다.</p>
<p>이어서,</p>
<h3 id="reducer">Reducer</h3>
<p>돌아돌아 결국 <code>Action =&gt; Store =&gt; View</code>의 관계에 필요한 <code>Reducer</code>라는 키워드를 알 수 있었습니다.</p>
<p><code>rootReducer</code> 단어를 먼저 접하긴 했지만, 사실 <code>Redux</code>는 <code>Store</code>와 <code>Action</code> 사이에 <code>Reducer</code>라는 특별한 레이어가 존재합니다.</p>
<p><code>Reducer</code> 는 다시 특정 <code>Action</code>과 <code>Store</code>의 <code>State</code>로 구성됩니다.</p>
<p>다시 생각하면 </p>
<p><code>View =&gt; Action =&gt; Reducer =&gt; Store =&gt; View</code> 의 구조로 순환하는게 <code>Redux</code>라고 볼 수 있습니다.</p>
<p>그럼 이 Reducer의 예시 코드를 한 번 볼까요?</p>
<pre><code class="language-javascript">export const counterSlice = createSlice({
  name: &quot;counter&quot;,
  initialState,
  reducers: {
    increment: (state) =&gt; {
      state.value += 1;
    },
    decrement: (state) =&gt; {
      state.value -= 1;
    },
    incrementByAmount: (state, action: PayloadAction&lt;number&gt;) =&gt; {
      state.value += action.payload;
    },
  },
  extraReducers: (builder) =&gt; {
    builder
      .addCase(incrementAsync.pending, (state) =&gt; {
        state.status = &quot;loading&quot;;
      })
      .addCase(incrementAsync.fulfilled, (state, action) =&gt; {
        state.status = &quot;idle&quot;;
        state.value += action.payload;
      });
  },
});</code></pre>
<p>이것저것 잔뜩 정의되어 있습니다.</p>
<p>이 중, reducers property를 보면, 함수 3개가 보입니다.</p>
<p>그 중 action을 가지고 있는 함수는 <code>incrementByAmount()</code> 라는 이름의 함수입니다.</p>
<p>그렇다면 나머지 둘은 <code>action</code>이 존재하지 않는 걸까요?</p>
<p>엄연히 얘기하면 그건 아닙니다.</p>
<blockquote>
<p>그 전에 사실 타고 들어가자면 <code>selector, dispatch</code> 개념도 먼저 알아야 하는데, <code>Store</code>의 <code>state</code>를 직접 변경하지 않도록 하는 하나의 장치 정도로 얕게 이해하고 넘어갑시다.</p>
</blockquote>
<p>실제 <code>dispatch</code>가 사용되는 위치는 <code>Button onClick()</code> 시점에 사용되기에 <code>action</code> 객체의 <code>type</code>이 존재하지 않는 것이지, 일종의 <code>action</code> 개념은 존재한다고 생각해도 되는게 아닐까 합니다.</p>
<blockquote>
<p><code>action</code>은 <code>type</code>이라는 분류 키워드가 존재하고, 하나의 <code>action</code> 객체에 <code>type</code>과 커스텀 프로퍼티가 함께 있습니다. 원래의 <code>Redux</code>에서는 이 <code>dispatch</code> 에 <code>action</code> 객체를 전달받는 콜백함수를 전달해 <code>store</code>의 <code>state</code>를 변경합니다.</p>
</blockquote>
<p>라고 유추하는 것이지, 명확한 사실은 아닙니다.</p>
<p>Redux가 아닌 RTK니까요.</p>
<p>요약하자면,</p>
<ol>
<li><p>Action과 Store 사이에는 [action, state] 구조로 이루어진 Reducer가 존재한다.</p>
</li>
<li><p>Store state는 dispatch 메서드로 변경해야 한다.</p>
</li>
<li><p>Reducer는 연산 레이어로 볼 수 있다.</p>
</li>
</ol>
<p>정도가 되겠습니다.</p>
<p>오늘은 여기까지 하고, 다음 글에서 좀 더 깊게 이어서 다뤄보도록 하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PS : 프로그래머스 코테연습 level 1 완료]]></title>
            <link>https://velog.io/@dongwu-kim/PS-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%BD%94%ED%85%8C%EC%97%B0%EC%8A%B5-level-1-%EC%99%84%EB%A3%8C</link>
            <guid>https://velog.io/@dongwu-kim/PS-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%BD%94%ED%85%8C%EC%97%B0%EC%8A%B5-level-1-%EC%99%84%EB%A3%8C</guid>
            <pubDate>Sun, 17 Oct 2021 10:50:37 GMT</pubDate>
            <description><![CDATA[<h1 id="반갑습니다">반갑습니다.</h1>
<p><a href="https://github.com/dongwu-kim/Algorithm/tree/main/level1">level1 문제 정리 repo</a></p>
<p>블로그 글은 오래만이네요.</p>
<p>숨고 인턴십이 끝나고 열심히 PS와 놓쳤던 학습을 다시 진행하고 있었습니다.</p>
<p>일주일 조금 넘게 문제를 열심히 풀었더니 어느새 프로그래머스 코테연습 level1 Javascript 문제는 다 풀게 되었습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/8d7a32a0-1f77-4833-afd3-9ee8f4bfe37b/image.png" alt=""></p>
<p>시작이 10만등인가... 5만등인가 기억은 나지 않지만, 그래도 랭킹도 좀 올랐습니다!</p>
<p>아직까지는 할 만 했던 것 같은데, level2는 얼마나 어려워질지 걱정이 좀 되네요.</p>
<p>그래도 문제를 풀며 보다 재밌는 방식으로 자료구조나 알고리즘 공부를 했던 것 같습니다.</p>
<blockquote>
<p>이론보다 실습이 더 꽂히는 타입인가봅니다.</p>
</blockquote>
<p>과정에서 에라토스테네스의 체, 탐욕법, 아직은 생소한 다양한 개념들을 맛볼 수 있었고, 문제를 열심히 풀고 다른 사람들의 풀이를 보며 현타가 오기도 했습니다.</p>
<p>다른 사람 풀이에 자극 받아 쉬운 문제들로는 메서드 체이닝에 지속적으로 도전했고, 지금은 대부분 함수 결과를 리턴하는 방식으로 코드를 작성하고 있습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/101080b4-ad69-46d5-be3e-ac502a736f16/image.png" alt=""></p>
<blockquote>
<p>이제 보니 표현식이 아닌건 좀 불편하긴 하네요...</p>
</blockquote>
<h2 id="정규표현식-replace">정규표현식, replace()</h2>
<p>일부 문제를 거치며 정규표현식과 문자열 다루는 것에 익숙해질 수 있는 시간이었습니다.</p>
<p>카카오 블라인트 채용 테스트(아마 가장 쉬운 문제일겁니다.) 같은 경우에는 정규표현식이 없으면 상당히 긴 코드라인과 반복을 통한 문자열 처리가 들어가야하는데, 정규표현식으로 풀면 무척 간단해서 <strong>사실 꿀 좀 빨았습니다.</strong></p>
<p>프로젝트 진행 중에 바빠서 따로 공부할 시간이 없어 손을 놓고 있었는데, 정규표현식을 통한 문자열 처리를 문제풀이에서 본격적으로 사용하다보니 꽤나 익숙해진 것 같기도 합니다.</p>
<p>그래도 아직 다양한 기호로 이루어진 정규식이 바로 떠오르지 않기는 합니다.</p>
<blockquote>
<p>아, 구린 정규식을 전달한 <code>replace()</code> 같은 경우에는 split - method - join 보다 성능이 좋지 않을 수 있습니다. </p>
</blockquote>
<p>또 <code>.replace()</code> 체이닝을 하면 효율성 테스트를 뚫는 것은 가끔 어려울 수 있습니다.</p>
<p>그러니 <strong>잘 작성된 정규표현식과 한 번의 메서드 호출</strong>을 작성하는게 심신에 이롭겠다는 생각을 하게 되었습니다.</p>
<h2 id="벨로그-작성이-뜸한-이유">벨로그 작성이 뜸한 이유</h2>
<p>안그래도 블로그에 해당 문제풀이를 하며 생각했던 것을 기록할까 했는데 역으로 작성했던 코드를 보며 이전 기억을 거슬러가는게 좀 더 머리에 남는 것 같기도 하고, 하루에 10문제 가까이 풀기에 블로그에 적기에는 상당히 오랜 시간이 소요되는 것 같아 포기했었습니다.</p>
<p>TMI 머신이라 문제 하나에 한 페이지씩 작성할 것 같다는 생각에 그만..</p>
<p><strong>사실 이력서와 포폴도 문제였지만요.</strong></p>
<p>대신 기록은 필요하니 Repo를 하나 만들어서 README에 문제별 분류를 해두었습니다!</p>
<p>코드를 보더라도 이게 어떤 문제였는지 역으로 유추할 수 있도록 간단한 핵심 내용을 오른편에 적어두었습니다.</p>
<p><a href="https://github.com/dongwu-kim/Algorithm/tree/main/level1">재업 - level1 문제 정리 repo</a></p>
<blockquote>
<p>PS만 하다보니 블로그에는 소홀해졌지만, 깃허브는 놓지 않았습니다!</p>
</blockquote>
<h2 id="마치며">마치며</h2>
<p>최근에는 열심히 작성한 이력서로 지원도 하고 있고, 서류 통과해 면접 일정도 잡힌 곳이 있기도 해서 블로그가 썰렁하긴 할 것 같습니다.</p>
<p>만약 level2 문제가 어렵고, 기록하지 않으면 애매하겠다 싶으면 README를 좀 더 열심히 적는 방법을 사용할 것 같네요.</p>
<p>벨로그와 깃허브의 컨셉을 분리하는 구조로 운영하기로 했으니, PS 관련 글은 여기서 마치겠습니다.</p>
<p>그럼 다들 남은 주말 푹 쉬시고, 다음주 이 때 쯤에 다시 주차 보고로 돌아오겠습니다!</p>
<p>안녕!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 18. 개념 공부와 PS]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-18.-%EA%B0%9C%EB%85%90-%EA%B3%B5%EB%B6%80%EC%99%80-PS</link>
            <guid>https://velog.io/@dongwu-kim/TIL-18.-%EA%B0%9C%EB%85%90-%EA%B3%B5%EB%B6%80%EC%99%80-PS</guid>
            <pubDate>Thu, 07 Oct 2021 13:32:04 GMT</pubDate>
            <description><![CDATA[<h1 id="1-드디어-ps">1. 드디어 PS</h1>
<p>안녕하세요.</p>
<p>오랜만에 글을 적는 것 같습니다.</p>
<p>저는 어제부로 드디어 PS를 시작했습니다.</p>
<p>알고리즘 이론도 없고, 가진건 JS 메소드 몇개와 얼마전 합류한 정규표현식 친구 외엔 없습니다.</p>
<p>그럼에도 프로그래머스의 문제를 열심히 풀고, 남들의 풀이를 이해해보려 합니다.</p>
<p>현재 이틀동안 3개의 문제를 풀었고, Repo로 풀이를 관리하는 방법을 선택했습니다.</p>
<p><a href="https://github.com/dongwu-kim/Algorithm">PS 레포</a></p>
<p>앞으로 열심히 레포를 채워넣을 예정입니다.</p>
<p>현재는 level1 수준이지만, 이번달 목표는 level2 진입 정도로 생각하고 있습니다.</p>
<p>바빠도 오전에는 꼭 문제를 하나 이상 풀겠습니다.</p>
<h1 id="2-공부">2. 공부</h1>
<p>매일 조금씩 그동안 사용했던 기술스택과 CS를 조금씩 녹여낼 예정입니다.</p>
<h2 id="react-후기">React 후기</h2>
<p>이번 챕터는 React 개념과 후기를 적어보도록 하겠습니다.</p>
<p>먼저, 기본적인 사고방식을 복습하고 넘어가겠습니다.</p>
<h3 id="선언형과-상태">선언형과 상태</h3>
<p>React는 선언형 라이브러리입니다.</p>
<p>선언의 이전에 React에는 State, 상태라고 하는 특별한 개념이 존재합니다.</p>
<p>상태는 특정 컴포넌트가 갖는 유니크한 형질로 볼 수 있습니다.</p>
<p>예를 들어, 지구 어딘가에 임의의 김동우가 있다면 김동우의 상태는 여러가지가 있습니다.</p>
<p>키가 크거나, 작거나, 덩치가 크거나, 작을 수도 있겠죠.</p>
<p>우리는 덩치가 큰(State) 김동우(Component)를 생각하고 해당 상태에 맞는 코드로 컴포넌트를 그려내기만 하면 됩니다.</p>
<p>그리고 덩치가 큰 상태의 반대인 작은 상태의 김동우를 코드에 녹여낼 수도 있습니다.</p>
<p>덩치가 큰 김동우를 만들기 위해 필요한 일련의 프로세스, 밥을 잘 먹이거나, 운동을 더 열심히 시키거나 하는 과정을 배제한 상태로 말이죠.</p>
<p>덩치가 작은 상태라면? (조건부 렌더링) -&gt; 이런 모습이어야 해. (view 변경)</p>
<p>라는 사고방식으로 말이죠.</p>
<p>그냥 사고하고, 작성하면 React는 View를 조작합니다.</p>
<p>바닐라로 DOM의 상태를 변경하기 위해 특정 DOM 노드를 지정하고, 값을 넣어주고, 스타일을 변경하기 위해 JS 내부에서 함수로 변경하던 과정은 React에 존재하지 않습니다.</p>
<p><strong>그냥 덩치가 크다면 이렇게 생겼어.</strong></p>
<p><strong>그런데 덩치가 작다면 이렇게도 생겼어.</strong></p>
<p>특정 컴포넌트의 View를 State에 따라 변경하는 것이 바로 React입니다.</p>
<p>그렇다면 React는 어떻게 View를 HTML, CSS 없이 변경할 수 있는걸까요?</p>
<p>단순합니다. </p>
<p>그냥 Javascript 코드 내부에서 HTML을 조작하면 됩니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/5f0ebc81-6f7c-470b-a533-26a0592b3c17/%E1%84%82%E1%85%A9%E1%86%AF%E1%84%85%E1%85%A1%E1%86%AB%20%E1%84%8B%E1%85%AF%E1%86%AB%E1%84%89%E1%85%AE%E1%86%BC%E1%84%8B%E1%85%B5.jpeg" alt=""> !!!..</p>
<p>React는 <code>JSX</code>를 활용해서 상태에 따라 바뀌는 HTML elements를 생성합니다.</p>
<p>render() 메소드, 혹은 특정 함수형 컴포넌트의 반환값이 바로 HTML elements입니다.</p>
<p>아무것도 몰라도 됩니다.</p>
<p>실제로 존재하지 않는 Tag를 <code>&lt;&gt;</code>로 감싸기만 해도 React는 잘 그려줍니다.</p>
<p>이렇게 HTML elements를 그려내는 하나의 함수, 혹은 클래스를 우리는 컴포넌트라고 부릅니다.</p>
<p>제가 만드는 대부분의 React App은 컴포넌트들의 조합입니다.</p>
<p>그리고 모든 React App이 컴포넌트들의 조합이죠.</p>
<p>그러나 막 만든 컴포넌트와 대충 조합된 구조는 분명 문제를 야기합니다. </p>
<p>예를 들어, depth가 무척 깊은 컴포넌트라면</p>
<h3 id="prop-drilling">Prop Drilling</h3>
<p>이 발생할 수 있습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/d5b1ce9b-c1f0-4d10-9f31-afd58018d274/image.png" alt=""></p>
<p>Props는 분명 어딘가에서 오는 State입니다.</p>
<p>대부분 상위 컴포넌트(Parents)에서 State를 관리하고, 자식 컴포넌트(Child) View와 연결된 State를 Props로 물려받는 것이 React에서는 올바른 방향이라고 얘기합니다.</p>
<p>문제는 wrapping된 컴포넌트의 depth가 깊을 때, 불필요한 렌더링이 발생하거나, 혹은 사용하지도 않는 State를 Props로 전달받고, 다시 Props로 하위 컴포넌트에 전달해주어야 하는 안티패턴이 나타난다는 점입니다.</p>
<p>이를 Prop drilling이라고 합니다.</p>
<p>React가 제시하는 방향성은 언제나 State의 위치는 어지간하면 상위 컴포넌트인데, 이러한 상황에서는 해당 방향이 올바른가? 하는 의구심을 품게 됩니다.</p>
<p>저는 해당 라이브러리를 사용할만큼 큰 프로젝트를 다뤄본 적은 없기에, 나름대로 React의 방향성을 지키며 어떻게 프로젝트를 구성할 수 있을지 생각해보는 시간을 가졌습니다.</p>
<ol>
<li><p>상태관리 라이브러리</p>
</li>
<li><p>적절하게 분포된 State</p>
</li>
<li><p>넓이로 확장</p>
</li>
</ol>
<p>세 가지 방법 정도가 있지 않을까 생각을 했는데, 확실히 2번은 타협에 가까워 팀 단위 개발에서 주관이 부딪힐 문제가 있다고 생각합니다.</p>
<p>그 외 3번은 조삼모사의 느낌이 드는 것 같습니다.</p>
<blockquote>
<p>결과적으로 3번은 React의 State 설계 방향성을 완벽히 어기기도 합니다.</p>
</blockquote>
<p>과정에서 Redux를 하나의 HOC로 바라보고, 상태관리 라이브러리를 선택하는 것이 React 개발 방향성과 일치하는 것이 아닐까 하는 결론에 도달할 수 있었습니다.</p>
<p>이 과정에서 Context API, 컴포넌트 합성 등에 대해서도 고민했습니다.</p>
<p>그러나, 대부분 상황에서 Redux는 해답이 될 수 있지만, Context API와 합성은 제약이 발생할 수 있다는 점에서 극히 일부에 적용될 수 있다는 생각을 했습니다.</p>
<p>Context API는 쉽게 생각하면 styled component의 Theme Provider와 같은 HOC 개념입니다.</p>
<p>컴포넌트 합성과 Context API의 경우 결국 하나의 컴포넌트에서 wrapping하는 패턴이 동일하다고 판단되어 Redux와 비교가 어려운 것 같습니다.</p>
<p>상황에 따라, 명확한 목적에 따라 방법을 구분할 수 있는 개발자가 되어야겠습니다.</p>
<h1 id="3">3.</h1>
<p>이것저것 적다 보니 글이 장황한 것 같습니다.</p>
<p>내일은 좀 더 깊은 이야기와 개념, 문제풀이로 뵙겠습니다.</p>
<p>밤이 늦었네요. </p>
<p>그럼 전 이만 물러가도록 하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 17. 기업협업 종료 & 계획 (feat. 프로젝트 고도화?)]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-17.-%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%EC%A2%85%EB%A3%8C-%EA%B3%84%ED%9A%8D-feat.-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B3%A0%EB%8F%84%ED%99%94</link>
            <guid>https://velog.io/@dongwu-kim/TIL-17.-%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%EC%A2%85%EB%A3%8C-%EA%B3%84%ED%9A%8D-feat.-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B3%A0%EB%8F%84%ED%99%94</guid>
            <pubDate>Tue, 05 Oct 2021 10:32:06 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>반갑습니다. 오랜만이네요!</p>
<p>연휴동안 푹 쉬기도 했고, 지난주 목요일, 기업협업을 마치고 드디어 자유의 몸(?)이 되었습니다.</p>
<p>막상 끝내니 섭섭하기도 하고, 남는 것이 많아 좋기도 했습니다.</p>
<p>회고 내용은 굳이 적지 않겠습니다!</p>
<p>회고보다는 이후 예정사항을 조금 정리해둘까 합니다.</p>
<p>그럼 시작하겠습니다.</p>
<h1 id="2-기업협업-종료-이후">2. 기업협업 종료 이후</h1>
<p>이것저것 부족한게 많았습니다.</p>
<p>협업 내내 CS에 기반한 DB 지식도 없었을뿐더러, 로직을 작성하는데 필요할법한 알고리즘 &amp; 자료구조 지식도 거의 전무했습니다.</p>
<p>이게 정말 필요했나? 생각해보면 또 그렇지 않을 수도 있지만,</p>
<p>분명 저는 DB에서 데이터를 가져와 사용했어야 했고, 그 과정에서 Object 형태의 데이터를 정제하기도 했습니다.</p>
<p>사용한 방법으로 데이터를 요청하는 것이 최선이 맞는지, 그리고 가져온 데이터의 형태가 최적의 형태였을지 그런 것들을 정말 잘 모르겠습니다.</p>
<p>코드가 지저분한건 그런 것들에 대한 저의 지식이 없기 때문일겁니다.</p>
<p>그래서 앞으로 무엇을 공부할거냐?</p>
<h2 id="21-코드-없는-알고리즘과-데이터-구조">2.1 코드 없는 알고리즘과 데이터 구조</h2>
<p>네. 바로 책(e-book도 있음)입니다.</p>
<p>내일부터는 아마 이 책을 조금씩 읽어보며 녹여내지 않을까 생각하고 있습니다.</p>
<p>책의 수준이 어떤지, 그리고 이 책이 정말 도움이 되는지 그런 것들은 사실 지금 레벨에서 중요하지 않다고 생각합니다.</p>
<p>아는 것이 많지 않으니까, 보면 도움이 되겠죠.</p>
<h2 id="22-기본적인-cs-지식--언어-동작원리">2.2 기본적인 CS 지식 &amp; 언어 동작원리</h2>
<p>DB는 물론, 이제 앞으로 준비해야 할 것은 다양한 CS 지식과 언어에 대한 당연히 알아야 할 탐구입니다.</p>
<p>브라우저, 네트워크 통신, JS는 어떻게 컴퓨터와 소통하는지, ...etc, 다양한 것들을 조금씩 알아볼까 합니다.</p>
<p>현재 알고 있는 것들도 분명 많을테지만, 정리하며 곱씹어나갈 시간이 필요한 것 같습니다.</p>
<p>급하지 않은 시점에 꼭 필요한 공부라고 생각합니다.</p>
<h2 id="23-jest">2.3 Jest</h2>
<p>지금도 하고 있고, 오늘도 잠깐 도전해보았습니다.</p>
<p>먼저, Jest를 활용한 unit test 1차 작성은 완료된 시점입니다.</p>
<blockquote>
<p>오늘에서야 드디어...</p>
</blockquote>
<p>일주일 동안 로직에 대한 test를 작성하며, 실제 로직의 문제점을 발견하기도 했고, test 로직이 잘못된 경우도 분명 있었습니다.</p>
<p>github actions CI를 활용하면, 내 test 로직이 왜 통과가 안될까? 하는 화도 나지만, 분명 strict한 test를 진행할 수 있었던 것 같습니다.</p>
<p>로컬에서는 그냥 대충 초록불인데, actions는 PR 통과를 시키지 않아서 좋았던 것 같습니다.</p>
<p>그렇게 coverage 채우기 프로젝트를 어느 정도 마치긴 했습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/04461f8b-2ba6-4380-8ab1-faaa72172bc3/image.png" alt=""></p>
<p>표의 항목을 어떻게 활용할지는 아직은 숙제인 것 같습니다.</p>
<p>완벽히 작성한 파일에서 어떤 부분이 문제인지도 확인해야겠네요.</p>
<h2 id="24-redux">2.4 Redux</h2>
<p>Redux는 고도화 과정을 논의할 때 나온 하나의 숙제입니다.</p>
<p>기존 presenter 단에서 관리하던 state들이 꽤 있는데다가, props interface를 좀 정리하고 싶다는 막연한 생각부터 시작이 된 것 같네요.</p>
<p>기왕 이렇게 고도화 할거라면 Redux를 사용해보자! 하는 마음이 있습니다.</p>
<h2 id="25-여유가-된다면">2.5 여유가 된다면</h2>
<p>프로젝트 고도화 내용 중 일부였던 구현되어있지 않은 View를 작성할 것 같습니다.</p>
<p>My page tab or Meeting tab View를 구현할 것도 같네요.</p>
<p>다만, 언제까지나 &quot;여유가 있다면&quot; 입니다.</p>
<p>셋이 하던 프로젝트를 혼자 고도화 할 예정이니까요.</p>
<h2 id="26-프로그래머스">2.6 프로그래머스?</h2>
<p>네. 이제 취업시장에 나가야 하니 본격적으로 알고리즘 문제도 한 번 풀어볼까 합니다.</p>
<p>책을 괜히 산게 아니었습니다.</p>
<p>현재는 계획 중인데, 아마 내일부터는 문제를 하나씩 풀어볼 것 같습니다.</p>
<p>짤막하게 TIL에도 올릴 것 같네요.</p>
<h1 id="3-그래서">3. 그래서</h1>
<p>달라질 것은 없는 것 같습니다.</p>
<p>그냥 하던 공부를 계속 할거고, 하던 프로젝트를 더 좋은 방향으로 나아가게 하기 위해 고민할 것 같습니다.</p>
<p>일상은 변화가 없을거고, 제가 하는 것들은 언제나 같지 않을까 생각합니다.</p>
<p>공부하고, 적용하는 일을 하기로 마음먹었으니까요.</p>
<p>다만 이제 일상도 Trello로 관리하기로 한 것은 어떤 느낌일지 궁금하긴 합니다.</p>
<p>그러니까 앞으로도 화이팅입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 16. Jest + GitHub actions = CI]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-16.-Jest-GitHub-actions-CI</link>
            <guid>https://velog.io/@dongwu-kim/TIL-16.-Jest-GitHub-actions-CI</guid>
            <pubDate>Tue, 28 Sep 2021 12:18:12 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>반갑습니다.</p>
<p>오늘은 드디어 노래를 부르던 unit test library, Jest를 사용하는 날이었습니다.</p>
<p>그 외에도 제목에서 보듯, GitHub에서 제공하는 branch 단위의 CI, actions를 적용해보았습니다.</p>
<p>총평은... </p>
<ul>
<li><p>repo에 actions를 test하며 적용하는데에는 아직 꽤 시간이 걸린다!</p>
<blockquote>
<p>빌드 단계에서 npm install을 이용했기 때문에 그렇긴 합니다.</p>
</blockquote>
</li>
<li><p>Jest는 조금 더 써봐야 알 것 같다. 그러나 없는것보다 훨씬 낫다.</p>
<blockquote>
<p>보다 더 확장된 test 개념이 필요할 것 같다. 현재는 비즈니스 로직만 test중.</p>
</blockquote>
</li>
<li><p>actions + Jest로 PR test 자동화, CI가 가능하다.</p>
<blockquote>
<p>더 다양한 actions도 알아봐야겠습니다. ㄹㅇ 신세계임.</p>
</blockquote>
</li>
</ul>
<p>그럼 어떤 방법으로 합쳐서 사용했는지, 그리고 test의 예시코드는 어떤 형태인지? 부족하지만 올려볼까 합니다.</p>
<h1 id="2-jest">2. Jest</h1>
<p>Jest를 사용하는 방법은 간단합니다.</p>
<ol>
<li><p>React-Native 기준, <code>npm i -D jest</code> 하나면 그냥 뚝딱! 사용할 수 있습니다.</p>
</li>
<li><p>그리고 테스트 해볼 로직이 가득한 파일과 1:1 대응으로 TS project 기준, <code>~.test.ts</code> 파일을 만들고 내부에 코드를 작성하면 됩니다.</p>
</li>
</ol>
<pre><code class="language-javascript">// dayjs.test.ts (utils logic test)

test(&#39;[DayJS Logic] dayOfWeekDate test : (I)dayNum(0-6) (O)yearMonth string 반환 검사&#39;, () =&gt; {
  for (let i = 0; i &lt;= 6; i++) {
    const [year, month, day] = dayOfWeekDate(i).split(&#39;-&#39;);
    expect(parseInt(year, 10)).toEqual(NOW_YEAR_NUM);
    expect(parseInt(month, 10)).toBeLessThanOrEqual(12);
    expect(parseInt(day, 10)).toBeLessThanOrEqual(31);
  }
});</code></pre>
<ol start="3">
<li><p>이후 프로젝트 폴더 위치에서 <code>npm test</code>만 입력해주면? 바로 test가 실행됩니다.</p>
</li>
<li><p>추가적으로 프로젝트 내의 test 파일이 어느정도의 테스트를 진행하고 있고, 앞으로 얼마나 더 진행해야 하는지 확인하려면 <code>npx jest --coverage</code>를 실행해주면 됩니다.</p>
</li>
</ol>
<p>이런저런 과정들을 거쳤지만, Jest를 사용하는 것은 큰 문제가 아니었습니다.</p>
<p><strong>추후 Native logic을 가져다 사용할 수 없는 경우가 있다는데, <code>mocking</code> 등과 같은 키워드로 해결할 수 있다는 힌트도 얻었습니다.</strong></p>
<p>이외에도 <code>UI components test, Redux chunk action</code> 등에 대한 키워드도 들었는데, 약간 먼 미래라 여기다 적어두기만 하겠습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/738a642c-3c18-4c71-bbb8-13fe4bf7cd94/image.png" alt=""></p>
<blockquote>
<p>터미널에서 test 결과도 볼 수 있습니다. ㅋㅋ </p>
</blockquote>
<h1 id="3-github-actions">3. GitHub actions</h1>
<p>오후 내내 actions와 씨름했습니다.</p>
<p><a href="https://daphne-dev.github.io/2021/06/16/github-action-001/">도움받은 블로그</a> 링크를 올려둘테니, 관심있는 분은 사용해보셔도 될 것 같습니다!</p>
<p>actions는 MS를 등에 업은 GitHub의 project CI 기능입니다.</p>
<p>대표적으로 push와 pull_request 두 기능을 trigger로 삼아 자동으로 특정 테스트가 실행되는 등의 관리가 가능합니다.</p>
<p>remote push, PR 둘 다 관리할 수 있기에 앞으로 더 많은 유저들이 사용하지 않을까 싶습니다.</p>
<p>actions는 사실 단일 기능보다 특정 외부 라이브러리와 함께 사용할 때 빛을 발하는 것 같습니다.</p>
<p>이를테면, Jest 같은 test library와 말이죠.</p>
<p>마켓플레이스에 다양한 actions가 존재하니 필요하면 거기서 가져다 사용해도 좋습니다.</p>
<p><a href="https://github.com/marketplace?type=actions">Github 마켓플레이스 : action</a></p>
<p>저는 오늘 action이 돌아가는걸 좀 보려고 직접 타이핑하며 하나의 스크립트를 만들어봤습니다.</p>
<p>아래 스크립트는 jest와 연동된 스크립트입니다.</p>
<p>보시면 npm install, npm test 등의 익숙한 명령어들이 보입니다.</p>
<pre><code class="language-javascript">name: PR_Test

on: [pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Setup Node
        uses: actions/setup-node@v1
        with:
          node-version: &quot;14.x&quot;

      - name: npm_install
        run: npm install

      - name: run_test_code
        run: npm test

      - name : if_fail
        uses: actions/github-script@v4
        with:
            github-token: ${{ secrets.TOKEN }}
            script : |
              const ref = &quot;${{github.ref}}&quot;
              const pull_number = Number(ref.split(&quot;/&quot;)[2])
              console.log(pull_number, ref.split(&quot;/&quot;), github.ref)
              await github.pulls.createReview({
                ...context.repo,
                pull_number,
                body : &quot;테스트코드를 다시 확인해주세요. &quot;,
                event : &quot;REQUEST_CHANGES&quot;
              })
              await github.pulls.update({
                ...context.repo,
                pull_number,
                state: &quot;closed&quot;
              })
        if: failure()</code></pre>
<ul>
<li><p><code>script : ~</code>의 경우 javascript 문법으로 이루어져 있습니다.</p>
</li>
<li><p><code>on : [pull_request]</code> 부분은 해당 event일 때를 의미합니다.</p>
<p>이 부분이 가장 중요한게, 초기설정에는 <code>branch : [~]</code> 와 같은 코드가 포함되어있습니다.</p>
<p>그러나, branch를 특정할 경우 feature/~, etc... 등의 brach에서 올리는 PR을 자동으로 발견하지 못하는 상황이 있었습니다.</p>
<p>이에 수동으로 빌드하고, 수동으로 테스트하며 actions를 완성했네요.</p>
</li>
</ul>
<blockquote>
<p>Jest가 깔려 있는 프로젝트의 CI 환경을 구축하고 싶으시다면 가져다 사용하시면 됩니다!</p>
</blockquote>
<p>외에도 push event, PR에 대해 많은 action이 존재합니다.</p>
<blockquote>
<p><code>jobs</code>, <code>github-token 설정하는 방법</code> 등은 구글에 정리글이 많이 있으니 궁금하신 분들은 좀 돌아보셔도 될 것 같네요.</p>
</blockquote>
<p><img src="https://images.velog.io/images/dongwu-kim/post/6f5c8aae-a285-48cc-9c41-de469a22020b/image.png" alt=""></p>
<p>action 탭에서 이렇게 테스트를 통과하지 못한 PR을 확인할수도 있습니다.</p>
<blockquote>
<p>굳이 확인 안해도 PR은 테스트를 통과하지 못할 경우 자동으로 closed 처리됩니다.
 이것도 설정할 수 있습니다. (closed || 단순 merge 제한)</p>
</blockquote>
<h1 id="4">4.</h1>
<p>오늘도 이것저것 신문물을 접해보며 벙쪄있는 하루였습니다.</p>
<p>그래도 테스트가 돌아가는걸 보니까 상당히 즐겁더라구요.</p>
<p>내일은 테스트 로직을 잔뜩 만들어볼 예정입니다.</p>
<p>그럼 내일은 로직을 들고 오겠습니다! 좋은 하루 되세요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 15. 기업협업 : 발표]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-15.-%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%EB%B0%9C%ED%91%9C</link>
            <guid>https://velog.io/@dongwu-kim/TIL-15.-%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%EB%B0%9C%ED%91%9C</guid>
            <pubDate>Mon, 27 Sep 2021 11:52:02 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>오늘은 기업협업을 프로젝트 발표가 있는 날이었습니다.</p>
<p>개인적으로 프로젝트 발표인 만큼 로직이나 아키텍쳐 등과 같은 발표를 할까 했지만, 발표에 그런 내용을 넣는 것은 큰 의미가 없다는 생각이 들어 시각자료만 만들어두었습니다.</p>
<p>또한 네이버 사다리가 저는 발표가 아닌 Refactoring과 시연 준비를 담당하도록 지시했기에 열심히 발표 아이디어를 제공하고, 시연 전까지 부족했던 부분을 채워넣는 역할을 했습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/2a6a6a91-1b43-4157-992f-49c81d01fff9/image.png" alt=""></p>
<blockquote>
<p>네이버 갓다리..</p>
</blockquote>
<p><img src="https://images.velog.io/images/dongwu-kim/post/5b49874c-6329-4744-8891-368e550ef3ab/image.png" alt=""></p>
<blockquote>
<p>열심히 만들었던 Architecture 시각화 자료 =&gt; ReadMe에 넣을 예정이다.</p>
</blockquote>
<p>아무튼 이런저런 일이 있었는데, 오늘은 Fix를 하는 일이 대부분이었기 때문에 로직을 다루는 일이 주류였습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/cc8c7ef3-b4a8-427e-971c-6d93f83db7d6/image.png" alt=""></p>
<p>열심히 PR을 올리며 수정했었네요.</p>
<blockquote>
<p>역시 되돌아봐도 참 고생 많았던 것 같습니다.</p>
</blockquote>
<h1 id="2">2.</h1>
<p>로직을 fix해야 하는 상황들이 많았다는 것은 아무래도 TDD 방식의 개발이 아니어서인가? 하는 생각이 들었습니다.</p>
<p>어떤 문제가 야기될 수 있는지 인지할 수 없는 개발이라고 생각되네요.</p>
<p>당장 View에 보여질 기능들을 빠르게 구현하다보니 놓친 부분이 많기도 한 것 같습니다.</p>
<p>Refactoring 사항을 좀 체크하며 fix를 진행했는데,</p>
<p>Repository에서 데이터를 처리하는 함수들 중에는 중복이 많기도 하고, 또 hooks 내부에서 좀 더 잘 처리하면 될 것 같은 것들도 있었던 것 같습니다.</p>
<p>그리고 무엇보다도 있어서는 안될 일 중 하나인, View에 Data가 잘못 표시되는 상황이 오늘 많았습니다.</p>
<blockquote>
<p>그래서 내일은 Jest를 이용한 test file을 좀 추가해볼 생각입니다.</p>
</blockquote>
<h1 id="3">3.</h1>
<p>2번의 생각이 왜 들었는지, 오늘 fix된 사항들을 보며 다시 생각해보고자 합니다.</p>
<ol>
<li>TimeStamp 기준 변경 =&gt; serverTime으로 변경 ( 디바이스 시간 무시 )</li>
<li>Maps 위치정보 전달 (latitude, longitude)</li>
<li>출퇴근 toggle (useEffect 조건 및 초기값 변경)</li>
<li>이번주 근무 공휴일 제외 logic 및 조건부 렌더 반영</li>
<li>일 평균 근로시간 null 조건 추가</li>
<li>일평균 근로시간 logic 수정</li>
<li>근무예정 margin 추가</li>
<li>근무일지 월요일 기준 지난주 일과 불러오기 logic 추가</li>
<li>Day workTime avg logic fix</li>
<li>DetailLog logic fix</li>
</ol>
<p>위 리스트를 각 항목에 맞게 재배치하면,</p>
<h3 id="add">ADD</h3>
<ul>
<li>Maps 위치정보 전달 (latitude, longitude)</li>
<li>이번주 근무 공휴일 제외 logic 및 조건부 렌더 반영</li>
<li>근무예정 margin 추가</li>
<li>근무일지 월요일 기준 지난주 일과 불러오기 logic 추가</li>
</ul>
<h3 id="modify">MODIFY</h3>
<ul>
<li>TimeStamp 기준 serverTime으로 변경 ( 디바이스 시간 무시 )</li>
<li>출퇴근 toggle (useEffect 조건 및 초기값 변경)</li>
</ul>
<h3 id="fix">FIX</h3>
<ul>
<li>일 평균 근로시간 null 조건 추가</li>
<li>일평균 근로시간 logic 수정</li>
<li>Day workTime avg logic fix</li>
<li>DetailLog logic fix</li>
</ul>
<p>로 분류할 수 있습니다.</p>
<p>이 중 <strong>FIX</strong>에 해당하는 사항들은 실제 시연 전 <code>UI Test</code> 단계에서 나온 소중한 녀석들인데, 사실 조금 더 신경썼다면 나올 필요 없었던 친구들이기도 합니다.</p>
<ol>
<li><p>로직을 짤 때, 단순히 구현에 초점을 맞추기보다는 조금 더 고민하는 시간이 필요하지는 않을까 생각이 들었습니다.</p>
</li>
<li><p>또는 충분한 테스트를 활용한 개발 조건이라면 이런식으로 배포 전에 빨리 알아낼 수 있다는 점이 정말 매력적일 수 밖에 없지 않을까? 하는 생각이 들기도 했습니다.</p>
</li>
</ol>
<h1 id="4">4.</h1>
<p>중구난방식 서술인데, 오늘은 요약이 좀 가능한 것 같습니다.</p>
<ol>
<li><p>역할분담은 역시 네이버 사다리다.</p>
</li>
<li><p>View에 기능이 빨리 구현되는 것만이 때로는 정도가 아닐 수 있다.</p>
</li>
<li><p>Fix에 해당하는 사항들을 돌아보고, 다음 로직은 신중히 작성하자.</p>
</li>
<li><p>UI test도 test다. 툴을 알아보고 나중에 사용해보자!</p>
</li>
</ol>
<p>이상입니다. 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 14. RN & nativeBase : layout]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-14.-RN-nativeBase-layout</link>
            <guid>https://velog.io/@dongwu-kim/TIL-14.-RN-nativeBase-layout</guid>
            <pubDate>Fri, 24 Sep 2021 14:44:33 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>안녕하십니까. 오늘은 사실 정말 내용이 없습니다.</p>
<p>확실하게 느낀 것은 저는 디자인에 재능이 정말 없다는 것과 디자이너 분들의 재능과 노력이 얼마나 대단한 것인지 느낀 것 같습니다.</p>
<p>또한 퍼블리싱을 하다 보니, 앱 퍼블리싱을 하시는 분들도 정말 대단한 분들이라는 생각이 드는 것 같습니다.</p>
<blockquote>
<p>SafeAreaView... 이 녀석아 정신 좀 차려봐..</p>
</blockquote>
<p>그래서 오늘은 공부를 했다기보다, 맨땅에 열심히 헤딩을 했다는 생각이 드는 것 같습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/02bc54ce-2dc6-4777-b52b-1727a7b1a743/image.png" alt=""></p>
<p>아래 칸을 열심히 채워야 하는데... 왜 채워지지 않는지 고민이 많습니다.</p>
<p>그렇기에 오늘은 TIL보다 해당 레이아웃을 수정하는데 열을 쏟고 싶습니다.</p>
<p>다음주 월요일! 대망의 발표가 끝나고 완성작을 들고 오겠습니다.</p>
<p>그럼 좋은 금요일 되세요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 13. RN & FB : 기능 쳐내기]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-13.-RN-FB-%EA%B8%B0%EB%8A%A5-%EC%B3%90%EB%82%B4%EA%B8%B0</link>
            <guid>https://velog.io/@dongwu-kim/TIL-13.-RN-FB-%EA%B8%B0%EB%8A%A5-%EC%B3%90%EB%82%B4%EA%B8%B0</guid>
            <pubDate>Thu, 23 Sep 2021 14:20:59 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>반갑습니다.</p>
<p>오늘은 흘러가듯 한 챕터로 글을 마치겠습니다.</p>
<p>오늘 내용은 제목 그대로 기능 쳐내기를 좀 했습니다.</p>
<p>예상치 못한 부분에서 꽤나 고민을 하기도 했고, 오랜만에 라이브러리 공식문서 나들이를 좀 했던 것 같습니다.</p>
<blockquote>
<p>date-picker! calendar! calendar-picker!</p>
</blockquote>
<p>이런 녀석들을 좀 보다보니 react-datePicker가 얼마나 혜자였는지 알 수 있었습니다.</p>
<p>라이브러리를 까보면서 원하는 기능이 뭐가 있는지도 열심히 탐구했으나, 결과적으로 만든 함수와 기능을 사용하지 않기로 결정되었습니다.</p>
<p>조금 아쉽지만, 다음에 또 필요하면 그 때 사용하도록 하죠!</p>
<p>썸네일은 오늘 팀이 함께 쳐낸 기능들입니다.</p>
<p>뭐가 많기는 많네요</p>
<p>실제로 view에 보여지는 것은</p>
<p>극히 일부의 기능들인데, 로직의 수는 꽤나 많습니다.</p>
<p>현업에서 이보다 훨씬 큰 규모의 프로젝트를 진행하시는 분들은 얼마나 힘들지 가늠이 잘 되질 않네요.</p>
<p>오늘 가장 힘들었던 부분은 사실 </p>
<ol>
<li><code>firebase DB</code>에 </li>
<li><code>calendar</code>로 <code>pick</code>한 각 <code>day date</code>의 주차를 의미하는 <code>week date</code> 로 접근해</li>
<li>반환된 값 내에서 실제 <code>pick</code>한 <code>day date</code> 사이의 <code>data</code>를 추출하고</li>
<li>다시 반환값 내에 추출된 <code>day date</code>로 접근하여</li>
<li>실제 근무했던 <code>log</code>를 얻어낸 뒤 배열로 정렬하고</li>
<li><code>log data</code>의 시작과 끝 값으로 계산한</li>
<li>근무시간(number)를</li>
<li>순회 중에 반복하며 더해준 뒤</li>
<li>원하는 시간값(number)을 </li>
<li><code>string</code>화 하는</li>
</ol>
<p>부분이었습니다.</p>
<p>무려 두시간 가까이 생각했던 것 같습니다.</p>
<p>그 이유는</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/a986d906-d1b3-4a2c-86e5-e5a72a44a005/image.png" alt=""></p>
<blockquote>
<p>두 번 정제했음에도 거지같은 자료구조를 만든 과거의 제가 있습니다.</p>
</blockquote>
<p>무튼 저런 비스무리한 무언가를 가져다가</p>
<pre><code class="language-javascript">async getWorkTimeAverage(startDate: string, endDate: string): Promise&lt;any | null&gt; {
    const uid = auth().currentUser?.uid;

    const startWeek = calcWeekOfYear(startDate);
    const endWeek = calcWeekOfYear(endDate);
    let commuteDayArray: string[] = [];
    let commuteTime = 0;

    try {
      let commuteData = await database()
        .ref(`/${uid}/commuteData`)
        .orderByKey()
        .startAt(`${startWeek}`)
        .endAt(`${endWeek}`)
        .once(&#39;value&#39;, snapshot =&gt; {
          return snapshot.val();
        });

      // 해당 일자 사이의 timeStamp 값 commuteDayArray에 저장.
      Object.values(Object.values(commuteData)[0].value).forEach((week: any) =&gt; {
        Object.keys(week).forEach(day =&gt; {
          if (stringToMilliSec(startDate) &lt; parseInt(day, 10) || parseInt(day, 10) &lt; stringToMilliSec(endDate)) {
            commuteDayArray.push(day);
          }
        });
      });

      Object.values(Object.values(commuteData)[0].value).forEach((week: any) =&gt; {
        commuteDayArray.forEach(commuteDate =&gt; {
          if (week[commuteDate]) {
            let start: any = Object.values(week[commuteDate]).sort()[0];
            let end: any = Object.values(week[commuteDate]).sort()[Object.values(week[commuteDate]).sort().length - 1];
            commuteTime = end - start + commuteTime;
          }
        });
      });
      return [
        endWeek - startWeek &gt; 0
          ? calcMiliSecTimeHourMinuteString(commuteTime / (endWeek - startWeek + 1))
          : calcMiliSecTimeHourMinuteString(commuteTime),
        endWeek - startWeek &gt; 0 ? commuteTime / (endWeek - startWeek + 1) : commuteTime,
      ];
    } catch {
      return null;
    }
  }</code></pre>
<p>이런 과정을 대충 슥- 거치면?</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/abe3e275-01f6-425e-82d4-aa8ec2a35a07/image.png" alt=""></p>
<p>이렇게 귀여운 데이터로 바뀝니다!</p>
<pre><code class="language-javascript">endWeek - startWeek &gt; 0
          ? calcMiliSecTimeHourMinuteString(commuteTime / (endWeek - startWeek + 1))
          : calcMiliSecTimeHourMinuteString(commuteTime)</code></pre>
<p>이 부분이 해당 데이터의 형태를 결정하는 부분인 것 같습니다.</p>
<p>그 외에도 슬슬 상태관리를 신경써볼까? 해서 Redux 공부를 추석동안 했으나,</p>
<p>아직 적용하지 않았기에 </p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/89a272e2-1495-4f4b-afdf-376c5209a8a3/image.png" alt=""></p>
<p><code>custom hooks</code> 를 이전보다 더 잘 활용하는 방법을 찾고, 듣도 보도 못했던 <code>self state Refactoring</code>을 해버렸습니다.</p>
<p>의존성 배열에 인자로 받은 timeStamp를 추가함으로써 지속적인 load 기능을 담당하는 하나의 state가 만들어진 것 같습니다.</p>
<p>이건 오늘 좀 새로운 발견이라 놀랐습니다.</p>
<blockquote>
<p>그만큼 제가 React FC에 대한 근본이 없다는게 아닐까요.</p>
</blockquote>
<p>여전히 typeScript는 공부중입니다만, 현재는 에러메세지 처리하며 실력쌓기가 진행되는게 아닐까 싶네요.</p>
<p>그래도 언제나 <code>strict</code>로 코딩하고는 있습니다.</p>
<p>이것저것 많이 하다 보니까 오늘 생각의 흐름대로 적어버렸습니다.</p>
<p>다음엔 더 좋은 내용들로 찾아올게요. 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 12. RN & FB : 1주차 회고]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-12.-RN-FB-%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@dongwu-kim/TIL-12.-RN-FB-%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 17 Sep 2021 14:36:32 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>일주일간 온전히 프로젝트에 집중했습니다.</p>
<p>정말 더없이 즐겁기도 했고, 머리카락이 꽤나 빠진게 아닐까 싶을 일주일이었습니다.</p>
<p>난생 처음 DB를 설계해보기도 하고, </p>
<blockquote>
<p>No SQL! 어거지로 가보자는 마인드입니다.</p>
</blockquote>
<p>처음으로 경험해본 React-Native 개발에 필요한게 뭐가 있을지 고민하기도 했습니다.</p>
<p>전혀 얕고 간단하지 않은데, 쉽다고 한 사람은 누군지 궁금하기도 했습니다.</p>
<p>오우, native 좀 칠 줄 아는 사람인가봅니다.</p>
<p>그래도 이것저것 경험해보고, 세팅해보고, 또 세팅해본 결과...</p>
<p>진짜 개발이 뭔지 어렴풋이 알게 되는 느낌이기도 합니다.</p>
<blockquote>
<p>개발은... 어쩌면 모두가 호환 가능한 세팅이 아닐까요..?</p>
</blockquote>
<p>네. 잡설은 그만 하도록 하고, 중요한 키워드를 적어보겠습니다.</p>
<ol>
<li><p>일은 정말로 비례하지 않더라.</p>
</li>
<li><p>로직짜는게 제일 행복한 시간이다.</p>
</li>
<li><p>Redux... 이젠 정말 시작하자!</p>
</li>
</ol>
<p>로 집중해보도록 하겠습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/bd8b9802-cedc-4aa4-85d0-6f275c8fad37/image.png" alt=""></p>
<blockquote>
<p>PR은 그짓말 안해...</p>
</blockquote>
<h1 id="2-일은-정말로-비례하지-않는다">2. 일은 정말로 비례하지 않는다.</h1>
<p>네. 일이라는 것은 정말 난이도가 비례하지 않더라는게 가장 중요한 내용인 것 같습니다.</p>
<p>분명 planning에서 열심히 포커를 칠 때는 &quot;아 ㅋㅋ 이쯤이야&quot; 했던 것들도 절대 그렇지 않았습니다.</p>
<p>애초에 기준이 1점짜리인데 5점짜리로 평가된 기분이랄까요.</p>
<p>이상하게 하나 해결하는데 이것저것 손볼게 많았습니다.</p>
<p>이런 간단한 프로젝트조차 예상하지 못한 부분에서 변수가 많았는데, 실제 현업에서 진행하는 프로젝트의 cost는 어떨지 상상할 수 없게 되었습니다.</p>
<blockquote>
<p>라이엇 클라이언트 구리다고 욕하던 제가 부끄러워지는 일주일이었습니다.</p>
</blockquote>
<p>그래도 덕분에 클린 아키텍처, 왜 클린 코드 하는지 잘 알게 되었습니다.</p>
<p>하나의 로직을 수정하는데 있어 거미줄이 될 경우 정말 지옥을 맛볼 수 있지 않을까 하는 생각이 점점 들기 시작했거든요.</p>
<p>분명 어딜 손봐야 하는지 알면서도, 잘 모르기에 모두 찍어보는 과정을 경험해야 해서 더 어려운 것 같습니다.</p>
<blockquote>
<p>보통은 Repository 선에서 정리되는데... 불안하더라구요.</p>
</blockquote>
<p>그러니 우린 아는 선에서라도 최대한 깔끔하고, 명확한 코드를 짜는 것으로 시작해야 할 것 같습니다.</p>
<ol>
<li><p>DRY-KISS? SOLID? S만 지켜도 꽤나 준수한 코드가 나온다.</p>
<blockquote>
<p>아 ㅋㅋ 자꾸 카운터에서 햄버거 만들게 시키지 말라고 ㅋㅋ</p>
</blockquote>
</li>
<li><p>이 로직이 현재 파일에 있어도 될지만 잘 고려해도 된다.</p>
<blockquote>
<p>어차피 대부분은 틀린 답이니까.</p>
</blockquote>
</li>
<li><p>어거지로 구분하더라도, 독립된 구조로 구분해보려 노력하자. </p>
<blockquote>
<p>이게 그 개방....폐쇄...뭐 그런건가유...?</p>
</blockquote>
</li>
</ol>
<p>사실 LID는 적용할 틈도 없었지만, pratt의 조언들이 점점 SOLID를 공부할수록 맞아 떨어지는 것 같아 소름이 돋고 있습니다.</p>
<p>그러니, SOLID를 공부하는게 옳지 않을까 싶습니다.</p>
<h1 id="3-로직짜는게-행복한-시간이다">3. 로직짜는게 행복한 시간이다!</h1>
<p>네. 지옥같은 세팅을 수차례, 아니? 매일 오전마다 경험하면서 오늘 처음 들었던 생각입니다.</p>
<p>로직을 구성하는 하루가 제일 보람차고, 행복한 시간들입니다.</p>
<p>오전 내내 android와 ios 사이에서 줄타기하는 이 시간들, </p>
<p><code>cd android &amp;&amp; ./gradlew clean</code></p>
<p><code>npm cache clean --force</code></p>
<p>X code -&gt; <code>command + shift + k</code> or <code>excluded architecture</code> + <code>url scheme</code>,</p>
<p><code>npx react-native run-ios</code>, <code>npx react-native run-android</code></p>
<p>거기에 <code>AVD manager wipe data</code>, <code>pod deintegrate &amp;&amp; install, arch -x86_64</code> 시리즈...</p>
<p>또 수도 없이 많지만, 이제는 정말 세팅광이 되어버린 수준입니다.</p>
<p>알지도 못했던 <code>.plist</code> 뒤져보기, <code>AppDeligate</code> 훔쳐보기, <code>build.gradle</code> 털기 등등...</p>
<p>정말 많은 세팅을 진행하지 않았을까 하고 생각이 듭니다.</p>
<p>심지어 android는 아직도 app crush를 수시로 뱉어대고 있습니다.</p>
<blockquote>
<p>에러코드는 이제 우습지도 않아...</p>
</blockquote>
<p>그러니 모두 로직을 짤 수 있는 시간을 소중히 생각하길 바랍니다.</p>
<p>저는 이제 컴퓨터랑 슬슬 대화를 하고 있어요 😂</p>
<h1 id="4-redux-내가-간다">4. Redux... 내가 간다!</h1>
<p><img src="https://images.velog.io/images/dongwu-kim/post/0a7d419b-f867-4744-8eb3-1472f3c162ad/image.png" alt=""></p>
<p>이야... 이것저것 참 많죠?</p>
<p>버튼 event 하나에 필요한 state입니다.</p>
<p>문제는 저런 버튼 말고도 progress bar, cell... 다양한 것들이 하나의 view에 나타난다는 점이죠.</p>
<p>전부 다른 data를 load하고, save해야 합니다.</p>
<p>이제는 하나의 presenter에서 useEffect로 관리하는 것은 극히 비효율적일 수 밖에 없습니다.</p>
<blockquote>
<p>원래라면 component 분리로 어떻게든 해결했겠지만, presenter라는 view HOC 개념을 적용해서 이렇게 된거긴 합니다.</p>
</blockquote>
<p>따라서 Redux와 hook을 적절히 혼용하며 상태를 관리하는건 어떨까! 하는 생각이 들었습니다.</p>
<p>꿈만 같은 이야기이긴 한데, 추석 사이에 빠르게 쳐서 다음주 중으로 Redux까지 적용하는게 목표입니다.</p>
<h1 id="5">5.</h1>
<p>그냥저냥 한 주를 잘 보낸것 같기도 합니다.</p>
<p>PR만 봐도 어거지로 짠 로직이 꽤나 있구나... 싶기도 하고, 이제 클린 아키텍쳐 비슷한 무언가로도 로직 위치를 결정할 수 있기도 하니 성장한게 아닐까 싶네요.</p>
<p>그래도 아직 한참 남았습니다.</p>
<p>firebase DB 구조만 봐도 부족함이 느껴지고, 매번 로직을 짤 때 DB 구조를 변경하고 있으니까요.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/a04bb55c-2baa-4d1d-822b-6b2a38b44e43/image.png" alt=""></p>
<blockquote>
<p>그래도 아름답다... 그죠?</p>
</blockquote>
<p>아무튼 우리 모두 화이팅입니다.</p>
<p>즐거운 추석 되시고, 추석 내내 풀리지 않던 문제 잘 풀리시길 기도할게요.</p>
<p>모두 행복하세요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 11. RN & FB : geolocation, DB 변경]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-10.-RN-FB-geolocation-DB-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@dongwu-kim/TIL-10.-RN-FB-geolocation-DB-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Thu, 16 Sep 2021 13:18:59 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>반갑습니다.</p>
<p>오늘은 새로운 것과 이전의 것을 함께 다루니 온고지신과도 같네요!</p>
<p>네. 제목 그대로 <code>React-native-community/geolocation</code> 을 이용한 것과 기존 만들어둔 DB 구조에 대한 고민이 주제입니다.</p>
<p>오늘의 키워드는 office hour 시간에 나온 것들과 이전 프로젝트 구현에 있어 느꼈던 것들을 모아보겠습니다.</p>
<ul>
<li>date와 log</li>
<li>appstate</li>
<li>geolocation &amp; reverseGeolocation</li>
</ul>
<p>이정도가 되겠습니다.</p>
<p>차례대로 가보시죠.</p>
<h1 id="2-date와-log">2. date와 log</h1>
<p>이전까지의 DB 구조에서 통근 data를 관리하는 방식은 다음과 같았습니다.</p>
<p>예)</p>
<pre><code class="language-javascript">commute : {
  &quot;end&quot; : &quot;13:00:00&quot;,
  &quot;start&quot; : &quot;01:00:00&quot;,
  &quot;time&quot; : number~,
  &quot;timeLag&quot; : &quot;12:00:00&quot;,
}</code></pre>
<p>문제는 해당 구조로 자료를 저장할 경우 </p>
<ul>
<li>다양한 기기에서 출퇴근을 관리할 때 데이터가 꼬일 수 있다.</li>
<li>당일 출퇴근 버튼을 여러번 눌렀을 때, 올바르게 반영할 수 없다.</li>
</ul>
<p>등과 같은 이슈가 발생할 수 있습니다.</p>
<p>저는 당장 당일 출퇴근 toggle에 대해서만 고민하고 있었는데 pratt은 다기종까지도 고려할 수 있는 것을 보고 좀 더 넓게 볼줄도 알아야겠다는 생각이 들었습니다.</p>
<p>경험과 지식은 역시 위대한 것 같습니다.</p>
<p>따라서, 해당 방식이 아니라 출근과 퇴근의 경로를 분산시키는 방법을 사용하는 것은 어떨지 힌트를 받았습니다.</p>
<p>지금 당장 생각해보면</p>
<pre><code class="language-javascript">{ 
  commute : [...year[...week{...day}]],
  startlog : [...year, [...week, [...day]]],
  endlog : [...year, [...week, [...day]]] 
}</code></pre>
<p>혹은 start와 end log를 매일 리셋시키는 방법을 사용하는건 어떨까 싶기도 합니다.</p>
<p>아직 당장은 데이터를 어떤 방법으로 구성하는게 효율적일지 잘 모르겠습니다.</p>
<p>그러나 No SQL! 그냥 갈아엎고 다시 만들기도 쉬우니 막 해보겠습니다.</p>
<h1 id="3-appstate">3. Appstate</h1>
<p>App은 현재 우리 폰만 보더라도 background와 foreground라는 개념이 존재합니다.</p>
<p>이를 구분할 수 있는 개념이 Appstate인데, 자세한 사항은 <a href="https://reactnative.dev/docs/appstate">RN 공식 문서</a>를 추천드리겠습니다.</p>
<p>처음 알게된 키워드인데, 본격적으로 사용하기 전에 저도 정독할 예정입니다.</p>
<blockquote>
<p>steve 화이팅!</p>
</blockquote>
<h1 id="4-geolocation">4. geolocation</h1>
<p>네. 라이브러리입니다.</p>
<p>휴대전화의 위치센서를 사용할 수 있는 기가 막힌 라이브러리죠.</p>
<blockquote>
<p>android studio는 왜... 문제인지 모르겠습니다.</p>
</blockquote>
<p>그래도 ios에서 테스트해본 결과 괜찮았습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/6b7a30b4-556a-4a41-a7dd-73f5f52e2673/image.png" alt=""></p>
<p>알아보니 샌프란시스코에 있더라구요...</p>
<p>위 날짜와 시간은 하드코딩입니다!</p>
<p>아직 DB가 완성이 아니라 불러오기도 뭐해서 우선은 그대로 두었습니다.</p>
<p>오늘 steve가 로직을 작성하고, 제가 refactoring 했던 코드 올리고 마치겠습니다.</p>
<p>다들 좋은 목요일밤 되세요!</p>
<h2 id="mainusecase">MainUseCase</h2>
<pre><code class="language-javascript">import {MAPS_API_KEY} from &#39;../../../env.json&#39;;

import {UsingFirebaseDB} from &#39;../../data/repository/UsingFirebaseDB&#39;;
import {ILocation} from &#39;../../presentation/interface/IGeolocation&#39;;

export class MainUseCase extends UsingFirebaseDB {
  async reverseGeolocation(location: ILocation) {
    const {latitude, longitude} = location;

    const adressCall = await fetch(
      `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&amp;language=ko&amp;key=${MAPS_API_KEY}`,
    );
    const adressJson = await adressCall.json();

    const addressArr = adressJson.results[0].formatted_address.split(&#39; &#39;);
    const locationAddress = `${addressArr[2]} ${addressArr[3]}`;

    return locationAddress;
  }
}</code></pre>
<h2 id="uselocation">useLocation</h2>
<pre><code class="language-javascript">import {useEffect, useState} from &#39;react&#39;;
import {MainUseCase} from &#39;../../domain/useCase/MainUseCase&#39;;
import {ILocation} from &#39;../interface/IGeolocation&#39;;

import Geolocation from &#39;@react-native-community/geolocation&#39;;
import {Alert} from &#39;react-native&#39;;

const mainLogic = new MainUseCase();
const {reverseGeolocation} = mainLogic;

export const useLocation = () =&gt; {
  const [location, setLocation] = useState&lt;ILocation | null&gt;(null);
  const [address, setAddress] = useState&lt;string&gt;(&#39;&#39;);

  useEffect(() =&gt; {
    if (location === null) {
      Geolocation.getCurrentPosition(
        position =&gt; {
          const {latitude, longitude} = position.coords;
          setLocation({latitude, longitude});
        },
        error =&gt; {
          if (error.code === 1) {
            Alert.alert(&#39;위치확인 승인이 필요합니다&#39;);
          } else if (error.code === 2) {
            Alert.alert(&#39;위치확인을 사용할 수 없습니다.&#39;);
          } else {
            Alert.alert(&#39;시간이 초과되었습니다.&#39;);
          }
          console.log(error.code, error.message);
        },
      );
    } else {
      reverseGeolocation(location).then(locationAddress =&gt; {
        setAddress(locationAddress);
      });
    }
  }, [location, address]);

  return [address];
};</code></pre>
<h2 id="mainpresenter">MainPresenter</h2>
<pre><code class="language-javascript">import React, {useEffect, useState} from &#39;react&#39;;
import {useLocation} from &#39;../../hooks/useLocation&#39;;
import {Main} from &#39;./Main&#39;;

export const MainPresenter = ({navigation}: any) =&gt; {
  const [address] = useLocation();

  useEffect(() =&gt; {}, []);
  return &lt;Main navigation={navigation} address={address} /&gt;;
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 10. RN & FB & dayjs : 대환장파티]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-10.-RN-FB-dayjs-%EB%8C%80%ED%99%98%EC%9E%A5%ED%8C%8C%ED%8B%B0</link>
            <guid>https://velog.io/@dongwu-kim/TIL-10.-RN-FB-dayjs-%EB%8C%80%ED%99%98%EC%9E%A5%ED%8C%8C%ED%8B%B0</guid>
            <pubDate>Wed, 15 Sep 2021 12:10:48 GMT</pubDate>
            <description><![CDATA[<h1 id="1-잡설">1. 잡설</h1>
<p>네. 반갑습니다.</p>
<p>본격적인 프로젝트의 시작입니다.</p>
<p>정말 즐거운데, 이게 참 즐겁다가 불행하다가 정말 모르겠습니다. 이제는 저도 모르겠네요.</p>
<p>뭐든 새로운 기술을 사용해보는건 참 흥미롭습니다.</p>
<p>뭐 때문에 막힐지 모르니까 매일이 크레바스 위를 걷는 기분인 것 같네요.</p>
<p>그래도 오늘 할당을 다 채웠다는 사실이 정말 기쁩니다.</p>
<blockquote>
<p><strong>오늘 일감</strong></p>
</blockquote>
<ul>
<li>Text 제한하기(min 20, max 300)</li>
<li>실시간으로 당일, 전일 데이터 관리하는 로직 작성하기<ul>
<li>00:00 기준 오늘 할일 =&gt; 어제 한 일 이동(DB load : on)</li>
<li>저장하기 (DB save : set) 버튼 구현</li>
<li>터치 시 수정기능 (TextArea)</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>완료사항</strong></p>
</blockquote>
<ul>
<li>Text 제한하기(min 20, max 300) ✔️</li>
<li>실시간으로 당일, 전일 데이터 관리하는 로직 작성하기 save &amp; onLoad로 해결? ✔️<ul>
<li>00:00 기준 오늘 할일 =&gt; 어제 한 일 이동(DB load : on) ✔️</li>
<li>저장하기 (DB save : set) 버튼 구현 ✔️</li>
</ul>
</li>
<li>터치 시 수정기능 (TextArea) ✔️</li>
<li>DB 설계 일부 변경하기 (task dir) ✔️</li>
<li>dayjs 로직 정리하기 ✔️</li>
</ul>
<p>돌아보니 이것저것 그래도 좀 했네요.</p>
<p>나름 즐거운 하루인건 맞았나봅니다.</p>
<h1 id="2-firebase-db-setting-errorandroid">2. firebase DB setting error(android)</h1>
<p>오늘은 신박한 DB 연동 문제가 있었습니다.</p>
<p>이상하게 어제까지만 해도 잘 되던게 오늘 갑자기 자고 일어나니 모르쇠로 일관하더라구요.</p>
<p>android는 참 알다가도 모르겠습니다.</p>
<p>꾸역꾸역 세팅을 처음부터 다시 설정하며, firebase project 설정을 다시 추적하던 중에 이유를 알 수 있었습니다.</p>
<ul>
<li><code>google-services.json</code>을 확인해보니,</li>
</ul>
<pre><code class="language-javascript">   &quot;project_info&quot;: {
    &quot;project_number&quot;: &quot;~&quot;,
      ---
    &quot;firebase_url&quot;: &quot;https://~&quot;, // firebase_url 이 녀석이 없었습니다..
      ---
    &quot;project_id&quot;: &quot;~&quot;,
    &quot;storage_bucket&quot;: &quot;~&quot;
  },</code></pre>
<p>저게 왜 없어졌는지 저는 정말 알다가도 모르겠더군요.</p>
<p>아마 초기세팅에서부터 빠져있었거나, 아니면 중간에 DB연동 이슈를 해결하려고 이리저리 적용하다 사라진게 아닐까 싶습니다.</p>
<p>firebase DB를 사용하는데 어려움이 있으신 분들은 이걸 보고 해결하시면 어떨까 싶기도 하네요.</p>
<ol>
<li><p>저것 말고도 해결과정에서 <code>android studio</code>에 직접 <code>google-services.json</code>을 드래그해서 refactor 할 경우 프로젝트가 박살나는 것도 하나의 이슈였습니다.</p>
<blockquote>
<p>당황하지 않고, 바로 clone 다시 만들기. local에서 작업파일 살려오기.</p>
</blockquote>
</li>
</ol>
<p>이것저것 원시적인 방법으로 돌아가지 않을 수 있었습니다.</p>
<p>때로는 일차원적인 방법이 가장 최고일지도 모르겠네요.</p>
<h1 id="3-dayjs">3. <a href="https://day.js.org/en/">dayjs</a></h1>
<p>이녀석 참 물건이었습니다.</p>
<p>제 벨로그를 보셨던 분들은 그동안 제가 <code>Date()</code>와 얼마나 많은 싸움을 했었는지 아실 수도 있겠습니다.</p>
<blockquote>
<p>1차 프로젝트에서의 countdown timer, 2차에서의 calendar... 3차는 DB와 Date?</p>
</blockquote>
<p>그냥 대충 <code>dayjs().~</code> 해버리면 뚝딱입니다. </p>
<p>물론 필요한 로직 정도는 짜야겠지만, 변환과정을 생략할 수 있다는건 축복인 것 같습니다.</p>
<p>그래서 이번 로직은 가독성이 상당히 좋습니다.</p>
<p>로직도 아니고, 그냥 뭐랄까요... 어... 좀 더 편하게 가져다 쓰기?</p>
<p>그 쯤 되는 것 같습니다.</p>
<p>필요하신 분들은 꼭 써보시길 바랍니다.</p>
<blockquote>
<p>2019년 기준 moment.js는 개발을 중단하기로 했습니다. 
또한 moment.js는 오래된 라이브러리임에 따라 안정성을 유지한 채 확장하기 어렵고, 사용 목적에 비해 큰 메모리를 차치하기에 요즘은 dayjs로 갈아타고 있다고 합니다.</p>
</blockquote>
<p>dayjs의 경우 지속적으로 개발하고 있고, locale도 100여개 정도를 지원한다고는 합니다.</p>
<p>이정도 프로젝트에서는 뭘 사용해도 상관없겠지만요.</p>
<pre><code class="language-javascript">import dayjs from &#39;dayjs&#39;;
import &#39;dayjs/locale/ko&#39;;

dayjs.locale(&#39;ko&#39;);

export const dayjsNow = () =&gt; {
  return dayjs().format(&#39;YYYY-MM-DD HH:mm:ss&#39;);
};

export const nowYear = () =&gt; {
  return dayjs().year();
};

export const nowMonth = () =&gt; {
  return dayjs().month() + 1;
};

export const nowDate = () =&gt; {
  return dayjs().date();
};

export const nowTime = () =&gt; {
  return `${dayjs().hour()}:${dayjs().minute()}:${dayjs().second()}`;
};

export const nowDateArray = () =&gt; {
  return [nowYear(), nowMonth(), nowDate()];
};

export const todayYearMonthDate = () =&gt; {
  return dayjs().format(&#39;YYYY-MM-DD&#39;);
};

export const yesterdayYearMonthDate = () =&gt; {
  const yesterday = `${nowYear()}-${nowMonth()}-${nowDate() - 1}`;
  return dayjs(yesterday).format(&#39;YYYY-MM-DD&#39;);
};

export const timeLag = (fromDayString: string, subDayString: string) =&gt; {
  const time = dayjs(subDayString).valueOf() - dayjs(fromDayString).valueOf();
  const hour = Math.floor(time / (60 * 60 * 1000));
  const minute = Math.floor((time / (60 * 1000)) % 60);
  const seconds = Math.floor((time / 1000) % 60);

  const workedTime = `${hour}:${
    minute &lt; 10 ? &#39;0&#39; + minute : minute
  }:${seconds}`;
  return workedTime;
};
</code></pre>
<p>이전 프로젝트에서는 year, month, day data를 따로 요청할 일이 없었는데, 이번 DB 특성상 따로 사용할 일이 있어 우선은 빼두었습니다.</p>
<p>셋 다 사용하게 될 경우에는 array method를 사용하는게 나을 것 같네요.</p>
<p>개인적으로 slice와 split을 좋아하지 않기도 하고, 최적화에 있어 유의미하지 않겠다는 판단이 들어 따로 두었습니다.</p>
<p>그래도 프로젝트 후반부에는 어떨지 모르겠습니다.</p>
<h1 id="4">4.</h1>
<p>그 이외에도 뭐...</p>
<pre><code class="language-javascript">import React, {useEffect, useState} from &#39;react&#39;;
import {WorkLogUseCase} from &#39;../../../domain/useCase/WorkLogUseCase&#39;;

import {WorkLog} from &#39;./WorkLog&#39;;

const workLogLogic = new WorkLogUseCase();
const {buttonDisableTest, getYesterdayWorkLog, getTodayWorkLog, saveWorkLog} =
  workLogLogic;

const loadWorkLog = (
  setYesterdayWorkLogText: React.Dispatch&lt;React.SetStateAction&lt;string&gt;&gt;,
  setTodayWorkLogText: React.Dispatch&lt;React.SetStateAction&lt;string&gt;&gt;,
) =&gt; {
  getYesterdayWorkLog().then(workLogObj =&gt; {
    if (workLogObj !== null) {
      setYesterdayWorkLogText(workLogObj.workLog);
    }
  });
  getTodayWorkLog().then(workLogObj =&gt; {
    if (workLogObj !== null) {
      setTodayWorkLogText(workLogObj.workLog);
    }
  });
};

export const WorkLogPresenter = () =&gt; {
  const [yesterdayWorkLogText, setYesterdayWorkLogText] = useState&lt;string&gt;(&#39;&#39;);
  const [todayWorkLogText, setTodayWorkLogText] = useState&lt;string&gt;(&#39;&#39;);
  const [saveOrInsert, setSaveOrInsert] = useState&lt;string&gt;(&#39;저장하기&#39;);
  const [saveButtonDisabled, setSaveButtonDisabled] = useState&lt;true | false&gt;(
    true,
  );
  const [insertCheck, setInsertCheck] = useState&lt;true | false&gt;(false);

  useEffect(() =&gt; {
    if (!insertCheck) {
      loadWorkLog(setYesterdayWorkLogText, setTodayWorkLogText);
    }
    if (yesterdayWorkLogText !== undefined &amp;&amp; todayWorkLogText !== undefined) {
      buttonDisableTest(yesterdayWorkLogText.length, todayWorkLogText.length)
        ? setSaveButtonDisabled(true)
        : setSaveButtonDisabled(false);
    }
  }, [yesterdayWorkLogText, todayWorkLogText, insertCheck]);

  return (
    &lt;WorkLog
      yesterdayWorkLogText={yesterdayWorkLogText}
      todayWorkLogText={todayWorkLogText}
      setYesterdayWorkLogText={setYesterdayWorkLogText}
      setTodayWorkLogText={setTodayWorkLogText}
      saveOrInsert={saveOrInsert}
      saveButtonDisabled={saveButtonDisabled}
      setInsertCheck={setInsertCheck}
      saveWorkLog={() =&gt; {
        saveWorkLog(yesterdayWorkLogText, todayWorkLogText);
      }}
    /&gt;
  );
};</code></pre>
<p>이런 녀석이나</p>
<pre><code class="language-javascript">import {WorkLogRepository} from &#39;../../data/repository/WorkLogRepository&#39;;

export class WorkLogUseCase extends WorkLogRepository {
  buttonDisableTest(yesterdayTextLength: number, todayTextLength: number) {
    if (
      yesterdayTextLength &lt; 20 ||
      todayTextLength &lt; 20 ||
      yesterdayTextLength &gt;= 300 ||
      todayTextLength &gt;= 300
    ) {
      return true;
    } else {
      return false;
    }
  }

  saveWorkLog(yesterdayWorkLogText: string, todayWorkLogText: string) {
    if (
      yesterdayWorkLogText.length &lt; 20 ||
      todayWorkLogText.length &lt; 20 ||
      yesterdayWorkLogText.length &gt;= 300 ||
      todayWorkLogText.length &gt;= 300
    ) {
      return;
    } else {
      return super.setWorkLogInDB(yesterdayWorkLogText, todayWorkLogText);
    }
  }
}</code></pre>
<p>이런 녀석,</p>
<pre><code class="language-javascript">import {UsingFirebaseDB} from &#39;./UsingFirebaseDB&#39;;
import {todayYearMonthDate, yesterdayYearMonthDate} from &#39;../../utils/dayjs&#39;;

import {IWorkLog} from &#39;../../presentation/interface/IWorkLog&#39;;
export class WorkLogRepository extends UsingFirebaseDB {
  async getYesterdayWorkLog(): Promise&lt;IWorkLog | null&gt; {
    try {
      const workLog = await super.getDataFromDB(
        `/uid/task/${yesterdayYearMonthDate()}`,
        &#39;value&#39;,
        snapshot =&gt; {
          return {...snapshot.val()};
        },
      );
      return workLog;
    } catch {
      return null;
    }
  }
  async getTodayWorkLog(): Promise&lt;IWorkLog | null&gt; {
    try {
      const workLog = await super.getDataFromDB(
        `/uid/task/${todayYearMonthDate()}`,
        &#39;value&#39;,
        snapshot =&gt; {
          return {...snapshot.val()};
        },
      );
      return workLog;
    } catch {
      return null;
    }
  }

  async setWorkLogInDB(yesterdayLogText: string, todayWorkLogText: string) {
    try {
      await super.setDataToDB(
        `/uid/task/${yesterdayYearMonthDate()}/workLog`,
        yesterdayLogText,
      );
      await super.setDataToDB(
        `/uid/task/${todayYearMonthDate()}/workLog`,
        todayWorkLogText,
      );
    } catch {
      console.log(&#39;DB update Error! : WorkLog&#39;);
    }
  }
}</code></pre>
<p>이런 녀석이 있긴 합니다.</p>
<p>load 함수를 useCase로 뺄까 말까 고민중인데, 아무래도 인자가 setState 함수에 해당하기 때문에 고민이 되는 것 같습니다.</p>
<p>그 이외에 디테일한 수정은 팀의 방향성에 맞춰 리팩토링 &amp; unit test 시간에 진행하기로 결정했습니다.</p>
<p>그럼 오늘도 모두 고생하셨습니다.</p>
<p>저는 내일 다른 글로 뵙겠습니다. 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 9. React-Native : Clean Architecture 적용기 -2-]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-9.-React-Native-Clean-Architecture-%EC%A0%81%EC%9A%A9%EA%B8%B0-2-</link>
            <guid>https://velog.io/@dongwu-kim/TIL-9.-React-Native-Clean-Architecture-%EC%A0%81%EC%9A%A9%EA%B8%B0-2-</guid>
            <pubDate>Mon, 13 Sep 2021 11:47:19 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>그림은 밥 삼촌의 <a href="https://www.youtube.com/watch?v=Nltqi7ODZTM">The Clean Architecture</a> 책에 나오는 다이어그램입니다.</p>
<p>오늘은 꼭 Clean architecture를 적용하겠다는 일념하에 하루종일 코드만 바라보고 있었습니다.</p>
<p>그런데 역시 마법은 일어나지 않았고, 큰 고뇌에 빠지기만 하나 싶던 와중에 가뭄에 단비같은 Office hour 시간이 찾아왔습니다.</p>
<p>무게를 가볍게 하는 것 또한 clean architecture의 본래 목적이었음을 잊었기에 고민이 깊어지지 않았나 생각이 듭니다.</p>
<p>이어서 코드로 보겠습니다.</p>
<h1 id="2">2.</h1>
<h2 id="repository">Repository</h2>
<p>먼저, repository의 형태입니다.</p>
<pre><code class="language-javascript">import database, {FirebaseDatabaseTypes} from &#39;@react-native-firebase/database&#39;;

type EventType = FirebaseDatabaseTypes.EventType;
type DataSnapshot = FirebaseDatabaseTypes.DataSnapshot;

export class UsingFirebaseDB {
  getDataFromDB(
    dir: string,
    eventType: EventType,
    successCallback: (a: DataSnapshot, b?: string | null) =&gt; any,
  ) {
    return database().ref(dir).once(eventType).then(successCallback);
  }

  setDataToDB(dir: string, value: any) {
    return database().ref(dir).set(value);
  }

  updateDataInDB(dir: string, value: any) {
    return database().ref(dir).update(value);
  }
}</code></pre>
<p>결과를 생각했을 때, 이러한 코드는 사실 respository 중에서도 베이스가 되는 파일이어야 할 것 같습니다.</p>
<ol>
<li><p>repository는 단순히 CRUD 메서드의 추상만 존재하는 것이 아니라 실질적인 접근이 이루어지는 로직을 다루어야 한다.</p>
</li>
<li><p>이어서 useCase에서도 나오겠지만, repository의 무게가 가벼워질수록 useCase에서 처리할 로직이 많아지고, 이는 본래 유연한 변경이 가능해야 한다는 조건과 다른 형태의 App이 된다.</p>
</li>
<li><p>후에 DB를 통째로 바꾸어야 하는 상황에서 어떻게 비교적 간단하게 처리할 수 있을지 고민하는 것으로 repository를 만들자.</p>
</li>
</ol>
<p>등의 조언을 office hour 시간 Q&amp;A를 통해 얻을 수 있었습니다.</p>
<p>이어서 useCase로 넘어가봅시다.</p>
<h2 id="usecase">useCase</h2>
<pre><code class="language-javascript">import {Alert} from &#39;react-native&#39;;

import {UsingFirebaseDB} from &#39;../repository/UsingFirebaseDB&#39;;
import {
  GoogleSignin,
  statusCodes,
} from &#39;@react-native-google-signin/google-signin&#39;;
import auth from &#39;@react-native-firebase/auth&#39;;

import {CLIENT_ID} from &#39;../../../env.json&#39;;
export class SignInUseCase extends UsingFirebaseDB {
  async getAccessUserEmailFromDB() {
    try {
      const email = await super.getDataFromDB(
        &#39;/access/email&#39;,
        &#39;value&#39;,
        snapshot =&gt; {
          return [...snapshot.val()];
        },
      );
      return email;
    } catch {
      return null;
    }
  }

  async signInGoogleAuth() {
    GoogleSignin.configure({
      webClientId: CLIENT_ID,
    });

    try {
      const userInfo = await GoogleSignin.signIn();
      const {idToken, user} = userInfo;
      const googleCredential = auth.GoogleAuthProvider.credential(idToken);
      await auth().signInWithCredential(googleCredential);
      return user;
    } catch (e) {
      if (e.code === statusCodes.SIGN_IN_CANCELLED) {
        console.log(&#39;유저가 로그인 취소&#39;);
      } else if (e.code === statusCodes.IN_PROGRESS) {
        console.log(&#39;로그인 진행 중&#39;);
      } else if (e.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
        console.log(&#39;플레이 서비스를 사용할 수 없음&#39;);
      } else {
        console.log(&#39;그외 에러&#39;);
      }
      Alert.alert(&#39;다시 로그인 해주세요&#39;);
      return {};
    }
  }
}</code></pre>
<p>useCase의 로직은 제가 작성한 <code>getAccessUserEmailFromDB</code>와 Steve가 작성한 <code>signInGoogleAuth</code>로 이루어져 있습니다.</p>
<p>singleton을 깊게 공부하지 않았기에 적용에 대해서는 아직 잘 모르겠습니다만, 최적화를 고려할 때 필요한 부분에 적용하자는 마인드입니다.</p>
<p>useCase의 문제는 다음과 같았습니다.</p>
<ol>
<li><p>repository에서 다룰 수 있는 메서드를 굳이 다루고 있다.</p>
</li>
<li><p>이어서 나오겠지만, 비즈니스 로직을 지나치게 보수적으로 정했다.</p>
</li>
</ol>
<p>정도가 Q&amp;A 시간에 머리에 떠올랐던 것 같습니다.</p>
<p>그럼 이어서 presenter &amp; view를 봅시다.</p>
<h2 id="presenter--view">Presenter &amp; View</h2>
<p>먼저, presenter입니다.</p>
<pre><code class="language-javascript">import React, {useEffect, useState} from &#39;react&#39;;

import {SignInUseCase} from &#39;../model/useCase/SignInUseCase&#39;;
import {SignIn} from &#39;../view/SignIn&#39;;

interface SignInUser {
  id: string;
  name: string | null;
  email: string;
  photo: string | null;
  familyName: string | null;
  givenName: string | null;
}

const signInLogic = new SignInUseCase();
const {getAccessUserEmailFromDB, signInGoogleAuth} = signInLogic;

const handleSocialSignIn = (
  setSignInUserInfo: React.Dispatch&lt;React.SetStateAction&lt;SignInUser | {}&gt;&gt;,
) =&gt; {
  signInGoogleAuth().then(userInfo =&gt; {
    setSignInUserInfo(userInfo);
  });
};

const loadAccessEmail = (
  setAccessUserEmail: React.Dispatch&lt;React.SetStateAction&lt;string[] | null&gt;&gt;,
) =&gt; {
  getAccessUserEmailFromDB().then(email =&gt; {
    setAccessUserEmail(email);
  });
};

const signInValidation = (
  signInUserInfo: SignInUser | {},
  accessUserEmail: string[] | null,
) =&gt; {
  if (signInUserInfo &amp;&amp; accessUserEmail?.includes(signInUserInfo.email)) {
    return true;
  } else {
    return false;
  }
};

export const SignInPresenter = ({navigation}: any) =&gt; {
  const {navigate} = navigation;

  const [accessUserEmail, setAccessUserEmail] = useState&lt;string[] | null&gt;([]);
  const [signInUserInfo, setSignInUserInfo] = useState&lt;SignInUser | {}&gt;({});

  useEffect(() =&gt; {
    if (accessUserEmail !== null &amp;&amp; accessUserEmail.length === 0) {
      loadAccessEmail(setAccessUserEmail);
    }
    if (signInValidation(signInUserInfo, accessUserEmail)) {
      navigate(&#39;Main&#39;);
    }
  }, [accessUserEmail, signInUserInfo]);

  return (
    &lt;SignIn
      handleSocialSignIn={() =&gt; {
        handleSocialSignIn(setSignInUserInfo);
      }}
    /&gt;
  );
};
</code></pre>
<ol>
<li>presenter의 경우 설계 목적과 달리 광범위한 로직을 포함하고 있습니다.</li>
</ol>
<p>제 생각에 useCase로 일부 이전해야 할 부분이 있는 것 같습니다.</p>
<p>예를 들자면, signInValidation, handleSocialSignIn, loadAccessEmail 등의 함수는 useCase에 작성되는게 맞는 것 같습니다.</p>
<p>그 외에는 딱히 없는 것 같으니 넘어가도록 하겠습니다.</p>
<p>이어서 view입니다.</p>
<pre><code class="language-javascript">import React from &#39;react&#39;;
import {StyleSheet} from &#39;react-native&#39;;
import {Image, VStack} from &#39;native-base&#39;;
import {GoogleSigninButton} from &#39;@react-native-google-signin/google-signin&#39;;

interface ISignInProps {
  handleSocialSignIn: () =&gt; void;
}

export const SignIn = ({handleSocialSignIn}: ISignInProps) =&gt; {
  const {googleButton} = style;

  return (
    &lt;VStack flex={1} bg=&quot;white&quot; justifyContent=&quot;center&quot; alignItems=&quot;center&quot;&gt;
      &lt;Image
        source={require(&#39;../../data/images/soomgo_logo_rgb.png&#39;)}
        alt=&quot;Logo&quot;
        w=&quot;70%&quot;
        resizeMode=&quot;contain&quot;
        marginBottom={75}
      /&gt;
      &lt;GoogleSigninButton
        style={googleButton}
        size={GoogleSigninButton.Size.Wide}
        color={GoogleSigninButton.Color.Light}
        onPress={handleSocialSignIn}
      /&gt;
    &lt;/VStack&gt;
  );
};

const style = StyleSheet.create({
  googleButton: {
    position: &#39;absolute&#39;,
    bottom: 90,
  },
});
</code></pre>
<p>네. view는 view 답네요.</p>
<p>딱히 문제는 없어보이지만, 굳이 문제라면 nativebase와 RN component를 섞어서 사용해야 한다는 점?</p>
<p>그런 부분이 문제가 아닐까 싶습니다.</p>
<h1 id="3복기">3.복기</h1>
<p>오늘의 문제</p>
<h2 id="repository-1">Repository</h2>
<ol>
<li>repository는 단순히 CRUD 메서드의 추상만 존재하는 것이 아니라 실질적인 접근이 이루어지는 로직을 다루어야 한다.</li>
</ol>
<blockquote>
<p>경로, 요청, CRUD를 혼합한 특정 기능에 요구되는 DB 요청 메서드를 만들자.</p>
</blockquote>
<ol start="2">
<li>repository의 무게가 가벼워질수록 useCase에서 처리할 로직이 많아지고, 이는 본래 유연한 변경이 가능해야 한다는 조건과 다른 형태의 App이 된다.</li>
</ol>
<blockquote>
<p>독립적인 형태로! 가볍게! DRY-KISS는 필수!</p>
</blockquote>
<ol start="3">
<li>DB를 통째로 바꾸어야 하는 상황이라면 어떻게 비교적 간단하게 처리할 수 있을지 고민하는 것으로 repository를 만들자.</li>
</ol>
<blockquote>
<p>코딩을 줄일 수 있다면 줄이는 것이 언제나 고려사항에 있어야한다.</p>
</blockquote>
<h2 id="usecase-1">UseCase</h2>
<ol>
<li>repository에서 다룰 수 있는 메서드를 굳이 다루고 있다.</li>
</ol>
<blockquote>
<p>애매모호함을 탈피할 수 있도록 기준을 세우자.</p>
</blockquote>
<ol start="2">
<li>비즈니스 로직을 지나치게 보수적으로 정했다.</li>
</ol>
<blockquote>
<p>사실 일단 모아두고 나중에 분배하는 방법도 나쁘지 않아보인다.</p>
</blockquote>
<h2 id="presenter">Presenter</h2>
<ol>
<li>presenter의 경우 설계 목적과 달리 광범위한 로직을 포함하고 있습니다.</li>
</ol>
<blockquote>
<p>이제 누가 useCase?</p>
</blockquote>
<h2 id="view">View</h2>
<ol>
<li>nativebase와 RN component(StyleSheet)를 섞어서 사용해야 한다.</li>
</ol>
<blockquote>
<p>좀 더 문서를 읽어본다면 해결할 수 있지 않을까~</p>
</blockquote>
<p>이상입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 8. Git & GitHub (feat. Firebase)]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-8.-Git-GitHub-feat.-Firebase</link>
            <guid>https://velog.io/@dongwu-kim/TIL-8.-Git-GitHub-feat.-Firebase</guid>
            <pubDate>Fri, 10 Sep 2021 13:55:55 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>지옥같은 하루였습니다.</p>
<p>생전 처음으로 레포를 싹 다 갈아엎고 다시 만들 일이 있을 줄은 몰랐네요.</p>
<p>이게 다~ 안일했던 저의 잘못이라는게 누굴 탓할 수 없어 가슴이 아픕니다.</p>
<p>먼저, 오늘의 교훈부터 읊고 시작하겠습니다.</p>
<ol>
<li><p><code>commit</code> 관리를 생활화하자.</p>
</li>
<li><p><code>rebase</code>는 선택이 아니라 필수다.</p>
</li>
<li><p><code>pull</code>은 신중하게 하자.</p>
</li>
<li><p>다시는 후회하지 말자.</p>
</li>
</ol>
<p>앞으로 다시는! 후회하지 않았으면 좋겠습니다.</p>
<blockquote>
<p>git 갈갈이를 벌써 3번은 한 것 같은데, 이제는 바닥도 없긴 합니다.</p>
</blockquote>
<p>아무튼 이런 저런 노가다 끝에 결과적으로는 모든 <code>commit</code>을 정리할 수 있었고, PR 이후 merge된 대부분의 내용을 건졌습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/3f9d40f2-7fdc-44b8-bdd7-f1b0a10923cc/image.png" alt=""></p>
<blockquote>
<p>아 ㅋㅋ </p>
</blockquote>
<p><img src="https://images.velog.io/images/dongwu-kim/post/40ebb55c-ecb7-45f8-9b0f-83c9f4d00f26/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/0df8714a-ec83-45f2-8a42-1b1e57856362/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/db79ff6c-6fde-4426-a468-380f9faadc99/image.png" alt=""></p>
<blockquote>
<p>캬~ 낭만있다... 그죠?</p>
</blockquote>
<p>흔적도 없는 <code>repo</code>를 보니? 이번 주말 상태가 어떨지 보이는 것 같습니다.</p>
<p>그래도 이제 <code>git</code>은 두렵지가 않습니다.</p>
<p>이렇게 성장한거겠죠...?</p>
<h1 id="2">2.</h1>
<p>여기서 꿀팁 몇 개 전달해드리자면,</p>
<ol>
<li><p><code>!reject?</code> 깝치지마. <code>push -f</code> 는 신이다.</p>
</li>
<li><p><code>merge</code>? 그것도 <code>rebase -i</code> 가 다 해준다.</p>
</li>
<li><p><code>commit</code> 정리? 그것도 <code>rebase -i HEAD~~</code> 가 다 해준다.</p>
</li>
<li><p>하위 branch에서 막무가내 <code>pull</code>? 그것도 <code>rebase</code>가 다 해결해준다. </p>
</li>
<li><p>git이 꼬여도 오늘 하루는 날렸다고 생각하고 차근차근 main부터 살려나가면 된다.</p>
</li>
</ol>
<p><code>commit</code> 복사가 된 상태다? 무지성 <code>rebase</code>.</p>
<p>무조건 git rebase로 다 정리하면 됩니다.</p>
<blockquote>
<p>과정은 지옥일 수 있습니다. 무자비한 conflict 뭐 그런거..</p>
</blockquote>
<p>가장 high level의 branch commit이 답도 없다면, 언제든 지저분한 commit이 재발할 수 있습니다.</p>
<p>여러분, 모두 rebase를 생활화 합시다.</p>
<p>rebase 알아도 코 베이는 무서운 gitHub 세상이니까요...</p>
<h1 id="3">3.</h1>
<p>Firebase DB 연동에서 android 친구를 좀 소홀히 했더니, 어김없이 android 친구가 DB 연동이 싫다며 거부하고 있습니다.</p>
<p>지 멋대로 method를 거부하고 있길래 오늘도 열심히 build.gradle을 뒤지며 빠진 부분을 체크했는데, 혹시 RN 유저분들 중 잊으신게 있다면 꼭 보고 해결하셨으면 좋겠습니다.</p>
<h2 id="-android--app--buildgradle">/ android / app / build.gradle</h2>
<ol>
<li>implementation 확인해보기</li>
</ol>
<pre><code class="language-gradle">dependencies {
    implementation platform(&#39;com.google.firebase:firebase-bom:28.4.0&#39;)
    implementation fileTree(dir: &quot;libs&quot;, include: [&quot;*.jar&quot;])
    //noinspection GradleDynamicVersion
    implementation &quot;com.facebook.react:react-native:+&quot;  // From node_modules
    implementation &quot;androidx.swiperefreshlayout:swiperefreshlayout:1.0.0&quot;

    implementation &#39;com.google.firebase:firebase-analytics&#39;
    }</code></pre>
<p>저 중에 가장 중요한건 platform, firebase-bom과 com.google.firebase~analytics 부분입니다.</p>
<p>둘 중 하나만 없어도 사실상 firebase 왜 쓰냐? 이런 말이 나올 수 있습니다.</p>
<ol start="2">
<li>apply 확인해보기</li>
</ol>
<pre><code class="language-gradle">apply plugin: &#39;com.google.gms.google-services&#39;</code></pre>
<p>위 플러그인 apply 안했으면 사실상 팥 없는 호빵 상태입니다.</p>
<p>DB연동이 되든 아니든 저거 안넣으면 사실상 firebase - android &amp; ios 가 아닌 firebase - ios 입니다.</p>
<blockquote>
<p>아~ 안드로이드 말고 ios가 힙하다고~</p>
</blockquote>
<p>인정하지만, 그래도 둘 다 개발해야죠 ... 🥲🥲</p>
<h1 id="4">4.</h1>
<p>그런데 왜 저는 위 두 사항을 뒤늦게 적용해도 android 친구가 DB를 열어주지 않을까 싶기도 합니다.</p>
<p>네. 그 문제는 다음 글에서 꼭! 해결하겠습니다.</p>
<p>실패하면 초기세팅부터 다시 시작하겠습니다.</p>
<p>결국 해결한다는 말이 되겠네요! </p>
<p>그럼 오늘도 읽어주셔서 감사합니다! 모두 화이팅!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 7. 기업협업 프로젝트 시작 && Firebase RealTimeDataBase : NoSQL ]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-7.-%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91-Firebase-RealTimeDataBase-NoSQL</link>
            <guid>https://velog.io/@dongwu-kim/TIL-7.-%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91-Firebase-RealTimeDataBase-NoSQL</guid>
            <pubDate>Thu, 09 Sep 2021 11:39:25 GMT</pubDate>
            <description><![CDATA[<h1 id="1">1.</h1>
<p>프로젝트의 시작인 하루입니다.</p>
<p>이것저것 참 많은 일들이 있었지만, 오늘은 하루종일 꽂혀 있던 <code>Firebase realTimeDataBase</code>의 설계, <code>No SQL - document</code>에 대한 내용으로 작성할까 합니다.</p>
<p>기존 <code>RDB</code>와는 좀 달리 생각해야 하는데, 마냥 쉽지 않아서 좀 난항을 겪고 있습니다.</p>
<p>또한 <code>firebase</code>의 경우 쿼리문을 통한 접근을 따로 지원하지 않고, 다양한 메소드를 통해 <code>DB</code> 자료를 검색할 수 있습니다.</p>
<ul>
<li><p>관계가 정의된 상태가 아닐 때 <code>data</code>를 어떻게 저장해야 할까?</p>
</li>
<li><p>저장된 데이터를 어떻게 메소드를 덜 사용하면서 효율적으로 가져올 수 있을까?</p>
</li>
<li><p><code>Date</code> 데이터를 보관한다면 <code>key</code>를 어떻게 설정하는게 좋을까?</p>
</li>
</ul>
<p>이런저런 고민이 참 많았습니다.</p>
<p>숙소 예약을 담당했던 전 프로젝트 팀원에게 조언을 구할까 했지만, 한 번 혼자 힘으로 첫 <code>DB</code> 설계를 시작해보고자 합니다.</p>
<h1 id="2">2.</h1>
<p>우선 <code>No SQL</code>은 정말 말 그대로 설계하기 나름인 것 같습니다.</p>
<p>스키마도 존재하지 않고, 그렇기에 <code>PK, FK</code> 등과 같은 제약도 없습니다.</p>
<p>따라서 확장하고 싶으면 마음껏 하는게 맞지만, 최적화는 다른 문제입니다.</p>
<p><code>firebase</code>의 경우 <code>document, JSON</code> 형태의 <code>DB</code> 구조입니다.</p>
<p>원하는 결과를 위해 하나의 특정 key로 접근하는 구조로 만들어진 것이죠.</p>
<p>&quot;저장해야 할 대부분의 데이터는 <code>Date String</code>이기에 이를 년, 월, 주 단위로 최적화하기 위해 데이터를 옮기는 방법은 어떨까?&quot; 가 바로 첫번째 생각이었습니다.</p>
<p>그러나 해당 방법은 각 key의 value가 서로 의존성을 갖게 됩니다.</p>
<p>예를 들어, </p>
<ol>
<li><p>일별 데이터를 주(key)에 담는다.</p>
</li>
<li><p>월요일에는 주를 초기화하고, 특정 월(9)에 주 데이터를 담는다.</p>
</li>
<li><p>다음 월로 넘어가는 10.01 00:00에 월을 reset하고, 특정 년에 월 데이터를 담는다.</p>
</li>
</ol>
<p>가 되는데, 이렇게 될 경우 너무 번거롭고 지나치게 많은 로직을 발생시키는 것 같았습니다.</p>
<p>따라서 해당 방법은 가차없이 폐기했습니다.</p>
<p>예상 구조는</p>
<pre><code class="language-javascript">{
  userId : { 
    userInfo : {...},
    year : {&quot;1&quot; : [...], ..., },
    month : {&quot;1st&quot; : [...], ..., },
    week : [{...}],
    day : {start : &quot;&quot;, end : &quot;&quot;, time : number},               
  },

  userId : {...},
}</code></pre>
<p>이런 느낌이었는데, 모르니까 참 용감한 것 같습니다.</p>
<p>코드로 써보니까 정말 택도 없네요.</p>
<h1 id="3">3.</h1>
<p>다음 방법은 그냥 틀을 빡세게 잡고 가는 방법입니다.</p>
<pre><code class="language-javascript">{
  userId : {
    userInfo : {...},
    &quot;2021&quot; : { 
      &quot;1&quot; : {
        &quot;1st&quot;: {
          &quot;Mon&quot; : {
            &quot;start&quot; : Date, 
              &quot;end&quot; : Date, 
              &quot;time&quot; : number,
          },
          ...
        },
        ...
      },
      ...
    },
    &quot;2022&quot; : {...}
  }
}</code></pre>
<p>이런 느낌이 되겠네요.</p>
<p>그리고 이걸 한 번 더 묶어버리면, </p>
<pre><code class="language-javascript">{
  userId : {
    userInfo : {...},
    commuteData : {
      &quot;2021&quot; : { 
        &quot;1&quot; : {
          &quot;1st&quot;: {
            &quot;Mon&quot; : {
              &quot;start&quot; : Date, 
                &quot;end&quot; : Date, 
                &quot;time&quot; : number,
            },
            ...
          },
          ...
        },
        ...
      },
      &quot;2022&quot; : {...}
    }
  }
}</code></pre>
<p>가 되는 것이 아닐까 생각합니다.</p>
<p>그럼 년, 월, 주, 요일로 각각 따로 접근할 수 있기는 합니다.</p>
<p>firebase method는 하나의 dir 경로로 접근하는 것과 같은 방식으로 데이터에 접근하게끔 구성되어 있습니다.</p>
<p>그런데 사실 아직 이게 개발 단계에서 옳은 방법인가에 대해 검증하지는 못한 것 같습니다.</p>
<p>이것저것 No SQL에 대해 공부하고 있는데, 어떤게 효율적일지 잘 모르겠습니다.</p>
<p>저는 아직까지는 이렇게 구성하는게 좋을 것 같다는 생각이 고정적인 것 같습니다.</p>
<p>접근에 용이하고, 로직에서 활용도가 높은 구조일 것 같아서 그런 것 같습니다.</p>
<p>무지의 늪에 빠져 도전하고 깨져봐야 아나봅니다...</p>
<p>그럼 오늘도 깨져보러 가겠습니다! 화이팅!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 6. React-Native : NativeBase (+ cleanArchitecture)]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-6.-React-Native-NativeBase-cleanArchitecture</link>
            <guid>https://velog.io/@dongwu-kim/TIL-6.-React-Native-NativeBase-cleanArchitecture</guid>
            <pubDate>Wed, 08 Sep 2021 10:54:39 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/dongwu-kim/post/be0bec97-e114-4bba-be14-253fa9f18618/image.png" alt=""></p>
<h1 id="1">1.</h1>
<p>오늘은 정말 새로운 주제입니다.</p>
<p>RN을 사용하는 사람들에게 재밌는 lib 일 것 같습니다.</p>
<p>기존 RN의 <code>StyleSheet</code>와는 달리 인라인으로 <code>style props</code>를 작성할 수 있고, 다양한 기능들을 지원하고 있는 <a href="https://nativebase.io/">nativebase</a> 입니다.</p>
<p>처음 봤을 때는 부트스트랩인가? 싶었는데, 비슷하긴 한 것 같습니다.</p>
<p>다만, 조금 더 유저의 노력이 들어갑니다.</p>
<p>그래도 디자인에 대한 부담을 줄일 수 있으니 관심있으신 분들은 사용해보시면 좋을 것 같습니다.</p>
<blockquote>
<p>각종 토이 프로젝트에서 사용하기 좋은 느낌입니다.</p>
</blockquote>
<h1 id="2">2.</h1>
<p>그래서 오늘은 <code>nativebase</code>로 <code>refactoring</code>을 좀 해봤습니다.</p>
<p><img src="https://images.velog.io/images/dongwu-kim/post/bf9e9a85-0202-48da-83fc-b5dac2b7cc82/image.png" alt=""></p>
<p>여전히 좀 구리긴 합니다만, 그래도 나름의 스타일이 들어간 것 같아서 기분은 좋았습니다.</p>
<blockquote>
<p>디자인 멈춰!</p>
</blockquote>
<p>다양한 <code>style hooks</code>를 지원하고 있기에 버튼을 눌렀을 때 전체 배경색의 변경 등의 기능도 가능합니다.</p>
<p>다음 프로젝트는 아마도 nativebase를 애용하지 않을까 싶네요.</p>
<h1 id="3">3.</h1>
<p>지난주부터 계속 고민하던 바로 그 주제입니다.</p>
<p><a href="https://medium.com/@justfaceit/clean-architecture%EB%8A%94-%EB%AA%A8%EB%B0%94%EC%9D%BC-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%84%EC%99%80%EC%A3%BC%EB%8A%94%EA%B0%80-1-%EA%B2%BD%EA%B3%84%EC%84%A0-%EA%B3%84%EC%B8%B5%EC%9D%84-%EC%A0%95%EC%9D%98%ED%95%B4%EC%A4%80%EB%8B%A4-b77496744616">Clean Architecture</a></p>
<p>해당 개념을 읽고, 공부했을 때는 분명 아~ 그런가보다 수준이었는데 막상 프로젝트에 적용하려니 각종 로직들을 수정하는 것은 물론 class와 추상에 대한 공부를 다시 하는게 맞지 않을까 생각이 들었습니다.</p>
<blockquote>
<p>그래서 현재는 개념 학습을 위해 다양한 function 으로 내부가 이루어져 있는 구조입니다.</p>
</blockquote>
<p>먼저, 글을 열심히 읽어본 결과, 현재 To Do List 의 경우 firebase를 사용하고 있기 때문에 <code>data layer</code>를 따로 고려하지 않아도 될 것 같다고 생각했습니다.</p>
<p>그러나 DB 접근 <code>method class</code>를 선언해두었기에 <code>Repository</code> 개념 정도는 가지고 갈 수 있었습니다.</p>
<p><code>useCase</code>에 존재해야 하는 로직은 어떤 형태일지도 고민이 되었고, <code>useCase</code> 비즈니스 로직이 어떤 기능들을 수행하는지도 고민이 되었던 것 같습니다.</p>
<p>그리고 무엇보다도 <code>View</code>와 <code>Presenter</code>를 분리하는 일도 쉽지 않았습니다.</p>
<p>차근차근 해결해나간 결과, 나름의 기준을 정해보았습니다.</p>
<h2 id="31">3.1</h2>
<p><strong>계층간섭이 가능한 개념은 계층 내에서 단 하나만 존재하기로 약속하자.</strong></p>
<p>저는 위 글과 같이 <code>presenter</code> - <code>useCase</code> - <code>repository</code> 셋을 선택했습니다.</p>
<p><code>presenter</code>와 <code>repository</code>는 서로 접점이 없어 각 계층에서 어떤 일이 벌어지는지 알 수 없도록 구현했습니다.</p>
<p>: <code>presenter</code>는 <code>respository</code>에 접근할 수 없고, 반대의 경우도 마찬가지라고 생각했습니다.</p>
<p>가장 고민이 되었던 것은 <code>useCase</code>입니다. </p>
<p><code>useCase</code>의 경우 양방향으로 소통할 수 있는 구조인 것으로 보이는데, 현실적으로는 <code>repository</code>에서 받아온 <code>DB method</code>를 생성해서 실제 로직에 사용하는 식으로 구현되었습니다.</p>
<h2 id="32">3.2</h2>
<p><strong>약속된 기능만을 수행할 수 있도록 작성하자.</strong></p>
<p><code>useCase</code>에서는 repository의 DB method를 내부에 생성했기 때문에 DB 접근이 가능합니다.</p>
<p>그러나 <code>presenter</code>는 DB에 접근하는 로직을 내부에 보유하지 않아 접근할 수 없습니다.</p>
<p><code>presenter</code>의 경우 해당 프로젝트에서는 <code>handle~</code> 등과 같은 <code>View control</code>이 가능합니다.</p>
<p><strong>그러나 DB에 접근하기 위해서는 View에 특정 메소드를 호출해야 합니다.</strong></p>
<p>분명 View와 useCase는 접점이 존재하지 않는데, 어떻게 작성해야 할지 고민이 많았던 것 같습니다.</p>
<p>signIn 기능이 대표적인 예시인데, </p>
<p>signIn의 <code>presenter</code>를 제 멋대로 작성해본 것을 코드로 올려보겠습니다.</p>
<pre><code class="language-javascript">import auth from &#39;@react-native-firebase/auth&#39;;
import {checkUserInfoInDB} from &#39;../../../domain/SignIn/usecase/authLogic&#39;;

...

export const signInCheck = (signInInfo: any): void =&gt; {
  const userId = auth().currentUser?.uid;

  checkUserInfoInDB(`users/${userId}/user`, signInInfo);
};</code></pre>
<p>극단적이긴 한데, <code>signInCheck()</code> 이라는 하나의 함수를 정의해두고, 내부에서 <code>userInfo</code>가 DB에 존재하는지 확인하는 함수를 호출합니다.</p>
<p><code>presenter</code>에서는 약속대로 DB에 접근하지 않지만, 현재 로그인한 유저가 맞는지 auth 메소드를 통해 점검하고 있습니다.</p>
<p>명확하게 분리하기 위해서는 사실 <code>useCase</code>에 <code>auth()</code>가 존재해야 하는데, 해당 부분을 어떻게 옮기는게 맞을지도 지속적으로 고민하고 있습니다.</p>
<blockquote>
<p>극히 작은 규모의 프로젝트에서 어거지로 <code>presenter</code>와 <code>useCase</code>를 분리하려다 보니 이런 고민이 생기는 것 같습니다.</p>
</blockquote>
<p>앞으로는 cleanArchitecture 이전에, class로 어떻게 계층 내부에서 인터페이스화했는지를 더 공부해야 할 것 같습니다.</p>
<p>그래서 요즘은 java 코드를 더 많이 보게 되는 것 같습니다.</p>
<h2 id="33">3.3</h2>
<p>해당 개념에 대해 조악하게나마 적용해보니 유일하게 느낀 점이 있었습니다.</p>
<p>유지보수에 무조건 유리할 수 밖에 없겠다! 하는 점입니다.</p>
<p>어떤 부분이 문제가 생기면 어딜 참고하면 되는지 바로 알 수 있고,</p>
<blockquote>
<p>예를 들면 DB에서 유저정보를 불러오지 못하는 것 같아요! 
=&gt; presenter 내 경로, useCase 내 DB 로직 점검</p>
</blockquote>
<p>그러나 아직도 두 계층을 확인해야 한다는 점에서 제 계층분리가 잘못되었다는 것을 알 수 있는 것 같습니다.</p>
<p>하나로 줄일 수 있는 방법을 늘 고민하는데, 아직은 쉽지 않은 것 같습니다....</p>
<p>또한 로직의 길이가 짧아지고, KISS 원칙을 잘 지킬 수 있게 되는 것 같았습니다.</p>
<p>하나의 함수 내부 로직을 고민하고, 또 고민하기 때문입니다.</p>
<p>그렇기에 좋은 도전이었다고 생각합니다.</p>
<h1 id="4">4.</h1>
<p>주절주절 참 많이도 썼는데, 사실 잘 몰라서 그렇습니다.</p>
<p>시행착오를 수도 없이 겪었는데, 머리 속에서 수도 없이 고민했기에 글로 적는 것이 마냥 쉽지가 않네요.</p>
<p>그럼에도 더 무언가를 공부하고, 도전하는 기분이 들어 좋았던 것 같습니다.</p>
<p>해당 주제를 다시 가지고 온다면 그 때는 누구에게나 자랑하듯 보여줄만한 코드들과 함께 왔으면 좋겠습니다.</p>
<p>그러기 위해 더 열심히 살아야겠네요.</p>
<p>그럼 이번 글은 여기서 마치겠습니다. 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[(TIL) 5. React-Native & FireBase : DB연동 & logic refactoring]]></title>
            <link>https://velog.io/@dongwu-kim/TIL-5.-React-Native-FireBase-DB%EC%97%B0%EB%8F%99-logic-refactoring</link>
            <guid>https://velog.io/@dongwu-kim/TIL-5.-React-Native-FireBase-DB%EC%97%B0%EB%8F%99-logic-refactoring</guid>
            <pubDate>Mon, 06 Sep 2021 14:02:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://images.velog.io/images/dongwu-kim/post/36bf9fd5-09f7-4ec7-8ac1-513fc232e8da/Simulator-Screen-Recording-iPhone-12-2021-09-06-at-17.44.44.gif" alt=""></p>
<h1 id="1">1.</h1>
<p>드디어 ToDoList 항목까지 DB와 연동할 수 있었습니다.</p>
<p>현재 DB에 저장되는 정보는 사용자의 google 계정 info, todos가 있습니다.</p>
<p>app의 개별 데이터는 사용자 id를 title로 갖는 독립적인 테이블의 형태로 저장됩니다.</p>
<p>그렇기에 app 사용자 사이에는 절대 영향을 끼치지 않습니다.</p>
<p>중앙에 실시간으로 반영되는 app별 DB가 완성된 것으로 생각이 듭니다.</p>
<h1 id="2">2.</h1>
<p>무엇보다도 신기했던 점은 noSQL DB를 직접 사용해보니 RDB에 비해 확장성이 좋을 수 밖에 없다는 것이 느껴진다는 점입니다.</p>
<p>그냥 원하는 데이터를 원하는 키워드로 큰 고민 없이 편하게 확장할 수 있었습니다.</p>
<p>각 테이블 사이에 관계가 없기 때문에 독립적으로 구성되고, 각각의 테이블에서 원하는 정보들을 조합해 하나의 app service를 구성할 수 있습니다.</p>
<p>그래서 1, 2차 프로젝트에 비해 3차 프로젝트의 데이터 구조는 생각보다 덜 복잡하지 않을까 생각했습니다.</p>
<p>기껏해야 2가지 정보만을 담고 있는 ToDoList app이지만 어서 큰 프로젝트를 만들어보고 싶어지네요.</p>
<h1 id="3">3.</h1>
<p>드디어 class 형태로 다양한 메서드들을 정의했습니다.</p>
<p>사용자를 고려하는 마음으로 만들다보니 module 내 type file을 다시 import 하는 경우도 있었습니다.</p>
<p>어떤 점이 문제인지는 명확히 모르겠으나, lib 내에서 정의한 type, interface를 굳이 한 번 더 사용하는 느낌이라 석연치 않았던 점은 있었습니다.</p>
<p>그래도 나름 메서드를 만들어두고 사용하다보니 상당히 편한 점이 많았습니다.</p>
<p>login은 한 번 사용하기에 몰랐지만, DB 관련 메서드들은 정말 편했습니다.</p>
<h2 id="31">3.1</h2>
<p>database.ts</p>
<pre><code class="language-javascript">import database from &#39;@react-native-firebase/database&#39;;
import {FirebaseDatabaseTypes} from &#39;@react-native-firebase/database/lib&#39;;

type Snapshot = FirebaseDatabaseTypes.DataSnapshot;

export const useDb = class {
  dir: string;

  constructor(dir: string) {
    this.dir = dir;
  }

  on(
    eventType:
      | &#39;value&#39;
      | &#39;child_added&#39;
      | &#39;child_changed&#39;
      | &#39;child_moved&#39;
      | &#39;child_removed&#39;,
    callback: (data: Snapshot, previousChildKey?: string | null) =&gt; void,
  ): void {
    database().ref(this.dir).on(eventType, callback);
  }

  set(value: any, onComplete?: (error: Error | null) =&gt; void): void {
    if (onComplete) {
      database()
        .ref(this.dir)
        .set(value)
        .catch(res =&gt; {
          onComplete(res);
          return;
        });
    }

    database().ref(this.dir).set(value);
  }

  update(
    value: {[key: string]: any},
    onComplete?: (error: Error | null) =&gt; void,
  ): void {
    if (onComplete) {
      database()
        .ref(this.dir)
        .update(value)
        .catch(res =&gt; {
          onComplete(res);
          return;
        });
    }

    database().ref(this.dir).update(value);
  }

  remove(): void {
    // 사용할 때 신중하게 생각해주세요. DB 테이블을 통째로 날리는 메소드입니다.
    database().ref(this.dir).remove();
  }

  push(value: any): void {
    database().ref(this.dir).push(value);
  }
};</code></pre>
<p>별거 없어 보이지만, 생각보다 정말 편했습니다.</p>
<h2 id="32">3.2</h2>
<p>googleSignIn.ts</p>
<pre><code class="language-javascript">import {GoogleSignin} from &#39;@react-native-google-signin/google-signin&#39;;
import auth from &#39;@react-native-firebase/auth&#39;;

import {WEB_CLIENT_ID} from &#39;../config&#39;;

export interface GoogleUserInfo {
  idToken: string;
  serverAuthCode: string;
  scopes: string[];
  user: {
    email: string;
    id: string;
    givenName: string;
    familyName: string;
    photo: string;
    name: string;
  };
}

export class GoogleSignInMethod {
  googleSignIn(setUser: Function): void {
    GoogleSignin.configure({
      webClientId: WEB_CLIENT_ID,
    });

    const signIn = async () =&gt; {
      try {
        const userInfo = await GoogleSignin.signIn();
        const {idToken} = userInfo;
        const googleCredential = auth.GoogleAuthProvider.credential(idToken);

        await auth().signInWithCredential(googleCredential);
        await setUser(userInfo);
      } catch (error) {
        console.log(error);
      }
    };

    signIn();
  }

  googleSignOut(setUser: Function): void {
    GoogleSignin.configure({
      webClientId: WEB_CLIENT_ID,
    });

    const signOut = async () =&gt; {
      try {
        await GoogleSignin.signOut().then(() =&gt; {
          auth().signOut();
        });
        await setUser(null);
      } catch (error) {
        console.log(error);
      }
    };

    signOut();
  }
}</code></pre>
<p>기존 signUp 과정이 있었던 것을 어떻게 처리할까 고민하다가 그냥 과감히 삭제했습니다.</p>
<p>생각해보니 DB에 저장할지 말지를 결정하면 되는 문제였기 때문에 class 내부에서 해당 관련 내용을 포함하지 않는 것이 KISS 원칙을 준수하는 것 같았습니다.</p>
<p>따라서 최초 로그인 당시의 DB 저장은 해당 클래스 내부에서 판단하는 것이 아닌, 버튼이 눌린 시점의 view에서 판단해야겠다고 생각했습니다.</p>
<h2 id="33">3.3</h2>
<p>이에 Login.tsx 파일의 내부도 변경되었습니다.</p>
<pre><code class="language-javascript">import React, {useState, useEffect} from &#39;react&#39;;
import {
  StyleSheet,
  SafeAreaView,
  View,
  Text,
  TouchableOpacity,
} from &#39;react-native&#39;;
import {GoogleSigninButton} from &#39;@react-native-google-signin/google-signin&#39;;
import {GoogleUserInfo, GoogleSignInMethod} from &#39;../../utils/googleSignin&#39;;
import auth from &#39;@react-native-firebase/auth&#39;;
import {useDb} from &#39;../../utils/database&#39;;

const checkUser = (signInInfo: any): void =&gt; {
  const userId = auth().currentUser?.uid;

  const dbUserInfo = new useDb(`/users/${userId}`);

  dbUserInfo.on(&#39;value&#39;, snapshot =&gt; {
    if (!snapshot.val()?.googleInfo &amp;&amp; signInInfo) {
      dbUserInfo.set({googleInfo: {...signInInfo.user}});
      return;
    }
  });
};

export const SignIn = ({navigation}: any) =&gt; {
  const gogoleSignInMethod = new GoogleSignInMethod();
  const {googleSignIn, googleSignOut} = gogoleSignInMethod;

  const [signInInfo, setSignInInfo] = useState&lt;GoogleUserInfo | null&gt;(null);

  const {navigate}: any = navigation;

  useEffect(() =&gt; {
    if (signInInfo) {
      checkUser(signInInfo);
    }
  }, [signInInfo]);

  const {signInContainer, signInBlock, signOutText} = styles;

  return (
    &lt;SafeAreaView style={signInContainer}&gt;
      &lt;View style={signInBlock}&gt;
        {signInInfo &amp;&amp; (
          &lt;&gt;
            &lt;TouchableOpacity
              onPress={() =&gt; {
                navigate(&#39;ToDoList&#39;);
              }}&gt;
              &lt;Text style={signOutText}&gt;Go ToDoList&lt;/Text&gt;
            &lt;/TouchableOpacity&gt;
            &lt;TouchableOpacity
              onPress={() =&gt; {
                googleSignOut(setSignInInfo);
              }}&gt;
              &lt;Text style={signOutText}&gt;Sign Out&lt;/Text&gt;
            &lt;/TouchableOpacity&gt;
          &lt;/&gt;
        )}
        {!signInInfo &amp;&amp; (
          &lt;GoogleSigninButton
            size={GoogleSigninButton.Size.Wide}
            color={GoogleSigninButton.Color.Dark}
            onPress={() =&gt; {
              googleSignIn(setSignInInfo);
            }}
          /&gt;
        )}
      &lt;/View&gt;
    &lt;/SafeAreaView&gt;
  );
};</code></pre>
<p>막상 보니 코드가 좀 길긴 하네요.</p>
<p>그래도 최소한의 기능과 로직만을 포함했다고 생각합니다.</p>
<p>다음엔 어딜 더 줄일지 고민해야겠습니다.</p>
<h1 id="4">4.</h1>
<p>생각보다 이것저것 한건 없는데 빨리 하루가 지나갔습니다.</p>
<p>로직을 재구성하기 위해 나름 그림(?)도 하루종일 그리고, 주말 사이에 읽었던 자료들을 떠올리며 어떻게 구성하는게 맞는지 고민하는데 시간을 많이 쓴 것 같습니다.</p>
<p>안타깝게도 프로젝트의 규모가 상당히 작아 주말 사이에 공부했던 개념을 적용시키는 것은 배보다 배꼽이 더 큰 느낌이기에 포기했습니다.</p>
<p>그래도 조금더 고민해보았던 것은 google 말고 다양한 방식의 social Auth를 적용하기 위해 하나의 SignIn class를 만들어 상속을 활용한 틀을 만들어둘까 했던 점입니다.</p>
<p>다음 프로젝트에는 꼭 반영해봐야겠습니다.</p>
<p>이상으로 ToDoList는 마치도록 하겠습니다.</p>
<p>내일은 RN의 다양한 기능, geolocation 등을 ToDoList에 집어넣어볼까 합니다.</p>
<blockquote>
<p>혼종 예약입니다.</p>
</blockquote>
<p>그럼 글은 이만 마치도록 하겠습니다. 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
    </channel>
</rss>