<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>열정보단 시스템</title>
    <link>https://0biglife.tistory.com/</link>
    <description>열정은 기본/ 시스템이 갖춰진 개발자가 되자</description>
    <language>ko</language>
    <pubDate>Sun, 28 Jun 2026 08:43:04 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>0BigLife</managingEditor>
    <image>
      <title>열정보단 시스템</title>
      <url>https://tistory1.daumcdn.net/tistory/4473961/attach/cb94c36332884b49b5d52ef3f2e5b909</url>
      <link>https://0biglife.tistory.com</link>
    </image>
    <item>
      <title>블로그를 이사했습니다.</title>
      <link>https://0biglife.tistory.com/entry/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%9D%B4%EC%82%AC%ED%96%88%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 0biglife 입니다.&lt;br /&gt;기술 블로그를 &lt;a title=&quot;0biglife.com&quot; href=&quot;https://www.0biglife.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;0biglife.com&lt;/a&gt; 으로 옮기게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1443&quot; data-origin-height=&quot;945&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JU1jV/btsPthyTr2Q/0Q2cGBHRC8C4Vpk0KNWJN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JU1jV/btsPthyTr2Q/0Q2cGBHRC8C4Vpk0KNWJN1/img.png&quot; data-alt=&quot;https://www.0biglife.com&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JU1jV/btsPthyTr2Q/0Q2cGBHRC8C4Vpk0KNWJN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJU1jV%2FbtsPthyTr2Q%2F0Q2cGBHRC8C4Vpk0KNWJN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1443&quot; height=&quot;945&quot; data-origin-width=&quot;1443&quot; data-origin-height=&quot;945&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.0biglife.com&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;감사합니다.&lt;/p&gt;</description>
      <category>Entj Life</category>
      <category>0biglife</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/72</guid>
      <comments>https://0biglife.tistory.com/entry/%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%9D%B4%EC%82%AC%ED%96%88%EC%8A%B5%EB%8B%88%EB%8B%A4#entry72comment</comments>
      <pubDate>Wed, 23 Jul 2025 00:43:30 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 너네 Next.js 왜 써? (with. SSR, SSG, CSR)</title>
      <link>https://0biglife.tistory.com/entry/Why-Nextjs-with-SSR-SSG-CSR</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;645&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTdMj0/btsIyzkpG1E/J5NeyAMpf3ZEc8znfYMgs0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTdMj0/btsIyzkpG1E/J5NeyAMpf3ZEc8znfYMgs0/img.jpg&quot; data-alt=&quot;멋지죠 우리 제노스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTdMj0/btsIyzkpG1E/J5NeyAMpf3ZEc8znfYMgs0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTdMj0%2FbtsIyzkpG1E%2FJ5NeyAMpf3ZEc8znfYMgs0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;683&quot; height=&quot;614&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;645&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;멋지죠 우리 제노스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;여태 웹 애플리케이션 개발에서는 다양한 렌더링 기법을 사용하여 성능을 최적화하고 사용자 경험을 향상시키는 데 집중되고 있습니다. 오늘 게시글에서는 이러한 렌더링 기법들인 &lt;b&gt;SSR&lt;/b&gt;(Server Side Rendering), &lt;b&gt;SSG&lt;/b&gt;(Static Site Generation/쓱 아닙니다), &lt;b&gt;CSR&lt;/b&gt;(Client Side Rendering)에 대해 이해해보고, 각 기법의 장단점과 간단한 예제를 살펴보려고 합니다. 먼저 전통적인 웹 동작 방식부터 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;전통적인 웹 애플리케이션은 &lt;b&gt;MPA&lt;/b&gt;(Multi-Page Application)방식으로 동작했습니다. 사용자가 탭이나 링크를 클릭할 때마다 서버로부터 새로운 HTML을 받아와서 페이지 전체를 렌더링하며, 한 페이지에서 다른 페이지로 이동할 때 전체 콘텐츠를 로드해야하기 때문에 많은 페이지가 연결된 애플리케이션에게는 느려지는 이슈는 필연적이었습니다. 이를 해결하기 위하여 2000년대 초반에 AJAX를 통하여 개선하려했지만 페이지 복잡성 증가와 같은 역효과를 되려 가져왔고, 약 10년 후에서야 MPA와 AJAX의 업그레이드 버전인 SPA 모델이 출시되었습니다. 모던 웹의 패러다임인 &lt;b&gt;SPA&lt;/b&gt;(Single Page Application)은 말 그대로 하나의 페이지로 구성된 웹 애플리케이션이며, 서버로부터 페이지 전체를 불러오지 않고 현재 페이지를 동적으로 작성하여 브라우저에 렌더링하는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;CSR&lt;/b&gt;(Client Side Rendering)&lt;/span&gt;방식으로 동작됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ri6qO/btsIxjbVFwU/0cn4lyE4CeJqYV3Ty2R5KK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ri6qO/btsIxjbVFwU/0cn4lyE4CeJqYV3Ty2R5KK/img.jpg&quot; data-alt=&quot;SPA vs MPA&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ri6qO/btsIxjbVFwU/0cn4lyE4CeJqYV3Ty2R5KK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fri6qO%2FbtsIxjbVFwU%2F0cn4lyE4CeJqYV3Ty2R5KK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;320&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SPA vs MPA&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;위 사진은 SPA와 MPA 동작 방식의 차이점을 한눈에 보여줘서 가져와봤음니다. &lt;b&gt;SPA&lt;/b&gt; 방식에 대해 검색해보면 많은 분들이 '&lt;u&gt;SPA의 핵심 가치는 사용자 경험(UX) 향상에 있으며, 애플리케이션 속도 향상도 기대할 수 있어 &lt;span style=&quot;color: #1a5490;&quot;&gt;Mobile First 전략에 부합한다&lt;/span&gt;&lt;/u&gt;'고 나옵니다. 이 말에 대해 이해가 안되어 부연 설명을 해보자면, SPA는 다양한 디바이스와 해상도를 고려할 수 있고 반응형 디자인을 쉽게 구현할 수 있어, 모바일 환경에서도 일관된 사용자 경험을 제공해주고 모바일 앱과 동일한 백엔드 코드를 재사용할 수 있기 때문에 개발 및 유지보수가 용이합니다. 이 두 가지 요점을 통하여 모바일 퍼스트 전략을 구현하는 것이 장점이라는 의미로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;반면, &lt;b&gt;MPA&lt;/b&gt; 방식도 장점이 있습니다. MPA는 새로운 페이지를 요청할 때마다 서버에서 렌더링된 HTML, CSS, Javascript와 같은 정적 리소스가 다운로드되는 &lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;SSR&lt;/b&gt;(Server Side Rendering)&lt;/span&gt;방식으로 렌더링됩니다. 여러 페이지를 생성하여 많은 수의 키워드를 타겟팅할 수 있기 때문에 Google에서 검색을 통하여 접근할 수 있는 트래픽 양이 늘어나기 마련입니다. 페이지별 HTML 파일이 존재하여 페이지를 크롤링하기 때문에 검색 엔진이 작동하는 방식으로 &lt;b&gt;SEO&lt;/b&gt;(Search ENgine Optimization: 검색 최적화)에 친화적입니다. 추가적으로 서버에서 미리 만들어진 HTML을 전송받기 때문에 초기 로딩 속도가 빠르다는 장점도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SSR&lt;/b&gt; (Server-Side Rendering)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;SEO 친화적: &lt;/b&gt;서버에서 페이지가 완전히 렌더링된 후 클라이언트에 전달되기 때문에 검색 엔진이 콘텐츠를 쉽게 크롤링하고 인덱싱할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빠른 초기 로딩 속도&lt;/b&gt;: 초기 페이지 로드 시 이미 렌더링된 HTML을 전달받기 때문에, 클라이언트 측에서 빠르게 화면에 표시됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt; 페이지 이동시 느린 속도:&lt;/b&gt;&lt;span&gt; 페이지 이동시 전체 페이지를 다시 렌더링하기 때문에 속도에 영향을 받습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발 복잡성 증가&lt;/b&gt;:  클라이언트 측과 서버 측 모두에 프레임워크를 사용해야 하기 때문에 개발 시간이 더 소요됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CSR &lt;/b&gt;(Client-Side Rendering)&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 부하 감소(응답속도)&lt;/b&gt;: 초기 요청에 필요한 HTML만 전달하고, 나머지 렌더링 작업은 클라이언트에서 수행하기 때문에 서버의 부하가 줄어듭니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동적 콘텐츠 처리 용이&lt;/b&gt;:  클라이언트 측에서 자바스크립트를 통해 동적 콘텐츠를 업데이트할 수 있기 떄문에 실시간 데이터 처리에 유리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발 효율성&lt;/b&gt;: 일관된 개발 환경을 제공하여 클라이언트 측 로직을 한 곳에서 관리할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;초기 로드 타임 증가&lt;/b&gt;: 초기 로드 시 필요한 파일을 모두 로드 및 실행해야하기 때문에 초기 페이지 로딩시 사용자가 대기해야합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SEO(검색 엔진 최적화) 문제 &lt;/b&gt;: 검색엔진에 대한 크롤링이 되지 않아 색인이 되지 않는 문제가 발생합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;그렇다면 우리는 SPA의 CSR 방식과 MPA의 SSR 방식의 이점을 모두 취할 수 없을까? 모바일 친화적이고 빠른 속도 및 응답속도 사용자 경험을 향상시켜주는 CSR의 장점과 빠른 초기 구동 속도와 SEO 친화적인 SSR을 동시에 쓸 수 없을까? 그 이유를 Next.js 가 해소시켜줍니다.!&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lPtOa/btsIyxArFiC/HaB25revwcN18YetYbbf7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lPtOa/btsIyxArFiC/HaB25revwcN18YetYbbf7k/img.png&quot; data-alt=&quot;Next.js : &amp;quot;훗.&amp;quot;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lPtOa/btsIyxArFiC/HaB25revwcN18YetYbbf7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlPtOa%2FbtsIyxArFiC%2FHaB25revwcN18YetYbbf7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;377&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;571&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Next.js : &quot;훗.&quot;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;Next.js&lt;/b&gt;&lt;/span&gt;는 Vercel에서 만든 React Framework입니다. SSR, CSR, SSG를 모두 지원하는 &lt;b&gt;유연한 렌더링 방식&lt;/b&gt;을 제공해주고, Server Component와 Client Component를 통하여 원하는 페이지의 SSR 방식 적용을 통한 &lt;b&gt;SEO 최적화&lt;/b&gt;도 가능합니다. 당연히 SSR 방식을 통하여 &lt;b&gt;초기 구동 속도&lt;/b&gt;도 빨라지고  CSR을 초기 로딩 후 페이지 전환과 데이터 갱신이 빠르게 이루어져, 사용자에게 &lt;b&gt;매끄러운 경험&lt;/b&gt;을 제공해줍니다. Next.js는 &lt;b&gt;코드 분할을 자동으로 처리&lt;/b&gt;하여, 필요한 부분만 로드하고 성능을 최적화합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&amp;nbsp;자동 코드 분할(Automatic Code Splitting)가 정확히 뭔데?&lt;br /&gt;&lt;/b&gt;&amp;nbsp;자동 코드 분할은 웹 애플리케이션 성능 최적화를 위하여 Next.js가 제공하는 중요한 기능 중 하나입니다. 도무지 무슨 말인지 몰라서 따로 정리해봅니다. 웹 애플리케이션이 커질수록 모든 코드를 한 순간에 로드하는 방식은 비효율적입니다. 전체 코드를 한 번에 다운로드하면 초기 로딩 시간이 길어져 사용자 경험이 나빠지겠죠? 따라서, 필요한 코드만 로드하여 초기 로딩 속도를 단축시키고, 이후 필요한 기능이 있을 때 해당 코드만 추가적으로 로드하는 것이 효율적입니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 위하여 Next.js는 코드 분할 작업을 자동으로 처리해준다고 합니다. 개발자가 별도로 설정하지 않아도, Next.js는 페이지별로 필요한 코드만 분할하여 로드해줍니다.&lt;br /&gt;&amp;nbsp;명확한 이해를 돕기 위해 예시를 들어보겠습니다. 쇼핑몰 사이트가 있다고 가정해봅시다. 쇼핑몰의 메인 페이지, 제품 페이지, 결제 페이지가 있을 때, 자동 코드 분할을 통해여 다음과 같은 이점이 있습니다.&lt;br /&gt;&amp;nbsp; - &lt;b&gt;메인 페이지 로딩&lt;/b&gt;: 사용자가 페이지를 방문할 때, Next.js는 메인 페이지에 필요한 코드만 로드합니다. 제품 페이지나 결제 페이지와&amp;nbsp; &lt;br /&gt;관련된 코드는 로드되지 않습니다.&lt;br /&gt;&amp;nbsp; - &lt;b&gt;제품 페이지 로딩&lt;/b&gt;: 사용자가 제품 페이지로 이동하면, Next.js는 이 때 제품 페이지에 필요한 코드만 추가로 로드합니다. 이미 로드된 메인 페이지 코드는 다시 로드되지 않습니다.&lt;br /&gt;&amp;nbsp; - &lt;b&gt;결제 페이지 로딩&lt;/b&gt;: 결제 페이지로 이동할 때, 결제 페이지에 필요한 코드만 로드됩니다. 이처럼 필요한 시점에 필요한 코드만 로드함으로써 전체적인 로딩 시간을 줄이고, 사용자 경험을 향상시킬 수 있습니다.&amp;nbsp;&lt;br /&gt;이로써 Next.js의 자동 코드 분할 기능은 애플리케이션의 성능 최적화에 큰 도움을 줍니다. 초기 로딩 시간 단축, 불필요한 코드 로드 방지, 페이지별 효율적인 리소스관리 등을 제공하여 개발자가 성능 좋은 웹 애플리케이션을 쉽게 만들 수 있게 도와줍니다.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;Next.js에서의 CSR, SSR, SSG 예제를 살펴보기 앞서 Pre-Rendering과 Hydration 개념에 대해 정리하고 넘어가겠습니다. (살펴볼게 참 많죠?) &lt;b&gt;Pre-Rendering&lt;/b&gt;(사전 렌더링)이란 서버 단에서 DOM 요소들 빌드하여 HTML 문서를 렌더링하는 것을 의미하며, 초기 페이지 로드 속도를 향상시키고, SEO를 개선하는 데 목적이 있습니다. 이미 앞에서 각 렌더링 방식에 대한 설명과 동일한 내용입니다.&lt;b&gt; &lt;br /&gt;&amp;nbsp;Hydration&lt;/b&gt;은 이렇게 미리 렌더링된 HTML에 javascript를 결합하여 이벤트가 동작할 수 있게 만드는 과정을 말합니다. 클라이언트가 서버에서 받은 HTML을 React 애플리케이션으로 변환하는 과정으로 이해하시면 되겠습니다. Pre-Rendered HTML을 클라이언트에서 받은 후, React는 이벤트 핸들러 등 동적인 기능을 추가하여 전체적으로 인터렉티브하게 만듭니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwDMQA/btsIytk8x3C/gHlQB37KAPiTwkLQbJI9T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwDMQA/btsIytk8x3C/gHlQB37KAPiTwkLQbJI9T0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwDMQA/btsIytk8x3C/gHlQB37KAPiTwkLQbJI9T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwDMQA%2FbtsIytk8x3C%2FgHlQB37KAPiTwkLQbJI9T0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;683&quot; height=&quot;779&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SSR(Server-Side Rendering)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 각 요청마다 HTML을 서버에서 동적으로 생성합니다.&lt;br /&gt;&amp;nbsp; &amp;nbsp;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SSG(Static Site Generation)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;빌드 시점&lt;/b&gt;&lt;/span&gt;에 HTML을 (정적으로) 생성하고 저장하여, 각 요청 시마다 재사용합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kuv15/btsIzKsuF2A/KjqJKOjbAMzzbDiINnzyfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kuv15/btsIzKsuF2A/KjqJKOjbAMzzbDiINnzyfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kuv15/btsIzKsuF2A/KjqJKOjbAMzzbDiINnzyfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKuv15%2FbtsIzKsuF2A%2FKjqJKOjbAMzzbDiINnzyfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;387&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;따라서, Next.js은 렌더링할 때 기본적으로 두 가지 주요 방식인 SSR과 SSG 방식으로 Pre-Rendering을 수행하며, 둘의 차이는 HTML을 생성하는 시기에 있습니다. 기본적으로 SSG가 SSR보다 높은 성능을 가지는 것으로 알려져 있으며, Next.js에서도 SSG를 권장하고 있습니다. 이제 각 CSR/SSR/SSG 동작에 대한 정리와 어느 상황에 써야하는지, 그리고 예제를 살펴보면서 글을 마무리하려고 합니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 글은 작년 10월 26일에 발표된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;Next.js 14 버전&lt;/span&gt;을 기준으로 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;1. CSR &lt;/span&gt;&lt;/b&gt;&lt;span&gt;(Client-Side Rendering)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 렌더링이 클라이언트에서 이루어지며, 사용자와의 인터렉션이 많은 페이지에 적합합니다. 예를 들어, 사용자가 자주 상호작용하는 대시보드, 비동기 데이터 로드가 필요한 애플리케이션, SEO가 중요하지 않은 페이지가 이에 해당됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Next.js 14에서 App Router 방식으로 CSR을 구현하기 위해서 최상단에 &lt;b&gt;&quot;use client&quot;&lt;/b&gt;를 필수로 입력하면 해당 페이지는 CSR로 동작됩니다. 또한, React Hook을 사용할 수 있는 페이지는 CSR로 작성되어야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1720850686232&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;
import { useEffect, useState } from 'react';

export default function Dashboard() {
  const [data, setData] = useState&amp;lt;DataType&amp;gt;();

  useEffect(() =&amp;gt; {
    fetch('/api/data')
      .then((response) =&amp;gt; response.json())
      .then((data) =&amp;gt; setData(data));
  }, []);

  if (!data) return &amp;lt;div&amp;gt;Loading...&amp;lt;/div&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Dashboard&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{data.message}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;br /&gt;&lt;br /&gt;2. SSR&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;(Server-Side Rendering)&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;유저가 페이지를 요청할 때마다 서버에서 HTML을 동적으로 생성하는 방식이며, SEO 최적화에 유리하기 때문에 SEO와 초기 데이터가 중요한 페이지에 적합합니다. 항상 최신 상태로 유지되어야하는 페이지, 차트 등 사용자 요청마다 동적으로 페이지를 생성해서 다른 상태로 업데이트시켜주어야하는 상황에 사용됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;CSR 방식을 위해 React Hook인 &lt;b&gt;useEffect&lt;/b&gt;를 사용했다면, SSR 방식을 위해서는 getServerSideProps를 통하여 데이터를 불러옵니다.&amp;nbsp;Next.js가 Pre-Rendering 중 getServerSideProps 함수를 발견하면 컴포넌트 함수 호출 전에 &lt;b&gt;getServerSideProps&lt;/b&gt;를 먼저 호출하여 컴포넌트에 전달해줍니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;하지만 &lt;b&gt;App Router&lt;/b&gt;에서는 SSR을 구현하기 위해서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;getServerSideProps 대신 &lt;b&gt;async&lt;/b&gt; 함수와 fetch API를 사용하여 데이터를 서버 측에서 가져옵니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720850814902&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export async function generateMetadata({ params }) {
  const { id } = params;
  const post = await fetchPostData(id);
  return { title: post.title };
}

export default async function BlogPost({ params }) {
  const { id } = params;
  const post = await fetchPostData(id);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{post.content}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;3. SSG&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;span&gt;(Static Site Generation)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;빌드 시점에 HTML을 생성하여 정적 파일로 제공해주며, 재사용되기 때문에 매우 빠른 페이지 로드 속도를 이점으로 하는 방식입니다. 주로 업데이트가 자주 필요하지 않은 블로그 게시물, 마케팅 페이지에 적합합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;Next.js에서 SSG 방식으로 데이터를 받아오려면 &lt;b&gt;getStaticProps&lt;/b&gt;가 필요합니다. 빌드 시에 한 번만 호출되며, static file로 빌드되어 재사용되도록 해줍니다. App Router에서는 &lt;b&gt;generateStaticParams&lt;/b&gt;와 &lt;b&gt;generateMetadata&lt;/b&gt; 함수를 사용하여 빌드 시 정적 파일 생성합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720851416975&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export async function generateStaticParams() {
  const posts = await fetchAllPostIds();
  return posts.map((post) =&amp;gt; ({ id: post.id.toString() }));
}

export async function generateMetadata({ params }) {
  const { id } = params;
  const post = await fetchPostData(id);
  return { title: post.title };
}

export default async function BlogPost({ params }) {
  const { id } = params;
  const post = await fetchPostData(id);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{post.content}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend</category>
      <category>0biglife</category>
      <category>CSR</category>
      <category>next.js</category>
      <category>nextjs</category>
      <category>react</category>
      <category>SSG</category>
      <category>SSR</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/69</guid>
      <comments>https://0biglife.tistory.com/entry/Why-Nextjs-with-SSR-SSG-CSR#entry69comment</comments>
      <pubDate>Sat, 13 Jul 2024 14:08:48 +0900</pubDate>
    </item>
    <item>
      <title>NIPA인증 3. Kubernetes 데이터 복원, 폐기 구현</title>
      <link>https://0biglife.tistory.com/entry/NIPA%EC%9D%B8%EC%A6%9D-3-Kubernetes-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B3%B5%EC%9B%90%ED%8F%90%EA%B8%B0-%EA%B5%AC%ED%98%84%EC%8B%A0%EB%A2%B0%EC%84%B1-%EB%B3%B4%EC%9E%A5</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2670&quot; data-origin-height=&quot;1786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k1sAf/btsHrt0ZZc5/lv0FIpi0iVgcqoOlbXxcGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k1sAf/btsHrt0ZZc5/lv0FIpi0iVgcqoOlbXxcGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k1sAf/btsHrt0ZZc5/lv0FIpi0iVgcqoOlbXxcGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk1sAf%2FbtsHrt0ZZc5%2Flv0FIpi0iVgcqoOlbXxcGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2670&quot; height=&quot;1786&quot; data-origin-width=&quot;2670&quot; data-origin-height=&quot;1786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;이번 글에서는 지난 번 &lt;a href=&quot;https://0biglife.tistory.com/entry/NIPA%EC%9D%B8%EC%A6%9D-2Kubernetes-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%B1%EC%97%85%EB%B3%B5%EC%9B%90%ED%8F%90%EA%B8%B0-%EA%B5%AC%ED%98%84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;데이터 백업&lt;/a&gt;에 이어 데이터 복원과 폐기는 어떻게 구현되는지에 대해 정리하려고 한다. 데이터 백업, 복원, 폐기에 대한건 사용자 측면에서 민감하게 취급되기 때문에 NIPA 인증 취득을 위해서 제출되는 여러 서류에서 세부적으로 다뤄진다. 하단에 첨부한 신뢰성 항목에 기재된 &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: start;&quot;&gt;SLA(Service Level Agreement) 문서가 그 중 하나인데 데이터 보관과 폐기에 대한 정책은 3개월을 기준으로 설정했다. &lt;b&gt;서비스 회복 시간&lt;/b&gt;은 10분 미만으로 설정하였고, 이는 파드가 다시 Running 상태로 돌아오고 백업 압축 데이터를 복원시키는데까지 충분한 시간이다. &lt;b&gt;백업준수율&lt;/b&gt;은 백업되어야하는 실제 횟수와 성공한 횟수를 백분율로 계산한 수치인데 99%로 설정하였다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oyFRQ/btsHulA6zY6/2AhMUEiCG81Yd2lRIllLik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oyFRQ/btsHulA6zY6/2AhMUEiCG81Yd2lRIllLik/img.png&quot; data-alt=&quot;SLA&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oyFRQ/btsHulA6zY6/2AhMUEiCG81Yd2lRIllLik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoyFRQ%2FbtsHulA6zY6%2F2AhMUEiCG81Yd2lRIllLik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;706&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1622&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SLA&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;별개 내용이지만 문득 &lt;b&gt;'데이터 폐기와 회원탈퇴는 어떤 식으로 관리될까?'&lt;/b&gt; 라는 의문점이 들었다. 이게 정석이지 라는게 없다고 생각하기 때문에 작은 의견처럼 적어보자면, 회원탈퇴를 하게 되면 바로 유저를 제거하는 것이 아닌 DB 스키마에서 유저 계정이 활성화되었는지 여부를 boolean값으로 받아서 탈퇴한 사용자는 false처리를 해야하지 않나 싶다. '탈퇴된 유저의 데이터를 3개월 동안 보관하겠다'까지가 데이터 백업 안에 포함되어야할테고 3개월이 넘어간 데이터는 복원되지 않다는 것을 사용자에게 고지해준다면 이 부분에 대해서는 정책상 컴플레인도 없을 것이다.&lt;/blockquote&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 복원&lt;/b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;데이터를 복원하는 것이 그저 문장으로는 와닿지만 이를 어떻게 구현하는지가 고민이었다. 클라우드 스토리지에 압축된 파일들이 날짜별로 정리가 되어있고 이를 데이터베이스 파드로 가져와서 압축을 해제한 뒤에 Restore시켜야한다. 이를 구현하기 위해서 '어느 시점'에 복원을 시켜줄지를 먼저 생각하게 되었는데 이는 자연스럽게 배포된 StatefulSet 리소스의 생애주기를 관리하는 속성값이 yaml에 있는지를 찾아보게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;보란듯이 공식 문서에 친절히 작성되어있다. 파드 및 컨테이너의 생애주기를 제어하기 위하여 lifecycle이라는 속성값을 가져와서 재시작될 때마다 특정 동작을 가능케해주는 postStart를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btuzuY/btsHttUiKF3/Py1koYzSZLs2fGCG8mh2E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btuzuY/btsHttUiKF3/Py1koYzSZLs2fGCG8mh2E1/img.png&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;1956&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.1801%; margin-right: 10px;&quot; data-widthpercent=&quot;56.84&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btuzuY/btsHttUiKF3/Py1koYzSZLs2fGCG8mh2E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtuzuY%2FbtsHttUiKF3%2FPy1koYzSZLs2fGCG8mh2E1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1874&quot; height=&quot;1956&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clLJCB/btsHtL8g3xX/76cGw4bI25gm4oPzEjYqA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clLJCB/btsHtL8g3xX/76cGw4bI25gm4oPzEjYqA0/img.png&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;1930&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;43.16&quot; data-filename=&quot;blob&quot; style=&quot;width: 42.6571%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clLJCB/btsHtL8g3xX/76cGw4bI25gm4oPzEjYqA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclLJCB%2FbtsHtL8g3xX%2F76cGw4bI25gm4oPzEjYqA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1404&quot; height=&quot;1930&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;신나는 마음으로 바로 테스트 진행해보기..! 처음에는 핑을 찍는 것으로 테스트를 해보았고 결과적으로는 마운트된 볼륨의 지정된 경로를 찾아가서 오늘 날짜를 기준으로 가장 가까운 날짜의 압축 폴더를 먼저 찾는다. mongoDB를 쓰고 있기 때문에 mongorestore 커맨드를 통해 복원시켜준다! 그 후, 압축 해제한 파일은 제거해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1716016800758&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spec:
      restartPolicy: Always
      containers:
        - name: sample
          image: mongo:4.2
          ports:
            - containerPort: 27017
          lifecycle:
            postStart:
              exec:
                command:
                  - &quot;/bin/sh&quot;
                  - &quot;-c&quot;
                  - |
                    LATEST_BACKUP=$(find /data/sample -type f -name '*.tar.gz' | sort -r | head -n 1)
                    BACKUP_DATE=$(basename &quot;$LATEST_BACKUP&quot; .tar.gz)
                    tar -xzvf &quot;$LATEST_BACKUP&quot; -C /data/sample
                    mongorestore --uri=mongodb://sample-0.sample-svc.default.svc.cluster.local:27017 --db=sample /data/sample/$BACKUP_DATE/sample
                    rm -rf /data/sample/$BACKUP_DATE
          volumeMounts:
            - name: sample-volume
              mountPath: /data/sample
      volumes:
        - name: sample-volume
          persistentVolumeClaim:
            claimName: sample-pvc&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;생각보다 깔끔하지 않나? 재시작되는 시점마다 복원을 시켜주면 된다. 그치만 한계점으로는 매일 00시 기준으로 백업을 해준다면, 복원되는 시간 사이에 발생된 변경사항은 사용자에게 제공될 수 없다는 점이다. 이를 위해서 대기업에서는 더욱 세분화된 백업 기법을 활용하는데 꽤나 흥미롭다. 이 부분은 추후에 더 정리하여 새 게시글로 작성하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 폐기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;이제 폐기만 구현하면 데이터 백업 관리에 대한 모든 프로세스가 마치게 된다. 폐기는 어느 시점에 해주면 좋을까? 최대한 프로세스는 적은 시점으로 통일되면 좋다. 데이터 백업되는 시점에 매일 한 번씩 주기적으로 업데이트되기 때문에 이 시점에 전체 폴더를 필터링해서 기한을 넘은 파일은 제거해주면 된다. 즉, 폐기 프로세스는 Cronjob에 들어가면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1716017431052&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spec:
      backoffLimit: 1
      template:
        spec:
          containers:
          - name: sample
            image: mongo:4.2
            args:
            - /bin/sh
            - -c
            - |
              #!/bin/sh
              
              # 데이터 백업 로직
              TIMESTAMP=$(date +%Y%m%d) 
              BACKUP_DIR=&quot;/backup/$TIMESTAMP&quot;
              mongodump --host sample-0.sample-svc.default.svc.cluster.local:27017 --db sample --out $BACKUP_DIR
              tar -zcvf &quot;/backup/${TIMESTAMP}.tar.gz&quot; -C &quot;/backup&quot; &quot;$TIMESTAMP&quot;
              rm -rf &quot;$BACKUP_DIR&quot;

              # 데이터 폐기 로직
              DELETE_DATE=$(date -d &quot;90 days ago&quot; +%Y%m%d)
              find /backup -type f -name &quot;*.tar.gz&quot; | while read FILE; do
                FILE_DATE=$(basename &quot;$FILE&quot; .tar.gz)
                if [ &quot;$FILE_DATE&quot; -lt &quot;$DELETE_DATE&quot; ]; then
                  rm -f &quot;$FILE&quot;
                fi
              done
            volumeMounts:
            - name: sample-volume
              mountPath: /sample
          volumes:
          - name: sample-volume
            persistentVolumeClaim:
              claimName: sample-pvc
          restartPolicy: OnFailure&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;테스트까지 완료. 그리고 실제 클러스터에 반영! 데이터 백업, 복원, 폐기에 대한 구현을 완료되었고 실제 테스트를 진행하면서 백업준수율과 파드가 재시작되어 유저가 로그인 성공하는 시점까지 10분 이내인지까지 모두 성공적으로 마쳤다. '신뢰성'에 대한 항목이 필자는 SaaS 요건 중에 가장 까다로웠는데 완료되기까지 기한은 조금 걸렸어도 즐겁고 보람찬 경험이었다. 다음은 확장성을 다뤄볼까 한다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;프론트엔드 개발자가 데이터 백업 프로세스를 구현하는게 어떤 도움이 될지 고민을 하면서 NIPA 인증 취득하는 3개월을 보냈다. 일차원적으로는 즐겁다. 아직도 개발은 재밌고 모르는 것을 해내고 테스트까지 하면서 꼼꼼하게 살펴볼 때 즐겁다는 생각을 한다. 면접에서 React 프레임워크에 대한 질문을 못하는데 데이터 백업 구현에 대해 한 경험이 그 어떤 회사 면접관에게 어필이 될까? 그런 사람이 되는 것을 경계하고 프론트 공부를 더 다져야겠다는 생각을 했다. 인프라 업무 담당에 대해서는 현재 회사 내 나의 포지션에 대한 것들을 꼼꼼하게 채우되 더 넓은 시야를 경험했다는 것만을 가져가도 충분하겠다 싶다.&amp;nbsp;&lt;/p&gt;</description>
      <category>0biglife</category>
      <category>cronjob</category>
      <category>kubernetes</category>
      <category>데이터복원</category>
      <category>데이터폐기</category>
      <category>쿠버네티스</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/58</guid>
      <comments>https://0biglife.tistory.com/entry/NIPA%EC%9D%B8%EC%A6%9D-3-Kubernetes-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B3%B5%EC%9B%90%ED%8F%90%EA%B8%B0-%EA%B5%AC%ED%98%84%EC%8B%A0%EB%A2%B0%EC%84%B1-%EB%B3%B4%EC%9E%A5#entry58comment</comments>
      <pubDate>Thu, 16 May 2024 22:04:44 +0900</pubDate>
    </item>
    <item>
      <title>NIPA인증 2. Kubernetes 데이터 백업 구현</title>
      <link>https://0biglife.tistory.com/entry/nipa-1</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zjqp8/btsHmDPQPpb/huXpKVZNuhqxk2UICmn9R0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zjqp8/btsHmDPQPpb/huXpKVZNuhqxk2UICmn9R0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zjqp8/btsHmDPQPpb/huXpKVZNuhqxk2UICmn9R0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZjqp8%2FbtsHmDPQPpb%2FhuXpKVZNuhqxk2UICmn9R0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;648&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://0biglife.tistory.com/entry/nipa1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NIPA인증 1. 시작&lt;/a&gt;글에 이어 '확장성'에 대한 기술 구현을 적어보려고 한다. 인증 취득을 위한 &lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;u&gt;SaaS 솔루션은 보장되어야할 주요 요건&lt;/u&gt;들이 있는데, 이번 글에서 다룰 것은 &lt;b&gt;'신뢰성'&lt;/b&gt;에 대한 것이다. 신뢰성이 뭔지 간략히 설명하자면, 선뢰성을 가지는 솔루션은 리소스 관리에 있어서 장애 발생 또는 특정 동작을 통해 삭제되었을시 서비스가 다시 회복되는 데에 얼마나 걸리는지를 의미하는 &lt;b&gt;'서비스 회복 시간'이 보장&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;되어야한다. 운영을 위해 유지되어야할 데이터가 &lt;b&gt;백업준수율&lt;/b&gt;을 지키고 있는지도 보장되어야하고, &lt;b&gt;데이터 보관(백업), 반환,&lt;/b&gt; 그리고 &lt;b&gt;폐지&lt;/b&gt;에 대한 정책 및 기술도 구현되어있어야 한다. 그럼 시작해보자-!&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 백업&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&amp;nbsp;&lt;b&gt;데이터는 왜 백업되어야하는가?&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 먼저 얘기해보려고 한다. '단순히 서버가 다운되면 데이터베이스는 사라질테니까 해야하는거 아니야?' 라고 생각이 들겠지만 이를 인프라 측면에서 살펴보자. 인프라에 구성되는 대부분의 리소스는 클러스터 내부에 존재하며, 배포된 모든 애플리케이션의 최소 단위는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;파드(Pod)&lt;/b&gt;의 형태로 존재한다. 각 Pod는 일반적으로 Deployment를 통하여 생성된다는 가정 하에 이름의 뒤에 임의의 랜덤 문자열이 붙는다.&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(이 네이밍 규칙도 Kind별 서로 다른 방식이 있으나 여기서 다루지는 않는다.)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이러한 특성을 가지는 파드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;일회성&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;리소스로서 존재하기 때문에 문제가 발생하여 다운된다면 다운되기 직전 상태로 다시 복구시킬 수 없다. 그렇기 때문에 기업에서의 애플리케이션별 데이터 백업 전략이 수립되어야하는 것이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 백업을 위한 기술을 어떻게 구현할 수 있을까? 기업마다 다르겠지만 가장 일반적인 방식으로는 &lt;b&gt;스냅샷&lt;/b&gt;이 떠오른다. 물론 스냅샷은 개발이 간편하고 비용이 저렴하지만, 필자가 개발중인 프로젝트는 스냅샷을 사용할 수 없기에 다른 방식이 필요하다. 현재의 SaaS 솔루션은 기업 인프라 내부에 설치하는 방식으로 세팅이 되는데, 설치를 하게 되면 AWS, Microsoft Azure, CGP와 같은 퍼블릭 클라우드 프로바이더, 혹은 온프레미스 클라우드와 같은 환경에 최대한 &lt;u&gt;의존하지 않고서 솔루션의 데이터 백업 전략이 구현&lt;/u&gt;되어야 한다. 즉, 다양한 클라우드 환경에서 공용으로 쓰일 수 있는 전략이 필요하다. 그렇기 때문에 설치할 때마다 프로바이더별 스냅샷 설정을 해주는 것보다 설치하는 과정에서 필요한 리소스들을 코드로 넣어 자동화해주는 방식이 필요했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/41hkF/btsHpcMmlb1/FnUWiahJyr0OHMA2sVLVk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/41hkF/btsHpcMmlb1/FnUWiahJyr0OHMA2sVLVk0/img.png&quot; data-alt=&quot;https://blog.stackademic.com/scheduling-mysql-and-mongodb-database-backups-using-cron-46a83fad97e9&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/41hkF/btsHpcMmlb1/FnUWiahJyr0OHMA2sVLVk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F41hkF%2FbtsHpcMmlb1%2FFnUWiahJyr0OHMA2sVLVk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;381&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://blog.stackademic.com/scheduling-mysql-and-mongodb-database-backups-using-cron-46a83fad97e9&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;위와 같은 이유로 &lt;b&gt;Cronjob&lt;/b&gt;을 통한 주기적인 스케줄링을 통한 데이터 백업 전략을 채택했다. 솔루션을 사용하기 위한 설치 과정에서 클러스터 내부에 솔루션에 대한 데이터가 주기적으로 백업되기 위한 일종의 준비물들이 모두 세팅되는 것이다. 구현을 위한 필요 리소스는 다음과 같은 코드로 설치 과정에 들어간다. 설치 방식은 이 게시글에서 중요하지 않기에 생략하고 필요한 리소스만 설명하자면, &lt;b&gt;StorageClass&lt;/b&gt;, &lt;b&gt;PersistentVolume&lt;/b&gt;, &lt;b&gt;PersidstentVolumeClaim&lt;/b&gt;, &lt;b&gt;Statefulset&lt;/b&gt;, &lt;b&gt;Service&lt;/b&gt;가 필요하고 백업된 경로를 배포된 SaaS &lt;b&gt;Deployment&lt;/b&gt; yaml 내부에 넣어줘야한다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1715759840879&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 업로드된 yaml을 퍼블릭 url로 받아서 클러스터 내부에 설치
kubectl apply -f https://&amp;lt;external-url&amp;gt;.com/yaml/saas-storageclass.yaml
kubectl apply -f https://&amp;lt;external-url&amp;gt;.com/yaml/saas-pv.yaml
kubectl apply -f https://&amp;lt;external-url&amp;gt;.com/yaml/saas-pvc.yaml
kubectl apply -f https://&amp;lt;external-url&amp;gt;com/yaml/saas-headless-service.yaml
kubectl apply -f https://&amp;lt;external-url&amp;gt;.com/yaml/saas-statefulset.yaml

kubectl apply -f https://&amp;lt;external-url&amp;gt;.com/yaml/saas-deployment.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp; 각 리소스에 대한 역할을 간단히 설명하고, 정확히 기술적으로 어떤 속성값을 가져 yaml이 배포되는지를 기술한다. 그리고 cronjob&amp;nbsp; 프로세스를 설명하는 것이 이해하는 데에 도움이 될 것이다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;StorageClass&lt;/b&gt;: Storage의 동적 프로비저닝 방법을 정의하며 이는 PersistentVolumeClaim에서 요청할 때 쓰인다. 즉, 저장소와 관련하여 특정 애플리케이션이나 서비스 요구 사항을 충족시키기 위하여 필요한 자원을 하당하고&amp;nbsp; 구성한다.&amp;nbsp;&lt;br /&gt;- &lt;b&gt;PersistentVolume&lt;/b&gt;(PV): 클러스터에서 사용 가능한 Storage의 논리적인 볼륨을 의미한다.&lt;br /&gt;- &lt;b&gt;PersistentVolumeClaim&lt;/b&gt;(PVC): 애플리케이션이 Storage에 대하여 요청할 때 사용된다. 사용 가능한 PV 중에서 애플리케이션에 할당될 볼륨을 선택하고 요청한다.&lt;br /&gt;- &lt;b&gt;StatefulSet&lt;/b&gt;: 무작위로 생성되는 파드에 '순서'를 부여해주는 워크로드 리소스이며, PVC와 함께 사용되어 각 파드에 고유한 PV를 할당 및 보존시킨다. StatefulSet으로 생성된 파드들은 고유한 식별자를 가지기 때문에 파드가 다운되어도 재생성되어 내부에서 DNS를 통하여 연결시켜주는 네이밍 규칙에 대해 규정하는 중요한 역할을 한다.(이는 하단에서 더 자세히 다루겠다)&lt;br /&gt;- &lt;b&gt;Headless Service&lt;/b&gt;: 클러스터 내 각 파드에 대한 개별적인 DNS 레코드를 생성하는 서비스 유형이다. 일반적인 서비스처럼 apply하되, ClusterIP 속성값이 none을 주면 생성이 가능하며, StatefulSet과 함께 사용될 때 파드별 고유 DNS 레코드가 생성되기 때문에 네트워크 통신에 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;필자가 스냅샷이 아닌 Cronjob을 통하여 다양한 클라우드 환경에서 데이터 백업 전략을 구축한 이유는 위 리소스 중에서 StorageClass와 밀접한 관계를 가진다. StorageClass의 속성값 중에서 parameters와 provisioner에 주목하자. 마운트 시 사용자 권한을 제어하는 mountOption 속성이나 PV 삭제 시 스토리지 해제할 때의 동작을 지정하는 reclaimPolicy 등의 속성도 중요하지만 필자가 위에서 클라우드 프로바이더 종류에 제약을 받지 않도록 설정했다는 것은 &lt;b&gt;parameters&lt;/b&gt;와 &lt;b&gt;provisioner&lt;/b&gt;를 통하여 제어가 가능하다. parameters 속성은 특정 프로바이더에 해당하는 추가 매개변수를 지정하고 스토리지 유형을 지정하며, provisioner는 CSI 드라이버를 지정한다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1715765694158&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// azure - storageclass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: azure-sample
mountOptions:
- dir_mode=0777
- file_mode=0777
parameters:
  skuName: Standard_LRS
provisioner: file.csi.azure.com
reclaimPolicy: Retain
volumeBindingMode: Immediate

// aws - storageclass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: aws-sample
mountOptions:
- dir_mode=0777
- file_mode=0777
parameters:
  type: gp2
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Retain
volumeBindingMode: Immediate

// gcp - storageclass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gcp-sample
mountOptions:
- dir_mode=0777
- file_mode=0777
parameters:
  type: pd-standard
provisioner: kubernetes.io/gce-pd
reclaimPolicy: Retain
volumeBindingMode: Immediate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;그렇다면 이제 클라우드 프로바이더별로 &lt;b&gt;StorageClass&lt;/b&gt;가 마련이 되어있고,  실제 운영되는 Deployment와 배포되는 데이터베이스는 어떻게 연결되며, 백업을 위한 PV, PVC, StorageClass까지 어떻게 이어지는지를 살펴볼 차례다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/demKsX/btsHsqWupUc/tqloVaY9M6PpKkwbxlQniK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/demKsX/btsHsqWupUc/tqloVaY9M6PpKkwbxlQniK/img.png&quot; data-alt=&quot;이보다 직관적인 시각자료는 없다.! (출처: https://www.puzzle.ch/de/blog/articles/2021/08/10/manual-kubernetes-persistentvolumes-migration)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/demKsX/btsHsqWupUc/tqloVaY9M6PpKkwbxlQniK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdemKsX%2FbtsHsqWupUc%2FtqloVaY9M6PpKkwbxlQniK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;980&quot; height=&quot;173&quot; data-origin-width=&quot;980&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이보다 직관적인 시각자료는 없다.! (출처: https://www.puzzle.ch/de/blog/articles/2021/08/10/manual-kubernetes-persistentvolumes-migration)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;가장 먼저 PV, PVC를 생성하고 바인딩시켜준다. PVC는 실제 Storage를 요청하고, 이 요청은 StorageClass를 통하여 PV로 바인딩된다. 배포된 PV, PVC를 살펴보자.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1715861581178&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// sample-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: sample-pv
spec:
  capacity:
    storage: 2Gi   # pvc와의 용량 관계 검토할 것!
  storageClassName: sample-storageclass   # storageclass 바인딩
  accessModes:
  - ReadWriteMany
  azureFile:
    secretName: sample-secret
    shareName: sampleshare   # 실제 FileShare 이름이어야 한다
    readOnly: false
  mountOptions:
  - dir_mode=0777  
  - file_mode=0777
  - uid=0
  - gid=0

// sample-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sample-pvc
  namespace: default
spec:
  accessModes:
  - ReadWriteMany
  storageClassName: sample-storageclass
  resources:
    requests:
      storage: 2Gi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;위처럼 각 PV, PVC는 StorageClass와 바인딩시켜주고, PV의 경우 민감한 데이터는 secret으로 개별 배포 후 연결지어줄 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;StatefulSet&lt;/b&gt;은 주로 상태를 유지해야 하는 애플리케이션을 배포할 때 쓰이는 리소스이다. 캐싱 시스템, 메시지 브로커, 데이터베이스 서버 등에 쓰이는데, 그 이유는 크게 두 가지로 볼 수 있다. 데이터베이스의 경우에는 데이터의 지속성과 무결성이 중요한데 StatefulSet은 파드를 &lt;b&gt;순차적으로 관리&lt;/b&gt;하여 각각 고유한 식별자를 부여하기 때문에 데이터베이스의 상태를 보장하는 데에 유용하다. 이는 파드 네이밍 정의를 기존 Deployment가 랜덤한 문자열로 지정한다면, StatefulSet은 &lt;b&gt;&amp;lt;pod-name&amp;gt;-0&lt;/b&gt;, &lt;b&gt;&amp;lt;pod-name&amp;gt;-1&lt;/b&gt; 과 같이 순서를 부여한다. 두 번째 이유는 StatefulSet은 각 파드에 고유한 PVC를 할당하므로, 데이터베이스 인스턴스마다 고유한 스토리지가 부여되어 보관 및 관리를 도와준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbcvsD/btsHrygNZED/r2xnn2Dj74k2Yq9OWePnc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbcvsD/btsHrygNZED/r2xnn2Dj74k2Yq9OWePnc1/img.png&quot; data-alt=&quot;출처: https://www.linkedin.com/pulse/running-stateful-set-pods-using-headless-service-yosr-mahfoudh/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbcvsD/btsHrygNZED/r2xnn2Dj74k2Yq9OWePnc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbcvsD%2FbtsHrygNZED%2Fr2xnn2Dj74k2Yq9OWePnc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;412&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;577&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://www.linkedin.com/pulse/running-stateful-set-pods-using-headless-service-yosr-mahfoudh/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;Deployment와 함께 배포되는 Service 형태와는 다르게 StatefulSet은 &lt;b&gt;Headless&lt;/b&gt; &lt;b&gt;Service&lt;/b&gt;와 함께 배포된다. 파드의 순서 보장을 한다고 하였는데, Headless Service는 각 파드에&lt;b&gt; 개별적인 DNS 레코드를 생성&lt;/b&gt;하기 때문에 네트워크 통신을 쉽게 해준다. 클라이언트가 데이터베이스 연결할 때 각 파드를 개별적으로 식별할 수 있어야하기 때문에 꽤 중요한 역할을 하며, 이는 데이터베이스 고가용성 및 확장성과도 연관된다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;StatefulSet과 Service에 해주어야할 설정은 비교적 간단하다. 둘을 연결시키기 위하여 &lt;b&gt;selector&lt;/b&gt;를 통일해주고, StatefulSet의 경우에는 &lt;b&gt;spec.volumes[0].name&lt;/b&gt;과 &lt;b&gt;spec.containers[0].name.volumeMounts[0].name&lt;/b&gt;을 동일하게 해주어야 한다! 그리고 컨테이너 내부의 &lt;b&gt;volumeMount&lt;/b&gt;의 &lt;b&gt;mountPath&lt;/b&gt; 지정은 실제 파드 내부 디렉토리 경로가 된다. 꼭 기억하자! Headless Service는 정말 할게 없다. ClusterIP를 none으로만 지정해주면 이 서비스는 Headless Service가 된다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;잠깐!&lt;/b&gt; 막상 구현할 때에는 &quot;ClusterIP가 None이면 되는구나, 간단하네?&quot; 라고 생각하고 넘어갔지만 막상 왜 ClusterIP가 미지정되는 것이 파드 순서 보장하는 것이랑 어떤 상관관계를 가지는지 궁금해졌다. ClusterIP를 &quot;None&quot;으로 설정하면 이 Service에 ClusterIP가 할당되지 않는다. 그 대신 파드의 이름을 기반으로 한 DNS 레코드가 생성되기 때문에 이 Service는 생성된 DNS를 통하여 파드 간 통신을 도와주는 Service가 되는 것이다. 이로써 파드가 스케일링되거나 재시작되어도 DNS은 유지되고, 데이터베이스와 같은 상태 유지가 필요한 리소스가 관리되는 데에 필요한 순서 보장에도 크게 관여하는 것이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715862745484&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// sample-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample
spec:
  serviceName: sample-svc
  replicas: 1
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      restartPolicy: Always
      containers:
        - name: sample
          image: mongo:4.2
          ports:
            - containerPort: 27017
          volumeMounts:             
            - name: sample-volume   # spec.volumes[0].name과 같아야한다!
              mountPath: /data/sample   # 이 경로는 파드 내부 디렉토리 경로다!
      volumes:
        - name: sample-volume
          persistentVolumeClaim:
            claimName: sample-pvc   # pvc 연결은 필수

// sample-headless-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-svc
spec:
  clusterIP: None   # None으로만 해주면 Headless Service로 전환된다
  selector:
    app: sample
  ports:
    - protocol: TCP
      port: 27017
      targetPort: 27017&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;yaml을 보고 위에 설명한 내용을 보면 매우 간단하는 것을 알 수 있다. 이제 애플리케이션에 직접적으로 관여하는 모든 리소스 배포가 마무리되었다. 음.. 글이 좀 길어지는데 어쩔 수 없다.. 이제 Cronjob 만 살펴보면 데이터 백업에 대한 프로세스는 다 끝나간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;Cronjob&lt;/b&gt;은 주기적으로 작업을 실행하는 데에 사용되는 리소스다. 데이터 백업을 위한 필요한 설명만 할 것이기 때문에 추가적인 속성값은 개별 서치를 하길 바란다. Cronjob 구현을 위해서는 cron 표현식과 백업 스크립트 지정이 필요하다. 아래 yaml을 살펴보자.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1715864217098&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: batch/v1
kind: CronJob
metadata:
  name: sample-backup
spec:
  schedule: &quot;0 0 * * *&quot;   # 각각 &quot;분 시 일 월 요일&quot;을 의미한다. 이 경우 매일 자정에 실행된다.
  successfulJobsHistoryLimit: 3   # 성공적으로 완료된 Job의 최대 수량 지정
  failedJobsHistoryLimit: 3   # 실패한 Job이 유지되는 수량 지정
  jobTemplate:
    spec:
      backoffLimit: 1   # 실패시 재시도 횟수
      template:
        spec:
          containers:
          - name: sample
            image: mongo:4.2
            args:
            - /bin/sh
            - -c
            - |
              #!/bin/sh
              TIMESTAMP=$(date +%Y%m%d) 
              BACKUP_DIR=&quot;/backup/$TIMESTAMP&quot;
              mongodump --host sample-0.sample-svc.default.svc.cluster.local:27017 --db sample --out $BACKUP_DIR
              tar -zcvf &quot;/backup/${TIMESTAMP}.tar.gz&quot; -C &quot;/backup&quot; &quot;$TIMESTAMP&quot;
              rm -rf &quot;$BACKUP_DIR&quot;
            volumeMounts:
            - name: sample-volume   # StatefulSet과 동일하게 하단 volumes name과 통일
              mountPath: /sample   # 경로 지정
          volumes:
          - name: sample-volume
            persistentVolumeClaim:
              claimName: sample-pvc
          restartPolicy: OnFailure   # 컨테이너 재시작 정책(이 경우 실패시 재시작한다)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;cron 표현식으로는 매일 자정에 데이터를 백업하겠다고 설정하였고, 백업 스크립트는 현재 MongoDB를 쓰고 있기 때문에 mongodump를 통하여 원하는 MongoDB 컬랙션 데이터를 백업 파일로 추출해서 압축한 뒤, 날짜별로 클라우드 플랫폼 내부에 보관하였다. 이 때 DNS가 등장한다. &lt;b&gt;sample-0.sample-svc.default.svc.cluster.local:27017&lt;/b&gt;은 백업하고자 하는 IP, 즉 리소스의 경로인데 StatefulSet과 Headless Service 덕분에 무적의 DNS를 가지게 된 것이다. 규칙은 &amp;lt;파드명&amp;gt;.&amp;lt;서비스명&amp;gt;.&amp;lt;네임스페이스&amp;gt;.svc.clsuter.local:&amp;lt;포트&amp;gt; 의 형식이다. 데이터베이스 파드가 수 천번 다운되거나 재시작되어도 파드는 sample-0 이라는 이름으로 재생성되기 때문에 백업 스크립트와 리소스 모두 엔지니어가 변경 여부에 대해 신경쓸 일이 없어진다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;데이터 백업에 대한 내용이 생각보다 길어져서 여기서 마치고 복원과 폐기에 대한 내용은 3편으로 분리하였다. 부족한 부분이 분명 있겠지만 누군가에겐 시간 절약을 해줄 수 있는 지식과 궁금증을 해소해주는 두 마리 토끼를 잡게 해주는 게시글이기를 바란다. 인프라는 바다처럼 고려할 것들이 많고 어려우면서도 그 안에 소소한 즐거움이 있다. 어려워보이던 것을 몇 주 잡고 있으면 형체가 보이면서 이해가 되는가 하면 프론트나 백에서 코드 구현을 통한 로직 구현만 하다가 인프라 관리에 관여하는 코드를 짜면 뭔가 개발이라는 분야를 조금 더 입체적으로 접근하고 공부하는 기분이다. 프론트엔드 / 백엔드 / 데브옵스 엔지니어 포지션의 분리를 떠나서 개발하는 것에 대한 새로운 즐거움을 주는 경험이 되었고, 아는 만큼 보이는 것이 나를 더 단단하게 해주었으면 좋겠는 바람이다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;s&gt;3편에서 봐요!&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infrastructure</category>
      <category>0biglife</category>
      <category>cronjob</category>
      <category>kubernetes</category>
      <category>데이터백업</category>
      <category>쿠버네티스</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/55</guid>
      <comments>https://0biglife.tistory.com/entry/nipa-1#entry55comment</comments>
      <pubDate>Sat, 11 May 2024 16:20:45 +0900</pubDate>
    </item>
    <item>
      <title>React vs Svelte (feat.Vite)</title>
      <link>https://0biglife.tistory.com/entry/React-vs-Svelte</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HgB4D/btsDqN2PGYQ/Ga6FAqeu71GKfkZ5cMGfak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HgB4D/btsDqN2PGYQ/Ga6FAqeu71GKfkZ5cMGfak/img.png&quot; data-alt=&quot;지하철 노선도 같네요? o_oa,,&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HgB4D/btsDqN2PGYQ/Ga6FAqeu71GKfkZ5cMGfak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHgB4D%2FbtsDqN2PGYQ%2FGa6FAqeu71GKfkZ5cMGfak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1307&quot; height=&quot;820&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;지하철 노선도 같네요? o_oa,,&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모에 가까운 게시글 하나 올립니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 프로젝트를 시작함에 있어서 프레임워크 및 언어 선택, 구조 등 고려할 것들이 참 많은데, 이 게시글에서는 프론트엔드를 위해 어떤 프레임워크를 쓸 것인가에 대해 쓰려고 합니다. 두 개의 프로젝트 경험과 프론트엔드(React + Vite) 리딩을 해오면서 생긴 깊고도 얕은 안목으로 리서치하였고, 부족한 부분에 대해서는 추후 게시글을 보완할 계획입니다. 따라서, 리액트와 스벨트에 대한 장황한 이론들은 생략하고, 비교에 대한 것만 간략히 다룹니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;컴파일러 기반&amp;nbsp; vs&amp;nbsp; 런타임 라이브러리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;Svelte가 각광받고 있는 이유는 뛰어난 성능 때문입니다. 이 성능은 리액트뿐만이 아닌, 앵귤러(Angular)와 Vue보다 더 뛰어나다고 하는데, 여기서 성능은 속도, 최초 로딩, 메모리를 포함하며 그 이유는 코드를 해석하는 방식에서 오는 것으로 보입니다. &lt;b&gt;React는 런타임에 가상 DOM을 사용하는 라이브러리&lt;/b&gt;이고, &lt;b&gt;Svelte는 컴파일러 기반의 프레임워크&lt;/b&gt;입니다. 빌드 단계에서 컴포넌트를 일반 Javascript 코드로 변환하기 때문에 런타임에 별도의 Svelte 라이브러리가 필요하지 않습니다. 여기서 가상 DOM(Virtual DOM)이란, 사용자 인터페이스에 발생한 모든 변경사항에 대한 문서 객체 모델(DOM, Document Object Model)을 메모리에 유지시켜주는 가상 임시 저장소입니다. 실제 DOM을 쓰는 것보다 가상 DOM을 사용함으로써 변경사항에 대한 렌더링만 관여하기 때문에 효율적일 수 있지만 Svelte는 컴파일러 기능을 수행하는데, 가상 DOM과 같은 추상화 계층을 만들어내지 않고 일반적인 프레임워크가 런타임에 할 일을 빌드 시에 해결하면서 사용자의 브라우저 부하를 최소화시킵니다. 따라서, Svelte는 미리 컴파일되어 최적화된 일반 Javascript 코드로 변환되기 때문에 불필요한 코드가 제거되어 번들 크기는 감소하며, 이는 초기 로딩 속도를 향상시키는 데에도 기여합니다. 런타임에 별도 라이브러리가 필요하지않기 때문에 애플리케이션의 용량도 가벼워집니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;컴포넌트 작성 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Svelte는 Single File Components(SFC)를 사용하여 HTMP, CSS, Javascript 코드를 하나의 파일에 포함시킵니다. 파일 단위의 컴포넌트 관리로 컴포넌트 간의 명확한 의존성 파악이 가능하며, 자체적 모듈 관리를 통해 간결한 구조를 제공합니다. 이 부분은 실제 프로젝트 코드를 봐야 좀 명확하게 이해가 갈 것으로 보입니다. 반면, React는 Javascript 파일 안에서 JSX 문법으로 컴포넌트를 정의하고 컴포넌트 간의 의존성을 최소화하여 개발할 수 있습니다. JSX의 장점인 Javascript와 함께 동적인 UI 관리가 쉽게 구현 가능한 건 react를 쓰는 큰 장점입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음,, 리서치와 정리를 어느 정도 해보았지만 사실상 '어느게 더 뛰어나다' 혹은 '적합하다' 정도의 판단은 실제 프로젝트에 몸을 녹이면서 경험해봐야 체감이 되어보입니다. 추가적인 비교로는, React는 생태계 및 커뮤니티 확보가 잘되어있고, 대규모 프로젝트에 적합하며 복잡한 UI 구현에 최적화되어있습니다. 코드 구현 방식의 차이와 성능이 높다는 것만으로 무조건 Svelte를 써야한다고 말하기에는 일정 속도로 달려야할 프로젝트에서 새로운 프레임워크를 적용하는 것이 반드시 필요한 공수인가에 대한 의심 듭니다. 따라서, 저는 기존의 React와 Vite를 붙여서 사용하려고 합니다. 그러면 Vite는 무엇인가.!&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Vite ! Vite ! Vite !&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vite는 빠른 개발 환경을 제공하는 도구(Build Tool)로, Vue.js 개발팀에 의해 만들어졌으며 React, Svelte, VanilaJS 등 다양한 프레임워크를 지원합니다. 이미 webpack이라는 번들러 중심의 개발 환경과 배포 시스템이 대중적인데, Vite는 전통적인 번들러와는 차별화된 특징을 가지고 있습니다. Vite가 React와 결합하면 빌드 시 최적화를 수행하여 트리 셰이킹&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(트리 쉐이킹은&amp;nbsp;번들링 과정에서 불필요한 코드(사용되지 않는 모듈)를 식별하고 제거하는 기법입니다. 이는 페이지 로딩 속도를 향상시키고, 사용자 경험을 개선하는 데 중요한 역할을 합니다.)&lt;/span&gt;과 코드 스플리팅&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; text-align: start;&quot;&gt;코드에서 당장 사용하는 부분만을 로딩하고, 현재 필요하지 않은 코드 부분은 따로 분리시켜 나중에 로드함으로써 로딩시간을 개선하는 것&lt;/span&gt;)&lt;/span&gt;을 통해 번들 크기를 줄여주고, 프로덕션 환경에서 높은 성능을 제공해주게 됩니다. 이는 React 애플리케이션의 빠른 개발과 운영을 가능케하며, 빠른 핫 모듈 리플레이먼트(HMR)을 통해 수정된 코드를 빠르게 반영하여 개발자에게 더 나은 개발 경험을 제공해줍니다. Vite의 모듈 기반의 개발은 선언저인 종속성을 자동으로 추적하고 최적화하므로, 불필요한 코드가 번들에 포함되지 않고, 필요한 부분만 효과적으로 로드되어 초기 로딩 속도를 향상시킵니다. 따라서, Svelte를 통해 얻을 수 있는 이점들을 기존에 사용하던 React와 결합된 Vite 형태를 통해 충족할 수 있어보입니다.&lt;/p&gt;</description>
      <category>Frontend</category>
      <category>framework</category>
      <category>Frontend</category>
      <category>react</category>
      <category>svelte</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/47</guid>
      <comments>https://0biglife.tistory.com/entry/React-vs-Svelte#entry47comment</comments>
      <pubDate>Sun, 14 Jan 2024 00:59:54 +0900</pubDate>
    </item>
    <item>
      <title>NIPA인증 1. Outline</title>
      <link>https://0biglife.tistory.com/entry/nipa1</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxpBTa/btsHmQIuakg/wXZfTjIZcAzjF32G3tW3C0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxpBTa/btsHmQIuakg/wXZfTjIZcAzjF32G3tW3C0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxpBTa/btsHmQIuakg/wXZfTjIZcAzjF32G3tW3C0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxpBTa%2FbtsHmQIuakg%2FwXZfTjIZcAzjF32G3tW3C0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;966&quot; height=&quot;543&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;너무나도 오랜만인 글.. 반성 한 번 하고 시작..&lt;br /&gt;&lt;br /&gt;&amp;nbsp;개발 일을 시작한지 오늘이 정확히 16개월째인 날이다. 앱 프론트엔드 직무를 9개월 하다가 현재는 R&amp;amp;D 분야로 옮겨서 클라우드 SaaS 솔루션 개발을 하고 있다. (react-native)앱/(react)웹 프론트엔드와 nestjs 백엔드, 그리고 인프라 공부들을 확장시켜오면서 기록의 필요성을 느끼는 와중에 오늘은 무조건 시작해야겠다 싶어 냅다 블로그에 뛰어들어왔다. 그치만 이번 글은 그 시작점으로&amp;nbsp;근래 메인으로 맡은 NIPA 클라우드 컴퓨팅 품질 성능 인증에 대하여 아카이빙해보려고 한다. 아마 이번 게시글에서는 일정과 절차에 대해서만 다루고, 다음 게시글에서 기술적인 기록을 할 예정이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;NIPA인증&lt;/b&gt;은 클라우드컴퓨팅서비스 품질 성능 인증으로, SaaS솔루션이 가지고 있어야 할 가용성, 응답성,&amp;nbsp; 확장성, 신뢰성, 서비스 지속성 등 에 대한 항목에 대하여 검증되었음을 알려준다. 국내 시장에서의 여러 솔루션들도 NIPA 인증을 받은 것으로 알고 있으며, 그 절차는 한국클라우드산업협회(KACI)와 한국정보통신기술협회(TTA)를 통하여 진행되었다. 첫 미팅에서는 간략하게 시험 일정과 작성되어야할 문서 리스트에 대한 안내를 받았으며, 다음 미팅은 실제 서비스의 SaaS 솔루션 점검 항목에 대한 시험을 진행할 때 진행하기로 하였다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;소장님께서 업무 스케일과 일정에 맞게 원하는 인력과 세부 일정을 공유해달라고 하셨고 그 결과 문서와 프론트 이슈를 잡아줄 신입 개발자분과 백과 인프라를 같이 보시는 개발자 분을 포함한 세 명이서 진행을 하였다. 인증 취득까지 완료되는 기한은 대략 3개월 정도 걸릴 예정이며, 2달 간은 KACI와 TTA에 제출해야하는 문서 작성을 하고 나머지 한 달은 실제 서비스 인증을 위한 시험으로 진행될 예정이다. KACI에서는 SLA, 서비스 정책, 사용 설명서 등을 포함한 솔루션에 대한 전반적인 문서를 요구했고, TTA에서는 가용성, 응답성, 확장성, 신뢰성, 서비스 지속성과 같은 항목에 대한 구체적인 시험 환경과 사내 정책에 대한 문서가 요구되었다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;시험 기준에 대한 추가적인 설명으로는 &lt;b&gt;가용성&lt;/b&gt;은 에러율이 얼마나 발생하는지를 의미하고, &lt;b&gt;응답성&lt;/b&gt;은 응답속도, &lt;b&gt;확장성&lt;/b&gt;은 서버가 얼마나 빠르게 확장(scaling)되는지, &lt;b&gt;신뢰성&lt;/b&gt;은 데이터 백업/복원/폐기 정책이 잘 마련되어있고 실제로 백업된 내역이 표시되고 복원은 얼마나 빠르게 처리되는지에 대해 다룬다. 따라서, 다음 게시글에서는 이러한 각 항목들을 한국정보통신기술협회에서 실제로 시험을 보기 위하여 어떤 방식으로 구현을 하였는지에 대해 다룰 예정이다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infrastructure</category>
      <category>0bigtlife</category>
      <category>kaci</category>
      <category>NIPA</category>
      <category>TTA</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/46</guid>
      <comments>https://0biglife.tistory.com/entry/nipa1#entry46comment</comments>
      <pubDate>Sat, 14 Oct 2023 16:42:31 +0900</pubDate>
    </item>
    <item>
      <title>6월부터 9월, 발전의 시간</title>
      <link>https://0biglife.tistory.com/entry/6%EC%9B%94%EB%B6%80%ED%84%B0-9%EC%9B%94-%EB%B0%9C%EC%A0%84%EC%9D%98-%EC%8B%9C%EA%B0%84</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uj3Pt/btrLEHAKUsH/8Dbq2G6iKDMXmTvoO79EZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uj3Pt/btrLEHAKUsH/8Dbq2G6iKDMXmTvoO79EZk/img.png&quot; data-alt=&quot;히어로 개발자가 되고 싶어,,&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uj3Pt/btrLEHAKUsH/8Dbq2G6iKDMXmTvoO79EZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuj3Pt%2FbtrLEHAKUsH%2F8Dbq2G6iKDMXmTvoO79EZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1266&quot; height=&quot;962&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;히어로 개발자가 되고 싶어,,&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;오랜만에 글을 써봅니다. 바쁜 탓에 블로그를 들어오지 못한 사이 누적 조회수가 1만이 넘어섰는데 뭔가 기쁘네요. 제 블로그가 공부에 대한 기록이지만 그와 동시에 누군가가 공부하기에 도움이 되길 바라는 마음이었는데 실제로 그랬을지..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;6월 중순에 모빌리티 서비스 개발을 하는 스타트업에 들어가게 되었습니다. 현재까지 열심히 달렸고 며칠 전에 정규직 계약을 했습니다. 수습을 마치고 새 계약서를 쓰면서 더 열심히 달릴 예정이에요. 요새 드는 생각은 개발자하기를 참 잘했다고 생각합니다. 능동적으로 발전하는 직업을 갈구했고 그게 적성과 맞아서 꽤 기쁩니다. 신입 개발자로서 지금 회사에 들어오게 된 것은 제가 이뤄낸 인생에서 첫 번째 성공이자 도약점입니다. 지금 제 모습이 5년,10년 후에는 월급쟁이로 사는 것 이상의 값어치를 할거라 믿고 있고, 사실 생각 이상으로 강한 확신을 가지고 있습니다. 그러기에 매일같이 업무를 하고 있는데&amp;nbsp; 미래에 후임 개발자들을 가르칠 수 있는 능력있는 개발자가 되기 위한 거름이라는 마음으로 번아웃이 오지 않을 만큼의 꾸준한 속도로 달리고 있습니다. 요새 일상은 퇴근하고 집에서 밥을 먹고 카페에서 11시까지 개발을 하고 귀가하고 있네요. 내일은 추석 전날이기 때문에 여유로운 마음으로 나홀로 코노를 부술 생각이에요.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YFT5M/btrR8Vf9ntj/CMAEavtffIkSNGM8gK1uh0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YFT5M/btrR8Vf9ntj/CMAEavtffIkSNGM8gK1uh0/img.jpg&quot; data-alt=&quot;베이맥스 멋있죠,? 제 장래희망.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YFT5M/btrR8Vf9ntj/CMAEavtffIkSNGM8gK1uh0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYFT5M%2FbtrR8Vf9ntj%2FCMAEavtffIkSNGM8gK1uh0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;1280&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;베이맥스 멋있죠,? 제 장래희망.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그 사이에 배운 것들이 많아요. 먼저 디자이너와 개발자들과 어떻게 소통하는지, 개발자들과 소통할 때 어떤 방식으로 대화를 하는지와 같은 기술적인 것 외에 소통에 대해서 많이 배웠습니다. 기술적으로는 그 동안 알고 있던 흐릿한 것들이 조금 더 뚜렷해졌고, 앱이 완성되기까지 전체적인 그림은 어느 정도 손에 익는 것 같습니다(아직 부족하지만..) 가장 최근에 다뤘던 것 중에서 가장 흥미로웠던 것은 안드로이드 APK 를 추출해서 실기기에 담아서 테스팅하는 것과, iOS 의 경우에는 Test Flight로 어떤 방식으로 배포하는지,, 퍼블리셔와 프론트엔드 개발자의 차이,, 특정 프로세스에서 데이터를 가공할 때 프론트에서 해야할지 백에서 해야할지 등등,,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;적고픈 것들이 너무 많은데 카페 닫는 시간이 되어가서 우다다 적었네요. 다음에 다시 적어보겠습니다 : )&amp;nbsp;&lt;/p&gt;</description>
      <category>Entj Life</category>
      <category>0biglife</category>
      <category>ENFJ</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/43</guid>
      <comments>https://0biglife.tistory.com/entry/6%EC%9B%94%EB%B6%80%ED%84%B0-9%EC%9B%94-%EB%B0%9C%EC%A0%84%EC%9D%98-%EC%8B%9C%EA%B0%84#entry43comment</comments>
      <pubDate>Wed, 7 Sep 2022 21:37:37 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] React Query (2) useQuery 적용 (+Axios 구조화)</title>
      <link>https://0biglife.tistory.com/entry/React-Native-React-Query-2-%EC%A0%81%EC%9A%A9%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtem5d/btrCz0X0p3f/Hb6kaoPVJYQSxXlRmbXkM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtem5d/btrCz0X0p3f/Hb6kaoPVJYQSxXlRmbXkM0/img.png&quot; data-alt=&quot;퐁퐁&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtem5d/btrCz0X0p3f/Hb6kaoPVJYQSxXlRmbXkM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdtem5d%2FbtrCz0X0p3f%2FHb6kaoPVJYQSxXlRmbXkM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;430&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;퐁퐁&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;너무너무 오랜만에 글이다.. 최근에는 졸업 전시 관련 하드웨어 쪽 코드를 다루고 이력서 작성에 정신이 팔려서 개발의 비중이 조금 줄어들었다. 이력서를 작성하면서 느낀 것 중에서 클린 코드에 대해 다룬 것이 있다. 서칭을 하면서 찾아보는데 클린 코드를 위한 가장 기본적인 참고사항은 적용을 마쳤다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변수명(네이밍 규칙) 명시적으로 변경&lt;/li&gt;
&lt;li&gt;삼항연산자의 상황에 따른 대응 ( + 논리 연산자(&amp;amp;&amp;amp;)로 조건부 렌더링 적용 )&lt;/li&gt;
&lt;li&gt;True(boolean) 값을 Props로 넘길시 생략&lt;/li&gt;
&lt;li&gt;useEffect Hook 내부 함수는 밖으로 뺄 것&lt;/li&gt;
&lt;li&gt;인자가 Event 뿐인 이벤트 핸들러 함수는 함수명만 명시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React-Query와 Axios 모듈화로 API 요청 관리 &lt;/b&gt;(진행중)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;정도가 될 것 같은데 마지막 항목은 현재 작업 중이다. 현재는 검색 기능에서 조금 말썽을 부리는 중인데 우선 홈뷰와 맵뷰에서 unsplash API 로 데이터를 불러올 때 어떤 방식으로 .get 요청을 완료했는지를 정리해보려고 한다. 그리고 마지막에서는 react-query를 적용하기 전과 후의 코드 모두 비교해볼 예정이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWHglh/btrCDcoPbHV/arix4XGxb6GP5ngxwMWys1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWHglh/btrCDcoPbHV/arix4XGxb6GP5ngxwMWys1/img.png&quot; data-alt=&quot;api 폴더 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWHglh/btrCDcoPbHV/arix4XGxb6GP5ngxwMWys1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWHglh%2FbtrCDcoPbHV%2Farix4XGxb6GP5ngxwMWys1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;316&quot; height=&quot;543&quot; data-origin-width=&quot;438&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;api 폴더 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;React-Query 를 적용하기 전에 Axios 관련 폴더와 네이밍 정리를 먼저 했다. 일단 한눈에 봐도 좀 지저분하다. 가져와서 쓰고픈 API들이 있는데 서로 역할이 다르다보니 이런 식으로 짜여졌다. (나중에 진행할 프로젝트는 백엔드가 마련되어 있기 때문에 간단해질지도?)&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&lt;b&gt;Firebase&lt;/b&gt; : 유저 프로필 관리를 위해서 적용했다. 유저가 &lt;u&gt;프로필 편집할 시 이미지 전송&lt;/u&gt;을 위해 firebase-storage가 필요했고, 유저가 이미지와 텍스트 등의 데이터를 &lt;u&gt;게시글로 업로드&lt;/u&gt;하기 위해서는 해당 데이터 구조가 마련되어 있는 서버가 필요하여 firestore를 사용했다.&lt;br /&gt;&amp;nbsp;&lt;b&gt;MarkerAPI&lt;/b&gt; : 이 API는 지금 만들고 있는 앱 이전에 강의를 구입해서 듣던 프로젝트에서 강사분께서 직접 작성해서 공유해준 서버이다. 이 API에서는 로그인, 회원가입에 대한 요청을 보내면 유저 정보를 저장해주며, 일정 시간마다 지도 위에 마커를 뿌려준다. 강좌에서는 배달앱을 위하여 사용자들이 배달기사에게 요청을 보내는 마커를 필자는 생존자의 &lt;u&gt;위치 표시&lt;/u&gt;에 사용했다.&lt;br /&gt;&amp;nbsp;&lt;b&gt;SampleData&lt;/b&gt; : 이 폴더는 조만간 지울 폴더이다. Axios 구현 테스트를 하면서 API 요청을 보내기 전에 데이터를 내장된 폴더에서 가져와서 화면에 뿌려주는 용도로 작성되었으며, json 형태로 유저 정보, 게시글을 담당한다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;b&gt;UnsplashAPI&lt;/b&gt; : 독학하면서 가장 편하게 사용하고 있는 API이다. 이미지를 요청 파라미터에 count 변수에 원하는 만큼, 원하는 키워드로 요청해서 가져올 수도 있고, Unsplash 유저의 정보도 가져올 수 있어 정말 감사하게 생각하고 있는 API이다. 현재 내 프로젝트에서는 &lt;u&gt;홈뷰의 게시글&lt;/u&gt;과 &lt;u&gt;유저 데이터&lt;/u&gt;, 맵뷰에서의 마커에 표시할 &lt;u&gt;유저 정보&lt;/u&gt;, 채팅 카테고리의 &lt;u&gt;유저 리스트&lt;/u&gt;가 이 API로 적용된 상태이다.&lt;br /&gt;&amp;nbsp;&lt;b&gt;WeatherAPI&lt;/b&gt; : 맵뷰에서 사용자에게 날씨 정보를 제공해주기 위해 가져온 API이다. 날씨 정보를 날짜별로 가져오면 좋았을텐데 오늘에 대한 데이터만 제공해주는 부분은 조금 아쉬웠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이제 어떤 방식으로 axios를 정리했는지 정리해본당. 크게 client, service, type으로 분류를 했다. client.tsx는 가장 기본적인 인스턴스 생성을 위하여 만들었고 내부에는 url, header처리를 포함시켜다. service.tsx는 나중에 프로젝트 크기가 커지면서 api 호출이 많아졌을 때 좀 더 분기처리가 필요하겠지만, 일단은 service.tsx 하나에 다 포함시킬 생각이며 내부에는 사진 요청에 대한 api, 유저 정보 요청에 대한 api 함수를 넣어둔 상태이다. 마지막으로 type.ts는 현재 TypeScript를 적용했기 때문에 불러온 데이터에 대한 타입을 정리해주기 위한 용도로 작성되었다. 순서대로 코드는 아래와 같다!&lt;/p&gt;
&lt;pre id=&quot;code_1652959282870&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// : UnsplashAPI/client.tsx

import axios from 'axios';
import Config from 'react-native-config';

const client = axios.create({
  baseURL: 'https://api.unsplash.com',
  params: {
    client_id: `${Config.UNSPLASH_ACCESSTOKEN}`,
  },
});

export default client;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652959452758&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// : UnsplashAPI/service.tsx

import client from './client';
import {randomPhotoState, searchUserState} from './type';

export const getPhoto = async () =&amp;gt; {
  const response = await client.get&amp;lt;randomPhotoState[]&amp;gt;('/photos/random', {
    params: {
      count: 3,
      query: 'war',
    },
  });
  return response.data;
};

export const getUser = async () =&amp;gt; {
  const response = await client.get&amp;lt;randomPhotoState[]&amp;gt;('photos/random', {
    params: {
      count: 12,
    },
  });
  return response.data;
};

export const searchUser = async (text: string) =&amp;gt; {
  const response = await client.get&amp;lt;searchUserState&amp;gt;('/search/users?', {
    params: {
      query: text,
    },
  });
  return response.data.results;
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652959475976&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// : UnsplashAPI/type.ts

export type randomPhotoState = {
  alt_description: string;
  blur_hash: string;
  color: string;
  created_at: string;
  description: string;
  downloads: number;
  id: string;
  liked_by_user: boolean;
  likes: number;
  links: {
    // 생략
  };
  location: {
    // 생략
  };
  promoted_at: string;
  sponsorship: string;
  updated_at: string;
  urls: {
    // 생략
  };
  user: {
    // 생략
    profile_image: {
    // 생략
    };
    username: string;
  };
  view: number;
  width: number;
  height: number;
};

export type searchUserState = {
  total: number;
  total_pages: number;
  results: [searchUserResults];
};

export type searchUserResults = {
  id: string;
  updated_at: string;
  username: string;
  name: string;
  first_name: string;
  last_name: string;
  bio: string;
  location: string;
  links: {
    // 생략
  };
  profile_image: {
    // 생략
  };
  social: {
    instagram_uesrname: string;
  };
  photos: [
    {
    // 생략
      urls: {
    // 생략
      };
    },
  ];
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이젠 홈뷰에서 게시글 정보들을 unsplash api 를 통해 가져와서 react-query에서 제공해주는 useQuery훅을 통하여 화면에 뿌려줄 것이다. (기본 문법에 대한 링크는 &lt;a href=&quot;https://0biglife.tistory.com/entry/React-Query&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기!&lt;/a&gt;) 데이터를 조회하기 위해서는 useQuery 훅을 쓰고, 갱신하기 위해서는 useMutation 을 사용하는데 여기서는 전자만 사용한다.&lt;br /&gt;&amp;nbsp;가장 먼저 useQuery 첫 번째 인자로는 키 값, 두 번째 인자로는 Promise 반환 함수를 넣어줄 것인데, Axios 구조를 정리한 덕에 두 번째 인자는 미리 정리된 함수만 불러와서 넣어준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652961549048&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {data, isLoading} = useQuery('homePost', getPhoto);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;반환값은 실제 가공할 data와 data가 들어오지 않았을 때 로직처리를 위한 isLoading 값을 받아서 다음과 같이 처리했다. data가 없는 동안 ActivityIndicator를 넣어주고 data가 있을 때에는 콘솔에 Flatlist의 renderItem에 넣어줄 데이터를 넣어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1652961671221&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (!data) {
  console.log('data uploading : ', isLoading);
  return (
    &amp;lt;ActivityIndicator
      style={{
        flex: 1,
        justifyContent: 'center',
        alignContent: 'center',
      }}
      size=&quot;small&quot;
      color=&quot;gray&quot;
    /&amp;gt;
  );
} else {
  console.log('Home useQuery Data : ', data);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이제 이 data를 원하는 형태로 가공해서 화면에 뿌려주면 된다! 구현 자체는 생각보다 너무 간단했다. 아래 Axios를 직접 불러서 사용했을 때보다는 전체 코드 로직에 대한 가독성이 뛰어나다. useState를 통해 데이터를 받고 데이터가 없을 때의 상태 관리도 try-catch 문 안에서 잡아줬어야했는데 이 과정도 축약되고 한 눈에 보기 굉장히 편해졌다. 추가적으로, useQuery의 isError, isFetching을 통해 추가 로직 생성도 가능하고, Options을 사용해서 요청에 대한 자세하게 다룰 수도 있는데 아직 이 단계는 스터디 및 적용되지 않은 상태이다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;전체 코드&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1652960029111&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// React-Query 적용 전 !

const HomeFeed = () =&amp;gt; {
  const [loading,setLoading] = useState&amp;lt;boolean&amp;gt;(false);
  const [data,setData] = useState();

  const getUser = async () =&amp;gt; {
      try {
        setLoading(true);
        const response = await client.get('/photos/random', {
          params: {
            count: 1,
            query: 'war',
            client_id: `${Config.UNSPLASH_ACCESSTOKEN}`,
          },
        });
        setData(response.data);
        console.log('HomeFeed Unsplash Data : ', response.data);
      } catch (e) {
        console.log('HomeFeed/getUser Error : ', e);
      } finally {
        setLoading(false);
      }
    };
    
    useEffect(() =&amp;gt; {
      getUser();
    }, []);
   
   // 생략
   
 }
 
 export default HomeFeed;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1652961328996&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// React-Query 적용 후 !

import {useQuery} from 'react-query';

const HomeFeed = () =&amp;gt; {
  const navigation =
    useNavigation&amp;lt;NativeStackNavigationProp&amp;lt;HomeFeedStackParamList&amp;gt;&amp;gt;();
    
  const {data, isLoading} = useQuery('homePost', getPhoto);
  
  if (!data) {
    console.log('data uploading : ', isLoading);
    return (
      &amp;lt;ActivityIndicator
        style={{
          flex: 1,
          justifyContent: 'center',
          alignContent: 'center',
        }}
        size=&quot;small&quot;
        color=&quot;gray&quot;
      /&amp;gt;
    );
  } else {
    console.log('HomeFeed useQuery Data : ', data);
  }
  
  const renderItem = ({item}: {item: randomPhotoState}) =&amp;gt; {
    return (
      &amp;lt;CellContainer
        style={{
          height: item.user.bio ? 390 : 320,
          shadowColor: 'black',
          shadowOpacity: 0.2,
          shadowRadius: 3,
          shadowOffset: {width: 3, height: 3},
        }}&amp;gt;
        &amp;lt;HeaderSection&amp;gt;
          &amp;lt;ProfileView
            activeOpacity={0.2}
            onPress={() =&amp;gt;
              navigation.navigate('UserProfile', {
                id: item.id,
                user_name: item.user.name,
                user_location: item.user.location,
                user_profile: item.user.profile_image.large,
              })
            }&amp;gt;
            &amp;lt;ProfileImage source={{uri: item.user.profile_image.large}} /&amp;gt;
          &amp;lt;/ProfileView&amp;gt;
          &amp;lt;InfoView&amp;gt;
            &amp;lt;Name&amp;gt;{item.user.name}&amp;lt;/Name&amp;gt;
            &amp;lt;Location&amp;gt;{item.user.location}&amp;lt;/Location&amp;gt;
          &amp;lt;/InfoView&amp;gt;
        &amp;lt;/HeaderSection&amp;gt;
        &amp;lt;ImageSection
          style={{
            borderBottomLeftRadius: item.user.bio ? 0 : 10,
            borderBottomRightRadius: item.user.bio ? 0 : 10,
          }}
          source={{uri: item.urls.full}}
        /&amp;gt;
        &amp;lt;BodySection&amp;gt;
          &amp;lt;BodyText numberOfLines={3}&amp;gt;{item.user.bio}&amp;lt;/BodyText&amp;gt;
        &amp;lt;/BodySection&amp;gt;
      &amp;lt;/CellContainer&amp;gt;
    );
  };
  return (
    &amp;lt;MainContainer&amp;gt;
      &amp;lt;SearchBar/&amp;gt;
      &amp;lt;IonIcon
        // 생략
      /&amp;gt;
      &amp;lt;FlatList
        data={data}
        renderItem={renderItem}
        keyExtractor={item =&amp;gt; item.id}
        showsVerticalScrollIndicator={false}
      /&amp;gt;
    &amp;lt;/MainContainer&amp;gt;
  );
};

export default HomeFeed;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frontend/Axios</category>
      <category>0biglife</category>
      <category>react native</category>
      <category>react-query</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/42</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-React-Query-2-%EC%A0%81%EC%9A%A9%EA%B8%B0#entry42comment</comments>
      <pubDate>Thu, 19 May 2022 19:59:48 +0900</pubDate>
    </item>
    <item>
      <title>2022년 5월 11일 : 첫 번째 이력서 회고록</title>
      <link>https://0biglife.tistory.com/entry/2022%EB%85%84-5%EC%9B%94-11%EC%9D%BC-%EC%B2%AB-%EB%B2%88%EC%A7%B8-%EC%9D%B4%EB%A0%A5%EC%84%9C-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQhJjJ/btrCAw9S2Pd/tkKkHQgPRkpuRKPlSPwvn1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQhJjJ/btrCAw9S2Pd/tkKkHQgPRkpuRKPlSPwvn1/img.jpg&quot; data-alt=&quot;호이팅-! (by.파키)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQhJjJ/btrCAw9S2Pd/tkKkHQgPRkpuRKPlSPwvn1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQhJjJ%2FbtrCAw9S2Pd%2FtkKkHQgPRkpuRKPlSPwvn1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;600&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;호이팅-! (by.파키)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp; 어제 &lt;b&gt;React Native 포지션의 이력&lt;/b&gt;서를 드디어 제출했다. 개발로서의 첫 번째 이력서였으며, 이래저래 많은 생각이 들었다. 무엇보다 가장 강하게 든 생각은 '이력서를 쓰는 과정조차 좋은 경험'이라는 것이다. 배우는 과정은 정리하고 되풀이하는 데에서 그 모양이 더 잡힌다는 것을 잘 알고 있지만, 이력서를 정리하고 쓰는 이 전체 과정은 이를 강하게 보여주면서 동시에 현실적인 내 모습을 냉정하게 드러내는 고통스러운 시간이지 않나 싶다. 내가 어떤 게 부족하고, 어떤 것들이 어필이 덜 되는지를 스스로 느끼는 도중에도 정리를 멈출 수 없음이 마음을 더 무겁게 한다. &lt;br /&gt;&amp;nbsp;혹자는 '&lt;u&gt;부족한 것을 알면서 왜 더 준비하고 내지 않는가?&lt;/u&gt;'라고 물음을 던질지도 모르겠다. 필자는 학원과 같은 커리큘럼 없이 구글링으로 시작하여 내가 원하고자 하는 결과물을 위해 어떤 것들이 필요하고 부족한지를 찾아보고 지도를 그려나가면서 달려왔다. 구체적으로는, 네이티브 앱 개발을 하다가 ios, android 유저 모두 사용하는 앱을 만들고 싶어 하이브리드 앱 개발 방식으로 전향했는데 그 과정에서 Flutter와 React Native 갈림길을 선택해야 했다. 인터넷에서는 수많은 조언들이 있는데 어떤게 내가 원하는 것인지 남의 말을 듣고는 판단하기 힘들어 Flutter를 가장 먼저 접해본 후, RN 으로 방향을 틀었다.(Flutter가 아닌 RN으로 갈아탄 이유는 (1)서드파티 라이브러리가 많기에 기능에 대한 확장성이 뛰어나다고 판단했고, (2)Flutter보다 개발자 커뮤니티가 넓기 때문에 이는 개발함에 있어 무시할 수 없는 이점이라고 판단했다. 따라서, 결국 단순한 기능에서 그치는 앱을 개발할게 아니라면 RN으로 개발하는 것이 올바른 선택으로 보인다. 반면에, RN에서는 복잡하게 구현해야하는 기능이 Flutter에서는 간단하게 구현되는 것은 조금 부러웠다.) RN 역시 시작부터 Expo,Expo(bare), Cli 갈림길이 있어서 Expo부터 하나씩 공부해보았다. 어떻게 보면 조금,, 멍청한..? 방식일 수 있는데 시간을 이렇게 갈아서라도 내가 '왜 저 길이 안좋은 방식이지?'를 깨달았기에 낭비라고 생각하지는 않는다. &lt;br /&gt;&amp;nbsp;이러한 과정에서 뼈저리게 느낀 점은 &lt;b&gt;'배울 수 있는 하나의 장'&lt;/b&gt;이 필요하다는 것이다. 어느 정도 달려온 상태에서 누군가 가르쳐줬으면 좋겠다는 생각이 들어 뒤늦게 학원을 찾아봤다. 그러나 지금 학원을 다니기에 초반 커리큘럼은 나에게는 시간낭비라고 생각이 되었고, 숨고, 당근 마켓과 같은 커뮤니티를 통해 스터디로 진행해보려 했으나 멘토를 구하거나 내가 원하는 방향성과 맞게 같이 달릴 수 있는 인력을 찾기가 힘든 게 현실이었다. 그렇기 때문에 내가 최소한의 능력과, 회사에서 나에게 바라는 열정이 뒷받침이 된다면 하루빨리 입사해서 동기 또는 선배들을 통해 실무를 접하면서 나의 능력치를 도약시키고 싶다는 욕구가 큰 것이다.&lt;br /&gt;&amp;nbsp;따라서, 이번 이력서를 쓰면서 부족한 점들을 더 빠르게 채워야겠다는 것을 느꼈기에 나름 개발만 하던 내 건조한 일상에 또 다른 동기부여가 되었다고 생각한다. 어떤 것들이 부족했을까 간단히 몇 문장으로 추려서 정리해보았다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;회고 1.&amp;nbsp; 이력서 작성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;이력서를 어떻게 작성해야할지 몰라 개발 공부하듯이 최대한 구글링과 유튜브를 돌려가며 나에게 맞는 작성법을 적용해보려고 했다. 불필요한 정보가 없으면서, 깔끔하되 내가 어떤 것들을 다룰 줄 아는지 읽고 싶게 만드는 이력서를 작성하고 싶었다. (but,, 생각보다 줄줄이 설명해서 어필할만한 기능들이 많지가 않아 이 부분은 한계가 있었다.) 더불어, 나는 회사 들어가서 진행한 프로젝트와 같은 경력이 없기 때문에 이를 내가 여태 진행한 독학 Study Experience와 이력서 작성을 위하여 한 달간 만들어본 Project Experience를 넣었다. 솔직히 말하면 그렇게 조촐할 수가 없다.. 분하지만 어쩔 수 있나 더 열심히 못 달린 자신을 탓하고 동기부여로 삼는 수밖에!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;회고 2.&amp;nbsp; 코드 작성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;실무에서 나를 어떻게 평가할지를 생각해보았을 때, 나라는 사람이 어떻게 다른 사람들과 협업할 수 있으며, 어떤 코드 습관과 얼마나 꾸준히 공부하고 깊게 이해하여 주어진 미션을 빠른 시간 안에 정확하게 해결할 수 있는지를 궁금해할 것이라고 생각한다.&amp;nbsp; &amp;nbsp;&lt;br /&gt;&amp;nbsp;일단 코드가 깔끔해야 한다. 내가 쓰기에도 불편한 것들이 많았다. 이를 테면, image picker를 통하여 회원 가입할 때, 포스팅할 때, 프로필 이미지 편집할 때와 같이 두 번 이상 쓰이는 동작들은 재사용되도록 만들어야 한다. 심지어 image picker의 경우에는 한 화면에서 사용하려면 코드도 꽤 길다. &lt;u&gt;내 자신이 쓰기도 불편한 코드들은 상대방도 이해하기 힘든 것은 당연&lt;/u&gt;하고, 이는 &lt;u&gt;제 3자가 코드를 편집할 때에도 불편한 최악의 상황&lt;/u&gt;이지 않나 싶다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;이러한 재사용성에 대해 인지하고 적용하는 것이 숙달된다면, 코드 가독성뿐만 아니라 앱 전체 작동에도 분명 긍정적인 영향을 미칠 것이다. 재사용을 따지자면 또 어떤 것들이 떠오르는가? 빈번하게 쓰이는 동작 중 하나로는 &lt;u&gt;API 요&lt;/u&gt;청이 있다. axios 인스턴스를 사용하여 간단한 모듈을 만드는 것에서 그치지 않고 요청에 대한 함수를 개별 폴더로 묶어서 요청이 필요할 때 함수명과 파라미터만 전달한다면 그 역시 가독성이 뛰어날 것이다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;클린 코드에 대한 포스팅을 읽다가 이 전에 스터디를 진행했던 &lt;u&gt;React-query&lt;/u&gt;를 왜 쓰는지에 대해 다룬 포스팅을 읽었는데, react-query를 반영하게 되면 요청마다의 역할에 있어 서로 침범하지 않고 완벽하게 분리되기 때문에 유익하다고 한다. 이 점도 참고해야겠다. 클린 코드에 대한 포스팅을 좀 더 찾아보고 정리를 해서 개별 포스팅을 해볼 생각이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;첫 시도로서 설레기도 두렵기도 하지만 아직 너무나도 귀여운 나의 지식과 개발 실력을 보면 앞으로 얼마나 더 달려야 스스로 만족할지 조금은 막막하기도 하다. 하지만 언제나 그랬듯 이 모든 감정을 동기부여로 가공할 수 있음에 감사한다. 이런 글을 소중한 시간 내어 읽느라 감사한 마음을 전하고 나의 기록이 타인에게 또 다른 영감을 불러일으키길 바란다. 그럼 오늘도 해ㅡ피 개발타임을 보내길 바란다 : )&lt;/p&gt;</description>
      <category>Entj Life</category>
      <category>0biglife</category>
      <category>신입개발자</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/41</guid>
      <comments>https://0biglife.tistory.com/entry/2022%EB%85%84-5%EC%9B%94-11%EC%9D%BC-%EC%B2%AB-%EB%B2%88%EC%A7%B8-%EC%9D%B4%EB%A0%A5%EC%84%9C-%ED%9A%8C%EA%B3%A0%EB%A1%9D#entry41comment</comments>
      <pubDate>Wed, 11 May 2022 18:11:05 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] Hook (1) - useMemo, useCallback</title>
      <link>https://0biglife.tistory.com/entry/React-Native-Hook-1-useMemo-useCallback</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;605&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w1qEN/btrynhvhmaw/wkhHU8N0PurA5mYzODq1Y1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w1qEN/btrynhvhmaw/wkhHU8N0PurA5mYzODq1Y1/img.jpg&quot; data-alt=&quot;훅 정리 화이팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w1qEN/btrynhvhmaw/wkhHU8N0PurA5mYzODq1Y1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw1qEN%2Fbtrynhvhmaw%2FwkhHU8N0PurA5mYzODq1Y1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;897&quot; height=&quot;485&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;605&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;훅 정리 화이팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;오늘은 헷갈린 Hook들에 대해 정리해보려고 합니다. useState와 useEffect보다는 useMemo, useCallback이 다소 헷갈리기 때문에 해당 글을 작성하게 되었습니다. useEffect의 라이프사이클 관련 정리도 필요해 보이지만 그건 추후 추가된 글에서 다뤄보겠습니다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&quot;useMemo와 useCallback이 &lt;b&gt;왜 필요한가?&lt;/b&gt;&quot;를 먼저 생각해보겠습니다. 리액트 내장 훅인 이 두 함수가 우리에게 편리함을 제공해준다면, 분명히 필요성이 제시되었기 때문에 누군가 개발했다는 것을 의미를 합니다. 가장 핵심적인 키워드는 '렌더링 최적화'입니다. 예시를 하나 들어보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1H5NM/btrySOr79b7/rcf44qLkEpypTAOu7cB5uK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1H5NM/btrySOr79b7/rcf44qLkEpypTAOu7cB5uK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1H5NM/btrySOr79b7/rcf44qLkEpypTAOu7cB5uK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1H5NM%2FbtrySOr79b7%2Frcf44qLkEpypTAOu7cB5uK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;317&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;컴포넌트가 렌더링 되었다는 것은 사용자가 해당 함수(컴포넌트)를 호출시켰다는 것을 의미하며, 이 과정에서는 내부에 있는 변수 또는 함수들은 매번 다시 선언됩니다. 컴포넌트는 자신의 state가 변경되거나, 부모 컴포넌트로부터 받은 props가 변경되었을 때 리렌더링이 일어나게 됩니다. 이 경우, 하위 컴포넌트가 최적화된 상태가 아니라면 부모 컴포넌트로부터 받은 props가 변경되지 않더라도 리렌더링이 일어나게 되는데, 여기서 문제가 발생합니다. A 컴포넌트가 여러 개의 state, props를 받는 상태에서 &lt;b&gt;하나의 state가 변경된다면 나머지 state들도 모조리 다시 계산&lt;/b&gt;되어야 합니다. 이러한 불필요한 렌더링이 일어나는 경우를 대비하여 &lt;b&gt;React Rendering 최적화&lt;/b&gt;를 하기 위해서는 &lt;b&gt;메모이제이션(Memoization)&lt;/b&gt; 기능을 지원하는 리액트의 내장 훅인 useMemo와 useCallback이 필요한 것입니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;추가적으로, 리렌더링 조건은 다음과 같다.&lt;br /&gt;&amp;nbsp;&amp;rarr; 자신의 state가 변경될 때&lt;br /&gt;&amp;nbsp;&amp;rarr; 부모로부터 받은 props가 변경될 때&lt;br /&gt;&amp;nbsp;&amp;rarr; 부모 컴포넌트가 리렌더링 될 때&lt;br /&gt;&amp;nbsp;&amp;rarr; forceUpdate()를 실행하였을 때&amp;nbsp;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;메모이제이션(Memoization)이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;메모이제이션은 일종의 *캐싱과 같습니다. 특정 연산을 반복해야 할 때 사용되는 기법으로, 반복되는 결과를 메모리에 저장하여 중복되는 연산 없이 빠른 실행을 가능하게 해 줍니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;span style=&quot;color: #666666;&quot;&gt;(*캐시(cache) : 데이터나 값을 미리 복사해놓는 임시 장소를 의미합니다. 캐시는 접근 시간에 비해 원래 데이터에 접근하는 시간이 오래 걸리는 경우나 연산을 반복해야 하는 시간을 절약하고 싶을 때 사용됩니다. 데이터를 미리 복사해놓으면 계산이나 접근 시간 없이 더 빠른 속도로 데이터 접근이 가능합니다.)&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qtMBR/btryP9dbODO/ZL2peWhflvlwGL1l5ksDv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qtMBR/btryP9dbODO/ZL2peWhflvlwGL1l5ksDv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qtMBR/btryP9dbODO/ZL2peWhflvlwGL1l5ksDv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqtMBR%2FbtryP9dbODO%2FZL2peWhflvlwGL1l5ksDv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;420&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이러한 메모이제이션 기능을 지원해주는 훅인 useMemo와 useCallback은 퍼포먼스를 최적화해준다는 점에서 같지만 그 쓰임새가 다른데, useMemo의 경우에는 메모이제이션된 값을 반환을 하고, useCallback의 경우에는 메모이제이션된 콜백을 반환합니다. 그 차이점은 useCallback은 전달된 함수 그 자체를 캐싱하지만, useMemo는 전달된 함수가 실행되고 반환된 결과값을 캐싱한다는 점에 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;useMemo&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1649494687588&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useMemo(() =&amp;gt; { /* 로직 */ }, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;useMemo의 첫 번째 인자에는 연산을 마치고 반환하는 함수를 주고, 두 번째 인자는 의존하는 상태값(배열)을 넣어준다. 여기서 이 배열은 depedencies array라고 부르고 줄여서는 deps라고 부른다. 즉, deps에 들어간 state 또는 props가 변경되었을 때 첫 번째 인자의 로직이 실행된다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;useCallback&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1649494996339&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [sum, setSum] = useState&amp;lt;number&amp;gt;(0);

const addNum = useCallback(() =&amp;gt; {
  setSum(num + 1);
}, [num]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;useCallback도 비슷하다. 첫 번째 인자에 들어가는 함수는 오직 의존하는 상태값(deps)이 변경된 경우에만 갱신이 되며, 위 예제의 경우에는 num이 변경될 시에 1만큼 더 해서 useState로 선언된 sum값에 넣어주는 형식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 예제.&lt;/p&gt;
&lt;pre id=&quot;code_1649496305233&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const [detail, setDetail] = useState&amp;lt;boolean&amp;gt;(false);
  
  const toggleDetail = useCallback(() =&amp;gt; {
    setDetail(prev =&amp;gt; !prev);
  }, []);

  const onAccept = useCallback(async () =&amp;gt; {
    try {
      // 서버 요청 API
    } catch (error) {
      // 에러 핸들링
    } 
    // Redux Store 로직
  }, [accessToken, dispatch, item.orderId, navigation]); //필요한 deps

  const onReject = useCallback(() =&amp;gt; {
    // 로직
  }, [dispatch, item.orderId]); // deps&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frontend/상태관리(Redux, Storage, ContextAPI)</category>
      <category>0biglife</category>
      <category>react native</category>
      <category>react native hook</category>
      <category>react native useCallback</category>
      <category>react native useMemo</category>
      <category>useCallback</category>
      <category>useMemo</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/39</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-Hook-1-useMemo-useCallback#entry39comment</comments>
      <pubDate>Mon, 4 Apr 2022 16:45:43 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] Async-Storage / Encrypted-Storage 필요성과 사용법</title>
      <link>https://0biglife.tistory.com/entry/React-Native-Async-Storage-Encrypted-Storage-%ED%95%84%EC%9A%94%EC%84%B1%EA%B3%BC-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6WgAO/btryhNU0FDk/wg06ZMDgLZKfBOjZzfnwy1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6WgAO/btryhNU0FDk/wg06ZMDgLZKfBOjZzfnwy1/img.jpg&quot; data-alt=&quot;개발은 밥심&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6WgAO/btryhNU0FDk/wg06ZMDgLZKfBOjZzfnwy1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6WgAO%2FbtryhNU0FDk%2Fwg06ZMDgLZKfBOjZzfnwy1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;554&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개발은 밥심&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;오늘은 스토리지에 대해서 다뤄볼까 합니다. 스토리지를 다루기 위해 두 가지 라이브러리를 정리해보려고 하는데, Async-storage와 Encrypted-storage가 바로 그 주인공입니다. 가장 먼저, 스토리지(Storage)를 왜 써야하는지, 어떤 데이터를 넣어야하며- 그렇다면 redux는 어떻게 활용 측면에서 비교가 되는지 정리를 하고 마지막엔 코드 예제까지 정리하면서 마무리 해보려고 합니다-!&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;465&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kUAKy/btryk3P7FOY/TE7j1TksSCFoHgibb6RSW0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kUAKy/btryk3P7FOY/TE7j1TksSCFoHgibb6RSW0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kUAKy/btryk3P7FOY/TE7j1TksSCFoHgibb6RSW0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkUAKy%2Fbtryk3P7FOY%2FTE7j1TksSCFoHgibb6RSW0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;311&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;465&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;a href=&quot;https://0biglife.tistory.com/entry/React-Native-Redux-toolkit-%EC%A0%81%EC%9A%A9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;어제 작성한 글&lt;/a&gt;에서는 효율적인 상태 관리를 위해서 &lt;b&gt;Redux-toolkit&lt;/b&gt;을 다뤄보았다. Redux의 store는 데이터 저장 공간으로 활용되며 실제로 앱이 켜져있는 상태에서 데이터를 불러오는 성능이 가장 뛰어나다. 하지만 앱이 꺼지면 데이터가 사라지는 &lt;b&gt;일시적 저장 공간&lt;/b&gt;이기 때문에 그 한계 또한 명확하다. (컴퓨터로 치면 RAM 같은 존재..)&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W2TZs/btryjGukABU/kA3cxLyX0p1q8QbsiL4Kn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W2TZs/btryjGukABU/kA3cxLyX0p1q8QbsiL4Kn1/img.png&quot; data-alt=&quot;Async Storage 한 줄 소개&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W2TZs/btryjGukABU/kA3cxLyX0p1q8QbsiL4Kn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW2TZs%2FbtryjGukABU%2FkA3cxLyX0p1q8QbsiL4Kn1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1726&quot; height=&quot;678&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Async Storage 한 줄 소개&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;따라서 데이터를 유지해줄 공간이 필요하다. 앱이 꺼졌다 켜진 후에도 데이터 유지가 가능한 것이 바로 Async-storage이며, 웹으로 치면 로컬스토리지와 유사하다. 그렇다면 Encrypted-storage는 왜 필요할까? 그 이유는 Async-storage 공식 문서에서 찾아볼 수 있다. 공식 문서 한 줄 소개에서는 Async-storage를 'An asynchronous, unencrypted, persistent, key-value storage system for RN'이라고 정의되어 있다. 그대로 해석하면, '&lt;b&gt;비동기적&lt;/b&gt; - &lt;b&gt;암호화되지 않으며&lt;/b&gt; - &lt;b&gt;'키-값'으로 저장&lt;/b&gt;되는 시스템' 이라고 한다. 데이터가 암호화되지 않는 스토리지이기 때문에 누구든지 값을 열어볼 수 있어 귀중한 토큰을 보관할 시에는 적합하지 않다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1728&quot; data-origin-height=&quot;804&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xbw1s/btryhO0I6Ln/5EYhbJSk2Q0wfBFPGboCZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xbw1s/btryhO0I6Ln/5EYhbJSk2Q0wfBFPGboCZ0/img.png&quot; data-alt=&quot;Encrypted Storage 한 줄 소개&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xbw1s/btryhO0I6Ln/5EYhbJSk2Q0wfBFPGboCZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxbw1s%2FbtryhO0I6Ln%2F5EYhbJSk2Q0wfBFPGboCZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1728&quot; height=&quot;804&quot; data-origin-width=&quot;1728&quot; data-origin-height=&quot;804&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Encrypted Storage 한 줄 소개&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;이를 보안한 것이 바로 Encrypted-storage이다. '비동기적 - '키-값'으로 저장'은 동일하며 실제로 구현 방식도 async-storage와 동일하다. 공식 문서의 한 줄 소개만 보아도 Async storage의 약점인 보안을 강화시키기 위한 라이브러리임을 알 수 있다. 따라서, 보안이 철저히 필요한 토큰들은 Async storage가 아닌 Encrypted storage에 넣어주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;정리하면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Redux&lt;/b&gt;(store) : 앱이 켜진 상태에서만 유지되는 데이터 저장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Async storage&lt;/b&gt; : 보안에 민감하지 않으며, 앱이 꺼져도 유지되는 데이터 저장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Encrypted storage&lt;/b&gt; : 보안이 필요하며, 앱이 꺼져도 유지되는 데이터 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코드 예제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 구현 방식에 대해 정리해보자. 참고로 설치 방법은 간단한 검색만으로도 가능하니 여기서는 생략한다. 공식 문서 링크 참고 ! (추가적으로 예전에 작성했던 &lt;a href=&quot;https://0biglife.tistory.com/entry/%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-token-%EC%A0%80%EC%9E%A51&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;async-storage&lt;/a&gt; 에 대한 글도 참고해볼 것..!)&lt;br /&gt;&lt;br /&gt;Async Storage :&amp;nbsp;&lt;a href=&quot;https://github.com/react-native-async-storage/async-storage&quot;&gt;https://github.com/react-native-async-storage/async-storage&lt;/a&gt;&lt;br /&gt;Encrypted Storage : &lt;a href=&quot;https://github.com/emeraldsanto/react-native-encrypted-storage&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/emeraldsanto/react-native-encrypted-storage&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648960759425&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - react-native-async-storage/async-storage: An asynchronous, persistent, key-value storage system for React Native.&quot; data-og-description=&quot;An asynchronous, persistent, key-value storage system for React Native. - GitHub - react-native-async-storage/async-storage: An asynchronous, persistent, key-value storage system for React Native.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/react-native-async-storage/async-storage&quot; data-og-url=&quot;https://github.com/react-native-async-storage/async-storage&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bGSJYj/hyNTkwJ0ud/RAVitks9hZDQdFS7SHTkok/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/react-native-async-storage/async-storage&quot; data-source-url=&quot;https://github.com/react-native-async-storage/async-storage&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bGSJYj/hyNTkwJ0ud/RAVitks9hZDQdFS7SHTkok/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - react-native-async-storage/async-storage: An asynchronous, persistent, key-value storage system for React Native.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An asynchronous, persistent, key-value storage system for React Native. - GitHub - react-native-async-storage/async-storage: An asynchronous, persistent, key-value storage system for React Native.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1648960758597&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - emeraldsanto/react-native-encrypted-storage: React Native wrapper around EncryptedSharedPreferences and Keychain to pro&quot; data-og-description=&quot;React Native wrapper around EncryptedSharedPreferences and Keychain to provide a secure alternative to Async Storage. - GitHub - emeraldsanto/react-native-encrypted-storage: React Native wrapper ar...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/emeraldsanto/react-native-encrypted-storage&quot; data-og-url=&quot;https://github.com/emeraldsanto/react-native-encrypted-storage&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cZrcS6/hyNUoxubAI/BX9ovFEkxewzZUAcRCr8x0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/emeraldsanto/react-native-encrypted-storage&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/emeraldsanto/react-native-encrypted-storage&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cZrcS6/hyNUoxubAI/BX9ovFEkxewzZUAcRCr8x0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - emeraldsanto/react-native-encrypted-storage: React Native wrapper around EncryptedSharedPreferences and Keychain to pro&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;React Native wrapper around EncryptedSharedPreferences and Keychain to provide a secure alternative to Async Storage. - GitHub - emeraldsanto/react-native-encrypted-storage: React Native wrapper ar...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;필자 기준 스토리지에 저장(&lt;b&gt;setItem&lt;/b&gt;), 불러오기(&lt;b&gt;getItem&lt;/b&gt;), 삭제(&lt;b&gt;removeItem&lt;/b&gt;) 정도 알거나 아니면 추가적으로 필요한 메서드는 문서를 찾아서 쓰면 된다. 라이브러리에서 AsyncStorage를 가져와서 내부 함수들을 불러오는 방식으로 구현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648963499241&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//스토리지에 저장하기 - setItem

const storeData = async (value) =&amp;gt; {
  try {
    const jsonValue = JSON.stringify(value) //문자열로 변환하여 넣어주기 위함
    await AsyncStorage.setItem('key', jsonValue)
  } catch (e) {
    // saving error
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;반드시 기억해야할 점은, 키-값 형태로 보관됨에 있어 저장되는 값은 &lt;b&gt;무조건 문자열로 취급&lt;/b&gt;되어야한다. 따라서, 저장할 때에는 &lt;b&gt;JSON.stringfy()&lt;/b&gt; 를 써주고, 가져올 때에는&lt;b&gt; JSON.parse()&lt;/b&gt; 를 사용해서 구현해야 한다. 비동기 처리를 위하여 async/await과 에러 핸들링을 위해 try-catch 문을 써준다. key 값이 공백 또는 null 일 때에는 error를 throw 해주고, value 가 존재하지 않는다면 null을 반환하도록 한다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648963792640&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//스토리지에서 값 불러오기 - getItem

const getData = async () =&amp;gt; {
  try {
    const jsonValue = await AsyncStorage.getItem('key')
    return jsonValue != null ? JSON.parse(jsonValue) : null;
  } catch(e) {
    // error reading value
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Encrypted-storage 역시 동일하다. AsyncStorage 대신 EncryptedStorage만 넣어주면 된다 !&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 로그인할 때에 반환되는 토큰들을 스토리지에 넣어주는 코드이다. 동일하게 비동기를 위한 async/await 과 에러 핸들링을 위한 try-catch문과 함께 짰으며, 이 경우 refreshToken을 보안을 위하여 Encrypted storage에 setItem 함수를 사용하여 넣어주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648965059096&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import EncryptedStorage from 'react-native-encrypted-storage';

const onSubmit = useCallback(async () =&amp;gt; {
    if (loading) {
      return;
    }
    try {
      setLoading(true);
      const response = await axios.post(`${Config.API_URL}/login`, {
        email,
        password,
      });
      dispatch(
        userSlice.actions.setUser({
          name: response.data.name,
          email: response.data.email,
          accessToken: response.data.accessToken,
        }),
      );
      await EncryptedStorage.setItem(
        'refreshToken',
        response.data.refreshToken,
      );
    } catch (error) {
      const errorResponse = (error as AxiosError).response;
      if (errorResponse) {
        Alert.alert(errorResponse.data.message);
      }
    } finally {
      setLoading(false);
    }
  }, [dispatch, email, loading, password]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하단 코드는 스토리지에서 토큰을 지우기 위하여 removeItem 함수를 사용한 예제이다. 로그아웃한 사용자는 저장공간에 토큰이 존재하면 안되기 때문에 제거해주는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648965254296&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  import EncryptedStorage from 'react-native-encrypted-storage';

  const onLogout = useCallback(async () =&amp;gt; {
    try {
      await axios.post(
        `${Config.API_URL}/logout`,
        {},
        {
          headers: {
            authorization: `Bearer ${accessToken}`,
          },
        },
      );
      Alert.alert('알림', '로그아웃 되었습니다.');
      dispatch(
        userSlice.actions.setUser({
          name: '',
          email: '',
          accessToken: '',
        }),
      );
      await EncryptedStorage.removeItem('refreshToken');
    } catch (error) {
      const errorResponse = (error as AxiosError).response;
      console.error(errorResponse);
    }
  }, [accessToken, dispatch]);&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frontend/상태관리(Redux, Storage, ContextAPI)</category>
      <category>0biglife</category>
      <category>async storage</category>
      <category>encrypted storage</category>
      <category>react native</category>
      <category>react native async storage</category>
      <category>react native encrypted storage</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/38</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-Async-Storage-Encrypted-Storage-%ED%95%84%EC%9A%94%EC%84%B1%EA%B3%BC-%EC%82%AC%EC%9A%A9%EB%B2%95#entry38comment</comments>
      <pubDate>Sun, 3 Apr 2022 13:43:25 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] Redux-toolkit 적용</title>
      <link>https://0biglife.tistory.com/entry/React-Native-Redux-toolkit-%EC%A0%81%EC%9A%A9</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVrRHc/btryb2eQgyO/QRqPImkw8bDWCbWeMppYwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVrRHc/btryb2eQgyO/QRqPImkw8bDWCbWeMppYwk/img.png&quot; data-alt=&quot;리덕스툴킷 정리 화이팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVrRHc/btryb2eQgyO/QRqPImkw8bDWCbWeMppYwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVrRHc%2Fbtryb2eQgyO%2FQRqPImkw8bDWCbWeMppYwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;690&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;리덕스툴킷 정리 화이팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Redux는 React Native 공부를 하면서 채용조건을 찾아볼 때마다 자주 보였던 녀석이다. 하지만 적용해야 할 필요성을 느끼지 못했기에 여태 구현을 해오다가 상태 관리를 조금 더 효율적으로 관리할 필요가 생기면서 찾아보게 되었다. 처음엔 꽤 난잡한데 이해하기 위해서는 큰 그림을 볼 필요가 있었으며, 손 코딩을 하는 것이 큰 도움이 되었다. 그럼 이번 게시글에서는 redux-toolkit을 어떻게 적용하였는지에 대해 간략히 적어보려고 한다.&lt;br /&gt;&amp;nbsp;(참고로 이 게시글에서는 Redux가 아닌 Redux-toolkit에 대해 다룰 것이기 때문에 Redux에 대한 기본 지식이 습득된 상태로 볼 것을 추천한다 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공홈 링크는 다음과 같고, 설치 방법은 생략한다.&lt;br /&gt;&lt;a href=&quot;https://redux-toolkit.js.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://redux-toolkit.js.org/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648889473644&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Redux Toolkit | Redux Toolkit&quot; data-og-description=&quot;The official, opinionated, batteries-included toolset for efficient Redux development&quot; data-og-host=&quot;redux-toolkit.js.org&quot; data-og-source-url=&quot;https://redux-toolkit.js.org/&quot; data-og-url=&quot;https://redux-toolkit.js.org/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ORAMi/hyNUB4e5Wh/9zFwLIBaOk0AoYVxjNRam1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/7kAr1/hyNTkpyfpd/Po0Rw0Kdlx1mBL7tydnIzK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://redux-toolkit.js.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://redux-toolkit.js.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ORAMi/hyNUB4e5Wh/9zFwLIBaOk0AoYVxjNRam1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/7kAr1/hyNTkpyfpd/Po0Rw0Kdlx1mBL7tydnIzK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Redux Toolkit | Redux Toolkit&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The official, opinionated, batteries-included toolset for efficient Redux development&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;redux-toolkit.js.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;1698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgK2wQ/btryg4u4cZs/vI9Ksdak43JhZbi2G03Cnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgK2wQ/btryg4u4cZs/vI9Ksdak43JhZbi2G03Cnk/img.png&quot; data-alt=&quot;나름? 보기 쉽게 정리,,&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgK2wQ/btryg4u4cZs/vI9Ksdak43JhZbi2G03Cnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgK2wQ%2Fbtryg4u4cZs%2FvI9Ksdak43JhZbi2G03Cnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;1698&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;1698&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;나름? 보기 쉽게 정리,,&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EY8bS/btryd8S32UM/5xUxdkqVZFfcBVdKPGv9R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EY8bS/btryd8S32UM/5xUxdkqVZFfcBVdKPGv9R1/img.png&quot; data-alt=&quot;디렉토리 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EY8bS/btryd8S32UM/5xUxdkqVZFfcBVdKPGv9R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEY8bS%2Fbtryd8S32UM%2F5xUxdkqVZFfcBVdKPGv9R1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;277&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디렉토리 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;큰 그림은 이러하다 !&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱이 Redux Store에 접근하기 위하여 &lt;b&gt;Provider&lt;/b&gt;로 전체를 감싸준다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;store&lt;/b&gt;안에 모든 reducer를 합쳐주는 &lt;b&gt;RootReducer&lt;/b&gt;와 추가적인 미들웨어를 넣어준다.&lt;/li&gt;
&lt;li&gt;RootReducer에서 필요한 &lt;b&gt;reducer&lt;/b&gt;에 대한 처리를 해준다.&lt;/li&gt;
&lt;li&gt;각각의 reducer들과 초기값, 이름이 선언될 &lt;b&gt;Slice&lt;/b&gt;를 선언해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실상 이게 다인데.. 거꾸로 다뤄보면 이해가 쉬울 것 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태 관리를 위한 &lt;b&gt;Slice&lt;/b&gt;와 내부 리듀서들 세팅하기&lt;/li&gt;
&lt;li&gt;그렇게 설정된 Slice들을 &lt;b&gt;RootReducer&lt;/b&gt;에서 모아주기&lt;/li&gt;
&lt;li&gt;RootReducer와 미들웨어를 &lt;b&gt;store&lt;/b&gt;에 넣어주기&lt;/li&gt;
&lt;li&gt;취합된 store를 앱과의 연결을 위해 &lt;b&gt;Provider&lt;/b&gt;로 감싸주고 내부 인자로 넣어주기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Provider 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;큰 맥락은 사실상 Redux와 동일하다. Redux-toolkit은 다소 번거로운 부분들을 좀 더 쉽게 다룰 수 있게 개선된 느낌이다. 이제 코드를 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1648890740135&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import store from './app/redux/store/index';
import {Provider} from 'react-redux';

const appRedux = () =&amp;gt; (
  &amp;lt;Provider store={store}&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/Provider&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 방식은 'react-navigation' 의 NavigationContainer, 'react-query'의 QueryClientProvider,&amp;nbsp; styled-component의 ThemeProvider에서 보던 방식이라 이제 제법 익숙하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Store 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;reducer와 middleware를 합쳐주기 위해 configureStore 내부에 넣어준다. reducer는 Slicer에 선언된 모든 reducer들을 모아주는 취합해주는 rootReducer를 넣어주고, UI와 네트워크를 보다 쉽게 확인하기 위하여 redux-flipper를 미들웨어로 넣어주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648890856163&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {configureStore} from '@reduxjs/toolkit';
import {useDispatch} from 'react-redux';
import rootReducer from './reducers';

const store = configureStore({
  reducer: rootReducer,
  middleware: getDefaultMiddleware =&amp;gt; {
    if (__DEV__) {
      const createDebugger = require('redux-flipper').default;
      return getDefaultMiddleware().concat(createDebugger());
    }
    return getDefaultMiddleware();
  },
});
export default store;

//타입스크립트에서 쓰기 위한 wrapper
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () =&amp;gt; useDispatch&amp;lt;AppDispatch&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;store에 있는 state를 조회하기 위해서는 useSelector를, 갱신을 위해서는 useDispatch를 사용할 것이다. useDispathc의 경우에는 store.dispatch의 타입형을 typeof로 받아서 useDispatch에 넣어준 상태로 useAppDispatch를 export 하여 추후에 사용할 계획이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RootReducer 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;reducer들을 한 데 모아주기 위해서는 combineReducers 함수를 사용한다. Slice는 뒤에 나오게 될 userSlice, orderSlice 내부의 reducer를 넣어주었다. 이로써 나중에 특정 Slice로의 접근에 대한 분기점이 세팅된 셈이다.&lt;/p&gt;
&lt;pre id=&quot;code_1648891356442&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {combineReducers} from 'redux';
import orderSlice from '../slices/order';
import userSlice from '../slices/user';

const rootReducer = combineReducers({
  user: userSlice.reducer,
  order: orderSlice.reducer,
});

//RootReducer의 반환값 타입형은 RootState type alias로 지정 가능
export type RootState = ReturnType&amp;lt;typeof rootReducer&amp;gt;;
//ReturnType은 특정 함수의 반환값 타입을 가져오는 유틸 타입형
//이 유팁 타입이 Generic에 &amp;lt;typeof 함수명&amp;gt;을 쓰면 해당 함수의 반환값을 조회할 수 있다
//이렇게까지 반환값을 export 하는 이유는
//추후 useSelector를 사용할 때 이 타입을 참조해야하기 때문.

export default rootReducer;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;RootState는 추후 useSelector로 store 내부 상태를 조회할 때, rootReducer의 리던 타입형을 넣어주기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Slice 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;대망의 슬라이스. 형태는 아래 코드와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1648892596410&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {createSlice, PayloadAction} from '@reduxjs/toolkit';

const initialState = {
  name: '',
  email: '',
  accessToken: '',
};
const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setUser(state, action) {
      state.email = action.payload.email;
      state.name = action.payload.name;
      state.accessToken = action.payload.accessToken;
    },
    setName(state, action) {
      state.name = action.payload;
    },
    setEmail(state, action) {
      state.email = action.payload;
    },
    setAccessToken(state, action) {
      state.accessToken = action.payload;
    },
  },
  //extraReducer는 비동기 액션 생성시 필요
  // extraReducers: builder =&amp;gt; {},
});

export default userSlice;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;createSlice를 통하여 생성하며, 내부 인자로는 slice의 이름(문자열), 초기값, 상태 동작을 위한 reducers가 들어간다. reducers 내부에는 특정 동작을 가능하게 해줄 함수들이 들어가는데 이 함수들은 state와 action을 인자로 넣어주어 또다시 그 내부에서 추가 코드를 작성해준다. 이때, action은 payload를 통해 값에 접근하며 타입형이 필요할 시 &amp;lt;PayloadAction&amp;gt;을 넣어줄 수 있다!&lt;/p&gt;
&lt;pre id=&quot;code_1648892804524&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {createSlice, PayloadAction} from '@reduxjs/toolkit';

//
  ...
//

const orderSlice = createSlice({
  name: 'orders',
  initialState,
  reducers: {
    addOrder(state, action: PayloadAction&amp;lt;Order&amp;gt;) {
      state.orders.push(action.payload);
    }
});

export default orderSlice;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이런 방식 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;조회 및 갱신 (useSelector, useDispatch)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 언급하였듯이 조회 및 갱신은 useSelector와 useDispatch로 해준다. useDispatch는 타입을 넣어주고 useAppDispatch로 export 해주었었다. 아래 코드는 RootStack 코드로 로그인 여부에 따른 분기 처리와 MainTab과 AuthStack을 모아주는 역할의 코드이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;조회는 useSelector와 state를 통해 접근하고, 이때 state는 RootReducer에서 export 했던 타입형인 RootState을 넣어준다. 그리고 state를 통해 user와 같이 분기 처리해주었던 슬라이스로 접근(state.user.~)하여 상태를 조회할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648893512226&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {RootState} from '../redux/store/reducers';
import {useSelector} from 'react-redux';

const RootStack = () =&amp;gt; {
  const isLoggedIn = useSelector((state: RootState) =&amp;gt; !!state.user.email);

  return isLoggedIn ? (
    &amp;lt;Tab.Navigator&amp;gt;
      //
        ...
      //
    &amp;lt;/Tab.Navigator&amp;gt;
  ) : (
    &amp;lt;Stack.Navigator&amp;gt;
      //
        ...
      //
    &amp;lt;/Stack.Navigator&amp;gt;
  );
};

export default RootStack;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;액션을 통한 갱신은 useDispatch로 할 수 있다고 했다. 원하는 위치에(필자의 경우, 토큰을 refresh 하면서 유저 정보를 다시 서버로 요청하는 코드) 새롭게 선언된 dispatch를 넣어주고 내부에는 원하는 슬라이스와 actions를 통해 원하는 동작 함수를 찾아서 짜면 된다.(Slice.actions.function())&lt;/p&gt;
&lt;pre id=&quot;code_1648893747459&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {useAppDispatch} from '../redux/store';
//Slices
import userSlice from '../redux/slices/user';
import orderSlice from '../redux/slices/order';

const RootStack = () =&amp;gt; {
  const dispatch = useAppDispatch();

  useEffect(() =&amp;gt; {
    const getTokenAndRefresh = async () =&amp;gt; {
      try {
        // ... 추가 logic
        dispatch(
          userSlice.actions.setUser({
            name: response.data.data.name,
            email: response.data.data.email,
            accessToken: response.data.data.accessToken,
          }),
        );
      } catch (error) {
        // ... 에러 핸들링
      } finally {
        // ... 추가 코드 작성
      }
    };
    getTokenAndRefresh();
  }, [dispatch]);
  // ...
};

export default RootStack;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648893239488&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, {useEffect} from 'react';
//Reduc Type
import {RootState} from '../redux/store/reducers';
// ...
import {useSelector} from 'react-redux';
import {useAppDispatch} from '../redux/store';
//Slices
import userSlice from '../redux/slices/user';
import orderSlice from '../redux/slices/order';

const Tab = createBottomTabNavigator();
const Stack = createNativeStackNavigator();

const RootStack = () =&amp;gt; {
  const dispatch = useAppDispatch();
  //!!연산자 : undefined checking : null이나 undefined 면 false 를 반환 !
  const isLoggedIn = useSelector((state: RootState) =&amp;gt; !!state.user.email);

  //앱 실행 시 토큰 존재하면 로그인 활성화!
  useEffect(() =&amp;gt; {
    const getTokenAndRefresh = async () =&amp;gt; {
      try {
        const token = await EncryptedStorage.getItem('refreshToken');
        if (!token) {
          return; //없으면 탈출
        }
        //있으면 아래 경로로 토큰 쏴주고, 받아온 값을 리덕스로 보관
        const response = await axios.post(
          `${Config.API_URL}/refreshToken`,
          {},
          {
            headers: {
              authorization: `Bearer ${token}`,
            },
          },
        );
        dispatch(
          userSlice.actions.setUser({
            name: response.data.data.name,
            email: response.data.data.email,
            accessToken: response.data.data.accessToken,
          }),
        );
      } catch (error) {
        console.log('RootStack - useEffect - Error : ', error);
        if ((error as AxiosError).response?.data.code === 'expired') {
          Alert.alert('알림', '다시 로그인 해주세요.');
        }
      } finally {
        //로딩 화면으로 splash-screen 추가 예정
      }
    };
    getTokenAndRefresh();
  }, [dispatch]);

  return isLoggedIn ? (
    &amp;lt;Tab.Navigator&amp;gt;
      &amp;lt;Tab.Screen
        name=&quot;Home&quot;
        component={Home}
      /&amp;gt;
      &amp;lt;Tab.Screen
        name=&quot;Going&quot;
        component={Going}
      /&amp;gt;
      &amp;lt;Tab.Screen
        name=&quot;Profile&quot;
        component={Profile}
      /&amp;gt;
    &amp;lt;/Tab.Navigator&amp;gt;
  ) : (
    &amp;lt;Stack.Navigator
      screenOptions={{
        headerTitle: '',
        headerTintColor: 'black',
        headerShadowVisible: false,
      }}&amp;gt;
      &amp;lt;Stack.Screen name=&quot;LogIn&quot; component={LogIn} /&amp;gt;
      &amp;lt;Stack.Screen name=&quot;SignUp&quot; component={SignUp} /&amp;gt;
    &amp;lt;/Stack.Navigator&amp;gt;
  );
};

export default RootStack;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frontend/상태관리(Redux, Storage, ContextAPI)</category>
      <category>0biglife</category>
      <category>react native</category>
      <category>React Native redux-toolkit</category>
      <category>Redux-toolkit</category>
      <category>useDispatch</category>
      <category>useSelector</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/37</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-Redux-toolkit-%EC%A0%81%EC%9A%A9#entry37comment</comments>
      <pubDate>Sat, 2 Apr 2022 19:05:21 +0900</pubDate>
    </item>
    <item>
      <title>[React Native]  서버 요청 정리(1) (비동기, Axios, Config)</title>
      <link>https://0biglife.tistory.com/entry/React-Native-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EC%A0%95%EB%A6%AC-Axios-Config</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTLAMv/btrxJqE3w9j/1s9rWVT430d9qkf8gegzKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTLAMv/btrxJqE3w9j/1s9rWVT430d9qkf8gegzKk/img.png&quot; data-alt=&quot;서버 요청 정리 화이팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTLAMv/btrxJqE3w9j/1s9rWVT430d9qkf8gegzKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTLAMv%2FbtrxJqE3w9j%2F1s9rWVT430d9qkf8gegzKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;917&quot; height=&quot;516&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;서버 요청 정리 화이팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;여태 공부했던 서버 요청에 대한 모든 것들을 정리하려고 한다. 일단 서버와의 통신을 위해 axios를 쭉 사용해왔는데 &lt;b&gt;가장 먼저 왜 'axios'인지&lt;/b&gt;부터 시작해서 .get/.post에 대한 &lt;b&gt;구현 방법&lt;/b&gt;을 다루고, 두 번째 글에서는 모듈화를 위한 react-query, custom Hook 작성까지 다뤄볼 예정이다. (axios.create로 인스턴스를 만들어 간단한 모듈화 진행은 &lt;a href=&quot;https://0biglife.tistory.com/entry/Axios%EB%A1%9C-%ED%99%88%EB%B7%B0-Settingget?category=1031770&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 링크&lt;/a&gt;를 타고 가면 볼 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 앱 개발자에게 있어 굉장히 중요한 주제들 중 하나라고 생각한 만큼 이번 글은 길어질 것 같으니 공부하시는 분들에게 필요한 개념만 쇽-쇽 찾아가길 바란다. // if(도움이 된다면...)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 Axios 인가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;서버와의 통신을 위해 어떤 기술을 써야하는가가 독학 초반에서의 가장 큰 궁금증이었다. 처음엔 Fetch로 구현을 해보다가 Axios로 갈아타게 된 이유는 다음과 같다. 기존에 swift로 iOS 앱 개발을 할 때에는 Alamofire, Moya를 사용하여 모듈화 방법까지 공부했었는데 그 형태가 가장 유사한 것이 axios라고 판단하여 선택한 것도 크다. 또한, Axios의 자료들이 풍부하다는 것에서 그 망설임은 더욱 줄어들었다. 무턱대고 axios를 공부하면서 react-native와 친해졌으며, 모듈화까지 공부해본 현재까지도 앞으로 문제만 없다면 쭉 Axios를 활용하지 않을까 싶다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;공식 문서에 따르면 Axios의 정의는 다음과 같다. Axios는 &lt;b&gt;&quot;Promise based HTTP client for browser and node.js&quot;&lt;/b&gt;이다. 즉, Axios는 node.js와 브라우저를 위한 HTTP 통신 라이브러리인데, 가장 큰 장점은 비동기 HTTP 통신을 가능하게 해주며, 그에 대한 리턴 값을 promise 객체로 반환해주기 때문에 요청에 대한 응답(response)을 다루기 쉽다. 리턴 값은&amp;nbsp;json 타입으로 받는다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;이 외에도 Axios와 Fetch 간의 비교에 대한 부연설명은 하단 링크를 참고하길 바란다.&amp;nbsp;&lt;br /&gt;&lt;b&gt;&amp;nbsp;Axios vs Fetch&lt;/b&gt; : &lt;a href=&quot;https://www.geeksforgeeks.org/difference-between-fetch-and-axios-js-for-making-http-requests/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.geeksforgeeks.org/difference-between-fetch-and-axios-js-for-making-http-requests/&lt;/a&gt;​&lt;/p&gt;
&lt;figure id=&quot;og_1648453269908&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Difference between Fetch and Axios.js for making http requests - GeeksforGeeks&quot; data-og-description=&quot;A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.&quot; data-og-host=&quot;www.geeksforgeeks.org&quot; data-og-source-url=&quot;https://www.geeksforgeeks.org/difference-between-fetch-and-axios-js-for-making-http-requests/&quot; data-og-url=&quot;https://www.geeksforgeeks.org/difference-between-fetch-and-axios-js-for-making-http-requests/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0QUdP/hyNQ2HPeAD/kmzqOBcdKdxV0hRA42nGv1/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/difference-between-fetch-and-axios-js-for-making-http-requests/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.geeksforgeeks.org/difference-between-fetch-and-axios-js-for-making-http-requests/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0QUdP/hyNQ2HPeAD/kmzqOBcdKdxV0hRA42nGv1/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Difference between Fetch and Axios.js for making http requests - GeeksforGeeks&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.geeksforgeeks.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;Axios 사용을 위한 개념&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;Axios 요청을 보내는 방식은 이해만 한다면 굉장히 구현이 간단하고, 직접 몇 번만 구현하더라도 금방 손에 익게 된다. 하지만 '00을 할 때 이렇게 해야한다' 라고 할 때 '이러이러하기 때문에 사용해야 한다'라는 설명이 생략되면 자꾸 '그래서 이걸 왜 써야 하지?' 생각이 들면서 기능 구현이 완성되어도 찝찝하기만 하다. 대표적으로는 &lt;b&gt;비동기 통신이 왜 필요하며&lt;/b&gt;, &lt;b&gt;어떤 게 비동기 통신인지&lt;/b&gt; 공부하며 이해했다. 필요성이 있기 때문에 이 기능이 효율적임을 판단하는 것인데 그 과정이 해소되어야 한다는 의미이다. &lt;/span&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;(사실 이 부분 정리가 필요하여 이번 글을 작성하였다.)&lt;/span&gt; 비동기 통신 다음으로 궁금했던 것은 개념과 연계된&lt;b&gt; async/await&lt;/b&gt;, 그리고 구현할 때에는 &lt;b&gt;왜 try-catch문을 써야하는가&lt;/b&gt;이다. 이러한 의문점들을 다음 순서대로 간단히 정리해보겠다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;비동기, 동기 모두 통신 관련 용어이며, 동기는 직관적인 작업 과정으로 하나의 작업(task)이 마치고 결과가 나온 뒤에 그 다음 작업을 실행을 하기 때문에 순차적으로 실행되며 그렇기 때문에 다른 작업들은 blocking 된다. 반면, &lt;b&gt;비동기 작업&lt;/b&gt;은 작업이 마치지 않아도 그 다음 작업이 시작되기 때문에 동시에 여러 요청들을 수행할 수 있고, 기다리는 과정에서도 다른 함수를 호출할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJi4ck/btrxAbCUZfu/pULrdtpMhzZxr39lz1Lwsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJi4ck/btrxAbCUZfu/pULrdtpMhzZxr39lz1Lwsk/img.png&quot; data-alt=&quot;P..Promise 객체..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJi4ck/btrxAbCUZfu/pULrdtpMhzZxr39lz1Lwsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJi4ck%2FbtrxAbCUZfu%2FpULrdtpMhzZxr39lz1Lwsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;416&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;P..Promise 객체..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;이러한 비동기 작업이 언제 끝나는지 알려주는 객체가 바로 &lt;b&gt;Promise 객체&lt;/b&gt;이다. Promise 객체는 대기, 이행, 거부 세 가지 상태를 가지며 이는 각각 작업 완료 전, 작업 완료 후, 에러 발생으로 나뉜다.(그래서&lt;span style=&quot;color: #9d9d9d;&quot;&gt; try-catch를 쓰는 것인가?)&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;이 Promise 객체를, 즉 비동기를 더욱 쉽게 사용할 수 있도록 해주는 문법(ES2017, ES8)이 &lt;b&gt;async/await이다.!&lt;/b&gt; 이 문법은 간단히 말하면 함수 앞부분에 async 키워드를 넣어주고, 함수 내부에서는 Promise 앞부분에 await를 써줌으로써 그에 대한 리턴 값으로 Promise를 반환한다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648456787786&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function TestFunction() {
    try{
      const response = await fetch('...url...');
        // task..
    } catch (error) {
        // handle error
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; 이렇게 훑어보면 &lt;b&gt;try-catch&lt;/b&gt;문을 쓰는 것은 필수적인 것처럼 보인다. try-catch 문의 가장 큰 필요성은 &lt;b&gt;'에러 핸들링'&lt;/b&gt;에 있다. 요청에 대한 성공은 try안에서 비동기 작업을 위한 await를 언급해주면서 사용할 수 있고, 예외 상황에 대한 처리를 catch 안에서 잡아주어 예상된 에러/예상되지 못한 에러 처리를 할 수 있다.&amp;nbsp;&lt;br /&gt;&amp;nbsp; 한 문장으로 정리하면 ! 서버와의 통신을 위해서는 &lt;u&gt;비동기 작업&lt;/u&gt;이 필요하고, 이를 위해 &lt;u&gt;async/await 문법을 사용&lt;/u&gt;하고 &lt;u&gt;에러 핸들링을 위하여 try-catch문&lt;/u&gt;을 작성한다 !&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Axios 적용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;자! 이제 기다리고 기다리던 Axios 구현 파트이다. 우선 코드 작성 전, 설치 방법인데 이는 간단하기 때문에 링크로만 남기겠다.&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://github.com/axios/axios&quot;&gt;https://github.com/axios/axios&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648457730479&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - axios/axios: Promise based HTTP client for the browser and node.js&quot; data-og-description=&quot;Promise based HTTP client for the browser and node.js - GitHub - axios/axios: Promise based HTTP client for the browser and node.js&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/axios/axios&quot; data-og-url=&quot;https://github.com/axios/axios&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lnzCo/hyNPLVdgRA/fiAXXBBPRy6ueEy0m5dK41/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/axios/axios&quot; data-source-url=&quot;https://github.com/axios/axios&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lnzCo/hyNPLVdgRA/fiAXXBBPRy6ueEy0m5dK41/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - axios/axios: Promise based HTTP client for the browser and node.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Promise based HTTP client for the browser and node.js - GitHub - axios/axios: Promise based HTTP client for the browser and node.js&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 방식은 현재 github에 작성해둔 코드를 가져왔다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;.get 방식&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1648459925558&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
    const getData = async () =&amp;gt; {
      const response = await axios.get&amp;lt;{data: string}&amp;gt;(
        `${url}/get`,
        {
          headers: {
            authorization: `Bearer ${accessToken}`,
          },
        },
      );
    };
    getData();
  }, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;해당 방식은 get요청을 보낸 형태이며, 앞서 설명한 것처럼 비동기 통신을 위해 getData() 함수에 async를 명시해주고 함수 내부에는 await를 써줌으로써 Promise 객체를 반환하도록 해주었다. 선언된 response 변수는 axios 라이브러리의 get 방식을 채택하고 타입스크립트를 사용하기 때문에 받아오는 타입형도 generic을 넣어주었다. get method는 첫 번째 인자로 요청하는 path를 넣어주며, 두 번째 인자로는 header를 같이 넣어 요청할 수 있다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;첫 번째 인자 : url 변수 + 백엔드에서 넘겨줄 api 주소&lt;br /&gt;&amp;nbsp;두 번째 인자 : redux store에 넣어둔 accessToken을 헤더에 포함시켜 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;추가적으로, useEffect 는 async를 허용하지 않기 때문에 내부에 선언과 호출을 동시에 해줘야 한다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;이로써, 위 코드는 해당 코드가 들어간 화면이 나올 시 서버로부터 특정 데이터를 가져오게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;.post 방식&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;다음은 입력받은 데이터를 서버로 보내주는 post 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1648460993337&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const LoginTapped = useCallback(async () =&amp;gt; {
    if (loading) {
      return;
    }
    // 입력된 텍스트 검토하는 로직..
    try {
      setLoading(true);
      const response = await axios.post(`${Config.API_URL}/login`, {
        email,
        password,
      });
      //가져온 데이터 store에 넣어주는 코드..
      //가져온 토큰은 보안을 위해 Encrypted-Storage에 넣어주는 코드..
    } catch (error) {
      const errorResponse = (error as AxiosError).response;
      if (errorResponse) {
        Alert.alert(errorResponse.data.message);
      }
    } finally {
      setLoading(false);
    }
  }, [email, loading, password]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;LoginTapped 함수는 로그인 화면에서 사용자가 이메일, 비밀번호를 입력하고 나서 그 정보를 서버로 보내기 위하여 버튼을 누를 때 작동하는 함수다. get에 대해 작성한 코드처럼 axios와 관련된 로직만 봐주길 바란다. get 방식과 동일하고 path를 넣어주고 두 번째 인자로는 전송해야 하는 사용자의 정보를 파라미터로 넣었다. 이 경우에는, 토큰을 보낼 필요가 없다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;서버 요청에 대한 에러 핸들링을 위해 try-catch-finally 문을 사용했고, 받아온 error는 axios에서 지원해주는 AxiosError 형으로 바꿔서 if문과 Alert함수로 화면에 띄워주었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;추가적으로, 중요한 점을 배웠는데 useState으로 [loading, setLoading]을 제어하는 것이 얼마나 중요한지 배웠다. 요청에 대한 잠시 대기하는 순간을 loading 변수로 잡아주고 이 경우에는 로딩 화면 또는 스플래쉬 스크린을 뛰워줄 것이다!&lt;/p&gt;
&lt;pre id=&quot;code_1648461636242&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;LoginButton
  style={{
    backgroundColor: isActive ? 'gray' : 'lightgray',
        }}
  disabled={!isActive || loading}
  onPress={onSubmit}&amp;gt;
    &amp;lt;ButtonText&amp;gt;Login&amp;lt;/ButtonText&amp;gt;
&amp;lt;/LoginButton&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그리고 사용자가 버튼을 연타할 시 로그인이 동시다발적으로 발생하는 것을 고려하여 disabled에 loading도 넣어줘야한다.!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;react-native-config&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 라이브러리는 프로젝트가 여러 환경에서 빌드 또는 런될 때를 대비하여 필요한 라이브러리이다. 개발 환경과 운영(production) 환경을 포함한 다양한 환경으로 분리하여 작성될 수 있으나, 필자는 간단한 연습을 위해 .env 파일만 작성하여 사용했다. 설치 및 사용 방법은 공식 링크에 들어가면 쉽게 사용해볼 수 있다.&amp;nbsp;&lt;br /&gt;link : &lt;a href=&quot;https://github.com/luggit/react-native-config&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/luggit/react-native-config&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1648461932256&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - luggit/react-native-config: Bring some 12 factor love to your mobile apps!&quot; data-og-description=&quot;Bring some 12 factor love to your mobile apps! Contribute to luggit/react-native-config development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/luggit/react-native-config&quot; data-og-url=&quot;https://github.com/luggit/react-native-config&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bo7JnW/hyNQ2OFik7/QgDmiPv3K5A6Qvf61OzTKK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/luggit/react-native-config&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/luggit/react-native-config&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bo7JnW/hyNQ2OFik7/QgDmiPv3K5A6Qvf61OzTKK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - luggit/react-native-config: Bring some 12 factor love to your mobile apps!&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Bring some 12 factor love to your mobile apps! Contribute to luggit/react-native-config development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DA3m8/btrxHXcsMFU/vg7inozhNyhkFuKKISM4j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DA3m8/btrxHXcsMFU/vg7inozhNyhkFuKKISM4j0/img.png&quot; data-alt=&quot;.env file&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DA3m8/btrxHXcsMFU/vg7inozhNyhkFuKKISM4j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDA3m8%2FbtrxHXcsMFU%2Fvg7inozhNyhkFuKKISM4j0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1702&quot; height=&quot;1248&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1248&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;.env file&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에는 라이브러리로부터 Config를 불러와서 axios 요청에서 path 설정을 위한 변수 선언에 넣어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1648462100132&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Function = useCallback(async () =&amp;gt; {
    try {
      await axios.post(`${Config.API_URL}/complete`, {
        headers: {
          authorization: `Bearer ${accessToken}`,
        },
      });
      //추가 로직..
    } catch (error) {
      const errorResponse = (error as AxiosError).response;
      if (errorResponse) {
        Alert.alert('알림', errorResponse.data.message);
      }
    }
  }, [// ... //]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이상으로 필자가 Axios를 사용함에 있어서 궁금했던 점들과 구현한 코드를 간단히 정리해보았습니다. 두 번째 글에서는 이러한 요청들이 많아졌을 때 어떻게 하면 좀 더 효율적으로 처리할지에 대해 고민한 것들을 정리해보려고 합니다. 다소 부족한 부분과 심지어 틀린 부분도 있을 것이라 우려가 되는데 이러한 부분은 댓글로 남겨주면 너무너무 도움이 될 것 같습니다 : )&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/Axios</category>
      <category>0biglife</category>
      <category>axios</category>
      <category>react native</category>
      <category>React Native Axios</category>
      <category>react-native-config</category>
      <category>비동기통신</category>
      <category>서버 요청</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/35</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EC%A0%95%EB%A6%AC-Axios-Config#entry35comment</comments>
      <pubDate>Mon, 28 Mar 2022 16:46:11 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] AsyncStorage를 활용한 로그인 여부 판별</title>
      <link>https://0biglife.tistory.com/entry/React-Native-AsyncStorage%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%AC%EB%B6%80-%ED%8C%90%EB%B3%84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;로그인을 성공한 상태에서 앱을 껐다 켰을 때 로그인 상태를 유지시켜주기 위한 코드를 짜다가 난관에 부딪혔다.&lt;br /&gt;엄청 간단해보이던 구현이 생각보다 길어졌으나 그 덕에 AsyncStorage에 대해 더 공부하게 되었다. (아니면 지름길을 내가 볼 줄 모르는 것이라던지..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 로그인/회원가입을 담당하는 AuthStack과 BottomTabNavigator로 이루어진 MainTab에 대한 분기점은 아래 코드와 같이 삼항연산자를 통하여 간단하게 구현해놓았던 상태이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKn6qf/btruXbcNh1h/I9db9169FMLrDnMZGXkvQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKn6qf/btruXbcNh1h/I9db9169FMLrDnMZGXkvQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKn6qf/btruXbcNh1h/I9db9169FMLrDnMZGXkvQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKn6qf%2FbtruXbcNh1h%2FI9db9169FMLrDnMZGXkvQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1504&quot; height=&quot;1016&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무튼 리액트네이티브서울 커뮤니티에 이번 일을 계기로 질문글을 하나 작성하게 되었다. 작성하는 김에 Redux를 활용하면 어떠한 이점을 취할 수 있는지 구체적인 솔루션을 물어보았다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1678&quot; data-origin-height=&quot;1202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj95Vd/btruQlnHDyq/bpzOH2lfN8vpamkNJuBlA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj95Vd/btruQlnHDyq/bpzOH2lfN8vpamkNJuBlA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj95Vd/btruQlnHDyq/bpzOH2lfN8vpamkNJuBlA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj95Vd%2FbtruQlnHDyq%2FbpzOH2lfN8vpamkNJuBlA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1678&quot; height=&quot;1202&quot; data-origin-width=&quot;1678&quot; data-origin-height=&quot;1202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;부족한 질문에 대한 답변을 두 분이나 달아주셔서 안도..&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;1338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nxdyf/btruPbFEjxJ/AsrHXWY7hJvPktysaaUir0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nxdyf/btruPbFEjxJ/AsrHXWY7hJvPktysaaUir0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nxdyf/btruPbFEjxJ/AsrHXWY7hJvPktysaaUir0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNxdyf%2FbtruPbFEjxJ%2FAsrHXWY7hJvPktysaaUir0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1640&quot; height=&quot;1338&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;1338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, redux store는 앱이 재시동되면 다시 초기화되는 것을 나는 모르고 있었다.. 따라서, 로그인 여부에 대한 판별을 store에서 하던 나를 반성하게 되었고, asyncStorage로 갈아타게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//구현중&lt;/p&gt;</description>
      <category>Error Record</category>
      <category>0biglife</category>
      <category>asyncstorage</category>
      <category>react native</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/34</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-AsyncStorage%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%AC%EB%B6%80-%ED%8C%90%EB%B3%84#entry34comment</comments>
      <pubDate>Wed, 2 Mar 2022 19:03:07 +0900</pubDate>
    </item>
    <item>
      <title>[Strapi] Bearer Token 보내도 '403 Forbidden' 에러 뜰 경우</title>
      <link>https://0biglife.tistory.com/entry/Strapi-Bearer-Token-%EB%B3%B4%EB%82%B4%EB%8F%84-403-Forbidden-%EC%97%90%EB%9F%AC-%EB%9C%B0-%EA%B2%BD%EC%9A%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;제목 그대로 토큰과 url path 등 모든게 정상적으로 설정이 되어있음에도 불구하고 403 에러가 난다.&lt;br /&gt;결론은 서버 내부 권한 문제인데 이는 strapi 페이지에서 Setting 항목을 통해 수정이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2872&quot; data-origin-height=&quot;1370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qlKv2/btruiSUaQ8m/cHaAsB1uFzOJRhfDI55UU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qlKv2/btruiSUaQ8m/cHaAsB1uFzOJRhfDI55UU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qlKv2/btruiSUaQ8m/cHaAsB1uFzOJRhfDI55UU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqlKv2%2FbtruiSUaQ8m%2FcHaAsB1uFzOJRhfDI55UU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2872&quot; height=&quot;1370&quot; data-origin-width=&quot;2872&quot; data-origin-height=&quot;1370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택플로우 참고-!&lt;/p&gt;
&lt;figure id=&quot;og_1645774450069&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Strapi API calling error: {&amp;quot;statusCode&amp;quot;:403,&amp;quot;error&amp;quot;:&amp;quot;Forbidden&amp;quot;,&amp;quot;message&amp;quot;:&amp;quot;Forbidden&amp;quot;}&quot; data-og-description=&quot;I am working with strapi and i am getting an error 403 Forbidden on calling an api e.g http://localhost:1337/data I've called all the APIs and the result is same 403 error I've tried it with post...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/53956118/strapi-api-calling-error-statuscode403-errorforbidden-messageforbi&quot; data-og-url=&quot;https://stackoverflow.com/questions/53956118/strapi-api-calling-error-statuscode403-errorforbidden-messageforbi&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxwS1E/hyNwY0zTUg/gBgbMkCwoxiT2UOelpQixK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/53956118/strapi-api-calling-error-statuscode403-errorforbidden-messageforbi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/53956118/strapi-api-calling-error-statuscode403-errorforbidden-messageforbi&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxwS1E/hyNwY0zTUg/gBgbMkCwoxiT2UOelpQixK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Strapi API calling error: {&quot;statusCode&quot;:403,&quot;error&quot;:&quot;Forbidden&quot;,&quot;message&quot;:&quot;Forbidden&quot;}&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I am working with strapi and i am getting an error 403 Forbidden on calling an api e.g http://localhost:1337/data I've called all the APIs and the result is same 403 error I've tried it with post...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공 !&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;1564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oKn0q/btrugQP0zms/2lVj9xbTZ0GHCpngoDJJK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oKn0q/btrugQP0zms/2lVj9xbTZ0GHCpngoDJJK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oKn0q/btrugQP0zms/2lVj9xbTZ0GHCpngoDJJK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoKn0q%2FbtrugQP0zms%2F2lVj9xbTZ0GHCpngoDJJK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2412&quot; height=&quot;1564&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;1564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Error Record</category>
      <category>0biglife</category>
      <category>403 forbidden</category>
      <category>strapi</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/33</guid>
      <comments>https://0biglife.tistory.com/entry/Strapi-Bearer-Token-%EB%B3%B4%EB%82%B4%EB%8F%84-403-Forbidden-%EC%97%90%EB%9F%AC-%EB%9C%B0-%EA%B2%BD%EC%9A%B0#entry33comment</comments>
      <pubDate>Fri, 25 Feb 2022 16:35:36 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] Context API with Hook</title>
      <link>https://0biglife.tistory.com/entry/React-Native-Context-API-with-Hook</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;useContext가 가지는 의미. 역할. 로 시작&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhQnJb/btruK6jxvE7/Gbkqnk7kqHXlv7AqJUHMN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhQnJb/btruK6jxvE7/Gbkqnk7kqHXlv7AqJUHMN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhQnJb/btruK6jxvE7/Gbkqnk7kqHXlv7AqJUHMN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhQnJb%2FbtruK6jxvE7%2FGbkqnk7kqHXlv7AqJUHMN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;960&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pks2974.medium.com/react-context-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC-9c35ce6617fc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://pks2974.medium.com/react-context-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC-9c35ce6617fc&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1645607948415&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;React Context 간단 정리&quot; data-og-description=&quot;React Context API 를 간단한 예제로 알아보자&quot; data-og-host=&quot;pks2974.medium.com&quot; data-og-source-url=&quot;https://pks2974.medium.com/react-context-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC-9c35ce6617fc&quot; data-og-url=&quot;https://pks2974.medium.com/react-context-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC-9c35ce6617fc&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://pks2974.medium.com/react-context-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC-9c35ce6617fc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pks2974.medium.com/react-context-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC-9c35ce6617fc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React Context 간단 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;React Context API 를 간단한 예제로 알아보자&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pks2974.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 정보를 위한 전역 상태 관리를 적용하려고 시작한 공부.. 아직 정리 중&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;1702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVUYHG/btrtWtM1nL0/yV9sMigbcYiBW6KRgfmII0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVUYHG/btrtWtM1nL0/yV9sMigbcYiBW6KRgfmII0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVUYHG/btrtWtM1nL0/yV9sMigbcYiBW6KRgfmII0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVUYHG%2FbtrtWtM1nL0%2FyV9sMigbcYiBW6KRgfmII0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1266&quot; height=&quot;1702&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;1702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;context 만들었으면 앱 전체를 Provider로 감싸주기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;1650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEYbU5/btrtYehyihJ/1ScCb3Tcb6lcGXi42p48y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEYbU5/btrtYehyihJ/1ScCb3Tcb6lcGXi42p48y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEYbU5/btrtYehyihJ/1ScCb3Tcb6lcGXi42p48y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEYbU5%2FbtrtYehyihJ%2F1ScCb3Tcb6lcGXi42p48y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1188&quot; height=&quot;1650&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;1650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;context 실제 사용. 사용한 곳은 가입을 담당하는 Hook 에 적용&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;1092&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1W5AA/btrtTMzBzbk/pkXJiOKqvf0OXUwTK7wVXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1W5AA/btrtTMzBzbk/pkXJiOKqvf0OXUwTK7wVXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1W5AA/btrtTMzBzbk/pkXJiOKqvf0OXUwTK7wVXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1W5AA%2FbtrtTMzBzbk%2FpkXJiOKqvf0OXUwTK7wVXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1306&quot; height=&quot;1092&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;1092&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가입시 발생한 토큰 저장, 로그아웃시 토큰 삭제를 담당하는 함수&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;1096&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lFcCQ/btrtTMl0OOu/MuFkkseSPqjig6kYtQ4PL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lFcCQ/btrtTMl0OOu/MuFkkseSPqjig6kYtQ4PL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lFcCQ/btrtTMl0OOu/MuFkkseSPqjig6kYtQ4PL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlFcCQ%2FbtrtTMl0OOu%2FMuFkkseSPqjig6kYtQ4PL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1316&quot; height=&quot;1096&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;1096&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/상태관리(Redux, Storage, ContextAPI)</category>
      <category>0biglife</category>
      <category>createContext</category>
      <category>react native</category>
      <category>useContext</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/32</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-Context-API-with-Hook#entry32comment</comments>
      <pubDate>Mon, 21 Feb 2022 16:56:09 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] Type Checking for Navigator &amp;amp; Screen</title>
      <link>https://0biglife.tistory.com/entry/React-Native-Typescript-Nested-Navigator-%EA%B5%AC%EC%A1%B0-%EB%B3%80%EA%B2%BD</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;정리 전 진행 상황&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- Nesting Navigator 구조 재구성 (StackNavigator 간소화(MainStack 삭제 후 나머지 스크린을 RootStack으로 올림)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFiLcI/btrtGhfaZqE/kK4JoQ2gkAY1n9Fx9gnRvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFiLcI/btrtGhfaZqE/kK4JoQ2gkAY1n9Fx9gnRvk/img.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;1280&quot; width=&quot;297&quot; height=&quot;396&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFiLcI/btrtGhfaZqE/kK4JoQ2gkAY1n9Fx9gnRvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFiLcI%2FbtrtGhfaZqE%2FkK4JoQ2gkAY1n9Fx9gnRvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bInoH1/btrtBUSzqlP/reCElVvNNwK8635QnWxsOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bInoH1/btrtBUSzqlP/reCElVvNNwK8635QnWxsOk/img.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;1280&quot; data-is-animation=&quot;false&quot; width=&quot;421&quot; height=&quot;561&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bInoH1/btrtBUSzqlP/reCElVvNNwK8635QnWxsOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbInoH1%2FbtrtBUSzqlP%2FreCElVvNNwK8635QnWxsOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- Navigator 별로 ParamList Setting&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- SignUp, HomeView-PostCard Button 처럼 navigate이 필요한 화면에서 NavigationProps(필요시 CompositenNavigateProp 사용) Setting&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1790&quot; data-origin-height=&quot;1820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kZhVZ/btrtE1cNi1h/hOcnUtynvkI9IYUImg51c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kZhVZ/btrtE1cNi1h/hOcnUtynvkI9IYUImg51c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kZhVZ/btrtE1cNi1h/hOcnUtynvkI9IYUImg51c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkZhVZ%2FbtrtE1cNi1h%2FhOcnUtynvkI9IYUImg51c0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1790&quot; height=&quot;1820&quot; data-origin-width=&quot;1790&quot; data-origin-height=&quot;1820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진처럼 Navigator에 대한 Type Checking 만 별개 클래스로 코드를 짰다. Screen에 대한 TypeChecking은 NavigatinoProp과 RouteProp이 있는데, 화면마다 넣어줄 애들을 미리 세팅하다가 보니 너무 복잡해져서 코드가 꼬이면서 Nesting Navigation이 너무 지저분해졌다. 가독성도 떨어지게 되고 화면이 하나 추가될 때 설정해줄 코드도 너무 비효율적이기 때문에 Screen Props에 대한 코드는 화면을 담당한 코드 안에 넣어주기로 했다. 예시는 아래 사진과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;1860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etYwhx/btrtIlVBriz/CKgUJmpwXOwjc1bOP4ykK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etYwhx/btrtIlVBriz/CKgUJmpwXOwjc1bOP4ykK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etYwhx/btrtIlVBriz/CKgUJmpwXOwjc1bOP4ykK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetYwhx%2FbtrtIlVBriz%2FCKgUJmpwXOwjc1bOP4ykK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1796&quot; height=&quot;1860&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;1860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진에서 20번째 라인만 보면 된다. HomeViewProps를 interface로 정의해주고 그 안에 화면 이동을 위한 navigation과 추후에 파라미터 전달을 위한 route를 넣어주는 방식이다. HomeView의 경우, RootStack아래에 있으면서 MainTab에 속하기 때문에 CompositeNavigationProp을 써서 첫 번째 인자는 자기 자신, 두 번째 인자는 부모 네비게이션과 그 파라미터를 넣어주었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2612&quot; data-origin-height=&quot;1806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ304b/btrtGjdgga5/D6t0U972ohuh9JZc2zG4Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ304b/btrtGjdgga5/D6t0U972ohuh9JZc2zG4Xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ304b/btrtGjdgga5/D6t0U972ohuh9JZc2zG4Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ304b%2FbtrtGjdgga5%2FD6t0U972ohuh9JZc2zG4Xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2612&quot; height=&quot;1806&quot; data-origin-width=&quot;2612&quot; data-origin-height=&quot;1806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;navigate의 첫 번째 인자로는 '이동할 화면', 두 번째 인자로는 '전달해야하는 파라미터'로 주어 다음 화면에서 이전 화면으로부터 값을 이어받으면서 이동하게 되는데, 오른쪽 화면을 담당하는 코드는 홈뷰의 FlatList에 들어갈 셀(PostCard라고 부르겠다)이다. PostCard 내부에서도 받을 인자의 타입을 선언해주고, 받은 값들을 그대로 필요한 UI 내부에 넣어주어 화면을 구성시킨다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2486&quot; data-origin-height=&quot;1726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbHUG5/btrtISTcdXC/E3yu7KS3BgKaqLOIbkPxfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbHUG5/btrtISTcdXC/E3yu7KS3BgKaqLOIbkPxfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbHUG5/btrtISTcdXC/E3yu7KS3BgKaqLOIbkPxfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbHUG5%2FbtrtISTcdXC%2FE3yu7KS3BgKaqLOIbkPxfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2486&quot; height=&quot;1726&quot; data-origin-width=&quot;2486&quot; data-origin-height=&quot;1726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물은 전부터 해오던 비주얼과 동일하다. 이번 스터디를 통해 화면 이동시 필요한 부분들이 자리잡혔다. Typescript가 종종 미워지는 경우도 있었지만 결과적으로는 더 탄탄한 코드가 완성되어 확실이 이점이 있다고 생각한다. (추가 구현으로는, 게시글 프로필을 눌렀을 때 해당 게시글의 id를 받아와서 그 게시글을 올린 유저의 프로필로 이동할 수 있게 코드를 구현했다. 이 부분은 게시글을 쓸 정도의 스터디가 뭉치면 그 때 추가 작성할 예정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;react-navigation/native-stack 과 react-navigation/stack 의 차이점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1644998097791&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;What is the difference between @react-navigation/stack vs @react-navigation/native-stack?&quot; data-og-description=&quot;What is the difference between @react-navigation/stack vs @react-navigation/native-stack ? Is @react-navigation/stack is only for react applications and @react-navigation/native-stack is for only r...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/69064126/what-is-the-difference-between-react-navigation-stack-vs-react-navigation-nati#:~:text=Native%20Stack%20uses%20the%20Android,systems%20to%20navigate%20between%20pages.&amp;amp;text=The%20other%20one%20does%20not,the%20same%20or%20as%20performant.&quot; data-og-url=&quot;https://stackoverflow.com/questions/69064126/what-is-the-difference-between-react-navigation-stack-vs-react-navigation-nati&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xlBoD/hyNqFU3yGj/d3D2QutmH2sruHKKA7HqPk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/69064126/what-is-the-difference-between-react-navigation-stack-vs-react-navigation-nati#:~:text=Native%20Stack%20uses%20the%20Android,systems%20to%20navigate%20between%20pages.&amp;amp;text=The%20other%20one%20does%20not,the%20same%20or%20as%20performant.&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/69064126/what-is-the-difference-between-react-navigation-stack-vs-react-navigation-nati#:~:text=Native%20Stack%20uses%20the%20Android,systems%20to%20navigate%20between%20pages.&amp;amp;text=The%20other%20one%20does%20not,the%20same%20or%20as%20performant.&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xlBoD/hyNqFU3yGj/d3D2QutmH2sruHKKA7HqPk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;What is the difference between @react-navigation/stack vs @react-navigation/native-stack?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What is the difference between @react-navigation/stack vs @react-navigation/native-stack ? Is @react-navigation/stack is only for react applications and @react-navigation/native-stack is for only r...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;화면이 이동하고 화면에 대한 Type Checking을 하는 과정에 두 라이브러리의 차이점이 궁금하여 간단히 찾아보았다. 둘 다 화면이 이동하기 위한 navigation을 지원해주는 라이브러리이다. react-navigation/native-stack은 이름만 보면 알다시피 Android와 iOS 시스템 내부의 Native Stack을 활용하여 화면 전환을 하는 방식이다. 반면, react-navigation/stack은 조금 다르다. react-navigation/stack은 HTML/JavaScript 상에서 navigation을 모방하도록 설계되었으며, 그렇기 때문에 transition 을 위한 커스터마이징이 가능해진다. 세부적인 퍼포먼스 차이는 문서 상에서 확인할 수 있으며, transition에 있어 커스터마이징이 필요하다면 react-navigation/stack을 써야겠다.&lt;/p&gt;</description>
      <category>Frontend/Navigation</category>
      <category>0biglife</category>
      <category>nested navigation</category>
      <category>nested navigator</category>
      <category>react-navigation</category>
      <category>ReactNative</category>
      <category>RouteProp</category>
      <category>StackNavigationProp</category>
      <category>Typescipt</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/31</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-Typescript-Nested-Navigator-%EA%B5%AC%EC%A1%B0-%EB%B3%80%EA%B2%BD#entry31comment</comments>
      <pubDate>Tue, 15 Feb 2022 18:19:56 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] React Query (1) 기본기</title>
      <link>https://0biglife.tistory.com/entry/React-Query</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이번 게시글에서는 &lt;b&gt;리액트 쿼리&lt;/b&gt;에 대해 정리해보려고 한다. 개념을 정리함에 있어서 이 기능이 어떤 의미와 역할을 가지는지 파악하는 과정은 중요하며, 이는 내가 구현하는 코드에 어떤 이로운 영향을 미치는지 알아내는 중요한 과정이다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;우선, 리액트 쿼리(React Query)는 API 연동에 특화된 라이브러리로서 &lt;b&gt;Hook을 기반으로 데이터 로딩을 편하게 구현&lt;/b&gt;하도록 도와준다. 기존에 우리가 데이터를 넣어주거나 로딩 indicator를 띄워줄 때 또는 모달을 생성해줄 때 다음과 같이 useState를 써서 구현해주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644644216457&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Example = () =&amp;gt; {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  const fetchData = () =&amp;gt; {
    setLoading(true);
    try {
      const posts = await getPosts();
      setData(posts);
    } catch (e) {
      setError(e);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() =&amp;gt; {
    fetchData();
  }, []);

  // ...
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이러한 과정도 사실 어려운 과정을 아니지만 요청하는 API의 수가 많아질수록 그 작업은 더욱 복잡하고 귀찮아질 것이고, 컴포넌트 내부에 선언된 API 이기 때문에 사용자가 다른 화면으로 넘어가면서 해당 컴포넌트가 사라지면 지정된 상태 역시 사라진다. 따라서 나중에 다시 컴포넌트를 다시 생성시킬 때 기존에 받아온 응답 결과를 사용하지 못하고 새로운 API를 재요청해야 한다. 즉, 한 마디로 &lt;b&gt;캐싱이 불가능&lt;/b&gt;하다는 것이다.(=저장되지 않는다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;캐싱이 필요하다면 데이터를 Context, 리덕스, 리코일과 같은 라이브러리로 관리해야 하지만 그렇게 할 시에는 코드가 복잡해진다. 따라서, &lt;u&gt;&lt;b&gt;리액트 쿼리&lt;/b&gt;를 사용한다면 컴포넌트에서 Hook을 기반으로 &lt;b&gt;데이터 로딩을 효율적으로 관리&lt;/b&gt;할 수 있고, &lt;b&gt;캐싱도 제공&lt;/b&gt;하기 때문에 여러 이점을 취할 수 있다!&lt;br /&gt;&lt;br /&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt;&lt;u&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;useQuery Provider 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;useQuery를 적용하기 앞서 리액트 쿼리에서 캐시를 관리할 때 사용하는 QueryClient 인스턴스를 자식 컴포넌트에서 사용할 수 있도록 Provider 세팅을 해줘야 한다. &lt;br /&gt;&amp;nbsp;Provider의 위계가 없기 때문에 우선 ThemeProvider를 감싸도록 하였고 내부 인자로 QueryClient를 넣어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1644651664243&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {QueryClient, QueryClientProvider} from 'react-query';

const queryClient = new QueryClient();

const App = () =&amp;gt; {
  return (
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt; //Provider Setting
      &amp;lt;ThemeProvider theme={theme === 'dark' ? dark : light}&amp;gt; //ThemeProvider Setting
        &amp;lt;RootNavigation /&amp;gt;
      &amp;lt;/ThemeProvider&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;useQuery의 반환값&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&amp;nbsp;현재 홈뷰에서 서버로 뉴스피드 데이터를 .get하는 방식을 useQuery로 구현하려는 것이기 때문에 기존에 구현해놓은 코드를 밀고 새로 작성했다. useQuery는 &lt;b&gt;첫 번째 인자&lt;/b&gt;로는 조회하려는 &lt;b&gt;데이터의 키 값&lt;/b&gt;을 받고, 만약 이미 존재하는 데이터를 조회하려고 할 시 기존 데이터를 바로 보여준다. &lt;b&gt;두 번째 인자&lt;/b&gt;에는 &lt;b&gt;Promise를 반환하는 함수&lt;/b&gt;를 받는다. 예를 들어, 다음과 같다.&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&amp;nbsp;(Promise는 자바스크립트에서 비동기적 작업을 편하게 관리하도록 도와주는 객체이다. Promise와 동기/비동기에 대한 내용은 추후 정리해서 올릴 예정!)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1644661597137&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Example = () =&amp;gt; {
  const result = useQuery('posts', postData);
  const { data, error, isLoading } = result;

  if (isLoading) return &amp;lt;Component&amp;gt;...&amp;lt;/Component&amp;gt;;
  if (error) return &amp;lt;Component&amp;gt;...&amp;lt;/Component&amp;gt;;

  return &amp;lt;View&amp;gt;{ ...Data... }&amp;lt;/View&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp; 이때, useQuery Hook을 사용하여 반환된 result 객체는 다음과 같은 값을 가지고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;status&lt;/b&gt; : API의 요청 상태를 문자열로 나타낸다.&lt;br /&gt;- isLoading : 아직 데이터를 받아오지 않은 상태이며, 현재 데이터 요청 중인 상태&lt;br /&gt;- isError : 오류 발생&lt;br /&gt;- isSuccess : 데이터 요청 성공&lt;br /&gt;- idle : 비활성화된 상태(따로 설정하여 비활성화된 상태인 경우)&lt;br /&gt;- data : 요청 성공한 데이터&lt;br /&gt;- refetch : 다시 요청을 시작하는 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 요청 함수를 호출하는 상황에서 파라미터를 넘겨줘야 한다면 데이터 키 값과 함께 배열을 선언하고(이때 배열 키 값의 순서는 위계가 없다) 두 번째 인자로 들어갈 함수는 화살표 함수로 선언해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1644661996471&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const result = useQuery(['posts', id], () =&amp;gt; postData(id, name));
//객체 키값 순서 무관
const result = useQuery(['posts', {id: 1, age: 23}], () =&amp;gt; ...);
const result = useQuery(['posts', {age: 23, id: 1}], () =&amp;gt; ...);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;useQuery의 Options&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&amp;nbsp;useQuery의 세 번째&amp;nbsp; 인자에 options 객체를 넣어 Hook의 작동 방식을 설정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1644662232665&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Example = () =&amp;gt; {
  const result = useQuery('posts', postData, { //Options
    enabled: true,
    refetchOnMount: true,
  });
);
// Options: enabled, retry, staleTime, cacheTime, onSuccess, onError...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;useQuery 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;우선 홈뷰에서 게시글을 받아오기 위한 API 요청을 담당하는 getArticles 함수를 따로 선언을 해주었다. 이 함수는 useQuery의 두 번째 인자로 들어가야 하기 때문에 Promise 반환을 위하여 async, await을 사용하였다. 요청한 데이터 응답을 위하여 Generic에는 Feed[]를 넣어주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644662463392&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Data Generic
export interface Feed {
  id: number;
  name: string;
  title: string;
  url: string;
  thumbnailUrl: string;
  postTime: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;위에서 언급하였듯, 반환값으로 data와 isLoading을 주고 FlatList의 data에 반환된 data를 넣어주었다. renderItem에는 받아온 값을 따로 만들어놓은 &amp;lt;PostCard&amp;gt; 컴포넌트 안의 파라미터에 적합하도록 넣어주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1644661270158&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface HomeProps {
  navigation: StackNavigationProp&amp;lt;HomeParamsList, 'HomeView'&amp;gt;;
}

const getArticles = async () =&amp;gt; {
  const response = await apiClient.get&amp;lt;Feed[]&amp;gt;('/feeds');
  return response.data;
};

const HomeView: React.FC&amp;lt;HomeProps&amp;gt; = ({navigation}) =&amp;gt; {
  const {data, isLoading} = useQuery('articles', getArticles);
  const [refresh, setRefresh] = useState&amp;lt;boolean&amp;gt;(false);

  console.log({data, isLoading});

  if (!data) {
    return &amp;lt;ActivityIndicator size=&quot;large&quot; style={{flex: 1}} /&amp;gt;;
  }

  const wait = (timeout: number) =&amp;gt; {
    return new Promise(resolve =&amp;gt; setTimeout(resolve, timeout));
  };

  const refreshing = () =&amp;gt; {
    setRefresh(true);
    wait(1400).then(() =&amp;gt; setRefresh(false));
    getArticles();
  };

  return (
    &amp;lt;SafeContainer&amp;gt;
      &amp;lt;FlatList
        data={data}
        renderItem={({item}) =&amp;gt; (
          &amp;lt;PostCard
            id={item.id}
            name={item.name}
            title={item.title}
            postTime={item.postTime}
            url={item.url}
            thumbnailUrl={item.thumbnailUrl}
            // navigation={navigation}
          /&amp;gt;
        )}
        keyExtractor={item =&amp;gt; item.id.toString()}
        showsVerticalScrollIndicator={false}
        onRefresh={() =&amp;gt; refreshing()}
        refreshing={refresh}
      /&amp;gt;
    &amp;lt;/SafeContainer&amp;gt;
  );
};

export default HomeView;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과, 전에 구현한 상태와 동일하게 나온 것을 확인 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1978&quot; data-origin-height=&quot;1688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kHmzh/btrs8andVNh/AH1ycYJ4l6eO67zbRbY9bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kHmzh/btrs8andVNh/AH1ycYJ4l6eO67zbRbY9bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kHmzh/btrs8andVNh/AH1ycYJ4l6eO67zbRbY9bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkHmzh%2Fbtrs8andVNh%2FAH1ycYJ4l6eO67zbRbY9bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1978&quot; height=&quot;1688&quot; data-origin-width=&quot;1978&quot; data-origin-height=&quot;1688&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Frontend/Axios</category>
      <category>0biglife</category>
      <category>react-query</category>
      <category>ReactNaitve</category>
      <category>ReactQuery</category>
      <category>useQuery</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/30</guid>
      <comments>https://0biglife.tistory.com/entry/React-Query#entry30comment</comments>
      <pubDate>Fri, 11 Feb 2022 20:29:58 +0900</pubDate>
    </item>
    <item>
      <title>React Native 3개월차 중간점검</title>
      <link>https://0biglife.tistory.com/entry/React-Native-3%EA%B0%9C%EC%9B%94%EC%B0%A8-%EC%A4%91%EA%B0%84%EC%A0%90%EA%B2%80-%EC%9D%BC%EC%83%81-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RgwBj/btrsRPXYJJ1/Lk2KhXXJ1uePoKq1Kp58Dk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RgwBj/btrsRPXYJJ1/Lk2KhXXJ1uePoKq1Kp58Dk/img.jpg&quot; data-alt=&quot;월령공주 멋져..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RgwBj/btrsRPXYJJ1/Lk2KhXXJ1uePoKq1Kp58Dk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRgwBj%2FbtrsRPXYJJ1%2FLk2KhXXJ1uePoKq1Kp58Dk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;442&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;월령공주 멋져..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;코로나 양성이 떠서 자가격리를 하면서 체력이 많이 떨어졌다. 물론 방에서도 근력과 유산소 운동을 하긴 했지만, 감기 기운 있을 때 하루종일 잠을 자다보니 낮에도 너무 졸려서 공포방송을 틀어놓고 공부하는 해야할 정도..이다. 근황과 최근 생각에 대해 정리해볼 필요가 있기도 하고, 중간점검도 해볼 겸 해서 글을 작성하게 되었다. 우선, '열정은 기본이며, 시스템이 갖춰진 개발자'가 되자는 목표를 세운 것에 대해 간단히 부연설명을 하면서 서두를 시작해볼까 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;나는 동기부여가 무척 중요한 사람이다. 사람의 몸 역시 알고리즘으로 이루어져 있다고 생각하면 무언가를 실행함에 있어 실천하기가 더 편해진다. 이를 테면, 에너지 넘치는 텐션을 유지하기 위해 베이스가 강한 음악을 틀어놓고 샤워나 코딩을 하기도 하고, 먹기 싫어도 억지로 끼니를 챙기고 나면 힘이 생겨서 맥북을 들고 카페를 당차게 들어가게 된다. 의욕이 떨어지고 목표의식이 하락선을 타게 되면 일부러 동기부여를 위한 유튜브 영상을 쉬는 셈 치고 시청하곤 한다. 대표적으로 EO 채널의 여러 분야에서 도전하는 멋진 사람들의 인터뷰 내용을 듣다 보면 '아 내가 뭘 하고 있던 거지?' 하면서 조바심과 의욕이 생기게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하루는 시니어급 개발자들의 신입 개발자 취업을 주제로 한 영상을 봤는데, '시스템이 갖춰지는게 중요하다' 라는 포인트가 무척이나 인상적이어서 그 날 바로 개설하게 된 블로그가 바로 이 블로그이다. 열정은 누구에게나 있기에 본인이 열정이 있다는 것을 면접관에게 어필하는 것은 큰 의미가 없다. (열정은 정말이지 너무나 당연한 것이다.) 따라서, 그 열정을 증명할 방법이 필요하기 때문에 그 수단으로서의 '시스템'이 필요하다. 필자는 1주일에 최소 한 번은 공부에 대한 기록을 정리해서 올리자는 목표를 세웠으며, 매일 일정 시간 이상의 개발 독학을 진행하기로 정해놓았다. 그러나 그 패턴이 최근 들어 조금 흔들리는 것 같아서 반성하자는 취지 + 여태 공부를 정리해볼 겸 오랜만에 ENFJ 카테고리에 정리 글을 작성중이라는 사실..(화이팅)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여태 3개월 동안 작성한 기능은 다음과 같다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;소셜 로그인&lt;/b&gt;&lt;br /&gt;구글 : 유저정보/토큰 을 받아서 AsyncStorage에 저장했다&lt;span style=&quot;color: #ef6f53;&quot;&gt; (안드로이드 SHA-1 에러..)&lt;/span&gt;&lt;br /&gt;애플 : 아직 구현중(개발자 페이지에서 SignIn 등록 필요한 상태)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;회원 인증&lt;/b&gt;&lt;br /&gt;로직만 공부해둔 상태(twilio 활용)&lt;br /&gt;UI : 전화번호 입력 화면, 인증번호 입력 화면, 유저정보 입력 화면&lt;br /&gt;react-native-permissions를 활용한 접근권한 구현(카메라/갤러리/연락처/마이크)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네비게이션&lt;/b&gt;&lt;br /&gt;BottomTab Navigator : 홈(뉴스피드) + 검색뷰 + 게시판뷰 + 프로필뷰&lt;br /&gt;MaterialTopTab Navigator : 홈뷰에서 상단 카테고리 3개 구성&lt;br /&gt;Stack Navigator : 로그인-회원가입 / 프로필-프로필편집-웹뷰&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;카메라/앨범&amp;nbsp;&lt;/b&gt;&lt;br /&gt;ImagePicker 적용&lt;br /&gt;프로필뷰와 홈뷰에서 게시글 추가 기능에 각각 적용&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Restful API&amp;nbsp;&lt;/b&gt;&lt;br /&gt;회원가입시 유저정보 .post&lt;br /&gt;홈뷰로 들어가면 뉴스피드 .get&lt;br /&gt;게시글 추가시 지정된 이미지, 텍스트 .post&amp;nbsp;&lt;br /&gt;추가된 게시글을 홈뷰 refresh하면 자동업데이트(.get)&lt;br /&gt;프로필뷰 들어가면 유저 프로필사진과 이름 .get&lt;br /&gt;검색기능에서 .get 테스트 진행중&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Redux&lt;/b&gt;&lt;br /&gt;Provider, Action, Reducer 넣어뒀으나 정확히 어떤 기능에 어떻게 쓸지는 공부중&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결제&lt;/b&gt;&lt;br /&gt;웹뷰를 활용하여 PG 불러오는 건지 'bootpay'나 'iamport' 를 써야하는지 찾아보는 중&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2054&quot; data-origin-height=&quot;1726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1kA4c/btrsSuZ9Ec6/L3B3hM1O8VFQlJSLitHlGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1kA4c/btrsSuZ9Ec6/L3B3hM1O8VFQlJSLitHlGk/img.png&quot; data-alt=&quot;API test 내가 좋아하는 전기차 넣어보는 재미&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1kA4c/btrsSuZ9Ec6/L3B3hM1O8VFQlJSLitHlGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1kA4c%2FbtrsSuZ9Ec6%2FL3B3hM1O8VFQlJSLitHlGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2054&quot; height=&quot;1726&quot; data-origin-width=&quot;2054&quot; data-origin-height=&quot;1726&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;API test 내가 좋아하는 전기차 넣어보는 재미&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;흠.. 이렇게 정리해서 보니깐 시간에 비해 진행을 많이 못한 것 같다.. 우선 정리하길 잘했다. 아직 배울게 정말 많아서 조바심이 나기도 하고, 재밌기도 한데 무엇보다 같이 공부하거나 옆에서 피드백을 줄 수 있는 선배가 한 명 있으면 너무너무 도움이 클 것 같다.(뼈저리게 느끼는 중..) 독학을 하면서 느낀 점은 가장 먼저 '&lt;b&gt;내가 무엇을 모르는지에 대한 두려움&lt;/b&gt;'이 가장 큰 것 같다. 하지만 시간을 무식하게 갈아넣어서 공부하다보면 마인드맵핑하듯이 한 바닥에 쫙 깔리면서 지도가 그려지기도 하는데 이 과정을 어느 순간부터는 즐길 수 있게 된 것 같다. 나름 내 스스로가 단단해지는 과정이라고 합리화를 하면서 열심히 달리고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;P.S&lt;/b&gt; 공부하기 싫어질 때 취업 사이트를 뒤져보는데 최근에 가고픈 스타트업들이 몇 개 추려지면서 뭔가 설레기도 하다. 얼른 성장하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;645&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9qoMA/btrsMbup7cf/EpfLkLmuvNmLH1ombwXCc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9qoMA/btrsMbup7cf/EpfLkLmuvNmLH1ombwXCc1/img.png&quot; data-alt=&quot;개발 인생과 베이징 올림픽 화이팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9qoMA/btrsMbup7cf/EpfLkLmuvNmLH1ombwXCc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9qoMA%2FbtrsMbup7cf%2FEpfLkLmuvNmLH1ombwXCc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;645&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;645&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개발 인생과 베이징 올림픽 화이팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Entj Life</category>
      <category>0biglife</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/29</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-3%EA%B0%9C%EC%9B%94%EC%B0%A8-%EC%A4%91%EA%B0%84%EC%A0%90%EA%B2%80-%EC%9D%BC%EC%83%81-%EC%A0%95%EB%A6%AC#entry29comment</comments>
      <pubDate>Tue, 8 Feb 2022 19:37:50 +0900</pubDate>
    </item>
    <item>
      <title>구글 소셜 로그인 Android 에러</title>
      <link>https://0biglife.tistory.com/entry/%EA%B5%AC%EA%B8%80-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-Android-%EC%97%90%EB%9F%AC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;구글과 무슨 악연이 있는지.. 분명 제기능을 잘 하고 있던 소셜 로그인이 안드로이드에서만 빌드 에러를 부려 말썽이다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로 해결해야할 에러..&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. react.gradle 에러&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;1502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEJohe/btrseQJjpnJ/KjtkXh9zzqxyob7KeuYL01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEJohe/btrseQJjpnJ/KjtkXh9zzqxyob7KeuYL01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEJohe/btrseQJjpnJ/KjtkXh9zzqxyob7KeuYL01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEJohe%2FbtrseQJjpnJ%2FKjtkXh9zzqxyob7KeuYL01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1360&quot; height=&quot;1502&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;1502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/40019584/could-not-find-method-android-for-arguments-org-gradle-api-project&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/40019584/could-not-find-method-android-for-arguments-org-gradle-api-project&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1643617109263&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Could not find method android() for arguments org.gradle.api.Project&quot; data-og-description=&quot;Getting a bug, when I try to compile my project in studio , i have search quite a bit with no real solution to it Error:(17, 0) Could not find method android() for arguments [&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/40019584/could-not-find-method-android-for-arguments-org-gradle-api-project&quot; data-og-url=&quot;https://stackoverflow.com/questions/40019584/could-not-find-method-android-for-arguments-org-gradle-api-project&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hJXtM/hyNgdRa4yA/dcjRG9sv93C6Yj1CCnBPUK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/40019584/could-not-find-method-android-for-arguments-org-gradle-api-project&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/40019584/could-not-find-method-android-for-arguments-org-gradle-api-project&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hJXtM/hyNgdRa4yA/dcjRG9sv93C6Yj1CCnBPUK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Could not find method android() for arguments org.gradle.api.Project&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Getting a bug, when I try to compile my project in studio , i have search quite a bit with no real solution to it Error:(17, 0) Could not find method android() for arguments [&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1643617154845&quot; class=&quot;groovy&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 줄 코드가 맨 하단에 있던 걸 최상단으로 끌어올렸다(android {} 보다 위에 위치하도록.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Android Emulator Wifi disconnected 문제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=TNIz3P2r4rU&quot;&gt;https://www.youtube.com/watch?v=TNIz3P2r4rU&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=TNIz3P2r4rU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/pApxP/hyNgm2kqeN/rcUXXd412CcUK2hwbQHbyK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_420_220_660&quot; data-video-width=&quot;700&quot; data-video-height=&quot;394&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/TNIz3P2r4rU&quot; width=&quot;700&quot; height=&quot;394&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 구글 연결 + Wifi 문제 해결되었으나, 로그인 인증 완료시 'A non-recoverable sign in failure occurred' 에러&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhlZWi/btrr9gJZydo/xEdqaFom457FosZJ5kHmUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhlZWi/btrr9gJZydo/xEdqaFom457FosZJ5kHmUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhlZWi/btrr9gJZydo/xEdqaFom457FosZJ5kHmUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhlZWi%2Fbtrr9gJZydo%2FxEdqaFom457FosZJ5kHmUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;119&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbF7XN/btrshzat9Kr/TrC4UKmstnDG0h7Yr3DYa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbF7XN/btrshzat9Kr/TrC4UKmstnDG0h7Yr3DYa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbF7XN/btrshzat9Kr/TrC4UKmstnDG0h7Yr3DYa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbF7XN%2Fbtrshzat9Kr%2FTrC4UKmstnDG0h7Yr3DYa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;491&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해쉬값에 대한 참고 블로그&amp;nbsp;&lt;/p&gt;</description>
      <category>Error Record</category>
      <category>0biglife</category>
      <category>error</category>
      <category>react native</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/28</guid>
      <comments>https://0biglife.tistory.com/entry/%EA%B5%AC%EA%B8%80-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-Android-%EC%97%90%EB%9F%AC#entry28comment</comments>
      <pubDate>Mon, 31 Jan 2022 19:42:20 +0900</pubDate>
    </item>
    <item>
      <title>[ReactNative] Axios로 게시물 Post하기</title>
      <link>https://0biglife.tistory.com/entry/ReactNative-Axios%EB%A1%9C-%EA%B2%8C%EC%8B%9C%EB%AC%BC-Post%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 FlatList로 구현해놓은 홈뷰에 새로운 게시글을 넣어볼까 한다. 과정은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HomeNavigation의 HeaderRight 버튼을 통해 모달뷰 접근&lt;/li&gt;
&lt;li&gt;모달뷰에서 선택지를 골라서(카메라/앨범/--) UploadView 접근&lt;/li&gt;
&lt;li&gt;UploadView에서 게시글에 들어갈 새로운 이미지소스와 텍스트를 입력&lt;/li&gt;
&lt;li&gt;상단 체크 버튼 누를 시 서버로 Post 요청&lt;/li&gt;
&lt;li&gt;홈뷰에서 리스트 Refresh할 시, 서버로 모든 게시글 Get 요청&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 게시글 텍스트와 이미지소스를 제외한 데이터만 받아오는 에러가 있었는데, 타입형 문제도 아니고 뭐지? 싶어서 어제 하루종일 낑낑대다가 결국 잠들었다.. 오늘 일어나서 터미널창 끄고 시뮬레이터도 다시 실행했더니 해결.. (네?..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무튼 nodejs 코드는 다음을 추가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1643267189795&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//뉴스피드 가져오기
router.get('/api/feeds', (req, res) =&amp;gt; {
	res.json(feeds);
});

//게시물 업로드
router.post('/api/feeds/add', (req, res) =&amp;gt; {
	const feed = {
		id: feeds.length + 1,
		name: '0biglife',
		title: req.body.title,
		url: req.body.url,
		thumbnailUrl: req.body.thumbnailUrl,
		postTime: req.body.postTime,
	};
	feeds.push(feed);
	res.json({ ok: true, feeds: feed });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;body의 파라미터를 통해 값을 전달해올 것이므로 res.body.(변수명) 으로 받아와서 넣어줬다. id는 1만큼 더해서 넣어주고, name은 아직 회원가입할 때 유저가 입력할 정보들에 대한 픽스가 되지 않은 상태라 우선 내 닉네임을 넣어줬다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1643267324563&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//React-Native Code
let today = new Date();
  let time = {
    year: today.getFullYear(),
    month: today.getMonth() + 1,
    date: today.getDate(),
  };
  let timeSet = `${time.year}-${time.month}-${time.date}`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 값은 RN 쪽에서 Date() 함수를 써서 '연도-월-일' 로만 전달했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은, UploadView 코드.&lt;/p&gt;
&lt;pre id=&quot;code_1643267447449&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//UploadView 
  const PostingDone = () =&amp;gt; {
    if (uri) {
      apiClient
        .post&amp;lt;ResponseFeed&amp;gt;('/feeds/add', {
          title: textInput,
          url: uri,
          thumbnailUrl: uri,
          postTime: timeSet,
        })
        .then(response =&amp;gt; {
          console.log(response.data);
        });
      navigation.goBack();
    } else {
      Alert.alert('no ImageSource!');
    }
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 넣지 않은 상태로 게시물 업로드할 시 로직은 아직 넣지 않은 상태이기 때문에 알람창만 넣어줬다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 구현으로는, 위에서 말한 세부적인 로직과 서버에서 최신 업로드 순(수정 시 미포함)으로 게시물을 업데이트하는 것을 구현할 계획이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과물&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;123.gif&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R6RHy/btrrPXReypS/sVkhoK5ZocEbbFpZTJXPX1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R6RHy/btrrPXReypS/sVkhoK5ZocEbbFpZTJXPX1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R6RHy/btrrPXReypS/sVkhoK5ZocEbbFpZTJXPX1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/R6RHy/btrrPXReypS/sVkhoK5ZocEbbFpZTJXPX1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;929&quot; data-filename=&quot;123.gif&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Logic 추가 구현&lt;/b&gt;(UploadView로 들어와있는 상태)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;취소 버튼에 대한 로직&lt;br /&gt;&lt;/b&gt;1-1 ) 이미지 소스 or 텍스트가 추가된 상태에서 사용자에게 취소 여부 되묻기&lt;br /&gt;1-2 ) 이미지 소스 or 텍스트가 없는 상태에서 홈뷰로 이동&lt;/li&gt;
&lt;li&gt;&lt;b&gt;업로드 버튼에 대한 로직 &lt;/b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(텍스트는 없어도 업로드 가능하도록 함)&lt;/span&gt;&lt;br /&gt;2-1 ) 이미지 소스가 추가된 상태에서 사용자에게 되물은 후 게시글 서버에 Post 요청.&lt;br /&gt;2-2 ) 이미지 소스가 없는 상태에서 사용자에게 되물은 후 홈뷰로 이동&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 다음과 같이 짰다.(모달은 props를 활용한 재사용이 가능한 컴포넌트로 구성)&lt;/p&gt;
&lt;pre id=&quot;code_1643270398445&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import styled from 'styled-components/native';
import Modal from 'react-native-modal';

interface Props {
  modalVisible: boolean;
  setModalVisible: any;
  alertTitle: string;
  leftTitle: string;
  rightTitle: string;
  leftButton: () =&amp;gt; void;
  rightButton: () =&amp;gt; void;
}

const SimpleModal = (props: Props) =&amp;gt; {
  return (
    &amp;lt;SafeAreaView&amp;gt;
      &amp;lt;Modal
        isVisible={props.modalVisible}
        animationInTiming={1}
        animationOutTiming={1}
        // hideModalContentWhileAnimating={true}
        onBackdropPress={() =&amp;gt; props.setModalVisible(false)}&amp;gt;
        &amp;lt;ModalContainer&amp;gt;
          &amp;lt;ModalTitle&amp;gt;
            &amp;lt;ModalTitleText&amp;gt;{props.alertTitle}&amp;lt;/ModalTitleText&amp;gt;
          &amp;lt;/ModalTitle&amp;gt;
          &amp;lt;ButtonWrapper&amp;gt;
            &amp;lt;ModalButton onPress={props.leftButton}&amp;gt;
              &amp;lt;ModalButtonText&amp;gt;{props.leftTitle}&amp;lt;/ModalButtonText&amp;gt;
            &amp;lt;/ModalButton&amp;gt;
            &amp;lt;ModalButton onPress={props.rightButton}&amp;gt;
              &amp;lt;ModalButtonText&amp;gt;{props.rightTitle}&amp;lt;/ModalButtonText&amp;gt;
            &amp;lt;/ModalButton&amp;gt;
          &amp;lt;/ButtonWrapper&amp;gt;
        &amp;lt;/ModalContainer&amp;gt;
      &amp;lt;/Modal&amp;gt;
    &amp;lt;/SafeAreaView&amp;gt;
  );
};

export default SimpleModal;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취소 버튼, 업로드 버튼에 대한 로직 추가.&lt;/p&gt;
&lt;pre id=&quot;code_1643270584021&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const CancelLogic = () =&amp;gt; {
    if (textInput || uri) {
      setCancelModal(true);
    } else {
      setCancelModal(false);
      navigation.goBack();
    }
  };

  const PostingDone = () =&amp;gt; {
    if (uri) { //uri 여부 확인
      apiClient
        .post&amp;lt;ResponseFeed&amp;gt;('/feeds/add', {
          title: textInput,
          url: uri,
          thumbnailUrl: uri,
          postTime: timeSet,
        })
        .then(response =&amp;gt; {
          console.log(response.data);
        });
      navigation.goBack();
    } else {
      setPostModal(false);
      Alert.alert('사진이 있어야 게시 가능합니다.');
    }
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/Axios</category>
      <category>0biglife</category>
      <category>axios</category>
      <category>nodejs</category>
      <category>react native</category>
      <category>RESTful API</category>
      <category>홈뷰 Post하기</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/27</guid>
      <comments>https://0biglife.tistory.com/entry/ReactNative-Axios%EB%A1%9C-%EA%B2%8C%EC%8B%9C%EB%AC%BC-Post%ED%95%98%EA%B8%B0#entry27comment</comments>
      <pubDate>Thu, 27 Jan 2022 16:15:40 +0900</pubDate>
    </item>
    <item>
      <title>[Nodejs/Express] 서버 구축</title>
      <link>https://0biglife.tistory.com/entry/NodejsExpress-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;React Native 안에서 axios 를 사용해서 정보를 업로드(post)하고, 가져오는(get) 방식을 직접 내 눈으로 확인하고 싶어서 간단한 서버를 만들어보기로 했다. 처음에는 어떻게 구현하는 건지 몰랐기에 엄청나게 복잡할 것으로 예상되기도 했었고, 혼자 공부하는 만큼 조언을 구하기 위해 카카오톡 오픈채팅방을 활용하여 업계 선배들에게 물어본 결과 직접 만들어보는 것이 도움이 될 거라고 하셨다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 만드는 데에 목적은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;취업 후 백엔드 개발자와의 원활한 소통&lt;/li&gt;
&lt;li&gt;RESTful API 설계 보완&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구축은 가장 먼저 npm init 과 express 설치를 통해 package.json을 생성하고, VSCode 상에서 폴더를 불러와서 index.js 로 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;package.json 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1643088710376&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//1. package.json 생성
npm init //or yarn init

//2. express 설치
npm install express --save&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctkR6j/btrrB5hMllx/b9g74NsOGUKieIZ9wEdEd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctkR6j/btrrB5hMllx/b9g74NsOGUKieIZ9wEdEd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctkR6j/btrrB5hMllx/b9g74NsOGUKieIZ9wEdEd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctkR6j%2FbtrrB5hMllx%2Fb9g74NsOGUKieIZ9wEdEd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1624&quot; height=&quot;688&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Express 불러오기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node_modules에서 express 파일을 불러오고, express()의 반환값을 라우팅을 위한 router로 지정해준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1643089166887&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//node_modules 의 express 파일 가져온다
const express = require('express');
//express 함수의 반환값을 router로 지정.
const router = express();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;BodyParser 세팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;API 개념을 처음 접했을 때 파싱에 대해 이해를 하느라 꽤 오래 걸렸던 것이 기억난다. 간단히 말하자면, 데이터를 원하는 형태로 가공하는 과정을 parsing이라 하며, 이 과정을 수행하는 모듈 또는 메소드를 parser라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;필요한 상황에서 그에 맞는 파싱이 필요한데 express 문서에 따르면 미들웨어가 없는 상태에서 req.body에 접근하는 경우에는 디폴트로 undefined가 설정된 상태이기 때문에 우리가 가공하려는 방향성에 맞게 추가 세팅을 해주어야 한다. 이러한 express를 통한 API 요청에서 받은 body값을 파싱하는 역할을 수행하는 것이 바로 bodyParser라는 미들웨어이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1643089904681&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;router.use(express.json());
router.use(express.urlencoded({ extended: true }));
// false : 기본 내장된 queryString 사용
// true : 추가 설치가 필요한 qs 라이브러리 사용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;임의 데이터 세팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;주고 받을 데이터 형태는 다음과 같이 설정했다.&lt;/p&gt;
&lt;pre id=&quot;code_1643090354887&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const users = [
	{ id: 1, name: '0biglife_01' },
	{ id: 2, name: '0biglife_02' },
	{ id: 3, name: '0biglife_03' },
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;라우팅 처리 및 서버 오픈&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;get , post 방식에서의 path 지정과 서버 오픈에 대한 코드. 데이터를 잘 받아오는지, 회원가입시 입력된 텍스트를 n 번째 id로 잘 post 해주는지를 위해 간단하게 설정했다. 추후에는 데이터 테이블이 복잡해지면서 더 복잡해질 것 같당&lt;/p&gt;
&lt;pre id=&quot;code_1643090433971&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//라우팅 처리
//GET METHOD
router.get('/', (req, res) =&amp;gt; {
  res.send('Server for API TEST!');
});

router.get('/api/users', (req, res) =&amp;gt; {
  res.json({ ok: true, users: users });
});

//param 사이에는 '&amp;amp;' 입력.!
router.get('/api/users/user', (req, res) =&amp;gt; {
  const user_id = req.query.user_id;

  const user = users.filter(data =&amp;gt; data.id == user_id);

  res.json({ ok: false, user: user });
});

// : &amp;lt;- type 모두 가능하단 뜻
router.get('/api/users/:user_id', (req, res) =&amp;gt; {
  const user_id = req.params.user_id;
  const user = users.filter(data =&amp;gt; data.id == user_id);

  res.json({ ok: true, user: user });
});

//POST METHOD
router.post(&quot;/api/users/add&quot;, (req, res) =&amp;gt; {
  const user = {
		id: users.length + 1,
		name: req.body.name,
	};

  users.push(user);
  
  res.json({ ok: true, users: user });
})

//3000포트로 서버 오픈 // router.listen(포트번호, 콜백함수)
router.listen(3000, () =&amp;gt; console.log('0BigLife Server :)'));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;전체코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1643090666144&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//node_modules 의 express 파일 가져온다
const express = require('express');
//express 함수의 반환값을 router로 지정.
const router = express();

// http://localhost:3000/ 
router.use(express.json());
router.use(express.urlencoded({ extended: true }));

const users = [
  { id: 1, name: '0biglife_01' },
  { id: 2, name: '0biglife_02' },
  { id: 3, name: '0biglife_03' },
];

//라우팅 처리
router.get('/', (req, res) =&amp;gt; {
  res.send('Server for API TEST!');
});

router.get('/api/users', (req, res) =&amp;gt; {
  res.json({ ok: true, users: users });
});

//param 사이에는 '&amp;amp;' 입력.!
router.get('/api/users/user', (req, res) =&amp;gt; {
   const user_id = req.query.user_id;

  const user = users.filter(data =&amp;gt; data.id == user_id);

  res.json({ ok: false, user: user });
});

// : &amp;lt;- 이거는 type 모두 가능하단 뜻
router.get('/api/users/:user_id', (req, res) =&amp;gt; {
  const user_id = req.params.user_id;
  const user = users.filter(data =&amp;gt; data.id == user_id);
  res.json({ ok: true, user: user });
});

//POST 방식!! 드디어..
router.post(&quot;/api/users/add&quot;, (req, res) =&amp;gt; {
  
  const user = {
		id: users.length + 1,
		name: req.body.name,
  };

  users.push(user);
  
  res.json({ ok: true, users: user });
})

//3000포트로 서버 오픈 // router.listen(포트번호, 콜백함수)
router.listen(3000, () =&amp;gt; console.log('0BigLife Server :)'));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 터미널 창에 node index.js 를 입력하면 log가 출력되면서 서버가 구축된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 영상에서의 로직은 : 회원가입시 유저가 텍스트를 입력하고 버튼을 누르게 되면 서버로 post 요청을 한다. 홈뷰에서 프로필뷰로 들어가데 되면 화면이 생성되었을 시(useEffect) 유저가 입력했던 정보를 불러와 화면에 뿌려준 상태이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;324342.gif&quot; data-origin-width=&quot;1888&quot; data-origin-height=&quot;1770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bESFUz/btrrJtui0NF/2dp3IJRCXcAJfkhdNzAi6K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bESFUz/btrrJtui0NF/2dp3IJRCXcAJfkhdNzAi6K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bESFUz/btrrJtui0NF/2dp3IJRCXcAJfkhdNzAi6K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bESFUz/btrrJtui0NF/2dp3IJRCXcAJfkhdNzAi6K/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1888&quot; height=&quot;1770&quot; data-filename=&quot;324342.gif&quot; data-origin-width=&quot;1888&quot; data-origin-height=&quot;1770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Backend</category>
      <category>0biglife</category>
      <category>axios</category>
      <category>Express</category>
      <category>nodejs</category>
      <category>react native</category>
      <category>간단한 서버 구축</category>
      <category>서버 구축</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/26</guid>
      <comments>https://0biglife.tistory.com/entry/NodejsExpress-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95#entry26comment</comments>
      <pubDate>Tue, 25 Jan 2022 15:14:50 +0900</pubDate>
    </item>
    <item>
      <title>휴대폰 본인인증 구현</title>
      <link>https://0biglife.tistory.com/entry/%ED%9C%B4%EB%8C%80%ED%8F%B0-%EC%9D%B8%EC%A6%9D</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 회원가입하기 위한 정보를 입력하기 전에 앱 권한 설정과 본인 인증 확인이 필요하다. 이번 시간에는 전화번호를 입력하여 본인 인증 기능을 구현하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 화면 UI는 아래와 같이 TextInput과 버튼이 전부이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;1718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lAhBJ/btrrhEEczr3/vIypjeA2zr1fksDcX2A1i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lAhBJ/btrrhEEczr3/vIypjeA2zr1fksDcX2A1i0/img.png&quot; data-alt=&quot; &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lAhBJ/btrrhEEczr3/vIypjeA2zr1fksDcX2A1i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlAhBJ%2FbtrrhEEczr3%2FvIypjeA2zr1fksDcX2A1i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1930&quot; height=&quot;1718&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;1718&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 좀 더 세부적으로 구현하기 위해, TextInput은 전화번호를 입력하기 위해 11개의 번호만 입력할 수 있도록 제한시켰고, 11개가 입력되었을 때만 버튼의 disabled에 false 값을 주었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PgDN6/btrrqnuf5DJ/1qxGDiRsCrMcZEHmLKO5Ak/img.gif&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;1682&quot; data-filename=&quot;화면 기록 2022-01-21 오후 6.20.00.gif&quot; data-is-animation=&quot;true&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/PgDN6/btrrqnuf5DJ/1qxGDiRsCrMcZEHmLKO5Ak/img.gif&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼은 TouchableOpcity로 구현했으며 키보드를 제외한 영역을 터치했을 시 키보드가 자동으로 사라지기 위해 TouchableWithoutFeedback을 사용해서 onPress에 Keyboard.dismiss() 함수를 넣어줬다. 그러나 TouchableWithoutFeedback를 사용했을 때 버튼의 onPress가 작동하지 않는 것을 확인하여 다시 삭제했다. 키보드가 자동으로 내려가는 것을 대체할 코드가 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;after.gif&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;1682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0FdmU/btrrn1GTk9S/rsJiHWuCHLbGKVKqio8uDK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0FdmU/btrrn1GTk9S/rsJiHWuCHLbGKVKqio8uDK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0FdmU/btrrn1GTk9S/rsJiHWuCHLbGKVKqio8uDK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/0FdmU/btrrn1GTk9S/rsJiHWuCHLbGKVKqio8uDK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2080&quot; height=&quot;1682&quot; data-filename=&quot;after.gif&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;1682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전화번호 본인 인증을 어떻게 구현하는지 감이 오질 않아 간단한 서칭을 진행했다. 대부분 Firebase를 통해 구현하는 것으로 강좌들과 자료들이 나왔는데 백엔드 친구에게 물어본 결과 백에서 파베 안써도 충분히 구현가능하다고 한다. 하단 사진은 구현 로직에 대해 잘 정리가 되어있어 첨부한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;1652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMnsna/btrruBFVs0o/bynvoT87hOMYcnmdqt9qF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMnsna/btrruBFVs0o/bynvoT87hOMYcnmdqt9qF0/img.png&quot; data-alt=&quot;출처 : https://zih0.tistory.com/3&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMnsna/btrruBFVs0o/bynvoT87hOMYcnmdqt9qF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMnsna%2FbtrruBFVs0o%2FbynvoT87hOMYcnmdqt9qF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;725&quot; height=&quot;823&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;1652&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://zih0.tistory.com/3&amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면,&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프론트에서 전화번호를 백으로 .post 전송&lt;/li&gt;
&lt;li&gt;백에서 작업 처리 후, 프론트는 난수 .get&lt;/li&gt;
&lt;li&gt;받은 난수와 사용자가 입력할 난수(인증번호) 비교.&lt;/li&gt;
&lt;li&gt;비교 결과, 동일하다면 본인인증 확인하고 인증 여부를 백으로 .post 전송&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전화번호 본인인증 구현에 대한 정리는 여기까지 하고 백엔드 친구와 작업이 이루어진 다음에 마저 추가할 계획이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UrxkP/btrrumB2XB1/7kVUL2b7d9mMEfWogbsKt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UrxkP/btrrumB2XB1/7kVUL2b7d9mMEfWogbsKt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UrxkP/btrrumB2XB1/7kVUL2b7d9mMEfWogbsKt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUrxkP%2FbtrrumB2XB1%2F7kVUL2b7d9mMEfWogbsKt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@josworks27/%ED%9C%B4%EB%8C%80%ED%8F%B0-%EC%9D%B8%EC%A6%9D-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@josworks27/%ED%9C%B4%EB%8C%80%ED%8F%B0-%EC%9D%B8%EC%A6%9D-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1642838138387&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;휴대폰 인증 로직 구현하기&quot; data-og-description=&quot;이번 로그에서는 허위유저의 가입을 방지하기 위해 회원가입을 위한 절차 중 SMS를 이용한 휴대폰 인증 기능을 구현한 것에 대해 다뤄보고자 한다. 휴대폰 인증 개요 부동산 앱의 특성상 개인정&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@josworks27/%ED%9C%B4%EB%8C%80%ED%8F%B0-%EC%9D%B8%EC%A6%9D-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0&quot; data-og-url=&quot;https://velog.io/@josworks27/휴대폰-인증-로직-구현하기&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bOG0jv/hyNazUzr8A/gWKnEOAnHR5jAabkd5w7qk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500&quot;&gt;&lt;a href=&quot;https://velog.io/@josworks27/%ED%9C%B4%EB%8C%80%ED%8F%B0-%EC%9D%B8%EC%A6%9D-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@josworks27/%ED%9C%B4%EB%8C%80%ED%8F%B0-%EC%9D%B8%EC%A6%9D-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bOG0jv/hyNazUzr8A/gWKnEOAnHR5jAabkd5w7qk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;휴대폰 인증 로직 구현하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 로그에서는 허위유저의 가입을 방지하기 위해 회원가입을 위한 절차 중 SMS를 이용한 휴대폰 인증 기능을 구현한 것에 대해 다뤄보고자 한다. 휴대폰 인증 개요 부동산 앱의 특성상 개인정&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//참고 블로그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 매일 같이 조회수가 나오는 게시글인데,,,, 업무에 허덕이느라 구현, 정리를 못하고 있어요,,ㅠㅠ&lt;/p&gt;</description>
      <category>Frontend/Auth</category>
      <category>0biglife</category>
      <category>react native</category>
      <category>TWILIO</category>
      <category>전화번호 인증</category>
      <category>휴대폰 인증</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/25</guid>
      <comments>https://0biglife.tistory.com/entry/%ED%9C%B4%EB%8C%80%ED%8F%B0-%EC%9D%B8%EC%A6%9D#entry25comment</comments>
      <pubDate>Thu, 20 Jan 2022 23:37:57 +0900</pubDate>
    </item>
    <item>
      <title>앱 권한에 대한 것 / react-native-permissions</title>
      <link>https://0biglife.tistory.com/entry/%EC%95%B1-%EA%B6%8C%ED%95%9C%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B2%83</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhw02E/btrq7H2zqG6/GIBzkNkSZhT4Y7SnxWB7tk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhw02E/btrq7H2zqG6/GIBzkNkSZhT4Y7SnxWB7tk/img.jpg&quot; data-alt=&quot;앱 권한 정리 화이팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhw02E/btrq7H2zqG6/GIBzkNkSZhT4Y7SnxWB7tk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhw02E%2Fbtrq7H2zqG6%2FGIBzkNkSZhT4Y7SnxWB7tk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;433&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;앱 권한 정리 화이팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bh1ity/btrq8tJMJIa/vGhqcw2WGdQKkab4UwnAHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bh1ity/btrq8tJMJIa/vGhqcw2WGdQKkab4UwnAHk/img.png&quot; data-alt=&quot;출처 : https://ichi.pro/assets/images/max/724/1*eUWhCErkv35_fl3HdJiZRw.png&amp;amp;amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bh1ity/btrq8tJMJIa/vGhqcw2WGdQKkab4UwnAHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbh1ity%2Fbtrq8tJMJIa%2FvGhqcw2WGdQKkab4UwnAHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;231&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://ichi.pro/assets/images/max/724/1*eUWhCErkv35_fl3HdJiZRw.png&amp;amp;amp;amp;amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앱 권한은 사용자가 특정 화면에서 이용할 수 있는 서비스에 대한 권한을 의미한다. 앱 권한은 사용자가 앱에 정보를 입력하면서 모든 권한에 대한 허용 메세지를 띄우거나, 특정 서비스를 직접 접근했을 때 메세지를 띄우기도 한다. 필자가 보기에는 카메라/앨범/위치 추적 관련 서비스를 이용하기 직전에 권한 허용 메세지를 띄우는 것이 합리적으로 보이나 최근에 살펴본 여러 앱에서는 회원가입할 때 모든 권한에 대한 메세지를 연달아 띄우는 것도 보았다. 어느 것이 더 효율적이냐는 사용자 입장에서 굉장히 세부적인 차이로 어느게 더 편리하고 접근성이 좋은지에 따라 결정하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4BNZK/btrrbUf1w1k/ICmK40FgFK20aWKKJyuOh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4BNZK/btrrbUf1w1k/ICmK40FgFK20aWKKJyuOh1/img.png&quot; data-alt=&quot;출처 : https://ichi.pro/assets/images/max/724/1*AMWm4LSY0XXx1N1tFXXEIA.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4BNZK/btrrbUf1w1k/ICmK40FgFK20aWKKJyuOh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4BNZK%2FbtrrbUf1w1k%2FICmK40FgFK20aWKKJyuOh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;724&quot; height=&quot;443&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://ichi.pro/assets/images/max/724/1*AMWm4LSY0XXx1N1tFXXEIA.png&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앱 권한에 대한 구현을 다루기 앞서, 사용자의 대응 방식에 대해서도 생각해볼 필요가 있다. 위 패턴은 사용자가 앱 권한에 대해 허용하지 않거나, 허용한 이후에 디바이스 설정에서 권한을 취소했을 시에 발생하는 패턴이다. 앱 권한 우리 앱 외부에서의 설정을 위해 사용자를 '디바이스 설정'으로 유도해야하며, 이 경우&amp;nbsp;&lt;a href=&quot;https://reactnative.dev/docs/linking&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Linking API&lt;/a&gt; 를 활용하여 하단 코드처럼 작성할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1642596867247&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { TouchableOpacity, Linking } from 'react-native'
const Component = props =&amp;gt; {
return (
    &amp;lt;TouchableOpacity
      activeOpacity={0.8}
      onPress={() =&amp;gt; Linking.openURL('app-settings:')}
    &amp;gt;
      &amp;lt;SettingsIcon /&amp;gt;
   &amp;lt;/TouchableOpacity&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 실제 권한들을 어떻게 제어할지 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 권한을 다루기 위해 permission 관련 다운로드수가 가장 많은 '&lt;a href=&quot;https://github.com/zoontek/react-native-permissions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;react-native-permissions&lt;/a&gt;'을 적용했다.&lt;/p&gt;
&lt;figure id=&quot;og_1642597945949&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - zoontek/react-native-permissions: An unified permissions API for React Native on iOS and Android&quot; data-og-description=&quot;An unified permissions API for React Native on iOS and Android - GitHub - zoontek/react-native-permissions: An unified permissions API for React Native on iOS and Android&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/zoontek/react-native-permissions&quot; data-og-url=&quot;https://github.com/zoontek/react-native-permissions&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bsi7iq/hyM9kbthT8/SWUF0WpJXnv8oKieR0ru3K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/zoontek/react-native-permissions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/zoontek/react-native-permissions&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bsi7iq/hyM9kbthT8/SWUF0WpJXnv8oKieR0ru3K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - zoontek/react-native-permissions: An unified permissions API for React Native on iOS and Android&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An unified permissions API for React Native on iOS and Android - GitHub - zoontek/react-native-permissions: An unified permissions API for React Native on iOS and Android&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Installation&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1642599475160&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm install --save react-native-permissions
# --- or ---
$ yarn add react-native-permissions&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치를 마치고 생각해봐야할 것은 권한을 서비스 이용이 직접적으로 필요할 시 메세지를 띄울 것인지, 아니면 회원가입시 모든 권한에 대한 메세지를 한꺼번에 꺼낼 것인지이다. 후자의 경우 더 디벨롭된 케이스로는 메세지를 띄우지 않고 사용자가 버튼만 눌렀을 때, 모든 권한이 자동으로 업로드되는 것이다.(스푼 라디오가 이와 같이 구현되어있는 상태) 필자는 우선 앱 권한에 대한 설명과 버튼을 담은 UI부터 작성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xib8i/btrrqZfWV2i/uf1qYlqhVuDkdY3TQLIUK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xib8i/btrrqZfWV2i/uf1qYlqhVuDkdY3TQLIUK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xib8i/btrrqZfWV2i/uf1qYlqhVuDkdY3TQLIUK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXib8i%2FbtrrqZfWV2i%2Fuf1qYlqhVuDkdY3TQLIUK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2650&quot; height=&quot;1842&quot; data-origin-width=&quot;2650&quot; data-origin-height=&quot;1842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Code.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1724&quot; data-origin-height=&quot;1608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVYlV4/btrrqm3L3N9/cGR8dKvlY2aVVflnv1QR0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVYlV4/btrrqm3L3N9/cGR8dKvlY2aVVflnv1QR0k/img.png&quot; data-alt=&quot;https://github.com/zoontek/react-native-permissions&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVYlV4/btrrqm3L3N9/cGR8dKvlY2aVVflnv1QR0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVYlV4%2Fbtrrqm3L3N9%2FcGR8dKvlY2aVVflnv1QR0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1724&quot; height=&quot;1608&quot; data-origin-width=&quot;1724&quot; data-origin-height=&quot;1608&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/zoontek/react-native-permissions&amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무나도 다양한 권한들이 존재하는데 이 중에서 연락처 접근, 마이크 접근, 갤러리/카메라 접근을 적용했다. 적용 방식은 생각보다 간단하다. request와 check 두 방식으로 분류되는데 요청의 경우에는 메세지를 띄우는 기능이고, check는 특정 권한의 상태를 확인하여 리턴한다. check의 경우에는 useEffect()를 활용하여 특정 화면이 켜지자마자 서비스를 위한 필요한 권한들이 잘 업로드되었는지 확인하는 용도로 쓰이면 유용해보인다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1642827342337&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//권한 요청
const requestPermission = () =&amp;gt; {
    request(PERMISSIONS.IOS.CAMERA).then(response =&amp;gt; {
      console.log(response);
    });
  };

//허용된 권한에 대한 체킹 + logging
  const checkPermission = () =&amp;gt; {//checkMultiple을 쓸거면 이 부분 생략 가능
    check(PERMISSIONS.IOS.CAMERA)
      .then(result =&amp;gt; {
        switch (result) {
          case RESULTS.UNAVAILABLE:
            console.log(
              'This feature is not available (on this device / in this context)',
            );
            break;
          case RESULTS.DENIED:
            console.log(
              'The permission has not been requested / is denied but requestable',
            );
            break;
          case RESULTS.LIMITED:
            console.log('The permission is limited: some actions are possible');
            break;
          case RESULTS.GRANTED:
            console.log('The permission is granted');
            break;
          case RESULTS.BLOCKED:
            console.log('The permission is denied and not requestable anymore');
            break;
        }
      })
      .catch(error =&amp;gt; {
        console.log('PERMISSION ERROR : ', error);
      });
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한이 여러 개 필요하지 않는다면 각각 함수를 만들어 개별 코드로 제어할 수 있으나, 필자는 requestMultiple을 활용하여 한꺼번에 요청할 계획이다. 최종 코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1642827659089&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const requestMultiplePermissions = () =&amp;gt; {
    requestMultiple([
      PERMISSIONS.IOS.CONTACTS,
      PERMISSIONS.IOS.CAMERA,
      PERMISSIONS.IOS.PHOTO_LIBRARY,
      PERMISSIONS.IOS.PHOTO_LIBRARY_ADD_ONLY,
      PERMISSIONS.IOS.MICROPHONE,
    ]).then(response =&amp;gt; {
      console.log('MULTIPLE REQUEST RESPONSE : ', response);
    });
  };

  const checkMultiplePermissions = () =&amp;gt; {
    checkMultiple([
      PERMISSIONS.IOS.CONTACTS,
      PERMISSIONS.IOS.CAMERA,
      PERMISSIONS.IOS.PHOTO_LIBRARY,
      PERMISSIONS.IOS.PHOTO_LIBRARY_ADD_ONLY,
      PERMISSIONS.IOS.MICROPHONE,
    ]).then(response =&amp;gt; {
      console.log('MULTIPLE CHECK RESPONSE : ', response);
    });
  };

  return (
    &amp;lt;Container&amp;gt;
      &amp;lt;TitleContainer&amp;gt;
        &amp;lt;Title&amp;gt;접근 권한 승인&amp;lt;/Title&amp;gt;
        &amp;lt;SubTitle&amp;gt;해당 서비스 이용을 위한 접근 권한을 허용합니다.&amp;lt;/SubTitle&amp;gt;
        &amp;lt;SubscriptionWrapper&amp;gt;
          &amp;lt;SubscriptionView&amp;gt;
            &amp;lt;IonIcon name=&quot;phone-portrait&quot; size={22} color=&quot;gray&quot; /&amp;gt;
            &amp;lt;SubScriptionText&amp;gt;연락처 접근 권한을 허용합니다.&amp;lt;/SubScriptionText&amp;gt;
          &amp;lt;/SubscriptionView&amp;gt;
          &amp;lt;SubscriptionView&amp;gt;
            &amp;lt;IonIcon name=&quot;camera&quot; size={22} color=&quot;gray&quot; /&amp;gt;
            &amp;lt;SubScriptionText&amp;gt;카메라 접근 권한을 허용합니다.&amp;lt;/SubScriptionText&amp;gt;
          &amp;lt;/SubscriptionView&amp;gt;
          &amp;lt;SubscriptionView&amp;gt;
            &amp;lt;IonIcon name=&quot;book&quot; size={22} color=&quot;gray&quot; /&amp;gt;
            &amp;lt;SubScriptionText&amp;gt;갤러리 접근 권한을 허용합니다.&amp;lt;/SubScriptionText&amp;gt;
          &amp;lt;/SubscriptionView&amp;gt;
          &amp;lt;SubscriptionView&amp;gt;
            &amp;lt;IonIcon name=&quot;mic&quot; size={22} color=&quot;gray&quot; /&amp;gt;
            &amp;lt;SubScriptionText&amp;gt;마이크 접근 권한을 허용합니다.&amp;lt;/SubScriptionText&amp;gt;
          &amp;lt;/SubscriptionView&amp;gt;
          &amp;lt;ButtonContainer onPress={() =&amp;gt; requestMultiplePermissions()}&amp;gt; //권한 요청
            &amp;lt;ButtonText&amp;gt;권한 허용&amp;lt;/ButtonText&amp;gt;
          &amp;lt;/ButtonContainer&amp;gt;
        &amp;lt;/SubscriptionWrapper&amp;gt;
      &amp;lt;/TitleContainer&amp;gt;
    &amp;lt;/Container&amp;gt;
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물 !&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3242343.gif&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;1682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mQSRQ/btrrsngOSVM/p3ao3gWdnh3Y4kDYHGXhU0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mQSRQ/btrrsngOSVM/p3ao3gWdnh3Y4kDYHGXhU0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mQSRQ/btrrsngOSVM/p3ao3gWdnh3Y4kDYHGXhU0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/mQSRQ/btrrsngOSVM/p3ao3gWdnh3Y4kDYHGXhU0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2080&quot; height=&quot;1682&quot; data-filename=&quot;3242343.gif&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;1682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음.. 사용자가 만약에 '설정'에 들어가서 앱 권한을 직접 해제했을 때를 대비하여, 권한에 대한 서비스 이용이 필요할 때 위에서 말한 것과 같이 useEffect()를 활용한 라이프사이클 안에서 권한에 대한 checking을 해야할 것 같고, 해제된 권한이 확인될 시 메세지를 다시 띄워야 할 것 같다. 추가로, 메세지를 띄우지 않고 모든 권한을 허용하는 방식도 추후 적용해볼 예정이다.&lt;/p&gt;</description>
      <category>Frontend/Auth</category>
      <category>0biglife</category>
      <category>react native</category>
      <category>React Native Permissions</category>
      <category>react-native-permissions</category>
      <category>갤러리접근</category>
      <category>마이크접근</category>
      <category>앱 권한</category>
      <category>연락처 접근</category>
      <category>카메라 접근</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/24</guid>
      <comments>https://0biglife.tistory.com/entry/%EC%95%B1-%EA%B6%8C%ED%95%9C%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B2%83#entry24comment</comments>
      <pubDate>Wed, 19 Jan 2022 20:25:14 +0900</pubDate>
    </item>
    <item>
      <title>사용자 유입에 대한 플로우 정리</title>
      <link>https://0biglife.tistory.com/entry/%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9C%A0%EC%9E%85%EC%97%90-%EB%8C%80%ED%95%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGYmn5/btrrdc031lN/7plkSOcG6vuVI5Z9qoKca1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGYmn5/btrrdc031lN/7plkSOcG6vuVI5Z9qoKca1/img.png&quot; data-alt=&quot;플로우 정리 화이팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGYmn5/btrrdc031lN/7plkSOcG6vuVI5Z9qoKca1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGYmn5%2Fbtrrdc031lN%2F7plkSOcG6vuVI5Z9qoKca1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;345&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;플로우 정리 화이팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 권한에 대해 정리를 해볼 것이다. 최근 번아웃이 오면서 개발 공부에 대한 정리를 조금씩 미뤘었는데 다시 키(&lt;span style=&quot;background-color: #ffffff; color: #222222;&quot;&gt;舵&lt;/span&gt;)를 잡아보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 사용자가 어플리케이션 내부로 들어와서 서비스를 이용하기 전에 등록해야하는 로그인/회원가입의 로직부터 정리할 필요가 있다. 필요한 내용을 다음과 같이 리스트업을 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;소셜 로그인 방식(Google/Apple)&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 정보 처리&lt;/li&gt;
&lt;li&gt;토큰 처리(Local Storage 저장)&lt;/li&gt;
&lt;li&gt;SignIn/SignOut&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;회원가입시 필요한 로직&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;휴대폰 인증 구현&lt;/li&gt;
&lt;li&gt;필요한 정보 정리(이메일, 이름, 지역, 나이 ~ )&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;앱 권한&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번은 처음 공부했을 때와는 달리 대략적은 틀과 구현 방법에 대해서는 알고 있기 때문에 큰 걱정은 없다. 가장 먼저 소셜 로그인을 통해 받아온 유저정보/토큰 중에서 필요한 정보를 storage에 저장한 뒤, 백단에 넘겨주면 백엔드 개발자가 해당 유저가 우리 서비스에 가입된 유저인지 아닌지에 대한 판별을 나에게 다시 남겨줄 것이다. 이를 분류하여 '만약 해당 유저가 우리 서비스에 가입이 되어있다면' 바로 홈뷰로 넘겨주고, 그렇지 않다면 회원가입뷰로 넘겨줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2-2번의 경우 정보에 대한 분류가 픽스가 된다면 Typescript 상에서 Type 지정만 따로 클래스에서 export 해주고 API 통신의 post를 통해 날려주면 될 것이다. 휴대폰 인증과 앱 권한에 대한 것은 스터디가 필요하다. 카메라/앨범 접근 허용에 대한 권한을 카메라/앨범이 직접적으로 필요할 시에 모달을 날려주는 것이 합리적으로 보이지만 이 부분은 조금 더 스터디가 필요하며, 그 외 위치나 마이크 등과 같은 권한을 회원가입 로직의 초반에 넣을 것인지, 후반에 넣을 것인지도 공부가 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;따라서, 사용자가 앱을 실행시키고나서부터 홈뷰까지 도달하기의 플로우는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splash Screen&amp;nbsp; &amp;nbsp;▶&amp;nbsp; &amp;nbsp;(소셜)로그인&amp;nbsp; &amp;nbsp;▶&amp;nbsp; &amp;nbsp;회원가입뷰(또는 홈뷰)&amp;nbsp; &amp;nbsp;▶&amp;nbsp; &amp;nbsp;휴대폰 인증&amp;nbsp; &amp;nbsp;▶&amp;nbsp; &amp;nbsp;필수 정보 입력&amp;nbsp; &amp;nbsp;▶&amp;nbsp; &amp;nbsp;접근 권한 확인&amp;nbsp; &amp;nbsp;▶&amp;nbsp; &amp;nbsp;홈뷰&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NEXT) &lt;/b&gt;&lt;a href=&quot;https://0biglife.tistory.com/24&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;앱 권한 정리&lt;/a&gt;&lt;/p&gt;</description>
      <category>Frontend/Auth</category>
      <category>0biglife</category>
      <category>react native</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/23</guid>
      <comments>https://0biglife.tistory.com/entry/%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9C%A0%EC%9E%85%EC%97%90-%EB%8C%80%ED%95%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%A0%95%EB%A6%AC#entry23comment</comments>
      <pubDate>Wed, 19 Jan 2022 20:12:33 +0900</pubDate>
    </item>
    <item>
      <title>애플 소셜 로그인(1) / @invertase/react-native-apple-authentication</title>
      <link>https://0biglife.tistory.com/entry/%EC%95%A0%ED%94%8C-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B81-invertasereact-native-apple-authentication</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 애플 소셜 로그인 기능에 대해 구현해보려고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2020년 04월 Apple 신규 가이드라인에 따르면, 소셜 로그인 서비스를 사용하는 앱은 필수적으로 Apple 로그인 역시 제공해야한다고 한다. 그렇기 때문에 기존에 구현했던 구글에 애플 버튼도 추가하여 로그인 서비스를 구현해야 하고, 추가적으로는 이를 백단에 어떤 분기점을 준 상태로 넘겨줘야 하는지도 고민해봐야할 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LDKsI/btrq8Jlim7e/d7Ghmr3n5kOBtAL7z4gLk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LDKsI/btrq8Jlim7e/d7Ghmr3n5kOBtAL7z4gLk1/img.png&quot; data-alt=&quot;https://developer.apple.com/kr/app-store/review/guidelines/#sign-in-with-apple&amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LDKsI/btrq8Jlim7e/d7Ghmr3n5kOBtAL7z4gLk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLDKsI%2Fbtrq8Jlim7e%2Fd7Ghmr3n5kOBtAL7z4gLk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1632&quot; height=&quot;794&quot; data-origin-width=&quot;1632&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://developer.apple.com/kr/app-store/review/guidelines/#sign-in-with-apple&amp;amp;amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(애플 개발자 계정(&lt;a href=&quot;https://developer.apple.com/account&quot;&gt;Apple Developer&lt;/a&gt;)에서 내 계정에 'Sign In With Apple' 기능을 추가하여 Xcode 상에 적용하는 과정은 생략하겠다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Apple Social Login&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플 로그인은 iOS 디바이스에만 구현이 될 것이며, 의존성 라이브러리는 다음을 사용할 계획이다. (&lt;a href=&quot;https://github.com/invertase/react-native-apple-authentication&quot;&gt;https://github.com/invertase/react-native-apple-authentication&lt;/a&gt;)&lt;/p&gt;
&lt;figure id=&quot;og_1642577518871&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - invertase/react-native-apple-authentication: A React Native library providing support for Apple Authentication on iOS a&quot; data-og-description=&quot;A React Native library providing support for Apple Authentication on iOS and Android. - GitHub - invertase/react-native-apple-authentication: A React Native library providing support for Apple Auth...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/invertase/react-native-apple-authentication&quot; data-og-url=&quot;https://github.com/invertase/react-native-apple-authentication&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c4ORuZ/hyM71dmH7I/UasdywTdkJ8mzRQs0fy0C0/img.png?width=1998&amp;amp;height=1750&amp;amp;face=0_0_1998_1750&quot;&gt;&lt;a href=&quot;https://github.com/invertase/react-native-apple-authentication&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/invertase/react-native-apple-authentication&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c4ORuZ/hyM71dmH7I/UasdywTdkJ8mzRQs0fy0C0/img.png?width=1998&amp;amp;height=1750&amp;amp;face=0_0_1998_1750');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - invertase/react-native-apple-authentication: A React Native library providing support for Apple Authentication on iOS a&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A React Native library providing support for Apple Authentication on iOS and Android. - GitHub - invertase/react-native-apple-authentication: A React Native library providing support for Apple Auth...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;1436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F9v6H/btrrcNfIVCZ/ek9pilmYelXGSlOnWoz4sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F9v6H/btrrcNfIVCZ/ek9pilmYelXGSlOnWoz4sK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F9v6H/btrrcNfIVCZ/ek9pilmYelXGSlOnWoz4sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF9v6H%2FbtrrcNfIVCZ%2Fek9pilmYelXGSlOnWoz4sK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1546&quot; height=&quot;1436&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;1436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Installation&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 과정은 늘 진행하듯이 yarn add ~ 와 pod install 로 완료해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1642577592446&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @invertase/react-native-apple-authentication

(cd ios &amp;amp;&amp;amp; pod install)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr2tmC/btrq8JFCtBZ/m9PiVhNgmYK9r9kBPx80T1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr2tmC/btrq8JFCtBZ/m9PiVhNgmYK9r9kBPx80T1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr2tmC/btrq8JFCtBZ/m9PiVhNgmYK9r9kBPx80T1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr2tmC%2Fbtrq8JFCtBZ%2Fm9PiVhNgmYK9r9kBPx80T1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1738&quot; height=&quot;634&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로, Apple Login Token은 JWT(Json Web Token) 기반이기 때문에 받아온 identityToken을 디코딩해주기 위해 필요한 라이브러리로서 '&lt;a href=&quot;https://www.npmjs.com/package/jwt-decode&quot;&gt;jwt-decode&lt;/a&gt;'를 설치해줬다.&lt;/p&gt;
&lt;pre id=&quot;code_1642577980571&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install jwt-decode&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Code.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1642578061192&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;
import appleAuth, {
  AppleButton,
} from '@invertase/react-native-apple-authentication';

/*
...
*/
const AppleSignIn = async () =&amp;gt; {
  const appleAuthRequestResponse = await appleAuth.performRequest({
    // performs login request
    requestedOperation: appleAuth.Operation.LOGIN,
    requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
  });
  //get user auth
  const credentialState = await appleAuth.getCredentialStateForUser(
    appleAuthRequestResponse.user,
  );
  if (credentialState === appleAuth.State.AUTHORIZED) {
    console.log('Apple Login Test');
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 로직에 대한 함수를 불러오는 방식은 생각보다 간단하다. 그리고 AppleButton을 배치해줬다.&lt;/p&gt;
&lt;pre id=&quot;code_1642578229060&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return (
    &amp;lt;Container&amp;gt;
      &amp;lt;Title&amp;gt;Login View&amp;lt;/Title&amp;gt;
      &amp;lt;Button title=&quot;Sign In&quot; onPress={() =&amp;gt; navigation.navigate('SignUp')} /&amp;gt;
      &amp;lt;GoogleSigninButton
        style={{width: 192, height: 48}}
        size={GoogleSigninButton.Size.Wide}
        color={GoogleSigninButton.Color.Dark}
        onPress={GoogleSignIn}
      /&amp;gt;
      &amp;lt;AppleButton
        buttonStyle={AppleButton.Style.WHITE}
        buttonType={AppleButton.Type.SIGN_IN}
        style={{
          width: 160,
          height: 45,
        }}
        onPress={() =&amp;gt; Alert.alert('apple sign in test')}
      /&amp;gt;
    &amp;lt;/Container&amp;gt;
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;32423324.gif&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;1682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BCkmg/btrq59SHVKU/4A8YlNNmerMwgkLkqAX2M1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BCkmg/btrq59SHVKU/4A8YlNNmerMwgkLkqAX2M1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BCkmg/btrq59SHVKU/4A8YlNNmerMwgkLkqAX2M1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/BCkmg/btrq59SHVKU/4A8YlNNmerMwgkLkqAX2M1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2080&quot; height=&quot;1682&quot; data-filename=&quot;32423324.gif&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;1682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/Auth</category>
      <category>0biglife</category>
      <category>react native</category>
      <category>react-native-apple-authentication</category>
      <category>소셜로그인</category>
      <category>애플로그인</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/21</guid>
      <comments>https://0biglife.tistory.com/entry/%EC%95%A0%ED%94%8C-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B81-invertasereact-native-apple-authentication#entry21comment</comments>
      <pubDate>Wed, 19 Jan 2022 16:38:05 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] Cropping for Image Picker</title>
      <link>https://0biglife.tistory.com/entry/React-Native-Cropping-for-Image-Picker</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 적용했던 react-native-image-picker에 crop 기능이 필요하여 추가 구현을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 react-native-image-picker로는 도저히 cropping 기능을 포함시킬 수가 없어서 결국 의존성 라이브러리를 교체하였다. 교체된 라이브러리는 react-native-image-crop-picker이며 사실상 설치 및 구현 과정을 전과 유사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ useState 로 editProfileView 안에서 이미지 교체로 적용하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3243.gif&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;845&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC4EwC/btrquhIR5dA/UQxIFcexoP7hejKKGcyp9k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC4EwC/btrquhIR5dA/UQxIFcexoP7hejKKGcyp9k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC4EwC/btrquhIR5dA/UQxIFcexoP7hejKKGcyp9k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cC4EwC/btrquhIR5dA/UQxIFcexoP7hejKKGcyp9k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1275&quot; height=&quot;845&quot; data-filename=&quot;3243.gif&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;845&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Frontend/Component</category>
      <category>0biglife</category>
      <category>ImagePicker</category>
      <category>react native</category>
      <category>react-native-image-crop-picker</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/20</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-Cropping-for-Image-Picker#entry20comment</comments>
      <pubDate>Tue, 11 Jan 2022 15:28:33 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] Typescript에서 Navigation 적용하기</title>
      <link>https://0biglife.tistory.com/entry/React-Native-Typescript%EC%97%90%EC%84%9C-Navigation-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;화면 전환에 대해 공부를 하면서 Typescript 상에서의 네비게이터 설정이 생각보다 복잡하여 정리가 필요하다. 가장 먼저 Navigation을 사용하기 위하여 의존성 라이브러리 설치가 먼저 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1641449215778&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add react-native-gesture-handler react-navigation 
yarn add react-native-safe-area-context react-native-screens 
yarn add @react-native-community/masked-view
yarn add @react-navigation/native @react-navigation/stack&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Typescript를 사용하기 전에는 JavaScript로만 구현을 했기 때문에 타입형에 대한 부분이 추가될 필요가 없어서 비교적 구현이 간단했다. 어쩌면 Typescript를 적용하면서 에러가 좀 꼬인 듯도 하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;1598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drvni7/btrpYl6uWnl/tulTQYfu2znptROErquKXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drvni7/btrpYl6uWnl/tulTQYfu2znptROErquKXK/img.png&quot; data-alt=&quot;https://reactnavigation.org/docs/typescript/&amp;amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drvni7/btrpYl6uWnl/tulTQYfu2znptROErquKXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdrvni7%2FbtrpYl6uWnl%2FtulTQYfu2znptROErquKXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1506&quot; height=&quot;1598&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;1598&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://reactnavigation.org/docs/typescript/&amp;amp;amp;amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;문서에는 위와 같이 명시되어 있다. Navigation을 정의해줄 때에도 파라미터에 대한 타입형이 필요하고 이로 인해 이전 화면에서 업데이트 된 변수를 넘겨줄 때에도 타입형과 함께 넘겨줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1641449921913&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';

import {
  profileView,
  editProfileView,
} from '../screens';

const Stack = createStackNavigator();

const ProfileStack: React.FunctionComponent = () =&amp;gt; {
  return (
    &amp;lt;Stack.Navigator&amp;gt;
      &amp;lt;Stack.Screen
        name=&quot;Profile&quot;
        component={profileView}
        options={{
          headerShown: false,
        }}
      /&amp;gt;
      &amp;lt;Stack.Screen
        name=&quot;EditProfile&quot;
        component={editProfileView}
        options={{
          headerShown: false,
        }}
      /&amp;gt;
    &amp;lt;/Stack.Navigator&amp;gt;
  );
};

export default ProfileStack;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 바탕으로 위와 같이 기존에 작성되었던 코드를 변경했다.&lt;/p&gt;
&lt;pre id=&quot;code_1641450018815&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';

import {
  profileView,
  editProfileView,
  // modalScreen,
} from '../screens';

export enum ProfileScreens {
  Profile = 'Profile',
  EditProfile = 'EditProfile',
}

export type ProfileStackParamList = {
  Profile: undefined;
  EditProfile: undefined;
};

const Stack = createStackNavigator&amp;lt;ProfileStackParamList&amp;gt;();

const ProfileStack: React.FunctionComponent = () =&amp;gt; {
  return (
    &amp;lt;Stack.Navigator&amp;gt;
      &amp;lt;Stack.Screen
        name={ProfileScreens.Profile}
        component={profileView}
        options={{
          headerShown: false,
        }}
      /&amp;gt;
      &amp;lt;Stack.Screen
        name={ProfileScreens.EditProfile}
        component={editProfileView}
        options={{
          headerShown: false,
        }}
      /&amp;gt;
    &amp;lt;/Stack.Navigator&amp;gt;
  );
};

export default ProfileStack;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;지금 당장은 enum을 사용해서 Screen에 대한 세부적인 뷰들을 미리 열거한 다음 선언하는 방식이 조금 굳이..? 싶은 느낌인데 언젠가는 이 부분에 대한 효율성을 깨달으리라..&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;변경된 부분은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. enum을 통한 스크린 선언&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1641450298263&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export enum ProfileScreens {
  Profile = 'Profile',
  EditProfile = 'EditProfile',
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 스크린마다 필요한 파라미터 선언&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1641450328971&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export type ProfileStackParamList = {
  Profile: undefined; //필요한 파라미터가 없는 상태
  EditProfile: undefined;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. name에 string을 enum 형식으로 교체&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1641450438852&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const ProfileStack: React.FunctionComponent = ({navigation}) =&amp;gt; {
  return (
    &amp;lt;Stack.Navigator&amp;gt;
      &amp;lt;Stack.Screen
        name={ProfileScreens.Profile}  //&amp;lt;-- 이 부분!
        component={profileView}
        options={{
          headerShown: false,
        }}
      /&amp;gt;
      &amp;lt;Stack.Screen
        name={ProfileScreens.EditProfile} //&amp;lt;-- 이 부분!
        component={editProfileView} 
        options={{
          headerShown: false,
        }}
      /&amp;gt;
    &amp;lt;/Stack.Navigator&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/Navigation</category>
      <category>0biglife</category>
      <category>react native</category>
      <category>react-native-navigation</category>
      <category>TypeScript</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/19</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-Typescript%EC%97%90%EC%84%9C-Navigation-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-1#entry19comment</comments>
      <pubDate>Thu, 6 Jan 2022 15:07:48 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] Stick-Header 구현</title>
      <link>https://0biglife.tistory.com/entry/React-Native-iOS-%EC%8B%A4%EC%A0%9C-%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4%EC%97%90%EC%84%9C-%EC%B9%B4%EB%A9%94%EB%9D%BC-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0-1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;머리 아픈 sticky header 구현.. 화이팅&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로필 페이징 구현은 react-native materialTopTabNavigator 로도 안되고.. react-native-tab-views로도 안되니.. UI 하나하나 다 짠 다음에 TouchableOpacity로 감싸주고 버튼마다 페이징으로 넣어줘야하려나?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//추후 정리 예정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-01-03 오후 7.56.05.gif&quot; data-origin-width=&quot;2150&quot; data-origin-height=&quot;1696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKGhzI/btrpyj3u1cb/wOJVqF6P2GSzZHUXNMNAoK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKGhzI/btrpyj3u1cb/wOJVqF6P2GSzZHUXNMNAoK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKGhzI/btrpyj3u1cb/wOJVqF6P2GSzZHUXNMNAoK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cKGhzI/btrpyj3u1cb/wOJVqF6P2GSzZHUXNMNAoK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2150&quot; height=&quot;1696&quot; data-filename=&quot;화면 기록 2022-01-03 오후 7.56.05.gif&quot; data-origin-width=&quot;2150&quot; data-origin-height=&quot;1696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Frontend/Component</category>
      <category>0biglife</category>
      <category>ios</category>
      <category>ReactNative</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/17</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-iOS-%EC%8B%A4%EC%A0%9C-%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4%EC%97%90%EC%84%9C-%EC%B9%B4%EB%A9%94%EB%9D%BC-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0-1#entry17comment</comments>
      <pubDate>Mon, 3 Jan 2022 19:56:46 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] iOS 실제 디바이스에서 카메라 접근하기</title>
      <link>https://0biglife.tistory.com/entry/React-Native-iOS-%EC%8B%A4%EC%A0%9C-%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4%EC%97%90%EC%84%9C-%EC%B9%B4%EB%A9%94%EB%9D%BC-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다가 간혹 가상 시뮬레이터가 아닌 실제 디바이스에서 테스트를 해봐야 할 일이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 react-native-image-picker를 사용해서 카메라/앨범 접근 권한 및 파일 불러오는 기능을 구현해보는데 앨범 접근은 시뮬레이터에서 가능하지만 카메라는 실기기에서만 테스트가 가능하다는 것을 확인했다. 이를 위해 'react-native run-ios'을 통해 시뮬레이터 돌리던 것을 추가 명령어를 돌려줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 ios-deploy를 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1641018360093&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g ios-deploy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음, 추가 명령어는 간단하다. 'react-native run-ios' 뒤에 '--device '나의 device 이름' --configuration Release'만 추가해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1641018457272&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;react-native run-ios --device '0BigLife' --configuration Release&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Video_2022-01-01-15-56-36.gif&quot; data-origin-width=&quot;220&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byG7Nd/btrpoxucWar/6kKrRAvFGVqDzp9I4e3cek/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byG7Nd/btrpoxucWar/6kKrRAvFGVqDzp9I4e3cek/img.gif&quot; data-alt=&quot;폰화면 녹화 왜이렇게 화질이 떨어지지..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byG7Nd/btrpoxucWar/6kKrRAvFGVqDzp9I4e3cek/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/byG7Nd/btrpoxucWar/6kKrRAvFGVqDzp9I4e3cek/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;220&quot; height=&quot;480&quot; data-filename=&quot;KakaoTalk_Video_2022-01-01-15-56-36.gif&quot; data-origin-width=&quot;220&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;폰화면 녹화 왜이렇게 화질이 떨어지지..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;빌드 완료 시 구글 로그인 ▶️ 홈뷰 이동 ▶️ 모달 ▶️ 카메라/앨범 접근 플로우에는 문제가 없고, 갤러리도 문제없이 뜨는 것을 확인했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 카메라 촬영 버튼은 작동하지만 온통 검정 화면만 보이던 현상이 있었는데, 접근 권한을 허용으로 안누를 시(또는, 권한 모달이 나올 때 앱을 종료할 시) 발생하였고, 이 부분에 대한 추가 처리가 필요한지 알아봐야겠다.&lt;/p&gt;</description>
      <category>Frontend/Component</category>
      <category>0biglife</category>
      <category>ios</category>
      <category>ReactNative</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/16</guid>
      <comments>https://0biglife.tistory.com/entry/React-Native-iOS-%EC%8B%A4%EC%A0%9C-%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4%EC%97%90%EC%84%9C-%EC%B9%B4%EB%A9%94%EB%9D%BC-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0#entry16comment</comments>
      <pubDate>Sat, 1 Jan 2022 16:00:43 +0900</pubDate>
    </item>
    <item>
      <title>카메라/앨범 접근 권한(react-native-image-picker 활용)</title>
      <link>https://0biglife.tistory.com/entry/%EC%B9%B4%EB%A9%94%EB%9D%BC%EC%95%A8%EB%B2%94-%EC%A0%91%EA%B7%BC-%EA%B6%8C%ED%95%9Creact-native-image-picker-%ED%99%9C%EC%9A%A9</link>
      <description>&lt;pre id=&quot;code_1640938795639&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add react-native-image-picker

# RN &amp;gt;= 0.60
cd ios &amp;amp;&amp;amp; pod install

# RN &amp;lt; 0.60
react-native link react-native-image-picker&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;908&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clozUr/btrpvTaSw8z/lEKzmTvsfxmt4jjYVzdy60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clozUr/btrpvTaSw8z/lEKzmTvsfxmt4jjYVzdy60/img.png&quot; data-alt=&quot;Android 설치&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clozUr/btrpvTaSw8z/lEKzmTvsfxmt4jjYVzdy60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclozUr%2FbtrpvTaSw8z%2FlEKzmTvsfxmt4jjYVzdy60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1910&quot; height=&quot;908&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;908&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Android 설치&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DNpHX/btrpoXefAZr/lK0AYGaodC1vvkYhpfjAdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DNpHX/btrpoXefAZr/lK0AYGaodC1vvkYhpfjAdK/img.png&quot; data-alt=&quot;iOS 설치&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DNpHX/btrpoXefAZr/lK0AYGaodC1vvkYhpfjAdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDNpHX%2FbtrpoXefAZr%2FlK0AYGaodC1vvkYhpfjAdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1914&quot; height=&quot;914&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;iOS 설치&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Frontend/Component</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/15</guid>
      <comments>https://0biglife.tistory.com/entry/%EC%B9%B4%EB%A9%94%EB%9D%BC%EC%95%A8%EB%B2%94-%EC%A0%91%EA%B7%BC-%EA%B6%8C%ED%95%9Creact-native-image-picker-%ED%99%9C%EC%9A%A9#entry15comment</comments>
      <pubDate>Fri, 31 Dec 2021 17:37:55 +0900</pubDate>
    </item>
    <item>
      <title>ModalView</title>
      <link>https://0biglife.tistory.com/entry/ModalView</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ModalView 를 만들기에 앞서 라이브러리를 사용할지 아니면 react-native 에서 기본으로 제공해주는 기능을 활용할지 선택을 해야한다. 구현을 하면서 모달뷰만큼은 Swift가 조금은 그리웠..는데 RN에서는 홈뷰와 페이징이 쉬웠다면 swift에서는 모달뷰 전환과 네이게이션 제어가 간편한 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2292&quot; data-origin-height=&quot;1416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qfr0t/btrpcOJKqud/aFy6nfDsfdc4330Ey5bCuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qfr0t/btrpcOJKqud/aFy6nfDsfdc4330Ey5bCuk/img.png&quot; data-alt=&quot;https://openbase.com/js/react-native-modal&amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qfr0t/btrpcOJKqud/aFy6nfDsfdc4330Ey5bCuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQfr0t%2FbtrpcOJKqud%2FaFy6nfDsfdc4330Ey5bCuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2292&quot; height=&quot;1416&quot; data-origin-width=&quot;2292&quot; data-origin-height=&quot;1416&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://openbase.com/js/react-native-modal&amp;amp;amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1640851602061&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i react-native-modal 
// or
yarn add react-native-modal&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//추후 정리본 추가&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2021-12-31 오후 5.04.37.gif&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;1670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcJxZq/btrpvRxdI4J/MaBdWLHQkPNbAsbVKELgg1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcJxZq/btrpvRxdI4J/MaBdWLHQkPNbAsbVKELgg1/img.gif&quot; data-alt=&quot;app/components/ModalView.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcJxZq/btrpvRxdI4J/MaBdWLHQkPNbAsbVKELgg1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bcJxZq/btrpvRxdI4J/MaBdWLHQkPNbAsbVKELgg1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1936&quot; height=&quot;1670&quot; data-filename=&quot;화면 기록 2021-12-31 오후 5.04.37.gif&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;1670&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/components/ModalView.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/Component</category>
      <category>0biglife</category>
      <category>react-native-modal</category>
      <category>ReactNative</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/13</guid>
      <comments>https://0biglife.tistory.com/entry/ModalView#entry13comment</comments>
      <pubDate>Thu, 30 Dec 2021 17:00:15 +0900</pubDate>
    </item>
    <item>
      <title>AsyncStorage 활용 : token 저장</title>
      <link>https://0biglife.tistory.com/entry/%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-token-%EC%A0%80%EC%9E%A51</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfIBMX/btroVjQcBLV/oyzFfx3t5pX612zcANfjt0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfIBMX/btroVjQcBLV/oyzFfx3t5pX612zcANfjt0/img.jpg&quot; data-alt=&quot;토큰 스터디 화이팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfIBMX/btroVjQcBLV/oyzFfx3t5pX612zcANfjt0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfIBMX%2FbtroVjQcBLV%2FoyzFfx3t5pX612zcANfjt0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;토큰 스터디 화이팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 로그인 구현을 iOS, Android 모두 완료했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 로그인을 마치는 순간, 구글 서버로부터 유저 정보와 토큰이 날라올 것이고, 우리는 여기서 필요한 정보를 저장하여 백으로 보내줄 것이다. Swift를 공부할 때엔, 받아온 token을 KeychainWrapper를 활용하여 안전하게 보관을 하고 필요할 때마다 꺼내서 인터셉터를 통해 헤더에 얹어서 API를 쏴주었다. RN 스터디 순서는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰을 어떻게 저장할 것인지&lt;/li&gt;
&lt;li&gt;Axios 상에서 Interceptor는 어떻게 구현하는지&lt;/li&gt;
&lt;li&gt;헤더에 얹어서 token을 .post하였다면, refresh Token 갱신을 어떻게 하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Token 저장하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;보통 State와 같은 Component의 상태 관리를 위해서는 Redux를 사용한다. 하지만 우리는 AccessToken, RefreshToken과 같은 토큰 보관을 위해서는 앱이 꺼져도 써야하는 데이터 관리를 위해서 Local 데이터베이스에 저장해야한다. 이를 위해 이번 글에서는 &lt;b&gt;AsyncStorage&lt;/b&gt;를 공부하여 기록해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AsyncStorage를 사용하는 이유는..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기적(async)으로 데이터를 저장 또는 불러오며, 영구적으로 저장시킨다.&lt;/li&gt;
&lt;li&gt;앱이 다운되더라도 기존 저장된 변수 및 세팅 사항들이 보존된다.&lt;/li&gt;
&lt;li&gt;오프라인 데이터베이스 담당 라이브러리 중 monthly download 가 가장 높다 : )&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용을 위한 저장 / 불러오기는 setItem / getItem 으로 이루어지며 string 값으로 입력시켜야 한다. 따라서 JSON이나 ARRAY형을 저장할 때에 다소 불편함이 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640581679234&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const signIn = async () =&amp;gt; {
    try {
      await GoogleSignin.hasPlayServices();
      const userInfo = await GoogleSignin.signIn();
      const accessToken = (await GoogleSignin.getTokens()).accessToken;
      setUser(userInfo);
      console.log('due_______', userInfo);
      console.log('Access-Token_____ : ', accessToken);
    } catch (error) {
      console.log('MESSAGE', error.message);
      if (error.code === statusCodes.SIGN_IN_CANCELLED) {
        console.log('User Cancelled the Login Flow');
      } else if (error.code === statusCodes.IN_PROGRESS) {
        console.log('Signing In');
      } else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
        console.log('Play services Not Availbale');
      } else {
        console.log('Some other Error happened');
      }
    }
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 저장하기 앞서 현재 받아온 accessToken은 GoogleSignin.getTokens()를 통해 변수 accessToken 안에 넣어준 상태이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AsyncStorage 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 설명에 따라 설치부터 진행한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://github.com/react-native-async-storage/async-storage&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/react-native-async-storage/async-storage&lt;/a&gt;)&lt;/p&gt;
&lt;pre id=&quot;code_1640583158573&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @react-native-async-storage/async-storage

...

npx pod-install // cd ios &amp;amp;&amp;amp; pod install &amp;amp;&amp;amp; cd ..&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코드 작성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;-setItem : &lt;/b&gt;첫 번째 인자로 키 값을 주고, 두 번째 인자는 저장할 값을 준다&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640585751084&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;AsyncStorage.setItem('Key', 'String');
AsyncStorage.setItem('Num', 10 + '');

//store method
const storeData = async (value) =&amp;gt; {
  try {
    await AsyncStorage.setItem('@storage_Key', value)
  } catch (e) {
    // saving error
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-getItem : &lt;/b&gt;불러올 값의 키 값을 인자로 넣어준다. 단, 찾으려는 키 값이 없을 시 null을 리턴한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640585797991&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;AsyncStorage.getItem('Key');

//get method
const getData = async () =&amp;gt; {
  try {
    const value = await AsyncStorage.getItem('@storage_Key')
    if(value !== null) {
      // value previously stored
    }
  } catch(e) {
    // error reading value
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용된 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1640585862945&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const storeData = async (value: string) =&amp;gt; {
    try {
      await AsyncStorage.setItem('GoogleAccessToken', value);
      console.log('token saved successfully');
    } catch (e) {
      console.log('token saved error : Asynce Storage');
    }
  };

  const signIn = async () =&amp;gt; {
    try {
      await GoogleSignin.hasPlayServices();
      const userInfo = await GoogleSignin.signIn();
      const accessToken = (await GoogleSignin.getTokens()).accessToken;
      //google auth
      console.log('due_______', userInfo);
      console.log('Google Access Token : ', accessToken);
      setUser(userInfo);
      //token setting
      storeData(accessToken);
      //token getItem
      AsyncStorage.getItem('GoogleAccessToken').then(res =&amp;gt;
        console.log('Storage Token : ', res),
      );
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과, google server 를 통해 받아온 accesstoken과 Storage에 'GoogleAccessToken'의 키 값으로 입력된 값을 콘솔로 출력한 결과 동일하게 나오는 것을 확인했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnBqF/btro32foeTy/sX7ASXKnlJRk9jiaQzioU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnBqF/btro32foeTy/sX7ASXKnlJRk9jiaQzioU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnBqF/btro32foeTy/sX7ASXKnlJRk9jiaQzioU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtnBqF%2Fbtro32foeTy%2FsX7ASXKnlJRk9jiaQzioU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1176&quot; height=&quot;286&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/Axios</category>
      <category>0biglife</category>
      <category>AccessToken</category>
      <category>asyncstorage</category>
      <category>axios</category>
      <category>ReactNative</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/11</guid>
      <comments>https://0biglife.tistory.com/entry/%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-token-%EC%A0%80%EC%9E%A51#entry11comment</comments>
      <pubDate>Mon, 27 Dec 2021 13:19:17 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] axios.create 인스턴스 생성 및 .get 방식 구현</title>
      <link>https://0biglife.tistory.com/entry/Axios%EB%A1%9C-%ED%99%88%EB%B7%B0-Settingget</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Restful API 는 내가 개발이 재미있다고 느낀 첫 번째 관문이다. 화면 상에 시각적 요소들을 배치하는 것도 중요한 장치이지만 눈에 보이지 않는 서버 자원들을 화면으로 옮겨담는 것은 굉장히 흥미로운 일이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Swift의 Alamofire 라이브러리로 API의 개념을 처음 공부했을 때에는 그 개념을 이해하는 데에만 며칠이 걸렸던 것 같다. 문장을 읽어도 '이게 무슨 말이지?' 싶었고 API 개념, 구현뿐만 아니라 generic type과 @escaping, closure, json parsing, codable &amp;amp; decodable, 비동기(DispatchQueue.main.async), 싱글톤과 같이 얽히면서 서로 구현에 들어가는 것들에 대한 공부도 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그 결과,&amp;nbsp; 짧은 개발 독학 중에서 실제 구현해본 것은 .get 과 .post 이며, 홈뷰에서 Swift-Alamofire를 통해 구현했던 순서는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2034&quot; data-origin-height=&quot;1178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSifRR/btroT5xa2GX/QlA1qiuOBiMKkR7mK8ZhB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSifRR/btroT5xa2GX/QlA1qiuOBiMKkR7mK8ZhB0/img.png&quot; data-alt=&quot;Xcode ~ APIManager - getData()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSifRR/btroT5xa2GX/QlA1qiuOBiMKkR7mK8ZhB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSifRR%2FbtroT5xa2GX%2FQlA1qiuOBiMKkR7mK8ZhB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2034&quot; height=&quot;1178&quot; data-origin-width=&quot;2034&quot; data-origin-height=&quot;1178&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Xcode ~ APIManager - getData()&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;홈뷰가 나타나기 전(viewWillAppear)에서 HomeView에 뿌려질 데이터를 가져온다.&lt;/li&gt;
&lt;li&gt;데이터는 manager 라는 싱글톤으로 구현된 클래스에서 가져오며, 그 안에서 getData() 함수가 전달된다.&lt;/li&gt;
&lt;li&gt;getData()는 추후 다수의 model 형태의 데이터를 위해 제네릭 타입으로 선언된다.&lt;/li&gt;
&lt;li&gt;요청은 url, method, params를 담고 있으며 statusCode가 주어진 범위에 속할 때 json 형태로 값을 받아온다.&lt;/li&gt;
&lt;li&gt;받아온 response는 switch문을 통해 성공 시 비동기적으로 model 형태로 리턴되며, 실패시 에러를 출력하고 nil값을 리턴한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RN에서는 Fetch, Ajax, Axios 를 주로 쓴다고 하는데 장단점 비교 후 Axios를 선택했다.(비교에 대한 게시글을 추후에..)&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Axios 모듈화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;서버로부터 요청을 보낼 일은 갈수록 많아질 것이기 때문에 모듈화는 필수적이다. 스터디 결과, 필요시 Axios 라이브러리로부터 하나씩 설계하는 것보다 이미 설계된 모듈만 가져와서 method, path, param를 넣어주는 것이 효율적이다.(개별 폴더 안에 client 함수별로 묶어서 처리하면 더 효율적일까?) 따라서, apis라는 폴더를 만들어주고 하위폴더로 data Type를 담을 model 폴더와 manager 역할을 해줄 client 폴더를 생성했으며, &lt;b&gt;axios.create&lt;/b&gt;를 사용하여 axios 인스턴스를 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LDBmV/btroZD7u77d/EU0R2OaVqaJA1JWZTijqKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LDBmV/btroZD7u77d/EU0R2OaVqaJA1JWZTijqKk/img.png&quot; data-alt=&quot;app/apis/model/data.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LDBmV/btroZD7u77d/EU0R2OaVqaJA1JWZTijqKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLDBmV%2FbtroZD7u77d%2FEU0R2OaVqaJA1JWZTijqKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;982&quot; height=&quot;586&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/apis/model/data.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ef7BLc/btroRZLu0iQ/rcqRLurelR564UI38wFDYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ef7BLc/btroRZLu0iQ/rcqRLurelR564UI38wFDYK/img.png&quot; data-alt=&quot;app/apis/service/client.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ef7BLc/btroRZLu0iQ/rcqRLurelR564UI38wFDYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fef7BLc%2FbtroRZLu0iQ%2FrcqRLurelR564UI38wFDYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1164&quot; height=&quot;570&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/apis/service/client.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;생성된 apiClient 내부에는 추후 넣어줄 토큰을 포함시킬 헤더와 baseUrl, param 등이 담길 예정이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;1356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yDxsN/btroZEk2yVX/caAJeGZzPMP4kZFYr4iAk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yDxsN/btroZEk2yVX/caAJeGZzPMP4kZFYr4iAk1/img.png&quot; data-alt=&quot;app/screens/Home/homeView.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yDxsN/btroZEk2yVX/caAJeGZzPMP4kZFYr4iAk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyDxsN%2FbtroZEk2yVX%2FcaAJeGZzPMP4kZFYr4iAk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1286&quot; height=&quot;1356&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;1356&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/screens/Home/homeView.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 선언된 apiClient를 가져와서 홈뷰가 보여지기 전에 받아올 것이기 때문에 swift 상에서 viewWillAppear를 담당하는 useEffect 내부에서 구현한다. 형태는 get으로 가져오고 담아줄 Type형은 Photo이기 때문에 &amp;lt;Photo[]&amp;gt;로 선언 후, setPost를 통해 저장된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 posts에 저장된 데이터는 return 내부에서 FlatList 의 data로 들어가 화면에 뿌려지게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;1290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9xnmV/btroZEL8PF1/1Lu7hfPBh1c23yaxqkZwG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9xnmV/btroZEL8PF1/1Lu7hfPBh1c23yaxqkZwG0/img.png&quot; data-alt=&quot;app/components/PostCard.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9xnmV/btroZEL8PF1/1Lu7hfPBh1c23yaxqkZwG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9xnmV%2FbtroZEL8PF1%2F1Lu7hfPBh1c23yaxqkZwG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1316&quot; height=&quot;1290&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;1290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/components/PostCard.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props인 item을 통해 들어온 data는 필요한 데이터 네이밍에 따라 분류되어 UI를 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과물!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2021-12-26 오후 5.16.15.gif&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVJQlw/btroSsGDCtL/PkxLj1nLgivRRTwKtKVVj0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVJQlw/btroSsGDCtL/PkxLj1nLgivRRTwKtKVVj0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVJQlw/btroSsGDCtL/PkxLj1nLgivRRTwKtKVVj0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bVJQlw/btroSsGDCtL/PkxLj1nLgivRRTwKtKVVj0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1680&quot; data-filename=&quot;화면 기록 2021-12-26 오후 5.16.15.gif&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추후 보완점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; useEffect 쓰임에 따른 라이프사이클 스터디 정리&lt;/li&gt;
&lt;li&gt;&amp;nbsp;post 구현&lt;/li&gt;
&lt;li&gt;&amp;nbsp;client.tsx 상에서 외부 파일(ex: config.ts)을 통해 url이나 token 가져오는 것(추가 모듈화)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;▼ 포스팅 정리된 글 ▼&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1643688693020&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ReactNative] Axios로 게시물 Post하기&quot; data-og-description=&quot;이번에는 FlatList로 구현해놓은 홈뷰에 새로운 게시글을 넣어볼까 한다. 과정은 다음과 같다. HomeNavigation의 HeaderRight 버튼을 통해 모달뷰 접근 모달뷰에서 선택지를 골라서(카메라/앨범/--) UploadVie&quot; data-og-host=&quot;0biglife.tistory.com&quot; data-og-source-url=&quot;https://0biglife.tistory.com/entry/ReactNative-Axios%EB%A1%9C-%EA%B2%8C%EC%8B%9C%EB%AC%BC-Post%ED%95%98%EA%B8%B0&quot; data-og-url=&quot;https://0biglife.tistory.com/entry/ReactNative-Axios%EB%A1%9C-%EA%B2%8C%EC%8B%9C%EB%AC%BC-Post%ED%95%98%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/siaLM/hyNgnGRbX4/jykjXKKG2a3kOvDkckXsjk/img.gif?width=800&amp;amp;height=619&amp;amp;face=0_0_800_619,https://scrap.kakaocdn.net/dn/o5hef/hyNgiMiWIN/KYdVV0DC7Mj07uSJqk49AK/img.gif?width=800&amp;amp;height=619&amp;amp;face=0_0_800_619,https://scrap.kakaocdn.net/dn/c1AbU8/hyNgnmyfWk/5ZZHSxLVGEe7hUCENV8bD1/img.jpg?width=2000&amp;amp;height=2000&amp;amp;face=0_0_2000_2000&quot;&gt;&lt;a href=&quot;https://0biglife.tistory.com/entry/ReactNative-Axios%EB%A1%9C-%EA%B2%8C%EC%8B%9C%EB%AC%BC-Post%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://0biglife.tistory.com/entry/ReactNative-Axios%EB%A1%9C-%EA%B2%8C%EC%8B%9C%EB%AC%BC-Post%ED%95%98%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/siaLM/hyNgnGRbX4/jykjXKKG2a3kOvDkckXsjk/img.gif?width=800&amp;amp;height=619&amp;amp;face=0_0_800_619,https://scrap.kakaocdn.net/dn/o5hef/hyNgiMiWIN/KYdVV0DC7Mj07uSJqk49AK/img.gif?width=800&amp;amp;height=619&amp;amp;face=0_0_800_619,https://scrap.kakaocdn.net/dn/c1AbU8/hyNgnmyfWk/5ZZHSxLVGEe7hUCENV8bD1/img.jpg?width=2000&amp;amp;height=2000&amp;amp;face=0_0_2000_2000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ReactNative] Axios로 게시물 Post하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 FlatList로 구현해놓은 홈뷰에 새로운 게시글을 넣어볼까 한다. 과정은 다음과 같다. HomeNavigation의 HeaderRight 버튼을 통해 모달뷰 접근 모달뷰에서 선택지를 골라서(카메라/앨범/--) UploadVie&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;0biglife.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/Axios</category>
      <category>0biglife</category>
      <category>axios</category>
      <category>ReatNative</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/10</guid>
      <comments>https://0biglife.tistory.com/entry/Axios%EB%A1%9C-%ED%99%88%EB%B7%B0-Settingget#entry10comment</comments>
      <pubDate>Sun, 26 Dec 2021 17:03:34 +0900</pubDate>
    </item>
    <item>
      <title>구글 소셜 로그인(2) / @react-native-google-signin/google-signin</title>
      <link>https://0biglife.tistory.com/entry/react-native-google-signingoogle-signin-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%842</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;(1)글에서 언급했던 GoogleService-Info.plist 없이 시도한 것의 시작점은 Firebase 를 쓰면 안되기 때문에 당연히 GoogleService-Info.plist 없이 구현하는 방식으로 삽질을 했다. 우연히 Google Cloud Platform 을 보다가 아래 사진처럼 PLIST 다운로드를 우연히 발견했다.. 이걸 발견하지 못해서 몇 주간 구글 로그인 구현이 미뤄진게 말이 되나ㅠㅡㅠ.. 허무하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;1120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qhsFL/btroQbjIK9k/yDqYQHSAOLQKxuhJnkJjXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qhsFL/btroQbjIK9k/yDqYQHSAOLQKxuhJnkJjXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qhsFL/btroQbjIK9k/yDqYQHSAOLQKxuhJnkJjXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqhsFL%2FbtroQbjIK9k%2FyDqYQHSAOLQKxuhJnkJjXK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1680&quot; height=&quot;1120&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;1120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어찌됐건 다운받은 PLIST를 Xcode에 적용하고 URL Type을 재수정하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2788&quot; data-origin-height=&quot;892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brohli/btroPq22jL8/ei7ksBFUzUYSo5brtirAa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brohli/btroPq22jL8/ei7ksBFUzUYSo5brtirAa0/img.png&quot; data-alt=&quot;Xcode - GoogleService-Info.plist&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brohli/btroPq22jL8/ei7ksBFUzUYSo5brtirAa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrohli%2FbtroPq22jL8%2Fei7ksBFUzUYSo5brtirAa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2788&quot; height=&quot;892&quot; data-origin-width=&quot;2788&quot; data-origin-height=&quot;892&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Xcode - GoogleService-Info.plist&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1884&quot; data-origin-height=&quot;1686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfRQMU/btroOD9E5PQ/u0WT610zzyEsEekRhZacJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfRQMU/btroOD9E5PQ/u0WT610zzyEsEekRhZacJK/img.png&quot; data-alt=&quot;app/screens/Auth/login.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfRQMU/btroOD9E5PQ/u0WT610zzyEsEekRhZacJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfRQMU%2FbtroOD9E5PQ%2Fu0WT610zzyEsEekRhZacJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1884&quot; height=&quot;1686&quot; data-origin-width=&quot;1884&quot; data-origin-height=&quot;1686&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/screens/Auth/login.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 iosClientId와 webClientId 를 모두 세팅해주고 webClientId를 쓰기 때문에 당연히 offlineAccess도 true값을 넣어주었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6aV9n/btroPIh7sNI/yjHt3jUMqZnIRIkoQM9KB0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6aV9n/btroPIh7sNI/yjHt3jUMqZnIRIkoQM9KB0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6aV9n/btroPIh7sNI/yjHt3jUMqZnIRIkoQM9KB0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/6aV9n/btroPIh7sNI/yjHt3jUMqZnIRIkoQM9KB0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1962&quot; height=&quot;1680&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼이 정상적으로 작동하였고, 로그인이 완료되면 터미널 창에 구글 유저 정보를 포함한 idToken, scopes, serverAuthCode가 들어오는 것을 확인하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추후 기능구현&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;터미널 유저 정보 pretty 하게 변환&lt;/li&gt;
&lt;li&gt;토큰 저장 및 axios .post 구현&lt;/li&gt;
&lt;li&gt;SignIn 로직 중에서 어느 부분에 token post 할지 test&amp;nbsp;&lt;/li&gt;
&lt;li&gt;post한 한 후, 백에서 유저 여부 판별 후 회원가입뷰 또는 홈뷰로 화면 전환&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Frontend/Auth</category>
      <category>googlesignin</category>
      <category>ReactNative</category>
      <category>SocialLogin</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/9</guid>
      <comments>https://0biglife.tistory.com/entry/react-native-google-signingoogle-signin-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%842#entry9comment</comments>
      <pubDate>Fri, 24 Dec 2021 17:52:13 +0900</pubDate>
    </item>
    <item>
      <title>Error/react-native-pager-view</title>
      <link>https://0biglife.tistory.com/entry/Errorreact-native-pager-view</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;1650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lhfDf/btroJ5EKZHL/cYcUiUccBZFKP1EWurASWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lhfDf/btroJ5EKZHL/cYcUiUccBZFKP1EWurASWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lhfDf/btroJ5EKZHL/cYcUiUccBZFKP1EWurASWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlhfDf%2FbtroJ5EKZHL%2FcYcUiUccBZFKP1EWurASWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1868&quot; height=&quot;1650&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;1650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/69278465/unable-to-resolve-module-react-native-pager-view-react-native&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/69278465/unable-to-resolve-module-react-native-pager-view-react-native&lt;/a&gt;&lt;/p&gt;</description>
      <category>Error Record</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/7</guid>
      <comments>https://0biglife.tistory.com/entry/Errorreact-native-pager-view#entry7comment</comments>
      <pubDate>Fri, 24 Dec 2021 14:00:47 +0900</pubDate>
    </item>
    <item>
      <title>구글 소셜 로그인(1) / @react-native-google-signin/google-signin</title>
      <link>https://0biglife.tistory.com/entry/Google-SignIn-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%841</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;애증의 구글 로그인 구현..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 끈기를 테스트하는지 무한한 에러 덕분에 git commit 되돌리는 연습만 엄청 한 것 같다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift로 구현할 때는 설치와 UI 구현은 굉장히 쉬웠고 되려 받아온 정보를 Alamofire(Restful API Library)로 날리기 위해서 API 연습에 시간을 들였었는데 React Native 에서는 설치조차 다소 까다로운듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 반드시 구현할 각오로 시작해본다. 순서는 우선 iOS pod file 적용 후, GoogleSignIn Button 을 화면에 출력해본다. 여태 어려움을 겪던 부분은 GoogleService-info.plist 의 적용에 대한 것인데 RN 오픈 채팅방에 문의해본 결과 필수로 적용해야한다고 해서 이 부분도 적용을 한 후에 하나씩 천천히 구현해본다..plz&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2076&quot; data-origin-height=&quot;1680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cx91Yv/btroK1aJ8LN/ik7OPvG9UxoflyEyq4tHRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cx91Yv/btroK1aJ8LN/ik7OPvG9UxoflyEyq4tHRK/img.png&quot; data-alt=&quot;app/screens/Auth/login.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cx91Yv/btroK1aJ8LN/ik7OPvG9UxoflyEyq4tHRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcx91Yv%2FbtroK1aJ8LN%2Fik7OPvG9UxoflyEyq4tHRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2076&quot; height=&quot;1680&quot; data-origin-width=&quot;2076&quot; data-origin-height=&quot;1680&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/screens/Auth/login.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 현재는 loginView에 Title과 SignIn(회원가입뷰로 이동) 버튼과 Log In(홈뷰로 이동)만 띄워둔 상태이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LFft7/btroKzey4tz/kd1vsKJr8SabhCmI2ih0qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LFft7/btroKzey4tz/kd1vsKJr8SabhCmI2ih0qK/img.png&quot; data-alt=&quot;Termial Commit : &amp;quot;BackUp/JustforGOOGLESIGNIN&amp;quot;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LFft7/btroKzey4tz/kd1vsKJr8SabhCmI2ih0qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLFft7%2FbtroKzey4tz%2Fkd1vsKJr8SabhCmI2ih0qK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;986&quot; height=&quot;944&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Termial Commit : &quot;BackUp/JustforGOOGLESIGNIN&quot;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&amp;nbsp;블로그의 장점을 여기서 한 가지 발견했다. 단계마다 이미지와 정리글을 남기니까 슈슉 공부를 다 하고 나서 돌이켜보면 정리가 좀 안됐는데 비교적 정리가 되는 듯 하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;자 ! 문서부터 살펴보자&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2620&quot; data-origin-height=&quot;1668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QP4BL/btroJ0Q3UgD/Rkz9K3oKIpXHwmFWrTQqJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QP4BL/btroJ0Q3UgD/Rkz9K3oKIpXHwmFWrTQqJK/img.png&quot; data-alt=&quot;https://openbase.com/js/@react-native-google-signin/google-signin/documentation&amp;amp;amp;amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QP4BL/btroJ0Q3UgD/Rkz9K3oKIpXHwmFWrTQqJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQP4BL%2FbtroJ0Q3UgD%2FRkz9K3oKIpXHwmFWrTQqJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2620&quot; height=&quot;1668&quot; data-origin-width=&quot;2620&quot; data-origin-height=&quot;1668&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://openbase.com/js/@react-native-google-signin/google-signin/documentation&amp;amp;amp;amp;amp;amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요 녀석과 사투를 벌일 것이다. openbase 라는 사이트는 음원 플레이어를 구현해보고 싶어서 다양한 라이브러리 중에서 어느 것을 쓸지 고민하다가 가장 많이 쓰이는 라이브러리들을 비교해주길래 종종 들어가서 보는 사이트이다(참고!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 라이브러리 설치&amp;nbsp; :&amp;nbsp; yarn add @react-native-google-signin/google-signin&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;1204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGRWlO/btroPwN2crt/FJYrsxEADN5ekW1kTc0MJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGRWlO/btroPwN2crt/FJYrsxEADN5ekW1kTc0MJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGRWlO/btroPwN2crt/FJYrsxEADN5ekW1kTc0MJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGRWlO%2FbtroPwN2crt%2FFJYrsxEADN5ekW1kTc0MJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;986&quot; height=&quot;1204&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;1204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqTilh/btroJBcIYIL/LTGnUNXky1WdVyNq042Dn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqTilh/btroJBcIYIL/LTGnUNXky1WdVyNq042Dn0/img.png&quot; data-alt=&quot;package.json : 추가 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqTilh/btroJBcIYIL/LTGnUNXky1WdVyNq042Dn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqTilh%2FbtroJBcIYIL%2FLTGnUNXky1WdVyNq042Dn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1438&quot; height=&quot;290&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;package.json : 추가 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Pod Install&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;1230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp2YFG/btroKxBcSGU/A26lPyar1hhfVZlpTet440/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp2YFG/btroKxBcSGU/A26lPyar1hhfVZlpTet440/img.png&quot; data-alt=&quot;terminal : pod install ( or npx pod-install )&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp2YFG/btroKxBcSGU/A26lPyar1hhfVZlpTet440/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp2YFG%2FbtroKxBcSGU%2FA26lPyar1hhfVZlpTet440%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1010&quot; height=&quot;1230&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;1230&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;terminal : pod install ( or npx pod-install )&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pod install 이 완료가 되었으면 그 다음 차례는 ClientID 적용을 위한 GoogleService-Info.plist 를 Xcode 파일 내부에 넣어줄 차례다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. GoogleService-Info.plist 적용 여부 확인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 부분이 까다로운 이유는 해당 plist 파일은 Firebse를 사용할 때에만 활용을 할 수 있다는 것이다. 즉, Firebase 를 사용할 때 웹 상에서 iOS앱 생성을 해야하는데 생성하기 직전 프로세스에서 plist 파일을 제공해준다. 다른 경로는 아직 찾지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그래서 이번에는 GoogleService-Info.plist 없이 URL Type 만 지정된 상태에서 GoogleSignIn 테스트를 해보려고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N6Bol/btroQbiQgLr/CSEsy2uaBslAqnvzBQore1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N6Bol/btroQbiQgLr/CSEsy2uaBslAqnvzBQore1/img.png&quot; data-alt=&quot;Xcode - Project - Info - URL Types&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N6Bol/btroQbiQgLr/CSEsy2uaBslAqnvzBQore1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN6Bol%2FbtroQbiQgLr%2FCSEsy2uaBslAqnvzBQore1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1898&quot; height=&quot;626&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Xcode - Project - Info - URL Types&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2492&quot; data-origin-height=&quot;1780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tg1sz/btroPHbxqhl/6VSOwKe6q0FkRYQPGsm58K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tg1sz/btroPHbxqhl/6VSOwKe6q0FkRYQPGsm58K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tg1sz/btroPHbxqhl/6VSOwKe6q0FkRYQPGsm58K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTg1sz%2FbtroPHbxqhl%2F6VSOwKe6q0FkRYQPGsm58K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2492&quot; height=&quot;1780&quot; data-origin-width=&quot;2492&quot; data-origin-height=&quot;1780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대차게 실패.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;failed to determine clientID-GoogleService-Info.plist 에러..오랜만..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 문서로 돌아가보자..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2106&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0RbuY/btroMpC9zWw/YRZD4T9kZTToHgKrPnvDB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0RbuY/btroMpC9zWw/YRZD4T9kZTToHgKrPnvDB0/img.png&quot; data-alt=&quot;https://github.com/react-native-google-signin/google-signin/blob/cf678328e4c63279c2fcc6f52d254bbc1187c54e/docs/get-config-file.md&amp;amp;amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0RbuY/btroMpC9zWw/YRZD4T9kZTToHgKrPnvDB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0RbuY%2FbtroMpC9zWw%2FYRZD4T9kZTToHgKrPnvDB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2106&quot; height=&quot;894&quot; data-origin-width=&quot;2106&quot; data-origin-height=&quot;894&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/react-native-google-signin/google-signin/blob/cf678328e4c63279c2fcc6f52d254bbc1187c54e/docs/get-config-file.md&amp;amp;amp;amp;amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 이미 보고 숙지한 상태에서 적용을 한건데 왜 안될까.. 삽질 시작.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;1692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgJ4ZW/btroPHo7I1O/IrJ1b0yknzkOR7CImTBtNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgJ4ZW/btroPHo7I1O/IrJ1b0yknzkOR7CImTBtNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgJ4ZW/btroPHo7I1O/IrJ1b0yknzkOR7CImTBtNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgJ4ZW%2FbtroPHo7I1O%2FIrJ1b0yknzkOR7CImTBtNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1734&quot; height=&quot;1692&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;1692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구들 발견..! 왜 이 포럼을 이제 발견했을까..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;1460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yYb5n/btroNIo3pXr/9rG0cvNxvpVzGJ8kQ2vpZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yYb5n/btroNIo3pXr/9rG0cvNxvpVzGJ8kQ2vpZK/img.png&quot; data-alt=&quot;https://github.com/react-native-google-signin/google-signin/issues/525&amp;amp;amp;amp;amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yYb5n/btroNIo3pXr/9rG0cvNxvpVzGJ8kQ2vpZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyYb5n%2FbtroNIo3pXr%2F9rG0cvNxvpVzGJ8kQ2vpZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1738&quot; height=&quot;1460&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;1460&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/react-native-google-signin/google-signin/issues/525&amp;amp;amp;amp;amp;amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ByoQw/btroPe8SOV0/Stejvmw7ZsIcLJEIidEf80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ByoQw/btroPe8SOV0/Stejvmw7ZsIcLJEIidEf80/img.png&quot; data-alt=&quot;app/screens/Auth/login.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ByoQw/btroPe8SOV0/Stejvmw7ZsIcLJEIidEf80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FByoQw%2FbtroPe8SOV0%2FStejvmw7ZsIcLJEIidEf80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1914&quot; height=&quot;1720&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;1720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/screens/Auth/login.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vonovak : GoogleService-Info.plist 를 적용하지 않는다면 iosClientId를 활용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로, webClientId 를 지우면서 offlineAccess : false로 지정해주었고, iosClinetId와 Xcode 상에서의 URL Types 까지 지정해준 다면 GoogleService-Info.plist 없이 구글 로그인 버튼이 작동하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;gif.gif&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7fy5y/btroQ6aSQAo/ShhnpinlSEB6znQjz0GrK1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7fy5y/btroQ6aSQAo/ShhnpinlSEB6znQjz0GrK1/img.gif&quot; data-alt=&quot;app/screens/Auth/login.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7fy5y/btroQ6aSQAo/ShhnpinlSEB6znQjz0GrK1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c7fy5y/btroQ6aSQAo/ShhnpinlSEB6znQjz0GrK1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1962&quot; height=&quot;1680&quot; data-filename=&quot;gif.gif&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;1680&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/screens/Auth/login.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추후 기능구현&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구글 서버 연동 후, 토큰 및 유저 정보 받아오기&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Frontend/Auth</category>
      <category>googlesignin</category>
      <category>ReactNative</category>
      <category>SocialLogin</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/6</guid>
      <comments>https://0biglife.tistory.com/entry/Google-SignIn-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%841#entry6comment</comments>
      <pubDate>Fri, 24 Dec 2021 13:31:10 +0900</pubDate>
    </item>
    <item>
      <title>Styled-components ThemeProvider 활용(1)</title>
      <link>https://0biglife.tistory.com/entry/Styled-components-ThemeProvider-%ED%99%9C%EC%9A%A9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 앱 개발에서 React Native로 넘어와서 가장 마음 급하게 구현해보고자 한 것들은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;홈뷰 구현(swift의 cell 관리가 꽤 까다롭다고 생각했기 때문에 RN에서의 홈뷰 구현은 어떻게 하는지 궁금했고, 현재는 FlatList로 뉴스피드 구현한 상태)&lt;/li&gt;
&lt;li&gt;Restful API 구현&lt;/li&gt;
&lt;li&gt;컴포넌트 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;iOS 앱을 공부할 때에는 light/dark theme에 대한 관리는 Xcode 상에서의 asset에서 두 이미지를 하나로 묶어서 관리했기 때문에 RN으로 넘어와서는 큰 방법론적인 해결책이 떠오르지 않았다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(하단 사진 첨부)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFuGKM/btroAIWlhfv/YcScNqskF5DiqX1UH3r98K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFuGKM/btroAIWlhfv/YcScNqskF5DiqX1UH3r98K/img.png&quot; data-alt=&quot;Xcode-Assets : color set 지정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFuGKM/btroAIWlhfv/YcScNqskF5DiqX1UH3r98K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFuGKM%2FbtroAIWlhfv%2FYcScNqskF5DiqX1UH3r98K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1608&quot; height=&quot;1086&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Xcode-Assets : color set 지정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;React Native 로는 Styled-components에서 제공해주는 ThemeProvider를 통해 이를 적용해보려고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z5ygD/btroEz5lhy9/msQoj99oMjCjVMgKZNAlvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z5ygD/btroEz5lhy9/msQoj99oMjCjVMgKZNAlvK/img.png&quot; data-alt=&quot;app/index.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z5ygD/btroEz5lhy9/msQoj99oMjCjVMgKZNAlvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz5ygD%2FbtroEz5lhy9%2FmsQoj99oMjCjVMgKZNAlvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1412&quot; height=&quot;594&quot; data-origin-width=&quot;1412&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/index.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.tsx 안에서 가장 바깥에 ThemeProvider로 감싸준 다음, props로 theme을 넘겨준다. 이 theme은 style 폴더 안에 해당하는theme.tsx를 가져와서 우선 dark모드를 담당할 컬러가 제대로 들어가는지 테스트했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EHEsu/btroDuQ0vBG/FW3VWnQmRZT0QaARaDnd01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EHEsu/btroDuQ0vBG/FW3VWnQmRZT0QaARaDnd01/img.png&quot; data-alt=&quot;app/styles/theme.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EHEsu/btroDuQ0vBG/FW3VWnQmRZT0QaARaDnd01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEHEsu%2FbtroDuQ0vBG%2FFW3VWnQmRZT0QaARaDnd01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1436&quot; height=&quot;834&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/styles/theme.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;타입스크립트로 구현했기 때문에 DefaultTheme 타입형으로 선언하고 dark,light 분리.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;1688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfJiGW/btrozkV48EC/l3g2TG93sDqdpwzjLN7bC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfJiGW/btrozkV48EC/l3g2TG93sDqdpwzjLN7bC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfJiGW/btrozkV48EC/l3g2TG93sDqdpwzjLN7bC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfJiGW%2FbtrozkV48EC%2Fl3g2TG93sDqdpwzjLN7bC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1998&quot; height=&quot;1688&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;1688&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용완료 !&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO1eOO/btroDJHePMx/SpRA6a7aFKUAdsDet1EIp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO1eOO/btroDJHePMx/SpRA6a7aFKUAdsDet1EIp1/img.png&quot; data-alt=&quot;app/navigations/MainTab.tsx&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO1eOO/btroDJHePMx/SpRA6a7aFKUAdsDet1EIp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO1eOO%2FbtroDJHePMx%2FSpRA6a7aFKUAdsDet1EIp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1418&quot; height=&quot;842&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;app/navigations/MainTab.tsx&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Component는 적용이 되는데 Navigator 안의 screenOptions는 에러가 뜨는 것을 발견..!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Styled-components ThemeProvider 활용(2) 보완점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 컴포넌트 적용&lt;/li&gt;
&lt;li&gt;navigator - screenOptions에는 적용이 안됨 ! (=&amp;gt;&amp;nbsp; useTheme/useContext 를 공부해보자)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Frontend</category>
      <category>ReactNative</category>
      <category>styled-components</category>
      <category>ThemeProvider</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/5</guid>
      <comments>https://0biglife.tistory.com/entry/Styled-components-ThemeProvider-%ED%99%9C%EC%9A%A9#entry5comment</comments>
      <pubDate>Wed, 22 Dec 2021 15:47:49 +0900</pubDate>
    </item>
    <item>
      <title>git Error</title>
      <link>https://0biglife.tistory.com/entry/git-Error</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XvZE0/btropsHmBoK/nvUlv5AUCns28YorSUUxh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XvZE0/btropsHmBoK/nvUlv5AUCns28YorSUUxh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XvZE0/btropsHmBoK/nvUlv5AUCns28YorSUUxh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXvZE0%2FbtropsHmBoK%2FnvUlv5AUCns28YorSUUxh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;848&quot; height=&quot;650&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git pull 후 push 필요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요시 merge.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;not recommended : git push -f origin main&lt;/p&gt;</description>
      <category>Error Record/Git</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/4</guid>
      <comments>https://0biglife.tistory.com/entry/git-Error#entry4comment</comments>
      <pubDate>Tue, 21 Dec 2021 13:58:20 +0900</pubDate>
    </item>
    <item>
      <title>개발의 시작,</title>
      <link>https://0biglife.tistory.com/entry/%EA%B0%9C%EB%B0%9C%EC%9D%98-%EC%8B%9C%EC%9E%91</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #7e98b1;&quot;&gt;&lt;s&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;해당 글은 블로그 작성을 더 이상 미루지 말고자 우선 가볍게 여태 공부한 내용을 간략히 정리해보자는 취지의 글이다.&lt;/span&gt;&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개발 공부를 처음으로 접했을 때 Swift 로 입문했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;시작했던 이유는 단순히 멋져보여서였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;멋져보이는 것을 혹자는 가볍게 바라볼 수 있겠지만 필자에게 있어 &lt;b&gt;'멋지다!'&lt;/b&gt; 라는 것은 삶에 있어 크나큰 동기부여이자 무서운 힘이라고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;멋있는 건 삶에 의미와 행동의 계기를 만들어내고 목표를 구체화시키도록 도와주며, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;매일 밖을 겉돌아다니며 시간을 버리던 청년의 하루를 12시간씩 앉혀둘 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp;멋져보여서 시작했던 개발 공부는 2021년 1월에 시작하여 2021년 7월까지 6개월 간 iOS 앱개발을 독학하였고, 2021년 12월부터 현재까지는 React Native를 활용한 크로스 플랫폼 앱 개발을 독학 중이다. iOS 앱 개발 당시에는 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;학업과 병행이 필요하였으며 학업 외의 시간을 최대한 헛되이 쓰지 않도록 노력했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;게임이나 유튜브 영상을 보던 대중교통 이용시간에는 부족했던 개발 개념, 문법 기초와 기술에 대한 방법론과 같이 읽는 것만으로 도움이 될 수 있는 것들을 습득했고, 실내건축과 디지털 아트 두 전공에 대한 과제를 마치면 자기 전까지 코드를 치면서 실력을 쌓아보려고 했다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;6개월 동안 공부했던 내용은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Swift 언어 문법과 Xcode 사용 방법&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;iOS 기본 UI에 대한 스터디&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개발 디렉터리 구조와 Camel Case와 같은 시각적 용이를 위한 코드 설계 방식&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스토리보드 방식과 프로그래밍 방식을 통한 화면 설계 및 데이터 구조&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;iOS앱 개발이 이루어지는 방식과 백엔드 친구와의 간단한 업무 소통 (Restful API 포함, token 날려주기)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;token이 필요한 이유(굉장히 흥미로움. 나중에 따로 글을 작성하고 싶다) + accessToken과 만료된 토큰 처리에 대한 스터디&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 내용을 세부적으로 풀면,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;UI 스터디 결과, 홈뷰/검색 뷰/프로필 뷰 적용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;디자이너가 작업한 컴포넌트(logo/icon..)를 Vector Image로 적용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;추후(2021년 12월~) React Native에서 FlatList로 구현하던 것을 Swift에서는 Cell 방식으로 처리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;카메라/앨범으로 접근 권한 및 촬영된 이미지를 파일로 저장 후 프로필 뷰에 적용(simulator에서는 불가능)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;소셜 로그인 구현(Google/Kakao/Apple)&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Restful API 구현을 위한 Alamofire 적용 + AlamofireImage(이미지 처리)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;CRUD 구현에 있어서 효율적인 라우팅 방식 구현(baseUrl, method, path, json parsing, interceptor를 활용하여 header, token 등 전송) -&amp;gt; 추후 Moya Library를 알게 되어 기존 라우팅 방식으로 Moya 방식으로 전환하다가 React Native로 변경&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;+ 방식 : 실제 구현했던 API는 구글 로그인 시 구글 서버로부터 유저 정보, 토큰.. 을 .get 해온 상태에서 백엔드 쪽에 token을 .post 해줌으로써 해당 유저가 우리 어플리케이션에 가입된 유저인지 판별(이는 정형이가 구현)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;+ 홈뷰에서 게시글 추가 버튼을 통해 추가된 정보를 body에 실어보내는 .post 기능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;+ patch, put의 차이에 대한 스터디 + delete는 개념만 스터디하고 실제 구현하지 않은 상태&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;부족한 점&lt;/b&gt;(중요!)&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;효율적인 코드 작성(늘 어려운 부분. 취업 후 선배님들에게 더 배우고 싶다)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;에러 발생 시 구글 검색에 대한 의존. 스스로 해결하는 능력이 더 다져져야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;체력 관리(오래 앉아있으려면 건강 관리가 필수인 것이 날이 갈수록 느껴진다.)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;구현한 것들을 정확하고 자세하게 구두로 서술하는 방법 연습 필요.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot; 개발을 처음 시작했을 때의 열정과 즐거움은 갈수록 증가하고 있다 &quot;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이런 내 모습을 지켜보면서 '나는 개발자가 하고 싶다'라고 판단했고 짧은 시간 안에 열심히 앱 제작을 하고 포폴 정리를 하여 나를 성장시켜줄 수 있는 스타트업을 들어가는 것이 2022년 목표이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;하고픈거 다 할 수 있다 :-)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Entj Life</category>
      <category>0biglife</category>
      <category>SWiFT</category>
      <category>개발시작</category>
      <author>0BigLife</author>
      <guid isPermaLink="true">https://0biglife.tistory.com/3</guid>
      <comments>https://0biglife.tistory.com/entry/%EA%B0%9C%EB%B0%9C%EC%9D%98-%EC%8B%9C%EC%9E%91#entry3comment</comments>
      <pubDate>Tue, 21 Dec 2021 12:40:29 +0900</pubDate>
    </item>
  </channel>
</rss>