<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발블로그</title>
    <link>https://hyriverstudy.tistory.com/</link>
    <description>FE 개발자 입니다</description>
    <language>ko</language>
    <pubDate>Thu, 2 Jul 2026 23:36:39 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hyriver(강화영)</managingEditor>
    <image>
      <title>개발블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/5548304/attach/eead709279ab40d39e4f73629efd9fae</url>
      <link>https://hyriverstudy.tistory.com</link>
    </image>
    <item>
      <title>node.js와 npm에 대해서</title>
      <link>https://hyriverstudy.tistory.com/70</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2939013384_1735199446.1207.jpg&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvEKch/dJMcaaxvuNI/KFy08AZqklkzkoe4DTC35K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvEKch/dJMcaaxvuNI/KFy08AZqklkzkoe4DTC35K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvEKch/dJMcaaxvuNI/KFy08AZqklkzkoe4DTC35K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvEKch%2FdJMcaaxvuNI%2FKFy08AZqklkzkoe4DTC35K%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;420&quot; height=&quot;289&quot; data-filename=&quot;2939013384_1735199446.1207.jpg&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;289&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;node와 npm..&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Node.js&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 웹브라우저에서 사용하는 언어이다. 이 &lt;b&gt;자바스크립트를 브라우저가 아닌 다른 환경에서 사용하기위한 런타임 환경&lt;/b&gt;이다. 그런데 런타임 환경이 뭐지? 이건 엔진과 다른걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;런타임 환경&lt;/b&gt;이란? &lt;b&gt;코드가 실행될 때 필요한 것들&lt;/b&gt; 예를 들어 엔진, api, 메모리 관리 등&lt;b&gt;을&lt;/b&gt; &lt;b&gt;제공하는 환경&lt;/b&gt;이다. &lt;b&gt;엔진&lt;/b&gt;은 런타임 환경에 속하는 것 중 하나로 &lt;b&gt;자바스크립트 코드를 읽고 기계어로 바꿔서 실행&lt;/b&gt;하는 일을 한다(브라우저와 노드는 둘다 &lt;b&gt;Chrome의 V8 엔진&lt;/b&gt;을 사용). 네트워크 통신(네트워크 모듈)이나 DOM 조작(렌더링/DOM api), setTimeout(web api) 등은 런타임 환경에서 하는 것이지, 엔진의 역할이 아니다.&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;그럼 우리가 리액트를 사용할 때 node가 왜 필요할까? 리액트는 브라우저에서 실행되는 것 아닌가?&lt;/h4&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;12.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4XhrX/dJMcadgGl5m/ocRNxtKO3tbsl6AiH2pMv0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4XhrX/dJMcadgGl5m/ocRNxtKO3tbsl6AiH2pMv0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4XhrX/dJMcadgGl5m/ocRNxtKO3tbsl6AiH2pMv0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4XhrX%2FdJMcadgGl5m%2FocRNxtKO3tbsl6AiH2pMv0%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;314&quot; height=&quot;367&quot; data-filename=&quot;12.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;467&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;p data-ke-size=&quot;size16&quot;&gt;우리가 리액트가 브라우저로 실행된다고 생각하는 이유는 &lt;b&gt;리액트에서 개발한 파일을 js로 변환해서 이 js가 브라우저에서 실행&lt;/b&gt;되는 것이지 &lt;b&gt;그 전까지의 과정에 필요한 도구들은 node에서 실행&lt;/b&gt;된다. 리액트를 만들고 실행할 때 create-react-app, vite, webpack 등의 툴은 node 위에서 돌아간다. 참고로 &lt;b&gt;CRA&lt;/b&gt;나 &lt;b&gt;Vite&lt;/b&gt;는 리액트 그 자체가 아니다! 리액트는 하나의 라이브러리인데, 이 &lt;b&gt;리액트를 사용하기 위한 개발 환경 및 빌드 도구 세트&lt;/b&gt;이다. 리액트설치 + webpack 설정 + babel 설정 + ESLint 설정 등으로 이루어진.. 얘네가 node 위에서 돌아가는 것! 그래서 리액트를 사용할 때는 노드가 필요하다.&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;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;&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;h2 data-ke-size=&quot;size26&quot;&gt;NPM&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm은 &lt;b&gt;노드 환경에서 사용할 라이브러리(패키지)를 설치하고 관리하는 도구&lt;/b&gt;이다. 예를 들어 리액트 네비게이션이라는 라이브러리가 있는데, 이 라이브러리가 작동하기 위해서는 노드라는 환경 위에서 작동하므로 이를 설치/관리하기 위해서는 npm이 필요하다.&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;b&gt;프로젝트에서 사용하는 라이브러리의 버전&lt;/b&gt;과 &lt;b&gt;업데이트 가능 범위 등을 관리&lt;/b&gt;해서 팀원이 &lt;b&gt;npm install을 하면 같은 버전환경을 그대로 재현 가능&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;그럼 우리가 npm start로 사용하는건 뭘까?&lt;br /&gt;npm start는 &lt;b&gt;package.json에 있는 script의 start를 실행하는 명령어&lt;/b&gt;이다. npm start를 하게되면 npm이 package.json을 읽는데 scripts안의 start를 찾아서 그 문자열을 그대로 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1770873602933&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;start&quot;: &quot;react-script start&quot;
}&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;p data-ke-size=&quot;size16&quot;&gt;그럼 npm 이 리액트를 실행시키는 건가..?&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-filename=&quot;90.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ng2Hs/dJMcac27wj3/ZdFaWhAtl9B03rKdpuCrhK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ng2Hs/dJMcac27wj3/ZdFaWhAtl9B03rKdpuCrhK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ng2Hs/dJMcac27wj3/ZdFaWhAtl9B03rKdpuCrhK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNg2Hs%2FdJMcac27wj3%2FZdFaWhAtl9B03rKdpuCrhK%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;494&quot; height=&quot;278&quot; data-filename=&quot;90.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;360&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;아니다.. npm start는 &lt;b&gt;node로 vite 또는 cra를 실행시키는 트리거&lt;/b&gt;인것! 실제로 일을 하는건 node, vite, 브라우저이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;vite 또는 cra를 실행하면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;로컬서버&lt;/span&gt;가 실행이 되고(예: localhost:5173)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 로컬서버가 오픈된다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;gt; jsx에서 js 변환준비를 하고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 브라우저는 해당 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;로컬서버&lt;/span&gt;에 요청을 보낸다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;gt; vite 서버가 index.html을 반환&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;gt; vite가 jsx에서 js로, ts에서 js로 변환 등을 수행하여 변환된 js를 브라우저에 보낸다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 그럼 브라우저는 js를 실행하고 dom을 렌더링 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; 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;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;개발을 하면서 사용하는데에만 익숙해져서 뭘하는지 제대로 몰랐던 node와 npm에 대해 이번기회에 자세히 알게되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;업무를 하면서 이런 기본적인 것들에 대해 물어보면 말문이 막혔는데,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 블로그로 작성하면서 명확히 알게된 것이 앞으로 디딤판이 되지 않을까 생각한다.&amp;nbsp;&lt;/p&gt;</description>
      <category>JavaScript</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/70</guid>
      <comments>https://hyriverstudy.tistory.com/70#entry70comment</comments>
      <pubDate>Wed, 18 Feb 2026 10:11:56 +0900</pubDate>
    </item>
    <item>
      <title>react-native 작동원리</title>
      <link>https://hyriverstudy.tistory.com/69</link>
      <description>&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;ChatGPT Image 2026년 2월 6일 오후 10_00_35.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIaIEU/dJMcafrWDaQ/DsYmQYqK0aJqeWbpKHNARK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIaIEU/dJMcafrWDaQ/DsYmQYqK0aJqeWbpKHNARK/img.png&quot; data-alt=&quot;대충 GPT로 뽑은 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIaIEU/dJMcafrWDaQ/DsYmQYqK0aJqeWbpKHNARK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIaIEU%2FdJMcafrWDaQ%2FDsYmQYqK0aJqeWbpKHNARK%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;652&quot; height=&quot;435&quot; data-filename=&quot;ChatGPT Image 2026년 2월 6일 오후 10_00_35.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대충 GPT로 뽑은 이미지&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 네이티브는 3개의 스레드로 작동한다.&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;1. javaScript thread&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. shadow thread&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. main thread&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;이렇게 세가지 스레드로 분리된 이유는 각각 다른 작업을 하면서 부드러운 화면처리가 가능하도록 만든다. 화면을 그리면서 js계산을 하고, 레이아웃도 가능하도록 할 수 있다.&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;h4 data-ke-size=&quot;size20&quot;&gt;JavaScript thread&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- js thread에서는 우리가 작성한 리액트 코드가 실행된다. 리액트 코드가 실행된다는 것은 화면을 그리는게 아니라 &lt;b&gt;컴포넌트 함수가 호출되고 JSX가 계산된다&lt;/b&gt;는 뜻이다. 컴포넌트 렌더링(JSX 계산), state/props 계산, 비즈니스 로직, api 응답처리, 애니메이션 계산 등이 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 리액트 네이티브에서는 &lt;b&gt;Hermes라는 자바스크립트 엔진&lt;/b&gt;을 사용한다. 일반적으로 js는 앱 실행 후 js를 파싱하고 실행되는데, hermes는 &lt;b&gt;빌드 시 js를 바이트 코드로 변환&lt;/b&gt;하고, &lt;b&gt;앱 실행 시 바로 실행&lt;/b&gt;된다. 그래서 앱의 시작속도가 빠르고 메모리 사용도 적다. 또한 웹 api와 DOM이 없어서 가볍다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- js thread는 싱글 스레드다. 동시에 하나의 작업만 처리하며 큐에 쌓인 일을 순서대로 처리한다. 블로킹되면, 버튼을 눌렀을 때 반응이 없거나 스크롤이 끊기거나 애니메이션이 끊긴다.&lt;span style=&quot;background-color: #f6e199;&quot;&gt; &lt;b&gt;js 스레드는 상태계산, 이벤트 처리 등의 짧고 빠른 계산용&lt;/b&gt;으로 되었기때문에 큰 데이터를 반복처리하거나 자주 반복되는 무거운 작업을 시키면 안된다.&lt;/span&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;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;Bridge vs JSI&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네이티브(main, shadow)로 넘어가기 전, bridge에 대해 알아보자. JS코드와 네이티브 코드는 다른 언어이기때문에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;bridge(JSI)를 통해 변환하는 작업을 해야한다.&lt;/span&gt;네이티브로 변환하는 작업이다. 예전에는 bridge였는데, 최근에는 JSI로 변경되었다(0.76부터).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Bridge&lt;/b&gt; : 브릿지는 JSON으로 변환해서 함수를 호출한다. 이런 직렬화/파싱을 하면 큰 객체, 배열, 스크롤 등의 빈번한 호출에서는 더 느려지며 요청을 보내고 응답을 기다려야하는 비동기 처리만 가능하다. 이 문제를 해결하기 위해 나온것이 JSI이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;JSI&lt;/b&gt; : jsi는 앞서 말한 &lt;b&gt;hermes 엔진&lt;/b&gt;을 사용하는데 이는 네이티브와 동일한 C++언어로 작성되어있다. 그래서 &lt;b&gt;따로 변환할 필요 없이 네이티브 함수를 직접 호출하여 사용&lt;/b&gt;한다. 또한 비동기는 물론 동기 처리까지 가능하여 즉시 결과 사용이 가능하다(ex. 레이아웃, 애니메이션 프레임 계산 등). 그래서 라이브러리를 사용하게될 때도 JSI 기반의 라이브러리를 사용하는 것이 효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JS에서 처리하는 게 많아지면 JSI의 수행도 많아진다. 그래서&lt;span style=&quot;background-color: #f6e199;&quot;&gt; js에서 처리해야하는 것, 네이티브에서 처리해야하는 것을 구분짓고 판단할 수 있어야한다.&amp;nbsp;&lt;/span&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;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Shadow thread(백그라운드 스레드)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 레이아웃 계산, flexbox 처리 등 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;화면을 그리기전 &lt;b&gt;계산하는 역할&lt;/b&gt;이다.&lt;span&gt; 화면을 그리는 메인 스레드를 도와준다고 생각하면 쉽다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;- 리액트 네이티브에서는 css방식과 유사하지만 css엔진이 아닌 &lt;b&gt;Yoga&lt;/b&gt;라는 크로스 플랫폼 레이아웃 엔진을 사용한다. ios는 UIkit, android는 View System으로 레이아웃을 계산한다. 이들은 css를 모르기 때문에 공통으로 레이아웃을 계산하기 위해서 Yoga를 사용한다.&lt;/span&gt;&lt;/span&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;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Main thread&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;- &lt;b&gt;실제로 화면을 그리는 작업&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;- &lt;span style=&quot;background-color: #f6e199;&quot;&gt;너무 많은 레이아웃작업, 무거운 네이티브 연산 시 블로킹&lt;/span&gt;되는데, 이때 화면이 멈추거나 스크롤이 안되거나 터치 반응이 없다. 이 경우 &lt;span style=&quot;background-color: #f6e199;&quot;&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;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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;리액트 네이티브 작동원리를 왜 알아야할까&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;d2eed3.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lKgvM/dJMcacosfEC/Sjt9WpOhpbHZORwixAjNJk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lKgvM/dJMcacosfEC/Sjt9WpOhpbHZORwixAjNJk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lKgvM/dJMcacosfEC/Sjt9WpOhpbHZORwixAjNJk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlKgvM%2FdJMcacosfEC%2FSjt9WpOhpbHZORwixAjNJk%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;400&quot; height=&quot;380&quot; data-filename=&quot;d2eed3.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;380&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;리액트 네이티브 작업을 하면서 스크롤이 버벅이거나 터치 반응이 느리거나 하는 등의 현상이 일어나기 마련이다. 이때 js스레드와 백그라운드 스레드, 메인 스레드 각각의 역할을 알고 구분지어 판단할 수 있어야한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;예를 들어 onScroll에서 setState를 사용하게 될 경우 초당 수십번의 렌더링 작업을 js 스레드에서 하게된다. js 스레드 과부화는 애니메이션이나 스크롤이 끊겨보이게 만든다. 이럴 경우 js 스레드가 아닌 메인스레드를 타는 Reanimated를 사용하거나 ref, throttle 등으로 불필요한 렌더링을 줄여야 한다는 것을 알고 적용시킬줄 알아야한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>React-Native</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/69</guid>
      <comments>https://hyriverstudy.tistory.com/69#entry69comment</comments>
      <pubDate>Fri, 6 Feb 2026 21:42:06 +0900</pubDate>
    </item>
    <item>
      <title>3년차 프론트엔드 개발자 회고</title>
      <link>https://hyriverstudy.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;26년이 지난지 벌써 한달이 다되어간다. 교육 때 회고를 한번 하고 취업을 하고서는 한번도 회고를 하지 않았는데, 아마도 지금까지 해온게 없다고 생각하며 회피하면서 회고를 하지 않았던게 아닌가 싶다.&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 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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Keep&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) ui/ux에 대한 관심&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ui/ux에 대한 관심으로 디테일한 부분을 캐치할 수 있는 것 같다. 내가 만드는 앱을 의식적으로 사용하면서 불편하다고 느꼈던 것을 개선했던 경험이 있다. 많은 양의 아이템을 렌더링 하면서 버벅거리는 부분이나 다양한 상황에서의 에러처리 등이 있었다. 또 추가적으로 필요하다고 생각하는 부분은 기획팀과 소통하면서 보완하기도 했다. 사실 ui/ux에 대한 부분은 조금 감으로 하는 면이 없지 않아 있는데 관련 서적을 찾아보면서 이론적인 부분들을 채우면 좋을 것 같다.&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;b&gt;2) 꾸준한 기술 블로그 작성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그를 작성하면 해당 주제에 대해 깊이 알 수 있었다. 이전에는 일주일에 한번 작성하자고 했지만 현실적으로 무리라는 것을 깨닫고 2주에 한번, 적어도 한달에 한번은 작성하자는 생각을 했는데 작년동안 얼추 잘 지켜왔던 것 같다. 2주에 한번 포스팅하는 것을 목표로 하기 위해 작년 말에 기술블로그를 모임을 가입했다. 강제적인 시스템을 만들어 더 꾸준히, 열심히 할 수 있을 것 있을 것 같다.&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;b&gt;3) 업무관련 정리&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;2. Problem&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 개발 속도가 느리다&lt;/b&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 예외처리 부족 및 빌드 후 버그 속출&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 디자인 패턴&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;3. Try&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 개발속도 올리기&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;u&gt;작업 전 5분 설계&lt;/u&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;컴포넌트 개수&lt;/li&gt;
&lt;li&gt;상태 어디에 둘지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;u&gt;디버깅 속도 올리기&lt;/u&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;흐름 정리&lt;/li&gt;
&lt;li&gt;로그는 실행됐는지 확인하는 것이 먼저 &amp;rarr; 타임라인을 눈에 보이게&lt;/li&gt;
&lt;li&gt;추측 금지! 검증만할 것&lt;/li&gt;
&lt;li&gt;한번에 하나만 의심&lt;/li&gt;
&lt;li&gt;지우기 디버깅&lt;/li&gt;
&lt;/ul&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;&lt;b&gt;2) 예외처리&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;1. 데이터 관련&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 응답이 null / undefined 인 경우를 처리했는가&lt;/li&gt;
&lt;li&gt;배열 데이터의 길이가 0인 경우를 고려했는가&lt;/li&gt;
&lt;li&gt;응답 데이터 구조 변경 (key 누락, 네이밍 변경)에 안전한가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;2. 렌더링 타이밍&lt;/u&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;로딩 / 에러 / 빈 상태 UI가 분리되어 있는가&lt;/li&gt;
&lt;li&gt;key, memo, useCallback dependency 문제는 없는가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;3. 사용자 행동 예외&lt;/u&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;빠른 화면 이동 시 언마운트 후 setState를 방지했는가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;4. 플랫폼 / 네이티브 예외 (React Native)&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Android / iOS 동작 차이를 고려했는가&lt;/li&gt;
&lt;li&gt;권한 거부 / 취소 케이스를 처리했는가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;5. 상태 / 로직&lt;/u&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 dependency 배열이 정확한가&lt;/li&gt;
&lt;li&gt;조건문에서 true / false, 부정 조건을 다시 확인했는가&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;&lt;b&gt;3) 사용하는 라이브러리, 프레임워크를 정확하게 파악하기 -&amp;gt; 기술블로그 활용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관심있다고 한 ui/ux를 위해서 작업 중 사용하는 라이브러리나 프레임워크의 다양한 활용 방법과 깊이있는 이해가 필요했다. 이번에 기술블로그 모임을 하면서 꾸준히 블로그 포스팅을 해야하는데 이때 라이브러리, 프레임워크에 대한 글을 작성해야겠다.&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;b&gt;4) 정보처리기사 자격증&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실기가 4월에 있는 정보처리기사 시험을 준비할 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) 디자인 패턴 공부&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4월 정처기 시험이 끝나면 디자인 패턴 책을 구매해서 공부할 예정이다. 한빛미디어에서 나온 자바스크립트 + 리액트 디자인패턴 책을 사용할까 싶다.&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;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성하고 보니 try의 비중이 너무 많은 것 같다는 생각이 들기도 하지만.. 일단 목표를 크게 잡고 보는거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 2026년의 한달이 지났다. 다음 회고는 6월 말이 될 것 같다. 그때까지 쓸 말이 많기를 기대한다.&lt;/p&gt;</description>
      <category>회고</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/68</guid>
      <comments>https://hyriverstudy.tistory.com/68#entry68comment</comments>
      <pubDate>Sun, 25 Jan 2026 10:40:54 +0900</pubDate>
    </item>
    <item>
      <title>js를 ts로 변환하기(리액트 프로젝트)</title>
      <link>https://hyriverstudy.tistory.com/67</link>
      <description>&lt;h2 id=&quot;heading-eslint&quot; style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  기초 세팅 : 타입스크립트와 타입 버전 eslint 설치&lt;/h2&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1. 타입스크립트 설치&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: oklch(0.21 0.009 255); color: oklch(0.985 0.0015 255); text-align: start;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm install --save typescript @types/node @types/react @types/react-dom @types/jest # 패키지 설치
npx tsc --init  # tsconfig.json 파일 생성(jsconfig.json은 삭제한다)&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;h4 data-ke-size=&quot;size20&quot;&gt;2. tsconfig 설정 (애플코딩 참고)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플젝의 tsconfig는 아래와 같이 설정했고, 그 외로 설정할 수 있는 속성들은 그 아래에 작성했다&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: oklch(0.21 0.009 255); color: oklch(0.985 0.0015 255); text-align: start;&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// tsconfig.json
{
  &quot;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;src&quot;,
    &quot;module&quot;: &quot;esnext&quot;, // 리액트 프로젝트에서 추천(최신 ECMAScript 모듈)
    &quot;target&quot;: &quot;es5&quot;,    // 타스를 어떤버전의 자스 파일로 바꿀건지
    &quot;lib&quot;: [&quot;dom&quot;, &quot;es2015&quot;],
    &quot;allowJs&quot;: true, // js 파일들 ts에서 import해서 쓸 수 있는지 
    &quot;checkJs&quot;: false, // 일반 js 파일에서도 에러체크 여부 
    &quot;skipLibCheck&quot;: true,
    &quot;strict&quot;: true, //strict 관련, noimplicit 어쩌구 관련 모드 전부 켜기
    &quot;esModuleInterop&quot;: true,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;,  // tsx 파일을 jsx로 어떻게 컴파일할 것인지 'preserve', 'react-native', 'react'
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;allowImportingTsExtensions&quot;: true

  },
  &quot;include&quot;: [&quot;src&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. eslint 타입스크립트 설치 및 .eslintrc.js 수정&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: oklch(0.21 0.009 255); color: oklch(0.985 0.0015 255); text-align: start;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: oklch(0.21 0.009 255); color: oklch(0.985 0.0015 255); text-align: start;&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended', // TypeScript 권장 규칙 추가
    'airbnb',
    'airbnb/hooks',
    'prettier',
  ],
  parser: '@typescript-eslint/parser', // TypeScript 파서 추가
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  plugins: ['react', '@typescript-eslint', 'prettier'],   // TypeScript 플러그인 추가
  rules: {
    'import/extensions': [    // import 시 js, ts 안붙여도 에러 안나도록
      'error',
      'ignorePackages',
      {
        js: 'never',
        jsx: 'never',
        ts: 'never',
        tsx: 'never',
      },
    ],
    'react/react-in-jsx-scope': 'off',
    'react/require-default-props': 'off',
    'react/function-component-definition': [
      2,
      { namedComponents: ['arrow-function', 'function-declaration'] },
    ],
    'react/jsx-props-no-spreading': 0,
    'max-params': ['error', 3],
    'no-var': 'error',
    'no-alert': 'off',
    'no-console': 'off',
    'no-unused-vars': 'off',  // 함수타입에서 매개변수를 사용하지 않으면 에러 발생하는 것 해제
    'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
    'react/no-array-index-key': 'off',
    'react/prop-types': 'off',
    'react/no-unstable-nested-components': 'off',
    'react-hooks/exhaustive-deps': 'off',
    'no-restricted-syntax': 'off',
    'react/jsx-filename-extension': 'off',
    'import/prefer-default-export': 'off',
    'consistent-return': 'off',
  },
  settings: {
    'import/resolver': {
      node: {
        paths: ['src'],
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    },
  },
  ignorePatterns: ['build/'],
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;heading-ts&quot; style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  라이브러리 ts 버전 재설치&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용하고 있는 라이브러리가 여러개인데 타입스크립트 버전이 필요한 라이브러리들은 따로 설치를 해줘야 했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보통은&lt;span&gt;&amp;nbsp;&lt;/span&gt;@types&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 붙여 타스 버전으로 재설치해준다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1. AG-Grid&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 ag-grid-react만 설치되어 있었는데, 타입을 사용하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;ag-grid-community가 필요해서 설치해주었다. 현재 공식문서에는 ag-grid-react를 설치하면 커뮤니티가 자동으로 설치된다고 한다. (Install the&lt;span&gt;&amp;nbsp;&lt;/span&gt;ag-grid-react&lt;span&gt;&amp;nbsp;&lt;/span&gt;package, which also installs&lt;span&gt;&amp;nbsp;&lt;/span&gt;ag-grid-community)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2. react-date-range&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;타입스크립트 버전을 설치했다가 의존성 충돌로 타입스크립트 버전을 낮추었다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: oklch(0.21 0.009 255); color: oklch(0.985 0.0015 255); text-align: start;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm install --save @types/react-date-range
npm install typescript@4.9.5 --save-dev  # 버전 낮추기&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;heading-8juhcdsvztrk5wg67oa6rk9ioqzvoyglq&quot; style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  코드 변경 과정&lt;/h2&gt;
&lt;h4 id=&quot;heading-6rcd7lk07j2yio2dgoyehsdsp4dsoju&quot; style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;객체의 타입 지정&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;객체의 속성에 접근할 때 타입스크립트가 객체에 속성이 있는지 확신하지 못하여 에러를 내는 경우가 많았다. 그때 여러 방식으로 객체에게 속성이 있음을 명시해주었는데, 상황에 따라 맞는 방법을 사용해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;객체의 키에 동적으로 접근할 때&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 보통 객체의 키를 문자열로 받아서 접근하게 되면&lt;span&gt;&amp;nbsp;&lt;/span&gt;string이 다 오는 게 아니라 &lt;i&gt;data의 키만 와야해!&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;라며 에러를 발생시킨다. 그럴 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;[key as keyof typeof data]를 사용하여 키가 data의 키라는 것을 명확히 알려준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767946573787&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  data[key as keyof typeof data]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;객체에 해당 속성이 있음을 확신하지 못할 때&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 옵셔널 속성의 경우 타입스크립트가 속성이 있다고 확신하지 못한다. 이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;ldquo;속성&amp;ldquo; in 객체로 해당 속성이 있을 때 수행하도록 하고, 만약 데이터(객체)가&lt;span&gt;&amp;nbsp;&lt;/span&gt;undefined또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;null인 경우 에러가 발생하므로 데이터가 있는지 확인하는 작업도 같이 조건문에 넣는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767946619401&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  if(data &amp;amp;&amp;amp; &quot;name&quot; in data) { }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;객체의 타입이 여러개일 경우 또는 리턴 타입을 일치시킬 때&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 유니언 타입을 사용하거나 데이터가 없어서 빈객체를 내보내야할 때,&lt;span&gt;&amp;nbsp;&lt;/span&gt;as 타입을 사용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;데이터의 타입은 정확히 이거다&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;라고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;타입 단언&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767946643749&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 데이터가 유니언이고 각각의 타입일 경우의 속성을 사용할때
  const data: AType | BType;
  const A = (data as AType)?.name;
  const B = (data as BType)?.age;

  // data가 없을 때 리턴 타입을 일치시켜야 할 때
  if (!data) {
     return {} as CType;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;reduce로 chkboxArray의 id 값을 acc의 속성으로 지정하면서 새로운 객체를 만들고 있다. reduce가 새로 반환하는 값의 타입을 지정해줘야 한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;Record&amp;lt;string, boolean&amp;gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 사용하여 객체 타입을 지정해주었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767946730894&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const chkboxObject = chkboxArray.reduce&amp;lt;Record&amp;lt;string, boolean&amp;gt;&amp;gt;((acc, { id }) =&amp;gt; {
    acc[id] = true;
    return acc;
  }, {});&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 id=&quot;heading-axios&quot; style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Axios 요청에서&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;interceptor&lt;/span&gt;를 사용해 header의 엑세스토큰을 변경해주는 작업을 했었는데, 기존에는&lt;br /&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;const { headers, ...restConfig } = config;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이렇게 기존 config에서 header를 분리한 후&lt;br /&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;headers: { ...headers, 'x-access-token': window.sessionStorage.getItem('accessToken'), }&lt;/span&gt;&lt;br /&gt;이런 식으로 엑세스토큰을 지정해주었다. 그런데 이렇게 되면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;AxiosHeaders&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;타입이 아닌&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;일반 객체&lt;/b&gt;가 되어버린다.&lt;br /&gt;그래서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;타입불일치&lt;/b&gt;로 에러가 발생한다.&lt;br /&gt;이 때는 기존 config 객체를 얕은 복사를 한 후 아래와 같이 해당 속성을 수정하면 타입이 그대로 유지된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767947047741&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;  instance.interceptors.request.use(
    config =&amp;gt; {
      const newConfig = { ...config }; // 기존 config 객체를 복사 (얕은 복사)
      newConfig.headers['x-access-token'] = window.sessionStorage.getItem('accessToken');
      return newConfig;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 id=&quot;heading-ag-grid-1&quot; style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Ag-Grid&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;column에&lt;span&gt;&amp;nbsp;&lt;/span&gt;ColDef&lt;span&gt;&amp;nbsp;&lt;/span&gt;타입 설정&lt;br /&gt;cellRenderer는 마크업을 직접 리턴 할 수 없다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컴포넌트 분리해서 전달&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;받도록 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767947085038&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  import { ColDef } from 'ag-grid-community';
  import CancelBtn from 'components/grid/CancelBtn';

  const column: ColDef[] = [
    {
      field: 'no',
      checkboxSelection: true,
      width: 100,
      headerName: 'No',
      headerCheckboxSelection: true,
      showDisabledCheckboxes: true,
    },
    {
      field: 'IDX',
      headerName: '취소',
      cellRenderer: CancelBtn,
    },
  ];&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ag-grid 테이블에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컬럼명 가져오기&lt;/b&gt;&lt;br /&gt;기존에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;e.column.colId를 사용했으나&lt;span&gt;&amp;nbsp;&lt;/span&gt;e.colDef.field로 이벤트 객체가 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767947108960&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    const onCellClicked = (e: CellClickedEvent) =&amp;gt; {
      if (e.colDef.field === 'IDX') {
        if (window.confirm('취소 하시겠습니까?')) {
        }
      }
    };&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 id=&quot;heading-react-query&quot; style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;React-Query&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useQuery에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;useQuery&amp;lt;DataType[]&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이렇게 반환 타입을 지정할 수 있다. 나는&lt;span&gt;&amp;nbsp;&lt;/span&gt;select를 사용하여 응답 데이터의 rows 속성의 값을 반환했고 그것이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;DataType[]&lt;/span&gt;이기에 위와 같이 설정했다. 그런데&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;useQuery의 제네릭&lt;/b&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;초기 응답&lt;/b&gt;을 기준으로 동작한다. 그러니까&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;{ rows: DataType[] }&amp;nbsp;&lt;/span&gt;이 타입이 반환타입이라고 생각한다. 하지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;select로 반환 데이터를 변경&lt;/b&gt;했기 때문에 실제 데이터는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;DataType[]&lt;/span&gt;이 반환 되면서 타입 불일치에러가 발생한다. 이럴때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;select의 응답 데이터에 타입을 지정&lt;/b&gt;&lt;/u&gt;해주어야 한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;select: (res: { rows: DataType[] }) =&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767947238790&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   const { data, refetch } = useQuery(
    queryKyes,
    () =&amp;gt;
      request({
        url: 'withdraw',
        method: 'get',
        params: { ...date },
      }),
    {
      select: (res: { rows: DataType[] }) =&amp;gt;
        res.rows.map((item, index) =&amp;gt; ({
          ...item,
          id: index + 1,
        })),
    },
  );&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 id=&quot;heading-6riw7yoaioyekeyxhq&quot; style=&quot;background-color: #ffffff; color: oklch(0.21 0.034 264.665); text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;기타 작업&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래와 같이 bannerLabels에&lt;span&gt;&amp;nbsp;&lt;/span&gt;BannerLabel[]&lt;span&gt;&amp;nbsp;&lt;/span&gt;타입을 지정하고 map으로 돌릴때 item을&lt;span&gt;&amp;nbsp;&lt;/span&gt;BannerLabel로 타입을 한번더 지정해주면&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;_1.webp&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TA89l/dJMcabv8sJx/kZYkdJ0irmyYs4gt1VrwoK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TA89l/dJMcabv8sJx/kZYkdJ0irmyYs4gt1VrwoK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TA89l/dJMcabv8sJx/kZYkdJ0irmyYs4gt1VrwoK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTA89l%2FdJMcabv8sJx%2FkZYkdJ0irmyYs4gt1VrwoK%2Fimg.webp&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;540&quot; height=&quot;310&quot; data-filename=&quot;_1.webp&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;348&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-filename=&quot;_2.webp&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;75&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFpQdd/dJMcaihJcrQ/pK7C4Z5ttCkJw5n6KJy2P0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFpQdd/dJMcaihJcrQ/pK7C4Z5ttCkJw5n6KJy2P0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFpQdd/dJMcaihJcrQ/pK7C4Z5ttCkJw5n6KJy2P0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFpQdd%2FdJMcaihJcrQ%2FpK7C4Z5ttCkJw5n6KJy2P0%2Fimg.webp&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;680&quot; height=&quot;65&quot; data-filename=&quot;_2.webp&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;75&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;no-unused-prop-types&amp;nbsp;&lt;/span&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-filename=&quot;_3.webp&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vFhLo/dJMcahb2GtA/FOqyGbU6HIzbVAOCZdwmo1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vFhLo/dJMcahb2GtA/FOqyGbU6HIzbVAOCZdwmo1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vFhLo/dJMcahb2GtA/FOqyGbU6HIzbVAOCZdwmo1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvFhLo%2FdJMcahb2GtA%2FFOqyGbU6HIzbVAOCZdwmo1%2Fimg.webp&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;757&quot; height=&quot;183&quot; data-filename=&quot;_3.webp&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;236&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;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션스토리지에 데이터를 저장해두고 그 값을 json으로 파싱해서 가져올 때&lt;br /&gt;기존에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;const storedQeuryData = JSON.parse(sessionStorage.getItem(&quot;data&quot;));&lt;span&gt;&amp;nbsp;&lt;/span&gt;이렇게 사용했지만&lt;br /&gt;&lt;b&gt;세션스토리지에 data 값이 없는 경우의 반환값&lt;/b&gt;도 만들어줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767948290713&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    const storedQeuryData = sessionStorage.getItem('data');
    const parsedData = storedQeuryData ? JSON.parse(storedQeuryData) : null;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;숫자배열을 만들 때 기존에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;Array(24) .fill() .map((_, i) =&amp;gt; (&lt;span&gt;&amp;nbsp;&lt;/span&gt;이렇게 사용했으나&lt;br /&gt;Array(24)는 24칸의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;빈 배열&lt;/b&gt;을 만드는 것이다.&lt;br /&gt;그래서 필수로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;fill()&lt;/b&gt;를 호출하는데, 이는 타입을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;any&lt;/b&gt;로 추론한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이를 대신하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;Array.from({ length: 24 }, (_, i) =&amp;gt; (&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 사용하는데 이 코드는&lt;br /&gt;24칸의 배열을 만들거야~ 하지만! 요소를 다 채우면서 만들어볼게~&lt;br /&gt;이기 때문에 빈 배열이 아닌, 다 채워진 배열을 만드므로 타입 에러가 나지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767948326656&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  Array(24) .fill() .map((_, i) =&amp;gt; (i));  // 에러
  Array.from({ length: 24 }, (_, i) =&amp;gt; (i));  // 권장&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: oklch(0.373 0.034 259.733);&quot;&gt;&lt;b&gt;기존 배열에 데이터를 추가할 때&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체의 속성이 정확히 일치해야 한다. 타입이 일치하지 않은 경우 필요한 속성만 가지고 있는 객체 배열을 새로 만들고, 새로운 배열에 객체를 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767948341127&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 필요한 속성만 있는 객체 배열을 만들고  
  const corpList = datalist?.map(({ DEPOSIT_YN, S_ID, S_NAME }) =&amp;gt; ({
      DEPOSIT_YN,
      S_ID,
      S_NAME,
    }));

  // 그 객체에 새로운 데이터를 추가
    corpList?.push({
      DEPOSIT_YN: 'Y',
      S_ID: 'hana',
      S_NAME: '하나은행',
    });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: oklch(0.373 0.034 259.733);&quot;&gt;onClick&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;(event: MouseEvent&amp;lt;HTMLButtonElement&amp;gt;) =&amp;gt; void&lt;br /&gt;refetch&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;(options?: (RefetchOptions &amp;amp; RefetchQueryFilters&amp;lt;TPageData&amp;gt;) | undefined) =&amp;gt; Promise&amp;lt;QueryObserverResult&amp;lt;any, unknown&amp;gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이다.&lt;br /&gt;매개 변수의 타입불일치로 에러가 발생하는데 이 경우 onClick에&lt;span&gt;&amp;nbsp;&lt;/span&gt;() =&amp;gt; refetch()를 사용하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이벤트 객체를 전달하지 않고&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;refetch()만 실행&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;_4.webp&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQ6y43/dJMcaiaXyjh/nA1k4ZK7SH0NLyIZsdViKK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQ6y43/dJMcaiaXyjh/nA1k4ZK7SH0NLyIZsdViKK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQ6y43/dJMcaiaXyjh/nA1k4ZK7SH0NLyIZsdViKK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQ6y43%2FdJMcaiaXyjh%2FnA1k4ZK7SH0NLyIZsdViKK%2Fimg.webp&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;777&quot; height=&quot;47&quot; data-filename=&quot;_4.webp&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;68&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-filename=&quot;_5.webp&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w21Ai/dJMcagxq7ve/GcXjVNWaLiozO8pbLla911/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w21Ai/dJMcagxq7ve/GcXjVNWaLiozO8pbLla911/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w21Ai/dJMcagxq7ve/GcXjVNWaLiozO8pbLla911/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw21Ai%2FdJMcagxq7ve%2FGcXjVNWaLiozO8pbLla911%2Fimg.webp&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;757&quot; height=&quot;66&quot; data-filename=&quot;_5.webp&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;105&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1767948360156&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  &amp;lt;button onClick={refetch}&amp;gt;Refetch&amp;lt;/button&amp;gt;   // 기존 js 방식에서는 타입을 확인하지 않으니 에러가 나지 않음
  &amp;lt;button onClick={() =&amp;gt; refetch()}&amp;gt;Refetch&amp;lt;/button&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;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: oklch(0.373 0.034 259.733); text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3주간의 타입스크립트 리팩토링 작업이 끝났다. 기존 코드와 타입스크립트를 이해하는 과정을 함께 거치다 보니 생각보다 시간이 걸렸다. 이렇게 js를 ts로 바꾸는 작업이 앞으로 많지는 않겠지만, 하게 된다면 util, hooks 등 공통 함수들 먼저 작업을 하고 각 페이지, 컴포넌트로 넘어가야겠다. (쉬워보이는 페이지와 컴포넌트부터 하다보니 나중에 util, hook을 변환하면 작업했던 파일을 다시 작업해야할 일이 생겼었다) 이젠 ts 프로젝트에 어느정도 익숙해진것 같지만 그럼에도 아직 공부해야할게 많다고 생각된다. ts 기초뿐만아니라 js 기초도 잘 잡아가면서 공부를 해야겠다.&lt;/p&gt;</description>
      <category>React</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/67</guid>
      <comments>https://hyriverstudy.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 9 Jan 2026 18:50:43 +0900</pubDate>
    </item>
    <item>
      <title>[리액트 네이티브] Tab Navigator에서 params에 따라 렌더링하기</title>
      <link>https://hyriverstudy.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React Navigation&lt;/b&gt;은 react native에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;화면이동(routing)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;시 사용되는 라이브러리로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;페이지가 쌓이는 구조(stack 구조)&lt;/b&gt;를 제공한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;Stack&lt;span&gt;&amp;nbsp;&lt;/span&gt;뿐만 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;Tab(하단탭),&lt;span&gt;&amp;nbsp;&lt;/span&gt;Drawer(옆에서 열리는 전체메뉴) 등의 화면 이동 방식(네비게이터)도 가지고 있다. 네비게이터들은 서로 중첩이 가능하다. 우리 프로젝트에서는 Stack과 Tab 네비게이터를 사용했고, Stack 내 하나의 화면에 Tab을 중첩하여 사용했다.&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;h3 id=&quot;heading-8jtmcdquldrs7jsgqzsmqnrsknrspu&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;  기본사용방법&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Stack 네비게이터를 생성 - 리액트 네비게이션은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;NavigationContainer&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;내에서 실행할 수 있다.&lt;br /&gt;Stack.Screen은 화면의 이름과 렌더링되는 컴포넌트를 지정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네비게이터 타입의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;키&lt;/b&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Stack.Screen의 name&lt;/b&gt;이 될 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;키의 타입&lt;/b&gt;이&lt;span&gt;&amp;nbsp;&lt;/span&gt;undefined면 화면 이동 시&lt;span&gt;&amp;nbsp;&lt;/span&gt;params가 필요없는 화면이고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체로 들어간다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;해당 객체를 화면 이동시 params로 넘겨준다.&lt;br /&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;navigation.navigate('Detail', {idx: item.index}&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;export type RootStackParamList = {
  Tab: NavigatorScreenParams&amp;lt;HomeTabParamList&amp;gt;;
  Shop: undefined;
  Detail: { idx: number };
}

const Stack = createNativeStackNavigator&amp;lt;RootStackParamList&amp;gt;(); // 스택 네비게이터 생성

const App = () =&amp;gt; (
  &amp;lt;NavigationContainer&amp;gt;  
    &amp;lt;Stack.Navigator&amp;gt;
      &amp;lt;Stack.Screen name='Tab' component={TabNavigator} /&amp;gt;
      &amp;lt;Stack.Screen name='Shop' component={Shop} /&amp;gt;
      &amp;lt;Stack.Screen name='Detail' component={Detail} /&amp;gt;
    &amp;lt;/Stack.Navigator&amp;gt;
  &amp;lt;/NavigationContainer&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;
&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;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 탭 네비게이터를 생성 - 탭 네비게이터는 스택의 화면 중 하나로 들어가고 있다. 하단 탭의 컴포넌트를&lt;span&gt;&amp;nbsp;&lt;/span&gt;tabBar로 설정할 수 있고 props는&lt;span&gt;&amp;nbsp;&lt;/span&gt;BottomTabBarProps&lt;span&gt;&amp;nbsp;&lt;/span&gt;타입이 전달되어 해당 탭의 이름과 파람스를 알 수 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pf&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot;&gt;&lt;code&gt;type HomeTabParamList = {
  Home: {tab: 'first' | 'second'} | undefined;
  Mypage: undefined;
  Menu: undefined;
}

// BottomTabBarProps의 속성을 사용해 탭 클릭 시 화면 이동
const TabBar = ({state, navigation}) =&amp;gt; (
  // ... 
  const onPress = () =&amp;gt; {
    navigation.navigate(state.route.name, state.route.params);
  }
 // ...
)

const Tab = createBottomTabNavigator&amp;lt;HomeTabParamList&amp;gt;();  // 탭 네비게이터 생성

const TabNavigator = () =&amp;gt; (
  &amp;lt;Tab.Navigator tabBar={TabBar}&amp;gt;
    &amp;lt;Tab.Screen name='Home' component={Home} /&amp;gt;
    &amp;lt;Tab.Screen name='Mypage' component={Mypage} /&amp;gt;
    &amp;lt;Tab.Screen name='Menu' component={Menu} /&amp;gt;
  &amp;lt;/Tab.Navigator&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&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;렌더링되지 않는다&lt;/b&gt;. 반면에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;스택 네비게이터&lt;/b&gt;는 화면 이동 시&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;새로 렌더링&lt;/b&gt;된다. 스택 네비게이터는 화면이 쌓이는 구조이기때문에 이전화면으로 이동이 가능하며, 새로운 화면으로 이동할 때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;새로운 스크린이 push 되는 방식&lt;/b&gt;이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;heading-4pqg77ipiousuoygna&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;⚠️ 문제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래서 탭 간의 이동을 할때, 특히나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;파람스에 따라 다르게 렌더링되는 화면의 이동&lt;/b&gt;이 잘 되지 않았다. 기존 스택 네비게이션에서 사용하는 방식을 사용하면 해당화면으로 이동은 하지만, 파람스에 따라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;tab을 보여주진 않았다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;그리고 특이하게 route.params?.tab가 변경되면 탭의 상태(select)를 변경하도록 했는데 setSelect가 두번 동작하면서 기존 tab으로 돌아가는 현상이 일어났다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;first &amp;rarr; second &amp;rarr; first&lt;span&gt;&amp;nbsp;&lt;/span&gt;이런식으로..&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot;&gt;&lt;code&gt;// Menu 화면에서 Home 화면으로 이동
// type CombinedParamList = RootStackParamList &amp;amp; HomeTabParamList;
navigation.navigate('Home', { tab: 'second' } as CombinedParamList['Home']);

// 위 네비게이션이 동작하면 Home 으로 이동하면서 params 변경에 따라 select가 변경되어 렌더링 되는 것을 예상했다
// Home.tsx
  const [select, setSelect] = useState&amp;lt;THomeTab&amp;gt;(route.params?.tab ?? 'first');

  useEffect(() =&amp;gt; {
    console.log('2️⃣ 셀렉트 변경됨', select);
  }, [select]);

  useEffect(() =&amp;gt; {
    if (route?.params?.tab) {
      setSelect(route?.params?.tab);
      console.log('1️⃣ 탭 변경됨', route?.params?.tab);
    }
  }, [route.params?.tab]);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 코드는 아래처럼 찍힌다&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;vbscript&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot;&gt;&lt;code&gt;LOG 1️⃣ 탭 변경됨 second     # route.params?.tab이 second로 변경
LOG 2️⃣ 셀렉트 변경됨 second  # setSelect가 작동하여 select가 second로 변경
LOG 2️⃣ 셀렉트 변경됨 first   # ????
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; 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 id=&quot;heading-navigationdispatchcommonactionsreset&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;  navigation.dispatch(CommonActions.reset(...))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;탭 네비게이션 이동시 리렌더링이 되지 않는것은 알겠으나 왜 setSelect가 두번 실행되어 기존 상태로 돌아가는지는 알아내지 못했다. 대신 홈의 각각의 탭으로 이동할 때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;네비게이션 기록을 초기화하여 새롭게 렌더링&lt;/b&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;navigation.dispatch(CommonActions.reset(...))&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 방식을 사용했다. 기존의 화면 상태나 파라미터가 남지 않고 완전 초기화되어 렌더링하는 방식이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot;&gt;&lt;code&gt;navigation.dispatch(
  CommonActions.reset({
  routes: [{ name: item.page, params: item.params as CombinedParamList['Home'] }],
  }),
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;react-navigation 라이브러리 사용 방법을 정리하면서 네비게이션이 어떤 구조로 이루어져있고, 동작하는지, 그리고 각각의 네비게이터의 특성에 따라 다르게 실행하는 방법도 익힐 수 있었다. 탭 네비게이터에서 새로 렌더링 하지않고 파람스에 따라 화면을 렌더링 시키는 다른 방법이 있는지도 궁금하다. 작업 중 일어난 버그를 완전히 해결하진 못했지만 사용하다보면 또 새롭게 알아가겠지 싶다.&lt;/p&gt;</description>
      <category>React-Native</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/66</guid>
      <comments>https://hyriverstudy.tistory.com/66#entry66comment</comments>
      <pubDate>Wed, 3 Dec 2025 16:33:56 +0900</pubDate>
    </item>
    <item>
      <title>[리액트 네이티브] 무한렌더링으로 아이템 정렬하면서 스크롤 시 탭 상단에 고정시키기 (ScrollView의 stickyHeaderIndices, useInfiniteQuery)</title>
      <link>https://hyriverstudy.tistory.com/65</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1749719324364.jfif&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjaHse/dJMcadmSDhU/WDfykcihks4COYexZxcAYk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjaHse/dJMcadmSDhU/WDfykcihks4COYexZxcAYk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjaHse/dJMcadmSDhU/WDfykcihks4COYexZxcAYk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjaHse%2FdJMcadmSDhU%2FWDfykcihks4COYexZxcAYk%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;504&quot; height=&quot;504&quot; data-filename=&quot;1749719324364.jfif&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&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;
&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;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;아이템이 무한스크롤로 정렬되고&lt;/li&gt;
&lt;li&gt;스크롤 시 탭이 상단에 고정되고&lt;/li&gt;
&lt;li&gt;중간에 랜덤 아이템이 나오는&lt;/li&gt;
&lt;/ul&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;
&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;h4 data-ke-size=&quot;size20&quot;&gt;1. FlatList vs ScrollView&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1) &lt;b&gt;무한 스크롤&lt;/b&gt;을 쓰다보니까 처음에는 &lt;b&gt;FlatList&lt;/b&gt;로 작업했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- renderItem에서 분기처리해서 각 탭에 맞는 아이템을 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 고정될 탭 아래에 스크롤은 되지만 공통적으로 들어가는 컴포넌트가 있어 리스트 가공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 고정될 탭은 stickyHeaderIndices로 설정&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 리스트는 state로 관리되고 있었는데 탭을 옮길때 마다 데이터 전체가 갈아끼워졌고, 이 부분을 차라리 컴포넌트로 관리하는게 낫겠다 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;불필요한 데이터 가공이 없어지고, state 구조도 단순해지는 &lt;b&gt;scrollView&lt;/b&gt;로 바꾸게 되었다.&lt;/span&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;
&lt;p data-ke-size=&quot;size18&quot;&gt;2) scrollView&lt;/p&gt;
&lt;pre id=&quot;code_1764294328148&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    &amp;lt;ScrollView
      stickyHeaderIndices={[1]}
      onScroll={onScroll}
      scrollEventThrottle={100}
    &amp;gt;
      &amp;lt;FirstCompo /&amp;gt;
      &amp;lt;StickyCompo /&amp;gt;
      &amp;lt;ThirdCompo /&amp;gt;
      {renderList()}
    &amp;lt;/ScrollView&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;- renderList로 각 탭에 맞는 리스트 컴포넌트 분기처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ScrollView 내에서 공통적으로 렌더링되는 컴포넌트를 나열하고, 고정되는 컴포넌트만 stickyHeaderIndices로 설정하므로 불필요한 데이터 가공 필요 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 무한렌더링은 onScroll로 작업&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;이렇게 ScrollView로 작업했을 때 코드가 훨씬 더 깔끔해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(작업을 다 하고 블로그 글을 쓰면서 찾아보니 각 탭별로 FlatList를 사용했어도 괜찮을 것 같았다는 생각이 든다)&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;h4 data-ke-size=&quot;size20&quot;&gt;2. throttle&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ScrollView에서 onScroll을 사용하여 무한렌더링을 하게될 때의 문제점이 있었다. contentOffset.y를 계산해 화면 하단에 닿으면 다음페이지의 데이터를 패치하도록 했더니 스크롤이 바닥에 닿을때 같은 호출이 여러번 일어났다. 이를 해결하기위해 onScroll에 throttle을 적용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1764302783872&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function throttle(callback: Function, delay: number) {
  let lastTime = 0;

  return function (...args: any[]) {
    const now = Date.now();

    if (now - lastTime &amp;gt;= delay) {   // 첫 요청은 실행, 요청의 최소 간격을 체크
      callback.apply(this, args);
      lastTime = now;   // 마지막 실행시간 저장
    }
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1764296678792&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const onScroll = throttle({
    seconds: 200,
    callback: (event: NativeSyntheticEvent&amp;lt;NativeScrollEvent&amp;gt;) =&amp;gt; {
      const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent;
      const isBottom = layoutMeasurement.height + contentOffset.y &amp;gt;= contentSize.height - 100;
      if (isBottom ) {
        nextPageFetch();
      }
    },
  });&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. useQuery vs useInfiniteQuery&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1) 무한스크롤을 할때 처음에는 useQuery를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;queryKey에 파람스로 들어가는 키를 state로 넣어주고, 하단에 닿을 때 마다 setState해서 키가 변경되어 리패치되는 방식을 사용했다. 하지만 이 방식의 문제점은 파람스를 queryKey로 넣어주다보니 캐시된 데이터를 변경할 때 데이터 마다 queryKey가 달라져 찾기가 힘들다는 것이었다.&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;2) 이후에 useInfiniteQuery를 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQuery보다 무한렌더링에 적합하고, 기존에 queryKey에 파람스 state 넣었던 것을 없애서 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1764305927869&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const {
  data,
  fetchNextPage,   // 다음 페이지 리패칭
  hasNextPage,   // 다음 페이지 존재 여부
  isFetchingNextPage,   //다음 페이지 불러오는중인지 판단 여부
} = useInfiniteQuery({
  queryKey: ['dataList'],
  initialPageParam: { lastIdx: null },
  queryFn: ({ pageParam = 0 }) =&amp;gt; fetchData(pageParam),
  getNextPageParam: (lastPage) =&amp;gt; {
    return lastPage.nextCursor; // 최근에 가져온 lastPage의 데이터를 참고해 다음 페이지 요청 시 필요한 파라미터 리턴
  },
});&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;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 data-ke-size=&quot;size16&quot;&gt;작업을 하면서 확실히 FlatList가 아닌 scrollView로 많은 아이템을 렌더링하는게 최적화에는 쉽지않다는걸 느꼈다. 이미 작업을 scrollView로 해서 throttle이나 useInfiniteQuery를 적용 예시를 배우기도 했지만 다음에 이런 페이지를 작업할때는 탭별로 FlatList를 적용하도록 해야겠다는 생각이든다.&lt;/p&gt;</description>
      <category>React-Native</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/65</guid>
      <comments>https://hyriverstudy.tistory.com/65#entry65comment</comments>
      <pubDate>Sat, 29 Nov 2025 14:44:57 +0900</pubDate>
    </item>
    <item>
      <title>이미지 항목 스크롤 시 버벅거림 수정(ScrollView FlatList 중첩, react-native-fast-image)</title>
      <link>https://hyriverstudy.tistory.com/64</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clZ5EX/btsQ51mqm3X/QI7xeCwWVsevI8f6CtoTzK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clZ5EX/btsQ51mqm3X/QI7xeCwWVsevI8f6CtoTzK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clZ5EX/btsQ51mqm3X/QI7xeCwWVsevI8f6CtoTzK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclZ5EX%2FbtsQ51mqm3X%2FQI7xeCwWVsevI8f6CtoTzK%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;450&quot; height=&quot;360&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 아이템이 많으면 스크롤 시 버벅거림이 심해져 사용자 경험에 좋지 않은 영향을 주고 있으므로 수정이 필요했다&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴포넌트 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 페이지 : MainShop, BrandShop, ProductSearchResult -&amp;gt; 여기서 각각 ScrollView를 사용하고 있다&lt;br /&gt;* 컴포넌트 ProductList (FlatList 사용) -&amp;gt; 각 페이지의 ScrollView 안에 ProcudtList를 사용하고 있다&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수정방법 1. 무한스크롤&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 많은 이미지를 한번에 보여주고 있어서 30개씩 아이템을 보여주는 무한스크롤을 사용했다. ScrollView의 onScroll 함수를 사용하여 화면의 끝에 닿을 경우 보여주는 아이템 개수(renderCount, renderList)를 수정하는 방식이었다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// ScrollView의 onScroll 수행시 작동되는 함수 - 스크롤 위치를 계산하여 콜백함수가 수행되도록
const ScrollDownCallback = ({
&amp;nbsp;&amp;nbsp;event,
&amp;nbsp;&amp;nbsp;callback,
&amp;nbsp;&amp;nbsp;bottomRef,
}: {
&amp;nbsp;&amp;nbsp;event: NativeSyntheticEvent&amp;lt;NativeScrollEvent&amp;gt;;
&amp;nbsp;&amp;nbsp;callback: () =&amp;gt; void;
&amp;nbsp;&amp;nbsp;bottomRef: React.RefObject&amp;lt;boolean&amp;gt;;
}) =&amp;gt; {
&amp;nbsp;&amp;nbsp;const { nativeEvent } = event;
&amp;nbsp;&amp;nbsp;const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
&amp;nbsp;&amp;nbsp;const isCloseToBottom = layoutMeasurement.height + contentOffset.y &amp;gt;= contentSize.height - 20;
&amp;nbsp;&amp;nbsp;// 바닥에 닿았는지 확인 = 화면높이 + 스크롤 위치 &amp;gt;= 전체 높이 - 20
&amp;nbsp;&amp;nbsp;if (isCloseToBottom &amp;amp;&amp;amp; !bottomRef.current) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bottomRef.current = true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;callback();
&amp;nbsp;&amp;nbsp;} else if (bottomRef.current) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bottomRef.current = false;
&amp;nbsp;&amp;nbsp;}
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 콜백함수에는 스크롤된 횟수(renderCount)와 아이템 리스트(renderList) 상태가 변경됨
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;ScrollView
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onScroll={e =&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handleScroll({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;event: e,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;callback: () =&amp;gt; {
&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;setRenderList(productList.slice(0, renderCount * 30));
&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;setRenderCount(prev =&amp;gt; prev + 1);
&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;bottomRef,
&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;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;➡️ &lt;b&gt;문제점&lt;/b&gt; : 이렇게 할 경우 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;5번째 스크롤까지는 끊김없이 잘 보이지만 이후에는 렌더링되는 양이 많아져 스크롤을 내리면 똑같이 버벅거림&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수정방법 2. ScrollView, FlatList 중첩 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 이 둘을 중첩해서 사용하는걸 지양해야한다&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;* &lt;b&gt;중복 스크롤 영역 문제&lt;/b&gt; : FlatList는 내부적으로 ScrollView 기반이다. 그래서 &lt;b&gt;ScrollView 안에 FlatList를 넣으면 부모 ScrollView와 자식 FlatList가 각각 스크롤 이벤트를 관리&lt;/b&gt;하게 된다.&lt;br /&gt;&lt;br /&gt;* &lt;b&gt;성능저하&lt;/b&gt; : FlatList의 장점은 가상화(virtualization : 보이는 아이템만 렌더링하고, 보이지 않는 아이템은 언마운트해서 성능 최적화) 기능이다. 그런데 ScrollView 안에 넣으면 모든 아이템을 한번에 렌더링해서 &lt;b&gt;FlatList의 가상화 효과가 사라진다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서 각 페이지의 ScrollView를 삭제하고 FlatList를 넣어주는데, item 컴포넌트를 따로 만들어 renderItem에 넣어주었다. 이 과정에서 ProductList는 삭제됐다. onEndReached를 사용하면 무한렌더링을 구현할 수 있지만 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;중첩을 삭제하는 것만으로도 스크롤 버벅거림이 없어졌기 &lt;/span&gt;&lt;/b&gt;때문에 따로 구현할 필요는 없었다.&lt;br /&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;h3 data-ke-size=&quot;size23&quot;&gt;수정방법3. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;react-native-fast-image 라이브러리 사용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이 방법은 방법2를 사용하고도 버벅거림이 발생할 때 사용하는 방법이다.&lt;/span&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;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;많은양의 이미지가 있는 경우 &lt;b&gt;한꺼번에 이미지 디코딩이 일어나기 때문에 Bridge(js와 네이티브간의 통신) 병목현상으로 인한 스크롤 끊김 현상&lt;/b&gt;이 발생할 수 있다. 이때 &lt;b&gt;네이티브 단에서 이미지 디코딩을 해주는 react-native-fast-image&lt;/b&gt; 라이브러리를 사용하면 js 스레드가 가벼워져 버벅거림이 줄어든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr 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;br /&gt;&amp;nbsp;&lt;br /&gt;다시 한번 더 탄탄한 기본기를 갖춰야 작은 버그들이 생기지 않는다는 것을 깨닫게되었다.&lt;/p&gt;</description>
      <category>React-Native</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/64</guid>
      <comments>https://hyriverstudy.tistory.com/64#entry64comment</comments>
      <pubDate>Fri, 10 Oct 2025 19:15:36 +0900</pubDate>
    </item>
    <item>
      <title>[react-query] 좋아요 낙관적 업데이트</title>
      <link>https://hyriverstudy.tistory.com/63</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;api요청을 한 후 새롭게 적용된 데이터를 보여줘야할 때, 우리가 생각하는 것보다 늦게 데이터가 반영된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 api 요청을 하면서 ui를 미리 반영하는 &lt;b&gt;낙관적 업데이트&lt;/b&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;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_Photo_2025-09-29-16-32-47 001.gif&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcoWCw/btsQVsqQ0yp/ufhBdT3oU0iO2hkQqkLA9K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcoWCw/btsQVsqQ0yp/ufhBdT3oU0iO2hkQqkLA9K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcoWCw/btsQVsqQ0yp/ufhBdT3oU0iO2hkQqkLA9K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bcoWCw/btsQVsqQ0yp/ufhBdT3oU0iO2hkQqkLA9K/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;482&quot; height=&quot;234&quot; data-filename=&quot;KakaoTalk_Photo_2025-09-29-16-32-47 001.gif&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(gif가 좀 이상하게 잘려서 아다리가 안맞음 ㅠ;) 이렇게 좋아요를 클릭해도 숫자가 늦게 반영이 되는 상황에서 낙관적 업데이트를 사용하면 좋아요를 클릭 시 개수를 바로 반영시킬 수 있다.&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;h4 data-ke-size=&quot;size20&quot;&gt;낙관적 업데이트 방법&lt;/h4&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;일단 나는 react-query를 사용하고 있고, 좋아요 클릭 시 post 요청을 보내고 있다.&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;1. useMutate의 onMutate에서 변경하고자 하는 쿼리를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;queryClient.cancelQueries&lt;/span&gt; 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 버튼을 클릭하면서 동일한 쿼리의 데이터가 요청될 수 있다. 이 경우 &lt;b&gt;낙관적 업데이트한 데이터를 덮어씌울 수 있으므로&lt;/b&gt;(race condition) 그 요청 결과가 캐시를 덮어씌우지 못하도록 막는다.&lt;/p&gt;
&lt;pre id=&quot;code_1759121082184&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;onMutate: async () =&amp;gt; {
  await queryClient.cancelQueries({ queryKey: ['comment', idx] });&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;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;background-color: #f6e199;&quot;&gt;queryClient.getQueryData&lt;/span&gt;로 해당 쿼리 데이터를 받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1759121228491&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const cache = queryClient.getQueryData(['comment', idx]);&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;p data-ke-size=&quot;size16&quot;&gt;3. 캐시에 map을 돌려 같은 idx의 경우 좋아요 값과 개수 값을 변경해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1758857767824&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      const copy = cache?.map(i =&amp;gt; {
        if (i.IDX === IDX) {
          return {
            ...i,
            IS_LIKE: !prevIsLike ? 1 : 0,
            LIKE_CNT: !prevIsLike ? i.LIKE_CNT + 1 : i.LIKE_CNT - 1,
          };
        }
        return i;
      });&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;p data-ke-size=&quot;size16&quot;&gt;4. 변경한 copy 데이터를 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;queryClient.setQueryData&lt;/span&gt;에 넣어준다. 그리고 &lt;b&gt;캐시데이터를 onMutate에서 리턴&lt;/b&gt;해준다. 이 리턴해준 데이터는...&lt;/p&gt;
&lt;pre id=&quot;code_1759121699169&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      queryClient.setQueryData(['comment', idx], { copy });
      
      return { cache };
}&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;p data-ke-size=&quot;size16&quot;&gt;5. &lt;b&gt;onError의 context&lt;/b&gt;에 담기게 된다. 만약 에러가 날 경우 onError에서 setQueryData로 context의 해당 데이터를 가져다 사용하여 롤백되도록 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1759125998641&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    onError: (err, _, context) =&amp;gt; {
      if (context?.cache) {
        queryClient.setQueryData(['comment', idx], context.cache);
      }
    },&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;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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2025-09-29-16-32-47 002.gif&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G9lro/btsQUCgtXJy/iOAXW81unjgbLA5w5rWB7K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G9lro/btsQUCgtXJy/iOAXW81unjgbLA5w5rWB7K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G9lro/btsQUCgtXJy/iOAXW81unjgbLA5w5rWB7K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/G9lro/btsQUCgtXJy/iOAXW81unjgbLA5w5rWB7K/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;512&quot; height=&quot;260&quot; data-filename=&quot;KakaoTalk_Photo_2025-09-29-16-32-47 002.gif&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;흐린눈을 하려면 할 수 있는 사항이지만 좋은 UX를 위해서는 이런 사소한 디테일을 챙기는 것 또한 중요한 것 같다&lt;/p&gt;</description>
      <category>React</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/63</guid>
      <comments>https://hyriverstudy.tistory.com/63#entry63comment</comments>
      <pubDate>Mon, 29 Sep 2025 20:16:33 +0900</pubDate>
    </item>
    <item>
      <title>[에러해결] 푸시 알람으로 페이지 진입 후 댓글 작성 시 반영안됨 (queryClient.invalidateQueries 사용)</title>
      <link>https://hyriverstudy.tistory.com/62</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&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;/p&gt;
&lt;pre id=&quot;code_1756072710469&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const { mutate: postComment } = useMutation({
  // ..
  
    onSuccess() {
      queryClient.invalidateQueries({
        queryKey: [type, 'detail', idx, isLogin],
      });
      queryClient.invalidateQueries({ queryKey: ['comment'] });
    },
  })&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;1. 푸시 알람으로 페이지 진입 시 queryClient.getQueryData가 undefined라는 걸 알게됨&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;2. 댓글 정보를 가져올 때 글의 idx를 전역상태에 저장하는데, 전역상태에서 받아오는 idx를 props에서 받아오는 걸로 변경시켰더니 getQueryData가 받아와지고, 댓글 작성시 반영도 정상적으로 됐다. (하지만 전역상태의 idx와 props의 idx는 동일하다)&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;3. 전역상태에서 받아오는 idx와 props의 idx의 타입을 비교해봤을 때 각각 number와 string이었다. 푸시 알람에서 받아오는 idx는 string으로 받아와져서 쿼리가 달랐던 것&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;배경지식) queryClient.invalidateQueries&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;queryClient.invalidateQueries는 캐시를 무효화하는 함수이다. 캐시를 무효화한다는 것은 데이터의 상태를 &lt;span style=&quot;background-color: #9feec3;&quot;&gt;fresh(신선한)&lt;/span&gt; 상태에서 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;stale(오래된)&lt;/span&gt; 상태로 표시하는 것이다. &lt;b&gt;데이터가 stale 상태이면서 해당 쿼리가 화면에 마운트되어 있을 때(useQuery를 사용할 때) refetch가 트리거 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 왜 refetchQueries가 아닌 &lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;invalidateQueries를 사용할까? 이 두개의 차이가 뭘까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;retetch는 &lt;b&gt;지금 당장 데이터 새로 불러오기&lt;/b&gt; 라면 invalidate는 캐시를 무효화시킨 뒤 &lt;b&gt;필요할 때 다시 불러오기&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;지금 댓글 작성 후 바로 데이터를 불러와야하는 나의 상황에서는 &lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;refetchQueries가 더 잘 맞다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;✏️ 결과&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1756076137916&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const { idx, mypage } = route.params;
  const numIdx = Number(idx); // 푸시로 받는 idx는 string이므로 숫자로 변환
  
  const { data } = useQuery({
  // ..
    setIdx(res.IDX);
  })&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;params로 받는 idx를 number로 처리하고 전역상태에 저장한다&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;.&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>React-Native</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/62</guid>
      <comments>https://hyriverstudy.tistory.com/62#entry62comment</comments>
      <pubDate>Mon, 25 Aug 2025 07:57:58 +0900</pubDate>
    </item>
    <item>
      <title>npm run ios 에러 해결기 2</title>
      <link>https://hyriverstudy.tistory.com/61</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전부터 리액트 네이티브 프로젝트에서 ios 시뮬레이터를 실행시키면 절대 한번에 실행되는 법이 없었다. 그래서 그때도 어떻게 에러를 해결했는지 기록해놨었는데, 그걸 참고해도 실행이 안되더라는  그래서 또 다시 작성한다. npm run ios로 스트레스 받지 않는 그날을 위해&lt;/p&gt;
&lt;h2 id=&quot;heading-1-pod-install&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. pod install&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;깃에서 프로젝트를 받고&lt;span&gt;&amp;nbsp;&lt;/span&gt;npm run ios를 하기 전&lt;span&gt;&amp;nbsp;&lt;/span&gt;pod install(프로젝트에 필요한 라이브러리 설치)을 했다. 하지만 언제나 여기부터 막혔었다. 항상 다른 이유로..  이번에는 아래와 같은 에러가 떴다&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rbenv: pod: command not found&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The `pod' command exists in these Ruby versions:&lt;br /&gt;2.7.5&lt;br /&gt;3.2.2&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pod 명령어를 사용할 수 없다&lt;/b&gt;는 것은&lt;br /&gt;1. cocoaPod가 제대로 설치되지 않았거나&lt;br /&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;ruby 환경이 제대로 설정되지 않은 경우다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;iOS의 의존성 관리 도구&lt;/b&gt;인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;CocoaPods&lt;/b&gt;는 루비에 의해 개발되었으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;루비 환경에서 작동&lt;/b&gt;한다. 에러 문구로 루비 버전과 관련된 것이 떴으므로 로컬의 루비 버전을 확인해보았다. 정확히 기억나지 않지만 저 두 버전은 아니었으므로 루비 버전을 바꾸어주었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CocoaPods는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RubyGems&lt;/b&gt;(&lt;b&gt;루비에서 사용하는 패키지 관리자.&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;node.js의 npm)에 등록된 많은 패키지 중 하나다. 그래서 제일 처음 코코아패드를 설치할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;gem install cocoapods로 설치를 했을 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;rbenv global 3.2.2  # 시스템 전체에서 기본적으로 사용할 루비 버전 변경
rbenv rehash  # 해당 버전에 맞게 갱신

rbenv local 3.2.2  # 프로젝트 폴더에 루비버전이 설정되어 있다면 로컬로 버전 변경
rbenv rehash

pod --version # CocoaPods 설치 확인&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;루비 3.2.2로 버전을 변경한 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;pod install을 하니까 아래와 같이 에러가 떴다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[!] Invalid Podfile file:&lt;br /&gt;[!] Invalid RNGestureHandler.podspec file: undefined method `exists?' for [File:Class](File:Class).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;루비 3.2.2에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;File.exists?&lt;span&gt;&amp;nbsp;&lt;/span&gt;메소드가 유효하지 않으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #111111;&quot; href=&quot;https://github.com/software-mansion/react-native-gesture-handler/issues/2367#issuecomment-1375796621&quot;&gt;File.exist?&lt;span&gt;&amp;nbsp;&lt;/span&gt;메소드를 사용&lt;/a&gt;하라고 한다.&lt;br /&gt;node_modules/react-native-gesture-handler/RNGestureHandler.podspec&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;File.exists?를&lt;span&gt;&amp;nbsp;&lt;/span&gt;File.exist?로 수정하고 pod install을 했다. 하지만 동일한 에러 발생&amp;hellip;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 나는 동일한 프로젝트 파일을 여러 개 받아놓고 이것저것 시도해보면서 에러를 해결하고 있었는데, 다른 파일에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;루비 버전을 2.7.5로 설치했을 때 pod install이 성공했었다. 정석적인 에러 해결 방법은 아니지만 다시&lt;span&gt;&amp;nbsp;&lt;/span&gt;rbenv local 2.7.5로 루비 버전 변경 완 ^^&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;잘 설치되는 듯 하다가 또 아래와 같은 에러가 떴다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[!] Error installing boost&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Verification checksum was incorrect, expected f0397ba6e982c4450f27bf32a2a83292aba035b827a5623a14636ea583318c41, got 79e6d3f986444e5a80afbeccdaf2d1c1cf964baa8d766d20859d653a16c39848&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 에러를 구글링을 했을 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;boost.podspec&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일의&lt;span&gt;&amp;nbsp;&lt;/span&gt;'&lt;a href=&quot;https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2'&quot;&gt;https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2'&lt;/a&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;'&lt;a href=&quot;https://archives.boost.io/release/1.76.0/source/boost_1_76_0.tar.bz2'&quot;&gt;https://archives.boost.io/release/1.76.0/source/boost_1_76_0.tar.bz2'&lt;/a&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #111111;&quot; href=&quot;https://stackoverflow.com/questions/77738691/error-installing-boost-verification-checksum-was-incorrect-expected&quot;&gt;변경했더니 해결되었다는 글&lt;/a&gt;을 보고 적용했더니 성공했다..!  &lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 에러는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;체크썸(파일이나 데이터가 전송되는 과정에서 손상 또는 변경되었는지 사용)의 불일치&lt;/b&gt;인데, 다운로드를 하고 보니 체크썸이 일치하지 않아서 변경해야된 것이 아닐까싶다.&lt;/p&gt;
&lt;h2 id=&quot;heading-2-npm-run-ios-podfile&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. npm run ios - podfile 수정&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;드디어 pod install이 작동되었고&lt;span&gt;&amp;nbsp;&lt;/span&gt;npm run ios를 했다. 당연히 바로 성공하지 않았고요.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;빌드중이라고 돌아가더니 로그가 한 5억줄 찍혔다. 에러는 아래와 같이 나왔지만 로그 상단에 다른 에러들도 많았다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BUILD FAILED&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The following build commands failed:&lt;br /&gt;CompileC /Users/&amp;hellip;/Library/Developer/Xcode/DerivedData/App-csubxcbigzkboqcppnqqkwlxtozs/Build/Intermediates.noindex/&lt;a style=&quot;color: #111111;&quot; href=&quot;http://pods.build/Debug-iphonesimulator/Yoga.build/Objects-normal/arm64/Yoga.o&quot;&gt;Pods.build/Debug-iphonesimulator/Yoga.build/Objects-normal/arm64/Yoga.o&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;/Users/&amp;hellip;/node_modules/react-native/ReactCommon/yoga/yoga/Yoga.cpp normal arm64 c++&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #111111;&quot; href=&quot;http://com.apple/&quot;&gt;com.apple&lt;/a&gt;.compilers.llvm.clang.1_0.compiler (in target&lt;span&gt;&amp;nbsp;&lt;/span&gt;'Yoga'&lt;span&gt;&amp;nbsp;&lt;/span&gt;from project 'Pods')&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PhaseScriptExecution [CP]\ Copy\ XCFrameworks /Users/&amp;hellip;/Library/Developer/Xcode/DerivedData/App-csubxcbigzkboqcppnqqkwlxtozs/Build/Intermediates.noindex/&lt;a style=&quot;color: #111111;&quot; href=&quot;http://pods.build/Debug-iphonesimulator/Ads-Global.build/Script-46EB2E000298E0.sh&quot;&gt;Pods.build/Debug-iphonesimulator/Ads-Global.build/Script-46EB2E000298E0.sh&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(in target 'Ads-Global' from project 'Pods')&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2 failures)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 이런 에러들이 뜨면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;pod install을 다시 하라던지, DerivedData를 삭제하라던지, 빌드 캐시를 삭제하라던지&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이런 얘기들이 많다. 캐시 삭제를 몇 번을 하고 pod install도 수 없이 했으나 여전히 똑같았고, gpt한테 갖다받치니 Flipper를 비활성화 해보라고 하심.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flipper는 리액트 네이티브 디버깅 툴&lt;/b&gt;인데,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;리액트 네이티브 레이아웃 엔진(라이브라리)인 Yoga&lt;/b&gt;와 충돌 가능성이 있다고 한다. 그래서&lt;span&gt;&amp;nbsp;&lt;/span&gt;ios/Podfile에서 아래와 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;Flipper 관련 코드를 비활성화&lt;span&gt;&amp;nbsp;&lt;/span&gt;했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;  # use_flipper!()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;pod install&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;npm run ios를 했더니 에러 하나가 삭제됨! 웃긴 게 요가랑 충돌할 수 있다고 해서 삭제했는데 두번째 에러가 삭제되었다. 소 뒷걸음질치다 에러잡기 &lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;heading-3-npm-run-ios-xcode&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3. npm run ios - Xcode 확인&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(아래의)남은 에러는 도저히 모르겠어서 Xcode에서 확인해보기로 했다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;The following build commands failed:
    CompileC /Users/.../Library/Developer/Xcode/DerivedData/App-csubxcbigzkboqcppnqqkwlxtozs/Build/Intermediates.noindex/Pods.build/Debug-iphonesimulator/Yoga.build/Objects-normal/arm64/Yoga.o /Users/.../node_modules/react-native/ReactCommon/yoga/yoga/Yoga.cpp normal arm64 c++ com.apple.compilers.llvm.clang.1_0.compiler (in target 'Yoga' from project 'Pods')
(1 failure)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Xcode 무사와요.. 리액트 네이티브를 쓰면서 Xcode는 빌드 에러 날때만 실행해봐서 그런지 그냥.. 모르겠는.. 어떤 것이었다. 암튼, 여기서 빌드를 실행했더니&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;에러 1)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;요가 파일에서 아래의 에러가 떴다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;Fix&lt;span&gt;&amp;nbsp;&lt;/span&gt;클릭해서 수정하고 다시 빌드 &amp;rarr; 해결&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9.png&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KTz7G/btsPQwosZ8E/zi8khd7qDoOB9a8IlXVkpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KTz7G/btsPQwosZ8E/zi8khd7qDoOB9a8IlXVkpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KTz7G/btsPQwosZ8E/zi8khd7qDoOB9a8IlXVkpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKTz7G%2FbtsPQwosZ8E%2Fzi8khd7qDoOB9a8IlXVkpK%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;473&quot; height=&quot;82&quot; data-filename=&quot;9.png&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;82&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;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;에러 2)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RCT-Folly에서 아래와 같은 에러가 떠서 픽스 클릭 후 빌드 &amp;rarr; 해결&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;10.webp&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5VrNW/btsPSEsaB8G/2k5xu07zyA38zKm1oFb16K/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5VrNW/btsPSEsaB8G/2k5xu07zyA38zKm1oFb16K/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5VrNW/btsPSEsaB8G/2k5xu07zyA38zKm1oFb16K/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5VrNW%2FbtsPSEsaB8G%2F2k5xu07zyA38zKm1oFb16K%2Fimg.webp&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;695&quot; height=&quot;88&quot; data-filename=&quot;10.webp&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;88&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;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;에러 3)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;두 개의 에러가 떴다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cj0eNS/btsPSzdA420/rE9q2aWUwq2vlKmksGSBek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cj0eNS/btsPSzdA420/rE9q2aWUwq2vlKmksGSBek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cj0eNS/btsPSzdA420/rE9q2aWUwq2vlKmksGSBek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcj0eNS%2FbtsPSzdA420%2FrE9q2aWUwq2vlKmksGSBek%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;690&quot; height=&quot;130&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;130&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 style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러니까 저&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;구글어쩌구는 arm64(디바이스용)에서만 빌드가 가능&lt;/b&gt;하다. 하지만 나는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;iOS 시뮬레이터로 빌드를 진행중이기 때문에 충돌이 발생&lt;/b&gt;한다. 그래서&lt;span&gt;&amp;nbsp;&lt;/span&gt;arm64를 Excluded Architectures에 설정해줘야한다. 그런데&lt;span&gt;&amp;nbsp;&lt;/span&gt;arm64를 제외시키면 프로젝트가 실행이 되긴 하는건가?&lt;span&gt;&amp;nbsp;&lt;/span&gt;라는 의문이 들었는데, 해당 파일은 시뮬레이터에서는 실행이 안되는 채로 빌드가 되는 듯 하다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Xcode에서 변경&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 프로젝트 파일의 Build Settings에서 Excluded Architectures에 arm64를 넣어준다. 나의 경우 i386을 arm64로 수정해줬다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;12.webp&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nidix/btsPSRx7D6z/0EiOZ3D4perP4KxNq8RxH1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nidix/btsPSRx7D6z/0EiOZ3D4perP4KxNq8RxH1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nidix/btsPSRx7D6z/0EiOZ3D4perP4KxNq8RxH1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnidix%2FbtsPSRx7D6z%2F0EiOZ3D4perP4KxNq8RxH1%2Fimg.webp&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;1510&quot; height=&quot;527&quot; data-filename=&quot;12.webp&quot; data-origin-width=&quot;1510&quot; data-origin-height=&quot;527&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;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Podfile 수정&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: podfile에 아래 코드를 추가해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 에러3은 뜨지 않았으나 다른 에러가 떴고, 이것저것 시도해보다 아래와 같은 에러가 났다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;13.png&quot; data-origin-width=&quot;222&quot; data-origin-height=&quot;815&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BsC4l/btsPPaGr34F/fXxNtVUUR9gkxd8kj7qTa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BsC4l/btsPPaGr34F/fXxNtVUUR9gkxd8kj7qTa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BsC4l/btsPPaGr34F/fXxNtVUUR9gkxd8kj7qTa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBsC4l%2FbtsPPaGr34F%2FfXxNtVUUR9gkxd8kj7qTa1%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;222&quot; height=&quot;815&quot; data-filename=&quot;13.png&quot; data-origin-width=&quot;222&quot; data-origin-height=&quot;815&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해결할 의지를 상실해벌임&lt;br /&gt;ㅋㅋㅋㅋㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;heading-4-podfile&quot; style=&quot;background-color: #ffffff; color: #111827; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4. 다시 돌아가서 podfile&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음날 다시 여러 개의 프로젝트 파일 중 하나로 갈아 타서&amp;hellip;&lt;br /&gt;구글링과 지피티의 콜라보로 해결책을 얻었다. 주석 처리했던 pod install의 flipper를 아래와 같이 변경하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;pod install&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;npm run ios를 했더니&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #18181b; color: #e5e7eb;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;use_flipper!({ 'Flipper' =&amp;gt; '0.185.0' }) # 최신 버전으로 변경&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;??? 성공했어요&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;filpper 없애는 것이 아니라 최신 버전으로 수정했더니 빌드가 되었다&amp;hellip;.. &lt;br /&gt;실패를 너무 많이 했더니 성공하더라도 어안이 벙벙한&amp;hellip;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;암튼 이렇게 ios 빌드를 성공시켰다 시뮬레이터는 항상 빌드하기 전에 부담을 가지고 있었는데(이러한 과정들을 거쳐야하기에) 그래도 이번에 에러들을 하나씩 뜯어보면서 정리를 해놔서 다음에는 부담이 덜할 것 같다. 안드로이드 스튜디오나 Xcode의 사용법에 대해서는 아직 미숙한데 이들도 부담 가지지 말고 차근차근 공부를 시작해봐야겠다.&lt;/p&gt;</description>
      <category>React-Native</category>
      <author>hyriver(강화영)</author>
      <guid isPermaLink="true">https://hyriverstudy.tistory.com/61</guid>
      <comments>https://hyriverstudy.tistory.com/61#entry61comment</comments>
      <pubDate>Wed, 13 Aug 2025 16:03:09 +0900</pubDate>
    </item>
  </channel>
</rss>