<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dalbodre_dev.log</title>
        <link>https://velog.io/</link>
        <description>휘뚜루마뚜루</description>
        <lastBuildDate>Sat, 14 Jun 2025 07:42:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dalbodre_dev.log</title>
            <url>https://images.velog.io/images/dalbodre_ari/profile/4e3036f2-d980-491e-be7a-a286b4a860bd/cat-4277400_1920.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dalbodre_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dalbodre_ari" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[numeric input 알잘딱 포맷팅]]></title>
            <link>https://velog.io/@dalbodre_ari/numeric-input-%EC%95%8C%EC%9E%98%EB%94%B1-%ED%8F%AC%EB%A7%B7%ED%8C%85</link>
            <guid>https://velog.io/@dalbodre_ari/numeric-input-%EC%95%8C%EC%9E%98%EB%94%B1-%ED%8F%AC%EB%A7%B7%ED%8C%85</guid>
            <pubDate>Sat, 14 Jun 2025 07:42:20 GMT</pubDate>
            <description><![CDATA[<h2 id="numeric-input-알잘딱-포맷팅">numeric input 알잘딱 포맷팅</h2>
<p><img src="https://velog.velcdn.com/images/dalbodre_ari/post/24c56ae5-a51c-4f04-b99f-2951d8af9824/image.png" alt=""></p>
<p>휴대전화, 사업자번호 등 사용자가 입력한 값에 자동으로 하이픈(-)을 붙여주는 간단한 로직이 필요했습니다. 분명히 이전에도 설정했던 로직인데도 또 다시 머리를 싸매게되어 <del>미래의 나를 위해</del> 정리해둡니다.</p>
<h3 id="요구사항">요구사항</h3>
<ul>
<li>props로 포맷 자리수를 배열로 받기 (예: [3, 4, 4], 네이밍 numberTextFormat)</li>
<li>해당 포맷 형식으로 하이픈 자동 삽입 (01012345678 → 010-1234-5678)</li>
<li><code>type=&quot;text&quot;</code> + <code>inputMode=&quot;numeric&quot;</code> (010 등 0으로 시작하는 값 반영)</li>
<li>props로 setValue,onChange 등을 통해 포맷팅된 값을 전달</li>
</ul>
<h3 id="1차-시도-onchange에서-포맷팅">1차 시도: onChange에서 포맷팅</h3>
<pre><code class="language-ts">const formatNumberText = (raw: string, format: number[]) =&gt; {
  const result: string[] = []
  let cursor = 0

  for (const len of format) {
    const part = raw.slice(cursor, cursor + len)
    if (part) result.push(part)
    cursor += len
  }

  return result.join(&#39;-&#39;)
}

const handleChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
  const raw = e.target.value.replace(/\D/g, &#39;&#39;)
  const formatted = formatNumberText(raw, numberTextFormat)
  // props로 받은 value, (formatted string을 파라미터로 받는) setValue
  setValue(formatted)
}</code></pre>
<p>이 방식은 입력 자체는 잘 되지만, 예상치 못한 문제가 생깁니다. <code>010-123</code> 상태에서 두번째 0을 지우려고 커서를 1 앞으로 이동하여 Backspace를 누르면, <code>-</code>가 지워지고 <code>010123</code>로 변경된 value는 다시 포맷팅되어, 의도한 대로 지워지거나 이동하는 것이 아니라 버벅이는 느낌이 듭니다. 따라서 사용자가 지우려던 하이픈 앞 숫자로 자연스럽게 이동하기 위해 입력 전후 하이픈의 개수 차이를 계산해서 커서를 보정해줘야 합니다.</p>
<h3 id="2차-시도-커서-위치-보정">2차 시도: 커서 위치 보정</h3>
<pre><code class="language-ts">const handleChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
  const prevFormatted = prevFormattedRef.current

  const maxLength = numberTextFormat.reduce((a, b) =&gt; a + b, 0)
  const raw = e.target.value.replace(/\D/g, &#39;&#39;).slice(0, maxLength)
  const newFormatted = formatNumberText(raw, numberTextFormat)

  // 커서 위치 보정
  const cursor = e.target.selectionStart ?? newFormatted.length
  requestAnimationFrame(() =&gt; {
    const el = inputRef.current
    if (!el) {
      return
    }

    // 입력 전후 하이픈 개수 차이 → 커서가 밀려야 하는 거리
    const prevOffset = prevFormatted.slice(0, cursor).replace(/\d/g, &#39;&#39;).length
    const newOffset = newFormatted.slice(0, cursor).replace(/\d/g, &#39;&#39;).length
    const diff = newOffset - prevOffset

    const newCursor = cursor + diff
    el.setSelectionRange(newCursor, newCursor)
  })

  setValue(newFormatted)
  prevFormattedRef.current = newFormatted
}</code></pre>
<p>이 방법으로도 충분하지만, 프로젝트에서는 파일, select 등 사용자에게 입력받는 컴포넌트들이 여러가지가 있었는데요. 이들의 공통적인 처리를 위하여 외부에서 change event를 받아 사용하고싶었습니다.</p>
<h3 id="3차-시도-fake-event-생성">3차 시도: fake event 생성</h3>
<pre><code class="language-ts">const fakeEvent = {
  ...e,
  target: { ...e.target, value: newFormatted },
}
onChange(fakeEvent)</code></pre>
<p>원래 있던 이벤트에 e.target.value만 변경하면 됐기 때문에, 위와 같이 이벤트를 만들어 onChange 함수를 호출하면 될 것이라 생각했습니다. 하지만 일부 DOM 속성들은 getter로만 존재하고 enumerable하지 않기 때문에 onChange 함수를 호출할 때에 e.target.id, name 등이 없었고, <code>Uncaught TypeError: Illegal invocation</code> 에러도 발생했습니다. 따라서 아래와 같이 Proxy 설정으로 우회가 필요했습니다. </p>
<pre><code class="language-ts">const fakeEvent = Object.create(e)
Object.defineProperty(fakeEvent, &#39;target&#39;, {
  value: new Proxy(e.target, {
    get(target, prop) {
      return prop === &#39;value&#39; ? newFormatted : target[prop as keyof HTMLInputElement]
    },
  }),
})
Object.defineProperty(fakeEvent, &#39;currentTarget&#39;, {
  value: new Proxy(e.target, {
    get(target, prop) {
      return prop === &#39;value&#39; ? newFormatted : target[prop as keyof HTMLInputElement]
    },
  }),
})
onChange(fakeEvent as React.ChangeEvent&lt;HTMLInputElement&gt;)</code></pre>
<p>이렇게 하면 value만 위장하고, id, name, dataset은 원래대로 유지됩니다.</p>
<h3 id="결론">결론</h3>
<ul>
<li>자연스러운 numeric input 포맷팅을 위해서는 커서 보정도 추가로 필요</li>
<li>외부에서 일반 onChange로 호출하려면 value만 오버라이드한 fake event 생성</li>
<li>같은 방식으로 YYYY-MM-DD, 카드번호, 사업자번호 등에 쉽게 응용 가능</li>
</ul>
<p>아래는 <del>textArea와 함께 사용한</del> 코드 전문입니다.</p>
<pre><code class="language-ts">const formatNumberText = (raw: string, format: number[]) =&gt; {
  const result: string[] = []
  let cursor = 0

  for (const len of format) {
    const part = raw.slice(cursor, cursor + len)
    if (part) result.push(part)
    cursor += len
  }

  return result.join(&#39;-&#39;)
}</code></pre>
<pre><code class="language-ts">const handleChange = (_e: React.ChangeEvent&lt;HTMLInputElement | HTMLTextAreaElement&gt;) =&gt; {
  _e.preventDefault()
  if (!numberTextFormat?.length) {
    onChange(_e)
    return
  }

  const e = _e as React.ChangeEvent&lt;HTMLInputElement&gt;
  const maxLength = numberTextFormat.reduce((a, b) =&gt; a + b, 0)
  const raw = e.target.value.replace(/\D/g, &#39;&#39;).slice(0, maxLength)
  const newFormatted = formatNumberText(raw, numberTextFormat)
  const prevFormatted = value

  // fake event 생성
  const fakeEvent = Object.create(e)
  Object.defineProperty(fakeEvent, &#39;target&#39;, {
    value: new Proxy(e.target, {
      get(target, prop) {
        return prop === &#39;value&#39; ? newFormatted : target[prop as keyof HTMLInputElement]
      },
    }),
  })
  Object.defineProperty(fakeEvent, &#39;currentTarget&#39;, {
    value: new Proxy(e.target, {
      get(target, prop) {
        return prop === &#39;value&#39; ? newFormatted : target[prop as keyof HTMLInputElement]
      },
    }),
  })
  onChange(fakeEvent as React.ChangeEvent&lt;HTMLInputElement&gt;)

  // 커서 위치 보정
  const cursor = e.target.selectionStart ?? newFormatted.length
  requestAnimationFrame(() =&gt; {
    const el = inputRef.current
    if (!el) return

    const prevOffset = prevFormatted.slice(0, cursor).replace(/\d/g, &#39;&#39;).length
    const newOffset = newFormatted.slice(0, cursor).replace(/\d/g, &#39;&#39;).length
    const diff = newOffset - prevOffset
    const newCursor = cursor + diff

    el.setSelectionRange(newCursor, newCursor)
  })
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD 적용하기 : ReactNative 실전편]]></title>
            <link>https://velog.io/@dalbodre_ari/TDD-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-ReactNative-%EC%8B%A4%EC%A0%84%ED%8E%B8</link>
            <guid>https://velog.io/@dalbodre_ari/TDD-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-ReactNative-%EC%8B%A4%EC%A0%84%ED%8E%B8</guid>
            <pubDate>Wed, 20 Apr 2022 05:53:40 GMT</pubDate>
            <description><![CDATA[<h2 id="tdd란">TDD란</h2>
<h3 id="test-driven-development란">Test Driven Development란</h3>
<p>소프트웨어 개발 방법론 중 하나로, 개발 자체가 “테스트&quot;에 의해 주도되는 방식이다. 제품의 기능 구현을 위한 코드와 별개로, 해당 기능이 정상적으로 움직이는지 검증하기 위한 테스트 코드를 먼저 작성한다.</p>
<h3 id="tdd-개발-단계">TDD 개발 단계</h3>
<ol>
<li>ReadMe Driven Development : 요구사항 기술</li>
<li>Make it <strong>RED</strong>(fail)  : 테스트 코드 작성</li>
<li>Make it <strong>GREEN</strong>(success) : 실제 코드 작성</li>
<li>Make it <strong>BLUE</strong>(refactoring) : 최소한으로 작성된 코드 리팩토링</li>
</ol>
<h3 id="왜-tdd를-사용하는가">왜 TDD를 사용하는가?</h3>
<p>일반적인 개발방식을 사용하는 경우, 소비자의 요구사항이 처음부터 명확하지 않을 수 있어 처음부터 완벽한 설계는 어렵다. 또한 반복되는 재설계와 코드의 재작성은 제품의 품질을 떨어뜨린다. 작은 기능 수정에도 모든 부분을 다시 테스트해야 하므로, 테스트 비용이 증가한다.
TDD는, 기능별로 테스트코드와 제품코드를 작성하므로 종속성과 의존성이 낮은 모듈화된 방식으로 개발하게 된다. 이는 재설계, 디버깅 시간을 단축시키고 추가 구현에 용이하다. (특히 기존 코드에 미칠 수 있는 영향을 최소화할 수 있다)</p>
<h2 id="라이브러리-설치-및-소개">라이브러리 설치 및 소개</h2>
<h3 id="jest">JEST</h3>
<p>페이스북에서 만든 JavaScript 테스팅 라이브러리이다. react-native-cli로 프로젝트 구성시, 이미 dev dependency에 포함되어있다.</p>
<ul>
<li>describe() : 여러 개의 테스트 코드를 하나의 테스트 작업 단위로 묶어주는 API</li>
<li>test() : 테스트 코드를 돌리기 위한 API. 하나의 테스트 케이스를 의미</li>
<li>expect() : 테스트할 대상(기대 값)을 넣는 API</li>
<li>beforeEach() : 각 테스트 코드가 돌기 전에 반복적으로 수행할 로직을 넣는 API</li>
</ul>
<h3 id="enzyme">Enzyme</h3>
<p>에어비앤비에서 만든 React 컴포넌트 테스팅 라이브러리이다. enzyme, enzyme-adapter-react, react-dom를 함께 설치한다.</p>
<ul>
<li>shallow: 간단한 컴포넌트를 메모리에 렌더링 (단일 컴포넌트를 테스트)</li>
<li>mount: HOC나 자식 컴포넌트 까지 전부 렌더링 (다른 컴포넌트와의 관계 테스트 )</li>
<li>render: 컴포넌트를 정적인 html로 렌더링 (컴포넌트가 브라우저에 붙었을 때 html로 어떻게 되는지 판단할 때 사용)</li>
</ul>
<h3 id="detox">Detox</h3>
<p>사용자 관점에서 End to End로 테스트하기 위한 라이브러리이다. android/ios 모두 지원하지만 우선은 ios에서만 테스트해보았다. applesimutils, detox-cli, detox, jest-circus를 함께 설치한다. </p>
<ul>
<li>실제로 사용자 관점에서 테스트하기위한 async 기능 제공</li>
</ul>
<h2 id="todolist-테스트코드-작성">ToDoList 테스트코드 작성</h2>
<h3 id="컴포넌트-렌더링-및-props-확인">컴포넌트 렌더링 및 props 확인</h3>
<p>아래와 같은 간단한 ToDo 리스트에 대한 테스트코드를 작성해보자!
<img src="https://velog.velcdn.com/images/dalbodre_ari/post/1ddba2c0-ae44-4860-8be0-6fb86068c67c/image.png" alt=""></p>
<p>먼저 앱을 처음 실행했을 때에 화면 제목으로 사용될 Text 컴포넌트 내에 &quot;TODO TDD&quot;라는 내용이 들어가있는지, AddToDo와 ToDoList가 존재하는지는 아래와 같이 작성할 수 있다.</p>
<pre><code class="language-js">describe(&#39;App&#39;, () =&gt; {
  const wrapper = shallow(&lt;App&gt;&lt;/App&gt;);
  test(&#39;snapshot test&#39;, () =&gt; {
    expect(wrapper).toMatchSnapshot();
  });
  it(&#39;is Text visible?&#39;, () =&gt; {
    expect(wrapper.find(&#39;Text&#39;).contains(&#39;ToDo TDD&#39;)).toBe(true);
  });
  it(&#39;is AddToDO visible?&#39;, () =&gt; {
    expect(wrapper.find(&#39;AddToDo&#39;)).toHaveLength(1);
  });
  it(&#39;is ToDoList visibla?&#39;, () =&gt; {
    expect(wrapper.find(&#39;ToDoList&#39;)).toHaveLength(1);
  });
});</code></pre>
<p>AddToDo 컴포넌트는 TextInput과 버튼으로 구성되어있고, 버튼 클릭 시 TextInput 내부에 있는 내용으로 (props로 가지고 있는) OnAdded 함수를 호출한다. 내부에 해당하는 컴포넌트가 있는지 확인하는 동작은 위의 코드와 비슷하게 작성하면 되니, 함수 호출과 관련된 부분의 작성법은 아래와 같다. 테스트를 진행하기 전에 AddToDo를 렌더링하고 TextInput 내용을 작성하고 버튼을 누른다. 이후 it에서 OnAdded 함수가 불렸는지, 텍스트 내용을 가지고 불렸는지를 확인한다.</p>
<pre><code class="language-js">describe(&#39;AddToDo Interaction&#39;, () =&gt; {
  let wrapper;
  let props;
  const text = &#39;Add To Do Item&#39;;

  beforeEach(() =&gt; {
    props = {
      onAdded: jest.fn(),
    };
    wrapper = shallow(&lt;AddToDo {...props}&gt;&lt;/AddToDo&gt;);
    wrapper.find(&#39;TextInput&#39;).simulate(&#39;changeText&#39;, text);
    wrapper.find(&#39;Button&#39;).prop(&#39;onPress&#39;)();
  });

  it(&#39;should call the onAdded callback with input text&#39;, () =&gt; {
    expect(props.onAdded).toHaveBeenCalledTimes(1);
    expect(props.onAdded).toHaveBeenCalledWith(text);
  });
});</code></pre>
<p>ToDoRenderITem에 대한 테스트 코드 작성도 비슷하다. 기본적으로 내부에 Text, Button들이 알맞게 자리하고 있는지, 초기 스타일은 어떻고 만약 props로 특정 데이터가 들어오면 어떤 다른 스타일을 가지고있는지를 확인한다. 이후 각 버튼이 클릭됨에 따라 적절한 함수가 알맞게 불렸는지를 확인할 수 있다.</p>
<pre><code class="language-js">// component rendering
describe(&#39;rendering&#39;, () =&gt; {
  let warpper;
  let props;

  beforeEach(() =&gt; {
    props = {
      item: {},
    };
    wrapper = shallow(&lt;ToDoRenderItem {...props}&gt;&lt;/ToDoRenderItem&gt;);
  });

  it(&#39;should render a Text&#39;, () =&gt; {
    expect(wrapper.find(&#39;Text&#39;)).toHaveLength(1);
  });

  it(&#39;should render two buttons&#39;, () =&gt; {
    expect(wrapper.find(&#39;Button&#39;)).toHaveLength(2);
  });

  describe(&#39;Uncompleted&#39;, () =&gt; {
    it(&#39;should have the default style&#39;, () =&gt; {
      expect(wrapper.prop(&#39;style&#39;)).toBe(styles.default);
    });
  });

  describe(&#39;Completed&#39;, () =&gt; {
    beforeEach(() =&gt; {
      props.item.completed = true;
      wrapper = shallow(&lt;ToDoRenderItem {...props}&gt;&lt;/ToDoRenderItem&gt;);
    });
    it(&#39;should have the completed style&#39;, () =&gt; {
      expect(wrapper.prop(&#39;style&#39;)).toBe(styles.completed);
    });
  });
});

// interaction
describe(&#39;interaction&#39;, () =&gt; {
  let wrapper;
  let props;
  beforeEach(() =&gt; {
    props = {
      item: {text: &#39;first ToDo&#39;, completed: false},
      index: 0,
      onCompleted: jest.fn(),
      onDeleted: jest.fn(),
    };

    wrapper = shallow(&lt;ToDoRenderItem {...props}&gt;&lt;/ToDoRenderItem&gt;);
  });

  it(&#39;Complete feature&#39;, () =&gt; {
    wrapper.find(&#39;Button&#39;).at(0).prop(&#39;onPress&#39;)();
    expect(props.onCompleted).toHaveBeenCalledTimes(1);
    expect(props.onCompleted).toHaveBeenCalledWith(props.index);
  });

  it(&#39;Delete feature&#39;, () =&gt; {
    wrapper.find(&#39;Button&#39;).at(1).prop(&#39;onPress&#39;)();
    expect(props.onDeleted).toHaveBeenCalledTimes(1);
    expect(props.onDeleted).toHaveBeenCalledWith(props.index);
  });
});</code></pre>
<p><del>뭔가 세미나에서 이야기하다가 그럼 이 모든 코드를 하나의 파일로 작성해야 하냐는 질문을 받았는데, yarn test 명령어를 실행하면 해당 폴더 내의 모든 테스트코드 파일들이 실행되고 확인되니 컴포넌트별, 기능별로 알맞게 잘 나눠서 쓰면 된다</del></p>
<p>처음 파일을 실행하면 모든 테스트들이 실패했다고 나올 것이다. 앞에서 말했던 RED단계이다! 이제 테스트코드들을 통과할 수 있는 GREEN 단계를 진행하면 된다. (BLUE 단계는 이후 테스트 코드와 관계 없이 진행되는 단계임을 잊지말자) 만약 해당하는 테스트 코드를 다 통과할 수 있도록 코드를 작성했다면, yarn test 명령어를 통해 실행할 경우 아래와 같은 결과를 확인할 수 있을 것이다. <img src="https://velog.velcdn.com/images/dalbodre_ari/post/e5270d6c-0b5c-4089-9df3-f491b7ae2739/image.png" alt=""></p>
<h3 id="detox를-통해-e2e-동작-확인하기">Detox를 통해 E2E 동작 확인하기</h3>
<p>컴포넌트의 렌더링이나 내부 데이터 확인 등은 앞과 같이 확인할 수 있지만, 사용자가 실제로 서비스를 사용하는 시나리오들이 모두 정상적으로 동작하는지는 어떻게 확인할 수 있을까? Detox의 End To End 테스트 기능을 통해 TodoRenderItem을 사용자가 직접 추가/완료, 삭제하는 과정이 정상적으로 동작하는지를 확인해보도록 하자.</p>
<ul>
<li>추가 및 완료 : 사용자가 입력 필드에 내용을 입력하고, 추가 버튼을 클릭하고, 미완료 상태의 ToDoRenderItem이 정상적으로 추가되었는지를 확인한다. 이후 완료 버튼을 클릭하면 해당 아이템이 완료 상태로 전환되었는지를 확인하면 완료된다.</li>
<li>삭제 : 앞과 비슷하게 입력 필드에 내용을 입력하고, 추가 버튼 클릭, 목록에  추가되는지를 확인한다. 이후 삭제 버튼을 클릭했을 때에 목록에 아이템이 삭제되어 표시되지 않는지를 확인한다.</li>
</ul>
<pre><code class="language-js">describe(&#39;Interaction Test&#39;, () =&gt; {
  beforeAll(async () =&gt; { // 처음에 앱을 실행하고
    await device.launchApp();
  });
  beforeEach(async () =&gt; { // 매 테스트마다 리로드를 통해 혹시 모를 영향을 끼칠 요소들을 방지하자
    await device.reloadReactNative();
  });

  it(&#39;Completing ToDo Item should work!&#39;, async () =&gt; {
    const text = &#39;입력 완료 여부 체크&#39;;
    await element(by.id(&#39;textInput&#39;)).tap();
    await element(by.id(&#39;textInput&#39;)).typeText(text);
    await element(by.id(&#39;addButton&#39;)).tap();
    await element(by.id(&#39;completeButton&#39;)).multiTap(1);
    await expect(
      element(
       // toDoList라는 아이템 내에 &#39;입력 완료 여부 체크&#39;라는 내용을 
       // 가진 텍스트가 있는지 확인하고, 
       // 그 아이디가 현재 testId로 &#39;completed&#39;를 가지고있는지 확인한다.
        by.id(&#39;completed&#39;).and(by.text(text)).withAncestor(by.id(&#39;toDoList&#39;)),
      ),
    ).toBeVisible(); 
  });

  it(&#39;Deleting ToDo Item should work!&#39;, async () =&gt; {
    const text = &#39;입력 삭제 여부 체크&#39;;
    await element(by.id(&#39;textInput&#39;)).tap();
    await element(by.id(&#39;textInput&#39;)).typeText(text);
    await element(by.id(&#39;addButton&#39;)).tap();
    await element(by.id(&#39;deleteButton&#39;)).multiTap(1);
    await expect(
      element(by.text(text).withAncestor(by.id(&#39;toDoList&#39;))),
    ).not.toBeVisible(); // not toBeVisible
  });
});</code></pre>
<p>detox build -c ios로 빌드하고 detox test -c ios로 테스트하면 아래와 같은 결과를 확인할 수 있다. 테스트 커맨드 실행시 아래와같이 실제로 앱이 동작하면서 사용자가 텍스트 입력, 버튼을 클릭하는 것을 그대로 실행하면서 시나리오가 진행되는 것을 눈으로 보면서 테스트할 수 있다. 
<img src="https://velog.velcdn.com/images/dalbodre_ari/post/e37838f8-7cbb-4085-82b7-b461d8a200ce/image.gif" alt="">
실행 중에 터미널을 확인하면 해당 테스트가 실행됨에 따라 실행이 정상적으로 완료되었다고 [OK] 문구가 추가되고, 테스트에 성공/실패했는지 여부를 자세하게 알려준다. QA 시간을 획기적으로 단축할 수 있어보여 개인적으로 너무 마음에 들었다.
<img src="https://velog.velcdn.com/images/dalbodre_ari/post/899f681d-2c80-45d5-9fa4-131944a4e3d8/image.png" alt=""></p>
<p>중간에 코드를 바꾼 이후 리로드되어 자동으로 코드에 반영되어야 한다고 생각했는데, 정작 세미나에서 계속 안되어서 아예 detox build된 내용을 아예 날리고 확인하니 정상적으로 진행되었다. (게다가 xcode clean build를 위한 커맨드를 날리고 다시 빌드했었는데, 알고보니 그 클린 커맨드가 작동이 안되었었다.. xcode로 빌드된게 아니라나 뭐라나 ㅠㅠㅠ 엄청 당황했다!!) 안되면 그냥 detox 공식문서에서 하라는 대로 하자..!</p>
<h2 id="꼭-tdd를-써야할-필요는-없다">꼭 TDD를 써야할 필요는 없다.</h2>
<p>물론, 모든 프로젝트와 기타 모든 상황에서 TDD를 무조건 사용해야 하는 것은 아니다. TDD를 처음 도입할 때에는 이를 사용하지 않을 때보다 초기비용이 크게 들기 마련이다(테스트코드 작성). 또한 TDD는 버그를 사전에 발견할 수 있게 하는 것일 뿐, 버그 “해결&quot;은 결국 개발자의 몫이라는 것도 잊어서는 안된다. 게다가 단순히 반복적으로 테스트코드를 복붙하고 의미없이 사용하는 것은 업무프로세스 증가로 이어지기만 할 뿐이다. <strong>TDD는 공동의 목표를 효율적으로 달성하기 위한 “도구”로, 잘 사용하는 것은 결국 우리들의 손에 달려있다는 것을 잊어서는 안된다</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenAPI Generator와 Git Subtree를 이용한 프로젝트 관리]]></title>
            <link>https://velog.io/@dalbodre_ari/OpenAPI-Generator%EC%99%80-Git-Subtree%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@dalbodre_ari/OpenAPI-Generator%EC%99%80-Git-Subtree%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Wed, 06 Apr 2022 00:57:31 GMT</pubDate>
            <description><![CDATA[<h2 id="openapi-generator">OpenAPI Generator</h2>
<p>일반적으로 프론트엔드는 백엔드에서 swagger와 같은 문서 툴로 정의된 API 명세를 전달받게 됩니다. 자바스크립트에서라면 바로 fetch 혹은 axios로 api에 요청하는 코드를 작성하면 되지만, 타입스크립트의 경우 swagger에 명시된 타입을 바탕으로 추가적인 타입을 정의하는 단계가 필요합니다. 하나의 요청/응답에는 한두개부터 수십개까지의 타입 정의가 필요하며, 규모가 커지거나 변경사항이 있을 경우 생산성에 영향이 있을 수 있습니다. OpenAPI Generator는 OpenAPI Specification를 만족하는 API 명세(yaml 또는 json 파일)로부터 요청과 응답에 필요한 타입들과 호출 함수들을 자동으로 생성하여 이러한 문제를 해결할 수 있습니다.</p>
<hr>
<h2 id="openapi-generator-실행">OpenAPI Generator 실행</h2>
<p>JDK가 함께 필요하기 때문에 설치되지 않은 경우 설치/환경변수 설정을 먼저 해주어야 합니다.</p>
<p>아래의 npx 명령어를 사용하여 추가적인 설치 없이 OpenAPI Generator을 사용할 수 있습니다.</p>
<pre><code>&gt; npx @openapitools/openapi-generator-cli generate -i &lt;file&gt; -g &lt;generator&gt; -o &lt;directory&gt;</code></pre><p>개별적으로 npm, homebrew, scoop, docker를 사용하여 글로벌하게 설치할 수도 있습니다.(참고 : <a href="https://openapi-generator.tech/docs/installation">https://openapi-generator.tech/docs/installation</a>)</p>
<pre><code>&gt; npm install @openapitools/openapi-generator-cli -g
&gt; brew install openapi-generator
&gt; scoop install openapi-generator-cli</code></pre><h2 id="openapi-generator-실행-옵션">OpenAPI Generator 실행 옵션</h2>
<p>OpenAPI Generator CLI를 실행하는 옵션은 다음과 같습니다.</p>
<blockquote>
<p>-i: 포함할 파일 목록 작성<br>-g: generator 이름<br>-o: 저장할 디렉토리 이름</p>
</blockquote>
<p>OpenAPI Generator가 생성할 수 있는 generator에는 typescript-axios, javascript-fetch, javascript-axios, typescript-fetch 등이 있습니다. 더욱 많은 generator 리스트는 <a href="https://openapi-generator.tech/docs/generators/">https://openapi-generator.tech/docs/generators/</a> 에서 확인할 수 있습니다.</p>
<hr>
<h2 id="example">Example</h2>
<p><a href="https://github.com/OAI/OpenAPI-Specification">OpenAPI INITIATIVE</a>에서는 몇가지 예제 데이터를 제공하고 있는데, 그 중 petstore.yaml 파일과 이를 바탕으로 타입과 호출 함수들을 생성해보겠습니다.</p>
<h4 id="예시-데이터에서-openapi-generator-실행">예시 데이터에서 OpenAPI Generator 실행</h4>
<pre><code>&gt; cd example
&gt; npx @openapitools/openapi-generator-cli generate \
    -i petstore.json \
    -g typescript-axios \
    -o typescript-axios/</code></pre><p>위의 명령어를 실행하면 generated된 코드들을 typescript-axios 폴더에서 확인하실 수 있습니다.</p>
<hr>
<h2 id="프로젝트-관리-subtree">프로젝트 관리: subtree</h2>
<h4 id="1-상위-레포지토리에서-서브트리-원격-저장소-추가">1. 상위 레포지토리에서 서브트리 원격 저장소 추가</h4>
<pre><code>git remote add subtree-test subtree-test.git</code></pre><p>상위 레포지토리에서 서브트리를 fetch, pull, push 하기 위해 서브트리로 사용할 현재의 Git 저장소를 상위 저장소의 새로운 원격 저장소로서 추가합니다. 아래에서는 폴더의 이름을 원격 저장소의 이름과 동일하게 cm-api-specification로 지정하였으나, 각 상위 레포지토리에서 다르게 지정할 수 있습니다. 저장소 추가 후에는 <code>git remote</code> 명령어로 옳게 추가되었는지 확인합니다.</p>
<h4 id="2-상위-레포지토리에서-서브트리-추가">2. 상위 레포지토리에서 서브트리 추가</h4>
<pre><code>git subtree add --prefix subtree subtree-test master</code></pre><p>명령어의 각 요소는 <code>git subtree add --prefix &lt;클론할 폴더&gt; &lt;원격 저장소의 이름&gt; &lt;브랜치 이름&gt; &lt;squash 여부&gt;</code> 순서입니다. --prefix 옵션은 서브트리를 클론할 폴더를 지정하고, 클론될 서브트리 원격 저장소의 이름, 체크아웃할 브랜치 이름을 지정하면 서브트리가 추가됩니다.</p>
<p>서브트리를 추가할 때에 subtree에 존재하던 기존의 모든 커밋을 가져오지 않으려면 squash 옵션을 뒤에 추가하면 됩니다. 단, --squash 옵션을 추가한 경우 이후 git subtree pull 명령어를 실행할 때에도 --squash 옵션을 마지막에 추가해주어야 합니다. 동일 레포지토리로 인식하지 못하고 <code>fatal: refusing to merge unrelated histories</code> 에러가 발생할 수 있습니다.</p>
<p>서브트리가 옳게 추가되면 상위 레포지토리의 폴더 구조는 아래와 같습니다.</p>
<pre><code>상위 레포
├─ README.md (상위 레포지토리의 파일)
└─ subtree-test (서브트리 폴더)
   └─ README.md (서브트리의 파일)</code></pre><h4 id="3-상위-레포지토리에서-서브트리-업데이트">3. 상위 레포지토리에서 서브트리 업데이트</h4>
<p>상위 레포지토리에서 서브트리의 변경사항을 내려받고 업데이트하는 명령어는 아래와 같습니다.</p>
<pre><code>&gt; git remote update 또는 git fetch subtree-test master
&gt; git subtree pull --prefix subtree subtree-test master

&gt; git subtree push --prefix subtree subtree-test master</code></pre><p>변경사항이 있는 경우 자동으로 머지 커밋이 생성될 수 있으며, 명령어가 짧지 않으므로 각 상위 폴더에서 npm 스크립트 등을 작성하여 사용하는 편이 좋습니다.</p>
<h4 id="그-외">그 외</h4>
<ul>
<li>상위 레포지토리에서 서브트리 제거<br>서브트리는 단순히 폴더로서 추가되어, <code>git rm -r &lt;폴더 이름&gt;</code> 명령어를 입력하면 됩니다.</li>
<li>상위 레포지토리에서 일부 폴더를 분리<br><code>git subtree split -P {디렉터리} -b {브랜치명}</code></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[헷갈리지 말자 JS 코딩테스트]]></title>
            <link>https://velog.io/@dalbodre_ari/JS-%EA%B8%B0%EB%B3%B8-%EC%A0%95%EB%A6%AC-for-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@dalbodre_ari/JS-%EA%B8%B0%EB%B3%B8-%EC%A0%95%EB%A6%AC-for-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Thu, 17 Mar 2022 18:12:00 GMT</pubDate>
            <description><![CDATA[<p>For Loop</p>
<ul>
<li>for<ul>
<li>기본형<pre><code class="language-js">for (let i = 0; i &lt; 9; i++) {
 console.log(i)
}</code></pre>
</li>
</ul>
</li>
<li>forEach<ul>
<li>Array의 prototype을 상속받은 객체가 사용할 수 있는 함수</li>
<li>반복문이 아니라 &#39;함수&#39;. 인자로 함수를 받아 각 배열의 요소에 해당 함수를 적용한다</li>
</ul>
</li>
<li>for ... in<ul>
<li>Object에 있는 key에 차례로 접근</li>
<li>Array에도 사용할 수도 있지만 일반적으로 Object를 제외한 객체에는 사용하지 말 것</li>
<li>for in으로 순회 시, prototype chain을 따라 확장 속성들도 함께 순회<ul>
<li>필요 시 Object.keys()로 확장 속성을 피해서만 반복 가능</li>
</ul>
</li>
</ul>
</li>
<li>for ... of <ul>
<li>이터러블한 객체를 모두 순회 가능</li>
<li>내부에 [Symbol.iterator]를 가진 객체라면 어떤 객체든 순회할 수 있으나, 없는 객체는 불가</li>
</ul>
</li>
</ul>
<p>Hash</p>
<ul>
<li>new Map()<ul>
<li>map.entries()는 iterator 반환</li>
<li>map.delete() : 성공 시에만 true</li>
<li>map.set()</li>
<li>map.get()</li>
<li>map.forEach((value, key, map) =&gt; { /* ... */ })<pre><code class="language-js">function solution(participant, completion) {
var answer = &#39;&#39;;
const map = new Map();
completion.forEach(completePlayer =&gt; {
   if (map.get(completePlayer))
       map.set(completePlayer, map.get(completePlayer)+1)
   else map.set(completePlayer, 1)
})
participant.forEach(participatedPlayer =&gt; {
   if (!map.get(participatedPlayer))
       answer = participatedPlayer
   else map.set(participatedPlayer, map.get(participatedPlayer)-1)
})
return answer;
}</code></pre>
</li>
</ul>
</li>
</ul>
<p>Set</p>
<pre><code class="language-js">let setA = new Set();
setA.add(&#39;a&#39;);
setA.add(&#39;b&#39;);
setA.add(&#39;a&#39;);
console.log([...setA.keys()]); // [&#39;a&#39;, &#39;b&#39;]
console.log([...setA.values()]); // [&#39;a&#39;, &#39;b&#39;]</code></pre>
<p>List</p>
<ul>
<li>arr.indexOf(a) ==&gt; 존재하지 않으면 -1 반환</li>
<li>arr.lastIndexOf(a) ==&gt; 존재하지 않으면 -1 반환</li>
<li>arr1.concat(arr2) ==&gt; 원본 배열 수정 없이 새로운 배열 반환</li>
<li>arr.slice(start, end?) ==&gt; 원본 배열 수정 없이 새로운 배열 반환</li>
<li>arr.splice(start, n?) ==&gt; 원본 배열에서 start~start+n-1까지를 삭제, 삭제된 배열값 반환</li>
<li>arr.shift() ==&gt; 원본 배열에서 첫 번째 요소를 제거하고, 제거된 요소를 반환</li>
<li>arr.unshift(...args) ==&gt; 원본 배열 왼쪽에 요소들을 추가하고, 추가된 배열의 길이를 반환<ul>
<li><pre><code class="language-js">const array1 = [1, 2, 3];
console.log(array1.unshift(4, 5)); // 5
console.log(array1); // [4,5,1,2,3]</code></pre>
</li>
</ul>
</li>
<li>arr.flat(n) ==&gt; 원본 배열을 n차원만큼 flatten하게 바꿈. 빈 값은 자동 제거</li>
<li>리스트에서 특정 원소의 갯수 찾기<ul>
<li>arr.filter(x =&gt; x==n).length</li>
</ul>
</li>
<li>가장 큰 원소의 index 찾기<ul>
<li>let i = arr.indexOf(Math.max(...arr));</li>
</ul>
</li>
</ul>
<p>Sort</p>
<ul>
<li>arr.sort()<ul>
<li>sort 인자로 비교 함수가 없을 경우, 유니코드 순서에 따라 정렬됨<ul>
<li>[1,2,3,10] =&gt; [1,10,2,3]</li>
</ul>
</li>
<li>숫자 오름차순 정렬 예시<pre><code class="language-js">arr.sort(function(a, b)  {
  if(a &gt; b) return 1;
  if(a === b) return 0;
  if(a &lt; b) return -1;
});</code></pre>
<pre><code class="language-js">arr.sort(function(a, b)  {
  return a - b
});</code></pre>
</li>
</ul>
</li>
</ul>
<p>Math</p>
<ul>
<li>Math.floor(3.9) =&gt; 3</li>
<li>Math.ceil(3.1) =&gt; 4</li>
<li>Math.round(3.5) =&gt; 4</li>
<li>Math.min(...arr)</li>
<li>Math.max(...arr)</li>
</ul>
<p>Combinations</p>
<pre><code class="language-js">function combination(arr, selectNum) {
  const result = [];
  if (selectNum === 1) return arr.map((v) =&gt; [v]);

  arr.forEach((v, idx, arr) =&gt; {
    const fixed = v;
    const restArr = arr.slice(idx + 1); // 
    const combinationArr = combination(restArr, selectNum - 1);
    const combineFix = combinationArr.map((v) =&gt; [fixed, ...v]);
    result.push(...combineFix);
  });
  return result;
}</code></pre>
<p>Permutations</p>
<pre><code class="language-js">function permutation(arr, selectNum) {
  let result = [];
  if (selectNum === 1) return arr.map((v) =&gt; [v]);

  arr.forEach((v, idx, arr) =&gt; {
    const fixer = v;
    const restArr = arr.filter((_, index) =&gt; index !== idx); // 
    const permuationArr = permutation(restArr, selectNum - 1);
    const combineFixer = permuationArr.map((v) =&gt; [fixer, ...v]);
    result.push(...combineFixer);
  });
  return result;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[우아하게(?) react native 권한 관리하기]]></title>
            <link>https://velog.io/@dalbodre_ari/%EC%9A%B0%EC%95%84%ED%95%98%EA%B2%8C-react-native-%EA%B6%8C%ED%95%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dalbodre_ari/%EC%9A%B0%EC%95%84%ED%95%98%EA%B2%8C-react-native-%EA%B6%8C%ED%95%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 08 Mar 2022 05:12:37 GMT</pubDate>
            <description><![CDATA[<h2 id="android-ios의-사용자-권한">Android, iOS의 사용자 권한</h2>
<p>개발자가 사용자의 위치, 카메라, 사진, 파일 등의 데이터에 접근하기 위해서는 해당 접근에 대한 사용자 권한 승인이 필수적이다. 기기에 상관없이 모든 권한들이 통일되어있다면 더욱 좋았겠지만 애플과 안드로이드는 각자만의 권한 처리 방식을 사용한다. 특히 iOS 13버전/android API30에서 권한과 관련된 여러 업데이트들이 존재하는데, 몇가지 중요한 점들만 살펴본다면 아래와 같다.</p>
<ol>
<li>iOS</li>
</ol>
<ul>
<li>사용자가 한번이라도 권한 요청을 거절하는 경우, 앱에서 권한 재요청이 불가하다.<ul>
<li>(사용자가 설정 페이지로 이동하도록 하여 권한 승인을 자연스럽게 유도하자!)</li>
</ul>
</li>
<li>iOS 13버전에서 위치 정보 권한에 대한 always allow 유저 플로우가 변경되었다. <ul>
<li>처음 사용자에게 위치 권한을 요청하는 창에는 &#39;앱 사용 중에만 허용&#39;, &#39;한번 허용&#39;, &#39;승인하지 않음&#39; 세 가지 선택지만 존재한다. </li>
<li>사용자가 &#39;앱 사용 중에만 허용&#39; 옵션을 선택하고 앱을 지속적으로 사용한다면, 시스템이 &quot;Allow <del>~</del> to also access your location even when you are not using the app?&quot;이라고 물어본다. 해당 권한 요청 시스템 모달에는 &#39;Keep Only While Using&#39; 옵션과 &#39;Change to Always Allow&#39; 옵션이 포함된다.</li>
<li>항상 허용 옵션을 통해 위치 권한을 승인했더라도, 주기적으로 (3일에 한번씩) 추적된 위치정보를 맵으로 보여주면서 위치 권한을 유지할 것인지 묻는 모달이 뜬다.</li>
</ul>
</li>
<li>iOS 13버전에서 일회성 권한 승인 옵션 추가<ul>
<li>single session에서만 권한을 grant한다. 사용자가 앱을 relaunch할 때마다 시스템 모달이 다시 뜨게된다.
<img src="https://images.velog.io/images/dalbodre_ari/post/6c567f3a-11a1-47d2-8507-2731e8eb17b3/image.png" alt=""></li>
</ul>
</li>
</ul>
<ol start="2">
<li>Android</li>
</ol>
<ul>
<li>사용자가 권한을 거절하여도, 서비스에서 런타임 권한 설정을 재요청할 수 있다.<ul>
<li>단, 사용자가 한번이라도 이미 권한 승인 요청을 거절한 이후에는 &#39;다시 묻지 않기&#39; 옵션이 추가된다. (이를 선택하지 않으면 계속해서 권한 재요청 가능)</li>
</ul>
</li>
<li>백그라운드, 포어그라운드 위치 정보 접근 권한이 나뉘어있다.<ul>
<li>앱이 실행되고 있을 때에만 접근 가능한 포어그라운드 위치 정보(ACCESS_FINE_LOCATION 및 ACCESS_COARSE_LOCATION)와 백그라운드에 있을 때에도 접근 가능한 ACCESS_BACKGROUND_LOCAITON이 구별된다.</li>
<li>ACCESS_COARSE_LOCAITON은 네트워크만을 이용하여 단말기 위치를 구분하고, ACCESS_FINE_LOCATION은 GPS와 네트워크를 함께 사용하여 더욱 정확한 위치 정보를 제공한다. </li>
<li>개발자가 FINE LOCATION에 대한 권한을 요청하더라도, 시스템은 사용자가 COARSE LOCATION에 대한 정보만을 제공할 수 있도록 하고 있다. 따라서 manifest에서 두가지 권한을 모두 요청하거나 덜 민감한 정보인 COARSE LOCAITON 권한만을 요청해야 한다. (아래의 그림 참고. ACCESS_FINE_LOCATION 권한만 요청하는 경우 Android 31-API12-부터는 무시된다)</li>
</ul>
</li>
<li>Android 30(API11)에서 일회성 권한 승인 옵션 추가<ul>
<li>단 iOS와는 다르게 권한 유지가 일정 시간동안 유지되어, 앱을 재실행해도 권한이 승인되어있는 경우가 발생하기도 한다.</li>
<li>오랜 시간동안 앱을 사용하지 않은 경우, 시스템이 강제로 앱의 (민감한) 런타임 권한을 삭제한다. </li>
<li>iOS와 비슷하게 항상 허용 옵션이 기본 시스템 권한 모달에 옵션으로 포함되어있지 않다. 항상 허용 옵션이 필요한 권한의 경우에만 사용자가 설정페이지로 이동할 수 있는 링크가 시스템모달 본분에 포함된다.
<img src="https://images.velog.io/images/dalbodre_ari/post/d7d33c9e-13c1-4e1a-a138-3c7393bd9cee/image.png" alt=""></li>
</ul>
</li>
</ul>
<h2 id="react-native-permissions-react-native에서-사용자-권한-관리하기">react-native-permissions, react native에서 사용자 권한 관리하기</h2>
<p>다행히 react native에서도 권한 요청 및 확인을 할 수 있도록 하는 <a href="https://github.com/zoontek/react-native-permissions">react-native-permissions</a>라는 라이브러리가 존재한다. docs가 매우 자세하게 나와있기는 하지만, 영어보다 한국어가 편한 <del>나 포함</del> 사람들을 위해 사용방법을 코드예시와 함께 정리해보았다.</p>
<h3 id="1-라이브러리-설치">1. 라이브러리 설치</h3>
<p><code>yarn add react-native-permissions</code> 명령어를 통해 설치한다. (만약 react native 버전이 63 미만이라면 linking 설정이 추가로 필요하다. <del>이건 내가 안쓰니까 패스</del>)</p>
<h3 id="2-안드로이드-manifest-권한-설정">2. 안드로이드 manifest 권한 설정</h3>
<p>android/apps/src/main/AndroidManifest.xml에 사용할 권한을 아래와 같이 추가합니다. 앞서 말했듯이 ACCESS_FINE_LOCATION을 사용하기 위해서는 ACCESS_COARCE_LOCATION이 필수로 필요하다는 것을 잊지 않도록 한다.</p>
<pre><code>&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
  package=&quot;com.myawesomeapp&quot;&gt;

  &lt;!-- 🚨 Keep only the permissions used in your app 🚨 --&gt;

  &lt;uses-permission android:name=&quot;android.permission.ACCEPT_HANDOVER&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.ACCESS_BACKGROUND_LOCATION&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.ACCESS_COARSE_LOCATION&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.ACCESS_MEDIA_LOCATION&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.ACTIVITY_RECOGNITION&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.ANSWER_PHONE_CALLS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.BLUETOOTH_ADVERTISE&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.BLUETOOTH_CONNECT&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.BLUETOOTH_SCAN&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.BODY_SENSORS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.CALL_PHONE&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.CAMERA&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.GET_ACCOUNTS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.PROCESS_OUTGOING_CALLS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.READ_CALENDAR&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.READ_CALL_LOG&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.READ_CONTACTS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.READ_EXTERNAL_STORAGE&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.READ_PHONE_NUMBERS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.READ_PHONE_STATE&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.READ_SMS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.RECEIVE_MMS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.RECEIVE_SMS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.RECEIVE_WAP_PUSH&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.RECORD_AUDIO&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.SEND_SMS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.USE_SIP&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.WRITE_CALENDAR&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.WRITE_CALL_LOG&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.WRITE_CONTACTS&quot; /&gt;
  &lt;uses-permission android:name=&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot; /&gt;
  &lt;uses-permission android:name=&quot;com.android.voicemail.permission.ADD_VOICEMAIL&quot; /&gt;

  &lt;!-- … --&gt;

&lt;/manifest&gt;</code></pre><h3 id="3-ios-권한-설정">3. iOS 권한 설정</h3>
<p>처음에는 iOS의 권한 handler가 아무것도 설치되어있지 않다. ios/Podfile에 아래와 같이 추가하려는 권한에 대한 permission handler을 추가하고 ios 폴더에서 <code>pod install</code>명령어를 실행한다.</p>
<pre><code>target &#39;YourAwesomeProject&#39; do

  # …

  permissions_path = &#39;../node_modules/react-native-permissions/ios&#39;

  pod &#39;Permission-AppTrackingTransparency&#39;, :path =&gt; &quot;#{permissions_path}/AppTrackingTransparency&quot;
  pod &#39;Permission-BluetoothPeripheral&#39;, :path =&gt; &quot;#{permissions_path}/BluetoothPeripheral&quot;
  pod &#39;Permission-Calendars&#39;, :path =&gt; &quot;#{permissions_path}/Calendars&quot;
  pod &#39;Permission-Camera&#39;, :path =&gt; &quot;#{permissions_path}/Camera&quot;
  pod &#39;Permission-Contacts&#39;, :path =&gt; &quot;#{permissions_path}/Contacts&quot;
  pod &#39;Permission-FaceID&#39;, :path =&gt; &quot;#{permissions_path}/FaceID&quot;
  pod &#39;Permission-LocationAccuracy&#39;, :path =&gt; &quot;#{permissions_path}/LocationAccuracy&quot;
  pod &#39;Permission-LocationAlways&#39;, :path =&gt; &quot;#{permissions_path}/LocationAlways&quot;
  pod &#39;Permission-LocationWhenInUse&#39;, :path =&gt; &quot;#{permissions_path}/LocationWhenInUse&quot;
  pod &#39;Permission-MediaLibrary&#39;, :path =&gt; &quot;#{permissions_path}/MediaLibrary&quot;
  pod &#39;Permission-Microphone&#39;, :path =&gt; &quot;#{permissions_path}/Microphone&quot;
  pod &#39;Permission-Motion&#39;, :path =&gt; &quot;#{permissions_path}/Motion&quot;
  pod &#39;Permission-Notifications&#39;, :path =&gt; &quot;#{permissions_path}/Notifications&quot;
  pod &#39;Permission-PhotoLibrary&#39;, :path =&gt; &quot;#{permissions_path}/PhotoLibrary&quot;
  pod &#39;Permission-PhotoLibraryAddOnly&#39;, :path =&gt; &quot;#{permissions_path}/PhotoLibraryAddOnly&quot;
  pod &#39;Permission-Reminders&#39;, :path =&gt; &quot;#{permissions_path}/Reminders&quot;
  pod &#39;Permission-Siri&#39;, :path =&gt; &quot;#{permissions_path}/Siri&quot;
  pod &#39;Permission-SpeechRecognition&#39;, :path =&gt; &quot;#{permissions_path}/SpeechRecognition&quot;
  pod &#39;Permission-StoreKit&#39;, :path =&gt; &quot;#{permissions_path}/StoreKit&quot;

end</code></pre><p>이후 ios/프로젝트이름/Info.plist에 권한에 대한 설명을 추가한다. 해당 string은 시스템 권한 설정 모달의 메시지로서 사용된다. 복붙하다가 카메라 권한 요청 모달에 갤러리 접근 메시지가 뜨는 불상사가 없도록 하자. XCode에서 직접 설정도 가능하다.</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
&lt;plist version=&quot;1.0&quot;&gt;
&lt;dict&gt;

  &lt;!-- 🚨 Keep only the permissions used in your app 🚨 --&gt;

  &lt;key&gt;NSAppleMusicUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSBluetoothAlwaysUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSBluetoothPeripheralUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSCalendarsUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSCameraUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSContactsUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSFaceIDUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSLocationAlwaysAndWhenInUseUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSLocationAlwaysUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSLocationTemporaryUsageDescriptionDictionary&lt;/key&gt;
  &lt;dict&gt;
    &lt;key&gt;YOUR-PURPOSE-KEY&lt;/key&gt;
    &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;/dict&gt;
  &lt;key&gt;NSLocationWhenInUseUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSMicrophoneUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSMotionUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSPhotoLibraryUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSPhotoLibraryAddUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSRemindersUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSSpeechRecognitionUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSSiriUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;
  &lt;key&gt;NSUserTrackingUsageDescription&lt;/key&gt;
  &lt;string&gt;YOUR TEXT&lt;/string&gt;

  &lt;!-- … --&gt;

&lt;/dict&gt;
&lt;/plist&gt;</code></pre><h3 id="4-실제-권한-check-request-코드-작성하기">4. 실제 권한 check, request 코드 작성하기</h3>
<p>react-native-permissions의 권한 체크/요청에 따른 결과는 아래 표를 따른다. </p>
<table>
<thead>
<tr>
<th>Return value</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td><code>RESULTS.UNAVAILABLE</code></td>
<td>This feature is not available (on this device / in this context)</td>
</tr>
<tr>
<td><code>RESULTS.DENIED</code></td>
<td>The permission has not been requested / is denied but requestable</td>
</tr>
<tr>
<td><code>RESULTS.GRANTED</code></td>
<td>The permission is granted</td>
</tr>
<tr>
<td><code>RESULTS.LIMITED</code></td>
<td>The permission is granted but with limitations</td>
</tr>
<tr>
<td><code>RESULTS.BLOCKED</code></td>
<td>The permission is denied and not requestable anymore</td>
</tr>
</tbody></table>
<p>권한 체크/요청 플로우는 아래와 같다.</p>
<ol>
<li>권한 체크 요청</li>
<li>해당 feature가 기기에서 사용 가능한지 확인 (불가하면 UNAVAILABLE)</li>
<li>해당 feature가 기기에서 요청 가능한지 확인 (불가하면 GRANTED, LIMITED,BLOCKED)</li>
<li>DENIED 상태를 확인한 후, 권한 승인 요청</li>
<li>권한 승인 시 GRANTED, iOS에서 거절 시 BLOCKED, Android에서 거절 시 DENIED, Android에서 &#39;다시 보지 않기&#39; 옵션 선택 시 BLOCKED</li>
</ol>
<p>해당 플로우를 따라 아래와 같이 코드로 작성할 수 있다. check 결과가 DENIED일 때에 request를 실행했으며, 그 결과가 GRANTED가 아닐 경우 LIMITED, BLOCKED와 같이 취급하도록 하였다. (LIMITED의 경우 권한 종류에 따라 달라지는데, 해당 프로젝트에서는 앱 사용 중에만 위치 정보에 접근하기 때문에 따로 케이스를 분리하지는 않았다)</p>
<pre><code class="language-ts">let requested: PermissionStatus;
const checked = await check(needPermission);
switch (checked) {
  case RESULTS.UNAVAILABLE:
    return handlePermissionError(
      strings.PERMISSION_UNAVAILABLE,
      essential
    );
  case RESULTS.GRANTED:
    return handlePermissionSuccess();
  case RESULTS.DENIED:
    requested = await request(needPermission);
    if (requested === RESULTS.GRANTED) {
      return handlePermissionSuccess();
    }
  case RESULTS.LIMITED:
  case RESULTS.BLOCKED:
  default:
    return handlePermissionError(strings.PERMISSION_BLOCKED, essential);
}</code></pre>
<h2 id="우아하게-권한-관리하기">우아하게(?) 권한 관리하기</h2>
<p><img src="https://images.velog.io/images/dalbodre_ari/post/8bc99401-17d1-4c9c-a63f-5045904ab6a4/image.png" alt="">
<a href="https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/accessing-user-data/">iOS</a>와 <a href="https://developer.android.com/training/permissions/requesting?hl=ko#explain">Android</a> 모두 권한 요청 시도가 있을 때에는, 위의 그림과 같이 사용자에게 해당 권한이 왜 필요하고 어떻게 사용되는지 전달하는 것을 권유하고있다. 그러나 모든 개별 권한에 맞춤형 페이지를 만드는 것도 어려웠고, 실제 그 기능을 사용할 때에 (<strong>보통 한 두개의</strong>) 권한에 대한 설명 메시지를 간략하게 보여주는 것 만으로도 충분히 사용자에게 권한 요청에 대한 이유를 설명할 수 있을 것이라고 생각했다. 아래는 dot slash dash라는 어플인데, 이런 방식으로 앞에 시스템 권한 요청 모달이 뜨면 뒤쪽에 해당 권한에 대한 설명이 보이도록 위의 함수를 업데이트했다. <img src="https://images.velog.io/images/dalbodre_ari/post/4c2dd4d2-5354-4331-a401-c95467f0eedc/image.png" alt=""></p>
<p>react native 앱의 최상단에 모달을 삽입하고 해당 모달을 관리하는 mobx store을 사용하여, 앞에서 짰던 권한요청 함수가 실행될 때에 open 여부와 message를 변경하는 getPermission 함수를 작성하였다. 함수는 permission 이름을 받아, 기기가 iOS인지 Android인지를 확인하고 그에 맞는 권한을 체크/요청한다. 권한이 승인된 경우에는 인자로 받은 onSuccess 함수를 실행하고, 권한이 거절되면 (essential 여부에 따라 설정페이지로 이동하며) onFailed 함수를 실행하도록 구성하였다. </p>
<pre><code class="language-ts">const androidPermissions: PermissionsPerOS = {
    location: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
    camera: PERMISSIONS.ANDROID.CAMERA,
    photo: PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE,
};
const iosPermissions: PermissionsPerOS = {
    location: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
    camera: PERMISSIONS.IOS.CAMERA,
    photo: PERMISSIONS.IOS.PHOTO_LIBRARY,
};
const permissionsPerOS =
    Platform.OS === strings.PLATFORM_IOS ? iosPermissions : androidPermissions;

const getPermission = async (
    permission: PossiblePermission,
    onSuccess?: () =&gt; void,
    onFailed?: () =&gt; void,
    essential = false
): Promise&lt;boolean&gt; =&gt; {
    const needPermission = permissionsPerOS[permission];
    permissionModalStore.setMessage(PERMISSION_REQUEST_MESSAGE[permission]);
    permissionModalStore.setOpen(true);

    const handlePermissionSuccess = () =&gt; {
        if (onSuccess) onSuccess();
        permissionModalStore.setOpen(false);
        permissionModalStore.setMessage(&#39;&#39;);
        return true;
    };

    const handlePermissionError = (message: string, openSetting = false) =&gt; {
        if (openSetting) goToSettings(message);
        if (onFailed) onFailed();
        permissionModalStore.setOpen(false);
        permissionModalStore.setMessage(&#39;&#39;);
        return false;
    };

    let requested: PermissionStatus;
    const checked = await check(needPermission);
    switch (checked) {
        case RESULTS.UNAVAILABLE:
            return handlePermissionError(
                strings.PERMISSION_UNAVAILABLE,
                essential
            );
        case RESULTS.GRANTED:
            return handlePermissionSuccess();
        case RESULTS.DENIED:
            requested = await request(needPermission);
            if (requested === RESULTS.GRANTED) {
                return handlePermissionSuccess();
            }
        case RESULTS.LIMITED:
        case RESULTS.BLOCKED:
        default:
            return handlePermissionError(strings.PERMISSION_BLOCKED, essential);
    }
};</code></pre>
<h2 id="여러-권한-한번에-요청하기">여러 권한 한번에 요청하기</h2>
<p>앱을 사용하다가 사진을 첨부하거나, 근처 매장을 찾는 등의 경우에는 한 두개의 권한에만 승인 요청이 발생한다. 그러나 앱을 처음 깔거나, 앱을 실행할 때마다(<del>앱에 따라서는..좋지 않은 플로우기는 하다</del>) 앱에서 필요한 설정들을 설명하고 한번에 승인을 요청하기도 한다. 이러한 권한 요청들을 리스트로 받아, 리스트 순서대로 권한을 요청하는 getPermissions 함수를 작성해보자. Promise들을 순차적으로 실행 보장하기 위해 재귀적인 방법을 쓰기도 하지만 아래 함수에서는 reduce와 async/await를 사용하였다.</p>
<pre><code class="language-ts">const getPermissions = async (
    permissions: PossiblePermission[],
    onSuccess?: () =&gt; void,
    onFailed?: () =&gt; void,
    essential = false
): Promise&lt;void&gt; =&gt; {
    const permissionsResult = permissions.reduce(
        async (previousPermission, currentPermission) =&gt; {
            const previousPermissionResult = await previousPermission;
            const currentPermissionResult = await getPermission(
                currentPermission
            );
            previousPermissionResult.push(currentPermissionResult);
            return previousPermissionResult;
        },
        Promise.resolve&lt;boolean[]&gt;([])
    );

    permissionsResult.then((result) =&gt; {
        if (result.every(Boolean) &amp;&amp; onSuccess) onSuccess();
        if (!result.every(Boolean) &amp;&amp; onFailed) {
            if (essential) goToSettings(strings.PERMISSION_BLOCKED);
            onFailed();
        }
    });
};</code></pre>
<p>아래는 실제로 <code>getPermissions([&#39;location&#39;, &#39;camera&#39;, &#39;photo&#39;])</code> 함수를 실행했을 때 확인할 수 있는 결과이다.
<img src="https://images.velog.io/images/dalbodre_ari/post/ca221886-d477-4580-8e6c-d9e475331cfb/ezgif-1-b470309b45.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Asynchronous JavaScript: call stack, macro/microtask queue]]></title>
            <link>https://velog.io/@dalbodre_ari/Asynchronous-JavaScript-call-stack-macromicrotask-queue</link>
            <guid>https://velog.io/@dalbodre_ari/Asynchronous-JavaScript-call-stack-macromicrotask-queue</guid>
            <pubDate>Thu, 24 Feb 2022 06:58:31 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트는 싱글 스레드 언어로 잘 알려져 있다. 자바스크립트 엔진은 콜스택에 들어온 함수를 바로 실행한다. 오랜 시간이 걸리는 동기함수가 콜스택에 들어오면 그동안 다른 함수는 전혀 실행될 수 없는데, 결국 &#39;응답 없는 페이지&#39;라는 경고가 왕왕 발생하기도 한다. <img src="https://images.velog.io/images/dalbodre_ari/post/2e546b70-0bfb-48a7-a420-592fbb3a1bf9/image.png" alt=""></p>
<h2 id="외부-호스팅-환경을-통한-javascript의-비동기-처리">외부 호스팅 환경을 통한 JavaScript의 비동기 처리</h2>
<p>그렇다면 어떻게 자바스크립트에서는 비동기 처리를 하고 있는 것일까. 다들 한번쯤은 setTimeout과 같은 기능을 써보았을텐데, 싱글 스레드만으로 비동기처리를 한다는 것이 도저히 상상이 가지 않는다. 내 머리가 잘못 된건가 아니면 자바스크립트가 사실은 싱글스레드가 아닌 것인가..?! </p>
<p>다행히 둘다 아니다. 자바스크립트의 비동기처리는 브라우저와 같은 외부 호스팅 환경에 의존한다. 외부 호스팅 환경의 Web API, Callback Queue, Event Loop가 비동기 처리를 위해 이용된다. </p>
<p>처음 자바스크립트 콜스택에 setTimeout 함수가 들어오면 자바스크립트 엔진은 이를 수행한다. 자바스크립트 엔진은 Web API를 통해 호스팅환경에게 타이머를 실행하도록 넘기는 것으로 함수 실행을 마치고, 완료된 setTimeout 함수는 콜스택에서 지워진다. 브라우저는 이후 타이머가 완료되면 (setTimeout과 함께 전달되었던) 콜백을 콜백큐에 넘긴다. 이벤트루프는 반복해서 콜스택이 비어있는지를 확인하는데, 비어있는 경우에 콜백큐의 첫번째 이벤트를 콜스택에 밀어넣는다.</p>
<p><img src="https://images.velog.io/images/dalbodre_ari/post/42a1e23d-716b-492f-9549-e3cbb24064e0/image.png" alt=""></p>
<h3 id="settimeout-코드-예시">setTimeout 코드 예시</h3>
<pre><code class="language-js">console.log(&#39;Hi&#39;)
setTimeout(function A() {
  console.log(&#39;Hello Again&#39;)
},1000)
console.log(&#39;Bye&#39;)</code></pre>
<p>setTimeout이 포함된 예시코드가 어떻게 동작하는지 단계별로 알아보자. </p>
<ol>
<li>코드가 실행되면, 가장 첫번째 줄인 console.log(‘Hi’)가 콜스택에 추가된다. 이를 JS엔진이 실행하여 콘솔에 hi가 출력되면 콜스택에서 console.log가 삭제된다. </li>
<li>다음줄인 setTimeout(~)가 콜스택에 추가되고 실행된다. 자바스크립트 엔진은 WebAPI를 통해 브라우저에 타이머를 생성한다. 브라우저에서 카운트다운이 이어서 처리된다. 자바스크립트 엔진은 WebAPI 호출을 완료하였기 때문에 카운트다운이 되고있는지와는 상관없이 콜스택에서 setTimeout을 제거한다.</li>
<li>Bye를 출력하는 함수가 콜스택에 추가되고, Hi와 동일하게 콘솔에 출력된 후 콜스택에서 제거된다.</li>
<li>시간이 지나 타이머가 완료되면 브라우저는 setTimeout의 콜백함수였던 A를 콜백큐에 밀어넣는다. 이벤트루프는 현재 <del>Bye까지 출력되어</del> 콜스택이 비어있기 때문에 A함수를 콜백큐에서 콜스택으로 밀어넣는다. </li>
<li>A함수가 실행되면 콜스택에 console.log(&quot;Hello Again&quot;) 함수 블록이 추가된다. <del>3번이 끝나면서 비어있던 콜스택에 A 함수 블럭이 쌓이고 그 위에 Hello Again을 출력하는 함수블럭이 또 있는 상태가 된다.</del> 자바스크립트 엔진은 콘솔창에 Hello Again을 출력하는 것으로 console.log 함수를 완료하고, 이에 따라 console.log 함수 블럭과 하단의 A 함수 블럭이 차례대로 콜스택에서 제거된다.</li>
</ol>
<h2 id="webapi-타이머-사용-시-주의점">WebAPI 타이머 사용 시 주의점</h2>
<p>주의해야할 점은 실제 콜백함수가 언제 실행될지 아무도 장담할 수 없다는 것이다. 위에서는 1000ms 이후에 콜백을 실행해달라고 코드를 작성했지만, 브라우저는 단순히 1000ms 타이머가 완료되면 콜백큐에 해당 콜백을 넣어준다. 콜백 큐에는 이미 추가된 이벤트들이 존재할 수도 있고, 언제 콜스택이 완전히 비어 이벤트루프가 콜백큐의 함수를 실행할 수 있을지는 아무도 모른다. 
<a href="https://developer.mozilla.org/ko/docs/Web/API/setTimeout#%EB%94%9C%EB%A0%88%EC%9D%B4%EA%B0%80_%EC%A7%80%EC%A0%95%ED%95%9C_%EA%B0%92%EB%B3%B4%EB%8B%A4_%EB%8D%94_%EA%B8%B4_%EC%9D%B4%EC%9C%A0">setTimeout에 대한 MDN 글</a>에서 콜백 딜레이, 비활성 탭의 타임아웃, 추적 스크립트 스로틀링, 페이지 로드 등 타이머 실행 지연에 대한 자세한 설명을 확인할 수 있다. </p>
<h2 id="마이크로-태스크-큐--모든-콜백이-시간-순으로-실행되는-것은-아니다">마이크로 태스크 큐 : 모든 콜백이 시간 순으로 실행되는 것은 아니다</h2>
<p>Promise는 자바스크립트에서 비동기 처리를 쉽게 처리하기위한 객체이다. <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise">MDN</a>에서는 미래의 어떤 시점에 결과를 제공하겠다는 &#39;약속&#39;(프로미스)이라고 간단하게 설명하고 있다. Promise는 resolve되면 다음 then의 함수를 이행한다. 
<img src="https://images.velog.io/images/dalbodre_ari/post/61b955e3-6af0-448f-91dd-fcaaf3019693/image.png" alt=""></p>
<p>그렇다면, 아래 코드를 실행하면 어떻게 될까? setTimeout과 Promise가 하나의 코드에 존재한다. setTimeout은 0초의 시간간격을 두고 있고 Promise또한 바로 resolve했기 때문에, setTimeout과 Promise 모두 즉시 콜백 함수를 콜백 큐에 넣게 될것이다.</p>
<pre><code class="language-js">setTimeout(()=&gt;{console.log(&quot;A&quot;)},0)
Promise.resolve()
  .then(()=&gt;{console.log(&#39;B&#39;)})
  .then(()=&gt;{console.log(&#39;C&#39;)})</code></pre>
<p>정답은 B-C-A 순이다. Promise는 마이크로 태스크로 취급되기 때문이다. </p>
<p>콜백 큐에 들어가는 task들에는 우선순위가 존재한다. 비동기처리를 하는 데에 모든 task들이 FIFO 수준으로 처리되기에는 부족했을 것이다. 따라서 일반 (macro) task보다 먼저 실행되어야 하는 microTask를 구분하기 시작했다. 위에서 설명했던 콜백 큐가 macroTask들만 가진 큐와 microTask만으로 구성된 큐로 분리된 것이다. 이벤트 루프는 자바스크립트의 콜스택이 비었는지 확인하고, 만약 비었다면 (일반)태스크 큐 대신 마이크로 태스크 큐의 함수들을 먼저 실행한다. 
<img src="https://images.velog.io/images/dalbodre_ari/post/a200b011-0470-49c7-b68f-3d2d956dd8b9/image.png" alt=""></p>
<p>Promise는 일반적으로 마이크로 태스크로 취급된다. 물론 특정 브라우저에서는 Promise가 일반 태스크로 취급되는 경우도 있다고 한다. <del>우선 크롬과 사파리가 아니면 상관 없는게 아닐까 ㅎㅎ..</del> 각 태스크큐의 종류와 해당하는 태스크들은 다음과 같다.</p>
<ul>
<li>microtask queue : Promise, queueMicrotask, MutationObserver</li>
<li>(macro)task queue : setTimeout, setInterval</li>
<li>RAF queue : requestAnimationFrame</li>
</ul>
<p>과거 ThreeJS를 통해 3D 애니메이션을 만들 때에 setInterval 대신 requestAnimationFrame을 사용했었는데, 오랜만에 보니까 반갑기도 하고,,, 해당 내용은 ThreeJS 포스팅을 하면서 내용을 추가하거나 함께 정리해보아야겠다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[What is Typescript? (effective typescript Chap.1)]]></title>
            <link>https://velog.io/@dalbodre_ari/What-is-Typescript-effective-typescript-Chap.1</link>
            <guid>https://velog.io/@dalbodre_ari/What-is-Typescript-effective-typescript-Chap.1</guid>
            <pubDate>Thu, 17 Feb 2022 07:06:31 GMT</pubDate>
            <description><![CDATA[<h2 id="what-is-typescript">What is Typescript?</h2>
<p>(참고 : <a href="https://www.typescriptlang.org/docs/handbook/2/basic-types.html">https://www.typescriptlang.org/docs/handbook/2/basic-types.html</a>)</p>
<h3 id="동적-타입-언어-자바스크립트">동적 타입 언어, 자바스크립트</h3>
<p>자바스크립트는 인터프리터 언어이면서 동적 타입 언어로, 런타임 환경에서 한줄한줄씩 코드를 읽고 실행하며 값이 할당되는 과정에서 값의 타입에 의해 변수의 타입이 결정된다. 런타임에서 코드가 실행될 때 해당하는 값이 어떤 동작과 능력을 가지고 있는지를 확인하고, 불가능하다면 TypeError를 반환한다.</p>
<p>예시에서는 message가 Hello World라는 문자열 값이지만, 중간에 숫자나 boolean 등의 값으로 재할당이 일어나면서 변수의 타입 또한 변경될 수 있다. 두번째/세번째 줄의 실행 결과는 해당 코드가 실행되는 당시의 message의 값에 따라 달라지게 되며 message가 호출 가능한지, toLowerCase라는 프로퍼티를 가지는지(string 타입인지), 만약 가진다면, toLowerCase를 호출 가능한지 등을 평가하고 실행이 완료될 것이다.  </p>
<pre><code class="language-js">let message = &quot;Hello World!&quot;;
message.toLowerCase();
message(); // TypeError: message is not a function</code></pre>
<p>실행 시점에서 string과 number과 같은 원시 타입은 typeof 연산자를 통해 각 값들의 타입을 확인할 수 있지만, 함수의 파라미터와 같은 경우에는 그 타입을 장담할 수 없다. 아래 예시 함수에서는 x가 호출 가능한 프로퍼티인 flip을 가져야만 정상적으로 동작한다. 그러나 함수가 실제로 호출되는 그 순간의 x값과 타입에 따라 정상작동 여부가 평가되기 때문에 코드를 실행 전에 동작을 예측하기 어렵다. </p>
<pre><code class="language-js">function fn(x) {
  return x.flip();
}</code></pre>
<p>공식문서의 내용을 그대로 옮겨보자면, _타입_이란 어떤 값이 fn으로 전달될 수 있고, 어떤 값은 실행에 실패할 것임을 설명하는 개념이다. JavaScript는 오직 동적 타입만을 제공하며, 코드를 실행해야만 어떤 일이 벌어지는지 비로소 확인할 수 있다.</p>
<blockquote>
<p>A type is the concept of describing which values can be passed to fn and which will crash. JavaScript only truly provides dynamic typing - running the code to see what happens.</p>
</blockquote>
<h3 id="타입스크립트">타입스크립트</h3>
<p>위의 문제를 해결하기 위해, 코드를 실행하기 전에 이러한 버그를 미리 발견할 수 있는 정적 타입 검사기 TypeScript가 도입되었다. _정적 타입 시스템_은 사용된 값들의 형태와 동작을 정하고, 이 정보들을 기반으로 프로그램이 제대로 작동할지에 대하여 알려준다. 아래 코드를 (자바스크립트로 실행하기 전에) 타입스크립트로 실행하면 런타임에 발생했을 에러를 런타임 이전에 확인할 수 있다.</p>
<pre><code class="language-js">const message = &quot;hello!&quot;;
message(); // This expression is not callable. Type &#39;String&#39; has no call signatures.</code></pre>
<p>또한, 타입스크립트는 자바스크립트 실행 시 런타임 에러는 발생하지 않지만 무언가 이상한 경우도 사용자에게 알려주어 휴먼에러를 방지(!)하기도 한다. JS에서는 객체에 존재하지 않는 프로퍼티에 접근하면 에러를 반환하는 것이 아니라 undefined를 반환하지만, 타입스크립트의 정적 타입 시스템은 (<del>오류를 발생시키지 않는 “유효한” JS 코드라도</del>) 오류로 간주할 수 있다.</p>
<pre><code class="language-js">// 미선언 속성
const user = {
  name: &quot;Daniel&quot;,
  age: 26,
};
user.location; // Property &#39;location&#39; does not exist on type &#39;{ name: string; age: number; }&#39;.

// 오타
const announcement = &quot;Hello World!&quot;;
announcement.toLocaleLowercase(); // Error
announcement.toLocalLowerCase(); // Error
announcement.toLocaleLowerCase();

// 미호출 함수 : ()로 호출하는 것을 잊었을 때
function flipCoin() {
  return Math.random &lt; 0.5; // Operator &#39;&lt;&#39; cannot be applied to types &#39;() =&gt; number&#39; and &#39;number&#39;.
}

// 논리 오류 : 실행되지 않는 블록
const value = Math.random() &lt; 0.5 ? &quot;a&quot; : &quot;b&quot;;
if (value !== &quot;a&quot;) {
  // ...
} else if (value === &quot;b&quot;) { 
  // This condition will always return &#39;false&#39; since the types &#39;&quot;a&quot;&#39; and &#39;&quot;b&quot;&#39; have no overlap.
  // ...
}</code></pre>
<p>실제 타입스크립트는 크게 ts파일을 js파일로 컴파일(번역의 의미를 강조하여 트랜스파일이라고도 한다)하는 기능과, 이제까지 우리가 이야기했던 타입 시스템 기능을 가진다. 타입스크립트와 자바스크립트의 관계성, 타입스크립트의 기본적인 특성을 조금 더 자세하게 살펴보도록 하자!</p>
<h2 id="item1-relation-between-typescript-and-javascript">Item1. Relation between Typescript and Javascript</h2>
<h3 id="타입스크립트는-타입이-정의된-자바스크립트의-상위집합superset이다">타입스크립트는 타입이 정의된 자바스크립트의 상위집합(superset)이다.</h3>
<p>문법적으로 오류가 없는 자바스크립트 프로그램은 타입스크립트 프로그램이라고 할 수 있다. 단순히 main.js 파일을 main.ts 파일로 바꾸어도 정상적으로 동작한다. 그러나 모든 타입스크립트는 자바스크립트가 아닌데, 타입스크립트 파일(ts, tsx)을 컴파일하면 타입과 관련된 구문들이 모두 삭제되고 자바스크립트 파일(js, jsx)이 생성되는 것을 알고있다면 이해가 편할 것이다.</p>
<p>아래는 유효한 타입스크립트 프로그램이지만, 노드로 바로 실행하면 unexoected token &quot;:이 있다는 오류를 출력한다.</p>
<pre><code class="language-ts">function greet(who: string) {
  console.log(&#39;Hello&#39;, who)
}</code></pre>
<p>해당 파일을 컴파일하면 타입과 관련된 부분은 모두 삭제되고 아래와 같은 자바스크립트 프로그램으로 변환된다. (아래 코드로 작성된 자바스크립트, 타입스크립트 파일 모두 유효하다.)</p>
<pre><code class="language-js">function greet(who) {
  console.log(&#39;Hello&#39;, who)
}</code></pre>
<h3 id="타입-체커를-통해-문제를-찾아낼-수-있다">타입 체커를 통해 문제를 찾아낼 수 있다.</h3>
<h4 id="타입-추론을-통한-문법적-오류-탐색">타입 추론을 통한 문법적 오류 탐색</h4>
<p>문법적 유효성과 동작의 유효성은 다른 문제이지만, 많은 자바스크립트의 런타임 문제가 타입 체커만으로도 개선될 수 있다.(약 15%의 문제가 개선된다는 설문조사가 있다고 한다) 아래 코드는 JS로 실행하는 경우 런타임에서 &quot;city.toUppercase is not a function&quot;이라는 타입에러가 발생한다. toUpperCase 함수의 오타이지만 실제 해당 코드가 실행될 때에나 오류를 알 수 있다. 반면 타입스크립트의 타입체커는 city 변수의 값을 통해 타입을 추론하여 &quot;toUppercase 속성이 string속성에 없습니다. toUpperCase를 사용하시겠습니까&quot;라고 런타임 이전에 알려준다.</p>
<pre><code class="language-js">let city = &#39;new york city&#39;;
console.log(city.toUppercase()); // 오타 -&gt; toUpperCase()</code></pre>
<h4 id="의도와는-다르게-작성된-잘못된-코드-탐색">의도와는 다르게 작성된 잘못된 코드 탐색</h4>
<p>실제 오류는 발생하지 않지만 의도와는 다르게 작성된 코드 또한 존재한다. 아래 예시는 name, capital 속성을 가지고 있는 state에서 오타로 capitol 속성을 부르는 경우이다. 실제 코드를 실행하면 에러 없이 undefined를 세번 프린트하게 되지만, 런타임 이전에 타입 체커는 &quot;capitol 속성이 없으며 capital을 사용할 것이냐&quot;라고 물어봐주어 개발자의 의도와 실제 코드 동작이 달라지는 경우를 방지해준다.</p>
<pre><code class="language-js">const states = [
  { name: &#39;Alabama&#39;, capital: &#39;Montgomery&#39; },
  { name: &#39;Alaska&#39;, capital: &#39;Juneau&#39; },
  { name: &#39;Arizona&#39;, capital: &#39;Phoenix&#39; },
]
for (const state of states) {
  console.log(state.capitol)
  // ~~~~~~~ Property &#39;capitol&#39; does not exist on type
  //         &#39;{ name: string; capital: string; }&#39;.
  //         Did you mean &#39;capital&#39;?
}</code></pre>
<h4 id="의도를-분명하게-나타내기-위한-명시적-타입-선언-가능">의도를 분명하게 나타내기 위한 명시적 타입 선언 가능</h4>
<p>아래 코드는 위의 코드와 똑같아보일 수 있으나, states의 정의에서 오타가 생긴 경우이다. 타입 체커는 capital이 옳은 속성 명인지 capitol이 옳은 속성 명인지 모른다. 따라서 State의 타입 선언이 없었다면 state의 타입을 name과 capitol이라는 속성을 가진 객체로 추론하여 console.log 부분에서 &quot;capital 속성을 capitol로 바꾸시겠습니까?&quot; 하고 물어봤을 것이다. 반면 State 타입 선언이 존재하는 경우, states를 정의하는 과정에서 의도에 맞는 코드인지 묻는 아래와 같은 제안을 확인하게 된다.</p>
<pre><code class="language-ts">interface State {
  name: string
  capital: string
}
const states: State[] = [
  { name: &#39;Alabama&#39;, capitol: &#39;Montgomery&#39; },
  // ~~~~~~~~~~~~~~~~~~~~~
  { name: &#39;Alaska&#39;, capitol: &#39;Juneau&#39; },
  // ~~~~~~~~~~~~~~~~~
  { name: &#39;Arizona&#39;, capitol: &#39;Phoenix&#39; },
  // State 정의가 있을 때에 의도를 명확하게 전달할 수 있다.
  // ~~~~~~~~~~~~~~~~~~ Object literal may only specify known
  //         properties, but &#39;capitol&#39; does not exist in type
  //         &#39;State&#39;.  Did you mean to write &#39;capital&#39;?
  // ...
]
for (const state of states) {
  console.log(state.capital)
  // State 정의가 없었다면??
  // ~~~~~~~ Property &#39;capital&#39; does not exist on type
  //         &#39;{ name: string; capitol: string; }&#39;.
  //         Did you mean &#39;capitol&#39;?
}
</code></pre>
<h3 id="관계성-한눈에-보기">관계성 한눈에 보기</h3>
<p>타입스크립트는 자바스크립트에서 발생하는 런타임오류를 줄이기 위해 도입되었으며,크게 컴파일 기능 + 타입 체크 기능으로 구성된다. 아래 그림에서 자바스크립트와의 관계성을 한번에 확인할 수 있다. 타입과 관련된 구문들이 존재하지 않는 타입스크립트만 자바스크립트라 할 수 있으며(컴파일 전후 동일), 모든 타입스크립트/자바스크립트가 타입 체크를 통과하지는 않는다. (사용자가 capital을 선언하고 capitol을 부르는 코드를 의도했을 수도 있지 않은가!)
<img src="https://images.velog.io/images/dalbodre_ari/post/9e6398c5-d3af-42d5-a78c-42633174458e/image.png" alt=""></p>
<h4 id="타입-스크립트는-자바스크립트를-모델링한다">타입 스크립트는 자바스크립트를 모델링한다</h4>
<p>타입스크립트는 자바스크립트 기반으로 제작되었기 때문에, 아래와 같이 기본적인 타입 변환과 같은 특징들을 그대로 계승한다.</p>
<pre><code class="language-ts">const x = 2 + &#39;3&#39; // OK, type is string
const y = &#39;2&#39; + 3 // OK, type is string</code></pre>
<p>단, (위에서 봤던 정의되지 않은 속성에 접근을 막는 것과 같이) 의도치 않은 코드가 런타임오류로 이어질 수 있어 자바스크립트에서는 정상적으로 동작하는 코드에도 타입 에러를 표시하기도 한다. 책의 저자는 아래와 같이 null과 7을 더하거나 불필요한 매개변수를 추가해서 함수 호출을 하는 것을 당연하게 여긴다면 타입스크립트를 쓰지 말라고 한다.(<del>돌려까는 기술인가..!ㅋㅋ</del>)</p>
<pre><code class="language-ts">const a = null + 7 // Evaluates to 7 in JS
// ~~~~ Operator &#39;+&#39; cannot be applied to types ...
const b = [] + 12 // Evaluates to &#39;12&#39; in JS
// ~~~~~~~ Operator &#39;+&#39; cannot be applied to types ...
alert(&#39;Hello&#39;, &#39;TypeScript&#39;) // alerts &quot;Hello&quot;
// ~~~~~~~~~~~~ Expected 0-1 arguments, but got 2</code></pre>
<h4 id="모든-런타임-오류를-잡아낼-수-있지는-않다">모든 런타임 오류를 잡아낼 수 있지는 않다</h4>
<p>타입스크립트는 타입시스템을 기반으로 설계되었기 때문에, 아래와 같이 타입 체크를 통과하더라도 여전히 런타임에서 오류가 발생할 수 있다. 타입 체커를 통과했더라도 에러가 발생하는 경우는 any를 사용할 수록 빈번하게 발생하는데, 이는 item5에서 더욱 자세하게 이야기하고 있다.</p>
<pre><code class="language-js">const names = [&#39;Alice&#39;, &#39;Bob&#39;]
console.log(names[2].toUpperCase()) // TypeError: Cannot read property &#39;toUpperCase&#39; of undefined</code></pre>
<h2 id="item2-typescript-settings">Item2. typescript settings</h2>
<p>타입스크립트 설치 후 tsc --init 실행 시, 설정 파일은 자동으로 생성된다. 대부분의 설정들은 어디서 소스파일을 찾을지 어떤 종류의 출력을 생성할지 등을 제어하는 데에 그치지만, 일부 설정은 타입스크립트 언어 자체의 핵심 요소들을 제어하기도 한다.</p>
<h3 id="noimplicitany">noImplicitAny</h3>
<p>noImplicityAny는 변수의 타입 추론이 any로 되는 것을 방지한다. 아래 코드는 해당 설정이 false로 되어있을 때에는 문제가 없다. 그러나 noImplicitAny 설정이 true로 되어있을 때에는 &quot;Parameter &#39;~&#39; implicitly has an &#39;any&#39; type&quot;라는 에러가 발생한다.</p>
<pre><code class="language-ts">function add(a, b) {
  return a + b
}
add(10, null)</code></pre>
<p>타입이 추론된 값이 any가 되었을 때에만 오류이므로 아래처럼 타입을 명시한다면 해당 오류를 해결할 수 있다. 단, any를 매개변수에 사용하게 되면 타입체커는 제 기능을 제대로 할 수 없다. (<del>item6에서 다시 말하겠지만 자동완성이나 변수명 바꾸기 등에서도 제외된다 ㅠㅠ</del>) </p>
<pre><code class="language-ts">function add(a: any, b: any) {
  return a + b
}
function sub(a: number, b: number) {
  return a - b
}</code></pre>
<p>타입스크립트는 타입 정보를 가질 때에 가장 효과적이기 때문에 되도록이면 noImplicityAny를 true로 설정하고 변수에 any가 아닌 타입을 명시하는 것을 습관화해아한다. 해당 설정은 왜 있는지 의문을 가질 수도 있는데, 윗윗 예시에서와 같이 타입 정보가 아예 없으면 js 코드와 동일하다는 것을 깨달을 수 있다. <strong>이미 JS로 작성된 프로젝트에 TS를 마이그레이션할 때를 제외하고는 noImplicitAny false를 사용하지 않도록 하자!</strong></p>
<h3 id="strictnullchecks">strictNullChecks</h3>
<pre><code class="language-ts">const x: number = null
// tsConfig: {&quot;noImplicitAny&quot;:true,&quot;strictNullChecks&quot;:true}일 때에만
//    ~ Type &#39;null&#39; is not assignable to type &#39;number&#39;</code></pre>
<p>strictNullChecks는 null과 undefined를 모든 타입에서 허용할지를 설정한다. 단 해당 설정은 noImplicitAny가 true로 설정되었을 때에만 작동한다.(하긴 아니라면 이곳저곳에서 any로 타입이 추론되어 null 체크가 무의미할 듯 하다) 만약 의도적으로 null이나 undefined를 허용하려면 <code>const x: number | null = null;</code>로서 설정할 수 있다. 해당 변수가 null일 가능성이 있을 때에 속성에 접근하기 위해서는 if로 null이 아님을 확인하거나 <code>!</code>를 통해 null이 아니라고 assert할 수 있다.</p>
<pre><code class="language-ts">const el = document.getElementById(&#39;status&#39;)
el.textContent = &#39;Ready&#39;
// ~~ Object is possibly &#39;null&#39;

if (el) {
  el.textContent = &#39;Ready&#39; // OK, null has been excluded
}
el!.textContent = &#39;Ready&#39; // OK, we&#39;ve asserted that el is non-null</code></pre>
<h2 id="item3-understand-that-code-generation-is-independent-of-types">Item3. Understand That Code Generation Is Independent of Types</h2>
<p>타입스크립트 컴파일러는 크게 두가지 역할을 한다.</p>
<ol>
<li>최신 타입스크립트/자바스크립트를 부라우저에서 동작할 수 있도록 구버전의 자바스크립트로 트랜스파일 (default ES3, target 변경 가능)</li>
<li>코드의 타입오류 체크</li>
</ol>
<p>이 두가지는 서로 독립적이다. 때문에 타입 오류가 있어도 자바스크립트로의 컴파일에는 문제가 없고, 타입 체크에 통과한다 하더라도 자바스크립트 실행 시 런타임 에러가 발생할 수 있다.</p>
<h3 id="타입-오류가-있는-코드도-컴파일-가능">타입 오류가 있는 코드도 컴파일 가능</h3>
<pre><code class="language-js">let x = &#39;hello&#39;
x = 1234</code></pre>
<p>위의 코드에서 x는 &#39;hello&#39;를 기반으로 스트링 타입이라고 추론된다. 타입체커는 &#39;1234&#39; 형식은 &#39;string&#39; 형식에 할당할 수 없다는 타입 에러를 띄우지만, tsc를 통해 컴파일하면 정상적으로 js로 변환된다. <strong>타입스크립트 오류는 문제가 될 만한 부분들을 알려 주지만, 빌드를 멈추지는 않는다.</strong></p>
<p>책의 저자는 웹 애플리케이션을 만들면서 일정 부분에 타입 에러가 발생해도 여전히 타입스크립트는 컴파일된 산출물을 생성하기 때문에, 문제가 된 오류를 수정하지 않아도 애플리케이션의 다른 부분을 테스트할 수 있다는 장점을 언급했다. 이런 타입오류가 발생했을 때에 컴파일을 막기 위해서 noEmitOnError 설정을 변경할 수 있다!</p>
<h3 id="런타임에는-타입-체크가-불가능하다">런타임에는 타입 체크가 불가능하다</h3>
<p><code>&quot;~~&quot;는 형식만 참조하지만, 여기서는 값으로 사용되고 있습니다.&quot;</code>라는 에러를 몇번 접해본 적이 있어, 이마를 탁 치게 되었다. </p>
<pre><code class="language-ts">interface Square {
  width: number
}
interface Rectangle extends Square {
  height: number
}
type Shape = Square | Rectangle

function calculateArea(shape: Shape) {
  if (shape instanceof Rectangle) {
    // ~~~~~~~~~ &#39;Rectangle&#39; only refers to a type,
    //           but is being used as a value here
    return shape.width * shape.height
    //         ~~~~~~ Property &#39;height&#39; does not exist
    //                on type &#39;Shape&#39;
  } else {
    return shape.width * shape.width
  }
}</code></pre>
<p>instanceof 체크는 런타임에 일어나지만, Rectangle과 같은 인터페이스, 타입, 타입 구문은 자바스크립트로 컴파일되는 과정에서 제거된다. 따라서 런타임에서까지 타입 정보를 유지하기 위한 방법이 필요한데, 보통 아래의 세가지 방법을 주로 사용한다.</p>
<ol>
<li>속성 체크
속성 체크는 런타임에 접근 가능한 값에만 관련되지만, 타입체커 또한 shape의 타입을 Rectangle로 보정해주기 때문에 에러가 사라진다고 한다.<pre><code class="language-js">interface Square {
width: number
}
interface Rectangle extends Square {
height: number
}
type Shape = Square | Rectangle
function calculateArea(shape: Shape) {
if (&#39;height&#39; in shape) {
 shape // Type is Rectangle
 return shape.width * shape.height
} else {
 shape // Type is Square
 return shape.width * shape.width
}
}</code></pre>
</li>
<li>태그 속성 사용
1번 방법과 비슷한 방법으로, 타입 정보를 런타임에도 접근 가능한 &#39;태그&#39;로서 명시적으로 저장하는 방법이 있다. 런타임에도 타입 정보를 유지할 수 있기 때문에 자주 사용되는 기법이라고 한다.<pre><code class="language-js">interface Square {
kind: &#39;square&#39;
width: number
}
interface Rectangle {
kind: &#39;rectangle&#39;
height: number
width: number
}
type Shape = Square | Rectangle
</code></pre>
</li>
</ol>
<p>function calculateArea(shape: Shape) {
  if (shape.kind === &#39;rectangle&#39;) {
    shape // Type is Rectangle
    return shape.width * shape.height
  } else {
    shape // Type is Square
    return shape.width * shape.width
  }
}</p>
<pre><code>3. 타입과 값 모두 사용할 수 있는 클래스형 사용하기
타입은 컴파일에 사라지기 때문에 런타임에 접근 가능한 값을 사용하는 방식이다. 아래와 같이 타입을 클래스로 만들면 된다! ```type Shape = Square | Rectangle```에서는 Rectangle이 타입으로 참조되지만, ```shape instanceof Rectangle```에서는 값으로 참조된다. 이 내용은 item8에서 조금 더 자세하게 다룬다.
```js
class Square {
  constructor(public width: number) {}
}
class Rectangle extends Square {
  constructor(public width: number, public height: number) {
    super(width)
  }
}
type Shape = Square | Rectangle

function calculateArea(shape: Shape) {
  if (shape instanceof Rectangle) {
    shape // Type is Rectangle
    return shape.width * shape.height
  } else {
    shape // Type is Square
    return shape.width * shape.width // OK
  }
}</code></pre><h3 id="타입-연산은-런타임에-영향을-주지-않는다">타입 연산은 런타임에 영향을 주지 않는다</h3>
<p>아래 예시의 컴파일 전후 코드를 살펴보면 바로 이해할 수 있다. string 또는 number 타입인 값을 항상 number로 정제하려 할 때, 1번 코드는 타입 체커에서는 number로 가정되고 오류 없이 통과하지만, 이것이 변환된 2번의 자바스크립트 코드에서는 정제 과정이 없다. 실제 동작을 위해서는 3번 코드처럼 자바스크립트 런타임 타입을 체크하고 변환을 수행해야 한다.</p>
<pre><code class="language-ts">// 1. 컴파일 전
function asNumber(val: number | string): number {
  return val as number;
}

// 2. 컴파일 후
function asNumber(val) {
  return val;
}

// 3. TOBE
function asNumber(val: number | string): number {
  return typeof val === &#39;string&#39; ? Number(val) : val;
}</code></pre>
<h3 id="런타임-타입과-선언된-타입은-다를-수-있다">런타임 타입과 선언된 타입은 다를 수 있다</h3>
<p>아래 코드에서 value는 boolean으로 선언되어 default 부분은 실행되지 않아야 한다. 따라서 타입스크립트는 접근할 수 없는 코드라는 에러를 찾아내야 하는데, 실제로는 (strict모드로 설정하더라도) 그렇지 않다. 자바스크립트 코드로 컴파일되면 타입선언값이 지워지고, API의 반환값으로 setLightSwitch 코드를 실행했을 때에 value가 문자열값이 되는 등의 경우가 있을 수 있기 때문이다. </p>
<pre><code class="language-ts">function setLightSwitch(value: boolean) {
  switch (value) {
    case true:
      turnLightOn()
      break
    case false:
      turnLightOff()
      break
    default:
      console.log(`I&#39;m afraid I can&#39;t do that.`)
  }
}</code></pre>
<h3 id="타입스크립트-타입으로는-함수를-오버로드할-수-없다">타입스크립트 타입으로는 함수를 오버로드할 수 없다.</h3>
<p>C++과 같은 언어는 동일한 이름에 매개변수만 다른 여러 버전의 함수를 허용한다. 타입스크립트에서는 타입과 런타임 동작이 무관하기 때문에, 아래와 같은 함수 오버로딩은 불가능하다. (실제 컴파일된 코드를 예상하면 이해가 쉽겠지만, 똑같은 코드가 단순히 두번 반복된 코드일 뿐이다) </p>
<pre><code class="language-ts">function add(a: number, b: number) {
      // ~~~ Duplicate function implementation
  return a + b
}
function add(a: string, b: string) {
      // ~~~ Duplicate function implementation
  return a + b
}</code></pre>
<p>아래와 같이 타입 수준에서만 오버로딩을 지원하고 타입체커를 정상적으로 통과하지만, 해당 부분은 컴파일되면 없어진다는 것을 유념해야 한다. 실제 함수의 구현은 하나뿐이다.</p>
<pre><code class="language-ts">function add(a: number, b: number): number
function add(a: string, b: string): string

// 실제 구현체
function add(a, b) {
  return a + b
}

const three = add(1, 2) // Type is number
const twelve = add(&#39;1&#39;, &#39;2&#39;) // Type is string</code></pre>
<h3 id="타입스크립트-타입은-런타임-성능에-영향을-주지-않는다">타입스크립트 타입은 런타임 성능에 영향을 주지 않는다</h3>
<p>계속 반복해왔듯이, 타입과 타입 연산자는 자바스크립트로 컴파일될 때 모두 제거되어 런타임에는 (당연히 성능을 포함하여) 아무런 영향을 주지 못한다. 타입스크립트 컴파일에서도 성능을 위한 몇가지 커스터마이징을 제공하기도 한다. 빌드도구에서 transplie only 설정을 통해 타입체크를 건너뛰고 컴파일만 실행할 수도 있으며, tsc --target 옵션을 통해 컴파일 타겟 버전을 설정하여 호환성과 성능 정도를 조절할 수 있다.</p>
<h2 id="item4-get-comfortable-with-structural-typing">Item4. Get Comfortable with Structural Typing</h2>
<p><img src="https://images.velog.io/images/dalbodre_ari/post/305c12d5-b1d9-4f3f-a580-2ca91873f9a1/image.png" alt=""></p>
<blockquote>
<p>One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural typing”.</p>
</blockquote>
<p>타입스크립트 공식문서에는 “타입스크립트의 코어 원리 중 하나는 타입 체킹을 형태(Shape)에 중점을 두며, 이것을 &quot;덕 타이핑&quot; 또는 &quot;구조적 서브타이핑&quot; 이라고 부른다” 라고 말하고 있다. JS는 덕 타이핑 기반으로, 함수의 매개변수 값이 제대로 주어진다면 그 값이 어떻게 만들어졌는지/어떤 타입인지 신경쓰지 않는다. TS 또한 이런 기본 코어 동작들을 모델링하여 비슷하게 작동한다. <strong>&quot;객체 적합성&quot;이 객체의 실제 타입이 아니라 특정 메소드와 속성이 존재하는지에 의해 결정된다.</strong> <del>~ 타입이 정해지면 타입의 속성들이 정해지는 것이 아니라, 타입의 속성들이 정해지면 타입이 정해진다고 생각하면 조금 더 이해가 쉬울까? 모 블로그에서는 타입 검사 측면과 다형성 측면의 관점을 분리해서 생각하라고 첨언한다 ~</del></p>
<pre><code class="language-ts">interface Vector2D {
  x: number
  y: number
}
function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y)
}
interface NamedVector {
  name: string
  x: number
  y: number
}
const v: NamedVector = { x: 3, y: 4, name: &#39;Zee&#39; }
calculateLength(v) // OK, result is 5</code></pre>
<p>위의 코드를 보면 바로 이해가 갈 것이다. v는 NamedVector지만 Vector2D가 가지는 number타입의 x와 y속성을 가지고 있기 때문에, calculateLength 함수 호출이 가능하다. 여기서 중요한 점은, Vector2D와 NamedVector 사이의 관계를 따로 선언하거나, 네임드벡터를 위한 별도의 calculateLength 함수를 구현하지 않아도 정상적으로 타입체크가 일어난다는 점이다. 객체 적합성(또는 호환성)을 위해서는 타입 선언에 나열된 속성들을 전부 가지고 있기만 하면 되고, 더 많이 가지고 있어도 상관없다!</p>
<h3 id="구조적-타이핑이-문제를-일으키기도-한다">구조적 타이핑이 문제를 일으키기도 한다</h3>
<p>함수를 작성할 때 호출에 사용하는 매개변수의 속성들이 매개변수의 타입에 선언된 속성만을 가질 것이라고 생각하기 쉬운데, 이로 인해서 오류 및 의도와는 다르게 작동하는 경우가 발생하기도 한다.</p>
<h4 id="함수의-구조적-타이핑">함수의 구조적 타이핑</h4>
<pre><code class="language-ts">interface Vector2D {
  x: number
  y: number
}
interface Vector3D {
  x: number
  y: number
  z: number
}
function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y)
}
function normalize(v: Vector3D) {
  const length = calculateLength(v)
  return {
    x: v.x / length,
    y: v.y / length,
    z: v.z / length,
  }
}
normalize({x:3, y:4, z:5}) // {x:0.6, y:0.8, z:1}, 길이가 1이 넘는다</code></pre>
<p>위와 같이 Vector3D의 크기를 1로 맞추기 위해 normalize라는 함수를 만들었다고 하자.
calculateLength는 x와 y값만 고려하기 때문에 이때 x가 3, y가 4, z가 5인 3D벡터를 정규화하면 1보다 길이가 더 긴 벡터를 아웃풋으로 갖게 된다. 타입스크립트가 구조적 타이핑을 지원하지 않았다면 calculateLength 호출 시 에러를 띄웠겠지만, 구조적 타이핑을 지원하기 때문에 Vector3D가 vector2D와 호환되면서 에러 없이 z가 길이 계산에서 무시된 것이다. 사실 이 부분을 보면서 그냥 단순한 휴먼에러가 아닌가? 싶었지만 타입스크립트 설정 변경을 통해 이러한 문제를 해결할 수 있다고 한다..!!</p>
<p>의도와는 다르게 동작하는 예시가 하나 더 있다. 위의 케이스에서 caculateLength의 문제점을 깨달은 사람이 Vector3D인 v의 속성들을 모두 돌면서 length를 계산하도록 코드를 고쳤다고 하자. 아래 for 루프에서 axis들은 분명 x, y, z 중에 하나이고 coord는 숫자가 되어 오류 없이 코드가 작동해야 하는데, 타입스크립트는 &quot;string은 Vector3D의 인덱스를 사용할 수 없고 coord는 암시적으로 any타입이 된다&quot;는 오류를 낸다. </p>
<pre><code class="language-ts">interface Vector3D {
  x: number
  y: number
  z: number
}
function calculateLengthL1(v: Vector3D) {
  let length = 0
  for (const axis of Object.keys(v)) {
    const coord = v[axis]
    // ~~~~~~~ Element implicitly has an &#39;any&#39; type because ...
    //         &#39;string&#39; can&#39;t be used to index type &#39;Vector3D&#39;
    length += Math.abs(coord)
  }
  return length
}</code></pre>
<p>이는 함수를 작성할 때 구조적 타이핑을 잊고 Vector3D의 속성을 제한해서 생각했기 때문에 나타나는 실수이다. <code>const vec3D = { x: 3, y: 4, z: 1, address: &#39;123 Broadway&#39; }</code>를 예시로 들면 이해가 쉬운데, 이때 <code>calculateLengthL1(vec3D)</code>를 실행하면 NaN을 리턴한다. 구조적 타이핑으로 인해 v는 어떤 속성이든 가질 수 있고, axis는 &quot;x&quot;|&quot;y&quot;|&quot;z&quot; 타입이 아니라 string 타입을, coord는 any타입을 갖게된다. 이런 경우는 루프를 사용하기보다는 앞에서와 같이 속성들을 각각 더하는 구현이 옳다.</p>
<pre><code class="language-ts">function calculateLengthL1(v: Vector3D) {
  return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z)
}</code></pre>
<h4 id="클래스의-구조적-타이핑">클래스의 구조적 타이핑</h4>
<p>구조적 타이핑은 함수 뿐만 아니라 클래스 관련 할당시에도 일어난다.</p>
<pre><code class="language-ts">class C {
  foo: string
  constructor(foo: string) {
    this.foo = foo
  }
}

const c = new C(&#39;instance of C&#39;)
const d: C = { foo: &#39;object literal&#39; } // OK!</code></pre>
<p>위 코드에서 c는 C가 값으로 사용되었고 d에서는 C가 타입으로 사용되었는데, d는 정상적으로 C타입에 할당된다. C가 가지고있는 모든 속성을 포함하고 있기 때문에 구조적 타이핑에 따라 d는 C타입과 호환될 수 있는 것이다.</p>
<p>아래 코드는 인프런의 타입스크립트 스터디에서 해당 내용을 정리한 것이다.</p>
<pre><code class="language-ts">class C {
  foo: string
  constructor(foo: string) {
    this.foo = foo
  }
  method() {}
}
class E {
  method() {}
}
class D extends E {
  foo: string
  constructor(foo: string) {
    super()
    this.foo = foo
  }
}

const c = new C(&#39;instance of C&#39;)
const d: C = { foo: &#39;object literal&#39; } // error. &#39;method&#39; 속성이 &#39;{ foo: string; }&#39; 형식에 없지만 &#39;C&#39; 형식에서 필수입니다.
const e: C = { foo: &#39;&#39;, method() {} } // foo, method 속성이 모두 있으면 okay.

const f: C = new D(&#39;&#39;) // prototype chain 상에 method가 존재하면 okay.
const g = Object.create({ method() {} }, { foo: { value: &#39;&#39; } }) // g: any
const h: C = g // C type 강제(assert)하여 okay.
const i: { foo: string; method: () =&gt; void } = Object.create({ method() {} }, { foo: { value: &#39;&#39; } })
const j: C = i // { foo, method } 타입을 강제하여 okay.</code></pre>
<h4 id="구조적-타이핑은-유닛테스트에도-도움이-된다">구조적 타이핑은 유닛테스트에도 도움이 된다</h4>
<p>테스트를 작성할 때에도 구조적 타이핑을 통해 쉽게 처리할 수 있다. 데이터베이스에 쿼리하고 결과를 처리하는 getAuthors가 다음과 같다고 하자. DB가 어떻게 이루어져있는지 명확하게 선언하지 않고 runQuery 메소드를 정의한것만으로도, DB 데이터의 타입을 선언하고 mocking할 수 있다.</p>
<p>참고로, 모킹이란 데이터베이스 또는 외부 API에 의존하는 코드를 테스트해야 할 때에, 
실제 데이터베이스와 연동하거나 실제 외부 API를 호출하는 대신 
외부에 의존하는 부분을 임의의 가짜로 대체하는 테스팅 기법이다.</p>
<p>타입스크립트는 만약 우리가 명시적으로 인터페이스들을 자세히 서술했다면 DB가 인터페이스를 충족하는지를 확인했을 것이다. 그러나 우리의 테스트코드는 실제 DB에 대한 정보나 해당 라이브러리가 불필요하다. 구조적 타이핑의 추상화를 통해서, 우리는 DB의 구현과 테스트로직을 분리할 수 있다. (물론 라이브러리간의 의존성 또한 분리할 수 있다! item 52를 확인하는 그날까지!)</p>
<pre><code class="language-ts">interface Author {
  first: string
  last: string
}
interface DB {
  runQuery: (sql: string) =&gt; any[]
}
function getAuthors(database: DB): Author[] {
  const authorRows = database.runQuery(`SELECT FIRST, LAST FROM AUTHORS`)
  return authorRows.map(row =&gt; ({ first: row[0], last: row[1] }))
}
test(&#39;getAuthors&#39;, () =&gt; {
  const authors = getAuthors({
    runQuery(sql: string) {
      return [
        [&#39;Toni&#39;, &#39;Morrison&#39;],
        [&#39;Maya&#39;, &#39;Angelou&#39;],
      ]
    },
  })
  expect(authors).toEqual([
    { first: &#39;Toni&#39;, last: &#39;Morrison&#39; },
    { first: &#39;Maya&#39;, last: &#39;Angelou&#39; },
  ])
})</code></pre>
<h2 id="item5-limit-use-of-the-any-type">Item5. Limit Use of the any Type</h2>
<p>타입스크립트의 타입 시스템은 점진적이고 선택적이다. 코드에 타입을 조금씩 추가할 수도 있고, 언제든지 타입 체커를 해제할 수도 있다. 이 기능들은 any 타입을 통해 가능하다. 그러나 any는 타입스크립트의 장점들을 사용하지 못하게 하고, 위험성 또한 가지고 있다. 타입스크립트에 익숙하지 않다면 any 타입이나 as any와 같은 단언문을 사용하고 싶고 남용하게 되겠지만, 이들의 위험성과 불편함을 살펴보도록 하자.</p>
<h3 id="타입-불안정성">타입 불안정성</h3>
<pre><code class="language-ts">let age: number
age = &#39;12&#39;
// ~~~ Type &#39;&quot;12&quot;&#39; is not assignable to type &#39;number&#39;
age = &#39;12&#39; as any // OK</code></pre>
<p>타입 체커는 선언에 따라 age를 number로 판단한다. age를 any로 단언한다면 지금 당장 타입 체커는 통과할 수 있다. 그러나 이후 런타임<code>age += 1</code>를 실행했을 때에 age의 값이 의도와는 다르게 “121”라는 문자열을 갖게 될것이다.</p>
<h3 id="타입-시그니처타입-선언-무시">타입 시그니처(타입 선언) 무시</h3>
<p>함수를 작성할 때는 (noImplicitAny 설정에 따라) function contract-이하 시그니처-를 명시해야 한다. 호출하는 쪽은 약속된 타입의 입력을 제공하고, 함수는 약속된 타입의 출력을 반환한다. 그러나 any 타입을 사용하면 이런 약속을 어길 수 있다.</p>
<pre><code class="language-ts">function calculateAge(birthDate: Date): number {
  return 0
}
let birthDate: any = &#39;1990-01-19&#39;
calculateAge(birthDate) // OK</code></pre>
<p>앞의 예시에서 birthDate 매개변수는 string이 아닌 Date타입이어야 한다고 약속했다. 그러나 any 타입을 사용하면 calculateAge의 시그니처를 무시할 수 있다. 자바스크립트에서는 암시적으로 타입이 변환되기 때문에 이런 경우 특히 문제가 될 수 있다.</p>
<h3 id="언어-서비스-미제공">언어 서비스 미제공</h3>
<p>아래와 같이 자동완성 기능이나 이름 변경 기능 또한 사용할 수 없다!
<img src="https://images.velog.io/images/dalbodre_ari/post/9ae88e6a-053a-4743-aa12-d2e7ab6cabda/image.png" alt=""><img src="https://images.velog.io/images/dalbodre_ari/post/120b3fa7-d4b5-4d9f-bb14-8932dea1484b/image.png" alt=""></p>
<h3 id="코드-리팩토링-시-버그를-유발한다">코드 리팩토링 시, 버그를 유발한다.</h3>
<pre><code class="language-ts">interface ComponentProps {
  onSelectItem: (item: any) =&gt; void
}
function renderSelector(props: ComponentProps) {
  /* ... */
}

let selectedId: number = 0
function handleSelectItem(item: any) {
  selectedId = item.id
}

renderSelector({ onSelectItem: handleSelectItem })</code></pre>
<p>any 타입을 사용하면 버그 발견 또한 어려워진다. 어떤 아이템을 선택할 수 있는 웹 애플리케이션을 만든다고 위의 코드와 같이 가정해보자. 애플리케이션에는 onSelectItem 콜백이 있는 컴포넌트가 있을 것이다.
만약 onSelectItem함수가 item 객체를 모두 받지 않고 필요한 부분만 전달하도록 컴포넌트를 개선한다고 하자. 여기서는 id만 필요하니, 아래와 같이 제일 우선 상단의 ComponentProps을 변경하게 될 것이다. </p>
<pre><code class="language-ts">interface ComponentProps {
  onSelectItem: (id: number) =&gt; void
}
function renderSelector(props: ComponentProps) {
  /* ... */
}

let selectedId: number = 0
function handleSelectItem(item: any) {
  selectedId = item.id
}

renderSelector({ onSelectItem: handleSelectItem })</code></pre>
<p>문제는 여기서 발생한다. ComponentProps만 변경했을 뿐인데 타입 체크를 모두 통과한다. handleSelectItem이 any 매개변수를 받기 때문에, item 매개변수에 item 객체가 아닌 숫자 id를 직접 전달받아도 타입 체크를 통과한다. 물론 편집기에서는 문제가 없다고 나오지만 실제 런타임에서는 item.id 구문을 실행할 때에 number의 id속성을 찾게 되면서 오류가 발생한다.</p>
<p>이 외에도, any 타입은 타입 설계를 불명확하도록 하거나 런타임에 타입 오류를 발견하게 되는 문제를 유발한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CRA path alias 설정]]></title>
            <link>https://velog.io/@dalbodre_ari/CRA-path-alias-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@dalbodre_ari/CRA-path-alias-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Sun, 06 Feb 2022 10:26:12 GMT</pubDate>
            <description><![CDATA[<h2 id="cra-eject-없이-path-alias-설정하기">CRA eject 없이 path alias 설정하기</h2>
<p>간단한 페이지 하나 그냥 쓰자 했다가 &#39;../../../..&quot;를 보는 순간 바로 해결하기로 마음먹었다 ㅋㅋㅋㅋ 다음에 기억 안나면 다시 보기!</p>
<h3 id="craco-w-craco-alias">craco w/ craco-alias</h3>
<ol start="0">
<li><p>패키지 설치</p>
<pre><code>$ yarn add @craco/craco
$ yarn add craco-alias -D</code></pre></li>
<li><p>path 정보 입력</p>
<pre><code>// tsconfig.paths.json
{
 &quot;compilerOptions&quot;: {
     &quot;baseUrl&quot;: &quot;./&quot;,
     &quot;paths&quot;: {
         &quot;@src/*&quot;: [&quot;src/*&quot;],
         &quot;@assets/*&quot;: [&quot;src/assets/*&quot;],
         &quot;@components/*&quot;: [&quot;src/components/*&quot;],
         &quot;@context/*&quot;: [&quot;src/context/*&quot;],
         &quot;@pages/*&quot;: [&quot;src/pages/*&quot;]
     }
 }
}</code></pre></li>
<li><p>craco 설정</p>
<pre><code>// craco.config.js
const CracoAlias = require(&#39;craco-alias&#39;);
</code></pre></li>
</ol>
<p>module.exports = {
    plugins: [
        {
            plugin: CracoAlias,
            options: {
                source: &#39;tsconfig&#39;,
                baseUrl: &#39;./&#39;,
                tsConfigPath: &#39;tsconfig.paths.json&#39;,
            },
        },
    ],
};</p>
<pre><code>3. tsconfig 설정</code></pre><p>// tsconfig.json
{
  &quot;compilerOptions&quot; : { ... },
  &quot;include&quot;: [
    &quot;src&quot;,
    &quot;craco.config.js&quot;
  ],
  &quot;extends&quot;: &quot;./tsconfig.paths.json&quot;
}</p>
<pre><code>
4. eslint 설정</code></pre><p>// .eslintrc.json
{
    &quot;rules&quot; : { ... },
    &quot;settings&quot;: {
        &quot;typescript&quot;: {},
        &quot;import/resolver&quot;: {
            &quot;node&quot;: {
                &quot;extensions&quot;: [&quot;.js&quot;, &quot;.jsx&quot;, &quot;.ts&quot;, &quot;.tsx&quot;]
            }
        }
    }
}</p>
<pre><code>

### craco w/o craco-alias
craco-alias 없이도 alias 설정이 가능하다. 1, 3, 4번 단계는 위와 동일하고 craco 설정만 추가한다.

0. 패키지 설치</code></pre><p>$ yarn add @craco/craco</p>
<pre><code>2. craco 설정</code></pre><p>// craco.config.json
const path = require(&#39;path&#39;);</p>
<p>module.exports = {
    webpack: {
        alias: {
            &#39;@&#39;: path.resolve(<strong>dirname, &#39;src/&#39;),
            &#39;@assets&#39;: path.resolve(</strong>dirname, &#39;src/assets&#39;),
            &#39;@components&#39;: path.resolve(<strong>dirname, &#39;src/components&#39;),
            &#39;@pages&#39;: path.resolve(</strong>dirname, &#39;src/pages&#39;),
            &#39;@context&#39;: path.resolve(__dirname, &#39;src/context&#39;),
        },
    },
};</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[마이크로 프론트엔드와 모노레포 & 제로빌드]]></title>
            <link>https://velog.io/@dalbodre_ari/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%99%80-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-%EC%A0%9C%EB%A1%9C%EB%B9%8C%EB%93%9C</link>
            <guid>https://velog.io/@dalbodre_ari/%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%99%80-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-%EC%A0%9C%EB%A1%9C%EB%B9%8C%EB%93%9C</guid>
            <pubDate>Wed, 02 Feb 2022 07:48:13 GMT</pubDate>
            <description><![CDATA[<p>이것은 마치 까먹지 않기 위해 정리하는 메모장이랄까.. toss slash21 보고나서 언젠가 정리해야지 미루고 미뤘는데,, 곳곳의 세미나에서 마이크로 프론트엔드 도입기를 자랑하기 시작했다,,,,,,</p>
<h3 id="모노리스-아키텍쳐에서부터-마이크로-프론트엔드까지">모노리스 아키텍쳐에서부터 마이크로 프론트엔드까지</h3>
<p><img src="https://images.velog.io/images/dalbodre_ari/post/845bf7e8-5ec3-4443-baf2-48d8deb21682/image.png" alt=""></p>
<h4 id="모노리스-아키텍쳐">모노리스 아키텍쳐</h4>
<p>기존의 개발 방식으로, 각각의 기능들을 개발한 후 하나의 앱으로 패키징하여 배포하는 것을 말한다. 일반적으로 하나의 레포지토리 내에 하나의 큰 앱만 존재하여, 코드 공유가 쉽고 형식 통일과 배포 관리에 용이하다는 장점이 있다.</p>
<p>여러 팀으로 구성되는 대규모 프로젝트에서 문제가 될 수 있다. 예를 들어 계정/상품 확인/장바고니/배송/결제 기능이 포함된 인터넷 쇼핑몰에서, 결제 기능을 개발하기 위해 로그인 개발이 먼저 완료되어야 하는 등 개발 병목현상이 발생한다. 결제 시스템에서 문제가 생기더라도 상품 확인 및 장바구니를 포함한 모든 기능을 사용하지 못하게 되는 에러 확산이 발생하기도 한다.</p>
<p>전체 서비스가 하나의 프레임워크와 언어에 제한되는 경우도 생긴다. (사실 이 예시는 어떻게든 연결하는 레이어를 담으면 되는게 아닌가 싶기는 하지만.. <a href="https://wooaoe.tistory.com/57">https://wooaoe.tistory.com/57</a> 요기 블로그 왈) 블록체인 모듈은 nodeJS를 일반적으로 사용하는데, 서버를 스프링을 통해 서비스를 시작하는 경우 이를 통해 연동해야 한다. 또한 수정하지 않은 다른 기능들을 포함한 전체 서비스 빌드가 필요하고, 작은 변경에도 높은 테스트 비용이 발생한다.</p>
<h4 id="soa--service-oriented-architecture-msa--micro-service-architecture">SOA : service oriented architecture, MSA : micro service architecture</h4>
<p>이런 문제를 해결하기 위해서 프론트와 백단이 나뉘기도 하고, 공통모듈을 기반으로 개별 모듈들의 의존성을 줄인 SOA와 아예 모듈들을 분리하여 API로 통신하는 마이크로 서비스 아키텍쳐가 제안되기도 했다. (참고 : <a href="https://www.redhat.com/ko/topics/cloud-native-apps/what-is-service-oriented-architecture">https://www.redhat.com/ko/topics/cloud-native-apps/what-is-service-oriented-architecture</a>)</p>
<p>마이크로 서비스에서, 앞서 말한 인터넷 쇼핑몰의 경우 로그인/결제/리뷰/장바구니 등등의 각각의 서비스는 개별적으로 모듈화되어 독립적으로 테스트 및 배포가 이루어진다. 서비스 구현 기술과는 상관없이 API를 사용하여 통신하기 때문에 서비스별로 기술스택이 달라도 상관이 없다. 특히 대규모이면서 레거시 코드가 대부분인 프로젝트에서, 이와 구분하여 새로운 기술스택을 사용한 기능 개발에 제한이 없다는 장점이 있다.</p>
<p>백엔드에서 MSA가 마구마구 사용되던 것에 반해, 프론트엔드는 모노리스 상태를 최근까지 유지해오던 경향이 있었다. 최근(<del>ㅇ..ㅓ..최근의 약간 이전..?</del>) 프론트 엔드 추세는 사용자 인터랙션이 일어나면 최소한의 리렌더링을 통해 기능하는 Single Page Application를 구성하는 것이었기 때문이다. 백엔드에서 겪었던 문제와 비슷하게, 시간이 지남에 따라 규모가 커지고 여러 팀이 함께 하나의 서비스를 개발하게 되면서 점차 유지관리가 어려워짐에 따라 대규모프로젝트에서 마이크로 프론트엔드가 도입되기 시작했다. (물론 내가 참여하는 대부분의 프로젝트는 대규모가 아니다 ㅎㅁㅎ,,)</p>
<h4 id="마이크로-프론트엔드">마이크로 프론트엔드</h4>
<p>마이크로 프론트엔드란 결국 프론트엔드에서 개별 팀이 담당하는 비즈니스 영역을 모듈화하여 완전히 구분하는 것을 말한다. 한 앱의 각각 일부 앱만 담당하는 것이 아니라, 각 팀 내에서 데이터베이스에서부터 사용자 인터페이스에 이르기까지 end-to-end를 완료하는 것이다.</p>
<p>마이크로 프론트엔드를 구성하는 방법에는 서버 템플릿 통합, 빌드타임 통합, iframe를 통한 런타임 통합, JS를 통한 런타임 통합, Web Components를 통한 런타임 통합 방식이 있다. 이 중에서 토스에서도 사용하고 있고, lerna를 통해 쉽게 사용할 수 있어 보이는(?) 빌드타임 통합 방식에 대해 조금 더 이야기해보려 한다.</p>
<h3 id="토스의-마이크로-프론트엔드-도입기--의존성-지옥과-긴-빌드타임">토스의 마이크로 프론트엔드 도입기 : 의존성 지옥과 긴 빌드타임</h3>
<p>토스는 8~9명으로 구성된 사일로가 하나의 기능 개발을 담당한다고 한다. 각 사일로마다 개별적인 서비스를 구현하기 때문에 21년 세미나 당시 25개 이상의 리액트 서비스가 존재했다. 새로운 프로젝트는 자연스럽게 기존에 존재하던 프로젝트에 추가되는 형태로, 각 서비스는 웹팩의 엔트리포인트로 구분되기는 하지만, 하나의 패키지에서 하나의 웹팩설정으로 한번에 빌드되는 구조였다.</p>
<p>이로 인해 의존성 지옥과 너무 긴 빌드타임이라는 문제점이 발생했다. 
서로 코드를 공유하지 않는 A와 B 서비스, 그리고 두 서비스가 공유하는 X라는 의존성 패키지가 존재한다고 하자. A는 이미 개발이 완료되어 정상적으로 서비스되던 중 B를 개발하다 보니 (이미 A에서 사용하던) X 패키지에서 버그 발견하게 되었다. 이때 B의 개발을 위해 X의 버전을 올리면 A에서 에러가 발생하거나 작동이 달라지는 경우가 종종 발생했다. </p>
<img src="https://images.velog.io/images/dalbodre_ari/post/7d5adfa6-b31b-43f8-871a-0ddde5d7e420/image.png" width="65%" style="display:flex;">
또한 (앞에서 말했던 모노리스의 문제점..) A 서비스에서만 변경이 있다고 하더라도 변경이 없는 B부터 Z 서비스를 모두 새로 빌드해야 했으며, 사진에서와 같이 개발 도중에도 20분에서 40분간 빌드를 돌리고 멍하니 기다려야 하는 병목현상이 두드러지기 시작했다.

<p>토스는 이를 해결하기 위해 기존의 거대한 소스코드를 독립적인 패키지로 분리하고 각각을 빌드하기로 결정하였는데, 레포지토리를 구분해서 사용하자니,,, 공통 코드의 공유 어려움, 사용 라이브러리의 파편화, 복잡한 서비스 관리 등의 문제가 발생하여 결국 모노레포를 도입하게 되었다. (<del>아래 그림ㅋㅋㅋㅋㅋ 미쳤나봨ㅋㅋㅋㅋ 너무 웃곀ㅋㅋㅋ</del>)
<img src="https://images.velog.io/images/dalbodre_ari/post/ee03b9f5-5312-4dbc-b487-ae2bd7e23a2c/image.png" width="65%"></p>
<h3 id="모노레포--의존성-지옥의-구원자">모노레포 : 의존성 지옥의 구원자</h3>
<p>모노레포는 무엇이고, 어떤 좋은 점을 갖고 있길래 토스 뿐만 아니라 Google, Facebook 및 Twitter와 같은 대기업이 사용할까? </p>
<p><img src="https://images.velog.io/images/dalbodre_ari/post/a6c71b6e-8293-4bf8-a6be-065556b34d8e/image.png" alt=""></p>
<p>일반적으로 자주 사용했던 멀티레포 방식은 여러 레포지토리에 패키지들을 분산시켜서 사용한다. 
장점 : </p>
<ul>
<li>Repository 별 Owner를 지정 : 수월한 패키지 관리</li>
<li>각 레포지토리별 빠른 CI Build : 하나의 Repository는 하나의 Continuous Integration 구성</li>
<li>패키지의 명확한 분리로 인한 유연성 향상 : Repository 상 서로 연계 관계가 없기 때문에 추가, 수정, 유지 관리 편리</li>
</ul>
<p>단점 : </p>
<ul>
<li>중복된 설정 및 반복된 설치 : 모든 공통된 설정과 모듈들을 반복적으로 설정/설치해야 함</li>
<li>이슈의 분산 : 각 다른 레포지토리가 연관되어있는 이슈 트래킹 관리 어려움</li>
<li>Dependency Hell : 여러 패키지들이 사용하는 같은 모듈에서 (의도됐든 의도되지 않았든) 버전 차이 발생 및 충돌 발생 가능</li>
<li>중복 코드의 가능성 : 중복된 설정 및 반복된 설치와 비슷하게 레포지토리가 분리되어 공통된 코드가 중복될 가능성이 커짐</li>
</ul>
<p>반면, 모노레포란 하나의 저장소에서 여러 프로젝트를 관리한다. (기존의 멀티레포의 단점을 장점으로, 장점을 단점으로 가진다)
장점 : </p>
<ul>
<li>공통 항목 단일화 : eslint, Build, Unit Test 등 공통된 설정 및 필요한 node module을 한 번의 설치와 한 번의 설정으로 모든 패키지가 사용할 수 있다. </li>
<li>쉬운 Package 공유</li>
<li>단일 이슈 트래킹 : 모노레포 내 연관된 패키지들에 관한 (분산될 필요 없는) 이슈 트래킹</li>
<li>효율적인 의존성 관리</li>
</ul>
<p>단점 : </p>
<ul>
<li>Repository의 거대화 : 분산되어 있던 모든 리소스를 하나의 레포로 합치면서,,</li>
<li>느린 CI Build : CI가 하나로 구성된다는 장점 == 규모가 커짐에 따라 분산된 CI 빌드보다 속도가 느릴 수밖에 없음</li>
<li>무분별한 의존성 : Package 간 의존성 관리가 쉽지만, 오히려 과도한 의존성 관계 발생 가능</li>
</ul>
<h3 id="제로빌드--긴-빌드시간을-줄이자">제로빌드 : 긴 빌드시간을 줄이자!</h3>
<p>토스에서는 빌드시간을 줄이기 위해서 yarn berry의 zero-install이 작동하는 방식을 차용했다. </p>
<p>(yarn berry와 node modules도 한번 정리해야 하긴 하는데,, 간략하게 정리하면 yarn berry는 기존의 yarn과는 다르게 의존성을 압축 파일로 관리하고 숫자가 적어 Git으로도 의존성을 충분히 관리할 수 있다. 이렇게 의존성을 아예 버전 관리에 포함하는 것을 Zero-Install이라고 한다. 새로 저장소를 복제하거나 브랜치를 바꾸었다고 해서 yarn install을 실행하지 않아도 되고, 이로 인해 CI/CD도 쉬워진다는 이점이 있다. 참고 : <a href="https://toss.tech/article/node-modules-and-yarn-berry">https://toss.tech/article/node-modules-and-yarn-berry</a>)</p>
<p>빌드 시간을 줄이기위해서 변경사항이 없는 패키지는 빌드를 하지 않도록 변경했다! 모노리스 방식에서는 이런 방식을 사용하기 어려웠지만, 마이크로 프론트엔드 아키텍쳐에서는 소스코드 변경이 있는 패키지만 새로 빌드하면 됐기 때문에 쉽게 도입할 수 있었다. 기존 빌드의 결과물을 아예 git에 저장하여 언제든 pull만 받으면 최신 빌드 결과를 사용할 수 있도록 하였다.</p>
<h3 id="무조건-모노레포를-사용해야-할까">무조건 모노레포를 사용해야 할까?</h3>
<p>모노레포가 좋다는건 알겠는데,, 그럼 무조건 마이크로서비스/모노레포를 선택해야 할까?</p>
<p>정답은 너무 당연하게, NO! 
이들은 어느 정도 복잡성을 가진 (각 서비스/패키지로 구별되는) 시스템에서 유용하다. 실제로는 여러 개의 서비스를 나누고 관리하는 것 또한 비용이기 때문에 오히려 생산성에 방해가 될 수도 있다. 특히 프로젝트 초반에는 실제로 사용자들에게 유용한 기능을 빠르고 정확하게 만드는 것이 중요할 때가 많고, 비교적 규모가 적은 프로젝트에서는 서비스 간에 경계를 명확하게 나누는 일이 어렵기도 하다. </p>
<p>결론 : 소규모 프로젝트 내 빠른 개발을 위해서는 모노리스/멀티레포 구성이 더 수월한 경우가 있음!</p>
<h3 id="lerna--모노레포를-쉽게-만들어보자">Lerna : 모노레포를 쉽게 만들어보자!</h3>
<p>lerna는 다중의 패키지가 있는 모노레포 자바스크립트 프로젝트의 test, build, release 같은 작업을 최적화 시켜주는 툴이다. 여러 패키지의 의존성을 묶어주는 역할과 각 패키지별 버전 업데이트를 도와준다.</p>
<p><img src="https://images.velog.io/images/dalbodre_ari/post/38ea488b-d51a-4a86-8351-adc3e612d06e/image.png" alt=""></p>
<p>lerna로 init한 프로젝트의 최상위에는 packages 폴더와 package.json, lerna.json이 존재한다. packages에는 각각의 서브 패키지들이 들어가며, package.json는 기존과 같이 script를 통해 (개별적으로 혹은 동시에) 패키지의 스크립트를 실행할 수 있도록 한다. lerna.json에서는 lerna관련  옵션값들이 저징되어 있는데, package의 폴더 지정이나 npm/yarn 지정 및 bootstrap 등을 지정할 수 있다.</p>
<p>Lerna의 여러 커맨드</p>
<ul>
<li>lerna init : 새로운 lerna 저장소 생성 </li>
<li>lerna bootstrap : 각 패키지별로 겹치는 dependency를 연결하면서 모든 패키지들의 의존성 설치
ex) @foo/server와 @foo/client에서 같은 lodash 라이브러리를 사용한다면, 
겹치는 의존성은 연결되어서 루트 디렉토리의 node_modules에 설치되고, 
겹치지 않는 의존성은 각각의 패키지 프로젝트의 node_modules에 설치</li>
<li>lerna import <pathToRepo> : <pathToRepo>에 해당하는 패키지를 커밋 히스토리와 함께 packages/<directory-name>으로 import해준다. 손쉽게 모노레포 완성!</li>
<li>lerna publish : 업데이트된 패키지의 새로운 배포 생성 (버전 업데이트, git 및 npm에서 모든 패키지 업데이트)</li>
<li>lerna chanced : 지난 배포 이후 어떤 패키지에 변화가 있었는지 확인</li>
<li>lerna run : 각 패키지들의 package.json에 정의된 script들을 실행</li>
</ul>
<p>현재 토이프로젝트로 만들고 있는 보드게임에서는 react native와 react native web을 통해 앱과 웹을 한번에 만들고 있는데, 모노레포 내에서 여러 의존성 충돌이 발생하여 해결하는데 애를 먹었었다. 어떤 것들이 문제였는지, 어떻게 해결했는지 미래의 내가 다시 기록해주길 바라면서,, (똑같은 문제 다시 보면 적어놔야지,,, mmazzarolo님 사랑합니다 <a href="https://github.com/mmazzarolo/react-native-universal-monorepo">https://github.com/mmazzarolo/react-native-universal-monorepo</a>) </p>
<p>오늘은 여기까지!</p>
<p>참고)
<a href="https://www.youtube.com/watch?v=FQoNY2W0s4E">https://www.youtube.com/watch?v=FQoNY2W0s4E</a>
<a href="https://toss.im/slash-21/sessions/2-5">https://toss.im/slash-21/sessions/2-5</a>
<a href="https://github.com/Pyrolistical/microapps.git">https://github.com/Pyrolistical/microapps.git</a> 
<a href="https://soobing.github.io/micro-frontends/">https://soobing.github.io/micro-frontends/</a>
<a href="https://martinfowler.com/articles/micro-frontends.html#TheExampleInDetail">https://martinfowler.com/articles/micro-frontends.html#TheExampleInDetail</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[V8 엔진의 과거, 현재 구조]]></title>
            <link>https://velog.io/@dalbodre_ari/V8-%EC%97%94%EC%A7%84%EC%9D%98-%EA%B3%BC%EA%B1%B0-%ED%98%84%EC%9E%AC-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@dalbodre_ari/V8-%EC%97%94%EC%A7%84%EC%9D%98-%EA%B3%BC%EA%B1%B0-%ED%98%84%EC%9E%AC-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Wed, 02 Feb 2022 05:47:36 GMT</pubDate>
            <description><![CDATA[<p><a href="https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e?gi=e0eeb78a5d24">https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e?gi=e0eeb78a5d24</a> 내용을 기반으로 정리한 내용입니다. 스터디 내용 정리를 더 이상 미뤄놓으면 안될 것 같아 사용했던 ppt를 기반으로 간단하게나마 정리해보려고 합니다.</p>
<hr>
<h3 id="과거-인터프리터-언어의-컴파일">과거)) 인터프리터 언어의 컴파일?</h3>
<p>시작하기 전에 간단하게 짚고 넘어가자면, 컴파일 언어는 실행 전 별도로 소스코드를 컴파일하여 기계어로 변환하기 때문에 실행시간이 비교적 빠르고 인터프리터 언어는 코드를 실행할 때에 인터프리터가 기계어로 번역하고 실행기 때문에 비교적 느립니다. </p>
<p>javascript는 인터프리터 언어이지만 코드를 해석하고 실행하는 엔진에서 각자 인터프리터 방식으로 구현될 수도 있고, 컴파일러 방식으로 구현될 수도 있습니다. 특히 과거 V8 엔진의 경우 자바스크립트 수행 속도를 개선하기 위해 자바스크립트 코드를 <del>(바이트 코드를 거치지 않고)</del> 머신코드로 번역하여 사용하는 컴파일러 형식을 사용했습니다. </p>
<ul>
<li>full-codegen : a simple and very fast compiler that produced simple and relatively slow machine code.</li>
<li>Crankshaft : a more complex (Just-In-Time) optimizing compiler that produced highly-optimized code.</li>
</ul>
<p>풀코드젠은 자바스크립트를 머신코드로 변환하여 빠르게 실행할 수 있도록 해주는 일종의 컴파일러이고 크랭크샤프트는 풀코드젠으로 코드가 실행된 뒤, 자바스크립트의 추상구문트리를 최적화하여 static single-assignment, SSA form으로 변환하는 옵티마이저입니다. </p>
<p>V8 엔진은 내부적으로 여러 쓰레드를 사용하는데, 메인 쓰레드에서는 코드를 컴파일하고 실행하고, 프로파일러 쓰레드에서는 어떤 메서드에서 시간이 오래 걸리는지 알려주며, 그 외에도 가비지 컬렉터 등을 위한 여러 쓰레드가 있습니다. 크랭크샤프트는 풀코드젠으로 코드가 실행된 이후 프로파일러 쓰레드가 충분한 데이터를 얻게된 이후, 그 데이터들을 바탕으로 최적화를 실행합니다.</p>
<hr>
<h3 id="크랭크-샤프트의-최적화-기법">크랭크 샤프트의 최적화 기법</h3>
<p> <img src="https://images.velog.io/images/dalbodre/post/7460bc53-039b-45a4-be49-68597178e419/image.png" alt=""></p>
<h4 id="1-인라이닝">1. 인라이닝</h4>
<p>크랭크샤프트의 최적화는 미리 가능한 많은 코드를 인라이닝(inlining)하는 것에서부터 시작합니다. 인라이닝이란 호출 지점(함수가 호출된 곳의 코드 위치)을 호출된 함수의 내용으로 바꾸는 단순한 과정으로, 이후의 최적화 과정에서 사용됩니다.</p>
<h4 id="2-히든-클래스">2. 히든 클래스</h4>
<p>이후 크랭크 샤프트는 히든클래스를 생성합니다. 자바스크립트는 프로토타입 기반의 언어이기 때문에 실질적인 클래스라는 것은 없으며 각 객체는 복제 과정을 통해 생성되는데, 객체 속성 값의 위치를 메모리에 저장하기 때문에 속성값을 읽어오는 것이 자바나 C#같은 언어에서보다 코스트가 큽니다. (자바에서는 모든 객체 속성이 컴파일 전에 고정된 객체 레이아웃에 의해 설정되고 런타임에서는 동적으로 추가되거나 제거될 수 없기도 하고 자바스크립트에서 해시함수를 이용해서 메모리 상에서 객체 속성의 위치를 매번 찾아내는 것이 코스트가 크기도 함) 따라서 크랭크샤프트는 자바와 비슷하게 런타임에 객체에 포함되는 속성들을 미리 결정하는 방식을 사용합니다.</p>
<p><img src="https://images.velog.io/images/dalbodre/post/0fdc6c9a-82c0-43de-b071-a9f5d9991ac4/image.png" alt="">
Point라는 예시를 통해 히든클래스가 어떻게 만들어지는지 살펴보겠습니다. 일단 new Point(1, 2)가 실행되면 V8은 이라는 C0 히든클래스를 생성합니다. C0은 첫번째 클래스 단계를 표현하기 위한 임의의 이름이며, Point에 아직 아무 속성도 정의되지 않았으므로 C0은 비어있습니다.</p>
<p><img src="https://images.velog.io/images/dalbodre/post/aa36fd55-1c5d-458d-bfbf-2b9ca1a95e56/image.png" alt="">
첫 번째 구문인 this.x = x가 수행되면 V8은 C0을 기반으로 C1이라는 두 번째 히든 클래스를 생성합니다. C1은 x속성을 찾을 수 있는 메모리상의 위치에 대한 설명을 포함하고 있으며, 생성되었던 C0 클래스에도 만약 x속성이 추가되면 객체가 가리키는 히든 클래스가 C0에서 C1으로 전환되어야 한다는 내용이 포함됩니다. </p>
<p><img src="https://images.velog.io/images/dalbodre/post/b73bf7fe-3700-4f8f-a5de-cea937381341/image.png" alt="">
this.y = y가 수행될 때도 동일한 동작이 반복됩니다. C2라는 새로운 히든클래스가 생성되고, C1에 y속성이 포인트 객체에 추가되면 C2로 변경되어야 한다는 클래스전환이 추가되며, 포인트 객체의 히든 클래스는 C2로 업데이트됩니다.</p>
<p><img src="https://images.velog.io/images/dalbodre/post/46c696e9-1e59-4399-983b-31659271d137/image.png" alt="">
여기서 중요한 점은 히든클래스 전환은 객체에 속성이 추가되는 순서에 의존적이라는 것입니다. 
예시를 보면 p1과 p2는 모두 객체에 a와 b속성을 추가로 가지지만 서로 다른 순서로 선언되었습니다. 이들은 결과적으로 같은 속성들을 가지게 되지만, 각 개체는 서로 다른 히든클래스를 가리키게 됩니다. 오른쪽 파란 그림으로 확인할 수 있듯이 p1에서는 속성 a가 먼저 추가되고 p2의 경우 b가 먼저 할당되면서 전환 경로도 달라지고, 서로 다른 히든클래스를 사용하게 됩니다. 
이와 같은 경우에 같은 히든클래스를 재사용할 수 있도록 속성을 같은 순서로 초기화하는 것이 성능상으로 훨씬 좋습니다.</p>
<h4 id="3-인라인-캐싱">3. 인라인 캐싱</h4>
<p>마지막 최적화 방법 중 하나인 인라인 캐싱은 앞서 말한 히든클래스와도 관련이 있습니다. 특정 객체에 메소드가 호출될 때마다 V8엔진은 특정 속성에 접근하기 위한 오프셋을 계산하기 위해 해당 객체의 히든클래스를 뒤져봐야 합니다. 동일한 히든 클래스의 동일한 메소드의 호출을 두 번 성공하고나면 V8은 히든클래스를 찾는 것을 생략하고 단순하게 스스로 해당 객체 포인터에 속성 오프셋을 더해 놓습니다. 이후 해당하는 메소드에 대한 모든 호출이 있을 때마다 V8엔진은 히든클래스가 변하지 않았다고 가정하고 이전에 캐싱해두었던 오프셋을 이용해 직접 메모리 주소로 이동하고, 이를 통해 실행 속도는 크게 증가합니다.
<img src="https://images.velog.io/images/dalbodre/post/f178c52c-ba68-4902-b391-478098a8e362/image.png" alt="">
앞의 예제처럼 타입은 같지만 런타임에서 만들어지는 히든클래스가 다른 객체의 경우(C4와 C6) 인라인캐싱 조건인 “동일한 히든 클래스&quot;를 만족하지 않기 때문에, 최대한 히든클래스를 공유할 수 있도록 하는 것이 성능에 유리합니다</p>
<hr>
<h3 id="현재-이그니션과-터보팬">현재)) 이그니션과 터보팬</h3>
<p>V8엔진은 5.9버전에서 크게 바뀌었는데, 파이프라인의 변화를 통해 성능 향상과 더불어 자바스크립트 응용프로그램들에서 메모리를 현저하게 절약할 수 있게 되었다고 합니다. 새로운 파이프라인은 인터프리터인 이그니션과 최적화 컴파일러인 터보팬으로 구성되어 있습니다. </p>
<p><del>저는 몰랐지만 타 블로그에서</del> V8은 8기통 엔진을, Ignition은 엔진에 시동걸 때 사용하는 점화기, TurboFan 또한 (실행 중 코드가? 너무 뜨거워졌을 때 최적화를 통해) 과열된 상태를 식혀준다는 의미가 있다고 합니다.
<img src="https://images.velog.io/images/dalbodre/post/cf9a3823-5cdf-4f14-a894-ac0ed6b0fadf/image.png" alt=""></p>
<p>Ignition은 기존의 Full-codegen을 대체하는 인터프리터입니다. 기존의 Full-codegen은 전체 소스 코드를 한번에 머신코드로 컴파일하면서 메모리 사용량이 컸는데, 모바일에서 특히 문제가 도드라졌다고 합니다. 이를 해결하기 위해 인터프리터 방식으로 돌아와 자바스크립트코드를 한줄한줄 실행할 때마다 머신코드 이전 단계인 바이트코드 상태로 컴파일하는 이그니션을 사용하게 되었습니다. 이를 통해 메모리 사용량 감소 뿐만 아니라, 파싱하기에도 편해졌으며, 터보팬에서 최적화를 할 때에 바이트코드만 고려하면 됐기 때문에 터보팬에서의 최적화 또한 훨씬 편리해졌다고 크롬 모바일팀에서 발표했습니다. (실제로 7개의 컴퓨터 아키텍쳐를 지원하기 위해서 사용되던 코드가 만육천 줄에서 3천줄로 감소했다고 함. 초기 실행에는 시간이 약간 걸리지만 바이트코드로라도 변환한 이후부터는 c++에 근사하는 성능을 낼 수도 있다고 함. 단, 잘짜진 코드만 ㅎㅎ;;)</p>
<p>터보팬의 경우 이그니션에서 만들어진 바이트 코드를 기반으로, 크랭크샤프트에서 최적화 단계에서 사용하던 히든클래스와 인라인 캐싱을 통해 최적화를 진행합니다. (최적화의 시작은 프로파일러 쓰레드가 메인 쓰레드를 감시하다가 일정 기준 이상 동일한 함수가 호출되면 시작됩니다.)</p>
<p>기본적인 동작 방식은 여기까지입니다! GC나 React Native에서 사용하는 헤르메스 엔진 등등 스터디에서 다뤘던 내용은 한참 남아있는데, 모두 다 정리할 생각을 하니 막막하네요.. 천천히 한걸음부터 해보겠습니다!! 😁😁</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[헷갈리지 말자 파이썬 코딩테스트]]></title>
            <link>https://velog.io/@dalbodre_ari/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B8%B0%EB%B3%B8%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@dalbodre_ari/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B8%B0%EB%B3%B8%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 05 Dec 2021 14:49:19 GMT</pubDate>
            <description><![CDATA[<h3 id="파이썬-기본-문법">파이썬 기본 문법</h3>
<ol>
<li><a href="#1-%ED%83%80%EC%9E%85-%ED%9E%8C%ED%8A%B8">타입 힌트</a></li>
<li><a href="#2-%EC%BB%B4%ED%94%84%EB%A6%AC%ED%97%A8%EC%85%98-list-comprehension-dict-comp-set-comp-generator-expression">컴프리헨션 (List Comprehension, Dict Comp., Set Comp., Generator Expression)</a></li>
<li><a href="#3-%EC%A0%9C%EB%84%88%EB%A0%88%EC%9D%B4%ED%84%B0-range">제너레이터, range</a></li>
<li><a href="#4-%EB%8F%84%EC%9B%80%EC%9D%B4-%EB%90%98%EB%8A%94-%EC%97%AC%EB%9F%AC-%ED%95%A8%EC%88%98%EB%93%A4-enumerate-%EB%82%98%EB%88%97%EC%85%88-%EC%97%B0%EC%82%B0%EC%9E%90-print%EC%99%80-string-formating-pass-locals-%EC%8A%A4%ED%83%80%EC%9D%BC-%EA%B0%80%EC%9D%B4%EB%93%9C">여러 함수들 (enumerate, 나눗셈 연산자, print와 string formating, pass, locals, 스타일 가이드)</a></li>
<li><a href="#5-%EC%B0%B8%EC%A1%B0-%EC%8B%9C-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90">참조 시 주의할 점</a></li>
<li><a href="#6-%EB%A6%AC%EC%8A%A4%ED%8A%B8-set-dict-%EA%B8%B0%EB%B3%B8-%EC%97%B0%EC%82%B0%EC%9E%90">리스트, set, dict 기본 연산자</a></li>
</ol>
<h3 id="1-타입-힌트">1. 타입 힌트</h3>
<p>파이썬의 경우 동적 타이핑 언어(인터프리터 언어)임에도 불구하고, 대규모 프로젝트에서도 가독성을 높이기 위한 타입힌트 기능이 있다. (<del>그러나 강제 규약이 아니어서 사용자가 타입 힌트를 보고도 잘못 할당하는 경우가 있을 수 있음. mypy을 설치하여 실행 이전에 확인하는 기능을 사용할 수 있다.</del>)</p>
<h3 id="2-컴프리헨션-list-comprehension-dict-comp-set-comp-generator-expression">2. 컴프리헨션 (List Comprehension, Dict Comp., Set Comp., Generator Expression)</h3>
<p>리스트, 집합, 딕셔너리 등 선언 시, 반복문과 조건문을 생성자 내부에서 사용하여 쉽게 정의하는 것</p>
<pre><code class="language-python">
# 반복 및 조건문
[n*2 for n in range(1, 10+1) if n % 2 == 1] # [2,6,10,14,18]

# if문들은 and로 묶여 모두 만족하는 경우에만 실행
[i for i in range(5) if i%2==0 if i%4==0] # [0,2,4]

# if, else를 함께 쓰는 경우에는 반복문 왼쪽에
[i if i%2==0 else &#39;odd&#39; for i in range(5)] # [0,&#39;odd&#39;,2,&#39;odd&#39;,4]

# 다중 조건문 실행결과
[(i,j) for i in range(2) for j in range(3)] # [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]

# 두가지 모두 동일한 실행 결과를 리턴
[(i,j) for i in range(3) if i%2 == 0 for j in range(3)]
[(i,j) for i in range(3) for j in range(3) if i%2 == 0]
# [(0, 0), (0, 1), (0, 2), (2, 0), (2, 1), (2, 2)]

# Set, Dict에서도 동일하게 작동한다.
[j for i in range(3) for j in range(3)] # [0, 1, 2, 0, 1, 2, 0, 1, 2]
{j for i in range(3) for j in range(3)} # {0, 1, 2}
{i:j for i in range(3) for j in range(3)} # {0: 2, 1: 2, 2: 2}

# 손쉽게 key와 value를 swap할 수 있다
a = {1: &#39;one&#39;, 2: &#39;two&#39;}
{value:key for key,value in a.items()} # {&#39;one&#39;: 1, &#39;two&#39;: 2}

# Generator도 쉽게 생성할 수 있다.
# 호출 가능한 범위를 벗어나게 되면 StopIteration 에러 발생
gen = (x**2 for x in range(10))
print(gen) # &lt;generator object &lt;genexpr&gt; at 0x105bde5c8&gt;
print(next(gen)) # call 1 # 0
print(next(gen)) # call 2 # 1
# ...
print(next(gen)) # call 11

# Generator Expression으로 생성하는 경우에도,
# yield로 생성하는 일반적인 경우와 똑같이 sum을 사용할 수 있다. (iterable 객체)
gen = (x**2 for x in range(10))
sum_of_squares = sum(gen) # 285</code></pre>
<h3 id="3-제너레이터-range">3. 제너레이터, range</h3>
<p>루프의 반복 동작을 제어할 수 있는 루틴 형태. 대량의 값을 생성하는 등의 동작에서 사용된다. 모든 값들을 메모리에 보관하지 않고 제너레이터만을 저장하고 실행시마다 값을 생성해내어 메모리를 크게 아낄 수 있다. yield를 통해 제너레이터 값을 내보낼 수 있으며, 함수는 종료되지 않고 다음 yield가 불리게 되는 때에 다시 실행된다.</p>
<pre><code class="language-python">def test_generator():
    yield 1
    yield &#39;string&#39;
    yield True
gen = test_generator()
type(gen) # &lt;class &#39;generator&#39;&gt;
next(gen) # 1
next(gen) # &#39;string&#39;
next(gen) # True
next(gen) # StopIteration 에러 발생</code></pre>
<p>두 객체는 서로 다른 객체이며, 각기 따로 동작한다.</p>
<pre><code class="language-python">h = test_generator()
i = test_generator()
h == i # False
h is i # False
next(h) # 1
next(i) # 1</code></pre>
<p>range는 제너레이터 방식을 활용하는 대표적인 함수이다. range()를 통해 range 클래스 객체를 생성할 수 있으며, 생성 조건만 정해두고 필요할 때마다 숫자를 생성한다. 아래 두 예시는 서로 다른 방식으로 100만개의 수를 출력하지만 데이터 타입과 메모리 점유율에서 차이가 있음을 알 수 있다. 미리 생성하지 않은 값은 인덱스에 접근 시 바로 생성하도록 구현되어 있기 때문에 리스트와 거의 동일한 느낌으로 사용할 수 있다.</p>
<pre><code class="language-python">a = [ n for n in range(1000000)]
b = range(1000000)
len(a) # 1000000
len(b) # 1000000
type(a) # &lt;class &#39;list&#39;&gt;
type(b) # &lt;class &#39;range&#39;&gt;
b # range(0, 1000000)
import sys
sys.getsizeof(a) # 8448728
sys.getsizeof(b) # 48</code></pre>
<h3 id="4-도움이-되는-여러-함수들-enumerate-나눗셈-연산자-print와-string-formating-pass-locals-스타일-가이드">4. 도움이 되는 여러 함수들 (enumerate, 나눗셈 연산자, print와 string formating, pass, locals, 스타일 가이드)</h3>
<p><strong>enumerate()</strong> : 인덱스와 값 모두 처리하기</p>
<pre><code class="language-python">a = [2,4,6,8]
for idx,val in enumerate(a):
    print(idx,val)
</code></pre>
<p><strong>나눗셈 연산자</strong> : //는 몫, %는 나머지를 반환. divmod는 몫과 나머지를 한번에 반환한다.</p>
<pre><code class="language-python">5 // 3 # 1
5 % 3 # 2
divmod(5,3) # (1, 2)</code></pre>
<p><strong>print</strong> : sep, end 지정 가능. string formating을 함께 사용하면 더욱 편리하다</p>
<pre><code class="language-python">print(&quot;aa&quot;, &quot;bb&quot;, &quot;cc&quot;, sep=&quot;~~&quot;, end=&quot;@@finish@@&quot;)
# aa~~bb~~cc@@finish@@ # 이후 줄바꿈 없이 다음 줄이 연이어서 프린트된다.

idx = 3
val = &quot;three&quot;
print(&quot;{0}: {1}&quot;.format(idx,val)) # 3: three
print(f&quot;{idx}: {val}&quot;) # 3: three</code></pre>
<p><strong>pass</strong> : 코드의 전체 골격을 잡아놓고 내부 함수들을 나중에 만들기 위해 아무 역할도 하지 않는 널 연산을 반환.</p>
<p><strong>locals</strong> : locals()는 현재 로컬 스코프에 정의된 모든 변수를 반환한다. print하여 알고리즘 디버깅 시에 유용하게 사용 가능하다. <del>pprint를 import하여 pprint.pprint(locals())를 통해 예쁘게 정렬된 로컬 변수들을 출력할 수 있다!</del></p>
<p><strong>인덴테이션</strong> : 파라미터가 시작되는 부분에 맞추거나, 다음 줄과 헷갈리지 않도록 인덴트 추가하기</p>
<p><strong>네이밍 컨벤션</strong> : 스네이크 케이스를 추천</p>
<p><strong>스타일 가이드</strong> : 불명확한 것이 없도록 한다. 함수의 변수 기본값으로 mutable한 객체를 사용하는 대신 immutable 객체 사용하기, True False 판별 시에는 implicit한 방법으로 간결하게 표현하기</p>
<pre><code class="language-python"># Good
def foo(a, b=None):
    if b is None:
        b = []
    pass

def foo(a, b: Optional[Sequence] = None):
    if b is None:
        b = []
    pass

if not users:
    print(&#39;no users&#39;)

if foo == 0:
    self.handle_zero()

if i % 10 == 0:
    self.handle_multiple_of_ten()

# Bad
def foo(a, b=[]):
    pass

def foo(a, b: Mapping = {}):
    pass

if len(users) == 0:  # foo != [] 등으로 체크하는 것도 비추, not users로 의미 전달 명확하게
    print(&#39;no users&#39;)

if foo is not None and not foo: # 0인 것을 암시적 False 대신 정수로 처리하는 것이 명확
    self.handle_zero()

if not i % 10: # 1 % 10 == 0과 같이 명시적으로 값을 비교하는 것이 좋다
    self.handle_multiple_of_ten()
</code></pre>
<h3 id="5-참조-시-주의할-점">5. 참조 시 주의할 점</h3>
<p><strong>C++와 다른 참조와 비교</strong>
C++의 경우 참조형에 재할당이 일어나더라도 가리키는 메모리 주소가 변하지 않는다. 반면, 파이썬은 원시타입이라는 것이 따로 존재하지 않고 모든 것이 객체-<em>불변객체 or 가변객체</em>-로 이루어져 있는데, 새로운 불변객체를 할당하게 되면 새로운 객체를 참조하게 되어 가리키는 메모리 주소가 달라지고 가변객체의 경우 참조 대상의 변수도 변경할 수 있게 되어 아래와 같은 결과를 가진다. <del>파이썬의 리스트는 모든 아이템의 레퍼런스 주소값을 개별적으로 모두 소유하고 있다. <a href="https://del4u.tistory.com/27">https://del4u.tistory.com/27</a> 설명 굿</del> (참고로, new_list = old_list[::] 또는 new_list = list(old_list)을 사용하여 새로운 리스트를 가리킬 수 있다.)</p>
<p>참고로 파이썬이 느린 이유 중 하나로, 단순히 정수형의 덧셈 연산을 하는 경우에도 C나 Java와 달리 객체에서 타입코드를 찾는 등 부가연산이 존재하기 때문이다.</p>
<pre><code class="language-cpp">// C++
int a = 10;
int &amp;b = a;
b = 7;
std::cout &lt;&lt; a &lt;&lt; std::endl; // 7</code></pre>
<pre><code class="language-python"># python
a = 10
b = a
b = 7
print(a, id(a),id(b)) # 10, 4550522560, 4550522464

a = [1, 2, 3]
b = a
b[2] = 5
print(a) # [1, 2, 5], 리스트의 경우 각각이 </code></pre>
<p><strong>is와 ==</strong>
is는 id()값을 비교하는 함수, ==는 값을 비교하는 연산자이다. None은 값 자체가 정의되어있지 않아 is로만 비교가 가능하다!!</p>
<h3 id="6-리스트-set-dict-기본-연산자">6. 리스트, set, dict 기본 연산자</h3>
<p><a href="https://www.ics.uci.edu/~pattis/ICS-33/lectures/complexitypython.txt">https://www.ics.uci.edu/~pattis/ICS-33/lectures/complexitypython.txt</a> 내용 중 일부
<img src="https://images.velog.io/images/dalbodre_ari/post/e46c6179-f0ca-4c1f-b4ea-e91823d316d6/image.png" alt="">
<img src="https://images.velog.io/images/dalbodre_ari/post/c0a1f824-6525-432a-a0fc-c6120079899c/image.png" alt="">
<img src="https://images.velog.io/images/dalbodre_ari/post/6947b2b6-ed0a-410a-b84a-fff45268f3db/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>