<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>개인적인 기록</title>
        <link>https://velog.io/</link>
        <description>개인적인 기록.</description>
        <lastBuildDate>Sat, 21 Jan 2023 13:14:23 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>개인적인 기록</title>
            <url>https://images.velog.io/images/choosey_/profile/fbebd59a-e22a-48cc-9596-cd8f3db826c2/KakaoTalk_Photo_2021-06-18-14-01-43.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 개인적인 기록. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/choosey_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Swift - 기초 2]]></title>
            <link>https://velog.io/@choosey_/Swift2</link>
            <guid>https://velog.io/@choosey_/Swift2</guid>
            <pubDate>Sat, 21 Jan 2023 13:14:23 GMT</pubDate>
            <description><![CDATA[<h1 id="dispatchqueue-async-sync-serial-concurrent">DispatchQueue (async, sync, serial, concurrent)</h1>
<hr>
<p>자바스크립트와는 다르게 멀티스레드가 지원이 된다. </p>
<p><strong>serial, concurrent, sync, async</strong>라는 개념이 있는데 
이에 대해 알아보자.</p>
<h2 id="serial">Serial</h2>
<ul>
<li>일반적인 코드 default 동작</li>
<li>하나의 작업이 완료된 후 다음작업을 진행한다. </li>
<li>스레드가 작업이 완료될떄까지 thread내의 다른작업은 멈춘 것처럼 보인다.</li>
<li>메인스레드가 serial로 동작 (UI, 일반적으로 작성된 코드)</li>
</ul>
<h2 id="concurrent">Concurrent</h2>
<ul>
<li>여러개 작업을 동시에 실행한다.(하나의 작업자 기준)</li>
</ul>
<pre><code class="language-swift">let queue = DispatchQueue(label: &quot;queuename&quot;, attributes: .concurrent)
// queue를 싱크로 사용하면 main 스레드 일시 정지

    queue.async {
        Thread.sleep(forTimeInterval: 2)
        print(&quot;finish1&quot;)
    }
    queue.async {
        Thread.sleep(forTimeInterval: 2)
        print(&quot;finish2&quot;)
    }
    queue.async {
        Thread.sleep(forTimeInterval: 2)
        print(&quot;finish3&quot;)
    }</code></pre>
<h2 id="async">async</h2>
<ul>
<li>여러 작업자가 동시에 작업 실행.</li>
<li>UI 관련된 작업은 Main thread에서 담당하기 떄문에 변경 불가</li>
</ul>
<pre><code class="language-swift">//동시 진행
DispatchQueue.global().async {
    Thread.sleep(forTimeInterval: 1.5)
    print(&quot;finish1)
}

DispatchQueue.global().async {
    Thread.sleep(forTimeInterval: 3)
    print(&quot;finish2)
}

//메인스레드 async는 serial로 진행
DispatchQueue.main.async {
    Thread.sleep(forTimeInterval: 1.5)
    print(&quot;finish1)
}

DispatchQueue.main.async {
    Thread.sleep(forTimeInterval: 1.5)
    print(&quot;finish1)
}

//이건 불가능

DispatchQueue.main.sync {

}

//이건 가능
DispatchQueue.global().async {
    DispatchQueue.main.sync {

    }
}</code></pre>
<h2 id="sync">sync</h2>
<ul>
<li>여러 작업자가 하나의 작업씩 순차적으로 실행</li>
</ul>
<pre><code class="language-swift">
DispatchQueue.global().sync {
    Thread.sleep(forTimeInterval: 1.5)
    print(&quot;finish1)
}

DispatchQueue.global().sync {
    Thread.sleep(forTimeInterval: 3)
    print(&quot;finish2)
}

</code></pre>
<h1 id="async--await">async &amp; await</h1>
<hr>
<p>swift는 비동기 프로그래밍을 많이 사용하고 이는 너무 장황하고(verbose), 복잡하고(complex), 부정확한(incorrect) 비동기 코드를 작성하기 쉽다.</p>
<p>따라서 swift에서 비동기 코드를 좀 더 동기적으로 작성하길 원한다면 사용하자</p>
<pre><code class="language-swift">func someWork(sec: UInt64) async -&gt; UInt64{
    try? await Task.sleep(nanoseconds: 1000 * 1000 * 1000 * sec)

 print(sec)
 return 1
}


func simpleWork(){
    &quot;simple&quot; + &quot;work&quot; + &quot;done&quot;
}

var myTask: Task&lt;Int, Never&gt;?

func start(){
    simpleWork()

    myTask = Task{
       // async let name = FN = 기본 규격
       async let s1 = someWork(sec: 1)
       async let s2 = someWork(sec: 3)
       async let s3 = someWork(sec: 4)

        // 비동기 취소되었는지 boolean 확인
       let isCancelled =  myTask?.isCancelled  

       if(isCancelled){
           return &quot;canceled!&quot;
       }
        // 비동기 실행
       let secArr = await [s1,s2,s3]

       let sum  = await s1 + s2 + s3 


    }

}

func pressCancelBtn () {
    myTask?.cancel() //비동기 취소
}



start()
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 기초 - 1]]></title>
            <link>https://velog.io/@choosey_/Swift1</link>
            <guid>https://velog.io/@choosey_/Swift1</guid>
            <pubDate>Wed, 18 Jan 2023 13:32:09 GMT</pubDate>
            <description><![CDATA[<h1 id="변수-상수">변수, 상수</h1>
<hr>
<p>swift에 <strong>변수</strong>는 <strong>var</strong> <strong>상수</strong>는 <strong>let</strong>으로 선언된다. (자주보던 친구들이다.)
javascript에서 <strong>null</strong>은 <strong>nil</strong>로 표기한다.</p>
<pre><code class="language-swift">var red:String = &quot;&quot;
var red = String() //초기화

let colors:String[] = [&quot;red&quot;,&quot;blue&quot;]
let colors = [String]() //초기화</code></pre>
<h1 id="array--set--dictionary">Array &amp; Set &amp; Dictionary</h1>
<hr>
<h2 id="array">Array</h2>
<p>index 순서가 존재하여 중복으로 저장 가능</p>
<pre><code class="language-swift">
let arr1: [Int]
var arr2: [String]

var array3 = [Int](repeating: 0, count: 10) // new Array(10).fill(0) in js


let count: Int = array3.count      // 배열 갯수 확인 : 10
let isEmpty: Bool = array3.isEmpty // false


// 4. 여러 타입을 저장하는 배열 생성하기
var array4: [Any] = [1, &quot;Sodeul&quot;, 20.4]
var array5: NSArray = [1, &quot;Sodeul&quot;, 20.4] 
NSArray는 Class 형식이라 요소는 무조건 &quot;인스턴스&quot;로 구성되어 있어야 함..!

// 요소 추가
var array6 = [1, 2, 3]
array6.insert(0, at: 0)                      // [0, 1, 2, 3]
array6.insert(contentsOf: [10, 100], at: 2)  // [0, 1, 10, 100, 2, 3 ]

</code></pre>
<h2 id="set">Set</h2>
<p>index 순서가 없기 때문에 출력 할 경우 무작위로 나오고 중첩 삽입도 불가하며 집합을 사용할 수 있습니다.</p>
<pre><code class="language-swift">var numbers: Set&lt;Int&gt; = [1,14,3,4]
var sets: Set&lt;Int&gt; = [0,1,3,5]

//합집합
sets.union(numbers) // [14, 1, 3, 4, 2, 5]
//교집합
sets.intersection(numbers) // [3, 1]
// 합 - 교
sets.symmetricDifference(numbers) // [14, 4, 2, 5]
//여
sets.subtracting(numbers) // [14, 4]

//한쪽에 포함여부
sets.isSubset(of:numbers) false
sets.isSuperset(of: numbers) //역 false

//한쪽에 불포함 여부
sets.isDisjoint(with: numbers) // false

</code></pre>
<h2 id="dictionary">Dictionary</h2>
<p>removeValue, updateValue로 값 변경 가능</p>
<pre><code class="language-swift">
let namesDic = [&quot;Kim&quot;:&quot;im&quot;, &quot;Lee&quot;:&quot;lee&quot;, &quot;Park&quot;:&quot;park&quot;]

namesDic.updateValue(&quot;Kim&quot;, forKey: &quot;kim&quot;)

let newNamesDic = namesDic.map { element in
    element.value
}

print(newNamesDic) // [&quot;kim&quot;,&quot;lee&quot;,&quot;park&quot;]

let namesDicfilter = namesDic.filter { $0.value.count &gt;= 4 }

print(namesDicfilter) // [&quot;park&quot;]

let namesDicReduce = namesDic.filter { $0.value.count &gt;= 3 }.map { $0.value.count }.reduce(0) { $0 + $1 }

print(namesDicReduce) // 10</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023년 목표]]></title>
            <link>https://velog.io/@choosey_/2023%EB%85%84-%EB%AA%A9%ED%91%9C</link>
            <guid>https://velog.io/@choosey_/2023%EB%85%84-%EB%AA%A9%ED%91%9C</guid>
            <pubDate>Sun, 08 Jan 2023 15:31:38 GMT</pubDate>
            <description><![CDATA[<h1 id="이직-⭐⭐⭐">이직 ⭐⭐⭐</h1>
<hr>
<p>퇴사와 함께 파트타임으로 웹앱 외주 개발을 하게 되었다.
react로 웹앱을 만드는 프로젝트인데 최근에 해당 형태로 작업하는 회사들도 많은걸로 알고 있어서 좋은 경험이 될 것 같다.</p>
<p>이 프로젝트가 끝나면 경력 삼아서 서비스 하는 기업으로 이직하여 서비스가 어떻게 유지보수되는지 경험하고 싶다.</p>
<h1 id="공부-⭐⭐">공부 ⭐⭐</h1>
<hr>
<h2 id="1-test-code">1. Test Code</h2>
<p>이직 준비를 하다보니 테스트 코드를 요구하는 곳이 정말 많았다.
당장 지금 작업하는 외주도 코드의 퀄리티를 보장하는 방법 중의 하나로 높은 coverage의 테스트 코드를 요구했었다.</p>
<p>물론 기본적으로 사용할 줄은 알고 Enzyme을 큰 프로젝트에서 사용해보긴 했지만... 이 정도로는 자신있게 테스트 코드를 스펙으로 어필하기엔 부족하다는 생각이다.
그래서 hook의 시대가 도래한 현재 react-testing-library를 더 공부하는게 좋겠다라는 생각에 사이드 프로젝트로 진행했던 59mins에 test code를 작성하면서 더 깊게 파보려고 한다.</p>
<h2 id="2react-18-버전">2.React 18 버전</h2>
<p>React 18 버전에 대해서는 공식문서 확인은 해봤지만 아직 사용해보지 못했다.
useDeferredValue (급하지 않은 부분의 재랜더링을 지연할 수 있는 기능)
useInsertionEffect ( Css-in-JS 라이브러리를 활용할 때 스타일 삽입 성능 문제를 해결할 수 있는 Hook )
같은 것들을 토이프로젝트로 직접 사용해봐야겠다.</p>
<h2 id="4-앱">4. 앱</h2>
<p>프론트엔드 공부할 시간도 부족하지만 놀시간을 조금 줄여 Swift를 공부하기 시작했다.</p>
<p>3주 정도 진행한 지금 나는 이 선택에 대단히 만족한다.</p>
<p>처음엔 프론트엔드 개발도 못하는데 무슨 Swift 라는 생각이 한편으로 들기도 했다.
하지만 Javascript 위주로만 공부하다가 다른 언어를 심도 있게 다뤄보니 Javascript에선 크게 생각하지 않아도 되는 스레드 같은 부분들에 대해 고민했고 이로 인해 개발적인 사고는 더 좋아진 것 같다.</p>
<p>앞으로도 취미로 앱 개발을 계속 공부할 생각이고 2~3월 정도에 토이프로젝트도 계획 중이다.</p>
<p>너무 잘맞으면 노선을 변경할수도..?</p>
<h2 id="5-서버">5. 서버</h2>
<p>작년 사이드 프로젝트였던 59mins에 웹소켓을 적용하고 싶다라는 생각이다.
그러기 위해서는 대대적인 서버 수정이 불가피</p>
<h1 id="🔥-개인적인-다짐">🔥 개인적인 다짐</h1>
<hr>
<p>2023년은 잠시 30세 였다가..
29세를 2번째 맞이하게 되는 해이다.
29세를 2번째 맞이하게 되는 해이다.</p>
<p>나는 아직 생각도 어린데 몸만 늙어가는 느낌..?
어렸을 땐 30이면 사랑하는 사람과 결혼도 하고 무엇인가 이뤄냈을줄 알았는데 현실은 잔인하기 짝이 없고 나도 짝이 없다. (헤헤)</p>
<p>작년은 내가 가지고 있는 것들에 비해 과분한 사람들과 인연이 된 한해였고
아직도 한 없이 부족한 내 능력을 감사하게도 다들 좋게 평가해주셔서 부담도 된다. </p>
<p>이러한 것들을 증명하고 보답하기 위해 노력할 것이고 앞으로 행하는 모든 것들에 최선을 다해서 후회 없는 한해가 되었으면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2년차 프론트엔드 개발자의 
2022년 회고]]></title>
            <link>https://velog.io/@choosey_/%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@choosey_/%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 25 Dec 2022 13:04:17 GMT</pubDate>
            <description><![CDATA[<p>크리스마스인 오늘 혼자 있으니 N년차 솔로인 나는 감수성이 더 풍부해진다.
올 한해 있었던 기억들을 더듬어 기록해보고자 한다.</p>
<h2 id="👎🏻-bad">👎🏻 BAD</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/choosey_/post/48bce8c9-b4dd-47e4-b387-5f844ecafd27/image.jpg
" width="100%" /></p>
<h3 id="1서비스-운영-경험">1.서비스 운영 경험</h3>
<p>IT 직군에선 서비스를 운영하면서 오는 문제점들을 경험하고 개선해나아가는 경험이 필요하다고 생각한다.
근무하던 회사는 에이전시이다 보니 유지보수 경험을 하기엔 상대적으로 힘들었고 이를 경험하기 위해서 사이드 프로젝트를 진행했지만 내가 기대했던만큼의 성과는 얻지 못했고 퇴사를 하게되는 트리거가 되었다.</p>
<h3 id="2건강관리">2.건강관리</h3>
<p>내년이면 30..아니 29이다.
&#39;어른들이 안아프려고 살려고 운동한다.&#39; 라는 말이 요즘 들어 크게 와닿고 있다.</p>
<p>20대 중반까지만해도 아픈 곳이 없었는데 요즘은 면역력이 약해진건지 툭하면 아프고 만성 위염까지 생겼다. 하루종일 컴퓨터를 보고 있으니 안구건조증은 더더욱 심해졌다.</p>
<p>나의 건강이 영원할거라고 어리석은 생각을 했던 것 같다.
이미 이곳저곳에서 이상신호를 보내고 있지만 지금부터라도 관리하면 더 나빠지는걸 지연시킬 수 있지 않을까...?</p>
<h2 id="👍🏻-good">👍🏻 GOOD</h2>
<hr>
<h3 id="1사이드-프로젝트--외주-작업">1.사이드 프로젝트 &amp; 외주 작업</h3>
<p>1년차 때는 적응하느라 하지 못했던 업무, 개인 토이프로젝트 이외에 사이드 프로젝트를 진행했고 같이 작업했던 외주 개발자분께서 좋게 생각해주셔서 외주 작업에 참여하게 되었다.</p>
<p>시작하기전엔 늘 같이 작업하던 사람들이 아닌 사람들과 작업하는 것에 지레 겁을 먹었다. 
그래도 하던대로만 하자라는 마음가짐으로 내 나름대로 소통하려고 열심히 임하니 모두 좋게 봐주셨고 다들 더 도와주시려고 하실 때도 있어 잘 적응할 수 있었다.</p>
<p>여러 개발자들의 코드를 참고할 수 있었고 모노레포, 마이크로서비스 아키텍처도 경험할 수 있어 2022년에 한 선택 중 가장 좋은 선택이었던 것 같다.</p>
<h3 id="2ios-앱-출시--swift-입문">2.IOS 앱 출시 &amp; Swift 입문</h3>
<p>사이드 프로젝트를 하면서 react-native로 간단한 IOS 앱을 만들었고 되게 재밌었다. IOS 심사 통과가 어려운 기능들은 없어서 기본적인 준비들로 한번에 통과했던 것 같지만 기본적으로 준비해야되는 것들을 경험해볼수 있었던 점이 좋았다. </p>
<p>이후에 개인적인 욕심이 생겨서 Swift를 배우기 시작하였고 처음으로 다른 언어를 조금 디테일하게 다루기로 마음 먹어서 기초부터 공부하고 있다. 
자바스크립트와 비슷한 문법들도 있지만 멀티스레드 같이 조금 다르게 사고해야하는 부분들이 있어서 개발자로써 코드를 짜는데 한단계 성장할 수 있을 것 같다! </p>
<h3 id="3퇴사">3.퇴사</h3>
<p>사실 입사할 때부터 고민했던 일이다.
들어가자마자 캔버스에 캔자도 모르는데 여러 형태의 차트를 캔버스에 그리고 애니메이션까지 입히는 프로젝트를 맡았다.
당시엔 프론트도 혼자였어서 사실상 도움을 줄 사람이 아무도 없어서 매일 야근하고 주말에도 출근했던 기억이.. (지금 돌아보면 덕분에 캔버스를 다룰 줄 알게 되어서 좋긴하다...)</p>
<p>이후에도 시니어 없이 소수의 프론트 인원으로 작업을 해야했고 무엇보다 구축하고 오픈까지만 하는 경우가 많아서 실제로 서비스를 운영해보고 싶었던 나로써는 아쉬웠다.</p>
<p>지금 년차에서 운영 경험을 하고 싶었고 결국 이 회사에선 더 발전하기 어렵다고 판단하여 이직하기로 마음 먹고 퇴사하였다.</p>
<p>현재 이직은 성공하지 못했지만.. 아직은 만족 중이다..</p>
<h2 id="마치며">마치며</h2>
<p>뭔가 이것저것 했지만 앞으로 몇발자국 못 나간 것 같은 느낌이 드는 한해였다.</p>
<p>비록 길이 굽이져 돌아가고 있지만 결국 앞으로 나아가고 있으니 이런저런 생각말고 지금 하고 있는만큼만 꾸준히 나아가 좋은 개발자가 되고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[사이드 프로젝트] 링쿠 - 나만의 앱 저장]]></title>
            <link>https://velog.io/@choosey_/%EB%A7%81%EC%BF%A0</link>
            <guid>https://velog.io/@choosey_/%EB%A7%81%EC%BF%A0</guid>
            <pubDate>Sat, 12 Nov 2022 00:34:19 GMT</pubDate>
            <description><![CDATA[<h1 id="제품-소개">제품 소개</h1>
<p><a href="https://www.notion.so/LinkU-49903f586285465792e2be64d14c1dd4">https://www.notion.so/LinkU-49903f586285465792e2be64d14c1dd4</a></p>
<h2 id="개요">개요</h2>
<hr>
<p>모바일에서 사파리나 크롬은 북마크가 저장한 뒤에 다시 찾기 불편하고 잊어버리는 경우가 많았고 이를 보완한 앱이 있었으면 좋겠다라고 생각하여 탄생한 앱이다.</p>
<p>지난 59mins에 이어서 사이드 프로젝트로 IOS앱을 만들게 되었다. </p>
<h2 id="작업-내용">작업 내용</h2>
<hr>
<h3 id="0-기술-스텍">0. 기술 스텍</h3>
<ul>
<li>React-Native</li>
<li>typescript</li>
<li>Realm</li>
</ul>
<h3 id="1-dicrectory-구조">1. Dicrectory 구조</h3>
<p>프로젝트 시작전 늘 고민하는 문제지만 답은 없는 것 같다.
이번엔 atomic design을 적용해보려고 한다.
추가로 index.ts는 파일들을 export 해주는 역할로만 사용했다.</p>
<p><strong>⦁ atomic design</strong></p>
<img src="https://velog.velcdn.com/images/choosey_/post/304f416e-c0ab-457f-b4e1-d1faa3aeedd5/image.png" width="100%" align="left" />




<p><strong>⦁ ./components/index.ts</strong></p>
<pre><code class="language-js">
import Buttons from &quot;./Buttons&quot;;
import Modals from &quot;./Modals&quot;;
import FlatLists from &quot;./FlatLists&quot;;
import Labels from &quot;./Labels&quot;;
import Loader from &quot;./Loader&quot;;
import Inputs from &quot;./Inputs&quot;;
import BottomBlur from &quot;./BottomBlur&quot;;

export { Buttons, Modals, FlatLists, Labels, Loader, Inputs, BottomBlur };

</code></pre>
<h3 id="2-realm">2. Realm</h3>
<p>Realm은 App.tsx에서 시작시 연결하도록 했고 굳이 redux나 recoil을 사용할 정도가 아니라고 판단되어 Context API에 전달하여 사용하도록 했다. </p>
<p><strong>⦁ ./App.tsx</strong></p>
<pre><code class="language-js">
  const [realm, setRealm] = useState(null);

  const startLoading = async () =&gt; {
    try {
      const connection = await new Realm.open({
        path,
        schema: [LinksSchema, CategoriesSchema, SearchSchema],
      });
      setRealm(connection);

      const imageAssets = cacheImages([
        require(&quot;./assets/img/logo.png&quot;),
        require(&quot;./assets/img/onBorading/onboarding1.png&quot;)
        ...
      ]);

      await Promise.all([...imageAssets]);

    } catch (error) {
      console.warn(error);
    } finally {
      setAppIsReady(true);
    }
  };

  useEffect(() =&gt; {
    if (isStarted &amp;&amp; AppState.currentState === &quot;active&quot;) {
      requestTrackingPermission();
    }
  }, [isStarted]);

  if (!appIsReady) {
    return null;
  }

  return (
    &lt;SafeAreaProvider&gt;
      &lt;SafeAreaView
        onLayout={onLayoutRootView}
        edges={[&quot;top&quot;, &quot;right&quot;, &quot;left&quot;]}
      &gt;
        &lt;StatusBar/&gt;
        &lt;DBContext.Provider value={realm}&gt;
            &lt;ThemeProvider
              theme={darkMode === &quot;dark&quot; ? darkThemes : lightThemes}
            &gt;
              &lt;NavigationContainer&gt;
                &lt;Navigator /&gt;
              &lt;/NavigationContainer&gt;
            &lt;/ThemeProvider&gt;
        &lt;/DBContext.Provider&gt;
      &lt;/SafeAreaView&gt;
    &lt;/SafeAreaProvider&gt;
  );
</code></pre>
<h3 id="3-반응형-레이아웃">3. 반응형 레이아웃</h3>
<p>React-Native 반응형 레이아웃은 이번에 작업하게 되었다. 
도움을 주는 react-native-responsive-dimensions라는 라이브러리가 있었고 이를 사용하게 되었다. </p>
<p>검수때 확인하니 제플린 사이즈를 최소 디바이스 사이즈 하나만 작업해서 큰 사이즈의 디바이스에선 너무 커보이는 레이아웃들이 있었고 작은 폰트사이즈들은 너무 작아보이는 문제점이 있었다. 
이런 부분들은 일부 분기처리해서 작업하긴 했지만 조금 더 좋은 방법이 있는지 확인 해봐야 할 것 같다.</p>
<p><strong>⦁ ./hooks/useResponseSize.ts</strong></p>
<pre><code class="language-js">
import {
  responsiveScreenHeight,
  responsiveScreenWidth,
  responsiveScreenFontSize,
} from &quot;react-native-responsive-dimensions&quot;;

const ZEPLIN_WIDTH = 320;
const ZEPLIN_HEIGHT = 568;

export function widthPercentage(width: number): number {
  const percentage = (width / ZEPLIN_WIDTH) * 100;

  return responsiveScreenWidth(percentage);
}

export function heightPercentage(height: number): number {
  const percentage = (height / ZEPLIN_HEIGHT) * 100;

  return responsiveScreenHeight(percentage);
}

export function fontPercentage(size: number): number {

  // size 뒤에 붙는 비율은 시안에 따라 유동적인듯함.
  const percentage = size * 0.16;

  return responsiveScreenFontSize(percentage);
}

</code></pre>
<h2 id="후기">후기</h2>
<hr>
<h3 id="좋았던-부분">좋았던 부분</h3>
<h4 id="1-앱-생태계를-겪어본-점">1. 앱 생태계를 겪어본 점</h4>
<p>사실 늘 앱을 만들어 보고 싶다라는 생각이 있었다.</p>
<p>IOS앱은 정말 여러 부분들을 신경 쓰면서 작업해야 하는걸 몸소 느꼈다.
그들의 정책들에 맞춰야 하기도 해야하고 서비스 중인 앱들을 참고하면서 어떻게 동작시키면 유저들이 불편함 없이 사용할 수 있을지 고민도 많이 할 수 있었던 값진 시간들이였다.</p>
<p>앞으로 하게 될 외주 프로젝트가 웹앱인데 큰 도움이 될 것 같다.</p>
<h3 id="아쉬웠던-점">아쉬웠던 점</h3>
<h4 id="1-react-native의-단점이-와닿았다">1. React-Native의 단점이 와닿았다.</h4>
<p>아직 React-Native를 다 파악하지 못해서 일수도 있지만 라이브러리 의존하는게 심했다.</p>
<p>업데이트가 되어지지 않는 라이브러리들은 RN 최신 버전에선 전혀 사용할 수 없었다. 직접 만드려면 네이티브의 모듈을 만들어야 될텐데 네이티브를 모르는 나로써는 새로운 라이브러리를 찾아나서야 했던 기억이 아쉬웠다.</p>
<h4 id="2-유저의-정보를-알-수-없었던-점이-아쉽다">2. 유저의 정보를 알 수 없었던 점이 아쉽다.</h4>
<p>59mins는 GA와 서버가 있어서 접속 경로를 확인할 수 있었지만 이번에는 local DB를 사용하여서 app store에서 제공하는 다운로드수, 국가, 실제 사용하고 있는 유저 정도만 확인 가능하였다.</p>
<p>다음 사이드 프로젝트는 서버를 통해 유저 트래킹을 통해 디벨롭 할 수 있도록 만들고 싶다.</p>
<h3 id="앞으로의-계획">앞으로의 계획</h3>
<p>앱을 개발하는 내내 뭔가 제한적인 느낌을 지울 수 없었고 간단한 기능들인데 라이브러리에 의존을 해야하는 상황이 빈번해 차라리 네이티브를 배우고 싶다라는 생각이 들었다. (그래서 swift 인프런 강의 결제함 🤣)</p>
<p>올해안으로 토이 프로젝트로 IOS 네이티브앱을 만드는걸 목표로 삼고 공부하려고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[발표 자료] 서버리스란? ]]></title>
            <link>https://velog.io/@choosey_/%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EB%9E%80</link>
            <guid>https://velog.io/@choosey_/%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4%EB%9E%80</guid>
            <pubDate>Thu, 13 Oct 2022 12:22:51 GMT</pubDate>
            <description><![CDATA[<h2 id="서버리스란">서버리스란?</h2>
<p>많은 분들이 serverless라는 단어만 듣고 서버가 없다라고 생각 하실텐데 이는 오해입니다.</p>
<p>서버리스는 서버가 없는 것이 아니라 서버를 직접 관리 하지 않는 경우를 이야기 하는 것 입니다.</p>
<blockquote>
</blockquote>
<p>서버가 없다 X
관리할 서버가 없다 O</p>
<br/>

<h2 id="서버-형태">서버 형태</h2>
<p><strong>1.컴퓨터를 직접 구매하여 보관하고 작동 시켜 직접 빌드</strong></p>
<p>초기엔 직접 컴퓨터를 구매해서 구동시켰습니다. 
따라서 컴퓨터를 보관할 위치도 필요하고 만약 정전이 나서 서버가 꺼지거나 허용량을 초과하면 난감한 상황에 이르렀겠죠.. 😅</p>
<p>근래에는 이를 보완한 클라우드 플랫폼이 등장합니다.</p>
<p><strong>2.클라우드</strong></p>
<p>대표적으로 <strong>아마존의 EC2</strong>가 있는데요 굳이 컴퓨터를 사서 직접 관리하는게 아니라 아마존의 장비와 서버를 빌려서 사용 할 수 있게 되었습니다. 정전으로 인해 서버가 꺼지는 것에 대해서 딱히 고려하지 않아도 되고 트래픽 초과시 load balancer가 동작하니 편하게 사용 가능하여진 것이죠</p>
<p>다만 이렇게 되어도 클라우드 비용, 서버 버전관리, 데이터 백업, 업데이트 등을 해야 합니다. </p>
<p><strong>3. 서버리스</strong></p>
<p>대표적으로 <strong>AWS lamda</strong> 가 있는데 이는 serverless 템플릿을 이용하여 코드를 분석해 함수 단위로 쪼갠 뒤 직접 관리 하지 않아도 되는 서버에 올려서 요청이 들어 왔을 때만 필요한 함수들을 깨우고 호출하여 동작 시키게됨 동작 후엔 다시 수면을 하게 되는데 이 덕분에 계속 서버를 켜두지 않아도 되어서 비용적으로도 굉장히 저렴해졌다고 합니다.
(AWS Lamda 기준 100만건 20센트)</p>
<br/>

<h2 id="단점">단점</h2>
<p> <strong>1. 호출 시간</strong></p>
<ul>
<li>아무래도 수면하고 있던 함수를 깨우기 때문에 항상 켜져있는 서버보단 느릴 수 밖에 없습니다. (이 때문에 게임, 검색 같은 서비스에선 당연히 사용 불가)</li>
</ul>
<p><strong>2. 플랫폼 종속적</strong></p>
<ul>
<li>클라우드 or 직접 서버를 보유 했을때보다 서버 이전이 훨씬 어렵다는고 합니다. 플랫폼에 따라 아예 어플리케이션의 구조를 바꿀는 경험을 할수도 있다고 하니 엄청난 리스크</li>
</ul>
<p><strong>3. 관리에 제약</strong></p>
<ul>
<li>서버를 직접 관리하는게 아니다보니 제어 가능한 부분이 제한적이라 복잡한 서비스에선 사용하기 어렵고 보안이 취약할 수 있음.</li>
</ul>
<h2 id="마치며">마치며</h2>
<p>서버리스는 사이드 프로젝트나 간단한 CRUD 정도의 소규모 서비스에서는 충분히 사용할만한 메리트가 있을 것 같습니다.
하지만 서버리스의 등장으로 서버 로직이 프론트단에도 내려오게 되어질 것으로 예상되어 프론트의 작업 범위가 늘어나지 않을까..라는 생각을 하면서 이 글을 마칩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Enzyme 테스트 코드 ]]></title>
            <link>https://velog.io/@choosey_/Enzyme</link>
            <guid>https://velog.io/@choosey_/Enzyme</guid>
            <pubDate>Mon, 12 Sep 2022 10:10:15 GMT</pubDate>
            <description><![CDATA[<p>최근의 작업한 프로젝트는 클래스형 컴포넌트가 사용된 레거시 코드에 새로운 기능을 추가하고 Enzyme을 이용한 snapshot 테스트 코드를 작성하는 것이었다.</p>
<p>테스트 코드를 이렇게 대량으로 작성했던 적은 없어서 기록으로 남기기 위해 글을 작성하려한다.</p>
<h2 id="작업-목표">작업 목표</h2>
<p>이전 커버리지는 90%이나 현재 수정 추가된 부분들로 인한 Failed가 많아서 Failed 처리 및 신규 기능 테스트 코드 작성으로 85%를 달성하는 조건이었다.</p>
<p>우선 테스트 코드 작성에 앞서 유감스럽게도 Enzyme을 React 최신 버전 지원을 안한다.
따라서 이 프로젝트가 사용하고 있는 17 버전에서 사용하기 위해선 Adapter 라이브러리를 설치 해줬어야 한다.
설치 후 아래 코드를 추가해줘야한다.</p>
<pre><code class="language-js">
import Adapter from &#39;@wojtekmaj/enzyme-adapter-react-17&#39;;


Enzyme.configure({ adapter: new Adapter() });
</code></pre>
<br/>

<h3 id="스냅샷-테스팅">스냅샷 테스팅</h3>
<p>스냅샷 테스팅은 렌더링된 결과가 이전에 결과와 일치하는지 확인하는 작업</p>
<p>아래는 예로 가져온 DateTime 컴포넌트의 테스트 코드이다.
최하위 View 컴포넌트이기에 DOM 확인 정도의 처리만하고 있다.</p>
<p>** DateTime.js **</p>
<pre><code class="language-js">
function DateTime({ className, timestamp }: Props) {
  const today = new Date(new Date().toDateString()).valueOf();

  return (
    &lt;span className={className}&gt;
      {today &lt;= timestamp ? (
         &lt;FormattedDate value={timestamp} formats={{ locale }} {...format} /&gt;
      ) : (
        &lt;FormattedTime value={timestamp} /&gt;
      )}
    &lt;/span&gt;
  );
}

export default DateTime;
</code></pre>
<p>** DateTime.test.js **</p>
<pre><code class="language-js">
describe(&#39;&lt;DateTime /&gt;&#39;, () =&gt; {
  const todayTimestamp = new Date(new Date().toDateString()).valueOf();

  const store = getStore({});
  it(&#39;should return time, if they are same.&#39;, () =&gt; {
    const wrapper = mount(
      &lt;IntlProvider locale=&quot;en&quot; key=&quot;en&quot;&gt;
        &lt;Provider store={store}&gt;
          &lt;DateTime timestamp={todayTimestamp} /&gt;
        &lt;/Provider&gt;
      &lt;/IntlProvider&gt;,
    );
    expect(wrapper.text()).toEqual(&#39;12:00 AM&#39;);
  });

  it(&#39;should return time, if the current timestamp is over it.&#39;, () =&gt; {
    const noon = new Date(todayTimestamp).setHours(12);
    const wrapper = mount(
      &lt;IntlProvider locale=&quot;en&quot; key=&quot;en&quot;&gt;
        &lt;Provider store={store}&gt;
          &lt;DateTime timestamp={noon} /&gt;
        &lt;/Provider&gt;
      &lt;/IntlProvider&gt;,
    );
    expect(wrapper.text()).toEqual(&#39;12:00 PM&#39;);
  });

  it(&#39;when receiveDate is same year&#39;, () =&gt; {
    const today = Date.now();
    const timestamp = today - 1000 * 60 * 60 * 24 * 3;
    const wrapper = mount(
      &lt;IntlProvider locale=&quot;en&quot; key=&quot;en&quot;&gt;
        &lt;Provider store={store}&gt;
          &lt;DateTime timestamp={timestamp} /&gt;
        &lt;/Provider&gt;
      &lt;/IntlProvider&gt;,
    );
    const dateStr = new Date(timestamp).toLocaleString(&#39;en&#39;, {
      month: &#39;short&#39;,
      day: &#39;2-digit&#39;,
    });
    expect(wrapper.text()).toContain(dateStr);
  });

  it(&#39;when receiveDate is not same year&#39;, () =&gt; {
    const timestamp = 1618431328353;
    const wrapper = mount(
      &lt;IntlProvider locale=&quot;en&quot; key=&quot;en&quot;&gt;
        &lt;Provider store={store}&gt;
          &lt;DateTime timestamp={timestamp} /&gt;
        &lt;/Provider&gt;
      &lt;/IntlProvider&gt;,
    );

    expect(wrapper.text()).toEqual(&#39;15/04/2021&#39;);
  });
});

</code></pre>
<p>mount를 통해서 IntlProvider내 DateTime 컴포넌트를 테스트하였다.  </p>
<p>mount는 Enzyme을 통하여 리액트 컴포넌트를 렌더링 해준다. wrapper 를 통해서 우리가  props 조회, DOM 조회, state 조회 등을 할 수 있다.  </p>
<p>그 이외에 shallow가 있는데 이는 리액트 컴포넌트를 렌더링 하지 않는다. div가 최상단이라는 의미인데 컴포넌트 내부까지 테스트할 필요가 없을 때 사용한다.
wrapper.instance(), wrapper.state()를 통해서 컴포넌트의 method, state에 쉽게 접근하여 비교 가능하다.</p>
<h2 id="결론"><strong>결론</strong></h2>
<p>Enzyme의 경우 react-testing-library와는 다르게 직관적이고 쉬운 느낌이다. state와 method에 접근하기 쉬웠고 snapshot만 비교해도 엄청난 coverage가 나와서 RTL에서 branch 수치 올리려면 힘들었는데 편하게 작업했던 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[사이드 프로젝트] 59Mins - 3편]]></title>
            <link>https://velog.io/@choosey_/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%ED%83%84</link>
            <guid>https://velog.io/@choosey_/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%ED%83%84</guid>
            <pubDate>Sun, 21 Aug 2022 11:21:02 GMT</pubDate>
            <description><![CDATA[<h2 id="사이드-프로젝트-소개">사이드 프로젝트 소개</h2>
<hr>
<h3 id="⭐️59mins⭐️"><strong><a href="https://disquiet.io/product/59mins"> ⭐️59mins⭐️</a></strong></h3>
<p>직장인들의 고민인 회의 시간을 보다 효율적으로 사용으로 운용하고자 만들어진 제품입니다.</p>
<img width="100%" src="https://velog.velcdn.com/images/choosey_/post/403e84f6-385a-4e16-8457-90957422ad18/image.png">


<br>



<h2 id="홍보">홍보</h2>
<p>홍보는 disquite, facebook , 지인들에게 진행이 되었다.</p>
<p>많은분들이 피드백을 주셨는데 대부분 모두 아이디어는 좋다는 의견을 주셨다.
그렇지만 기능들이 부족하고 </p>
<blockquote>
<ul>
<li>디자인이 심플하고 좋다.</li>
</ul>
</blockquote>
<ul>
<li>아이디어가 좋다.</li>
</ul>
<p>유지보수는 아마 백엔드 분들이 더 이상 참여 의사가 없어서 잠정 중단이 될 것 같다.</p>
<p>이대로 끝내기는 좀 아쉬운 느낌이다.
처음으로 한 사이드 프로젝트라 그런지 애착도 더 가고 소켓이랑 webrtc를 붙이거나 구글 캘린더만 연동 시켜도 꽤나 괜찮은 프로젝트가 될 것 같은데..</p>
<p>기회가 된다면 서버를 Nest로 작업해서 위에 기능들까지 붙여볼 수 있었으면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[사이드 프로젝트] 59Mins -  2편 ]]></title>
            <link>https://velog.io/@choosey_/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0-2%ED%83%84</link>
            <guid>https://velog.io/@choosey_/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0-2%ED%83%84</guid>
            <pubDate>Wed, 17 Aug 2022 01:11:56 GMT</pubDate>
            <description><![CDATA[<h2 id="사이드-프로젝트-소개">사이드 프로젝트 소개</h2>
<hr>
<h3 id="⭐️59mins⭐️"><strong><a href="https://disquiet.io/product/59mins"> ⭐️59mins⭐️</a></strong></h3>
<p>직장인들의 고민인 회의 시간을 보다 효율적으로 사용으로 운용하고자 만들어진 제품입니다.</p>
<img width="100%" src="https://velog.velcdn.com/images/choosey_/post/403e84f6-385a-4e16-8457-90957422ad18/image.png">


<br>


<h2 id="작업-내용">작업 내용</h2>
<hr>
<h3 id="1-api-수정-요청">1. API 수정 요청</h3>
<p>마이크로서비스처럼 백엔드가 짜여있었는데 상황에 따라선 화면을 그리는데에 이론상 API를 100번 이상 호출해야되는 경우도 있고 미팅을 최초 등록할때는 그냥 텍스트 저장만 하는데에 20번 이상 호출해야하고 게다가 서버에는 트랜잭션도 되어 있지 않았다.</p>
<p>graphql이였으면 모르겠지만 rest API에선 꽤나 부담스러웠고 백엔드 분들에게 수정 요청을 드리게 되었다.</p>
<h3 id="2-timer">2. Timer</h3>
<ol>
<li>타이머는 지정된 미팅 시간 (최대 60분)부터 카운트</li>
<li>지정된 시간 초과시 초과된 시간 출력</li>
<li>중간에 종료시 이어서 카운트</li>
</ol>
<p>Websocket을 이용하여 Realtime 구현이 목표였지만 여러가지 이유로 어렵다고 판단되어 1초에 한번 Polling을 하는 형태로 구현되어 있었다.</p>
<p>1초 단위로 언제 끝날거라는 보장 없이 Polling을 하는게 맞나? 라는 의문이 들었다.
구현하는게 어렵다기보단 비용이나 1초 마다 요청을 보내야하는 부담 때문에 팀원들을 설득하여 1분 단위로 보내기로 변경하였다.</p>
<pre><code class="language-js">
  useEffect(() =&gt; {
    (async () =&gt; {
      if (minutes &lt; 60) {
        if (progress &gt; minutes || progress === null) {
          await patchProgressTime(minutes + 1);
        }
      }
      if (minutes &gt; 60) {
        await patchProgressTime(-minutes);
      }
    })();
  }, [minutes, progress]);

</code></pre>
<br>

<h3 id="3-swr--ssr">3. SWR &amp; SSR</h3>
<p>이전 글에서 언급한대로 Next를 사용하였지만 해당 스펙을 사용할만한 이유를 납득하는 코드는 없었다. SSG도 없고 최근 추가된 ISR을 사용한 것도 아니었다.
아무래도 정보가 프라이빗한 페이지고 유저 경험상 SSR로 데이터를 불러와 처리했으면 하는 페이지들이 있어서 적용하였다</p>
<p>그중 한페이지인 미팅 진행 중 페이지이다. 
현재 구현된 코드에서 개인적으로 개선되었으면 하는 부분들이다.</p>
<ol>
<li>CSR로 작업하여  위에 언급된 타이머에 진행시간이 잠깐 변수 초기 값인 0으로 표기가 되는 현상이 있음</li>
<li>미팅 전, 미팅 완료인 미팅엔 접근 불가 처리가 안되어 있고 잘못된 url로 접속시 에러 페이지도 없음</li>
</ol>
<p>사실 모두 잠깐 보이는 화면이라서 어찌보면 CSR로 처리를 해도 되었을테지만 SSR이 더 좋은 선택인 것 같아서 적용하게 되었다.
<br></p>
<p><strong>/api/meeting/meeting.ts</strong></p>
<pre><code class="language-js">import useSWR from &quot;swr&quot;;
import { baseURL } from &quot;..&quot;;

import { Cookies } from &quot;react-cookie&quot;;
const cookies = new Cookies();

export const meetSWR = (id) =&gt; {
  const { data: meetData, mutate: meetMutate } = useSWR(
    id ? `${baseURL}/api/meet/?meet_id=${id}` : null,
    (url) =&gt;
      fetch(url, {
        headers: {
          Authorization: cookies.get(&quot;Authorization&quot;),
        },
      }).then((res) =&gt; res.json()),
    { revalidateOnFocus: false }
  );

  return { meetData };
};
</code></pre>
<br>

<p><strong>/page/meeting/[id].tsx</strong></p>
<pre><code class="language-js">
const MeetPage: NextPage&lt;{ meetData: MeetData; id: string }&gt; = ({
  meetData,
  id,
}: {
  meetData: MeetData;
  id: string;
}) =&gt; {
  return (
    &lt;SWRConfig
      value={{
        fallback: {
          [`${baseURL}/api/meetall/?meet_id=${id}`]: meetData,
        },
      }}
    &gt;
      &lt;Meeting /&gt;
    &lt;/SWRConfig&gt;
  );
};


export const getServerSideProps = async function ({
  req,
  params: { id },
}: any) {
  const session = await getSession({ req });
  const token = req.cookies[&quot;Authorization&quot;];

  let permanent: boolean = false;
  let destination: string;
  const isAuth = session &amp;&amp; token &amp;&amp; id

  if (isAuth) {
      const meetData = await fetch(`${baseURL}/api/meetall/?meet_id=${id}`, {
        headers: {
          Authorization: token,
          Accept: &quot;application/json&quot;,
        },
      }).then((res) =&gt; res.json());

      const { meet_status, meet_id } = meetData;

      if (!meet_id || meet_status !== &quot;y&quot;) {
        // 접근 불가 redirect 
        destination = !meet_id
          ? &quot;/404&quot;
          : meet_status === &quot;p&quot;
          ? `/setting/${meet_id}`
          : `/minutes/${meet_id}`;

        return returnDestination(permanent, destination);
      } else {
        return {
          props: { meetData, id },
        };
      }
  } else {
    destination = &quot;/login&quot;;
    return returnDestination(permanent, destination);
  }
};

export default MeetPage;

</code></pre>
<p>meetData를 가져와서 meet_id가 없다면 존재하지 않는 미팅으로 판단하였고
meet_status가 y(진행 중)이 아니라면 해당 상태에 맞게 redirect를 시킬 수 있게 SSR 작업을 하였다.</p>
<br>


<h2 id="4-form">4. Form</h2>
<h3 id="react-hook-form의-사용">react-hook-form의 사용</h3>
<p>react-hook-form이 패키지에 추가되었는데 직접 구현을 해놓으려고 하셨던 것 같은 코드로 판단이 되었다. </p>
<p>다만 아직 완성이 안되어 있었고 너무 추상적인 형태라서 아예 만들어진 형태에 강제하는게 더 빠른 작업이 가능할 것 같아서 react-hook-form을 사용하기로 하였다.</p>
<pre><code class="language-js">
 const {
    register,
    handleSubmit,
    getValues,
    setValue,
    watch,
    control,
    formState: { isSubmitting },
  } = useForm({
    mode: &quot;onChange&quot;,
    shouldFocusError: false,
  });


  useEffect(() =&gt; {
    if (meetAllData) {
      setValue(&quot;agendaForm&quot;, meetAllData.agenda);
    }
  }, [setCursor, setValue]);


 const { fields } = useFieldArray({
    control,
    name: &quot;agendaForm&quot;,
  });

return(
    &lt;Body&gt;
      &lt;Controller
        name={`agendaForm.discussion`}
        control={control}
        render={({ field }) =&gt; (
          &lt;&gt;
            &lt;TextArea
              {...field}
              name={`agendaForm.discussion`}
              style={{ padding: 0 }}
              value={field.value}
              control={control}
              ref={null}
              placeholder=&quot;논의할 내용에 대해 작성해주세요.&quot;
            /&gt;
          &lt;/&gt;
        )}
      /&gt;
    &lt;/Body&gt;
)

</code></pre>
<h3 id="회의록-실시간-저장">회의록 실시간 저장</h3>
<p>회의록은 실시간으로 저장이 되어야 했다.
react-hook-form의 watch 기능을 사용하여서 내용이 변경이 되었을 경우 저장할 수 있도록 하였다.</p>
<pre><code class="language-js">
const onPatchAgenda = async (data: IAgenda, id: string) =&gt;
    await axios.patch(`/api/agenda/${id}/`, data, { headers });

  useEffect(() =&gt; {
    let timer;

    const subscription = watch((value) =&gt; {
      const { agendaForm } = value;

      const { decisions, discussion, action: actions } = agendaForm[cursor];
      const prevData = meetAllData.agenda[cursor];
      const {
        decisions: prevDecisions,
        discussion: prevDiscussion,
        action: prevActions,
      } = prevData;

     const isAgendaChanged = decisions !== prevDecisions || discussion !== prevDiscussion

      if (isAgendaChanged) {
        clearTimeout(timer);
        timer = setTimeout(async () =&gt; {
          await onPatchAgenda({ discussion, decisions }, prevData.agenda_id);
        }, 200);
      }
    });

    return () =&gt; {
      clearTimeout(timer);
      subscription.unsubscribe();
    };
  }, [watch, cursor]);

</code></pre>
<h2 id="5-seo-ga-작업">5. SEO, GA 작업</h2>
<p>SEO는 로그인 페이지 말고는 노출되어지기가 쉽지 않은 형태였다. 그래서 우선 공통적인 SEO만 적용하기로 하였고 팀원들이 GA를 붙여보고 싶다라는 이야기가 나와서 GA까지 작업을 하게 되었다. </p>
<h3 id="seo">SEO</h3>
<pre><code class="language-js">
//app.tsx

const DEFAULT_SEO = {
  title: &quot;59mins&quot;,
  description:
    &quot;59mins는 직장 내 효율적인 회의를 돕는 웹 서비스입니다. 59분 이내로 아젠다 별 타이머를 세팅한 후 논의, 결정, 액션 아이템을 정리할 수 있는 양식에 따라 회의록을 작성할 수 있습니다. 미팅 도중에 타이머가 적절한 논의 시간과 결정 시간을 안내해주어 보다 효율적으로 시간을 관리할 수 있도록 도와드립니다. 회의가 종료된 후에는 동료들과 자가진단을 통해 회의 시간을 회고하고, 저장된 회의록을 조회 및 관리할 수 있습니다.&quot;,
  canonical: &quot;https://59mins.net&quot;,
  openGraph: {
    type: &quot;website&quot;,
    locale: &quot;ko_KR&quot;,
    url: &quot;https://59mins.net&quot;,
    title: &quot;59mins&quot;,
    description:
      &quot;59mins는 직장 내 효율적인 회의를 돕는 웹 서비스입니다. 59분 이내로 아젠다 별 타이머를 세팅한 후 논의, 결정, 액션 아이템을 정리할 수 있는 양식에 따라 회의록을 작성할 수 있습니다. 미팅 도중에 타이머가 적절한 논의 시간과 결정 시간을 안내해주어 보다 효율적으로 시간을 관리할 수 있도록 도와드립니다. 회의가 종료된 후에는 동료들과 자가진단을 통해 회의 시간을 회고하고, 저장된 회의록을 조회 및 관리할 수 있습니다.&quot;,
    site_name: &quot;59mins&quot;,
    images: [
      {
        url: &quot;https://59mins.net/image/Kakao_Share_Thumbnail.png&quot;,
        width: 96,
        height: 96,
      },
    ],
  },
  twitter: {
    handle: &quot;@handle&quot;,
    site: &quot;https://59mins.net&quot;,
    cardType: &quot;summary_large_image&quot;,
  },
};

</code></pre>
<h3 id="ga">GA</h3>
<pre><code class="language-js">//_document.tsx

&lt;Head&gt;
  ....
  &lt;meta name=&quot;google-site-verification&quot; content=&quot;&quot; /&gt;
  &lt;script async src=&quot;https://www.googletagmanager.com/gtag/js?id=G-&quot; /&gt;
  &lt;script
    dangerouslySetInnerHTML={{
      __html: `
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag(&#39;js&#39;, new Date());
    gtag(&#39;config&#39;, &#39;G-&#39;, {
      page_path: window.location.pathname,
    });
    `,
    }}
  /&gt;
&lt;/Head&gt;
</code></pre>
<br/> 

<h2 id="배포">배포</h2>
<hr>
<p>기능, 디자인 검수가 끝나고 프로젝트를 배포하기로 결정하게 되었다.
백엔드분들과 어떤 형태로 배포를 할지 논의가 되었는데 선택지는 두가지였다. </p>
<blockquote>
<ol>
<li>EC2에 프론트 &amp; 백엔드 배포</li>
<li>둘이 따로 배포</li>
</ol>
</blockquote>
<p>Next로 만들었기 떄문에 프론트는 Vercel에 배포를 하고 싶었다. CI/CD, 이미지 최적화, 성능 모니터링등의 기능 게다가 무료라는게 매력적으로 느껴졌다. </p>
<p>그래서 백엔드분들께 따로 배포하자고 말씀드렸고 프론트는 Vercel에 백엔드는 EC2에 배포하게 되었다. </p>
<p>백엔드를 배포하면서 서브도메인 &amp; SSL이슈 때문에 조금 시간이 지체되었지만 문제 없이 배포할 수 있었다.</p>
<p>처음 들었던 이야기와는 달리 프로젝트를 CSS 제외하고 새로 다시 만든 것 같다...
우선 1차적인 목표였던 배포를 마무리 지을 수 있어서 뿌듯했다.</p>
<br>
<br>

<p>배포 후 이야기는 다음 포스팅에서..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[사이드 프로젝트] 59mins - 1편 ]]></title>
            <link>https://velog.io/@choosey_/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0-1%ED%83%84</link>
            <guid>https://velog.io/@choosey_/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0-1%ED%83%84</guid>
            <pubDate>Fri, 12 Aug 2022 05:44:25 GMT</pubDate>
            <description><![CDATA[<h2 id="사이드-프로젝트-소개">사이드 프로젝트 소개</h2>
<hr>
<h3 id="⭐️59mins⭐️"><strong><a href="https://disquiet.io/product/59mins"> ⭐️59mins⭐️</a></strong></h3>
<p>직장인들의 고민인 회의 시간을 보다 효율적으로 사용으로 운용하고자 만들어진 제품입니다.</p>
<img width="100%" src="https://velog.velcdn.com/images/choosey_/post/403e84f6-385a-4e16-8457-90957422ad18/image.png">




<br>


<h2 id="계기">계기</h2>
<hr>
<p>프로젝트를 처음부터 참여한 것은 아니였다. </p>
<p>같은 회사에 근무 중이신 서비스 기획, 디자이너분이 하고 계신 프로젝트였고,
프론트 개발자 나가셔서 인원이 필요했던 상황이었다.</p>
<p>기능은 어느 정도 완성이 되어있고 디자인도 수정 사항이 거의 없는 상황이라고 전달 받았고 동작 하지 않는 부분 구현 &amp; 이슈 처리와 유지 보수를 할 개발자가 필요하다라고 말씀을 해주셨다. </p>
<p>나는 이 기획, 디자이너 두분이 일을 잘하신다 생각하고 있었고 개발 스택도 마음에 들어서 무조건 하고 싶었지만 내가 책임지고 마무리 할 수 있는지에 대한 고민이 컸다.</p>
<br>

<h2 id="계획">계획</h2>
<hr>
<h3 id="1-기획-검토">1. 기획 검토</h3>
<p>주요 페이지는 4페이지였다.</p>
<ol>
<li>로그인 회원가입</li>
<li>회의 정보 세팅</li>
<li>회의 진행 (회의록 작성 / 타이머) </li>
<li>회의 완료 </li>
</ol>
<p>초기에는 Real time으로 여러 유저에게 공유되어지게 기획을 했으나 한계가 있다고 판단하여 한명의 유저가 polling을 통해 저장만 하는 형태로 최종 픽스가 되었다.</p>
<p>다만 유저들이 원하는대로 동작을 시키지 않았을때의 정의가 부족하다는 느낌이 들었고 작업하면서 기획분들과 논의를 하면서 작업해야 될 것 같았다.</p>
<h3 id="2-코드-분석">2. 코드 분석</h3>
<ul>
<li>Next</li>
<li>Redux-toolkit</li>
<li>SWR</li>
<li>Styled-Components</li>
</ul>
<p>해당 기술 스택으로 개발이 되어있었다. </p>
<p>Next가 채택 되었는데 핵심 기능인 getStaticProps, getStaticPaths, getServerSideProps은 사용하지 않았고, 단지 회의 진행 중 화면에서 전역 상태를 사용하기 위해서 Redux를 사용하는 듯한 코드였다.
거기에 styled-component로 작업이 되었는데 class로 도배가 되어있었다.</p>
<p>기능도 어느 정도 완성 되었다고 전달 받았었는데 사실 제대로 마무리 되어있는게 하나도 없고 구현되었다고 한 부분들 조차 일부분만 작업 해놓거나 예외 처리가 하나도 안되어 있어 생각보다 오래 걸릴듯한 예감이 ...😇</p>
<h3 id="3-작업-진행-방향">3. 작업 진행 방향</h3>
<p>이미 상당 부분 진행되어 있는 코드 위에 작업하는 것은 쉽지 않았고 따로 인수인계 없이 코드를 파악하려니 코드를 짠만큼의 시간이 들었다. 
그래도 빠른 마무리를 위해서 나름대로의 방향을 정했다.</p>
<ul>
<li>구현되지 않은 항목들 우선 순위로 작업</li>
<li>최대한 기존 코드 스타일을 유지하고 되도록이면 동작하는 부분들은 건드리지 않음<ul>
<li>Next 기능 추가하여 사용 이유 납득 시킬 것</li>
<li>전역관리가 크게 필요치 않은 규모라 Redux제거</li>
<li>놓친 예외, 에러 처리에 대해서 작업 예정</li>
</ul>
</li>
</ul>
<br>



<p>작업 관련 이야기는 다음 포스팅에 이어서..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[발표 자료] React-Native 후기]]></title>
            <link>https://velog.io/@choosey_/React-Native-%EA%B3%B5%EB%B6%80-%ED%9B%84%EA%B8%B0-...ing</link>
            <guid>https://velog.io/@choosey_/React-Native-%EA%B3%B5%EB%B6%80-%ED%9B%84%EA%B8%B0-...ing</guid>
            <pubDate>Fri, 15 Jul 2022 09:16:11 GMT</pubDate>
            <description><![CDATA[<h2 id="react-native를-선택한-이유-">*<em>React-Native를 선택한 이유 *</em></h2>
<p>평소 APP에도 관심이 있었고 올 10월쯤에 앱으로 사이드 프로젝트를 진행하게 될 것 같아 어떤 스펙을 선택할지 고민하게 되었습니다.</p>
<h4 id="-⭐️⭐️⭐️-선택지-⭐️⭐️⭐️">** ⭐️⭐️⭐️ 선택지 ⭐️⭐️⭐️**</h4>
<ol>
<li><strong>네이티브</strong></li>
</ol>
<ul>
<li>네이티브의 경우 IOS, AOS 따로 개발을 해야함으로 제외</li>
</ul>
<ol start="2">
<li>** Flutter**</li>
</ol>
<ul>
<li>현재 사내 Flutter로 작업하시는 분들도 계심</li>
<li>React-native보다 훨씬 네이티브앱에 준하는 성능을 낼 수 있음</li>
</ul>
<ol start="3">
<li><strong>React-Native</strong></li>
</ol>
<ul>
<li>React와 흡사하여 러닝 커브가 낮음!</li>
<li>장점이자 단점인 오픈소스 의존도</li>
</ul>
<p><img src="https://velog.velcdn.com/images/choosey_/post/344d39c5-922c-4eb2-9e57-6f4402e44d72/image.png" alt="">
(스택 오버플로우 검색 트렌드.png)
<br/>
현재 3년간 추세만 보더라도 flutter가 더 핫하고 Google이 뒤에 있다는 점이 매력적인 것은 틀림이 없었다. 
그렇지만 Dart를 배워야한다는 것에 거부감이 들었고 
최근 여러 회사들이 프론트엔드 개발자의 React-Native 경험을 원하는 추세여서 결국 React-Native를 선택하게 되었습니다.
<br/></p>
<h2 id="내가-느낀-장단점">내가 느낀 장단점</h2>
<ul>
<li>장점
  -JS로 앱을 만들 수 있다 ❤️
  -React 생태계와 흡사해서 Redux, react-query 등도 사용 가능
  -웹으로도 확인 가능
  -Expo 앱 설치시 따로 만든 앱을 내려 받지 않아도 실행시킬 수 있었음 👍🏻
  <img src="https://velog.velcdn.com/images/choosey_/post/42894fd1-835e-4a0c-9ef5-f2488caf99ca/image.jpeg" alt="">
  -코드푸쉬로 JS단 코드를 심사 없이 배포 할 수 있음<pre><code>&lt;br/&gt; </code></pre></li>
</ul>
<ul>
<li>단점
  -View쪽이 생각보다 React와 많이 달랐음. HTML 아님
  -IOS, AOS 크로스 플랫폼 체크가 생각보다 어려웠다.
  -네이티브 연동을 내가 직접하는 것을 모르다보니 라이브러리나 패키지의 의존도가 높았음 IOS는 설치시 Pod install을 매번 실행시키는게 피로 했음.
  -라이브러리 오류 추적이 생각보다 힘듦 IOS, AOS 버전 or 네이티브 수정 해야 되는 경우들이 조금씩 있었음.
  -결국 딥하게 사용하려면 네이티브 지식이 필요</li>
</ul>
<br/>       
<br/> 


<h3 id="사이드-프로젝트-시작하게-되면-다시-돌아오겠습니다">사이드 프로젝트 시작하게 되면 다시 돌아오겠습니다.</h3>
<p>
<img src="https://velog.velcdn.com/images/choosey_/post/f159b025-2ae8-4225-af7e-4b8ceec0ab8c/image.gif" width="1000" height="400" align="center"/>
</p>





]]></description>
        </item>
        <item>
            <title><![CDATA[
[JS] ES6 ~ ES13 문법 ]]></title>
            <link>https://velog.io/@choosey_/JS6</link>
            <guid>https://velog.io/@choosey_/JS6</guid>
            <pubDate>Sun, 29 May 2022 06:48:55 GMT</pubDate>
            <description><![CDATA[<h2 id="자주-사용할-것-같은-문법들">자주 사용할 것 같은 문법들</h2>
<hr>
<h3 id="nullish-coalescing-operator">Nullish Coalescing Operator</h3>
<p>값이 있는지 없는지 판단</p>
<pre><code class="language-js">
const val = 0
const nullish = val ?? &#39;Null&#39; // 0 

</code></pre>
<h3 id="stringprototypetrimstart-trimend">String.prototype.trimStart, trimEnd</h3>
<p>trim을 좀 더 세부적으로</p>
<pre><code class="language-js">
//trimStart()
&#39; trimStart&#39;.trimStart(); // &#39;trimStart&#39;
&#39;trimStart &#39;.trimStart(); // &#39;trimStart &#39;

//trimEnd()
&#39;trimEnd &#39;.trimEnd(); // &#39;trimEnd&#39;
&#39; trimEnd&#39;.trimStart(); // &#39; trimEnd&#39;

</code></pre>
<h3 id="stringprototypereplaceall">String.prototype.replaceAll()</h3>
<p>replace를 해당 모든 문자열에 사용</p>
<pre><code class="language-js">//.replace(/문자/g,&quot;&quot;)) === .replaceAll(&quot;문자&quot;,&quot;&quot;));

console.log(&quot;BANANA.&quot;.replace(/A/g,&quot;a&quot;));
// &quot;BaNaNa&quot;


console.log(&quot;BANANA.&quot;.replaceAll(&quot;A&quot;,&quot;a&quot;));
// &quot;BaNaNa&quot;

</code></pre>
<h3 id="arrayprototypeflat-flatmap">Array.prototype.flat(), flatMap()</h3>
<p>중첩 배열, 빈공간 삭제</p>
<pre><code class="language-js">const arr = [&quot;a&quot;, [&quot;b&quot;], ,[&quot;c&quot;]];

arr.flat() // [&quot;a&quot;,&quot;b&quot;,&quot;c&quot;] 빈요소가 있으면 무시

let arr1 = [1, 2, 3, 4];

arr1.flatMap(x =&gt; [x * 2]); // [2, 4, 6, 8]
</code></pre>
<h2 id="promise">Promise</h2>
<hr>
<h3 id="promiseall">Promise.all</h3>
<p>모든 promise 이행 후 값 return 
하나의 promise라도 실패시 reject된다.</p>
<pre><code class="language-js">const promise1 = new Promise((resolve) =&gt; {
  setTimeout(() =&gt; {
    resolve(1);
  }, 1000);
});

const promise2 = new Promise((resolve) =&gt; {
  setTimeout(() =&gt; {
    resolve(2);
  }, 1000);
});

const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
  .then((result) =&gt; console.log(result))
  .catch((e) =&gt; console.error(e));

//

[1,2,3]
</code></pre>
<h3 id="promiseallsettled">Promise.allSettled</h3>
<p>모든 promise 이행 후 각 결과와 값 return
promise 실패시에도 값 반환 </p>
<pre><code class="language-js">
const promise1 = new Promise((resolve) =&gt; {
    setTimeout(() =&gt; {
        resolve(1);
    }, 3000);
});

const promise2 = new Promise((resolve) =&gt; {
    setTimeout(() =&gt; {
        resolve(2);
    }, 1000);
});

const promise3 = Promise.reject(new Error(&quot;err&quot;));

Promise.allSettled([promise1, promise2, promise3]).then((results) =&gt; {
    results.forEach((result) =&gt; {
          console.log(result);
    });
});


//return 
{status: &#39;fulfilled&#39;, value: 1}
{status: &#39;fulfilled&#39;, value: 2}
{status: &#39;rejected&#39;, reason: Error: err
</code></pre>
<p><strong>-계속 추가 예정-</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next query에 파비콘이 담겨오는 에러]]></title>
            <link>https://velog.io/@choosey_/Next-getServerSideProps</link>
            <guid>https://velog.io/@choosey_/Next-getServerSideProps</guid>
            <pubDate>Sat, 16 Apr 2022 01:26:38 GMT</pubDate>
            <description><![CDATA[<p>혹시나 Next.js getServerSideProps에서 Dynamic routing page에 params, query에서 [id]를 자겨오고 싶은데 두번째 렌더링시에 id에 favicon.ico가 담겨서 오는 경우가 있었습니다.</p>
<p>이땐 pulic directory에 favicon.ico를 추가해주시고_document에 /favicon.ico을 추가해주시면 해결하실 수 있으실거에요!
해결이 안되면 혹시 _app이나 다른 곳에 favicon.ico가 들어있지는 않은지 한번 더 확인을 해보시길..</p>
<pre><code>
 &lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&gt;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] 토큰 인증]]></title>
            <link>https://velog.io/@choosey_/token</link>
            <guid>https://velog.io/@choosey_/token</guid>
            <pubDate>Fri, 10 Dec 2021 04:36:59 GMT</pubDate>
            <description><![CDATA[<h1 id="토큰-인증">토큰 인증</h1>
<hr>
<h2 id="인증-방식">인증 방식</h2>
<blockquote>
<ol>
<li>사용자 로그인</li>
<li>서버 측에서 사용자(클라이언트)에게 유일한 토큰을 발급한다.</li>
<li>클라이언트는 서버 측에서 전달받은 토큰을 쿠키나 스토리지에 저장해 두고, 서버에 요청을 할 때마다 해당 토큰을 서HTTP 요청 헤더에 포함시켜 전달한다.</li>
<li>서버는 전달받은 토큰을 검증하고 유효시 처리</li>
</ol>
</blockquote>
<h2 id="장단점">장단점</h2>
<p>토큰 기반 인증은 세션과는 다르게 상태 정보를 유지하지 않는다. 또한, 서버가 전달받은 토큰을 검증만 하면 되기 때문에 <strong>서버의 부담을 줄이고 서비스의 확장성을 높일 수 있다.</strong></p>
<p>하지만 토큰은 세<strong>션에 비해 데이터가 무겁고 보안적인 단점이 있다.</strong>
<strong>XSS, CSRF 공격을 통해 해커에게 쉽게 탈취</strong> 당할 수 있다는 점이다. 
탈취를 당하면 유효기간 동안 서버는 어찌할 방법이 없다.</p>
<p>이러한 보안 문제를 위해 보통은 <strong>Access Token</strong>과 <strong>Refresh Token</strong>을 함께 사용한다.</p>
<h2 id="access-token--refresh-token">Access Token &amp; Refresh Token</h2>
<p>하나의 토큰 (Access Token) 만을 통한 인증 방식의 문제는 탈취당할 경우 만료까지 누구나 접근 가능해서 보안에 취약하다는 점이다.</p>
<p>그렇다면 유효시간을 짧게하면 되지 않을까?</p>
<p>이렇게 되면 사용자는 잦은 로그아웃을 경험하게 된다.
이를 해결하기 위해 Refresh Token이 같이 쓰이게 된다.</p>
<p>둘다 같은 토큰이지만 Access Token은 접근을 위함이고 Refresh Token은 Access Token을 발급 받기 위함이다.</p>
<p>최초 로그인시 둘다 발급되고 Access Token의 시간을 짧게 설정하여 만료시 긴 만료시간을 가진 Refresh Token을 이용하여 재발급 받는 형태로 최근에는 많이 사용되어지고 있다.</p>
<h3 id="저장위치">저장위치</h3>
<p>access token과 refresh token에 저장 위치에도 갑론을박이 많다.</p>
<ol>
<li>access token은 local storage에 refresh token은 쿠키 <ul>
<li>access token은 XSS 공격에 취약<br/></li>
</ul>
</li>
<li>access token, refresh token 둘다 cookie<ul>
<li>CSRF 취약 XSS에선 조금 더 나음<br/></li>
</ul>
</li>
<li>access token은 variable , refresh token은 쿠키에 저장.<ul>
<li>공격자가 response를 읽을 수 없어 access token이 보호된다라고 한다. 빈번하게 access token이 요청될 것 같긴한데 확실히 보안이 된다면 이 방법이 좋을 것 같다.  추후 토이 프로젝트를 하면서 적용해봐야겠다.</li>
</ul>
</li>
</ol>
<h2 id="마치며">마치며</h2>
<p>cookie &amp; session , token 인증에 대해서 알아봤는데 보안에 대해서는 아직 잘 몰라서 어떤 방식을 채택하는게 좋을지는 회사나 프로젝트 사정에 따라서 다를 것 같다. 다만 아직 cookie &amp; session 방식과 토큰의 경우 access token을 변수로 처리하는 방법은 해보지 않아서 토이프로젝트를 진행하면서 경험해보고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] Closure]]></title>
            <link>https://velog.io/@choosey_/clousre</link>
            <guid>https://velog.io/@choosey_/clousre</guid>
            <pubDate>Tue, 07 Dec 2021 11:06:01 GMT</pubDate>
            <description><![CDATA[<h1 id="closure-클로저란">Closure (클로저)란?</h1>
<p>MDN에서 정의한 클로저다.</p>
<blockquote>
</blockquote>
<p>“A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수와 그 함수가 선언됐을 때의 <strong>렉시컬 환경</strong>(Lexical environment)과의 조합이다.</p>
<p>사실 이 정의만 보고서는 제대로 이해가 안간다.
그래서 코드로 알아보도록 하자</p>
<pre><code class="language-js">function outerFunc() {
  var x = 10;
  var innerFunc = function () { console.log(x); };
  return innerFunc;
}

var inner = outerFunc(); 
inner(); // 10</code></pre>
<blockquote>
<p>스코프는 함수를 어디에 선언했는지에 따라 결정된다.
이를 <strong>렉시컬 스코핑(Lexical scoping)</strong>라 한다.<br/>
innerFunc의 상위 스코프는 outerFunc이며 outerFunc의 상위 스코프는 전역 스코프가 된다.
이떄 innerFunc는 자신의 상위스코프인 outerFunc, 전역에 접근할 수 있다.</p>
</blockquote>
<p>다시 MDN 정의로 돌아가보면 “함수”란 반환된 내부함수를 의미하고 “그 함수가 선언될 때의 렉시컬 환경(Lexical environment)”란 내부 함수가 선언됐을 때의 스코프를 의미한다. 즉,** 클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수**를 말한다.</p>
<p>더 간단히 말하면 <strong>클로저는 자신이 생성될 때의 환경(Lexical environment)을 기억하는 함수</strong>다라고 말할 수 있다.</p>
<p>그렇다면 클로저는 어디서 활용하는게 좋을까?</p>
<h2 id="상태유지">상태유지</h2>
<p>클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다.</p>
<pre><code class="language-js">var toggleBtn = document.querySelector(&#39;.toggle&#39;);

var toggle = (function () {
  var isShow = false;

  // ① 클로저를 반환
  return function () {
    box.style.display = isShow ? &#39;block&#39; : &#39;none&#39;;
        // ③ 상태 변경
    isShow = !isShow;
  };
})();

toggleBtn.onclick = toggle; // 클로저 할당</code></pre>
<p>toggle은 함수를 반환하고 즉시 소멸한다. toggle은 렉시컬 환경에 속한 변수 isShow를 기억하는 클로저다.</p>
<p>이처럼 클로저는 상태가 변경되어도 최신 상태를 유지해야 하는 상황에 매우 유용하다.
만약 자바스크립트에 클로저라는 기능이 없다면 상태를 유지하기 위해 전역 변수를 사용할 수 밖에 없다.</p>
<h2 id="전역-변수-사용-억제">전역 변수 사용 억제</h2>
<pre><code class="language-js">
var increase = (function () {
 // 카운트 상태를 유지하기 위한 자유 변수
  var counter = 0;
// 클로저를 반환
  return function () {
    return ++counter;
  };
}());


incleaseBtn.onclick = function () {
    count.innerHTML = increase();
};
</code></pre>
<p>즉시 실행 함수가 호출되며 ++counter가 할당되어있는 함수는 자신이 생성되었을 때의 렉시컬 환경(Lexical environment)을 기억하는 클로저다. 즉시실행함수는 호출된 이후 소멸되지만 즉시실행함수가 반환한 함수는 변수 increase에 할당되어 버튼 클릭시 호출된다.</p>
<p>변수 counter는 외부에서 직접 접근할 수 없는 <strong>private</strong> 변수이므로 전역 변수를 사용했을 때와 같이 의도되지 않은 변경을 걱정할 필요도 없기 때문이 <strong>보다 안정적인 프로그래밍이 가능</strong></p>
<p><br/><br/><br/></p>
<p>출처: 모던 자바스크립트 딥다이브 발췌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] Built-in (내장 객체)?]]></title>
            <link>https://velog.io/@choosey_/Javascript-%EC%A0%95%EC%88%98%EC%8B%A4%EC%88%98</link>
            <guid>https://velog.io/@choosey_/Javascript-%EC%A0%95%EC%88%98%EC%8B%A4%EC%88%98</guid>
            <pubDate>Mon, 06 Dec 2021 13:00:07 GMT</pubDate>
            <description><![CDATA[<h1 id="built-in이란">Built-in이란?</h1>
<hr>
<p>Javascript 코드를 처리하는 영역에 값 타입, 연산자, object를 사전에 만들어 놓은 것이다.</p>
<p>사전 처리를 하지 않고 즉시 사용할 수 있다는 장점이 있다. (Javascript 특징)</p>
<h2 id="1-값-타입">1. 값 타입</h2>
<pre><code>Undefined, Null, Boolean, Number, String, Object</code></pre><h2 id="2-연산자">2. 연산자</h2>
<pre><code>+, -, *, /, %, ++, --, new 등등..</code></pre><h2 id="3-오브젝트">3. 오브젝트</h2>
<pre><code>해당 오브젝트 처리를 위한 프로퍼티 집합 오브젝트들</code></pre><h3 id="number">Number</h3>
<pre><code class="language-js">
let number = new Number(1.2222);

//toFixed() : 소수의 자리수의 길이를 제한함
number.toFixed(2); // 1.22

//toPrecision(): 수의 길이를 제한함
number.toPrecision(1); // 1

const obj = Number

obj.isNaN(); //false

//이외에도 MAX_SAFE_INTEGER, MAX_VALUE, MIN_SAFE_INTEGER, MIN_VALUE, length 등이 존재
</code></pre>
<h3 id="string">String</h3>
<pre><code class="language-js">let str = new String(&quot;String&quot;)

str.length; // 6

//chartAt(index): 해당 인덱스 문자 반환
str.charAt(2); // r  

//concat(txt, add): 합친 문자열 반환
str.concat(&#39; &#39;, &#39;number&#39;); // String number

//split(txt): 받은 문자단위로 문자열 배열로 반환
let txt = &quot;Javascript Built in Object&quot;;
txt.split(&quot; &quot;); //[&quot;Javascript&quot;, &quot;Built&quot;, &quot;in&quot;, &quot;Object&quot; ]

//subString(): 시작, 끝 문자열 반환
txt.subString(start,end) // Jav

//toUpperCase(), toLowerCase() 대소문자로 변경하여 반환
txt.toUpperCase() // JAVASCRIPT BUILT IN OBJECT
txt.toLowerCase() // javascript built in object

//이외에도 replace, indexOf, lastIndexOf 등이 존재 </code></pre>
<h3 id="array">Array</h3>
<pre><code class="language-js">let arr = [1,2,3,4,5]
//push(): 배열에 새로운 데이터 추가
arr.push(6) // arr = [1,2,3,4,5,6]

//pop() 배열의 마지막 요소 제거
arr.pop() // arr = [1,2,3,4,5]

//reverse()
arr.reverse() // [5,4,3,2,1]

//slice(start,end) start부터 end 사이 값 Return 원본 배열 유지
arr.slice(0,1)// [1]


//splice(start,count) index부터 count개 return 원본 배열에서도 삭제

arr.splice(0,2) // [1,2], arr = [3,4,5]

//이외에도 sort, join, indexOf 등 존재</code></pre>
<h2 id="boolean">Boolean</h2>
<pre><code class="language-js">const isTrue = true

//toStirng string 값으로 return
isTrue.toString = &quot;true&quot;
</code></pre>
<h2 id="date">Date</h2>
<pre><code class="language-js">let date = new Date(2021, 12)

//getFullYear 연도 반환
date.getFullYear() // 2021

//getMonth 월 반환 (+1해야함)
date.getMonth() // 11 

//이외에도 getDate, getDay, getHours 등이 존재</code></pre>
<h2 id="global">Global</h2>
<pre><code>- new 연산자로 인스턴스 생성 불가
- 이름은 있지만 오브젝트 실체가 없음</code></pre><pre><code class="language-js">
//isFinite(): 유한수인지 검사하여 boolean return
isFnite(&#39;a&#39;) //false
isFnite(11) // true

//isNaN(): Not a number 인지 검사
isNaN(&#39;string&#39;) // true
isNaN(33) // false
isNaN(true) // false true -&gt; 1
isNaN(null) // false null -&gt; 0

//parseInt 문자열을 정수형 숫자로 변환
parseInt()
// 이외에도 eval, parseFloat, encodeURI, decodeURI등이 존재</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] Cookie와 Session ]]></title>
            <link>https://velog.io/@choosey_/cookiesession</link>
            <guid>https://velog.io/@choosey_/cookiesession</guid>
            <pubDate>Mon, 06 Dec 2021 08:49:50 GMT</pubDate>
            <description><![CDATA[<h2 id="cookie--session">Cookie &amp; Session</h2>
<hr>
<p>HTTP 프로토콜의 특징이자 약점을 보완하기 위해서 사용한다.</p>
<h3 id="http-프로토콜의-특징">HTTP 프로토콜의 특징</h3>
<blockquote>
<p>** 비연결지향(Connectionless)**</p>
</blockquote>
<ul>
<li>HTTP는 클라이언트가 요청(Request)을 서버에 보내고, 서버는 클라이언트에게 적절한 응답(Response)을 주고 연결(Connection)을 끊는 특성이 있다.</li>
</ul>
<blockquote>
<p><strong>상태없음(Stateless)</strong></p>
</blockquote>
<ul>
<li>커넥션을 끊는 순간 클라이언트와 서버의 통신이 끝나며 상태 정보는 유지하지 않는 특성이 있다.</li>
</ul>
<p>HTTP는 이 두 가지 특성을 보완하기 위해서 cookie,session을 사용하게 되었다.
만약 쿠키와 세션이 없다면 페이지 이동마다 인증을 해야할수도 있다.</p>
<h3 id="session">Session</h3>
<p>session은 서버에 클라이언트 정보를 저장, 구분할 수 있는 ID를 만들어 부여하는데 이를 session id라고 한다.</p>
<h3 id="cookie">Cookie?</h3>
<p>쿠키는 클라이언트단에 저장되는 key, value 값이다. 만료시간/날짜를 정하지 않으면 세션쿠키로 정해져 메모리에 남아있는(종료시점)까지 유효하다.
만료시간/날짜를 지정하면 메모리에서 사라지더라도 만료까지 유효한 지속쿠키로 저장된다.</p>
<h2 id="인증-방식">인증 방식</h2>
<blockquote>
<ol>
<li>사용자 로그인</li>
<li>서버에서 유효한지 확인 후 유효시 세션ID 저장 후 header로 발급</li>
<li>클라이언트 세션ID 쿠키에 저장</li>
<li>API 요청시 서버는 쿠키에 저장된 세션ID 검증 후 처리</li>
</ol>
</blockquote>
<h2 id="장단점">장단점</h2>
<p>쿠키로만 인증을 할 경우 탈취가 너무 쉽기 때문에 세션과 결합하여 인증한다.</p>
<p>쿠키 &amp; 세션은 서버에서 세션 저장소를 사용하기 때문에 부하가 높아지고 최근에는 서버가 분산되고 이에 따라서 세션ID 처리 에러가 난다던지 소셜 인증 같은 부분들에 한계가 있다.
이를 보완하기 위해 최근 자주 사용되는 토큰 기반 인증이 자주 사용된다고 한다. </p>
<br/>
<br/>
다음엔 토큰에 대해서 알아보자



]]></description>
        </item>
        <item>
            <title><![CDATA[정규표현식]]></title>
            <link>https://velog.io/@choosey_/%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D</link>
            <guid>https://velog.io/@choosey_/%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D</guid>
            <pubDate>Mon, 06 Dec 2021 07:20:53 GMT</pubDate>
            <description><![CDATA[<h3 id="groups-and-ranges">Groups and ranges</h3>
<table>
<thead>
<tr>
<th>Chracter</th>
<th>뜻</th>
</tr>
</thead>
<tbody><tr>
<td><code>|</code></td>
<td>또는</td>
</tr>
<tr>
<td><code>()</code></td>
<td>그룹</td>
</tr>
<tr>
<td><code>[]</code></td>
<td>문자셋, 괄호안의 어떤 문자든</td>
</tr>
<tr>
<td><code>[^]</code></td>
<td>부정 문자셋, 괄호안의 어떤 문가 아닐때</td>
</tr>
<tr>
<td><code>(?:)</code></td>
<td>찾지만 기억하지는 않음</td>
</tr>
</tbody></table>
<h3 id="quantifiers">Quantifiers</h3>
<table>
<thead>
<tr>
<th>Chracter</th>
<th>뜻</th>
</tr>
</thead>
<tbody><tr>
<td><code>?</code></td>
<td>없거나 있거나 (zero or one)</td>
</tr>
<tr>
<td><code>*</code></td>
<td>없거나 있거나 많거나 (zero or more)</td>
</tr>
<tr>
<td><code>+</code></td>
<td>하나 또는 많이 (one or more)</td>
</tr>
<tr>
<td><code>{n}</code></td>
<td>n번 반복</td>
</tr>
<tr>
<td><code>{min,}</code></td>
<td>최소</td>
</tr>
<tr>
<td><code>{min,max}</code></td>
<td>최소, 그리고 최대</td>
</tr>
</tbody></table>
<h3 id="boundary-type">Boundary-type</h3>
<table>
<thead>
<tr>
<th>Chracter</th>
<th>뜻</th>
</tr>
</thead>
<tbody><tr>
<td><code>\b</code></td>
<td>단어 경계</td>
</tr>
<tr>
<td><code>\B</code></td>
<td>단어 경계가 아님</td>
</tr>
<tr>
<td><code>^</code></td>
<td>문장의 시작</td>
</tr>
<tr>
<td><code>$</code></td>
<td>문장의 끝</td>
</tr>
</tbody></table>
<h3 id="character-classes">Character classes</h3>
<table>
<thead>
<tr>
<th>Chracter</th>
<th>뜻</th>
</tr>
</thead>
<tbody><tr>
<td><code>\</code></td>
<td>특수 문자가 아닌 문자</td>
</tr>
<tr>
<td><code>.</code></td>
<td>어떤 글자 (줄바꿈 문자 제외)</td>
</tr>
<tr>
<td><code>\d</code></td>
<td>digit 숫자</td>
</tr>
<tr>
<td><code>\D</code></td>
<td>digit 숫자 아님</td>
</tr>
<tr>
<td><code>\w</code></td>
<td>word 문자</td>
</tr>
<tr>
<td><code>\W</code></td>
<td>word 문자 아님</td>
</tr>
<tr>
<td><code>\s</code></td>
<td>space 공백</td>
</tr>
<tr>
<td><code>\S</code></td>
<td>space 공백 아님</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] 단방향  , 양방향  데이터 바인딩]]></title>
            <link>https://velog.io/@choosey_/Js4</link>
            <guid>https://velog.io/@choosey_/Js4</guid>
            <pubDate>Sat, 04 Dec 2021 09:44:58 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터-바인딩">데이터 바인딩</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/choosey_/post/ad959fce-3c81-484b-807d-fd760f57be7e/image.png" alt="">
데이터 바인딩이란 화면상에서 보여지는 데이터와 브라우저 메모리에 있는 데이터를 동기화 하는 것이다.
즉 HTML(View)에서 서버나 스크립트(Model) 데이터를 화면상에 그려주고 있는 상황에서 해당 데이터가 변경되었을 때 그에 맞춰 변경하는것이라고 생각하면된다. </p>
<h2 id="단방향-데이터-바인딩">단방향 데이터 바인딩?</h2>
<p>단방향 데이터 바인딩이란 한쪽 방향으로만 제어가 가능한 것을 의미합니다.
주로 사용하고 있는 React가 단방향 바인딩을 사용하고 있는데 단 데이터와 템플릿을 결합해 화면을 생성한다.** (JS -&gt; HTML만 가능)**
사용자의 입력에 따라 데이터가 갱신되기 때문에 데이터의 변화 동작이 있을때 업데이트하는 코드를 매번 작성해야하는 단점이 있다. 반면에 디버깅이 쉽고 성능적으로 양방향 보다 우수하다는 장점이 있다. </p>
<h2 id="양방향-데이터-바인딩">양방향 데이터 바인딩?</h2>
<p>단방향과 반대되는 바인딩 형태이다.
HTML -&gt; JS, JS -&gt; HTML 모두 가능한 형태로 Angular와 Vue가 이에 해당한다. 코드 사용면에서 이점이 있고 다만 watcher가 많아지게 될 경우 성능저하가 일어날 수 있다.</p>
<h2 id="결론">결론</h2>
<p>React, Vue를 모두 사용해봤는데 확실히 Vue를 사용할 때가 더 코드가 간결했던 경험이 있었는데 이 떄문이였던 것 같다. 그리고 watch를 빈번하게 사용하면 성능이 저하된다고 인강에서 봤었는데 양방향 데이터 바인딩이 많아지기 떄문이라는걸 이제 깨달았다.</p>
<p>일차원적으로 생각하면 Vue는 양방향, 단방향 모두 사용 가능하다고 판단이 되어서 이 부분만 놓고 보면 Vue가 더 좋은거 아닌가라는 생각이 든다..🤣</p>
<p>하지만 일관된 아키텍처를 선호하고 프로젝트가 커지면 당연히 그래야 된다고 생각하기 때문에 React가 나에겐 더 reasonable한 선택이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GraphQL]]></title>
            <link>https://velog.io/@choosey_/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%8C%81</link>
            <guid>https://velog.io/@choosey_/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%8C%81</guid>
            <pubDate>Fri, 18 Jun 2021 09:10:23 GMT</pubDate>
            <description><![CDATA[<h1 id="graphql이란">GraphQL이란?</h1>
<hr>
<p>GraphQL은 SQL과 마찬가지로 <strong>쿼리 언어</strong>입니다. 
SQL은 데이터베이스 시스템에 저장된 데이터를 효율적으로 가져오는 것이 목적이고, GQL은 웹 클라이언트가 데이터를 서버로 부터 효율적으로 가져오는 것이 목적입니다. 
SQL의 문장(statement)은 주로 <strong>백앤드 시스템에서 작성하고 호출</strong> 하는 반면, GQL의 문장은 주로 <strong>클라이언트 시스템에서 작성하고 호출</strong> 합니다.</p>
<p>SQL</p>
<pre><code>SELECT id name FROM user</code></pre><p>GQL</p>
<pre><code>{
  user {
      id
    name
  }
}</code></pre><h2 id="rest-api-vs-graphql">REST API VS GraphQL</h2>
<p>REST API는 URL, METHOD등을 조합하기 때문에 Endpoint가 다양하다.
GQL은 단 하나의 Endpoint가 존재한다.</p>
<p><strong>REST API는 EndPoint</strong>에 따라 데이터가 달라지고 <strong>GQL은 요청 스키마</strong>에 따라 데이터가 달라진다.</p>
<h2 id="resolver">Resolver?</h2>
<p>GQL의 핵심 개념으로 쉽게 말해서 쿼리를 해결하는 기능
스키마에서 정의한 각각의 필드마다, 함수를 하나씩 구현한다고 생각하면 된다.</p>
<h2 id="단점은">단점은?</h2>
<ul>
<li>캐싱이 복잡</li>
<li>파일 업로드 처리가 어려움 </li>
</ul>
<h2 id="정리">정리</h2>
<p>REST API처럼 데이터에 따라서 요청을 여러번 보내지 않기 때문에 확실히 퍼포먼스적인 이점이 있을 것이다.
그렇다고 무조건적으로 사용한다라기보단 프로젝트의 성격에 맞춰 선택하는 것이 바람직하다.</p>
<p>프론트엔드 개발자로써 GraphQL을 사용하게 되면 서버단의 로직이 프론트로 내려오게 되어 공부 범위가 늘어날 것 같은 예감이다.😇</p>
]]></description>
        </item>
    </channel>
</rss>