<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>공부하는 블로그</title>
    <link>https://devtimothy.tistory.com/</link>
    <description>자바스크립트 관련 번역을 취미로 합니다.</description>
    <language>ko</language>
    <pubDate>Sun, 5 Jul 2026 05:05:13 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>devtimothy</managingEditor>
    <image>
      <title>공부하는 블로그</title>
      <url>https://t1.daumcdn.net/cfile/tistory/99EAAE4A5D272B602B</url>
      <link>https://devtimothy.tistory.com</link>
    </image>
    <item>
      <title>근황: 리액트 학습</title>
      <link>https://devtimothy.tistory.com/173</link>
      <description>&lt;h1&gt;근황&lt;/h1&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;&amp;lt;리액트를 다루는 기술&amp;gt; 김민준님 책&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세가지를 병행해서 보고 있다. 상태 관리 쪽을 좀 자세히 살펴보고 있는데, 이제 좀 구조가 눈에 들어온다.&lt;br /&gt;&lt;br /&gt;Vue로 개발할 때는 Vuex만 있으면 되었는데 리액트에서는 선택지가 굉장히 많아서 처음에는 좀 당황스러웠다.&lt;br /&gt;&lt;br /&gt;Redux, Redux-thunk, Redux-saga... 요새는 swr, react-query까지.&lt;br /&gt;&lt;br /&gt;생태계의 히스토리를 잘 파악하지 못해서 '리액트 동네는 복잡한 동네구나' 싶었다.&lt;br /&gt;&lt;br /&gt;근데 이번 학습을 통해서는 상태 관리에 대해서 다시금 인사이트를 얻게 되는 시간이 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 학습을 통해서 알게된 바는 상태 관리를 사용해서 캐시 레이어에 대한 통제권을 개발자가 갖게 한다는 점이다.&lt;br /&gt;&lt;br /&gt;사실 나는 현업에서 GraphQL을 사용하면서 Apollo Client를 통해 캐시 레이어를 관리하며 사실 상태 관리가 굳이 필요하지 않았다. (지금와서 보니 REST API를 사용할 때 당시에는 상태관리를 제대로 이해하지 못하고 사용해왔다는 것을 느낀다.)&lt;br /&gt;&lt;br /&gt;이전에는 단순히 컴포넌트의 깊이가 깊어질 때 발생할 수 있는 문제 (props drilling, 그로 인해 발생하는 열차 충돌 문제 등으로 인한 디버깅의 어려움들...)에서 상태 관리를 써왔는데, 새삼 새롭게 다가왔다.&lt;/p&gt;
&lt;h1&gt;학습&lt;/h1&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의를 들으며 사이드 프로젝트를 진행하면서, 리덕스와 사가를 이용해서 어떻게 하면 optimistic update를 할 수 있을까를 고민했다.&lt;br /&gt;나름대로 고민해서 코드를 작성해보았지만 사실 어설플수밖에 없었다. 구글링을 하면서 optimistic update에 대해서 검색을 많이 해보았다&lt;br /&gt;redux 생태계 내에서는 redux-toolkit 외에는 딱히 괜찮은 방법이 없어보였다. redux toolkit 자체는 thunk를 기반으로 해서 비동기 처리를 하기도 하고, (비슷한 역할을 하는 라이브러리를 또 끼어넣고 싶지 않았다) 그간 saga 기반으로 한 코드 근간이 흔들리는 것이기에 좋은 방법이라고 생각이 들지 않았다.&lt;br /&gt;검색하다보니 react-query, swr 등의 도구가 왜 나타나게 되었는지도 이해가 갔다. 결국 캐싱 때문이구나.&lt;br /&gt;여튼 하고싶은 말을 다시 하겠다. 이재승님 강의 들으며 정말 좋았던 점은...&lt;br /&gt;사이드 프로젝트 진행하면서 고민했던 부분이&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;optimistic update&lt;/li&gt;
&lt;li&gt;이미 한번 불러온 api call에 대해서, 이미 로딩 된 것에 대해 어떻게 깔끔하게 처리할 수 있을지 고민을 많이 했고, 자료도 찾아보았는데 뭔가 매끈한 처리 방식이 보이진 않았다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 와중에 이재승님 강의의 담당자 찾기 프로젝트를 하면서인데, 와, 이 부분을 딱! 다뤄주시며 내 가려운 곳을 긁어주셨다.&lt;/p&gt;
&lt;h1&gt;학습 후기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 학습을 하면서 추천하고 싶은 로드맵은&lt;br /&gt;패스트캠퍼스 김민태님 강의 -&amp;gt; 리액트를 다루는 기술 책 -&amp;gt; 인프런 이재승님 강의 순이다.&lt;br /&gt;또한 학습하면서 도움이 되었던 방식은 떠올려보자면...&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;처음 접하면 뭐든지간에 낫 놓고 기역자도 모르는게 정상인 듯 하다.&lt;br /&gt;그 다음에는 코드를 다 날리고 내가 직접 짜보는 것이다. 그러면 어느정도는 잘 하다가 중간에 막히는 경우가 있는데, 이 때 내가 뭘 모르는지 알 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 변칙을 준다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 인프런과 리액트를 다루는 기술 책에서는 redux 부분을 다루면서 immer와 redux-actions 라이브러리를 사용했다.&lt;br /&gt;그러나 나는 딱히 이에 대한 메리트를 깊게 느끼지 못해서 사용하지 않았다.&lt;br /&gt;또한 회사 프로젝트에서도 두 라이브러리를 사용하지 않기에 최대한 회사 프로젝트와 흡사하게 코드를 가져가고자 노력했다. (회사 프로젝트와 라이브러리 구성을 최대한 비슷하게 맞춰놓고 코드작성을 하니까 확실히 회사 코드 리딩하는데 도움이 많이 된 듯 하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 함으로써 사실 책/강의와 내 코드의 모양새는 많이 달라지게 되었는데, 이를 통해서 또한 해당 라이브러리 (immer, redux-actions) 를 쓸 때의 장점을 직접 생각해보고 알아갈 수 있었다. (&quot;아래&quot;에 따로 적겠다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 다양한 배경 지식이 있음은 학습에 큰 도움을 준다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 난 현업에서 계속 Vue를 다뤄왔기에 진입장벽이 상대적으로 낮았던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 비동기 상태 관리를 이해한 후에는, 아폴로 클라이언트를 다루며 겪게 된 optimistic updates를 어떻게 적용할지 나름의 고민과 코드 작성을 했다. 물론 어설프지만, 충분한 고민을 한 뒤에 알게되는 정답과 먼저 답안지를 보고 느끼게 되는 감동의 차이는 분명 하늘과 땅 차이인듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이래서 다양한 것들을 많이 접해보는 게 중요한거구나 싶다.&lt;/p&gt;
&lt;h1&gt;앞으로 할 것들&lt;/h1&gt;
&lt;ol 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;리덕스 사가에서는 페이지네이션 n+1 문제를 어떻게 해결하려나 궁금해진다. 아폴로 클라이언트에서 굉장히 애먹었던 문제인데...&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 여기에 있다. &lt;a href=&quot;https://github.com/yunseop-dev/react-todo&quot;&gt;https://github.com/yunseop-dev/react-todo&lt;/a&gt;&lt;/p&gt;
&lt;h5&gt;아래&lt;/h5&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;immer는 확실히 코드량이 줄게 된다. 불변성을 다 따져가며 코드 짜간다는게 여간 불편하지 않을 수 없다. 이럴 땐 vue / vuex가 그립기도...&lt;/li&gt;
&lt;li&gt;redux-actions를 쓰면서 느낀 것은, reducer를 짜며 switch문에서 type을 받았는데, 하다보니 action 내의 payload를 타입별로 묶어서 사용할 수 없다는 것이 좀 불편했다. 변수 선언을 해도 switch문 내에서는 중첩된 변수명을 사용할 수가 없어서, 이래서 redux-actions 쓰는구나 싶었다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>자바스크립트</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/173</guid>
      <comments>https://devtimothy.tistory.com/173#entry173comment</comments>
      <pubDate>Sat, 14 Aug 2021 01:15:03 +0900</pubDate>
    </item>
    <item>
      <title>React Testing Library 튜토리얼</title>
      <link>https://devtimothy.tistory.com/172</link>
      <description>&lt;h3&gt;React Testing Library 튜토리얼&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;이 내용은 &lt;a href=&quot;https://www.robinwieruch.de/react-testing-library&quot;&gt;이 곳&lt;/a&gt;의 내용을 번역한 것입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Kent C. Dodds의 React Testing Library (React Testing Library)이 Airbnb의 Enzyme의 대안으로 출시되었습니다. Enzyme은 React 개발자에게 React 컴포넌트의 내부를 테스트 할 수있는 유틸리티를 제공하지만 React Testing Library는 한 걸음 물러서서 &quot;React 컴포넌트를 완전히 신뢰하기 위해 React 컴포넌트를 테스트하는 방법&quot;에 대해 질문합니다. 컴포넌트의 구현 세부 정보를 테스트하는 대신 React Testing Library 개발자를 React 애플리케이션의 최종 사용자의 입장에서 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;이 React Testing Library 자습서에서는 React 컴포넌트를 단위 테스트 및 통합 테스트하는 데 필요한 모든 단계를 자신있게 살펴볼 것입니다.&lt;/p&gt;
&lt;h4&gt;Jest vs React Testing Library&lt;/h4&gt;
&lt;p&gt;React 초보자는 종종 React에서 테스트하기위한 도구를 혼동합니다. React Testing Library는 Jest의 대안이 아닙니다. 서로를 필요로하고 그들 모두가 명확한 작업을 가지고 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;현대 React에서 개발자는 Jest를 테스트하기 위해 사용하지 않을 것입니다. JavaScript 애플리케이션을 위한 가장 인기있는 테스트 프레임 워크이기 때문입니다. 테스트 스크립트를 사용하여 package.json을 설정하면 실행할 수있는 테스트 러너 외에도 Jest는 테스트를 위해 다음 기능을 제공합니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;describe('my function or component', () =&amp;gt; {
  test('does the following', () =&amp;gt; {

  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;describe-block이 테스트 스위트 인 반면, test-block (대신 이름을 지정할 수도 있음)은 테스트 케이스입니다. 테스트 스위트에는 여러 테스트 케이스가있을 수 있으며 테스트 케이스는 테스트 스위트에 있을 필요가 없습니다. 테스트 케이스에 넣은 것을 어설션 (예 : Jest에서)이라고하며 성공 (녹색) 또는 오류 (빨간색)로 판명됩니다. 여기에 성공해야하는 두 가지 assertion이 있습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;describe('true is truthy and false is falsy', () =&amp;gt; {
  test('true is truthy', () =&amp;gt; {
    expect(true).toBe(true);
  });

  test('false is falsy', () =&amp;gt; {
    expect(false).toBe(false);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 테스트 스위트와 assertion이 포함 된 테스트 케이스를 test.js 파일에 넣으면 Jest를 실행할 때 자동으로 선택합니다. 테스트 명령을 실행할 때 Jest의 테스트 실행기는 기본적으로 test.js 접미사가있는 모든 파일을 일치시킵니다. 맞춤 Jest 구성 파일에서 일치하는 패턴 및 기타 사항을 구성 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;create-react-app을 사용하는 경우 Jest (및 React Testing Library)가 기본적으로 설치와 함께 제공됩니다. 커스텀 React 설정을 사용하는 경우 Jest (및 React Testing Library)를 직접 설치하고 설정해야합니다.&lt;/p&gt;
&lt;p&gt;Jest의 테스트 실행기를 통해 (또는 package.json에서 사용중인 스크립트) 테스트를 실행하면 이전에 정의 된 두 테스트에 대해 다음 출력이 표시됩니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt; PASS  src/App.test.js
  true is truthy and false is falsy
    ✓ true is truthy (3ms)
    ✓ false is falsy

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.999s
Ran all test suites related to changed files.

Watch Usage
 &amp;rsaquo; Press a to run all tests.
 &amp;rsaquo; Press f to run only failed tests.
 &amp;rsaquo; Press q to quit watch mode.
 &amp;rsaquo; Press p to filter by a filename regex pattern.
 &amp;rsaquo; Press t to filter by a test name regex pattern.
 &amp;rsaquo; Press Enter to trigger a test run.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;케이스에 대해 녹색으로 변해야하는 모든 테스트를 실행 한 후 Jest는 추가 지침을 제공 할 수있는 대화 형 인터페이스를 제공합니다. 그러나 종종 모든 테스트에 대해 녹색으로 변해야하는 하나의 테스트 출력 일뿐입니다. 소스 코드 든 테스트 든 파일을 변경하는 경우 Jest는 모든 테스트를 다시 실행합니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;function sum(x, y) {
  return x + y;
}

describe('sum', () =&amp;gt; {
  test('sums up two values', () =&amp;gt; {
    expect(sum(2, 4)).toBe(6);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실제 JavaScript 프로젝트에서 테스트하려는 함수는 다른 파일에있는 반면 테스트는 테스트 할 함수를 가져 오는 테스트 파일에 있습니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import sum from './math.js';

describe('sum', () =&amp;gt; {
  test('sums up two values', () =&amp;gt; {
    expect(sum(2, 4)).toBe(6);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;본질적으로 그것은 Jest입니다. 아직 React 컴포넌트에 대한 것은 없습니다. Jest는 명령 줄에서 Jest로 테스트를 실행할 수있는 기능을 제공하는 테스트 실행기입니다. 또한 Jest는 테스트 스위트, 테스트 케이스 및 assertion을위한 기능을 제공합니다. 물론 프레임 워크는 이보다 더 많은 것을 제공합니다 (예 : 스파이, 모의, 스텁 등). 하지만 본질적으로 그것이 우리가 애초에 Jest가 필요한 이유를 이해하는 데 필요한 모든 것입니다.&lt;/p&gt;
&lt;p&gt;Jest와 달리 React Testing Library는 React 컴포넌트를 테스트하는 테스트 라이브러리 중 하나입니다. 이 범주에서 또 다른 인기있는 것은 앞서 언급 한 효소입니다. 다음 섹션에서는 React 컴포넌트를 테스트하기 위해 React Testing Library를 사용하는 방법을 볼 것입니다.&lt;/p&gt;
&lt;h2&gt;테스트 라이브러리 리액트: 컴포넌트 렌더링&lt;/h2&gt;
&lt;p&gt;create-react-app을 사용하는 경우 기본적으로 React Testing Library가 있습니다. 커스텀 React 설정 (예 : React with Webpack) 또는 다른 React 프레임 워크를 사용하는 경우 직접 설치해야합니다. 이 섹션에서는 React Testing Library로 테스트에서 React 컴포넌트를 렌더링하는 방법을 배웁니다. src / App.js 파일에서 다음 앱 함수 컴포넌트를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React from 'react';

const title = 'Hello React';

function App() {
  return &amp;lt;div&amp;gt;{title}&amp;lt;/div&amp;gt;;
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 &lt;i&gt;src/App.test.js&lt;/i&gt; 파일에서 테스트 :&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import React from 'react';
import { render } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React Testing Library의 렌더링 함수는 모든 JSX를 사용하여 렌더링합니다. 나중에 테스트에서 React 컴포넌트에 액세스 할 수 있어야합니다. 그것이 있다는 것을 확신하기 위해 React Testing Library의 디버그 기능을 사용할 수 있습니다 :&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    screen.debug();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;명령 줄에서 테스트를 실행 한 후 앱 컴포넌트의 HTML 출력이 표시되어야합니다. React Testing 라이브러리를 사용하여 컴포넌트에 대한 테스트를 작성할 때마다 컴포넌트를 먼저 렌더링 한 다음 테스트에서 React Testing Library의 렌더러에 표시되는 내용을 디버깅 할 수 있습니다. 이렇게 하면 보다 자신있게 테스트를 작성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;div&amp;gt;
      Hello React
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그것에 대한 좋은 점은 React Testing Library는 실제 컴포넌트에별로 신경 쓰지 않는다는 것입니다. 다양한 React 기능 (useState, 이벤트 핸들러, props)과 개념 (제어 된 컴포넌트)을 활용하는 다음 React 컴포넌트를 살펴 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React from 'react';

function App() {
  const [search, setSearch] = React.useState('');

  function handleChange(event) {
    setSearch(event.target.value);
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Search value={search} onChange={handleChange}&amp;gt;
        Search:
      &amp;lt;/Search&amp;gt;

      &amp;lt;p&amp;gt;Searches for {search ? search : '...'}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

function Search({ value, onChange, children }) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;label htmlFor=&quot;search&quot;&amp;gt;{children}&amp;lt;/label&amp;gt;
      &amp;lt;input
        id=&quot;search&quot;
        type=&quot;text&quot;
        value={value}
        onChange={onChange}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;앱 컴포넌트의 테스트를 다시 시작하면 디버그 함수에서 다음 출력이 표시되어야합니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label
          for=&quot;search&quot;
        &amp;gt;
          Search:
        &amp;lt;/label&amp;gt;
        &amp;lt;input
          id=&quot;search&quot;
          type=&quot;text&quot;
          value=&quot;&quot;
        /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;p&amp;gt;
        Searches for
        ...
      &amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React Testing Library는 사람처럼 React 컴포넌트와 상호 작용하는 데 사용됩니다. 사람이 보는 것은 React 컴포넌트에서 HTML로 렌더링 된 것이므로이 HTML 구조를 두 개의 개별 React 컴포넌트가 아닌 출력으로 보는 것입니다.&lt;/p&gt;
&lt;h2&gt;테스트 라이브러리 리액트: 엘리먼트 선택&lt;/h2&gt;
&lt;p&gt;React 컴포넌트를 렌더링한 후 리액트 테스트 라이브러리는 엘리먼트를 캡처할 수 있는 다양한 검색 기능을 제공합니다. 그런 다음 이러한 엘리먼트는 어설션이나 사용자 상호 작용에 사용됩니다. 그러나 우리가 이런 일을 하기 전에, 그들을 찾는 방법에 대해 알아봅시다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    screen.getByText('Search:');
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React Testing Library의 렌더링 함수의 렌더링 된 출력이 무엇인지 잘 모르는 경우 항상 React Testing Library의 디버그 함수를 사용하십시오. HTML 구조에 대해 알고 나면 React Testing Library의 화면 개체 기능을 사용하여 엘리먼트를 선택할 수 있습니다. 그런 다음 선택한 엘리먼트를 사용자 상호 작용 또는 assertion에 사용할 수 있습니다. 엘리먼트가 DOM에 있는지 확인하는 assertion을 수행합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    expect(screen.getByText('Search:')).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;엘리먼트를 찾을 수없는 경우 기본적으로 편리하게 오류를 발생시킵니다. 이것은 선택한 엘리먼트가 처음에 존재하지 않는다는 테스트를 작성하는 동안 힌트를 제공하는 데 유용합니다. 몇몇 사람들은 이 동작을 이용하여 : getByTextgetByTextexpect를 사용한 명시 적 assertion 대신 암시 적 assertion 대체와 같은 검색 기능을 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    // implicit assertion
    // because getByText would throw error
    // if element wouldn't be there
    screen.getByText('Search:');

    // explicit assertion
    // recommended
    expect(screen.getByText('Search:')).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 함수는 우리가 지금 사용하고있는 문자열을 입력으로 받아들이지 만 정규 표현식도 받습니다. 정확한 일치를 위해 문자열 인수가 사용되는 반면, 부분 일치에는 정규 표현식을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    // fails
    expect(screen.getByText('Search')).toBeInTheDocument();

    // succeeds
    expect(screen.getByText('Search:')).toBeInTheDocument();

    // succeeds
    expect(screen.getByText(/Search/)).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 기능은 React Testing Library에있는 여러 유형의 검색 기능 중 하나 일뿐입니다. 거기에 무엇이 있는지 봅시다.&lt;/p&gt;
&lt;h2&gt;테스트 라이브러리 리액트: 검색 타입&lt;/h2&gt;
&lt;p&gt;&lt;i&gt;Text&lt;/i&gt; 가 여러 검색 타입 중 하나라는 것을 대해 배웠습니다. Text는 종종 React Testing Library로 엘리먼트를 선택하는 일반적인 방법이지만, 또 다른 강점은 getByText, getByRole을 사용한 role입니다.&lt;/p&gt;
&lt;p&gt;이 함수는 일반적으로 aria-label 속성으로 엘리먼트를 검색하는 데 사용됩니다. 그러나 버튼 엘리먼트의 버튼과 같이 HTML 엘리먼트에 대한 암시적 role도 있습니다. 따라서 표시되는 텍스트뿐만 아니라 React Testing Library를 통한 접근성 role로 엘리먼트를 선택할 수 있습니다. 이것의 깔끔한 기능은 사용할 수없는 role을 나타내면 role을 제안한다는 것입니다. 둘 다 React Testing Library에서 가장 널리 사용되는 검색 함수입니다 . getByText, getByRole&lt;/p&gt;
&lt;p&gt;깔끔한 점 : 렌더링 된 컴포넌트의 HTML에서 사용할 수없는 role을 제공하면 선택 가능한 모든 role이 표시됩니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    screen.getByRole('');
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉, 이전 테스트는 실행 한 후 명령줄에 다음을 출력합니다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Unable to find an accessible element with the role &quot;&quot;

Here are the accessible roles:

document:

Name &quot;&quot;:
&amp;lt;body /&amp;gt;

--------------------------------------------------
textbox:

Name &quot;Search:&quot;:
&amp;lt;input
  id=&quot;search&quot;
  type=&quot;text&quot;
  value=&quot;&quot;
/&amp;gt;

--------------------------------------------------&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML 엘리먼트의 암시적 role 때문에 이 검색 타입으로 검색할 수 있는 최소한 텍스트 상자(여기) 엘리먼트가 있습니다.&lt;code&gt;&amp;lt;input /&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    expect(screen.getByRole('textbox')).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DOM에 이미 HTML 엘리먼트에 암시적 role이 첨부되어 있기 때문에 테스트를 위해 HTML 엘리먼트에 aria-role을 명시적으로 할당할 필요가 없습니다. 이것이 리액트 테스트 라이브러리에서 검색 기능에 강력한 경쟁력을 만드는 이유입니다.&lt;/p&gt;
&lt;p&gt;엘리먼트에 특정한 다른 검색 타입이 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;LabelText:&lt;/b&gt; getByLabelText: &lt;code&gt;&amp;lt;label for=&quot;search&quot; /&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PlaceholderText:&lt;/b&gt; getByPlaceholderText: &lt;code&gt;&amp;lt;input placeholder=&quot;Search&quot; /&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AltText:&lt;/b&gt; getByAltText: &lt;code&gt;&amp;lt;img alt=&quot;profile&quot; /&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DisplayValue:&lt;/b&gt; getByDisplayValue: &lt;code&gt;&amp;lt;input value=&quot;JavaScript&quot; /&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그리고 소스 코드의 HTML에 속성을 할당해야 하는 마지막 검색 타입인 &lt;i&gt;TestId가&lt;/i&gt; 있습니다. 결국, 리액트 테스트 라이브러리와 렌더링 된 리액트 컴포넌트에서 엘리먼트를 선택하려면 go-to 검색 타입이어야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;getByText&lt;/li&gt;
&lt;li&gt;getByRole&lt;/li&gt;
&lt;li&gt;getByLabelText&lt;/li&gt;
&lt;li&gt;getByPlaceholderText&lt;/li&gt;
&lt;li&gt;getByAltText&lt;/li&gt;
&lt;li&gt;getByDisplayValue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다시 말하지만, 이들은 React Testing Library에서 사용할 수있는 모든 다양한 검색 타입이다.&lt;/p&gt;
&lt;h2&gt;리액트 테스트 라이브러리: 검색 VARIANTS&lt;/h2&gt;
&lt;p&gt;검색 타입과 달리 검색 변형도 있습니다. React Testing Library의 검색 변형 중 하나는 getByText 또는 getByRole에 사용되는 getBy입니다. React 컴포넌트를 테스트 할 때 기본적으로 사용되는 검색 변형이기도합니다.&lt;/p&gt;
&lt;p&gt;다른 두 가지 검색 변형은 queryBy 및 findBy입니다. 둘 다 getBy가 액세스 할 수있는 동일한 검색 타입에 의해 확장 될 수 있습니다. 예를 들어 모든 검색 타입이 있는 queryBy는 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;queryByText&lt;/li&gt;
&lt;li&gt;queryByRole&lt;/li&gt;
&lt;li&gt;queryByLabelText&lt;/li&gt;
&lt;li&gt;queryByPlaceholderText&lt;/li&gt;
&lt;li&gt;queryByAltText&lt;/li&gt;
&lt;li&gt;queryByDisplayValue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And &lt;i&gt;findBy&lt;/i&gt; with all its search types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;findByText&lt;/li&gt;
&lt;li&gt;findByRole&lt;/li&gt;
&lt;li&gt;findByLabelText&lt;/li&gt;
&lt;li&gt;findByPlaceholderText&lt;/li&gt;
&lt;li&gt;findByAltText&lt;/li&gt;
&lt;li&gt;findByDisplayValue&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;getBy와 queryBy의 차이점은 무엇입니까?&lt;/h3&gt;
&lt;p&gt;방의 큰 질문 : 언제 getBy를 사용하고 다른 두 가지 변형 인 queryBy와 findBy를 언제 사용할지. getBy가 엘리먼트 또는 오류를 반환한다는 것을 이미 알고 있습니다. 오류를 반환하는 getBy의 편리한 side-effect입니다. 개발자로서 테스트에 문제가 있음을 조기에인지 할 수 있기 때문입니다. 그러나 이로 인해 없어야하는 엘리먼트를 확인하기가 어렵습니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    screen.debug();

    // fails
    expect(screen.getByText(/Searches for JavaScript/)).toBeNull();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;디버그 출력에 &quot;Searches for JavaScript&quot;이라는 텍스트가있는 엘리먼트가없는 것으로 표시 되더라도이 텍스트가있는 엘리먼트를 찾을 수 없기 때문에 getBy가 assertion을 만들기 전에 오류를 던지기 때문입니다. 존재하지 않는 엘리먼트를 assertion하기 위해 getBy를 queryBy와 교환 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    expect(screen.queryByText(/Searches for JavaScript/)).toBeNull();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 엘리먼트가 없다고 assertion 할 때마다 queryBy를 사용하십시오. 그렇지 않으면 기본값은 getBy입니다. 그렇다면 findBy는 어떻습니까?&lt;/p&gt;
&lt;h3&gt;언제 findBy를 사용해야 합니까?&lt;/h3&gt;
&lt;p&gt;findBy 검색 variant은 결국 거기에 있게 될 비동기 엘리먼트에 사용됩니다. 적절한 시나리오를 위해 다음 기능 (검색 입력 필드와 독립적)을 사용하여 React 컴포넌트를 확장 해 보겠습니다. 초기 렌더링 후 App 컴포넌트는 시뮬레이션 된 API에서 사용자를 가져옵니다. API는 사용자 객체로 즉시 해결되는 JavaScript promise를 반환하고 컴포넌트는 promise의 사용자를 컴포넌트 state에 저장합니다. 컴포넌트가 업데이트되고 다시 렌더링됩니다. 이후에 조건부 렌더링은 컴포넌트 업데이트 후 &quot;다음으로 로그인&quot;을 렌더링해야합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function getUser() {
  return Promise.resolve({ id: '1', name: 'Robin' });
}

function App() {
  const [search, setSearch] = React.useState('');
  const [user, setUser] = React.useState(null);

  React.useEffect(() =&amp;gt; {
    const loadUser = async () =&amp;gt; {
      const user = await getUser();
      setUser(user);
    };

    loadUser();
  }, []);

  function handleChange(event) {
    setSearch(event.target.value);
  }

  return (
    &amp;lt;div&amp;gt;
      {user ? &amp;lt;p&amp;gt;Signed in as {user.name}&amp;lt;/p&amp;gt; : null}

      &amp;lt;Search value={search} onChange={handleChange}&amp;gt;
        Search:
      &amp;lt;/Search&amp;gt;

      &amp;lt;p&amp;gt;Searches for {search ? search : '...'}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;promise가 resolve 됨으로 인해 첫 번째 렌더링에서 두 번째 렌더링까지 컴포넌트를 테스트하려면 promise가 비동기 적으로 해결 될 때까지 기다려야하므로 비동기 테스트를 작성해야합니다. 즉, 컴포넌트를 가져온 후 한 번 업데이트 한 후 사용자가 렌더링 될 때까지 기다려야합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', async () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    expect(screen.queryByText(/Signed in as/)).toBeNull();

    expect(await screen.findByText(/Signed in as/)).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;초기 렌더링 후 getBy 검색 변형 대신 queryBy를 사용하여 &quot;Signed in as&quot; 텍스트가 없다고 assertion합니다. 그런 다음 새 엘리먼트가 발견되기를 기다립니다. 그러면 결국 promise가 해결되고 컴포넌트가 다시 렌더링 될 때 발견됩니다.&lt;/p&gt;
&lt;p&gt;이것이 실제로 작동한다고 생각하지 않는다면 다음 두 디버그 함수를 포함하고 명령 줄에서 해당 출력을 확인하십시오.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', async () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    expect(screen.queryByText(/Signed in as/)).toBeNull();

    screen.debug();

    expect(await screen.findByText(/Signed in as/)).toBeInTheDocument();

    screen.debug();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아직 존재하지 않지만 결국 나타날 엘리먼트에 대해 findBy 또는 queryBy 보다는 findBy를 사용하십시오. 누락된 엘리먼트를 어설션하는 경우 queryBy를 사용합니다. 그렇지 않으면 기본 값은 getBy 입니다.&lt;/p&gt;
&lt;h3&gt;다중(multiple) 엘리먼트는 어떻습니까?&lt;/h3&gt;
&lt;p&gt;세 가지 검색 변형 인 getBy, queryBy 및 findBy에 대해 배웠습니다. 모두 검색 타입 (예 : 텍스트, role, PlaceholderText, DisplayValue)과 연관 될 수 있습니다. 이러한 모든 검색 함수가 하나의 엘리먼트 만 반환하는 경우 여러 엘리먼트가 있는지 확인하는 방법 (예 : React 컴포넌트의 목록). 모든 검색 변형은 모든 단어로 확장 할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;getAllBy&lt;/li&gt;
&lt;li&gt;queryAllBy&lt;/li&gt;
&lt;li&gt;findAllBy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이들 모두는 엘리먼트 배열을 반환하고 검색 타입과 다시 연결될 수 있습니다.&lt;/p&gt;
&lt;h3&gt;Assertive Functions&lt;/h3&gt;
&lt;p&gt;Assertive 함수는 assertion의 오른쪽에서 발생합니다. 이전 테스트에서 두 개의 assertive 함수, 및. 둘 다 주로 React Testing Library에서 엘리먼트가 있는지 여부를 확인하는 데 사용됩니다.&lt;/p&gt;
&lt;p&gt;일반적으로 이러한 모든 Assertive Functions은 Jest에서 유래되었습니다. 그러나 React Testing Library는 이 API들을 확장한다. 이러한 모든 Assertive Functions은 create-react-app을 사용할 때 이미 설정된 추가 패키지로 제공됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;toBeDisabled&lt;/li&gt;
&lt;li&gt;toBeEnabled&lt;/li&gt;
&lt;li&gt;toBeEmpty&lt;/li&gt;
&lt;li&gt;toBeEmptyDOMElement&lt;/li&gt;
&lt;li&gt;toBeInTheDocument&lt;/li&gt;
&lt;li&gt;toBeInvalid&lt;/li&gt;
&lt;li&gt;toBeRequired&lt;/li&gt;
&lt;li&gt;toBeValid&lt;/li&gt;
&lt;li&gt;toBeVisible&lt;/li&gt;
&lt;li&gt;toContainElement&lt;/li&gt;
&lt;li&gt;toContainHTML&lt;/li&gt;
&lt;li&gt;toHaveAttribute&lt;/li&gt;
&lt;li&gt;toHaveClass&lt;/li&gt;
&lt;li&gt;toHaveFocus&lt;/li&gt;
&lt;li&gt;toHaveFormValues&lt;/li&gt;
&lt;li&gt;toHaveStyle&lt;/li&gt;
&lt;li&gt;toHaveTextContent&lt;/li&gt;
&lt;li&gt;toHaveValue&lt;/li&gt;
&lt;li&gt;toHaveDisplayValue&lt;/li&gt;
&lt;li&gt;toBeChecked&lt;/li&gt;
&lt;li&gt;toBePartiallyChecked&lt;/li&gt;
&lt;li&gt;toHaveDescription&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;리액트 테스트 라이브러리: 이벤트 실행&lt;/h2&gt;
&lt;p&gt;지금까지 getBy (및 queryBy)를 사용하여 React 컴포넌트에서 엘리먼트가 렌더링되었는지 여부와 다시 렌더링 된 React 컴포넌트에 원하는 엘리먼트 (findBy)가 있는지 여부 만 테스트했습니다. 실제 사용자 인터랙션은 어떻습니까? 사용자가 입력 필드에 입력하면 컴포넌트가 다시 렌더링 될 수 있으며 (예제처럼) 새 값이 표시되어야 합니다 (또는 어딘가에서 사용).&lt;/p&gt;
&lt;p&gt;React Testing Library의 fireEvent 함수를 사용하여 엔드 유저와의 인터랙션을 시뮬레이션 할 수 있습니다. 이것이 입력 필드에서 어떻게 작동하는지 봅시다 :&lt;/p&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    screen.debug();

    fireEvent.change(screen.getByRole('textbox'), {
      target: { value: 'JavaScript' },
    });

    screen.debug();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fireEvent 함수는 엘리먼트 (여기서는 텍스트 상자 role의 입력 필드)와 이벤트 (여기서는 &quot;JavaScript&quot;값을 가진 이벤트)를 받습니다. 디버그 함수의 출력은 이벤트 전후의 HTML 구조를 보여야 합니다. 입력 필드의 새 값이 적절하게 렌더링되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;또한 컴포넌트가 사용자를 가져 오기 때문에 App 컴포넌트와 같이 비동기 작업에 관여하는 경우 다음 경고가 표시 될 수 있습니다. &quot;Warning: An update to App inside a test was not wrapped in act(...).&quot; 우리에게 이것은 비동기 작업이 발생하고 컴포넌트가 이를 처리하는지 확인해야 함을 의미합니다. 종종 이것은 React Testing Library의 act 함수로 수행 할 수 있지만 이번에는 사용자가 해결할 때까지 기다려야 합니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;describe('App', () =&amp;gt; {
  test('renders App component', async () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    // wait for the user to resolve
    // needs only be used in our special case
    await screen.findByText(/Signed in as/);

    screen.debug();

    fireEvent.change(screen.getByRole('textbox'), {
      target: { value: 'JavaScript' },
    });

    screen.debug();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그런 다음 이벤트 전후의 assertions을 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;describe('App', () =&amp;gt; {
  test('renders App component', async () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    // wait for the user to resolve
    // needs only be used in our special case
    await screen.findByText(/Signed in as/);

    expect(screen.queryByText(/Searches for JavaScript/)).toBeNull();

    fireEvent.change(screen.getByRole('textbox'), {
      target: { value: 'JavaScript' },
    });

    expect(screen.getByText(/Searches for JavaScript/)).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우리는 queryBy 검색 variant을 사용하여 이벤트 전에 엘리먼트가 없는지 확인하고 getBy 검색 variant을 사용하여 이벤트 이후에 엘리먼트가 있는지 확인했습니다. 때로는 사람들이 후자의 assertion에 대해 queryBy를 사용하는 것을 볼 수 있습니다. 왜냐하면 거기에 있어야 할 엘리먼트에 관해서는 getBy와 유사하게 사용할 수 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;그거다. 테스트에서 해결해야하는 비동기 동작 외에도 React Testing Library의 fireEvent 함수를 간단하게 사용할 수 있으며 나중에 어설션을 만들 수 있습니다.&lt;/p&gt;
&lt;h3&gt;리액트 테스트 라이브러리: 사용자 이벤트&lt;/h3&gt;
&lt;p&gt;React Testing Library는 fireEvent API 위에 구축되는 확장 된 사용자 이벤트 라이브러리와 함께 제공됩니다. 이전에는 사용자 상호 작용을 트리거하기 위해 fireEvent를 사용했습니다. 이번에는 userEvent API가 fireEvent API보다 실제 브라우저 동작을 더 가깝게 모방하기 때문에 userEvent를 대체로 사용할 것입니다.&lt;/p&gt;
&lt;p&gt;For example, a triggers only a event whereas triggers a event, but also , , and events.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import App from './App';

describe('App', () =&amp;gt; {
  test('renders App component', async () =&amp;gt; {
    render(&amp;lt;App /&amp;gt;);

    // wait for the user to resolve
    await screen.findByText(/Signed in as/);

    expect(screen.queryByText(/Searches for JavaScript/)).toBeNull();

    await userEvent.type(screen.getByRole('textbox'), 'JavaScript');

    expect(
      screen.getByText(/Searches for JavaScript/)
    ).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;가능하면 리액트 테스트 라이브러리를 사용할 때 fireEvent를 통해 사용자 이벤트를 사용합니다. 이 글을 쓰는 시점에서 userEvent는 fireEvent의 모든 기능을 포함하지 않지만 나중에 변경될 수 있습니다.&lt;/p&gt;
&lt;h2&gt;리액트 테스트 라이브러리: 콜백 핸들러&lt;/h2&gt;
&lt;p&gt;때로는 단위 테스트로 React 컴포넌트를 분리하여 테스트합니다. 종종 이러한 컴포넌트에는 사이드 이펙트나 state가 없지만 입력 (props) 및 출력 (JSX, 콜백 핸들러) 만 있습니다. 우리는 이미 컴포넌트와 props가 주어 졌을 때 렌더링 된 JSX를 어떻게 테스트 할 수 있는지 보았습니다. 이제이 검색 컴포넌트에 대한 콜백 핸들러를 테스트합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Search({ value, onChange, children }) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;label htmlFor=&quot;search&quot;&amp;gt;{children}&amp;lt;/label&amp;gt;
      &amp;lt;input
        id=&quot;search&quot;
        type=&quot;text&quot;
        value={value}
        onChange={onChange}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;모든 렌더링 및 어설션은 이전과 같이 발생합니다. 그러나 이번에는 Jest의 유틸리티를 사용하여 컴포넌트에 전달되는 함수를 모의합니다. 그런 다음 입력 필드에서 사용자 상호 작용을 트리거 한 후 콜백 함수가 호출되었음을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;describe('Search', () =&amp;gt; {
  test('calls the onChange callback handler', () =&amp;gt; {
    const onChange = jest.fn();

    render(
      &amp;lt;Search value=&quot;&quot; onChange={onChange}&amp;gt;
        Search:
      &amp;lt;/Search&amp;gt;
    );

    fireEvent.change(screen.getByRole('textbox'), {
      target: { value: 'JavaScript' },
    });

    expect(onChange).toHaveBeenCalledTimes(1);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서도 userEvent가 fireEvent와 같이 브라우저의 사용자 행동과 더 가깝게 일치하는지 확인할 수 있습니다. fireEvent는 콜백 함수를 한 번만 호출하여 change 이벤트를 실행하지만 userEvent는 모든 키 입력에 대해 이를 트리거합니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;describe('Search', () =&amp;gt; {
  test('calls the onChange callback handler', async () =&amp;gt; {
    const onChange = jest.fn();

    render(
      &amp;lt;Search value=&quot;&quot; onChange={onChange}&amp;gt;
        Search:
      &amp;lt;/Search&amp;gt;
    );

    await userEvent.type(screen.getByRole('textbox'), 'JavaScript');

    expect(onChange).toHaveBeenCalledTimes(10);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;어쨌든, React Testing Library는 React 컴포넌트를 너무 격리하지 않고 다른 컴포넌트와의 통합 (통합 테스트)에서 테스트하도록 권장합니다. 그래야만 state 변경이 DOM에 적용되었는지 여부와 사이드 이펙트가 적용되었는지 여부를 실제로 테스트 할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;리액트 테스트 라이브러리: ASYNCHRONOUS / ASYNC&lt;/h2&gt;
&lt;p&gt;특정 엘리먼트가 findBy 검색 variant과 함께 나타날 때까지 기다리기 위해 React Testing Library로 테스트 할 때 async await를 사용하는 방법을 이전에 보았습니다. 이제 React에서 데이터 가져 오기를 테스트하기 위한 간단한 예제를 살펴 보겠습니다. 원격 API에서 데이터를 가져 오기 위해 axios를 사용하는 다음 React 컴포넌트를 살펴 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React from 'react';
import axios from 'axios';

const URL = 'http://hn.algolia.com/api/v1/search';

function App() {
  const [stories, setStories] = React.useState([]);
  const [error, setError] = React.useState(null);

  async function handleFetch(event) {
    let result;

    try {
      result = await axios.get(`${URL}?query=React`);

      setStories(result.data.hits);
    } catch (error) {
      setError(error);
    }
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button type=&quot;button&quot; onClick={handleFetch}&amp;gt;
        Fetch Stories
      &amp;lt;/button&amp;gt;

      {error &amp;amp;&amp;amp; &amp;lt;span&amp;gt;Something went wrong ...&amp;lt;/span&amp;gt;}

      &amp;lt;ul&amp;gt;
        {stories.map((story) =&amp;gt; (
          &amp;lt;li key={story.objectID}&amp;gt;
            &amp;lt;a href={story.url}&amp;gt;{story.title}&amp;lt;/a&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;버튼을 클릭하면 Hacker News API에서 기사 목록을 가져옵니다. 모든 것이 올바르게 진행되면 React에서 목록으로 렌더링 된 스토리 목록이 표시됩니다. 문제가 발생하면 오류가 표시됩니다. 앱 컴포넌트에 대한 테스트는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import React from 'react';
import axios from 'axios';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import App from './App';

jest.mock('axios');

describe('App', () =&amp;gt; {
  test('fetches stories from an API and displays them', async () =&amp;gt; {
    const stories = [
      { objectID: '1', title: 'Hello' },
      { objectID: '2', title: 'React' },
    ];

    axios.get.mockImplementationOnce(() =&amp;gt;
      Promise.resolve({ data: { hits: stories } })
    );

    render(&amp;lt;App /&amp;gt;);

    await userEvent.click(screen.getByRole('button'));

    const items = await screen.findAllByRole('listitem');

    expect(items).toHaveLength(2);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;App 컴포넌트를 렌더링하기 전에 API가 mock 되었는지 확인합니다. 우리의 경우 메서드에서 axios의 반환 값이 mock 됩니다. 그러나 데이터 fetch를 위해 다른 라이브러리 또는 브라우저의 fetch API를 사용하는 경우 이러한 항목을 mock 처리해야합니다.&lt;/p&gt;
&lt;p&gt;API를 mock하고 컴포넌트를 렌더링 한 후 userEvent API를 사용하여 API 요청으로 연결되는 버튼을 클릭합니다. 요청이 비동기식이므로 컴포넌트가 업데이트 될 때까지 기다려야합니다. 이전과 마찬가지로 React Testing Library의 findBy 검색 변형을 사용하여 결국 나타나는 엘리먼트를 기다립니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import React from 'react';
import axios from 'axios';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import App from './App';

jest.mock('axios');

describe('App', () =&amp;gt; {
  test('fetches stories from an API and displays them', async () =&amp;gt; {
    ...
  });

  test('fetches stories from an API and fails', async () =&amp;gt; {
    axios.get.mockImplementationOnce(() =&amp;gt;
      Promise.reject(new Error())
    );

    render(&amp;lt;App /&amp;gt;);

    await userEvent.click(screen.getByRole('button'));

    const message = await screen.findByText(/Something went wrong/);

    expect(message).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 마지막 테스트는 실패한 React 컴포넌트의 API 요청을 테스트하는 방법을 보여줍니다. 성공적으로 해결되는 promise로 API를 mock하는 대신 오류와 함께 promise를 거부합니다. 컴포넌트를 렌더링하고 버튼을 클릭 한 후 오류 메시지가 나타날 때까지 기다립니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import React from 'react';
import axios from 'axios';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import App from './App';

jest.mock('axios');

describe('App', () =&amp;gt; {
  test('fetches stories from an API and displays them', async () =&amp;gt; {
    const stories = [
      { objectID: '1', title: 'Hello' },
      { objectID: '2', title: 'React' },
    ];

    const promise = Promise.resolve({ data: { hits: stories } });

    axios.get.mockImplementationOnce(() =&amp;gt; promise);

    render(&amp;lt;App /&amp;gt;);

    await userEvent.click(screen.getByRole('button'));

    await act(() =&amp;gt; promise);

    expect(screen.getAllByRole('listitem')).toHaveLength(2);
  });

  test('fetches stories from an API and fails', async () =&amp;gt; {
    ...
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;완전성을 위해 이 마지막 테스트는 HTML이 표시 될 때까지 기다리지 않으려는 경우에도 작동하는것보다 명시적인 방식으로 promise를 기다리는 방법을 보여줍니다.&lt;/p&gt;
&lt;p&gt;결국 React Testing Library를 사용하여 React에서 비동기 동작을 테스트하는 것은 그리 어렵지 않습니다. Jest를 사용하여 외부 모듈 (여기서는 원격 API)을 모의 한 다음 데이터를 기다리거나 테스트에서 React 컴포넌트를 다시 렌더링해야합니다.&lt;/p&gt;
&lt;p&gt;React Testing Library는 React 컴포넌트에 대한 필자의 테스트 라이브러리입니다. 저는 이전에 Airbnb의 Enzyme을 계속 사용해 왔지만 React Testing Library가 구현 세부 사항이 아닌 사용자 행동을 테스트하는 방식을 좋아합니다. 실제 사용자 시나리오와 유사한 테스트를 작성하여 사용자가 애플리케이션을 사용할 수 있는지 테스트하고 있습니다.&lt;/p&gt;</description>
      <category>React</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/172</guid>
      <comments>https://devtimothy.tistory.com/172#entry172comment</comments>
      <pubDate>Sun, 1 Nov 2020 18:13:19 +0900</pubDate>
    </item>
    <item>
      <title>에어팟 프로 구매여정</title>
      <link>https://devtimothy.tistory.com/171</link>
      <description>&lt;p&gt;&lt;span data-offset-key=&quot;7qh0h-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;* 에어팟 프로 구매여정&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;b1fj5-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;aeuq7-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;1. 처음에는 중고나라와 당근마켓에 키워드 알람설정을 해서 원하는 가격대가 나올때까지 예의주시 하고 있었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;c01kk-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;se57-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;2. 싸게 나오는 것들은 19~20만원까지 풀리는 것들을 보았기에 20만원 근처 가격대로 구매하면 되겠다! 라는 생각에 적절한 가격의 매물이 올라오기를 기다렸다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;bqi43-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;5maa1-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;3. 근데 매물을 보다보면 미개봉 상품을 판매한다는 글이 올라오는데 애매하게 25만원 정도에 판매하는 경우가 많이 있었다. 내가 저 가격에 살거면 무료배송에 무료반품 가능한 쿠팡에서 사고말지라는 생각이 들었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;frm2-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;cljj7-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;4. 요새 또 에어팟 프로를 똑같이 복제한 중국산도 기승을 부리고 있다하여 걱정이 이만저만이 아니었다. 그래서 인터넷에서 중국산 구별방법을 찾아서 핸드폰에 메모를 해놓기도 했다. 심지어는 무게도 다르다고 해서 커피 원두 계량할때 쓰는 저울을 직거래할때 가져가려고 마음먹었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;5p88j-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;56m4q-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;5. 택배거래를 하자니 어렵고, 그래서 회사 근처 혹은 집 근처에서 매물이 올라오면 무조건 직거래를 해야겠다고 마음먹었다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;3r5f7-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;417di-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;6. 마침 마음에 드는 물건이 나와서 확인해보면 집에서 약간 먼 애매한 위치에서 직거래를 하게 되었다. 이마저도 서로 커뮤니케이션이 원활하지 못해서 거래가 불발이 되었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;a84tb-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;64i8f-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;7. 몇주동안 이렇게 지내다보니 &quot;내가 몇만원 아끼자고 이러면서까지 살아야하나&quot; 라는 현자타임이 오기 시작했다. 그래서 쿠팡에서 판매하는 에어팟 프로를 사고자 다시 쿠팡 앱을 기웃거리고 있었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;20hst-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;a59cd-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;8. 근데 이게 왠일일까. 쿠팡 로켓배송하는 에어팟에 중고 상품도 판매한다는 문구를 보았다. 가격도 보니까 내가 찾는 가격대인 21만원대 가격으로 책정되어 있었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;34bdd-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;1fp3m-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;9. 상품 설명을 보니 흠집이 있을수 있지만 제품 기능상에 문제는 없는 제품이라는 설명이 있었다. 어차피 무료배송 무료반품 가능하니까, 속는셈치고 한번 사보자. 라는 마음이 들어서 주문을 했다. 게다가 상품설명에 떡하니 적혀있는 &quot;애플 코리아 인증 정품&quot;.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;4af8e-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;bdls-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;10. 주문한 다음날 새벽에 제품이 도착했고, 제품 개봉을 해보니 아무래도 스카치테잎으로 박스를 봉해서 보내왔지만 제품 자체에 하자가 있거나 하진 않았다. 흠집도 안보이고 깔끔했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;dfr15-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;bkgc8-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;11. 게다가 직거래 하러 어디 가지 않아도 되고, 중국산 아닐까, 눈탱이 맞지 않을까 걱정하지 않아도 된다는 점이 너무나도 만족스러웠다. 커피 개량용 저울 들고다니며 거래자 앞에서 수평맞게 저울 맞춰놓고 그 위에 이어폰 올려놓고 무게 맞네 재보는 뻘쭘한 시간을 보낼 생각을 안해도 되니 더욱 좋았다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;133r4-0-0&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span data-offset-key=&quot;de34d-0-0&quot;&gt;&lt;span data-text=&quot;true&quot;&gt;12. 물론 그간 지하철에서 중고 매물 뒤적거리며 허비했던 시간을 고려하면 새거 사는게 제일 좋다. 고민하지말고 지르라.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>기타</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/171</guid>
      <comments>https://devtimothy.tistory.com/171#entry171comment</comments>
      <pubDate>Sun, 23 Aug 2020 14:15:42 +0900</pubDate>
    </item>
    <item>
      <title>(끄적거림) 독후감: 소트웍스 엔솔러지</title>
      <link>https://devtimothy.tistory.com/170</link>
      <description>&lt;p&gt;# 소트웍스 엔솔러지 독후감.&lt;/p&gt;
&lt;p&gt;객체지향 공부 한창 꽂혀있을 때, 돌아다니다가 우연히 봤던 &amp;lt;객체지향 생활 체조&amp;gt; 포스트를 보고 중고 책 서점으로 주문했던 책이다.&lt;/p&gt;
&lt;p&gt;마틴 파울러 및 공동저자들이 쓴 책이다. 일단 내용이 잘 안 읽혔다. 현재 80%정도 읽고 접어둔 상태이다. 아마도 오래 된 내용이어서가 가장 큰 이유이지 않을까 싶다.&lt;/p&gt;
&lt;p&gt;그래도 굉장히 굵직하고, 좋은 내용들이 많이 있다.&lt;/p&gt;
&lt;p&gt;TDD, 폴리글랏 프로그래밍, 객체지향 생활 체조, 함수형 프로그래밍, 반복 관리자 (이는 후에 애자일 개발 방식 등으로 발전되는 부분들을 다룬다.), DDD, 리팩토링, CI/CD 등...&lt;/p&gt;
&lt;p&gt;책이 하도 안읽혀서, 다른 분들 블로그는 어떻게 쓰셨나 보니까, 어떤 분이 한 8~9년전에 쓴 블로그 내용을 보게됐는데 &quot;이건 마틴 파울러가 CEO니까 가능한 얘기겠지만...&quot; 이라고 쓴 것을 보았다.&lt;/p&gt;
&lt;p&gt;요새는 정말 많은 회사들이 이 책에 적힌 내용들을 실행한다는 점에서 마틴 파울러 옹의 혜안에 감동하고, 감사하게 됐다.&lt;/p&gt;
&lt;p&gt;그런 의미에서 소트웍스 엔솔러지 책은 이제 접고, 오늘 택배 온 리팩토링 2판을 읽기 시작하도록 하겠다.&lt;/p&gt;
&lt;p&gt;* 오늘의 교훈: 너무 오래 된 책은 이해하려고 애쓰며 붙잡지 말고 가볍게 훑도록 하자.&lt;/p&gt;</description>
      <category>기타</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/170</guid>
      <comments>https://devtimothy.tistory.com/170#entry170comment</comments>
      <pubDate>Wed, 1 Apr 2020 00:48:08 +0900</pubDate>
    </item>
    <item>
      <title>Vue 하다가 React 하는 개발자 이야기 7 - Apollo Client의 Mutation 삭제 문제</title>
      <link>https://devtimothy.tistory.com/169</link>
      <description>&lt;h1&gt;Vue 하다가 React 하는 개발자 이야기 7 - Apollo Client의 Mutation 삭제 문제&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;B4B406A8-0716-4E6E-A23D-CB116DB18C7F.jpg&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1pDYX/btqCYEUA5KV/pBh4wHcix2LrtlneK6w5A0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1pDYX/btqCYEUA5KV/pBh4wHcix2LrtlneK6w5A0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1pDYX/btqCYEUA5KV/pBh4wHcix2LrtlneK6w5A0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1pDYX%2FbtqCYEUA5KV%2FpBh4wHcix2LrtlneK6w5A0%2Fimg.jpg&quot; data-filename=&quot;B4B406A8-0716-4E6E-A23D-CB116DB18C7F.jpg&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;654&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;오랜만에 포스팅을 올립니다. 그간 바쁘기도 하고, 사실 귀찮은 게 많아서 미루고 미루던 포스팅을 이제야 쓰게 됩니다.&lt;/li&gt;
&lt;li&gt;오늘은 Apollo Client의 refetch에 대해서 생각해보게 된 이야기를 해보고자 합니다. 위 사진처럼 카테고리 리스트를 만들면서 생기게 된 일입니다.&lt;/li&gt;
&lt;li&gt;리스트에 항목이 추가 / 삭제 되는 경우에는 보통 어떻게 처리를 하는지가 궁금해졌습니다. 리스트가 pagination을 하다보니까 mutation에 baseMutationOption을 줄 때 update() 내에서 캐시를 추가하기에는 좀 애매하다는 느낌이 들었기 때문입니다.&lt;/li&gt;
&lt;li&gt;위 목록을 보시면, 수정 버튼을 눌러 모달 창을 띄워서 각 항목을 mutation하고, 이 mutation에서 만일 카테고리 타입이 변경되었다면, 위 리스트에도 영향이 가게 될 것입니다. 이를 apollo cache상에서 어떻게 하면 처리할 수 있을까?를 생각해보게 되었는데요. &lt;b&gt;쿼리가 의존하는 varaibles의 맥락을 공유해야 한다&lt;/b&gt;는 점이 마음에 걸렸습니다. mutation 내에서 list를 불러오는 쿼리의 variables 맥락을 알아야 할 이유는 없으니까요. SRP에도 맞지 않고, 매우 나쁜 코딩이 될 것 같았습니다.&lt;/li&gt;
&lt;li&gt;사실 가장 쉬운 방법은 refetch를 하거나 페이지네이션 캐시 정책을 cache-and-network로 동기화하여 불러오는 것입니다.&lt;/li&gt;
&lt;li&gt;위 사진을 보시면 아시겠지만, 현재 작업중인 프로젝트는 admin 페이지이고 페이지네이션이 매겨져있습니다. 그리고 아무래도 자주 동기화가 이뤄지는 앱이다보니 refetch는 어쩔 수 없는 부분이라는 판단이 들었습니다.&lt;/li&gt;
&lt;li&gt;그러나 이 refetch를 줄이기 위해서 고민할 가치는 있습니다. 만일 목록이 infinity scroll 기반의 유저용 앱이라면 아마도 refetch 보다는 다른 설계를 고민하게 되었을것입니다.&lt;/li&gt;
&lt;li&gt;그리고 나중에 살펴보니, 이 문제가 굉장히 오래 된 문제라고 하더군요. 국내 GraphQL 커뮤니티에서 이 &lt;a href=&quot;https://github.com/apollographql/apollo-feature-requests/issues/5&quot;&gt;링크&lt;/a&gt;를 통해서 많은 분들이 저와 같은 고민을 하고 계셨다는 사실을 알게되었습니다.   이슈 넘버가 #5번인데, #899번에서 다시 동일한 문제로 언급되는 것을 확인할 수 있습니다. 아주 오래 된 문제이군요.&lt;/li&gt;
&lt;li&gt;아무래도 오픈소스이다보니, 사용자들의 지속적인 참여가 많이 필요한 듯 합니다. 저 역시도 기회가 닿는대로 기여할 수 있는 사람이 되면 좋겠습니다.  &lt;/li&gt;
&lt;/ol&gt;</description>
      <category>React</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/169</guid>
      <comments>https://devtimothy.tistory.com/169#entry169comment</comments>
      <pubDate>Sat, 28 Mar 2020 00:50:36 +0900</pubDate>
    </item>
    <item>
      <title>Vue 하다가 React 하는 개발자 이야기 6</title>
      <link>https://devtimothy.tistory.com/168</link>
      <description>&lt;h1&gt;Vue 하다가 React 하는 개발자 이야기 6&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;오늘은 GraphQL의 이야기를 먼저 해보고자 합니다. 앞서 출시한 프로젝트가 안정화 되어가고, 코로나 사태 등으로 인해 대외적으로 사용자들에게 집중하기보다는, 내실을 다지는 기간으로 보내고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;사실 API 쪽도 Apollo Federation 같은 기술을 이용해서 점진적인 코드 변경을 해가고자 했으나, 오래된 버젼의 라이브러리를 사용해오고, 코드가 너무 스파게티처럼 얽히고 설켜있어서 더 이상 유지보수가 어렵다고 판단되어 리팩토링이 아닌 code-first 방식으로 Re-writing 작업을 하고 있습니다. 주변에 같은 고민을 했던 사람들이 있나 싶어 찾아보아도 다들 Schema-first 에서 Code-first로 점진적인 Migration이 쉬운 일이 아님을 느꼈습니다. (링크: &lt;a href=&quot;https://docs.google.com/presentation/d/1XQWrYrz8wr8DzXV4e8Ffhhb9pjrAu8BXq2xrNdE1pW4/edit#slide=id.p&quot;&gt;&lt;strong&gt;Schema-First에서 Code-First로 Migration 하기&lt;/strong&gt;&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;구 버전 GraphQL API (schema-first) 에서는 타입을 지정하더라도 이가 변경되면 감지하지 못하는 경우가 있었습니다. 그러나 현재 작업되는 API (code-first) 에서는 type safe를 철저하게 감지해주는 것을 보면서 큰 장점임을 느낍니다. schema-first에서는 런타임에서만 스키마가 변경되거나 하는 것을 알 수 있었죠. 그래서 스키마가 많아질수록 실수도 많이 하게 되고, 코드 유지보수가 힘들었습니다. code-first로 넘어오면서 type safe가 보장되고, 보다 안전하게 코딩을 할 수 있는 것 같습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2번에서 걸어놓은 링크를 보니, Graphql-cli, GraphQL inspector 등의 도구들이 우리 개발하는 삶을 또한 윤택하게 해줄 것만 같네요. 내일 한번 시도해봐야겠습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;오늘 리액트로 작업하면서는 특이한 이슈를 만났습니다. Functional Component가 GraphQL Query / Mutation을 호출하는 시점에 오류가 발생하면, error 값을 그대로 유지하고 있는 오류였습니다. &lt;code&gt;if (error)&lt;/code&gt; 문 안에 alert 창을 띄워놨는데  re-rendering 되는 시점마다 alert를 실행하는 것이였죠.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;    const [getSpotList, { loading, error, data }] = useSpotsLazyQuery({
    variables: { ... },
  });
  // ... 생략 ...
    if (error) {
    window.alert(&amp;quot;에러 발생&amp;quot;)
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;컴포넌트 내에서 한번 발생한 error는 초기화되지 않고 남아 있는다는 점이 문제였는데, 이를 아래와 같이 해결했습니다. 이런 방식으로 해결하니 Query의 호출 시점 한번에만 alert이 발생하니, 렌더링 문제에 걸릴 일이 없었습니다. 이 문제를 부딪힌 덕에 React 내의 동작 방식을 다시한번 생각해 볼 수 있었습니다. ^^&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;    const [getMyList, { loading, error, data }] = useMyLazyQuery({
    variables: { ... },
        onError(error) {
      window.alert(&amp;quot;에러 발생&amp;quot;)
    }
  });&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;내일은 또 어떤 이슈가 저를 기다리고 있을까요? 피곤하지만 즐거운 마음으로 잠을 청합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>React</category>
      <category>code-first</category>
      <category>graphql</category>
      <category>JavaScript</category>
      <category>Programming</category>
      <category>React</category>
      <category>schema-first</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/168</guid>
      <comments>https://devtimothy.tistory.com/168#entry168comment</comments>
      <pubDate>Mon, 9 Mar 2020 23:16:09 +0900</pubDate>
    </item>
    <item>
      <title>Vue 하다가 React 하는 개발자 이야기 5</title>
      <link>https://devtimothy.tistory.com/167</link>
      <description>&lt;h3&gt;Vue 하다가 React 하는 개발자 이야기 5&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;오늘은 글을 두 개 씁니다. 제 친구가 리액트의 상태 관리에 대해 의견과, 아폴로 클라이언트 상태 관리에 대해 코드 공유를 해주었습니다. 짧게 공유해보고자 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// local state 지정
const initialData: IsLoggedIn = {
  isLoggedIn: !!getToken(),
}

cache.writeData({ data: initialData });

// 불러오기
const { data } = useQuery&amp;lt;IsLoggedIn&amp;gt;(IS_LOGGED_IN)
const isLoggedIn = !!data?.isLoggedIn

// Query
export const IS_LOGGED_IN = gql`
  query IsLoggedIn {
    isLoggedIn @client(always: true)
  }
`

// 상태 업데이트
login({
    variables: { data },
  update: (store, { data}) =&amp;gt; {
    if (data?.login) {
      store.writeQuery&amp;lt;IsLoggedIn&amp;gt;({ query: IS_LOGGED_IN, data : { isLoggedIn: true } })
      saveToken(data.login.token)
    }
  },
})&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;코드를 살펴보니, GraphQL과 완전히 사용방법이 같으니, 코드의 일관성 유지하는데 좋아보였습니다. 일단 친구의 의견은&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;GraphQL을 쓰면 Redux 말고 Apollo Client로도 충분히 가능하다.&lt;/li&gt;
&lt;li&gt;관리자 페이지 작성하는 정도면 props로 상태를 전달하는 것만으로도 충분할 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;였습니다. 친구는 Apollo Client로 상태 관리를 안 쓸 이유가 없다고 생각했습니다. 그러나 앞선 글 (&lt;a href=&quot;https://devtimothy.tistory.com/164&quot;&gt;링크&lt;/a&gt;) 에서 충분히 고민한 내용이기에 내용은 생략합니다.&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;또한 커뮤니티에서 MobX와 관련된 질문을 하신 분이 있어서 아래에 내용 정리를 해보았습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;MobX&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;러닝커브가 적지는 않다.&lt;/li&gt;
&lt;li&gt;ES 표준과 React 의 방향이 틀어져 대규모 지각 변동이 있다.&lt;ul&gt;
&lt;li&gt;Hook 대응으로 완전 다른 API 제안이 나옴 (mobx-react-lite)&lt;/li&gt;
&lt;li&gt;깔끔해 보이는 구문은 사실 class-field 와 decorator 문법이 낮은 스테이지일 때 그것을 “잘못” 활용한 것이라 API 가 변경되고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;번들 사이즈가 크다.&lt;ul&gt;
&lt;li&gt;action 을 통해 state 를 업데이트한다는 기본 flux 원칙은 동일하게 지키지만, 이를 프록시로 자동화했다.&lt;/li&gt;
&lt;li&gt;단순해보이지만 복잡성은 높다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Redux&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;라이브러리로서 하는 일이 적다. 그만큼 단순해서 배우기 쉽다.&lt;/li&gt;
&lt;li&gt;번들 사이즈가 가볍다. (9K)&lt;/li&gt;
&lt;li&gt;기본적인 원칙 외에는 전부 사용자에게 위임. (그래서 어렵다고 느낌)&lt;/li&gt;
&lt;li&gt;Redux-Toolkit 이 학습곡선을 해결해 준다.&lt;/li&gt;
&lt;li&gt;redux는 러닝 커브가 좀 있고, 코드가 지저분해진다. 또한 추가적으로 알아야 할 것들이 많다는 부분이 기본적으로 트레이드 오프 가능한 요소이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apollo Client 상태 관리 시스템&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;쓰기에 따라서 아주 전략적이기도 하고 graphql 에 익숙하다면 좋은 선택지&lt;/li&gt;
&lt;li&gt;버그가 좀 많다는 자그마한(?) 단점&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(답변을 해주신 &lt;a href=&quot;https://blog.cometkim.kr/&quot;&gt;Comet Kim&lt;/a&gt; 님께 감사드립니다.)&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;두서 없이 정리하긴 했지만, 다른 분들께도 도움이 되기를 바랍니다. (_ _)&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>React</category>
      <category>apollo client</category>
      <category>React</category>
      <category>VUE</category>
      <category>상태관리</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/167</guid>
      <comments>https://devtimothy.tistory.com/167#entry167comment</comments>
      <pubDate>Fri, 6 Mar 2020 20:15:00 +0900</pubDate>
    </item>
    <item>
      <title>Vue 하다가 React 하는 개발자 이야기 4</title>
      <link>https://devtimothy.tistory.com/166</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;Vue 하다가 React 하는 개발자 이야기 4&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;1. 오전 스크럼 미팅을 하면서 동료분이 redux를 선택하길 잘 한거 같다는 피드백을 주셨습니다. &lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;예전에 리덕스를 사용할 때는 장황하고 복잡하게 코드를 짰었는데, 다시 보니 깔끔한 코드 관리가 가능했다고 합니다.&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;앞으로도 이런 기술적인 논의와 연구가 지속될 수 있다면 좋겠습니다.&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;2. React + apollo + hooks로 작업하면서 놀랐던 점은 async / await을 쓰지 않았다는 점입니다. 비동기 요청이 분명 일어날텐데, 코어에서 어떻게 처리한걸까요?  &lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;3. 이 조합을 쓰면서 비동기 처리에 대한 부담이 줄어들어서 초보자들도 좀더 쉽게 웹 개발을 접할수 있겠다는 생각이 들었습니다. &lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;4. 그러나 한편으로는 비동기 처리에 대한 이해가 없는 개발자들이 더 늘어나지는 않을까 걱정도 되구요.  &lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;5. 어떤 기술을 접하던지 기술에 종속되는 것이 아니라, 구조를 아우를 수 있는 개발을 할 수 있어야겠습니다.&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;6. 오늘은 동료분과 페어 프로그래밍을 했습니다. 전 직장에서도 페어 프로그래밍이나 mob 프로그래밍을 해봤었지만, 정말 단기간에 엄청난 집중력을 필요로 하게 됩니다. 동료 개발자분이 피곤해 하시더군요 ㅋㅋ&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;7. 제가 아무래도 리액트에 익숙하지 않다보니 함께 작업을 하면서 제가 알지 못했던 크고 작은 팁을 알게되고, 불확실하게 알던 것에서는 확신을 얻게 되어 좋은 것 같습니다.&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;8. 어떠한 문제에 직면할때도 이게 어떻게 된 일인지에 대해 되짚어가며 하니 작업 능률도 높아지고, 동작 방식에 대해서도 어느정도 이해가 되는 듯 합니다.&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, Malgun Gothic, 맑은 고딕, dotum, 돋움, sans-serif;&quot;&gt;9. 금요일입니다. 목표했던 작업을 완료해서 기분이 좋네요. 즐겁게 한 주를 마무리 합니다.  &lt;/span&gt;&lt;/p&gt;</description>
      <category>React</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/166</guid>
      <comments>https://devtimothy.tistory.com/166#entry166comment</comments>
      <pubDate>Fri, 6 Mar 2020 18:16:07 +0900</pubDate>
    </item>
    <item>
      <title>Vue 하다가 React 하는 개발자 이야기 3</title>
      <link>https://devtimothy.tistory.com/165</link>
      <description>&lt;h3&gt;Vue 하다가 React 하는 개발자 이야기 3&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;오늘은 동료 개발자분이 작업하던 도중에 SSR 관련 이슈를 만나셨다고 합니다. 현재 프로젝트가 Next.js 로 구성되어 있는데, 동적 라우팅이 되는 페이지에서 문제가 발생한다고 합니다. (문제가 정확히 기억이 안나네요)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;개인적으로 관리자 페이지를 작성하는데 Next.js 를 쓰게 된 것이 이해가 되지는 않았습니다. SSR이 필요하지도 않고, 오히려 추가적인 관리 포인트가 더 늘어나기 때문입니다. 코드 레벨에서는 물론이고 인프라 레벨에서도 관리할 것들이 더 늘어납니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;그래서 아직 프로젝트가 초기 단계이니, 일반 클라이언트 사이드 앱으로 전환 하는 것이 어떤지 제안했습니다. 프로젝트가 더 커지면 아마 그때는 CSR로 전환하기는 더 어려워질지도 모르기 때문입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;동료분이 웹팩으로 프로젝트를 직접 구성하는 방법과, Create React App 으로 구성하는 방법이 있는데, 윕팩은 제로부터 시작한다면 큰 공수가 들기에 빠른 구성을 위해서 CRA를 택했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;React Router를 추가로 붙여야 하지만, 큰 공수가 드는 작업은 아니기에 별 이슈 없이 CSR 전환을 마칠 수 있었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;hooks를 사용하며 코드의 재사용성에 대한 논의도 이루어졌습니다. Hooks에서는 보통 자주 사용되는 패턴은 useXXX로 묶어서 View 단의 코드와 완전히 분리하여 재사용에 용이하도록 묶는다고 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;이 내용을 동료분과 논의하며 약간의 오해가 있었는데, 알고보니 서로의 표현 방식이 달라서 생겼던 오해였습니다. 알고보니 서로 같은 얘기를 하고 있더군요. 그래서 소통이 중요하다는 것을 느낍니다. 함께 작업하며 서로가 지향하는 컨벤션도 맞춰가며 작업할 수 있으니 말입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;사실 이 방법에 대해서는 몇달 전에 사이드 프로젝트를 통해서 Vue.js의 Composition API를 경험하면서 유사한 패턴을 학습할 수 있었습니다. Hooks에서 영감을 받아서 만들어진 것이 Vue.js의 Composition API입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;동료분이 이 방법에 대해서 이야기해주실 때 사실 저는 이런 기법을 알고 있었고, 그렇게 프로그램 구성을 하고자 생각하고 있었습니다. 아무튼 동료분이 친절하게 리마인드 시켜주신 덕에, 사소한 것부터 점검하면서 소통할 수 있어 유익한 시간이었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;내일은 동료분과 함께 페어 프로그래밍을 할 예정입니다. 아직은 React에 익숙하지 않은 사람이기에 함께 작업하며 좋은 시너지 효과가 날 것이라 생각됩니다. 오늘도 평화롭게 하루를 마칩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(지금 글을 적으며 생각해보니 차후에 운영 중인 웹도 React로 전환 할 생각을 하고 있었는데, 그때를 대비해서 이렇게 프로젝트를 구성했던 것은 아닌가 생각이 드네요. 제 논리에 허점이 있었던 것을 느낍니다. 흠... 만일 그렇다면 지금 맞닥뜨린 문제를 차후에도 또 겪게 될만한 문제일텐데요. 차라리 그 이슈를 함께 살펴보고 나서 컨버팅을 했더라면 좋았을 것 같습니다.)&lt;/p&gt;</description>
      <category>React</category>
      <category>React</category>
      <category>vue.js</category>
      <category>페어프로그래밍</category>
      <category>협업</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/165</guid>
      <comments>https://devtimothy.tistory.com/165#entry165comment</comments>
      <pubDate>Thu, 5 Mar 2020 22:34:00 +0900</pubDate>
    </item>
    <item>
      <title>Vue 하다가 React 하는 개발자 이야기 2</title>
      <link>https://devtimothy.tistory.com/164</link>
      <description>&lt;p&gt;Vue 하다가 React 하는 개발자 이야기 2&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;어제 올렸던 내용에 대해서는 일단 apollo client의 상태 관리 시스템은 기각하고 redux를 쓰는 것으로 결정을 했습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;커뮤니티에서 자문을 구한 결과, 아직 아폴로 상태 관리 시스템은 프로덕션에 적용하기에는 무리라는 판단이 들었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;전 회사에서도 node.js의 ORM을 선택할 때&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;star 수&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;활발하게 commit이 되고 있는가?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;문서화가 잘 되어 있는가?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;현재 프로젝트와 궁합이 잘 맞을까?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;등을 고려해서 선택했던 것이 TypeORM 이었는데, 사용하다보니 부족한 점이 많았습니다. 또한 처음 접하는 이슈 등에 대해서는 이슈 트래킹이나 검색도 잘 안되어 고생하는 경우도 있었습니다.&lt;/p&gt;
&lt;p&gt;이때&lt;/p&gt;
&lt;p&gt;1) 스타 수로만 프로젝트를 판단하면 안 되겠구나.&lt;/p&gt;
&lt;p&gt;2) contribution과 이슈 등록 등의 참여가 중요하겠다.&lt;/p&gt;
&lt;p&gt;3) 프로덕션에 실제 적용한 사례가 있는가? 충분히 검증이 되었는가도 중요한 지표가 되겠다.&lt;/p&gt;
&lt;p&gt;하는 것들도 많이 느끼게 되었습니다.&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;아무튼 이렇게 해서 아폴로 클라이언트의 상태 관리 시스템은 기각이 되고, 다시 논의를 하는데 우선적으로 제 의문은 과연 상태 관리가 필요한 상황인지에 대한 것이었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;제가 우려되었던 점은, 괜히 오버 엔지니어링을 하는 건 아닌가? 하는 생각이 제일 컸습니다. 닭 잡는데 소 잡는 칼 쓸 필요는 없다는 생각이 있었기 때문입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;동료 개발자분께서는 코딩을 하며 몇가지 원칙을 지켜서 작업하고자 하는 의도가 있으셨고, 그에 따라 상태 관리는 필요하다고 이야기 하셨습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;컴포넌트 간 상태 전달을 할 때나, 단방항 바인딩 등 리액트의 특성이 있고, 또한 컴포넌트를 제작할 때도 단일 책임 원칙을 비롯해 지키고자 하는 원칙들이 있음을 말씀하셨습니다. 그러다보니 상태 관리는 필수적으로 필요한 상황임을 이해하게 되었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;결국에는 남은 선택지는 Redux와 context api 인데, 동료 개발자분께서 이야기 하신 부분은&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Graphql의 경우 overfetching과 underfetching을 해결한 기술이다. Redux는 REST API를 사용할 때 더 필요한 기술이지, GraphQL에서 Redux를 쓰는 것은 결이 맞지 않다는 말씀을 하셨습니다. (사실 이 부분에 대해서는 이해가 잘 안되고, 공감하기도 좀 어려웠네요)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;context api의 경우 가볍게 쓰기는 좋지만 이 역시 앱 규모가 커지면 관리의 어려움이 있다고 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;9&quot;&gt;
&lt;li&gt;결론적으로는 가장 널리 쓰이고 있고, 참조할만한 자료가 많은 Redux를 택하는 것으로 방향이 정해졌습니다. 닭 잡는데 소 잡는 칼 쓰는 격은 아닌가? 라는 생각이 있었지만, 어차피 쓰는 칼인데 소 잡는 칼로 닭 잡으면 뭐 어떠냐. 라는 의견이 있었습니다.  &lt;/li&gt;
&lt;li&gt;개인적으로는 context api를 써보고 싶은 마음이 있었는데, 이 역시도 Redux에서 개념을 빌려온 부분도 있어서 우선적으로는 Redux에 대해 익숙해지는 것이 좋겠다는 생각도 들었습니다.&lt;/li&gt;
&lt;li&gt;또한 이야기를 나누다보니, 동료 개발자분께서 첫 실무 프로젝트에 들어가다보니 내심 부담이 되셨던 것 같습니다. 기술 도입에 앞서서 앞으로 어떻게 될지에 대한 예측이 불가능하니, 그 부분에 대한 어려움이 있으시더군요.&lt;/li&gt;
&lt;li&gt;완벽한 프로그램, 흠이 없는 프로그램을 만드는 것은 불가능하다 생각합니다. 인간이 만들기 때문이지요. null 참조를 고안했던 토니 호어(Tony Hoare) 역시도 null을 처음 만들때 이것이 &quot;10억 달러짜리 실수&quot;가 될 것이라고는 생각했을까요?&lt;/li&gt;
&lt;li&gt;예측이 안 되는 일을 미리 걱정하기보다는 우선은 기술을 학습하고, 적용하며 부딪혀 보며 생각하자고 의견이 모였고, 이제 Redux를 적용해가고자 합니다. Redux가 우리 프로젝트에 적합하지 않다 느껴지면 다른 대안을 찾아보아도 되지요.&lt;/li&gt;
&lt;li&gt;즐겁게 개발해가보고자 합니다 :) 오늘도 하루를 마칩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;참고 내용&lt;/p&gt;
&lt;p&gt;- &lt;a href=&quot;https://react.vlpt.us/redux/&quot;&gt;https://react.vlpt.us/redux/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1583325412514&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;6장. 리덕스 &amp;middot; GitBook&quot; data-og-description=&quot;이번 챕터에서 알아볼 주제는 리덕스(Redux) 입니다. 리덕스는 리액트 생태계에서 가장 사용률이 높은 상태관리 라이브러리입니다. 리덕스를 사용하면 여러분이 만들게 될 컴포넌트들의 상태 관련 로직들을 다른 파일들로 분리시켜서 더욱 효율적으로 관리 할 수 있으며 글로벌 상태 관리도 손쉽게 할 수 있습니다. 우리가 이전에 배운 Context API 를 사용해도 글로벌 상태 관리를 할 수 있고 상태 관리 로직을 분리 할 수 있습니다. 특히, Context AP&quot; data-og-host=&quot;react.vlpt.us&quot; data-og-source-url=&quot;https://react.vlpt.us/redux/&quot; data-og-url=&quot;https://react.vlpt.us/redux/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0Tfud/hyFaBL2OoT/hzxjf3KNs2Qa6k4gytOOUk/img.png?width=1128&amp;amp;height=527&amp;amp;face=0_0_1128_527&quot;&gt;&lt;a href=&quot;https://react.vlpt.us/redux/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.vlpt.us/redux/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0Tfud/hyFaBL2OoT/hzxjf3KNs2Qa6k4gytOOUk/img.png?width=1128&amp;amp;height=527&amp;amp;face=0_0_1128_527');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;6장. 리덕스 &amp;middot; GitBook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;이번 챕터에서 알아볼 주제는 리덕스(Redux) 입니다. 리덕스는 리액트 생태계에서 가장 사용률이 높은 상태관리 라이브러리입니다. 리덕스를 사용하면 여러분이 만들게 될 컴포넌트들의 상태 관련 로직들을 다른 파일들로 분리시켜서 더욱 효율적으로 관리 할 수 있으며 글로벌 상태 관리도 손쉽게 할 수 있습니다. 우리가 이전에 배운 Context API 를 사용해도 글로벌 상태 관리를 할 수 있고 상태 관리 로직을 분리 할 수 있습니다. 특히, Context AP&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;react.vlpt.us&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.to/ayushmanbthakur/redux-vs-context-api-3182&quot;&gt;- https://dev.to/ayushmanbthakur/redux-vs-context-api-3182&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1583325454905&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Redux vs Context API&quot; data-og-description=&quot;I'm sure, if you have somehow stumbled upon this post, you have some basic knowle...&quot; data-og-host=&quot;dev.to&quot; data-og-source-url=&quot;https://dev.to/ayushmanbthakur/redux-vs-context-api-3182&quot; data-og-url=&quot;https://dev.to/ayushmanbthakur/redux-vs-context-api-3182&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ddtEsB/hyE9rYz8Gf/xLnWpH0fNqWhOrkbfEE4lk/img.png?width=1128&amp;amp;height=600&amp;amp;face=0_0_1128_600,https://scrap.kakaocdn.net/dn/bvALMo/hyFashf9m5/Tjm5LTUfW96TsNZOae8nd0/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240,https://scrap.kakaocdn.net/dn/kxkSY/hyFay9CxQ0/2LXzBVKYUM3NFKn7NQuBKK/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240&quot;&gt;&lt;a href=&quot;https://dev.to/ayushmanbthakur/redux-vs-context-api-3182&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev.to/ayushmanbthakur/redux-vs-context-api-3182&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ddtEsB/hyE9rYz8Gf/xLnWpH0fNqWhOrkbfEE4lk/img.png?width=1128&amp;amp;height=600&amp;amp;face=0_0_1128_600,https://scrap.kakaocdn.net/dn/bvALMo/hyFashf9m5/Tjm5LTUfW96TsNZOae8nd0/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240,https://scrap.kakaocdn.net/dn/kxkSY/hyFay9CxQ0/2LXzBVKYUM3NFKn7NQuBKK/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Redux vs Context API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;I'm sure, if you have somehow stumbled upon this post, you have some basic knowle...&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;dev.to&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>apollo</category>
      <category>context api</category>
      <category>graphql</category>
      <category>React</category>
      <category>Redux</category>
      <category>VUE</category>
      <author>devtimothy</author>
      <guid isPermaLink="true">https://devtimothy.tistory.com/164</guid>
      <comments>https://devtimothy.tistory.com/164#entry164comment</comments>
      <pubDate>Wed, 4 Mar 2020 21:35:54 +0900</pubDate>
    </item>
  </channel>
</rss>