<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>gonn-i.log</title>
        <link>https://velog.io/</link>
        <description>https://gonnn-i.tistory.com/</description>
        <lastBuildDate>Thu, 20 Mar 2025 02:36:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>gonn-i.log</title>
            <url>https://velog.velcdn.com/images/gonn-i/profile/5003ec73-487f-41d4-bd6c-2fa95887650b/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. gonn-i.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gonn-i" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2.3 클래스 컴포넌트와 함수 컴포넌트 ]]></title>
            <link>https://velog.io/@gonn-i/2.3-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%ED%95%A8%EC%88%98-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@gonn-i/2.3-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%ED%95%A8%EC%88%98-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Thu, 20 Mar 2025 02:36:11 GMT</pubDate>
            <description><![CDATA[<p>함수 컴포넌트는 0.14버전에서부터 존재하던 선언방식인데, <strong>단 당시엔 무상태 컴포넌트 라서 그저 요소를 상태 관리없어 정적으로 랜더링하는 것이 목적</strong>이었다. 
(별다른 생태 주기 메서드나 상태가 필요없이 render 만 하는 경우에만 사용)</p>
<p>훅이 등장해서야 많은 작업을 할 수 있었기에, 이전까지는 클래스 컴포넌트를 사용했다.</p>
<h2 id="231-클래스-컴포넌트">2.3.1 클래스 컴포넌트</h2>
<p>기본적으로 클래스 컴포넌트를 만들려면, *<em>클래스 선언 후 extends로 만들고 싶은 컴포넌트를 상속받아야 한다. *</em></p>
<blockquote>
<p><code>React.Component</code>   /   <code>React.PureComponent</code></p>
</blockquote>
<h4 id="class-내부-살펴보기">Class 내부 살펴보기</h4>
<ul>
<li><p><code>constructor</code> (생성자)</p>
<ul>
<li><p>컴포넌트가 초기화되는 시점에 호출된다.</p>
</li>
<li><p>여기에서 state 초기화를 할 수 있다.</p>
</li>
<li><p>super()를 호출해 부모 클래스(React.Component)의 생성자를 실행해야 한다.</p>
<ul>
<li>이를 통해 React의 내부 기능을 사용할 수 있도록 초기 설정이 이루어진다.<ul>
<li>super(props)를 호출하면 부모 클래스에서 props를 사용할 수 있도록 전달할 수 있다.</li>
</ul>
</li>
</ul>
<pre><code>ES2022의 클래스 필드(Class Fields) 문법 덕분에 
constructor 없이도 state를 초기화할 수 있는데, state = { count: 1 }처럼 선언 가능하다.

ES2022 이상의 환경에서 지원되므로 구형 브라우저에서는 
Babel의 @babel/plugin-proposal-class-properties 플러그인을 사용해 트랜스파일링해야 한다.
</code></pre></li>
</ul>
</li>
<li><p><code>props</code> (속성)    </p>
<ul>
<li>컴포넌트 외부에서 값을 전달하는 용도로 쓰인다.</li>
<li>부모 컴포넌트에서 값을 변경할 수 있지만, 자식 컴포넌트에서는 읽기 전용임.    </li>
</ul>
</li>
<li><p><code>state</code> (상태)</p>
<ul>
<li>컴포넌트 내부에서 관리하는 값을 의미한다.</li>
<li>반드시 객체 형태여야 하며, state가 변경되면 컴포넌트가 리렌더링된다.</li>
</ul>
<pre><code class="language-js">  class SampleComponent extends React.Component {
state = { count: 0 };

  render() {
    return &lt;div&gt;{this.state.count}&lt;/div&gt;;
  }
}</code></pre>
</li>
<li><p><code>메서드</code></p>
<ul>
<li>클래스 컴포넌트에서 렌더링 함수 내부에서 사용되는 함수이다.  </li>
<li>보통 이벤트 핸들러(onClick, onChange 등)와 함께 사용된다.  </li>
<li>this 바인딩 문제를 해결하는 방법은 3가지가 있다.  <ul>
<li><strong>constructor에서 this 바인딩</strong>:  
this가 <code>undefined</code>가 되는 문제를 방지하기 위해 <code>bind()</code>를 사용해야 한다.  </li>
<li><strong>화살표 함수 사용</strong>:  
화살표 함수는 <strong>작성 시점에 <code>this</code>가 결정되므로 <code>bind()</code>가 필요 없다</strong>.  </li>
<li><strong>렌더링 함수 내부에서 새 함수 생성</strong> (지양해야 함):<br>매 렌더링마다 새로운 함수가 생성되어 이벤트 핸들러가 <strong>매번 새롭게 만들어짐 → 메모리 낭비 초래</strong>.</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">class SampleComponent extends React.Component {
  state = { count: 0 };

  // 1. Constructor에서 this 바인딩
  // constructor(props) {
  //   super(props);
  //   this.handleClick = this.handleClick.bind(this);
  // }

  // 2. 화살표 함수 사용
  handleClick = () =&gt; {
    console.log(this.state.count);
  };

  render() {
    return (
      // 3. 렌더링 시 함수 생성 (지양)
      // &lt;button onClick={() =&gt; this.handleClick()}&gt;Click&lt;/button&gt; 

      // 2번 방식인 화살표 함수 사용
      &lt;button onClick={this.handleClick}&gt;Click&lt;/button&gt; 
    );
  }
}
</code></pre>
<hr>
<h4 id="클래스-컴포넌트의-생명주기-메서드">클래스 컴포넌트의 생명주기 메서드</h4>
<blockquote>
<p><strong>생명주기 메서드가 실행되는 시점</strong></p>
<ul>
<li>마운트 : 컴포넌트가 생성되는 시점</li>
<li>업데이트 : 이미 생성된 컴포넌트의 내용이 변경되는 시점</li>
<li>언마운트 : 컴포넌트가 더 이상 존재하지 않는 시점</li>
</ul>
</blockquote>
<ul>
<li><p><strong>render()</strong></p>
<ul>
<li>컴포넌트가  <strong>UI를 랜더링하기 위해 쓰이며</strong>, 이 랜더링은 <strong>마운트와 업데이트 과정에서 일어난다.</strong></li>
<li>render 함수는 항상 순수해야 하기 때문에, 내부에서 <code>state</code> 를 직접 변경하는 <code>this.setState</code>를 호출해서는 안된다.  <ul>
<li>여기에서 말하는 순수성이란, 같은 입력값이 들어가면, 항상 같은 결과를 반환해야 한다는 뜻 </li>
</ul>
</li>
</ul>
</li>
<li><p><strong>componentDidMount()</strong></p>
<ul>
<li>마운트 후 호출: 컴포넌트가 화면에 렌더링(마운트)된 후 즉시 호출된다.</li>
<li>상태 변경 가능: this.setState()로 상태를 변경할 수 있다.</li>
<li>주의) 성능 문제를 일으킬 수 있으므로 일반적으로 state를 다루는 것은 생성자에서 하는 게 좋다. this.setState를 허용하는 것은 API 호출이나 DOM 관련 작업을 위해서 이다.</li>
</ul>
</li>
<li><p><strong>componentDidUpdate()</strong></p>
<ul>
<li>업데이트 후 호출: 일반적으로 state나 props의 변화에 따라 DOM을 업데이트하는 등에 사용된다.</li>
<li>상태 변경 가능: this.setState()로 상태를 변경할 수 있으나, 적절한 조건문이 없다면 불필요하게 상태변경함수가 호출된다.</li>
</ul>
</li>
<li><p><strong>componentWillUnmount()</strong></p>
<ul>
<li>언마운트 혹은 더 이상 사용되지 않기 직전에 호출된다.</li>
<li>메모리 누수나 불필요한 작동을 막기 위한 클린업 함수를 호출하기 위한 최적의 위치<ul>
<li>이벤트를 지우거나, API 호출을 취소하거나, setInterval, setTimeout 타이머를 지우는 작업 등을 한다. </li>
</ul>
</li>
<li>상태 변경 불가능: this.setState()로 상태를 변경할 수 없다 ❌</li>
</ul>
</li>
<li><p><strong>shouldComponentUpdate()</strong></p>
<ul>
<li><p>리렌더링 방지: state나 props 변경으로 인한 리렌더링을 막고 싶을 때 사용한다.</p>
<ul>
<li><p>특정 조건에서만 리렌더링을 허용하여 성능을 최적화 가능!</p>
<ul>
<li>this.setState() 호출 시 컴포넌트는 기본적으로 리렌더링을 트리거하는데, shouldComponentUpdate()에서 false를 반환하면 리렌더링을 방지할 수 있다. </li>
</ul>
<pre><code class="language-js">  shouldComponentUpdate(nextProps, nextState) {
  // props의 title이 같지 않거나 state의 input이 같지 않은 경우에만 컴포넌트를 업데이트
  return this.props.title !== nextProps.title || this.state.input !== nextState.input;
}</code></pre>
</li>
</ul>
</li>
<li><p><code>PureComponent</code> 가 shouldComponentUpdate()를 사용할 수 있기에, <code>Component</code>와 랜더링에 있어 차이점을 갖는다.</p>
<ul>
<li>얕은 비교: PureComponent는 props와 state의 얕은 비교를 수행하여, 값이 변경되지 않으면 리렌더링을 방지한다. </li>
<li>단! 복잡한 객체나 배열 상태 변화는 얕은 비교로 감지되지 않으므로, 상태가 객체일 때는 문제가 발생할 수 있다. </li>
</ul>
</li>
</ul>
</li>
<li><p><strong>static getDerivedStateFromProps()</strong></p>
<ul>
<li>render()를 호출하기 직전에 호출된다.</li>
<li>static으로 선언돼 있어 this에 접근할 수 없다. </li>
<li>여기에서 반환하는 객체는 모두 state로 들어가게 된다.</li>
</ul>
</li>
<li><p><strong>getSnapShotBeforeUpdate()</strong></p>
<ul>
<li>DOM이 업데이트되기 직전에 호출된다. (componentWiUUpdate()를 대체할 수 있는 메서드)</li>
<li>반환되는 값은 componentDidUpdate로 전달되며, 랜더링 전 원도우 크기 조절 / 스크롤 위치 조정 등의 작업 처리에 유용하다.</li>
</ul>
</li>
<li><p><strong>getDerivedStateFromError()</strong></p>
<ul>
<li>에러 상황에서 실행 : 자식 컴포넌트에서 에러가 발생했을 때 호출되는 에러 메서드이다. </li>
<li>static 메서드로, 하위 컴포넌트에서 발생한 에러정보인 error를 인수로 받는다.</li>
<li>해당 메서드는 항상 state 값을 반환한다.<ul>
<li>반환된 state 값은 컴포넌트의 상태로 설정되어 에러 발생 시 보여줄 UI를 결정하는 데 사용하기 때문</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>componentDidCatch()</strong></p>
<ul>
<li>에러 상황에서 실행 : getDerivedStateFromError에서 에러를 잡고 state를 결정한 이후에 실행</li>
<li>인수로는 getDerivedStateFromError와 동일한 <code>error</code>, 그리고 정확히 어떤 컴포넌트가 에러를 발생시켰는지 정보를 가지고 있는 <code>info</code> 를 받는다.</li>
<li>getDerivedStateFromError와 달리, 부수 효과를 수행하며 에러 로깅을 할 수 있다. </li>
</ul>
</li>
</ul>
<p>getDerivedStateFromError() 와 componentDidCatch()는 <code>ErrorBoundary</code>, 에러 경계 컴포넌트를 만들기 위한 목적으로 많이 사용된다. 이를 통해 전역에서 처리되지 않은 에러를 처리하기 위한 용도로 사용된다.</p>
<blockquote>
</blockquote>
<p>다만, ErrorBooundary에서 주의할 점이 있다.
<strong>1. 개발모드와 프로덕션 모드에서 동작 차이</strong>
    개발 모드에서는 에러가 <code>window</code>까지 전파되지만, 프로덕션 모드에서는 <code>componentDidCatch()가</code> 잡지 못한 에러만 전파된다. (개발 모드에서 에러 처리가 중복될 수 있음)
<strong>2. componentDidCatch()의 두 번째 인수 errorInfo</strong>
     errorInfo는 에러가 발생한 컴포넌트의 위치를 추적하는 정보인데, <code>errorInfo.componentStack</code> 를 통해 어느 컴포넌트에서 에러가 발생했는지 알 수 있다.
      이 정보는 Function.name 또는 displayName에 따라 결정되는데 익명함수<code>(memo(() =&gt; { ... }))</code>로 선언하면, 컴포넌트를 선언할 수 없어 정보가 제대로 표시되지 못한다. </p>
<hr>
<h4 id="클래스-컴포넌트의-한계">클래스 컴포넌트의 한계</h4>
<h5 id="클래스-컴포넌트의-단점">클래스 컴포넌트의 단점</h5>
<ol>
<li><p>데이터 흐름 추적 어려움  </p>
<ul>
<li>여러 생명주기 메서드에서 <code>state</code>가 업데이트됨 → 흐름 파악 어려움</li>
<li>메서드 작성 순서가 강제되지 않아 코드 읽기 불편 </li>
</ul>
</li>
<li><p>내부 로직 재사용 어려움  </p>
<ul>
<li>고차 컴포넌트(HOC) 사용 시 → wrapper hell 발생  </li>
<li>상속(PureComponent) 사용 시 → 복잡도 증가</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p>기능 증가 시 컴포넌트 크기 증가  </p>
<ul>
<li>생명주기 메서드 사용이 많아질수록 컴포넌트 크기 기하급수적 증가</li>
</ul>
</li>
<li><p>클래스 문법의 복잡성  </p>
<ul>
<li>자바스크립트의 <code>this</code> 특성으로 인해 초보자에게 혼란 유발  </li>
<li>함수형 컴포넌트보다 사용이 어렵고 덜 직관적</li>
</ul>
</li>
<li><p>코드 크기 최적화 어려움  </p>
<ul>
<li>트리 쉐이킹(Tree Shaking) 미적용 → 사용하지 않는 메서드도 번들에 포함됨  </li>
<li>메서드 이름이 최소화되지 않아 번들 크기 증가 </li>
</ul>
</li>
<li><p>핫 리로딩 불리</p>
<ul>
<li>핫 리로딩 : <strong>코드 변경 사항을 즉시 반영하여</strong> 애플리케이션을 다시 시작하지 않고도 업데이트된 내용을 적용하는 기술</li>
<li>코드 변경 시 즉각적인 반영 어려움 </li>
<li>(why)
클래스 컴포넌트는 instance(인스턴스) 내부에서 state를 관리하기 때문에, render() 함수가 수정되면 새 instance를 생성해야 하며, 기존 state는 초기화됨
&lt;-&gt; 함수형 컴포넌트는 클로저를 활용하여 state를 유지할 수 있다고 함, </li>
</ul>
</li>
</ol>
<hr>
<h2 id="232-함수-컴포넌트-vs-클래스-컴포넌트">2.3.2 함수 컴포넌트 vs 클래스 컴포넌트</h2>
<h4 id="생명주기-메서드의-부재">생명주기 메서드의 부재</h4>
<ul>
<li><strong>클래스 컴포넌트</strong>: <code>React.Component</code>를 상속받아 <code>componentDidMount</code>, <code>componentDidUpdate</code>, <code>componentWillUnmount</code> 등의 생명주기 메서드 사용한다. </li>
</ul>
<ul>
<li><strong>함수 컴포넌트</strong>: <code>useEffect</code> 훅을 사용하여 비슷한 동작을 구현하지만, 생명주기 메서드와는 엄연하게 같다고는 할 수 없다. <code>useEffect</code>는 부수 효과 처리용으로 설계되었다.</li>
</ul>
<h4 id="함수-컴포넌트-vs-클래스-컴포넌트-렌더링-값-고정-여부">함수 컴포넌트 vs 클래스 컴포넌트: 렌더링 값 고정 여부</h4>
<p><strong>차이점</strong></p>
<ul>
<li><strong>클래스 컴포넌트</strong>: <code>this.props</code>를 사용하여 props를 가져오고, 컴포넌트의 인스턴스가 변할 때마다 업데이트된 값을 읽는다.</li>
<li><strong>함수 컴포넌트</strong>: props는 함수의 인수로 전달되어, 렌더링 시점의 값을 기준으로 렌더링된다. 즉, 클릭한 시점의 값을 기준으로 메시지가 뜬다.</li>
</ul>
<p><strong>왜 차이가 발생하는데?</strong></p>
<ul>
<li><strong>클래스 컴포넌트</strong>는 <code>this</code>가 가리키는 객체가 변할 수 있으므로, 렌더링 중에 props 값이 바뀌면 변경된 값이 반영된다.</li>
<li><strong>함수 컴포넌트</strong>는 <code>this</code>가 없고, props와 state가 함수의 인수로 들어오므로 그 시점의 값이 렌더링에 사용된다.</li>
</ul>
<p><strong>해결 방법</strong></p>
<ul>
<li>클래스 컴포넌트에서는 <code>this.props</code>를 더 일찍 호출하여 값을 처리하거나, 함수의 인수로 넘겨줄 수 있다. (그치만, state과 props가 늘어날 수록 코드가 복잡해짐)</li>
<li>render 에 필요한 값 넣고 필요한 순간에 부르기 (랜더링될때마다 함수가 다시 할당되기에 성능저하 야기) </li>
</ul>
<hr>
<h2 id="233-정리">2.3.3 정리</h2>
<p> 클래스 컴포넌트는 당장 사라질 계획은 없고, 기존 코드가 많아 이를 모두 함수 컴포넌트로 바꾸는 건 쉽지 않다. 함수 컴포넌트로 전환하려면 충분한 경험과 실수 감지 시스템이 필요하고, 그렇지 않으면 굳이 바꿀 필요는 없다. 리액트를 처음 배우거나 새로운 프로젝트를 시작할 땐 함수 컴포넌트를 쓰는 게 좋다. 그치만 어느 정도 익숙해졌다면 클래스 컴포넌트도 공부할 필요가 있는데, 일부 에러 처리는 클래스 컴포넌트에서만 가능하기 때문이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2.1 - 2.2: JSX와 DOM]]></title>
            <link>https://velog.io/@gonn-i/2.1-2.2-JSX%EC%99%80-DOM</link>
            <guid>https://velog.io/@gonn-i/2.1-2.2-JSX%EC%99%80-DOM</guid>
            <pubDate>Tue, 18 Mar 2025 09:18:45 GMT</pubDate>
            <description><![CDATA[<h2 id="21-jsx란">2.1 JSX란?</h2>
<ul>
<li>JSX는 ECMAScript 라 불리는,** 자바스크립트의 표준은 아니다.**</li>
<li>목적으로는, 다양한 속성을 가진 <strong>트리 구조를 토큰화</strong>해서 <code>ECMASCipt</code>로 변환하는데에 초점을 둔다. (단, 트랜스파일 구조를 거쳐)</li>
</ul>
<blockquote>
<p>JSX란, 요약하자면 JS 내부에서 표현하기 까다로웠던 XML 스타일 트리 구문을 작성하는데 도움을 주는 문법이다. </p>
</blockquote>
<hr>
<h3 id="211-jsx-정의">2.1.1 JSX 정의</h3>
<p>JSX는 4가지 컴포넌트를 기반으로 구성되어 있다. 
(<code>JSXElement</code> / <code>JSXAttributes</code> / <code>JSXChildren</code> / <code>JSXStrings</code>) </p>
<ul>
<li><p><code>JSXElement</code> : <strong>HTML 의 요소와 비슷한 역할</strong>을 한다. 형태는 아래 4가지와 같다.</p>
<ul>
<li><code>JSXOpeningElement</code>: 선언 시작을 의미, 말그대로 오프닝을 담당</li>
<li><code>JSXClosingElement</code>: JSXOpeningElement 가 종료됐음을 알리는 요소</li>
<li><code>JSXSelfClosingElement</code> : 요소가 시작되고, 스스로 종료되는 형태를 의미 </li>
<li><code>JSXFragment</code> : 아무런 요소가 없는 형태, <code>&lt;&gt;&lt;/&gt;</code> </li>
</ul>
</li>
<li><p><code>JSXElementName</code> : JSXElement의 요소이름으로 쓸 수 있는 것을 의미</p>
<ul>
<li><code>JSXIdentifier</code> : JSX 내부에서 사용할 수 있는 식별자, $와 _로 시작할 수 있지만, 숫자로 시작하는 것은 불가능</li>
<li><code>JSXNamespacedName</code> : :을 사용하여 네임스페이스를 구분할 수 있다, 단, 한번만 사용 가능 </li>
<li><code>JSXMemberExpression</code> : .을 사용하여 요소를 연결할 수 있다, 단, 여러 개의 .을 사용할 수 있지만, :과 함께 사용할 수는 없다</li>
</ul>
</li>
<li><p><code>JSXAttributes</code>: JSXElement에 부여할 수 있는 속성을 의미, 필수값은 아니다. </p>
<ul>
<li><code>JSXSpreadAttributes</code> : 전개 연산자 (...)를 이용해 속성을 전달할 수 있다.</li>
<li><code>JSXAttribute</code> : 속성은 키-값 형태로 표현 (JSXAttributeName (key) / JSXAttributeValue (속성의 값) )</li>
</ul>
</li>
<li><p><code>JSXChildren</code> : <code>JSXElement</code>의 자식 값을 나타낸다. 기본 단위는 JSXChild로 JSXChildren은 0개 이상의 JSXChild를 포함할 수 있다.</p>
<ul>
<li>내부에는 일반적인 문자열 텍스트, 다른 JSX 요소, &lt;&gt;...&lt;/&gt;, {}로 감싼 표현식을 사용할 수 있다.</li>
</ul>
</li>
<li><p><code>JSXStrings</code> : HTML과 JSX 간 복사 &amp; 붙여넣기를 쉽게 하기 위해 설계된 문자열 표현 방식</p>
<ul>
<li><p>큰따옴표(&quot;)로 구성된 문자열 / 작은따옴표(&#39;)로 구성된 문자열 / JSXText (특수 문자 {, &lt;, &gt;, } 제외) 가 여기에 속한다</p>
</li>
<li><p>단, HTML 과의 차이점은 JavaScript에서는<code>\</code>가 이스케이프 문자로 사용되므로, 문자 그대로 표현하려면 <code>\\</code>로 이스케이프해야 한다. </p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="212-jsx는-자바스크립트에서-어떻게-변환될까">2.1.2 JSX는 자바스크립트에서 어떻게 변환될까</h3>
<p><code>@babel/plugin-transform-react-jsx</code> 플러그인을 통해, JSX 구문을 자바스크립트가 이해할 수 있는 형태로 변환한다.</p>
<hr>
<h2 id="22-가상-dom과-리액트-파이버">2.2 가상 DOM과 리액트 파이버</h2>
<h3 id="221-dom과-브라우저-랜더링-과정">2.2.1 DOM과 브라우저 랜더링 과정</h3>
<p> <strong><code>DOM</code>이란?</strong> (-&gt; 웹페이지에 대한 인터페이스)
<strong>웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보</strong>이다. </p>
<blockquote>
<p><strong>📍 브라우저 랜더링 과정</strong> (a.k.a Critical Rendering Path)</p>
<p>1️⃣ HTML 다운로드 및 파싱 → <strong>DOM 트리 생성</strong> </p>
</blockquote>
<p>2️⃣ CSS 다운로드 및 파싱 → ** CSSOM 트리 생성** 
(브라우저는 , display: none 과 같이 사용자에게 보여지지 않는 부분은 방문해 작업하지 않는다. 트리 분석을 빠르게 하기 위해서)</p>
<blockquote>
</blockquote>
<p>3️⃣ ** Render Tree 생성**  → 화면에 보이는 요소만 포함</p>
<blockquote>
</blockquote>
<p>4️⃣ ** 레이아웃(Layout, Reflow)**  → 요소의 위치와 크기 계산</p>
<blockquote>
</blockquote>
<p>5️⃣ <strong>페인팅</strong>  → 요소에 색상, 이미지 등 시각적 스타일 적용
(이때, 레이아웃 과정을 거치면, 페인팅도 거치게 된다.)</p>
<h3 id="222-가상-dom의-탄생과정">2.2.2 가상 DOM의 탄생과정</h3>
<p>브라우저에 웹 페이지를 랜더링하는 것은 비용이 든다. 
초기 랜더링 이외에도, 사용자 인터렉션에 따라 변경되는 사항도 고려해야 한다.</p>
<p>특히, <strong>위치나 크기를 다시 계산</strong>해야 하는 경우 
-&gt; <code>레이아웃</code> + <code>리페인팅</code> 을 야기하기 때문에 리소스적으로 비용이 든다.
또한, <strong>DOM 변경을 일으키는 요소가 많은 자식을 가질 경우</strong>, 하위 자식들도 덩달아 변경돼야 하기에 이에 대한 고려가 필요하다.</p>
<p>그래서 등장한 것이 바로, <code>가상 DOM</code>이다. (이하 VDOM)</p>
<blockquote>
<p><code>VDOM</code>은 
실제 브라우저의 DOM이 아닌 <strong>리액트가 관리하는 가상의 DOM을 의미</strong>한다. 
브라우저가 아닌 메모리에서 계산하는 과정을 거치면(VDOM 을 사용하면), 
실제 <strong>여러번 발생했을 랜더링 과정을 최소화할 수 있다.</strong></p>
</blockquote>
<p>오해 ) 일반적인 DOM을 관리하는 브라우저보다, VDOM이 항상 빠를까?
-&gt; 무조건 빠른 것이 아니라 리액트의 이 가상 DOM 방식은 대부분의 상황에서 웬만한 애플리케이션을 만들 수 있을 정도로 충분히 빠르다는 것이다.
(가상 DOM으로 랜더링 과정을 모아서 최적화시키는 건 맞지만, 항상 빠르다고 볼 순 없다. )</p>
<hr>
<h3 id="223-가상-dom을-위한-아키텍처-리액트-파이버">2.2.3 가상 DOM을 위한 아키텍처, 리액트 파이버</h3>
<h4 id="리액트-파이버란">리액트 파이버란?</h4>
<ul>
<li>파이버 재조정자가 관리하는, 자바스크립트 객체</li>
<li>DOM과 - VDOM 비교를 통해 변경사항을 수집하며, <strong>차이 정보를 가지고 있는 파이버를 기준으로 화면에 랜더링을 요청하는 역할</strong>을 한다. </li>
</ul>
<blockquote>
<p>*<em>파이버가 하는 일 *</em></p>
</blockquote>
<ul>
<li>작업을 작은 단위로 분할하고 쪼갠 다음, 우선순위를 매긴다.</li>
<li>이러한 작업을 일시 중지하고, 나중에 시작할 수 있다. </li>
<li>이전에 했던 작업을 다시 재사용하거나, 필요하지 않는 경우 폐기할 수 있다.</li>
</ul>
<p>초기 리액트 알고리즘은 스택 알고리즘 ==&gt; 모든 과정이 동기적으로 이루어졌다.
비효율적인 스택 구조를 타파하고자, <strong>파이버</strong> 개념이 생겼다.</p>
<blockquote>
<p>파이버의 작동과정</p>
<ul>
<li><strong>렌더 단계</strong>
  리액트는 가상 DOM을 기반으로 변경 사항을 계산 (= 사용자에게 노출되지 않는 모든 비동기 작업을 수행)
  이 과정에서 우선순위에 따라 작업을 중지하거나 버리는 등의 작업을 한다.</li>
<li><strong>커밋 단계</strong>
  변경 사항을 실제 DOM에 반영하는 과정.    이 단계는 동기적으로 실행되며, 중단될 수 없다
  React는 commitWork()를 실행하여 실제 DOM을 업데이트하고, useEffect 같은 side effect들을 처리함.</li>
</ul>
</blockquote>
<p>리액트 요소와 유사하지만, 큰 차이점은 
<code>리액트 요소</code>는 랜더링이 발생할때마다 새롭게 생성되지만, <code>파이버</code>는 가급적이면 재사용된다.</p>
<p>파이버는 하나의 element에 하나가 생성되는 1:1 관계를 가지고 있다. (여기에서 요소는 리액트 컴포넌트나, HTML DOM 노드 등을 말한다.) 생성된 파이버는 state가 변경되거나, 생명주기 메서드가 실행되거나 DOM 변경이 필요한 시점에 실행된다. </p>
<blockquote>
<p>리액트의 핵심 원칙은 <strong>UI를 문자열, 숫자, 배열과 같은 값으로 관리</strong>한다. (파이버의 객체 값에서도 알 수 있다.)
<strong>변수에 따라 이러한 UI 값을 보관</strong>하고, 리액트의 자바스크립트 코드 흐름에 따라 이를 관리하고, 표현하는 것이 바로 리액트이다.</p>
</blockquote>
<h4 id="파이버-트리">파이버 트리</h4>
<p>리액트 내부에는 현재 모습을 담은 <code>파이버 트리</code>와, 
작업 중인 상태를 나타내는 <code>workInProgress 트리</code>가 있다.</p>
<p>리액트 파이버 작업이 끝나면, 리액트는 단순히 포인터만 변경해서 <code>workInProgress</code> 트리를  -&gt; 현재 트리로 바꿔버리는데,
이 기술을 <strong>더블 버퍼링</strong>이라고 한다. </p>
<h4 id="fiber-트리-탐색-과정">Fiber 트리 탐색 과정</h4>
<blockquote>
<ul>
<li>beginWork() 실행</li>
</ul>
</blockquote>
<pre><code>각 Fiber 노드를 트리 형식으로 순회하면서 작업을 수행함.
더 이상 자식이 없는 노드(리프 노드)를 만날 때까지 탐색함.
completeWork() 실행</code></pre><blockquote>
<ul>
<li><p>completeWork() 실행</p>
<p> 자식 노드의 처리가 끝나면, 현재 노드의 작업을 완료함.</p>
</li>
<li><p>형제가 있다면 형제 노드로 이동</p>
<p> `현재 노드가 형제 노드를 가지고 있다면, 형제 노드의 beginWork()를 실행함.</p>
</li>
<li><p>더 이상 형제 노드가 없으면 부모로 돌아감 (return)</p>
</li>
</ul>
</blockquote>
<pre><code>형제 노드도 모두 처리되었다면, 부모 노드로 돌아가 자신의 작업이 완료되었음을 알림.</code></pre><ul>
<li>이렇게 생성된 트리에서, setState로 업데이트가 발생하면 기존(current) 트리를 기반으로 새로운(workInProgress) 트리를 생성한다.</li>
<li>이때, <strong>기존 Fiber를 재사용</strong>하며, 필요한 부분만 업데이트하여 리소스를 절약.</li>
<li>예전에는 동기식으로 처리했지만, 이제는 <strong>우선순위에 따라 중단/재개/폐기 처리</strong>한다.</li>
<li>작업 단위를 나누어 애니메이션·입력은 우선순위 높게, 목록 렌더링은 낮게 설정하며 최적의 순위로 작업을 완료하게끔 작동한다.</li>
</ul>
<hr>
<h3 id="224-파이버와-가상-dom">2.2.4 파이버와 가상 DOM</h3>
<ul>
<li>Fiber는 React 컴포넌트 정보를 1:1로 가지며, 비동기적으로 처리된다.</li>
<li>실제 DOM 업데이트는 동기적으로 이루어져야 하므로, 가상 DOM에서 미리 연산 후 적용하는 것이 효율적이다.</li>
<li>가상 DOM은 웹에서만 통용되는 개념인데, Fiber는 React Native에서도 사용 가능하다.</li>
<li>즉, Fiber와 가상 DOM은 동일한 개념이 아니다, React의 렌더러에 따라 다르게 작하더라도 내부적으로 파이버를 통해 조정되는 과정은 동일하기 떄문에 동일한 재조정자를 사용할 수 있게 된다! </li>
</ul>
<blockquote>
<p><strong>정리!! 📌 Fiber와 가상 DOM의 관계</strong>
<code>가상 DOM</code> : 실제 DOM을 직접 조작하지 않고, <strong>메모리상에서 변경 사항을 계산한 후 효율적으로 업데이트</strong>하는 DOM 
<code>Fiber</code> : 가상 DOM을 보다 <strong>최적화하여 조작하기 위한</strong> React 내부의 데이터 구조이자 알고리즘.</p>
</blockquote>
<h3 id="224-정리">2.2.4 정리</h3>
<p>가상 DOM과 리액트의 핵심은 브라우저의 DOM을 더욱 빠르게 그리고 반영하는 것이 아니라 바로 <strong>값으로 UI를 표현</strong>하는 것이다. <strong>화면에 표시되는 이를 자바스크립트의 문자열, 배열 등과 마찬가지로 값으로 관리</strong>하고 이러한 흐름을 효율적으로 관리하기 위한 메커니즘이 바로 리액트의 핵심이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1.7 타입스크립트 ]]></title>
            <link>https://velog.io/@gonn-i/1.7-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</link>
            <guid>https://velog.io/@gonn-i/1.7-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</guid>
            <pubDate>Sun, 16 Mar 2025 11:10:43 GMT</pubDate>
            <description><![CDATA[<h2 id="171-타입스크립트란">1.7.1 타입스크립트란?</h2>
<blockquote>
<p>&quot;TypeScript is JavaScript with syntax for types&quot;
타입스크립트는, *<em>기존 자바스크립트 문법에 타입을 가미한 것 *</em></p>
</blockquote>
<p>타입스크립트를 통해, 타입체크를 정적으로 런타임이 아닌 빌드 (트랜스파일) 과정에서 수행할 수 있게 해준다. </p>
<ul>
<li><p>JS는 런타임 (실행)하면서 오류를 발견하지만, TS는 빌드 시점에서 오류를 발견한다. </p>
<ul>
<li>JS : <code>JS 코드</code> → <code>AST (추상 문법 트리) 생성</code> → <code>바이트코드 변환</code> → <code>실행</code>    </li>
<li>TS : <code>TS 코드</code> → <code>AST 생성</code> → <code>타입 검사</code> → <code>JS로 변환(트랜스파일)</code> → <code>AST (추상 문법 트리) 생성</code> → <code>바이트코드 변환</code> → <code>실행</code>    </li>
</ul>
</li>
</ul>
<p>단, 타입스크립트는 자바스크립트의 슈퍼셋일 뿐이라, JS에서 안되는건 TS에서도 불가능 </p>
<hr>
<h2 id="172-리액트-코드를-효과적으로-작성하기-위한-타입스크립트-활용법">1.7.2 리액트 코드를 효과적으로 작성하기 위한 타입스크립트 활용법</h2>
<h3 id="any-대신-unknown-사용">any 대신 unknown 사용</h3>
<p><code>any</code>는 사실상 타입스크립트가 제공하는 정적 타이핑의 이점을 모두 버리는 것이나 다름없다. 
<code>any</code>는 모든 타입을 커버하기에, 어떤 상황에서도 런타임 과정에서 타입 에러를 잡아내지 못한다.
(예외적인 상황이 아니라면 사용하지 않는게 좋음 ) </p>
<p>아직 타입을 단정할 수 없는 경우에는 <code>unknown</code>을 사용하는 것이 좋다. 
    - <code>unknown</code>이란 모든 값을 할당할 수 있는 <code>top type</code> 이면서, 또 해당 값을 바로 사용하는 것이 불가능하기에 <code>type</code> 좁히기를 통해서 사용해야 하기에 타입에러를 잡아낼 수 있기도 하다. </p>
<pre><code class="language-ts">function doSomething(callback: unknown) {
  // type narrowing
  if (typeof callback === &#39;function&#39;) {
    callback();
    return;
  }
  throw new Error(&#39;callback은 함수여야 합니다.&#39;);
}</code></pre>
<h4 id="never-타입">never 타입</h4>
<p> 한편, <code>unknown</code>과 반대되는 <code>bottom type</code>인 <code>never</code>가 있기도 하다.
 <code>never</code>는 어떠한 타입도 들어올 수 없음을 의미한다. </p>
<p> <code>type what1 = string &amp; number</code>
<code>type what2 = (&#39;hello&#39; | &#39;hi&#39;) &amp; &#39;react&#39;</code></p>
<p>첫번째 타입은 문자열이면서, 숫자인 타입은 존재할 수 없으니 never가 선언되며,
두번째 타입의 경우, 교차점이 없기에 마찬가지로 코드상에서 존재 불가한 타입으로 나타난다. </p>
<p><strong><code>항상 예외를 던지는 함수(반환값이 필요없는 경우)</code></strong></p>
<pre><code class="language-ts">// throw를 만나면 실행 중단 -&gt; 반환값이 존재할 수 없음 
function throwError(message: string): never {
  throw new Error(message);
}</code></pre>
<h3 id="타입-가드를-적극-활용">타입 가드를 적극 활용</h3>
<p><strong>타입가드란</strong> - 타입을 좁히는데 도움을 줄 수 있는 것으로, <code>instanceof</code> 나 <code>typeof</code> , <code>in</code>을 이용한 조건문을 이용한다 </p>
<h4 id="instanceof">instanceof</h4>
<ul>
<li>객체 및 클래스의 인스턴스를 검사하며,</li>
<li><strong>특정 클래스의 인스턴스인지 판별</strong>할 수 있다. (반환값 T/F ) </li>
<li>특히, <code>[] instanceof Array</code> 를 정확히 판별할 수 있다. </li>
</ul>
<h4 id="typeof">typeof</h4>
<ul>
<li>특정 요소의 자료형을 확인하는데에 사용되며,</li>
<li>반환값으로 문자열 (&quot;string&quot;, &quot;object&quot;, &quot;function&quot; 등)이 반환된다, </li>
</ul>
<h4 id="in">in</h4>
<ul>
<li>property in object로 사용되는데, 주로 <strong>어떤 객체에 프로퍼티가 존재하는지 확인하는 용도로 사용</strong>된다. </li>
</ul>
<pre><code class="language-ts">interface Student {
  age: number;
  score: number;
}

interface Teacher {
  name: string;
}

function doSchool(person: Student | Teacher) {
  if (&quot;age&quot; in person) {    
     // 이때, person은 Student 타입
    }

  if (&quot;name&quot; in person) {
    // 이때, person은  Teacher 타입
  }
}</code></pre>
<h4 id="제네릭">제네릭</h4>
<p>단일 타입이 아닌, <strong>다양한 타입에 대응할 수 있도록 하는 타입도구</strong>이다.
이때 제네릭을 하나 이상 사용할 수 있다. </p>
<pre><code class="language-ts">function multipleGeneric&lt;First, Last&gt;(a1: First, a2: Last): [First, Last] {
  return [a1, a2];
}

const [a, b] = multipleGeneric&lt;string, boolean&gt;(&quot;true&quot;, true);

console.log(a); // string
console.log(b); // boolean</code></pre>
<h4 id="인덱스-시그니처">인덱스 시그니처</h4>
<p>인덱스 시그니처란, 객체 키를 정의하는 방식을 의미한다. </p>
<ul>
<li>[key: string] 과 같이 나타내는데, 이처럼 key에 원하는 타입을 부여할 수 있다.</li>
</ul>
<p>일반적인 인덱스 시그니처:</p>
<pre><code class="language-ts">type Hello = { [key: string]: string };</code></pre>
<ul>
<li>다만, key의 경우 string처럼 타입을 넓게 설정하면, 존재하지 않는 키에 접근할 위험이 있기에 
<code>Record</code>를 사용하거나, 더 좁게 인덱스 시그니처를 설정하거나, 그것도 아니면 <code>as로 key 타입 단언</code>을 하는 편이 좋다.  </li>
</ul>
<h5 id="recordk-v-와--as-keyof">Record&lt;K, V&gt; 와  as keyof</h5>
<p><code>Record&lt;K, V&gt;</code>는 객체의 키(K)와 값(V)의 타입을 명확하게 지정할 때 사용하는 유틸리티 타입이다. </p>
<ul>
<li>쉼표를 기준으로 key와 value를 구분하기 때문에 아래와 같이 사용할 수 있다. </li>
</ul>
<pre><code class="language-ts">// Record를 사용
type HeUo = Record&lt;&#39;hello&#39; | &#39;hi&#39;, string&gt;;

const hello: HeUo = {
  hello: &#39;hello&#39;,
  hi: &#39;hi&#39;,
};
</code></pre>
<pre><code class="language-ts">// 타입을 사용한 인덱스 시그니처
type HeUo = { [key in &#39;hello&#39; | &#39;hi&#39;]: string };

const hello: HeUo = {
  hello: &#39;hello&#39;,
  hi: &#39;hi&#39;,
};
</code></pre>
<hr>
<p><code>key as keyof Hello</code> 를 통해 key가 반드시 hello 나 hi를 포함하고 있다는 것을 ts에게 알려주는 타입 단언을 해줄 수 있다. </p>
<pre><code class="language-ts">type Helo = { hello: string, hi: string };

const hello: Hello = {
  hello: &#39;Hello!&#39;,
  hi: &#39;Hi!&#39;,
};

// Object.keys()는 string[]을 반환하기 때문에
// key를 keyof Hello 타입으로 단언하여 정확한 타입을 지정
Object.keys(hello).map((key) =&gt; {
  const value = hello[key as keyof Hello]; // key를 keyof Hello로 단언
  return value;
});
</code></pre>
<hr>
<h2 id="173-타입스크립트-전환-가이드">1.7.3 타입스크립트 전환 가이드</h2>
<h3 id="tsconfigjson-작성">tsconfig.json 작성</h3>
<p>타입스크립트 전환을 위해, 가장 먼저 해야할 것은 ts 작성이 가능한 환경을 만들어주는 것이다. 
최상위 디렉터리에 tsconfig 파일을 만들어주자.</p>
<pre><code class="language-ts">{
  &quot;compilerOptions&quot;: {
     // .ts 또는 .js 파일로 컴파일된 결과물이 저장될 폴더로, TypeScript의 컴파일 결과는 이 폴더로 이동
    &quot;outDir&quot;: &quot;./dist&quot;,               
      // .js 파일을 ts 프로젝트 내에서 허용할지 여부를 설정하는 옵션 
      // Js 파일이 존재하는 과도기적인 프로젝트에서 true로 설정하면 함께 작업할 수 있음
    &quot;allowJs&quot;: true,
      // TypeScript 코드가 어떤 JavaScript 버전으로 변환될지를 지정 
    &quot;target&quot;: &quot;es5&quot;
  },
     // TypeScript 컴파일러가 트랜스파일할 파일을 지정
    // `./src/**/*`는 src 폴더 내 모든 .ts 및 .js 파일을 포함함!
  &quot;include&quot;: [&quot;./src/**/*&quot;]          
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[1.6 리액트에서 자주 사용되는 JS 문법 ]]></title>
            <link>https://velog.io/@gonn-i/1.6-%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90%EC%84%9C-%EC%9E%90%EC%A3%BC-%EC%82%AC%EC%9A%A9%EB%90%98%EB%8A%94-JS-%EB%AC%B8%EB%B2%95</link>
            <guid>https://velog.io/@gonn-i/1.6-%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90%EC%84%9C-%EC%9E%90%EC%A3%BC-%EC%82%AC%EC%9A%A9%EB%90%98%EB%8A%94-JS-%EB%AC%B8%EB%B2%95</guid>
            <pubDate>Tue, 11 Mar 2025 18:03:49 GMT</pubDate>
            <description><![CDATA[<p>본게시글은 모던 리액트 deep dive를 읽고 정리한 글입니다.</p>
<p>자바스크립트 문법을 작성할때에, <strong>자바스크립트 표준인 <code>ECMAScript</code> 버전을 고려해야 한다.</strong></p>
<ul>
<li>왜냐하면 모든 브라우저와 자바스크립트 런타임이 항상 새로운 자바스크립트 문법을 지원하는 것은 아니기 때문이다. </li>
<li>익스플로러 11은 ES5까지만을 지원하기 때문에 최신 js 문법을 사용할 수 없다. </li>
<li>그래서 웹페이지에 접근하는 사용자의 브라우저와 버전은 개발자와 달리, 항상 최신 버전이 아닐 수 있기에 다양한 브라우저의 문법 지원을 염두에 두어야 한다.</li>
</ul>
<blockquote>
<p>그래서 우리에게 필요한 <strong><code>바벨</code></strong> </p>
</blockquote>
<p><code>바벨</code>은 자바스크립트의 최신 문법을 <strong>다양한 브라우저에서도 일괄적으로 지원할 수 있도록 코드를 트랜스파일</strong>한다. </p>
<ul>
<li>바벨 트랜스파일 코드는, 익스플로러를 지원하는 ES5를 기준으로 작성됐다</li>
</ul>
<hr>
<h2 id="161-구조-분해-할당">1.6.1 구조 분해 할당</h2>
<p>구조분해할당은 <strong>배열 또는 객체의 값을, 말 그대로 분해해서 개별 변수에 즉시 할당하는 것을 의미</strong>한다. 주로 객체나 배열에서 <strong>각각의 선언문 없이, 즉시 분해해서 변수 선언과 할당을 같이할 수 있다는 장점</strong>이있다.</p>
<ul>
<li>배열 구조분해 할당 -&gt; ES6(ECMA2015)</li>
<li>객체 구조분해 할당 -&gt; ECMA2018</li>
</ul>
<h4 id="배열-구조-분해-할당">배열 구조 분해 할당</h4>
<pre><code class="language-js">const array = [1, 2, 3, 4, 5];
const [first, second, third, ...arrayRest] = array;

// first 1
// second 2
// third 3
// arrayRest [4, 5]

// -----

const [value, setValue] = useState();</code></pre>
<p>대표적인 예시가 바로 <code>useState</code>이다. </p>
<ul>
<li><code>useState</code>가 객체가 아닌 배열을 반환하는 이유는 -&gt; 배열 구조 분해 할당은 자유롭게 이름을 선언할 수 있기 때문으로 추측할 수 있다. </li>
</ul>
<pre><code class="language-js">// 배열 구조 분해 할당은  , 위치에 따라 값이 결정된다,
const array = [1, 2, 3, 4, 5]
const [first, , , , fifth] = array // 2, 3, 4는 아무런 표현식이 없으므로 변수 할당 생략
// first  1
// fifth  5

// 기본값을 선언할 수도 있는데, 만약 사용하고자 하는 배열 길이가 짧을 경우 기본값을 사용할 수 있다. 

const array = [1, 2]
const [a = 10, b = 10, c = 10] = array

// a 1
// b 2
// c 10 (원래 undefined인데 10이 기본값으로 할당됨) </code></pre>
<p>기본값 할당시 주의할 점은, 오직 <code>undefined</code> 일때만 기본값이 적용된다는 점이다.</p>
<ul>
<li><code>null</code> / <code>0</code> / <code>&#39;&#39;</code> 일때는 기본값이 적용되지 않는다. </li>
</ul>
<pre><code class="language-js">// spread 연산자를 사용하여, 특정값 이후의 값을 다시금 배열로 선언 가능
const array = [1, 2, 3, 4, 5]
const [first, ...rest] = array

// first 1
// rest [2, 3, 4, 5]</code></pre>
<h4 id="배열-구조-분해-할당-코드가-바벨에서-어떻게-트랜스파일될까">배열 구조 분해 할당 코드가 바벨에서 어떻게 트랜스파일될까!</h4>
<pre><code class="language-js">// 트랜스파일하기 전
const array = [1, 2, 3, 4, 5]
const [first, second, third, ...arrayRest] = array

// 트랜스파일된 결과
var array = [1, 2, 3, 4, 5]
var first = array[0],
second = array[l],
third = array[2],
arrayRest = array.slice(3)</code></pre>
<hr>
<h4 id="객체-구조-분해-할당">객체 구조 분해 할당</h4>
<p>배열 구조 분해 할당과 마찬가지로, 구조를 분해해서 꺼내온다는 의미는 같다. 
다만, 객체에서 값을 꺼내온 뒤 할당하는데, 이때 <strong>객체는 객체 내부 이름으로 꺼내온다는 차이</strong>가 있다. (배열은 순서 기준으로 값을 꺼내온다)</p>
<pre><code class="language-js">// 전개 연산자도 사용 가능하며, 이름을 다시 할당할 수도 있다.
// 배열과 마찬가지로, 기본값을 설정하는 것도 가능하다. 
const object = {
a: undefined,
b: 2,
c: 3,
d: 4,
e: 5,
}

const { a:first = 10, b:second, c:third, ...objectRest } = object
// first 10
// second 2
// third 3
// objectRest = {d: 4, e: 5}</code></pre>
<p>객체 구조 분해할당의 경우, props 에서 값을 바로 꺼내올때 자주 사용되는 방식이기에 중요하다. </p>
<p>또한 단순히 값을 꺼내오는 것뿐만 아니라, 속성 이름 방식으로도 사용 가능하다
(변수 값으로 값 꺼내오기)</p>
<pre><code class="language-js">const key = &#39;a&#39;
const object = {
a: 1,
b: 1,
}

// 이때, [key]의 네이밍을 적용해주지 않으면 오류가 발생한다.
// 왜냐? -&gt; [key]로 값만 꺼냈지, 변수에 할당해주지 않았기 때문
const { [key]: a } = object
// a = 1
</code></pre>
<h4 id="객체-구조-분해-할당-코드가-바벨에서-어떻게-트랜스파일될까">객체 구조 분해 할당 코드가 바벨에서 어떻게 트랜스파일될까!</h4>
<p>배열 구조 분해 할당과는 달리, 트랜스파일 코드가 좀더 복잡하다. 
트랜스파일 과정에서는 두가지 함수를 사용하는데 내용을 아래와 같다.</p>
<p><code>_objectWithoutPropertiesLoose</code></p>
<ul>
<li>객체와 해당 객체에서 제외할 키가 포함된 배열 두가지를 인수로 받음<ul>
<li>이 두가지 값을 활용해서, 특정 키를 제외</li>
</ul>
</li>
</ul>
<p><code>objectWithoutProperties</code></p>
<ul>
<li>Object.getOwnPropertySymbols가 존재하는 환경인 경우를 대비</li>
</ul>
<p>객체 구조 분해 할당의 경우, <strong>트랜스파일을 거치면 번들링 크기가 상대적으로 크기 때문에</strong> 개발 환경에 따라서, <strong>ES5인 경우 객체 구조 분해 할당을 자주 쓰진 않는다면 꼭 써야 하는지 검토해볼 필요가 있다.</strong>
(... rest 와 같은 함수가 필요하다면 외부 라이브러리를 사용하는것도 고려해봄직 하다! <code>lodash.omit</code>  <code>rambda.omit</code>)</p>
<hr>
<h2 id="162-전개구문">1.6.2 전개구문</h2>
<p>전개 구문은 <strong>배열이나 객체, 문자열과 같이 순회할 수 있는 값에 대해 전개해서 간결하게 사용할 수 있는 구문</strong>이다.</p>
<ul>
<li>배열 전개 구문 -&gt; ES6(ECMA2015)</li>
<li>객체 전개 구문 -&gt; ECMA2018</li>
</ul>
<h4 id="배열-전개구문">배열 전개구문</h4>
<p>이전에는 배열 간의 합성을 위해서 push() concat() splice() 등의 메서드를 사용해야 했는데 전개구문을 이용하면 아주 간편하다.</p>
<pre><code class="language-js">const arr1 = [&#39;a&#39;, &#39;b&#39;]
const arr2 = arr1
arr1 === arr2 // true. 내용이 아닌 참조를 복사하기 때문 

const arr1 = [&#39;a&#39;, &#39;b&#39;]
const arr2 = [...arrl]
arr1 === arr2 // false. 실제로 값만 복사됐을 뿐</code></pre>
<h4 id="배열-전개-구문-트랜스파일-코드">배열 전개 구문 트랜스파일 코드</h4>
<pre><code class="language-js">// 트랜스파일하기 전
const arr1 = [&#39;a&#39;, &#39;b&#39;]
const arr2 = [...arr1, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;]

// 트랜스파일된 결과  (concat로 배열을 연결)
var arr1 = [&#39;a&#39;, &#39;b&#39;]
var arr2 = [].concat(arr1, [&#39;c&#39;, &#39;d&#39;, &#39;e&#39;])</code></pre>
<h4 id="객체-전개구문">객체 전개구문</h4>
<p>객체 전개 구문의 경우, 사용에 있어서 크게 다른 점은 없지만 
전개 구문 이후에 값 할당이 있다면, 그 내용이 덮어씌워질 수 있다.</p>
<p>또한, 객체 구조 분해 할당과 마찬가지로, 트랜스파일 코드가 굉장히 길다. 
단순히 값을 복사하는 배열과는 다르게, *<em>객체의 경우 객체 속성값 및 설명자 확인, 심벌체크 등 *</em>으로 인해 코드가 커지기에, 상대적으로 번들링 크기로 인해 사용에 있어 주의가 필요하다.</p>
<hr>
<h2 id="163-객체-초기자">1.6.3 객체 초기자</h2>
<p>객체 초기자란, 객체 선언시 객체에 넣고자 하는 키와 값을 가지고 있는 변수가 이미 존재한다면 해당 값을 간결하게 넣어줄 수 있는 방식</p>
<pre><code class="language-js">const a = 1
const b = 2

const obj = {
  a,
  b,
}
// {a: 1, b: 2}</code></pre>
<p><code>a: a</code> 와 같은 형식으로 작성하는 대신, 변수를 넣어주면 위와 같이 축약해서 선언하는 것이 가능하다. 트랜드파일 코드 역시 </p>
<hr>
<h2 id="164-array-프로토타입의-메서드-map-filter-reduce-foreach">1.6.4 Array 프로토타입의 메서드: map, filter, reduce, forEach</h2>
<p><code>map</code>, <code>filter</code>, <code>reduce</code>, <code>forEach</code> 는 ES5에서부터 사용한 문법으로, 별도의 트랜스파일이나 폴리필이 없어도 부담없이 사용할 수 있다는 특징이있다.</p>
<p><strong>1️⃣ Array.prototype.map</strong></p>
<pre><code class="language-jsx">const arr = [1, 2, 3, 4, 5]
const doubledArr = arr.map((iem) =&gt; iem * 2)
// [2, 4, 6, 8, 10]

// 리액트
const arr = [1, 2, 3, 4, 5]
const Elements = arr.map((iem) =&gt; {
return &lt;Fragment key={item}&gt;{item}&lt;/Fragment&gt;
})</code></pre>
<p>인수로 전달받은 배열과 똑같은 길이의 새로운 배열을 반환하는 메서드이다.
리액트에서도 특정 배열 기반, 리액트 요소를 반환하고자 할때 많이 사용된다.</p>
<p><strong>2️⃣ Array.prototype.filter</strong></p>
<pre><code class="language-jsx">const arr = [1, 2, 3, 4, 5]
const evenArr = arr.filter((item) =&gt; item % 2 === 0)
// [2, 4]</code></pre>
<p><strong>콜백함수에서 <code>truthy</code> 한 조건을 만족하는 경우에만, 원소를 반환하는 메소드</strong>이다. 참인 경우에만 필터링을 하기에, 기존 배열의 크기와 다른 크기를 가진 배열이 반환될 수도 있다. </p>
<p><strong>3️⃣ Array.prototype.reduce</strong></p>
<pre><code class="language-js">const arr = [1, 2, 3, 4, 5]
const sum = arr.reduce((result, item) =&gt; {
    return result + item
}, 0)
// 15</code></pre>
<p>쉽게 말해서 누적연산을 할때, 사용되는 메소드이다. 
메서드에서는 콜백 함수와 함께 초깃값을 추가로 인수를 받는데, 이 초깃값에 따라 배열이나 객체, 또는 그 외의 다른 무언가를 반환할 수 있다.</p>
<p><strong>4️⃣ Array.prototype.forEach</strong></p>
<pre><code class="language-js">const arr = [1, 2, 3]
    arr.forEach((item) =&gt; console.log(item))
// 1, 2, 3</code></pre>
<p>콜백 함수를 받아, <strong>배열을 순회하면서 단순히 콜백함수만을 실행</strong>하는 메서드이다. 
위에서 봤던 3개의 메서드와는 다르게, <strong>반환값이 없다는 점이 특징</strong>이다. 
또한, 반복문을 돌면서 중간에 *<em><code>break</code>나 <code>return</code> 을 해도 배열 순회를 멈출 수 없다. *</em></p>
<hr>
<h2 id="165-삼항-조건-연산자">1.6.5 삼항 조건 연산자</h2>
<p>자바스크립트에서 유일하게 3개의 피연산자를 취할 수 있는 문법이다. </p>
<p><code>조건문? 참일때 값 : 거짓일때 값</code> 의 형식으로 나타난다.</p>
<pre><code class="language-jsx">function Component({ condition }) {
    return &lt;&gt;{condition ? &#39;참, : &#39;거짓,}&lt;/&gt;
}</code></pre>
<p>종종 삼항 연산자를 중첩해서 사용하는 경우도 흔한데, 가독성면에서 가급적 지양하는 것이 좋다고 한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1.5 이벤트 루프와 비동기 통신 ]]></title>
            <link>https://velog.io/@gonn-i/1.5-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%86%B5%EC%8B%A0</link>
            <guid>https://velog.io/@gonn-i/1.5-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%86%B5%EC%8B%A0</guid>
            <pubDate>Mon, 10 Mar 2025 14:54:41 GMT</pubDate>
            <description><![CDATA[<h2 id="151-싱글-스레드-자바스크립트">1.5.1 싱글 스레드 자바스크립트</h2>
<p>자바스크립트는 싱글 스레드 언어이기에, 동기 방식으로 한번에 단 하나의 작업만을 처리한다. 이러한 처리는 직렬 방식이라고 하기도 하는데,
<strong>특정 요청이 시작된 이후에는 무조건 응답을 받은 이후에야 비로소 다음 작업을 처리할 수 있다.</strong></p>
<p>과거에는, 실행 단위가 오직 <strong><code>프로세스</code></strong> 뿐이었다. 
(프로그램을 구동해, 프로그램 상태가 메모리상에 실행되는 작업 단위 )
이때, 하나의 프로그램에는 - 오직 하나의 프로세스만이 할당되므로, 동시에 여러개의 복잡한 작업을 수행하기 어려웠다.</p>
<p>이에 따라, 더 작은 실행 단위인 <strong>스레드</strong>가 만들어졌고, 하나의 프로세스에서 여러개의 스레드를 만들어 스레드끼리 메모리를 공유하며 여러 작업을 동시에 수행할 수 있게 되었다. </p>
<p><strong>근데 자바스크립트는 왜 하필 싱글 스레드일까?</strong></p>
<blockquote>
<p><strong>자바스크립트의 시작은 비교적 간단한 작동 범위</strong>에서 &#39;간단한 경고창을 띄우거나, 버튼 위에 이미지를 띄우는 것&#39;과 같은 수행에 초점을 두었다. 
한편, 여러 스레드에서 DOM 을 조작한다면, <strong>메모리가 공유된채 동시에 같은 자원에 접근하게 되어 타이밍 이슈가 발생되었을 수도 있을 것</strong>이다. 
이러한 상황에 빗어대 보면, 자바스크립트를 싱글 스레드로 구현한 것은 굉장히 합리적인 결정이라 볼 수 있다.</p>
</blockquote>
<p><code>asynchronous</code> 비동기성 이라는 뜻은, <strong>동시에 일어나지 않는것을 의미</strong>하는데
동기식과 다르게 즉시 결과가 주어지지 않을수도 있고, 따라서 응답이 언제 올지도 알 수 없다.</p>
<p>그렇기 때문에 자바스크립트는 싱글 스레드 기반으로 동작하지만,  <strong>비동기 처리를 위해 브라우저(Web API)와 함께 이벤트 루프</strong>를 활용한다.</p>
<hr>
<h2 id="152-이벤트-루프란">1.5.2 이벤트 루프란</h2>
<p>이벤트 루프는 브라우저의 동작 타이밍을 관리하는 시스템이다.</p>
<ul>
<li>ECMAScript에 포함된 내용은 아니고, 런타임 환경에서 제공하는 기능인데, </li>
<li>하는 역할은 <strong>자바스크립트 엔진과 브라우저(Web API)가 협력하여 비동기 함수의 실행 시점을 조절</strong>한다고 보면된다. </li>
</ul>
<h4 id="🔗-호출-스택과-이벤트-루프">🔗 호출 스택과 이벤트 루프</h4>
<blockquote>
<p>🔹 주요 개념 정리</p>
<ul>
<li>*<em>콜 스택 *</em> <ul>
<li>자바스크립트에서 수행해야 할 코드나 함수를 <strong>순차적으로 담아둔 스택</strong></li>
<li>호출 되면 순차적으로 코드를 넣고, 실행이 끝나면 호출 스택에서 제거된다. </li>
</ul>
</li>
<li>*<em>이벤트 루프 *</em> <ul>
<li>콜 스택이 비었는지 확인하고, 실행할 작업(태스크 큐 또는 마이크로태스크 큐)이 있으면 가져오는 역할</li>
</ul>
</li>
<li>*<em>태스크 큐 *</em> <ul>
<li>setTimeout, setInterval 같은 비동기 작업이 실행을 기다리는 공간</li>
<li><strong>테스크 큐란, 실행해야 할 테스크의 집합</strong>을 의미한다. </li>
<li>이때, 호출 스택이 비었다면 선택된 큐 중에서 <strong>실행 가능한 가장 오래된 테스크</strong>를 &quot;이벤트 루프&quot;가 꺼내와 호출 스택에 넣는다</li>
</ul>
</li>
<li><strong>마이크로태스크 큐</strong> <ul>
<li>Promise.then(), MutationObserver 같은 높은 우선순위 작업이 대기하는 공간</li>
<li>테스크 큐보다 더 높은 우선권을 갖는다! (마이크로 테스크 큐가 빌 때까지 기존 테스크 큐는 대기)</li>
</ul>
</li>
</ul>
</blockquote>
<p>자바스크립트가 구동되는 환경인, Node.js와 브라우저에서는 여러 스레드가 사용되기 때문에, 이벤트 루프로 여러 스레드가 사용되는 환경과 - 자바스크립트 엔진을 연동한다</p>
<blockquote>
<p><strong>근데 싱글 스레드라 순차적으로 코드를 처리한다면, 비동기 함수는 누가 수행하는데?</strong>  </p>
</blockquote>
<p>이는 자바스크립트 코드가 동기식으로 실행되는 메인 스레드가 아닌 
<strong>테스크 큐가 할당하는 별도의 스레드에서 처리</strong>된다. 
그래서 <code>Web API</code> ||  <code>Browser API</code> 에서 비동기 로직이 실행되고, 그 콜백이 <code>테스크 큐</code>에 들어가는 것이다.</p>
<hr>
<p>예제 코드로 본 동작 과정을 아래와 같다. </p>
<pre><code class="language-js">function bar() {
  console.log(&quot;bar&quot;);
}

function baz() {
  console.log(&quot;baz&quot;);
}

function foo() {
  console.log(&quot;foo&quot;);
  setTimeout(bar, 0)
  baz();
}

foo();
</code></pre>
<blockquote>
<p>🔁 동작순서 
1️⃣ foo()가 <strong>호출 스택에 들어간다</strong>.</p>
</blockquote>
<ul>
<li>현재 스택: [ foo ]</li>
</ul>
<blockquote>
<p>2️⃣ console.log(&quot;foo&quot;)가 <strong>호출 스택에 들어간다</strong>.</p>
</blockquote>
<ul>
<li>현재 스택: [ foo, console.log ]</li>
<li>&quot;foo&quot; 출력 후 제거됨</li>
<li>현재 스택: [ foo ]</li>
</ul>
<blockquote>
<p>3️⃣ setTimeout(bar, 0) 실행</p>
</blockquote>
<ul>
<li>setTimeout 자체는 브라우저(Web API)에게 &quot;bar를 0ms 후 실행해줘&quot;라고 요청하는 역할만 함</li>
<li>setTimeout은 즉시 실행되고 호출 스택에서 제거됨</li>
<li>현재 스택: [ foo ]
(이때 bar는 <strong>태스크 큐(Task Queue)</strong>로 이동하지 않고, 브라우저 타이머가 먼저 담당)</li>
</ul>
<blockquote>
<p>4️⃣ baz()가 호출 스택에 들어간다.</p>
</blockquote>
<ul>
<li>현재 스택: [ foo, baz ]</li>
</ul>
<blockquote>
<p>5️⃣ console.log(&quot;baz&quot;)가 <strong>호출 스택에 들어간다.</strong></p>
</blockquote>
<ul>
<li>현재 스택: [ foo, baz, console.log ]</li>
<li>&quot;baz&quot; 출력 후 제거</li>
<li>현재 스택: [ foo, baz ]</li>
</ul>
<blockquote>
<p>6️⃣ baz() 실행이 끝났으므로 <strong>호출 스택에서 제거</strong>된다.</p>
</blockquote>
<ul>
<li>현재 스택: [ foo ]</li>
</ul>
<blockquote>
<p>7️⃣ foo() 실행이 끝났으므로 <strong>호출 스택에서 제거</strong>된다.</p>
</blockquote>
<ul>
<li>현재 스택: [] (완전히 비어 있음)</li>
</ul>
<blockquote>
<p>📌 <strong>이벤트 루프 시작</strong>
8️⃣ 이벤트 루프가 <strong>호출 스택이 비어 있는지 확인</strong>한다.</p>
</blockquote>
<ul>
<li>현재 스택: [] (비어 있음)</li>
<li><code>태스크 큐</code>를 확인했을 때, bar() 실행 대기 중</li>
</ul>
<blockquote>
<p>9️⃣ bar()가 <strong>호출 스택에 들어간다.</strong></p>
</blockquote>
<ul>
<li>현재 스택: [ bar ]</li>
</ul>
<blockquote>
<p>🔟 console.log(&quot;bar&quot;)가 <strong>호출 스택에 들어간다.</strong></p>
</blockquote>
<ul>
<li>현재 스택: [ bar, console.log ]</li>
<li>&quot;bar&quot; 출력 후 제거됨</li>
<li>현재 스택: [ bar ]</li>
</ul>
<blockquote>
<p>1️⃣1️⃣ bar() 실행이 끝났으므로 <strong>호출 스택에서 제거</strong>된다.</p>
</blockquote>
<ul>
<li>현재 스택: [] (완전히 비어 있음)</li>
</ul>
<hr>
<h3 id="153-태스크큐--마이크로태스트큐--동기-코드-랜더링-차이">1.5.3 태스크큐 / 마이크로태스트큐 / 동기 코드 랜더링 차이</h3>
<p><code>태스크 큐</code>는 setTimeout 같은 작업들이 들어가며, <strong>각 콜백이 실행될 때마다 DOM을 렌더링</strong>하므로 하나씩 화면에 보인다.
<code>마이크로태스크 큐</code>는 queueMicrotask, Promise 등에서 작업을 처리하며, **모든 작업을 끝낸 후 한 번만 렌더링이 이루어진다.</p>
<blockquote>
<p>[1] 태스크 큐</p>
</blockquote>
<pre><code class="language-js">// setTimeout으로 태스크 큐에 작업을 넣어서 1부터 렌더링
macrotask.addEventListenerCclick&#39;, function () {
    for (let i = 0; i &lt;= 100000; i++) {
        setTimeout(() =&gt; {
            macrotask.innerHTML = i
        }, 0)
    }
})</code></pre>
<p>각 <code>setTimeout</code> 콜백은 태스크 큐에 추가되어 이벤트 루프에 의해 <strong>순차적으로 처리</strong>되고, 각 콜백이 실행될 때마다 렌더링이 발생합니다.</p>
<blockquote>
<p>[2] 마이크로태스크 큐 </p>
</blockquote>
<pre><code class="language-js">// queueMicrotask로 마이크로 태스크 큐에 넣어서 1부터 렌더링
microtask.addEventListener(&#39;click’, function () {
    for (let i = 0; i &lt;= 100000; i++) {
        queueMicrotask(() =&gt; {
            microtask.innerHTML = i
    })
})</code></pre>
<p><code>마이크로태스크 큐</code> 코드는, 100,000까지 숫자가 올라가기 전까지는 렌더링이 일어나지 않다가 for 문이 끝나야 <strong>비로소 렌더링 기회</strong>를 얻으며 100,000이라는 숫자가 한 번에 나타난다.</p>
<blockquote>
<p>[3] 동기코드 </p>
</blockquote>
<pre><code class="language-js">// 동기 코드로 버튼에 1 부터 렌더링
sync.addEventListener(&#39;click&#39;, function () {
    for (let i = 0； i &lt;= 100000; i++) {
        sync.innerHTML = i
    }
})</code></pre>
<p><code>마이크로태스크 큐</code> 코드와 마찬가지로 렌더링이 전혀 일어나지 않다가 100,000까지 다 끝난 이후에야 <strong>한 번에 렌더링</strong>이 일어난다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS 클로저 ]]></title>
            <link>https://velog.io/@gonn-i/JS-%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@gonn-i/JS-%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Sat, 08 Mar 2025 12:23:49 GMT</pubDate>
            <description><![CDATA[<p>본 게시글은 모던 리액트 딥다이브를 보고 작성된 글입니다.</p>
<h1 id="14-클로저">1.4 클로저</h1>
<p><strong>리액트 클래스 컴포넌트</strong>는, 자바스크립트 클래스 / 프로토타입 / this와 관계있다면, <strong>리엑트 함수 컴포넌트</strong>는 <code>클로저</code>와 크게 관련이 있다. </p>
<h2 id="141-클로저란">1.4.1 클로저란?</h2>
<blockquote>
<p>클로저란? 
<strong><code>함수</code>와 <code>함수가 선언된 어휘적 환경</code>의 조합</strong> </p>
</blockquote>
<p>여기에서 <strong>선언된 어휘적 환경</strong> 이란, 변수나 함수가 코드 내부에서 어디에 선언되었는지를 말한다, 이 기준을 토대로 스코프가 설정된다. </p>
<ul>
<li><code>this</code>가 호출방식에 따라 <strong>동적으로 결정</strong>되는 반면, <code>선언된 어휘적 환경</code>은 <strong>선언된 순간 정적</strong>으로 결정된다.</li>
</ul>
<pre><code class="language-js">// add의 내부 함수로 innerAdd가 중첩, 이때 a의 유효범위는 add전체 
// b의 유효범위는 innerAdd, 내부 함수에서 a+b를 할 수 있는건 클로저 덕분
function add() {
  const a = 10;
  function innerAdd() {
    const b = 20;
    console.log(a + b);
  }

  innerAdd(); // 30
}

add();</code></pre>
<h2 id="142-변수의-유효-범위-스코프">1.4.2 변수의 유효 범위, 스코프</h2>
<h4 id="1-전역-스코프">(1) 전역 스코프</h4>
<p>전역 레벨에 선언된 변수로, 해당 스코프에서 <strong>변수 선언시 어디서든 호출</strong>할 수 있게 된다. 브라우저 환경에서 전역 객체는 <code>window</code>, Node.js 환경에서는 <code>global</code>이 있는데, 이 객체에 전역 레벨에서 선언한 스코프가 바인딩된다.</p>
<h4 id="2-함수-스코프와-블록-스코프">(2) 함수 스코프와 블록 스코프</h4>
<p>자바스크립트는 기본적으로 <strong>함수 레벨 스코프</strong>를 따른다고 한다. 특히, var 로 선언된 변수의 경우도 그렇데, 블록으로만 감싸줄 경우 그 밖에서도 접근 가능하다. </p>
<pre><code class="language-js">if(true) {
    var global = &#39;global scope&#39;
}
console.log(global); // &#39;global scope&#39;
console.log(global === window.global) // true</code></pre>
<p>만일 <code>{}</code> 와 같은, 블록에서가 아니라 함수 레벨에서 var로 변수를 선언했다면 <code>referenceError</code>가 나왔을 것이다. 다만, ES6 이후부터, 블록 레벨 스코프도 지원되면서,<code>let</code>과 <code>const</code>가 사용되고 있다.</p>
<h2 id="143-클로저의-활용">1.4.3 클로저의 활용</h2>
<pre><code class="language-js">function outerFunction() {
    var x = ’hello&#39;
    function innerFunction() {
        console.log(x)
    }
    return innerFunction
}

const innerFunction = outerFunction()
innerFunction() // &quot;hello&quot;</code></pre>
<p><code>outerFunction</code>은 <code>innerFunction</code>을 반환하며, 실행이 종료되었는데 
이때 반환한 함수에는 x라는 변수가 존재하지 않지만
<strong>함수가 선언된 어휘적 환경인, <code>outerFunction</code>에 x변수가 있기에</strong> hello를 출력할 수 있게 되는 것이다.</p>
<p>전역 스코프는 어디에서든지 값을 꺼낼 수도 있지만, 반대로는 누구든 접근 가능하고 수정도 가능하다. 이 경우, 클로저를 이용할 경우, 특정 변수에 접근할 수 있게 바꾸는 것 역시 가능하다. </p>
<h4 id="리액트에서-클로저">리액트에서 클로저</h4>
<p>그래서 리액트에서 클로저는 어떻게 사용될까? 
컴포넌트 훅에서 클로거가 사용되기도 하는데, 대표적인 예시가 바로 <code>useState</code>이다</p>
<pre><code class="language-js">function Component() {
    const [state, setstate] = useState()

  function handleClick() {
  // useState 호출은 위에서 끝남! 
  // ⭐️ 하지만 클로저 덕분에 setstate는 계속 내부의 최신값(prev)을 알고 있다 !!
      setState((prev) =&gt; prev + 1)
  }
}</code></pre>
<p>외부함수 <code>useState</code>가 반환한, 내부함수 <code>setState</code>는 외부 함수의 호출이 끝났음에도,** 자신이 선언된 외부 함수인 선언된 환경(state가 저장된 어딘가)을 기억하기 때문에 <code>state</code>값을 사용**할 수 있는 것 이다. </p>
<h3 id="144-주의할-점">1.4.4 주의할 점</h3>
<p>클로저는 생각보다 다루기 쉽지 않다. 그 예제가 바로 for문 내부에서 setTime 돌리는 경우인데 스코프에 따라서 <strong>예상치 못한 결과를 불러올 수도 있기 때문</strong>!
이다. </p>
<p><a href="https://gonnn-i.tistory.com/4">JS 클로저, for문 안에 setTime로 변수를 찍어보자 🕖</a></p>
<p>또한, 클로저가 생성될때마다 선언적 환경을 기억해야 하는 작업은, <strong>많은 비용을 야기</strong>하기 때문에 주의해야 한다. 
클로저의 기본 원리에 따라, <strong>클로저 선언 순간부터 내부 함수는 외부함수의 선언적인 환경을 기억하고 있어야 하므로, 사용하는 여부와 관계없이 저장</strong>해두기 때문이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1장 : 1.2 함수 / 1.3 클래스]]></title>
            <link>https://velog.io/@gonn-i/1%EC%9E%A5-1.2-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@gonn-i/1%EC%9E%A5-1.2-%ED%95%A8%EC%88%98</guid>
            <pubDate>Fri, 07 Mar 2025 13:56:08 GMT</pubDate>
            <description><![CDATA[<p>해당 게시글은 모던 리액트 딥다이브를 읽고, 작성한 정리글입니다. </p>
<hr>
<h2 id="121-함수란">1.2.1 함수란?</h2>
<pre><code class="language-js">// sum 은 함수명 
// a,b는 매개변수 
function sum(a, b) {
  // return 으로 작성된건 반환값
  return a + b
}

// 함수 호출과, 인수로 전달 되는 숫자들
sum(10, 24)</code></pre>
<p>함수란, 
작업을 수행하거나, 값을 계산하는 등의 과정 등을 
하나의 블록 단위로 감싸서 실행 단위로 만들어 둔 것이다. </p>
<p>리액트 함수도 이와 같은 형태를 따른다. </p>
<pre><code class="language-js">// Component 라는 함수를 선언하며,
// props 라는 단일 객체를 받고, 
function Component(props) {
  // JSX 를 반환한다.
    return &lt;div&gt;{props.hello}&lt;/div&gt;
}
</code></pre>
<h2 id="122-함수를-정의하는-4가지-방법">1.2.2 함수를 정의하는 4가지 방법</h2>
<h4 id="1-함수-선언문">(1) 함수 선언문</h4>
<pre><code class="language-js">// 함수 선언문의 일반적인 형식 
function sum(a, b) {
  return a + b
}</code></pre>
<p><strong>함수 선언문</strong>은 표현식이 아닌 ❌, 일반문으로 분류된다.
(이때 표현식이란 값을 산출하는 구문을 의미, 위에선 어떠한 값도 표현되지 않았기에 포함이 안되는 것이다)</p>
<p><code>const sum = function sum(a, b) { return a + b}</code></p>
<p>하지만 다음과 같이 표현식으로도 사용될 수 있기도 하다. </p>
<h4 id="2-함수-표현식">(2) 함수 표현식</h4>
<p>자바스크립트에서 함수는 일급객체에 해당한다.  </p>
<p><strong>자 그렇다면 일급객체가 뭘까?</strong></p>
<blockquote>
<p><strong>일급객체란?</strong>
다른 객체들에 일반적으로 적용가능한 <strong>연산(변수에 할당, 함수의 인자로 전달, 함수의 반환값으로 사용)을 모두 지원하는 객체</strong> </p>
</blockquote>
<p>위에서 <strong>변수에 객체를 할당하는 형태가 바로, 함수 표현식</strong>이다. 
보통, 위의 예제와 다르게 function 뒤에 할당하려는 함수의 이름을 생략하는 것이 일반적인데, 이는 실제 함수 호출을 위해 사용되지 않고 그저 식별자일뿐이기 때문이다. </p>
<p>*<em>그래서 둘이 뭐가 다를까요? *</em></p>
<p>변수에 할당을 했는가(표현식), 아닌가 (선언식) 의 차이는 
바로 <strong>호이스팅(hoisting)</strong>이다 </p>
<p>(호이스팅이란: 변수와 함수의 선언이, 실행 전에 메모리에 등록되는 현상
코드 실행 전에 선언이 끌어올려진 것처럼 동작하는 자바스크립트의 특징)</p>
<p><strong>함수선언식</strong>의 경우, 호이스팅으로 인해 선언 전에 호출하여도 정상적으로 작동한다. </p>
<p>하지만, <strong>함수표현식</strong>의 경우, 특히 var에 할당했다면 undefined로 초기화되어 함수로서 호출이 불가능하기에 오류가 난다. 
(let과 const는 TDZ로 인해 아예 접근이 불가하기에 오류가 난다. )</p>
<h4 id="3-function-생성자">(3) Function 생성자</h4>
<pre><code class="language-js">const add = new Function(&#39;a&#39;, ’b&#39;, &#39;return a + b&#39;)

add(10, 24) </code></pre>
<p>위와 같이 생성자로 함수를 만들 수 있는데, 작성 관점에서 가독성이 떨어지기에 실제 많이 사용되지 않는다고 한다. </p>
<h4 id="4-화살표-함수">(4) 화살표 함수</h4>
<pre><code class="language-js">const add = (a, b) =&gt; {
    return a + b
}

// const add = (a, b) =&gt; a + b</code></pre>
<p>ES6에서 추가된 함수이다. 특징으로는 
(1) constructor를 사용할 수 없고,
(2) 화살표 함수에는 arguments가 존재하지 않는다. </p>
<pre><code class="language-js">function hello() {
    console.log(arguments)
}

// Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
hello(1, 2, 3)

const hi = () =&gt; {
    console.log(arguments)
}
// Uncaught ReferenceError: arguments is not defined
hi(1, 2, 3)</code></pre>
<p>(3) 일반 함수와 화살표 함수는 <strong>this 바인딩에 있어서 차이</strong>를 보인다.</p>
<p>(this가 뭔데: 자신이 속한 객체나 자신이 생성할 인스턴스를 가리키는 값으로, <strong>함수 호출 방식에 따라서 동적으로 결정</strong>된다 )</p>
<p><code>일반 함수</code>의 경우, 호출된다면 그 내부의 <code>this</code>는 런타임 시점에서 결정되는 방식을 따르는데,
<code>화살표 함수</code>의 경우, 함수 자체의 바인딩을 따르지 않고 상위 스코프의 this를 그대로 따르게 된다. (선언 시점에서 this 결정 -&gt; 상위 스코프로 ) </p>
<p>그래서 단순 일반 함수를 축약한 형식이라고 보기엔 무리가 있다. </p>
<h2 id="123-다양한-함수-살펴보기">1.2.3 다양한 함수 살펴보기</h2>
<p>선언 방식 외에, 함수를 사용하는 방식에도 여러가지가 있다. </p>
<h4 id="1-즉시-실행함수">(1) 즉시 실행함수</h4>
<p>말 그대로 함수를 정의하고 그 순간 즉시 실행되는 함수인데, 
단 한번만 호출되고, 다시금 호출할 수 없는 함수이다. </p>
<pre><code class="language-js">(function (a, b) {
    return a + b
})(10, 24);

((a, b) =&gt; {
    return a + b
    },
)(10, 24) // 34</code></pre>
<h4 id="2-고차함수">(2) 고차함수</h4>
<p>앞선 언급에서 자바스크립트의 함수가 일급 객체라는 특징으로, 함수를 인수로 받거나 결과로 새로운 함수를 반환할 수 있다고 말했다. </p>
<p>이런 역할을 하는 함수를 <strong>고차함수</strong>라고 하기도 한다.</p>
<pre><code class="language-js">// 함수를 매개변수로 받는 대표적인 고차 함수, Array.prototype.map
const doubledArray = [1, 2, 3].map((iem) =&gt; iem * 2)
doubledArray // [2, 4, 6]


// 함수를 반환하는 고차 함수 
const add = function (a) {
    return function (b) {
    return a + b
    }
}
add(1)(3) </code></pre>
<h2 id="124-함수만들때-주의사항">1.2.4 함수만들때 주의사항</h2>
<blockquote>
<p>1️⃣ <strong>부수 효과를 최대한 억제하라</strong> 
    최대한 순수함수로 작동할 수 있도록 하되, 부수 효과를 피할 수 없다면 <code>useEffect</code>와 같이 그 작동을 최소화 하는 것이 좋다
 2️⃣ <strong>가능한 한 함수를 작게 만들어라</strong>
 명시적인 코드 라인수는 없겠지만, 최대한 하나의 책임을 맡게 하는 것이 좋다.
 3️⃣ <strong>누구나 이해할 수 있는 이름을 붙여라</strong> 
 네이밍은 최대한 간결하고 이해하기 쉽게 / <code>useEffect</code>나 <code>useCallback</code> 등 훅에 넘겨주는 콜백 함수에 네이밍 붙여주는 것도 가독성을 올리는 방법 중 하나</p>
</blockquote>
<h2 id="131-클래스란">1.3.1 클래스란?</h2>
<p>특정한 객체를 만들기 위한 일종의 템플릿
특히, 필요한 데이터나 이를 조작하는 코드를 추상화해 객체 생성을 더욱 편리하게 할 수 있다는 점에서 특징이다. </p>
<pre><code class="language-js">// 클래스를 상속 받을땐 extends를 사용한다.
// class Car extends vehicle {
class Car {
// constructor: 생성자다. 
// 최초에 생성할 때 어떤 인수를 받을지 결정하며 객체를 초기화하는 용도로도 사용된다. 
// (단, 단 하나만 존재할 수 있다, 혹은 필요없을 경우 생략도 가능하다) 
constructor(name) {
  // name은 프로퍼티로 인스턴스 생성시, 내부에서 정의할 수 있는 속성값이다.
    this.name = name
}

// 인스턴스 메서드 : 실제 prototype에 선언되므로 프로토타입 메소드라고 불리기도 한다. (프로토타입이란 모든 인스턴스가 공유하는 객체)
honk() {
    console.log(&#39;${thi .name}이 경적을 울립니다!&#39;)
}

// 정적 메서드 : 인스턴스가 아닌, 클래스 이름으로 호출할 수 있는 메서드
// 정적 메서드 내부의 this는 클래스 자기 자신을 가리킨다.  
static hello() {
    console.log(&#39;저는 자동차입니다&#39;)
}

// setter : 클래스 필드에 값을 할당할때 사용된다. 
set age(value) {
    this.carAge = value
}

// getter : 무언가 값을 가져올때 사용된다.
get age() {
    return this.carAge
}</code></pre>
<p>직접 객체에 선언하지 않았음에도, 프로토타입에 있는 메서드를 찾아 실행을 돕는 것을 <strong>프로토타입 체이닝</strong>이라고 한다. 
모든 객체는 프로토타입을 갖는데, 특정 속성을 찾을때까지
<code>자기 자신부터</code> -&gt; (프로토타입을 타고) -&gt; <code>최상위 객체인 Object</code> 까지 훑는다. </p>
<p>(<code>toString</code> 메서드도 객체 어디에서도 선언하는 경우가 없지만, <strong>프로토타입 체이닝</strong>을 거쳐 <code>Object</code>에 있는 <code>toString</code>을 찾아간다. )</p>
<h2 id="132-클래스와-함수의-관계">1.3.2 클래스와 함수의 관계</h2>
<p>클래스는 ES6부터 도입된 개념인데, 이전에는 프로토타입을 통해 클래스의 작동 방식을 유사하게 구현했다.</p>
<p>만일 ES6 미만의 환경에서는,  <code>_createClass</code> 라는 헬퍼 함수를 만들어 클래스와 동일하게 동작할 수 있도록 변경한다.</p>
<p>클래스 작동을 생성자 함수로 매우 유사하게 재현할 수 있는데, 이처럼 클래스는 자바스크립트의 문법적 설탕 역할을 한다고 볼 수 있으며, 프로토타입을 기반으로 작동한다는 사실도 확인 할 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 동등 비교]]></title>
            <link>https://velog.io/@gonn-i/1%EC%9E%A5-1.1-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%8F%99%EB%93%B1-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@gonn-i/1%EC%9E%A5-1.1-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%8F%99%EB%93%B1-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Fri, 07 Mar 2025 11:39:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/gonn-i/post/e466009c-aa5d-4737-8ef9-7bf9d2a7a095/image.png" alt=""></p>
<p>해당 게시글은 모던 리액트 딥다이브를 읽고, 작성한 정리글입니다. </p>
<h2 id="11-자바스크립트의-동등-비교">1.1 자바스크립트의 동등 비교</h2>
<p>리액트 컴포넌트 랜더링이 일어나는 이유 중 하나가, <code>props의 동등 비교</code>에 따른 결과이다. 
이 <strong>props의 동등 비교는 객체의 얕은 비교를 기반</strong>으로 이뤄지기 때문에 중요하다. </p>
<p>(1) 리액트의 가상 DOM과 실제 DOM 비교, 
(2) 리액트 컴포넌트가 랜더링할지 판단하는 방법, 
(3) 변수나 함수의 메모이제이션 등 거의 모든 작업이 동등 비교를 기반으로 이루어진다.</p>
<h3 id="111-자바스크립트의-데이터-타입">1.1.1 자바스크립트의 데이터 타입</h3>
<p><code>JS</code> 에서 타입은 크게 원시타입과 객체타입으로 나눠지며, 두 타입을 기준으로 하위엔 다음과 같이 나눠볼 수 있다. </p>
<ul>
<li><p>원시 타입(primitive type) : 객체가 아닌 모든 타입</p>
<ul>
<li><p><code>undefined</code></p>
<ul>
<li>선언 이후에 임의의 값을 할당하지 않는 경우, 자동으로 <code>undefined</code> 가 할당된다. 즉, 선언되었지만 할당되지 않은 값을 의미</li>
</ul>
</li>
<li><p><code>null</code></p>
<ul>
<li>아직 값이 없어거나 비어있는 경우로, <strong>명시적으로 비어있음을 나타내는 값을 의미</strong><ul>
<li>typeof 에 null 타입이 아닌 object로 반환된다고 한다 (이후 null 반환 시도가 이뤄졌으나 호환성 문제로 실패)</li>
</ul>
</li>
</ul>
</li>
<li><p><code>boolean</code></p>
<ul>
<li><p>falsy 로 표현되는 값 </p>
<table>
<thead>
<tr>
<th>값</th>
<th>타입</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>false</code></td>
<td>Boolean</td>
<td><code>false</code>는 대표적인 falsy한 값</td>
</tr>
<tr>
<td><code>0</code>, <code>-0</code>, <code>0n</code>, <code>0x0n</code></td>
<td>Number, BigInt</td>
<td><code>0</code>은 부호나 소수점 유무에 상관없이 falsy</td>
</tr>
<tr>
<td><code>NaN</code></td>
<td>Number</td>
<td><code>NaN</code>(Not a Number)은 falsy한 값</td>
</tr>
<tr>
<td><code>&quot;&quot;</code> / <code>&#39;&#39;</code> / ``</td>
<td>String</td>
<td>문자열이 falsy하기 위해서는 반드시 공백이 없는 빈 문자열이어야 함</td>
</tr>
<tr>
<td><code>null</code></td>
<td>null</td>
<td><code>null</code>은 falsy한 값</td>
</tr>
<tr>
<td><code>undefined</code></td>
<td>undefined</td>
<td><code>undefined</code>는 falsy한 값</td>
</tr>
</tbody></table>
<ul>
<li>truthy 로 표현되는 값 : falsy가 아니면 참으로 판명. 특히, 객체와 배열은 내부 값의 존재 여부과 관계업이 모두 <code>truthy</code> (<code>[]</code> <code>{}</code>)</li>
</ul>
</li>
</ul>
</li>
<li><p><code>number</code></p>
<ul>
<li>다른 언어들과는 다르게, 정수와 실수를 구분하지 않는다. <pre><code class="language-js">// ECMAScript 표준 기준 표현가능했던 범위 (bigInt 등장전)
const maxinteger = Math.pow(2, 53)
maxinteger - 1 === Number.MAX_SAFE_INTEGER // true
const mininteger = -(Math.pow(2, 53) - 1)
mininteger === Number.MIN_SAFE_INTEGER // true</code></pre>
</li>
</ul>
</li>
<li><p><code>bigint</code>    </p>
<ul>
<li><p>number 형의 제한을 극복하기 위해 등장한, 타입</p>
<pre><code class="language-js">// 사용할땐, 숫자 끝에 n을 붙여주거나 BigInt 함수를 이용한다.
const number = 9007199254740992
const bigint = 9007199254740992n

typeof number // number
typeof bigint // bigint
number == bigint // true
number === bigint // false(타입이 달라서)</code></pre>
</li>
</ul>
</li>
<li><p><code>string</code></p>
<ul>
<li>한쌍의 작은 따옴표(<code>&#39;</code>), 혹은 큰 따옴표(<code>&quot;</code>), 또는 템플릿 리터럴 표현방식인 백틱 (`) 으로 표현된다. 
(템플릿 리터럴: 줄바꿈이 가능하고, 문자열 내부에 표현식을 쓸 수 있음)<ul>
<li>문자열은 원시타입이라, 변경 불가! (b라는 변수에 foo 넣어두고 b[0]=&#39;H&#39; 이런식으로 변경할 수 없음)</li>
</ul>
</li>
</ul>
</li>
<li><p><code>symbol</code></p>
<ul>
<li>중복되지 않는 어떠한 고유값을 나타내기 위해서 만들어진 타입! </li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>객체 타입(object/reference type)</p>
<ul>
<li><code>object</code> (배열 / 함수 / 정규식 / 클래스 )<ul>
<li>객체 타입은 참조를 전달한다는 점이 특징!</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="112-값을-저장하는-방식의-차이">1.1.2 값을 저장하는 방식의 차이</h3>
<h4 id="원시타입과-객체타입의-큰-차이점---값을-저장하는-방식">원시타입과, 객체타입의 큰 차이점 -&gt; <strong>값을 저장하는 방식</strong></h4>
<p><code>원시타입</code>은 <strong>불변형태의 값으로 저장</strong>, 그래서 할당 시점에 메모리영역을 차지함 </p>
<p><code>객체타입</code>은 <strong>참조를 저장하고 있기에</strong>, 변경 가능한 형태로 저장됨 </p>
<pre><code class="language-js">// 내부 형태, 값이 같아도 객체는 참조를 저장하기 때문에 두 함수는 동등 비교시, false! 
var hello = {
  greet: &#39;hello, world&#39;,
};

var hi = {
  greet: &#39;hello, world&#39;,
};

// 객체 간의 동등 비교는 false
console.log(hello === hi); // false

// 원시값인 내부 속성값은 같음을 알 수 있다.
console.log(hello.greet === hi.greet); // true
</code></pre>
<h3 id="113-다른-비교-공식-objectis">1.1.3 다른 비교 공식, <code>Object.is</code></h3>
<blockquote>
<p><code>==</code> 과 <code>Object.is</code> 차이</p>
</blockquote>
<p><code>==</code> 비교는 강제로 형변환을 한후에, 타입이 다를경우 둘을 맞춰주고 비교가 이루어지는 반면, <code>Object.is</code> 는 <code>===</code> 와 같이 이러한 작업을 하지 않음 (타입이 다르면, 그냥 false)</p>
<blockquote>
<p><code>===</code> 과 <code>Object.is</code> 차이</p>
</blockquote>
<p><code>Object.is</code>는 <code>===</code> 와 달리, <code>+0</code>, <code>-0</code>을 구분한다.
<code>NaN</code>과 <code>Number.NaN</code> 도 마찬가지 이며, 
<code>NaN</code>와 <code>0/0</code> 도 구분하는 식의, 정밀 비교가 가능하다 
(ES6에서 동등 비교 <code>===</code> 한계를 극복하기 위해 도입됨)</p>
<p><strong>하지만 객체 비교에는 큰 차이가 없다!</strong></p>
<h4 id="114-리액트에서의-동등비교">1.1.4 리액트에서의 동등비교</h4>
<p>리액트에서 사용되는 동등비교는 <code>Object.is</code>를 사용한다. 구형 브라우저의 경우 (ES6 미지원) <code>polyfill</code>을 사용한다고 한다. 
(폴리필 코드: 최신 JavaScript 기능이 구형 브라우저에서 동작하지 않을 때, 이를 대신 구현하여 제공하는 코드)</p>
<pre><code class="language-js">// 폴리필 코드
function is(x: any, y: any) {
  return (
    // x === y 두 값이 완전히 같으면 기본적으로 true
    // x === 0인 경우 1 / x === 1 / y로 +0과 -0을 구별
    // NaN !== NaN이 항상 true이라 x와 y가 둘 다 NaN이라면 true를 반환
    (x === y &amp;&amp; (x !== 0 || 1 / x === 1 / y)) || (x !== x &amp;&amp; y !== y)
  );
}

const objectIs: (x: any, y: any) =&gt; boolean =
  typeof Object.is === &#39;function&#39; ? Object.is : is;
</code></pre>
<p>리액트에서는 <code>Object.is</code>로 먼저 비교를 진행한 후에, 객체 간 얕은 비교를 한번 더 수행한다. 
이때, 객체 간 얕은 비교란 <strong>객체의 첫번째 깊이에 존재하는 값만 비교하는 방법</strong>이다.</p>
<p>깊은 비교는 객체 내부의 모든 속성을 재귀적으로 확인해야 하지만, 이는 리소스가 많이 드는 일이다. 따라서, 객체에서 첫번째 <code>deps</code>의 키를 꺼내서, 비교하고자 하는 객체에 같은 키가 있는지, 그리고 그 값이 같은 지를 확인한다.</p>
<blockquote>
<p>리액트는 객체 비교에 있어서 불안정성이 있기에, 위의 비교 방법은 리액트 개발자로서 알아두면 좋을 것 이다!! 뒤에서 나올, useMemo나 useCallback의 필요성을 활용하기 위해 잘 알아두자! </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 완벽가이드] Section 24: React Query]]></title>
            <link>https://velog.io/@gonn-i/Section-24-React-Query</link>
            <guid>https://velog.io/@gonn-i/Section-24-React-Query</guid>
            <pubDate>Thu, 05 Sep 2024 06:30:41 GMT</pubDate>
            <description><![CDATA[<h2 id="react-query-뭐고-왜-쓰지">React Query 뭐고 왜 쓰지</h2>
<blockquote>
<p>서버 <strong>상태 패칭, 캐싱, 동기화, 업데이트 작업</strong>을 하주 쉽게 해줍니다
<a href="https://tanstack.com/query/latest/docs/framework/react/overview">React Query 공식문서</a></p>
</blockquote>
<p><code>react query</code>가 <code>tanstack query</code>로 이름이 바뀐 만큼 
(사유 : 범용적인(vue나 svelte 에도 적용) 비동기 상태 관리 라이브러리로 발전), 
제목만 <code>react query</code>로 써두고 이하의 것들은<code>tanstack query</code> 라고 지칭하겠다 !!</p>
<p>데이터를 가져오거나, 데이터 상태에 따라서 뭐 다시 리패칭하고, 요청에 대한 상태를 체크하는 건 이미 가능하긴 하다.</p>
<h3 id="그래요-원래도-그건-useeffect나-fetch-로-커버-가능한데요">그래요 원래도 그건 useEffect나 fetch 로 커버 가능한데요?</h3>
<p><strong>라고 말한다면, 라이브러리의 출현에는 다 이유가 있는 법.</strong>
(그리고 <code>useEffect</code>는 사실 side Effect 이기 때문에, 패칭이 여러개라면 코드 흐름 파악이 어려울 수 있고, 관리도 어려움 + 성능면에서도 좋지 않다고 알고 있음 )</p>
<p><code>tanstack query</code> 를 이용한다면 적은 코드로 효율화가 가능하며, 속도 향상이 가능하다. 또한 캐싱 처리도 쉽다고 할 수 있다. 이점은 다음과 같이 정리할 수 있다. 👇</p>
<h3 id="tanstack-query로-로딩상태-성공실패-캐싱-리패칭-stale-상태-관리를-자동으로-처리">tanstack Query로 로딩상태, 성공/실패, 캐싱, 리패칭, stale 상태 관리를 자동으로 처리</h3>
<ul>
<li><code>tanstack Query</code> 을 통해 얻을 수 있는 이점 </li>
</ul>
<blockquote>
<p><strong>💬 캐싱
 💬 동일한 데이터에 대해, 중복 요청 제거</strong>  (-&gt; 같은 쿼리키로 묶인 경우)
 💬 <strong>백그라운드에서 &#39;stale(오래된)&#39; 데이터를 업데이트
 💬 &#39;stale(오래된)&#39; 시점을 설정 가능 
 💬 페이지네이션 &amp; 무한 스크롤링 쉽게 구현 가능 
 💬 구조적 공유를 통해 쿼리 결과 메모</strong> 
 (-&gt; 참조 비교를 통해 불필요한 리랜더링 방지하거나 변경되지 않은 부분을 새롭게 할당하지 않고 유지하여 메모리 사용 최적화)
... 등등 </p>
</blockquote>
<hr>
<h2 id="그럼-어떻게-사용하나요">그럼 어떻게 사용하나요</h2>
<h3 id="초기세팅-및-기본-설정">초기세팅 및 기본 설정</h3>
<h4 id="1️⃣-패키지-설치">1️⃣ 패키지 설치</h4>
<pre><code class="language-shell">npm i @tanstack/react-query</code></pre>
<h4 id="2️⃣-최상위-컴포넌트-provider로-감싸주기">2️⃣ 최상위 컴포넌트 Provider로 감싸주기</h4>
<p><strong><code>https.js</code></strong> 
queryClient 선언 하기 </p>
<pre><code class="language-jsx">import { QueryClient } from &#39;@tanstack/react-query&#39;;

export const queryClient = new QueryClient();

// ... 여타 요청 코드 생략 </code></pre>
<p><strong><code>app.js</code></strong></p>
<pre><code class="language-jsx">import { QueryClientProvider } from &#39;@tanstack/react-query&#39;;
import { queryClient } from &#39;./utils/http.js&#39;;

// 라우터 부분 생략 ...

function App() {
  return (
    // Provide the client to your App
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;RouterProvider router={router} /&gt;
    &lt;/QueryClientProvider&gt;
  )
}</code></pre>
<p>여기에서 <code>queryClient</code>를 굳이 <code>app.js</code>에서 선언하지 않고 다른곳 (<code>https.js</code>) 에서 선언하고 export 하는 이유가 궁금했다. 그래서 gpt 에게 질문하니 <strong>역할 분리</strong>와 <strong>의존성 및 중복 코드 제거</strong>를 위해서 라는 답변을 받았다.  (역시,, 마법이 소라챗둥.. )</p>
<p>(그래서 http에 선언하는 이유)</p>
<ul>
<li><p><strong><code>queryClient</code>는 요청과 관련한 상태 관리 객체</strong>이다. <code>app.js</code>은 <strong>주로 라우팅이나 전체적인 레이아웃을 담당</strong>하는 반면, <code>http.js</code> 는 <strong>요청과 관련한 로직을 관리하기 때문에 한곳에서 서버와의 요청과 관련된 모든 로직을 관리</strong>하게 된다. 따라서, 관심사의 분리(Separation of Concerns) 원칙에 맞는 구조!!</p>
</li>
<li><p>또한, 여러 컴포넌트에서 <code>queryClient</code>를 사용할 수가 있는데 그때마다 <code>app.js</code>에 대한 의존성이 생기고, 불필요한 중복 코드가 늘어난다. 그래서 app 에서 선언해서 export 안 한다. (app.js를 임포트해서 쓰는 경우는 거의 없으니? html에서 가져다 쓰는거 말고는 )</p>
</li>
</ul>
<h3 id="이제-진짜-사용해보자-데이터-패칭하기-get">이제 진짜 사용해보자! 데이터 패칭하기 (GET)</h3>
<h4 id="여기서-잠깐-tanstack-query-없이-그동안-우리가-해왔던-방법">여기서 잠깐 tanstack Query 없이, 그동안 우리가 해왔던 방법</h4>
<pre><code class="language-js">  const [data, setData] = useState(null); // 이벤트 데이터를 저장할 상태
  const [isLoading, setIsLoading] = useState(true); // 로딩 상태를 저장
  const [isError, setIsError] = useState(false); // 에러 상태를 저장
  const [error, setError] = useState(null); // 에러 메시지 상태

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      setIsLoading(true); 
      setIsError(false); 

      try {
        const result = await fetchEvents({ max: 3 }); 
        setData(result); 
      } catch (err) {
          setIsError(true);
          setError(err);
        }
      } finally {
        setIsLoading(false); 
      }
    };

    fetchData(); 
  }, []); 
</code></pre>
<p>여러개의 상태관리와, 상태를 변경하는 작업이 이루어짐을 알 수 있다.</p>
<h4 id="이제-tanstack-query-사용하여-데이터-받아보자">이제 tanstack Query 사용하여 데이터 받아보자!</h4>
<pre><code class="language-jsx">  const { data, isPending, isError, error } = useQuery({
    queryKey: [&#39;events&#39;, { max: 3 }],
    queryFn: ({ signal, queryKey }) =&gt; fetchEvents({ signal, ...queryKey[1] }),
    staleTime: 5000, 
  });</code></pre>
<p>사실상 코드라인 수의 눈에 띄는 차이도 보이며! <code>useEffect</code> 를 사용하지 않았기 때문에 코드의 흐름 파악이 쉬워졌다. </p>
<h4 id="하나씩-뜯어봅시다-usequery">하나씩 뜯어봅시다 useQuery!</h4>
<pre><code class="language-jsx">  // tanstack query가 하는 일
    // 1) 요청을 관리하는 로직을 제공 (비동기 요청을 관리하고, 상태를 추적)
    // 2) 발생 가능한 오류를 추적하는 역할
    // 3) 데이터를 캐싱하여 중복된 요청을 방지 + 캐싱된 데이터를 최신 상태로 유지하면서 성능을 최적화
    // 단! useQuery는 프로미스를 반환하는 함수를 필요로 함
  const { data, isPending, isError, error } = useQuery({
    queryKey: [&#39;events&#39;, { max: 3 }], // queryKey를 통해서 react Query에 데이터를 캐싱,  + 중복된 요청을 막음
    //`queryKey`는 배열 형태로 첫 번째 요소는 주로 쿼리 이름을 의미하고, 두 번째 요소는 추가적인 매개변수를 담을 수 있음
    queryFn: ({ signal, queryKey }) =&gt; fetchEvents({ signal, ...queryKey[1] }),
    //`queryFn`은 데이터를 패칭하는 함수 
    // `signal`은 요청 취소를 관리하기 위한 AbortController의 신호
    staleTime: 5000, // 새로운 요청을 다시 보낼 시간 : staleTime은 캐싱된 데이터가 최신 상태로 유지되는 기간을 설정 (해당 ms동안 새로운 패칭을 시도하지 않고, 기존에 캐싱죈 데이터를 사용) / 디폴트는 0임
    gcTime: 30000, // 캐싱된 데이터 보관 시간 : (default : 5m = 300000ms) -&gt; 다른 컴포넌트로 이동해서 해당 쿼리가 더이상 필요해지지 않을때 설정 시간이 지나면 메모리에서 삭제
  });</code></pre>
<p>여타 옵션과 리턴값들 보기 🚪
<a href="https://tanstack.com/query/latest/docs/framework/react/reference/useQuery">useQuery 관련 문서</a></p>
<h3 id="데이터-업데이트-post-put-delete">데이터 업데이트 (POST, PUT, DELETE)</h3>
<h4 id="tanstack-query로-post-요청-보내보기">tanstack Query로 POST 요청 보내보기!</h4>
<pre><code class="language-jsx">  const {
    mutate,
    isPending: isEditPending,
    isError: isEditError,
  } = useMutation({
    mutationFn: updateEvent,
    onMutate: async (data) =&gt; {
      const newEvent = data.event;
      // 즉시 새로운 요청을 만들어내어 데이터 갱신 (낙관적 업데이트)
      await queryClient.cancelQueries({ queryKey: [&#39;events&#39;, id] }); // 진행 중인 쿼리 취소 후 (useQuery로 트리거된 쿼리만을 취소)
      const previousEvent = queryClient.getQueryData([&#39;events&#39;, id]); // 백엔드단에서의 오류에 대비하여 이전 쿼리값 저장
      queryClient.setQueryData([&#39;events&#39;, id], newEvent); // 특정 쿼리 (첫번쨰 인자)에 자체 데이터 (두번째 인자)를 설정

      return {
        previousEvent,
      }; // 이전 쿼리 값을 리턴함으로써 context로 접근 가능케함
    },
    // 롤백 프로세스
    onError: (error, data, context) =&gt; {
      queryClient.setQueryData([&#39;events&#39;, id], context.previousEvent);
    },
    // 수정 요청의 성공 여부와 관계없이 시행 (백엔드와 프엔간의 데이터 동기화를 위해 쿼리 무효화하여 갱신)
    onSettled: () =&gt; {
      queryClient.invalidateQueries([&#39;events&#39;, id]);
    },
  });

  function handleSubmit(formData) {
    mutate({ id, event: formData });
    navigate(&#39;../&#39;);
  }</code></pre>
<p>단, post나 delete put 은 초기 랜더링시 req를 날리는게 아니라, 버튼을 눌러서 되는 경우이기 때문에 위의 예시와 같이 이벤트핸들러에 전달할 함수에 mutate 함수를 작성해준다.</p>
<p><a href="https://tanstack.com/query/latest/docs/framework/react/reference/useMutation">useMutation</a></p>
<p>근데 이제 데이터의 변동이 생기면, 이제 요청을 받아올때에도 새로운 데이터를 받아 갱신 작업을 해주어야 하는데 이때 staleTime 만을 이용할때는 새로고침(재랜더링)이 이루어져야만 캐싱 데이터가 변경된다. 이럴때 우리가 써야 할 것은 <code>queryClient.invalidateQueries</code> 이다!</p>
<h3 id="staletime과-queryclientinvalidatequeries-차이">staleTime과 queryClient.invalidateQueries 차이</h3>
<p><strong><code>stale (구식)</code> 상태가 될때</strong>, tanstack Query 에서는 서버에서 필요한 순간에 <code>새로운 req</code> 를 보냄으로써 최신의 데이터를 가지도록 한다.  </p>
<p>(시간이 땡땡땡 다 되었다고 요청 다시 보내는거 아니란 말, 필요한 순간이라 함은  컴포넌트 마운트나 페이지 랜더링관 같은 상황 )</p>
<h4 id="시간을-기준으로-자동으로-데이터를-관리해주는-staletime">시간을 기준으로 자동으로 데이터를 관리해주는 staleTime</h4>
<blockquote>
<p>staleTime은 데이터를 캐싱할때에, 데이터의 유효 신선 (fresh) 시간을 설정한다. 
(<strong>일정 시간 지나야만</strong> 필요에 따라 새로운 요청을 보내고 캐시데이터 갱신)</p>
</blockquote>
<p>이때, 이 시간이 지나기 전까지 데이터는 <strong>최신한(Fresh) 상태</strong>로 여겨지며, 서버에 <strong>새로 데이터를 요청하지 않으고</strong> <strong>캐시된 데이터</strong>를 반환한다.</p>
<p>반면, 설정한 시간이 지나면 데이터는 <strong>신선하지 못한 상태 (stale) 라고 간주</strong>되어, 다시 데이터를 요청되는 상황에 (페이지가 다시 열리기거나, 컴포넌트가 마운트 되거나 하는 ..~ ) <strong>새로운 요청을 보내</strong> 새롭게 캐시한다.</p>
<p>결과적으로는 fresh 한 시간을 보장받기 때문에, 불필요한 요청을 없앰 </p>
<h4 id="수동으로-특정-쿼리를-갱신-queryclientinvalidatequeries">수동으로 특정 쿼리를 갱신 queryClient.invalidateQueries</h4>
<blockquote>
<p>필요한 순간에 (새로운 이벤트 추가 or 삭제시) 특정 쿼리를 <code>stale</code> 한 상태로 만들어서, 해당 쿼리를 무효한다. 이후 그 쿼리가 요청될때 강제로 데이터를 최신화한다. 
<strong>(특정 시점에 상태를 수동으로 관리하여, 최신화)</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 9465 스티커]]></title>
            <link>https://velog.io/@gonn-i/%EB%B0%B1%EC%A4%80-9465-%EC%8A%A4%ED%8B%B0%EC%BB%A4</link>
            <guid>https://velog.io/@gonn-i/%EB%B0%B1%EC%A4%80-9465-%EC%8A%A4%ED%8B%B0%EC%BB%A4</guid>
            <pubDate>Thu, 01 Aug 2024 08:39:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/gonn-i/post/85fa9f73-0882-4c69-8546-d384b7379a9f/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/9465">문제링크</a></p>
<h2 id="접근-방향-설명">접근 방향 설명</h2>
<blockquote>
<p><strong>DP는 규칙을 얼마나 잘 발견해서, 메모리에 어떤식으로 저장하는지가 제일 중요한 것 같다</strong></p>
</blockquote>
<blockquote>
<p>각 라인마다 할 수 있는 동작을 생각하는게 중요했던거 같다.
<strong>[경우의 수]</strong>
1️⃣ 윗줄 스티커를 뜯는 경우</p>
</blockquote>
<pre><code class="language-js">// (이전에 아랫줄 스티커 뜯 + 윗줄 스티커 || 이전에 쓰루 + 윗줄 스티커 ) 중에 최대값
    dp[i][0] = Math.max(dp[i - 1][1] + line1[i - 1], dp[i - 1][2] + line1[i - 1]);</code></pre>
<blockquote>
<p>2️⃣ 아랫줄 스티커를 뜯는 경우</p>
</blockquote>
<pre><code class="language-js">// (이전에 윗줄 스티커 뜯 + 아랫줄 스티커 || 이전에 쓰루 + 아랫줄 스티커 ) 중에 최대값
    dp[i][1] = Math.max(dp[i - 1][0] + line2[i - 1], dp[i - 1][2] + line2[i - 1]);</code></pre>
<blockquote>
<p>3️⃣ 이번 스티커를 뜯지 않고 쓰루 하는 경우</p>
</blockquote>
<pre><code class="language-js">// (이전에 윗줄 스티커 뜯   || 이전에 아랫줄 스티커 뜯 ) 중에 최대값
    dp[i][2] = Math.max(dp[i - 1][0], dp[i - 1][1]);</code></pre>
<blockquote>
<p>⭐️ <strong>누적합을 저장했으니, dp에서 마지막 idx 요소 중에 최대값 꺼내기</strong></p>
</blockquote>
<hr>
<pre><code class="language-js">let fs = require(&#39;fs&#39;);
let filePath = process.platform === &#39;linux&#39; ? &#39;/dev/stdin&#39; : &#39;/input.txt&#39;;
let input = fs
  .readFileSync(__dirname + filePath)
  .toString()
  .trim()
  .split(&#39;\n&#39;);

let T = +input.shift();
let ans = &#39;&#39;;

while (T &gt; 0) {
  let n = +input.shift();
  let line1 = input.shift().split(&#39; &#39;).map(Number);
  let line2 = input.shift().split(&#39; &#39;).map(Number);

  let dp = [...new Array(n + 1)].map((_) =&gt; []);

  dp[1][0] = line1[0];
  dp[1][1] = line2[0];
  dp[1][2] = 0;

  for (let i = 2; i &lt;= n; i++) {
    dp[i][0] = Math.max(dp[i - 1][1] + line1[i - 1], dp[i - 1][2] + line1[i - 1]); // 윗줄 스티커를 뜯는 경우
    dp[i][1] = Math.max(dp[i - 1][0] + line2[i - 1], dp[i - 1][2] + line2[i - 1]); // 아랫줄 스티커를 뜯는 경우
    dp[i][2] = Math.max(dp[i - 1][0], dp[i - 1][1]); // 스티커 안 뜯고 through , so + 0
  }

  ans += `${Math.max(...dp[n])}\n`;
  T--;
}

console.log(ans);</code></pre>
<hr>
<h2 id="풀이-과정에서-새롭게-느낀점배운점">풀이 과정에서 새롭게 느낀점(배운점)</h2>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/e112b56e-d0f3-49ad-affe-f79447045928/image.png" alt=""></p>
<p>백준 골드 달성 기념, 흠흠 플레까지 노려보자구요 👀 (<del>마음에도 없는 소리</del>)</p>
<hr>
<h2 id="문제">문제</h2>
<p>상근이의 여동생 상냥이는 문방구에서 스티커 2n개를 구매했다. 스티커는 그림 (a)와 같이 2행 n열로 배치되어 있다. 상냥이는 스티커를 이용해 책상을 꾸미려고 한다.</p>
<p>상냥이가 구매한 스티커의 품질은 매우 좋지 않다. 스티커 한 장을 떼면, 그 스티커와 변을 공유하는 스티커는 모두 찢어져서 사용할 수 없게 된다. 즉, 뗀 스티커의 왼쪽, 오른쪽, 위, 아래에 있는 스티커는 사용할 수 없게 된다.</p>
<p>모든 스티커를 붙일 수 없게된 상냥이는 각 스티커에 점수를 매기고, 점수의 합이 최대가 되게 스티커를 떼어내려고 한다. 먼저, 그림 (b)와 같이 각 스티커에 점수를 매겼다. 상냥이가 뗄 수 있는 스티커의 점수의 최댓값을 구하는 프로그램을 작성하시오. 즉, 2n개의 스티커 중에서 점수의 합이 최대가 되면서 서로 변을 공유 하지 않는 스티커 집합을 구해야 한다.</p>
<p>위의 그림의 경우에 점수가 50, 50, 100, 60인 스티커를 고르면, 점수는 260이 되고 이 것이 최대 점수이다. 가장 높은 점수를 가지는 두 스티커 (100과 70)은 변을 공유하기 때문에, 동시에 뗄 수 없다.</p>
<h2 id="입력">입력</h2>
<p>첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스의 첫째 줄에는 n (1 ≤ n ≤ 100,000)이 주어진다. 다음 두 줄에는 n개의 정수가 주어지며, 각 정수는 그 위치에 해당하는 스티커의 점수이다. 연속하는 두 정수 사이에는 빈 칸이 하나 있다. 점수는 0보다 크거나 같고, 100보다 작거나 같은 정수이다. </p>
<h2 id="출력">출력</h2>
<p>각 테스트 케이스 마다, 2n개의 스티커 중에서 두 변을 공유하지 않는 스티커 점수의 최댓값을 출력한다.</p>
<h2 id="예제-입력-1">예제 입력 1</h2>
<p>2
5
50 10 100 20 40
30 50 70 10 60
7
10 30 10 50 100 20 40
20 40 30 50 60 20 80</p>
<h2 id="예제-출력-1">예제 출력 1</h2>
<p>260
290</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 7576 토마토 ]]></title>
            <link>https://velog.io/@gonn-i/%EB%B0%B1%EC%A4%80-7576-%ED%86%A0%EB%A7%88%ED%86%A0</link>
            <guid>https://velog.io/@gonn-i/%EB%B0%B1%EC%A4%80-7576-%ED%86%A0%EB%A7%88%ED%86%A0</guid>
            <pubDate>Fri, 19 Jul 2024 16:34:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/gonn-i/post/6287f57b-c521-4686-b077-bb8ad8a9e791/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/7576">🍅문제링크</a></p>
<h2 id="접근-방향-설명">접근 방향 설명</h2>
<blockquote>
<ol>
<li>토마토 밭을 초기화 후, 1 (익은 토마토)의 <strong>위치 마킹</strong>하기, 이떄 토마토의 위치와 더불어 몇번째로 익혀진 토마토인지를 나타내는 n (=0)을 함께 넣어준다. (원래 익어있기 때문에 일단 0)</li>
<li>익은 토마토 <strong>상하좌우로 아직 익지 않은 토마토 (0)가 존재하는 경우를 BFS 로 탐색하여, 탐색해야할 배열 안에 넣어준다.</strong> </li>
<li>이때 needToVisite 에 넣을때는, 옆에 있어서 <strong>익어지게 했던 토마토의 n에 +1 을 한 값을 함께 넣어준다.</strong></li>
<li>BFS 탐색을 마친 후, 익지 않는 토마토가 있는지 확인하여 있다면 -1을 찍고 return 한다</li>
<li>만약 모든 토마토가 다 읽었다면 마지막으로 저장된 n을 담은 ans 를 출력한다.</li>
</ol>
</blockquote>
<h2 id="풀이-코드-해석">풀이 코드 해석</h2>
<pre><code class="language-java">let fs = require(&#39;fs&#39;);
let filePath = process.platform === &#39;linux&#39; ? &#39;/dev/stdin&#39; : __dirname + &#39;/input.txt&#39;;
let input = fs.readFileSync(filePath).toString().trim().split(&#39;\n&#39;);

const [M, N] = input.shift().split(&#39; &#39;).map(Number);

let tomato = [];
let needToVisit = [];
let ans = 0;
let moves = [
  [-1, 0],
  [1, 0],
  [0, -1],
  [0, 1],
];

// 토마토 상태 초기화
for (let i = 0; i &lt; N; i++) {
  tomato[i] = input[i].split(&#39; &#39;).map(Number);
}

// 익은 토마토의 위치를 큐에 추가
for (let i = 0; i &lt; N; i++) {
  for (let j = 0; j &lt; M; j++) {
    if (tomato[i][j] === 1) {
      needToVisit.push([i, j, 0]);
    }
  }
}

let front = 0;
// BFS
while (front &lt; needToVisit.length) {
  let [x, y, n] = needToVisit[front++]; // shif 대신 인덱스로 뽑아내기
  ans = n;
  for (let [dx, dy] of moves) {
    let next_x = x + dx;
    let next_y = y + dy;
    if (next_x &gt;= 0 &amp;&amp; next_x &lt; N &amp;&amp; next_y &gt;= 0 &amp;&amp; next_y &lt; M &amp;&amp; tomato[next_x][next_y] === 0) {
      needToVisit.push([next_x, next_y, n + 1]);
      tomato[next_x][next_y] = 1;
    }
  }
}

// 익지 않은 토마토가 있는지 확인
for (let i = 0; i &lt; N; i++) {
  if (tomato[i].includes(0)) {
    console.log(-1);
    return;
  }
}

console.log(ans);</code></pre>
<h2 id="풀이-과정에서-새롭게-느낀점배운점">풀이 과정에서 새롭게 느낀점(배운점)</h2>
<p> 2 ≤ M,N ≤ 1,000 이었기 떄문에, 시간 초과를 피할 수 없었다.. 
 처음에 shift()로 bfs 탐색을 돌렸는데, 그게 화근이었다</p>
<p> <strong>대신 인덱스 조정하여 탐색을 하니 해결 완이 되었다!</strong>
 <strong>shift 를 아무 생각없이 사용하면 안되겠다는 교훈을 얻은 문제!</strong></p>
<pre><code class="language-js">// BFS
while (front &lt; needToVisit.length) {
  let [x, y, n] = needToVisit[front++]; // shif 대신 인덱스로 뽑아내기
  ans = n;
  for (let [dx, dy] of moves) {
    let next_x = x + dx;
    let next_y = y + dy;
    if (next_x &gt;= 0 &amp;&amp; next_x &lt; N &amp;&amp; next_y &gt;= 0 &amp;&amp; next_y &lt; M &amp;&amp; tomato[next_x][next_y] === 0) {
      needToVisit.push([next_x, next_y, n + 1]);
      tomato[next_x][next_y] = 1;
    }
  }
}</code></pre>
<hr>
<h2 id="문제">문제</h2>
<p>철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자 모양 상자의 칸에 하나씩 넣어서 창고에 보관한다.</p>
<p>창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다. 보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다. 하나의 토마토의 인접한 곳은 왼쪽, 오른쪽, 앞, 뒤 네 방향에 있는 토마토를 의미한다. 대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다. 철수는 창고에 보관된 토마토들이 며칠이 지나면 다 익게 되는지, 그 최소 일수를 알고 싶어 한다.</p>
<p>토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.</p>
<h2 id="입력">입력</h2>
<p>첫 줄에는 상자의 크기를 나타내는 두 정수 M,N이 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M,N ≤ 1,000 이다. 둘째 줄부터는 하나의 상자에 저장된 토마토들의 정보가 주어진다. 즉, 둘째 줄부터 N개의 줄에는 상자에 담긴 토마토의 정보가 주어진다. 하나의 줄에는 상자 가로줄에 들어있는 토마토의 상태가 M개의 정수로 주어진다. 정수 1은 익은 토마토, 정수 0은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않은 칸을 나타낸다.</p>
<p>토마토가 하나 이상 있는 경우만 입력으로 주어진다.</p>
<h2 id="출력">출력</h2>
<p>여러분은 토마토가 모두 익을 때까지의 최소 날짜를 출력해야 한다. 만약, 저장될 때부터 모든 토마토가 익어있는 상태이면 0을 출력해야 하고, 토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다.</p>
<h2 id="예제-입력-1">예제 입력 1</h2>
<p>6 4
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 1</p>
<h2 id="예제-출력-1">예제 출력 1</h2>
<p>8</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 2467 용액]]></title>
            <link>https://velog.io/@gonn-i/%EB%B0%B1%EC%A4%80-2467-%EC%9A%A9%EC%95%A1</link>
            <guid>https://velog.io/@gonn-i/%EB%B0%B1%EC%A4%80-2467-%EC%9A%A9%EC%95%A1</guid>
            <pubDate>Fri, 19 Jul 2024 16:33:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/gonn-i/post/fad5591f-c3ad-4cc0-b130-0e5c07b8bf0d/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/2467">문제링크</a></p>
<h2 id="접근-방향-설명">접근 방향 설명</h2>
<blockquote>
<p>1) start = 0, end = nums.length -1 로 초기설정 &amp; 두 용액의 정보와 합을 <code>closer()</code> 에 전달
2) closer 함수에서는 두 용액의 합이, 이전의 용액 합보다 0에 더 가까울 경우 <strong>combine 배열에 <code>start Idx</code> 와 <code>end Idx</code>, 두용액의 합을 모두 넣어준다</strong>. 그리고 나서, <strong>두 용액의 합이 양수이면 true 아니면 false 를 반환</strong>
3) 두 용액의 합이 <strong>양수일 경우 <code>end Idx</code> 에 -1</strong> 을 해주고, <strong>음수이면 <code>start Idx</code> 에 +1</strong> 을 해주며 <strong>이분탐색을 돌린다.</strong></p>
</blockquote>
<h2 id="풀이-코드-해석">풀이 코드 해석</h2>
<pre><code class="language-java">let fs = require(&#39;fs&#39;);
let filePath = process.platform == &#39;linux&#39; ? &#39;/dev/stdin&#39; : &#39;/input.txt&#39;;
let input = fs
  .readFileSync(__dirname + filePath)
  .toString()
  .trim()
  .split(&#39;\n&#39;);

const N = +input.shift();
let nums = [];
nums = input[0].split(&#39; &#39;).map(Number);
let combine = [];

nums.sort((a, b) =&gt; a - b);
start = 0;
end = nums.length - 1;

while (start &lt; end) {
  mid = nums[end] + nums[start];

  if (closer(start, end, mid)) {
    end -= 1; // 두용액의 합이 양수일 경우, end idx - 1 
  } else {
    start += 1; // 두용액의 합이 음수일 경우, end idx + 1 
  }
}

// 두 용액의 합이 0과 제일 가까운지 판별 &amp; 두 용액의 합의 부호 반환 
function closer(s, e, m) {
  // 배열 초기 세팅
  if (combine.length == 0) {
    combine.push([s, e, Math.abs(m)]);
  } else { 
    // 새로운 용액의 합의 절대값이 이전보다 작을 경우
    if (combine[0][2] &gt; Math.abs(m)) { 
      combine.pop();
      combine.push([s, e, Math.abs(m)]);
    }
  }
  // 두 용액의 합이 양수인 경우 true, 아니면 false 
  return m &gt; 0;
}


let [s, e, m] = combine[0];
console.log(nums[s], nums[e]);</code></pre>
<hr>
<h2 id="풀이-과정에서-새롭게-느낀점배운점">풀이 과정에서 새롭게 느낀점(배운점)</h2>
<p>이분탐색에서 두 용액의 합이 0일 경우, break 해서 while을 탈출 시켰는데,, 
start, end 값을 넣어주지도 않고 끝내버리는 꼴이 되어버렸다. (뻘짓해놓고 왜 틀렸지 고민하는 바보..)</p>
<p>다시 천천히 식을 돌려보니 틀린 부분을 알 수 있었다..! 오히려 좋아.. ~ 실수해서 또 배웠다 
안 풀리면 천천히 돌려보자!!! ⭐️🔥</p>
<hr>
<h2 id="용액">용액</h2>
<p>시간 제한    메모리 제한    제출    정답    맞힌 사람    정답 비율
1 초    128 MB    40370    15355    11859    36.957%</p>
<h2 id="문제">문제</h2>
<p>KOI 부설 과학연구소에서는 많은 종류의 산성 용액과 알칼리성 용액을 보유하고 있다. 각 용액에는 그 용액의 특성을 나타내는 하나의 정수가 주어져있다. 산성 용액의 특성값은 1부터 1,000,000,000까지의 양의 정수로 나타내고, 알칼리성 용액의 특성값은 -1부터 -1,000,000,000까지의 음의 정수로 나타낸다.</p>
<p>같은 양의 두 용액을 혼합한 용액의 특성값은 혼합에 사용된 각 용액의 특성값의 합으로 정의한다. 이 연구소에서는 같은 양의 두 용액을 혼합하여 특성값이 0에 가장 가까운 용액을 만들려고 한다. </p>
<p>예를 들어, 주어진 용액들의 특성값이 [-99, -2, -1, 4, 98]인 경우에는 특성값이 -99인 용액과 특성값이 98인 용액을 혼합하면 특성값이 -1인 용액을 만들 수 있고, 이 용액의 특성값이 0에 가장 가까운 용액이다. 참고로, 두 종류의 알칼리성 용액만으로나 혹은 두 종류의 산성 용액만으로 특성값이 0에 가장 가까운 혼합 용액을 만드는 경우도 존재할 수 있다.</p>
<p>산성 용액과 알칼리성 용액의 특성값이 정렬된 순서로 주어졌을 때, 이 중 두 개의 서로 다른 용액을 혼합하여 특성값이 0에 가장 가까운 용액을 만들어내는 두 용액을 찾는 프로그램을 작성하시오.</p>
<h2 id="입력">입력</h2>
<p>첫째 줄에는 전체 용액의 수 N이 입력된다. N은 2 이상 100,000 이하의 정수이다. 둘째 줄에는 용액의 특성값을 나타내는 N개의 정수가 빈칸을 사이에 두고 오름차순으로 입력되며, 이 수들은 모두 -1,000,000,000 이상 1,000,000,000 이하이다. N개의 용액들의 특성값은 모두 서로 다르고, 산성 용액만으로나 알칼리성 용액만으로 입력이 주어지는 경우도 있을 수 있다.</p>
<h2 id="출력">출력</h2>
<p>첫째 줄에 특성값이 0에 가장 가까운 용액을 만들어내는 두 용액의 특성값을 출력한다. 출력해야 하는 두 용액은 특성값의 오름차순으로 출력한다. 특성값이 0에 가장 가까운 용액을 만들어내는 경우가 두 개 이상일 경우에는 그 중 아무것이나 하나를 출력한다.</p>
<h2 id="예제-입력-1">예제 입력 1</h2>
<p>5
-99 -2 -1 4 98</p>
<h2 id="예제-출력-1">예제 출력 1</h2>
<p>-99 98</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 완벽가이드] Section 22: 인증 ]]></title>
            <link>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD%EA%B0%80%EC%9D%B4%EB%93%9C-Section-22-%EC%9D%B8%EC%A6%9D</link>
            <guid>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD%EA%B0%80%EC%9D%B4%EB%93%9C-Section-22-%EC%9D%B8%EC%A6%9D</guid>
            <pubDate>Wed, 17 Jul 2024 16:24:31 GMT</pubDate>
            <description><![CDATA[<p>본 포스트는 <a href="https://www.udemy.com/course/best-react">Udemy 리액트 완벽가이드 2024</a> 를 듣고 정리한 내용입니다.</p>
<blockquote>
<p><strong>목차 🌳</strong>
1️⃣ 리액트 앱에서 유저의 권한을 부여하는 방법
2️⃣ 로그인 후, 백엔드로부터 받은 res에서 토큰 찾아 유저의 권한저장 및  유지 🔍
3️⃣ 유저의 권한 부여 이후, 백엔드에 req 보내기 📤
4️⃣ 유저의 권한 회수 및 로그아웃 방법 📭</p>
</blockquote>
<hr>
<h2 id="리액트-앱에서-유저의-권한을-부여하는-방법">리액트 앱에서 유저의 권한을 부여하는 방법</h2>
<h3 id="인증이란-무엇인가">인증이란 무엇인가?</h3>
<blockquote>
<p><strong>인증(권한 부여)</strong>를 통해,필요한 데이터를 보호하기 위해 사용
(내가 올린 글만을 수정하고 삭제, .. )
대개, 보호된 리소스에 접근하려는 미래의 요청에 <strong>서버 측의 세션이나, 인증 토큰을 사용</strong></p>
</blockquote>
<p>1️⃣ <strong>server-side session</strong>
서버에서 뿌려준 <strong>식별자를 저장해, 이를 클라이언트로 보내주는 방법</strong>으로, 세션을 이용하기 위해서는 프론트와 백의 긴밀한 연결이 선행되어야 한다, (그치만 react 는 분리된 백엔드 api를 주로 사용하기 때문에 토큰 사용이 권장됨)</p>
<p>2️⃣ <strong>Authentiation token</strong>
토큰을 이용할 경우, <strong>서버로 부터 생성된 인증 토큰을 (저장하지 않고) 클라이언트로 전송한다</strong>
이후, 클라이언트에서 토큰을 req에 첨부해서 (<code>header</code>에 포함) 보내면 서버가 토큰의 유효성을 검사한다.</p>
<p>이전에 사용했던 이벤트 등록 사이트에 로그인과 회원가입 기능, 유저의 권한에 따른 삭제와 수정, 로그아웃을 구현해보며 권한에 따른 UI 변경과 http 요청을 알아보자!</p>
<hr>
<h2 id="로그인-후-백엔드로부터-받은-res에서-토큰-찾아-유저의-권한저장-및--유지-🔍">로그인 후, 백엔드로부터 받은 res에서 토큰 찾아 유저의 권한저장 및  유지 🔍</h2>
<p>로그인에서 <code>post</code> 로 <code>req</code> 를 보낸 이후, <strong>받은 <code>res</code> 에서 <code>token</code> 을 추출해 이를 브라우저단에 저장</strong>한다! 보통 <code>locastorage storage</code> 나 <code>session storage</code> 등등을 사용하는데 취향에 따라 골라잡으면 될 듯하다. (물론 스토리지에 대한 선택은 브라우저를 뭘 쓰는냐에 따라 특정 웹 스토리지를 지원하지 않을 수도 있고, 데이터 지속성에도 차이가 있어 상황에 따라서도 더 생각해보자!)</p>
<p>영상에서는 <code>localstorage</code> 를 사용했고 토큰을 웹 단에서 저장하는 방법은 다음과 같았다. </p>
<p><strong><code>Authentication.js</code></strong></p>
<pre><code class="language-js">  // res 에 있는 토큰을 추출하여 사용 -&gt; 보통 localstorage에 저장
  const resData = await res.json();
  const token = resData.token;
  localStorage.setItem(&#39;token&#39;, token);</code></pre>
<p>또한, <code>JWT</code>를 통한 토큰 생성에 있어서, <strong>토큰에는 만료시간이 존재</strong>하는데 이에 따라 <strong>클라이언트쪽에서도 <code>localstorage</code>에 저장한 토큰을 제거해줘야 한다!</strong> 그래서 생성된 토큰에 대한 만료시간을 같이 저장하면서 권한을 업데이트 해준다. </p>
<p><strong><code>Authentication.js</code></strong></p>
<pre><code class="language-js">  // 현재 시간을 추출한다.
  const expiration = new Date();
  // 현재 시간 + 1시간을 더한 값을 계산한다.
  expiration.setHours(expiration.getHours() + 1);
  // 날짜 객체를 ISO 8601 형식의 문자열로 변환 -&gt; localstorage 에 저장하기
  localStorage.setItem(&#39;expiration&#39;, expiration.toISOString());</code></pre>
<p>이렇게 클라이언트 측에서 보관한 정보들은 <strong><code>util</code> 폴더안에서 그 값들을 꺼내보며 토큰이 존재하는지, 토큰이 만료되지는 않았는지를 판별한다.</strong></p>
<p><strong>utils -&gt; auth.js</strong></p>
<pre><code class="language-js">export function getAuthToken() {
  const token = localStorage.getItem(&#39;token&#39;);

  //토큰이 없는 경우,
  if (!token) {
    return;
  }
  const tokenDuration = getTokenDuration();

  // 토큰이 만료된 경우 
  if (tokenDuration &lt; 0) {
    return &#39;EXPIRED&#39;;
  }
  // 토큰이 존재하는 경우
  return token;
}

export function getTokenDuration() {
  // localstorage에 저장된 만료 시간을 꺼내어, 만료시간 - 현재시간을 뺀 값을 리턴
  const storedExpirationDate = localStorage.getItem(&#39;expiration&#39;);
  const expiration = new Date(storedExpirationDate);
  const now = new Date();
  const duration = expiration.getTime() - now.getTime();
  return duration;
}</code></pre>
<p>또한 여기에<code>loader</code>로 걸어줄 함수들도 선언해준다!</p>
<pre><code class="language-js">// loader로 걸어줄 토큰 추출 함수
export function tokenLoader() {
  return getAuthToken();
}

// token 이 있어야만 특정 페이지에 도달할 수 있도록 판별
export function checkAuthLoader() {
  const token = getAuthToken();

  if (!token) {
    return redirect(&#39;/auth&#39;);
  }

  return null;
}</code></pre>
<p><code>router</code>에 권한이 필요한 곳에 <strong><code>checkAuthLoader</code> 을 걸어주어, <code>URL</code> 을 통한 유입을 막고,</strong> 
<strong><code>getAuthToken</code> 을 통해 <code>token</code> 값의 유무에 따라 <code>Nav</code> 에서 노출되어야 할 정보를 조절하면 된다</strong>  </p>
<p><strong><code>router</code> 에 <code>loader</code> 어떻게 걸어주는지 잘보고 익히기</strong> 👀
<strong><code>app.js</code></strong></p>
<pre><code class="language-js">// 라우트 보호 
const router = createBrowserRouter([
  {
    path: &#39;/&#39;,
    element: &lt;RootLayout /&gt;,
    errorElement: &lt;ErrorPage /&gt;,
    id: &#39;root&#39;,
    loader: tokenLoader, ⭐️
    children: [
      { index: true, element: &lt;HomePage /&gt; },
      {
        path: &#39;events&#39;,
        element: &lt;EventsRootLayout /&gt;,
        children: [
          {
            index: true,
            element: &lt;EventsPage /&gt;,
            loader: eventsLoader,
          },
          {
            path: &#39;:eventId&#39;,
            id: &#39;event-detail&#39;,
            loader: eventDetailLoader,
            children: [
              {
                index: true,
                element: &lt;EventDetailPage /&gt;,
                action: deleteEventAction,
              },
              {
                path: &#39;edit&#39;,
                element: &lt;EditEventPage /&gt;,
                action: manipulateEventAction,
                loader: checkAuthLoader,  ⭐️
              },
            ],
          },
          {
            path: &#39;new&#39;,
            element: &lt;NewEventPage /&gt;,
            action: manipulateEventAction,
            loader: checkAuthLoader,  ⭐️
          },
        ],
      },
    // ... 생략 
    ],
  },
]);</code></pre>
<p><strong><code>MainNav.js</code></strong></p>
<pre><code class="language-jsx">import { Form, NavLink, useRouteLoaderData } from &#39;react-router-dom&#39;;
// 생략 ...

function MainNavigation() {
  // root 에 걸어둔 loader 함수의 반환값 가져오기 
  const token = useRouteLoaderData(&#39;root&#39;);

  return (
    &lt;header className={classes.header}&gt;
      &lt;nav&gt;
        &lt;ul className={classes.list}&gt;
          // 생략 ...
          {!token &amp;&amp; (
            &lt;li&gt;
              &lt;NavLink to=&quot;/auth?mode=login&quot; className={({ isActive }) =&gt; (isActive ? classes.active : undefined)}&gt;
                auth
              &lt;/NavLink&gt;
            &lt;/li&gt;
          )}
          {token &amp;&amp; (
            &lt;li&gt;
              &lt;Form action=&quot;/logout&quot; method=&quot;post&quot;&gt;
                &lt;button&gt;Logout&lt;/button&gt;
              &lt;/Form&gt;
            &lt;/li&gt;
          )}
        &lt;/ul&gt;
      &lt;/nav&gt;
    &lt;/header&gt;
  );
}

export default MainNavigation;
</code></pre>
<hr>
<h2 id="유저의-권한-부여-이후-백엔드에-req-보내기-📤">유저의 권한 부여 이후, 백엔드에 req 보내기 📤</h2>
<p>백엔드에서 토큰의 유효성을 검사하고 특정 api를 실행할 수 있도록 설계했다면, 클라이언트쪽에서는 
<strong>req를 보낼때, <code>header</code> 에 Authentication 값으로 <code>Bearer + token</code> 값을 함께 넣어주어야 한다!</strong> (localstorage 에 저장한 값을 빼서, 같이 req로 날려버려 ~)</p>
<p><strong>req 보내는 형식 예시</strong></p>
<pre><code class="language-jsx">// 📁 utils -&gt; auth.js 에서 만들어놓은 함수에서 token 값 반환 받기
  const token = getAuthToken();

  // 생략...
  const response = await fetch(url, {
    method: method,
    headers: {
      &#39;Content-Type&#39;: &#39;application/json&#39;,
      Authorization: &#39;Bearer &#39; + token, // ⭐️
    },
    body: JSON.stringify(eventData),
  });</code></pre>
<hr>
<h2 id="유저의-권한-회수-및-로그아웃-방법-📭">유저의 권한 회수 및 로그아웃 방법 📭</h2>
<p>유저는 2가지 경우에 권한을 잃게 된다.
<strong>1️⃣ 로그아웃 버튼 누르기</strong> 
<strong>2️⃣ 시간 만료로 인한, 토큰 만료</strong> </p>
<p>하나씩 처리 방법을 알아보자 </p>
<blockquote>
<p><strong>1️⃣ 로그아웃 버튼 누르기</strong> </p>
</blockquote>
<p>1) <strong><code>action</code>에서 <code>localstorage</code> 에 저장한 토큰정보와 만료시간을 제거</strong>하고, root 페이지로 리다이렉트를 건다</p>
<pre><code class="language-jsx">//components -&gt; Logout.js
import { redirect } from &#39;react-router-dom&#39;;

export function action() {
  localStorage.removeItem(&#39;token&#39;);
  localStorage.removeItem(&#39;expiration&#39;);
  return redirect(&#39;/&#39;);
}</code></pre>
<p>2) <code>router</code> 에 위에서 설정한 <code>action</code> 걸어두기 </p>
<pre><code class="language-jsx">// app.js
{
  path: &#39;/logout&#39;,
    action: logoutAction,
},</code></pre>
<p>3) 클릭하면 걸어둔 <strong>action 함수가 호출되어 로그아웃</strong>이 이루어지도록 설정</p>
<pre><code class="language-jsx">// MainNav.jsx
{token &amp;&amp; (
  &lt;li&gt;
    &lt;Form action=&quot;/logout&quot; method=&quot;post&quot;&gt;
      &lt;button&gt;Logout&lt;/button&gt;
    &lt;/Form&gt;
  &lt;/li&gt;
)}</code></pre>
<blockquote>
<p><strong>2️⃣ 시간 만료로 인한, 토큰 만료</strong> </p>
</blockquote>
<p><code>root page</code> 에서 <code>token</code> 값을 통해, <strong>자동 로그아웃 처리</strong></p>
<pre><code class="language-jsx">function RootLayout() {
  const token = useLoaderData();
  const submit = useSubmit();

  useEffect(() =&gt; {
    if (!token) {
      return;
    }

    // 토큰이 만료된 경우, 로그아웃 처리
    if (token === &#39;EXPIRED&#39;) {
      submit(null, { action: &#39;/logout&#39;, method: &#39;post&#39; });
    }

    // 토큰 만료시간에 따라, 타이머 on 설정 후 시간 도달시 자동 로그아웃
    const tokenDuration = getTokenDuration();
    setTimeout(() =&gt; {
      submit(null, { action: &#39;/logout&#39;, method: &#39;post&#39; });
    }, tokenDuration);
  }, [token, submit]);

  return (
    &lt;&gt;
        // ... 생략
    &lt;/&gt;
  );
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 완벽 가이드] Section 21: Routing _SPA 다중 페이지 구축]]></title>
            <link>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-21-Routing-SPA-%EB%8B%A4%EC%A4%91-%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-21-Routing-SPA-%EB%8B%A4%EC%A4%91-%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Sat, 13 Jul 2024 15:36:10 GMT</pubDate>
            <description><![CDATA[<p>본 포스트는 <a href="https://www.udemy.com/course/best-react">Udemy 리액트 완벽가이드 2024</a> 를 듣고 정리한 내용입니다.</p>
<blockquote>
<p>목차 🌳
1️⃣ Route와 SPA 🤨 
2️⃣ react-Router 사용하기 📁
3️⃣ router를 이용한 data Fetching과 submite 📥📤</p>
</blockquote>
<h2 id="route와-spa-🤨">Route와 SPA 🤨</h2>
<p><code>Routing</code> 은 네트워크에서 경로를 선택하는 프로세스를 의미
즉, 주소에 대한 요청이 들어왔을때 상응하는 content 로 이동시켜주는 작업 </p>
<p><code>Route</code>란!
<strong><code>URL</code> 경로에 따라 다른 콘텐츠</strong>를 보여주기 위해 설정된 규칙</p>
<p><code>Multiple Page Application</code> 에서는, 
<strong>새로운 페이지를 요청할 때마다</strong> 서버에서 렌더링된 정적 리소스(HTML, CSS, JavaScript)를 응답으로 받아오는 방식이다. 따라서, 페이지 이동하거나 새로고침하면 전체 페이지를 다시 렌더링했기에 interface를 가져오는 과정에서 사용자 경험이 떨어진다. (로딩~.. 지연 .. 화면 깜빡임)</p>
<p>그래서 react에서는 SPA (single page application) 을 사용하는데! </p>
<p><code>Single Page Application</code> 에서는,
<strong>최초의 HTML 요청을 하나만 전송하고</strong> (단, 하나의 리소스), 페이지 이동 시 <strong>기존 페이지의 내부를 수정해서 보여주는 방식</strong>이다. <code>Client Side Rendering</code> 으로 랜더링되기 때문에, 간단히 URL 을 감시하다가 그 URL 이 변경되면 다른 리액트 컴포넌트를 로딩할 수 있다.</p>
<hr>
<h2 id="react-router-사용하기-📁">react-Router 사용하기 📁</h2>
<h4 id="0️⃣-패키지-설치">0️⃣ 패키지 설치</h4>
<pre><code class="language-bash">npm i react-router-dom</code></pre>
<h4 id="1️⃣-최상위-컴포넌트에서-router-연결-및-라우트-설정">1️⃣ 최상위 컴포넌트에서 router 연결 및 라우트 설정</h4>
<p>본 강의에서는 v6 에서 지원하는 <code>createBrowserRouter</code> 를 설명<br><a href="https://reactrouter.com/en/main/upgrading/v5">참고용 v5 react router dom</a></p>
<blockquote>
<p>👀<strong><code>createBrowserRouter</code></strong> 안에 <strong>path와 element를 담은 객체 배열을 전달</strong>해준다.</p>
</blockquote>
<pre><code class="language-js">createBrowserRouter([
  {path: &#39;/&#39;, element: &lt;Example /&gt;},
  {path: &#39;/list&#39;, element: &lt;List /&gt;}, ... ])</code></pre>
<p>👀 <strong><code>RouterRrovier</code></strong> 에서 <strong>router prop으로 <code>createBrowserRouter</code> 값을 전달</strong>해줌</p>
<p><code>index.js</code></p>
<pre><code class="language-jsx">//index.js 나 app.js 관계없음
import { createBrowserRouter, RouterProvider } from &#39;react-router-dom&#39;;
import HomePage from &#39;./pages/Home&#39;;

// https://example.com/products
// https -&gt; 프로토콜
// example.com -&gt; 도메인
// 도메인 뒤에 부터가 path임

const router = createBrowserRouter([
  { path: &#39;/&#39;, element: &lt;HomePage /&gt; }, // 도메인 뒤에 &#39;/&#39; 그리고 아무것도 없으면 path: &#39;/&#39; 가 실행
  {},
]);

const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;React.StrictMode&gt;
    &lt;RouterProvider router={router}&gt;&lt;/RouterProvider&gt;
  &lt;/React.StrictMode&gt;
);</code></pre>
<h4 id="1️⃣-1-또-다른-방식으로-router-하는-방법">1️⃣-1 또 다른 방식으로 router 하는 방법</h4>
<blockquote>
<p><code>createBrowserRouter</code> 에 배열 객체를 넘겨주지 말고, 
<code>createRoutesFromElement</code> 을 통해 중첩 route 를 설정해주고 반환된 값을 넘겨주는 방법도 있다!</p>
</blockquote>
<pre><code class="language-js">const routeDefinitions = createRoutesFromElements(
  &lt;Route&gt;
    &lt;Route path=&quot;/&quot; element={&lt;HomePage /&gt;}&gt;&lt;/Route&gt;
    &lt;Route path=&quot;/products&quot; element={&lt;Products /&gt;}&gt;&lt;/Route&gt;
  &lt;/Route&gt;
);

const router = createBrowserRouter(routeDefinitions);

const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;React.StrictMode&gt;
    &lt;RouterProvider router={router}&gt;&lt;/RouterProvider&gt;
  &lt;/React.StrictMode&gt;
);</code></pre>
<hr>
<h4 id="2️⃣-link-를-통해-url-이동하기">2️⃣ Link 를 통해 URL 이동하기</h4>
<pre><code class="language-jsx">// 이전까지 URL 이동시킨 방법
&lt;p&gt;
  go to
  &lt;a href=&quot;/products&quot;&gt;제품 페이지로 이동&lt;/a&gt;
&lt;/p&gt;</code></pre>
<p>이전까진 우린 url 을 이동시키기 위해서, a 태그에 href 속성을 이용해왔다. 그치만, <strong>a 태그를 통한 이동은 서버에 새로운 요청을 전송</strong>하게 되고 이로 인해 <strong>모든 리액트 애플리케이션을 재랜더링 하게 된다!!!</strong> <del>이렇게 되면 SPA 쓰는 이유가 없음</del> </p>
<p>따라서 우리는 <code>react-router-dom</code> 의 <code>Link</code>를 이용할 것이다</p>
<blockquote>
<p><code>Link</code>는 <strong>HTTP 요청 전송을 막아주고</strong>, <code>Route</code> 설정에 맞춰 <code>to 속성</code>을 통해 변경된 URL 에 따라 적절한 콘텐츠를 업데이트 해준다.</p>
</blockquote>
<pre><code class="language-jsx">import { Link } from &#39;react-router-dom&#39;;


// Link 는 요소에 대한 클릭을 감시하여, 클릭시 Http 요청을 전송하는 브라우저의 기본 설정을 막아주며
// 대신 라우트 정의를 확인하여 그에 맞춰 페이지를 업데이트하게 된다
function HomePage() {
  return (
    &lt;&gt;
      &lt;h1&gt; 홈페이지입니다 🏠 &lt;/h1&gt;
      &lt;p&gt;
        go to
        &lt;Link to=&quot;/products&quot;&gt;제품 페이지로 이동&lt;/Link&gt;
      &lt;/p&gt;
    &lt;/&gt;
  );
}

export default HomePage;
</code></pre>
<hr>
<h4 id="3️⃣-레이아웃-및-중첩된-라우트-관리">3️⃣ 레이아웃 및 중첩된 라우트 관리</h4>
<p><code>Header</code>에 위치한 <code>Nav Bar</code> 와 같은 경우, <code>RootLayout Component</code>로 만들어 공통적으로 <code>Nav</code>가 필요한 route(페이지)에 부모 route 로서 감싸주어야 한다.</p>
<blockquote>
<p>이때 <strong><code>children</code> 속성</strong>에 <code>Nav</code>를 포함한 <code>RootLayout</code> 을 필요로 하는, <strong>자식 라우트</strong>들을 넣어주면 된다.</p>
</blockquote>
<pre><code class="language-jsx">// index.js
const router = createBrowserRouter([
  {
    path: &#39;/&#39;,
    element: &lt;RootLayout /&gt;,
    children: [ // children 배열 안에 중첩될 라우트를 넣어준다.
      { path: &#39;/&#39;, element: &lt;HomePage /&gt; }, // 도메인 뒤에 &#39;/&#39; 그리고 아무것도 없으면 path: &#39;/&#39; 가 실행
      { path: &#39;/products&#39;, element: &lt;Products /&gt; },
    ],
  },
]);</code></pre>
<blockquote>
<p>부모 라우트는 <code>wrapper</code> 역할을 하는데, 이에 대해 <strong>자녀 라우트 요소가 어디에서 자리잡을지를 <code>Outlet</code> 컴포넌트로 정해줘야 한다</strong>. *<em>(자식 요소의 랜더링 장소 표시) *</em></p>
</blockquote>
<pre><code class="language-js">import MainNav from &#39;../components/MainNav&#39;;
import { Outlet } from &#39;react-router-dom&#39;;
import classes from &#39;./Root.module.css&#39;;

function RootLayout() {
  return (
    &lt;&gt;
      &lt;MainNav /&gt;
      &lt;main className={classes.content}&gt;
        &lt;Outlet /&gt; // 중첩 라우트의 요소 위치 
      &lt;/main&gt;
    &lt;/&gt;
  );
}

export default RootLayout;
</code></pre>
<hr>
<h4 id="4️⃣-errorelement-를-통한-오류-페이지-표시">4️⃣ errorElement 를 통한 오류 페이지 표시</h4>
<p>잘못된 URL 을 입력할 경우, <code>react-router-dom</code> 에서는 자체적으로 다음과 같이 오류를 throw 해준다. </p>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/892ed708-0568-4462-8fc6-e4a972486fd3/image.png" alt=""></p>
<p>이때 개발자는 사용자 경험을 높이기 위해 404 에러를 다루는 페이지를 구성한다.</p>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/d08679b8-c98f-4a20-8133-4cb1b8910b99/image.png" alt="">
(구글의 깔꼼한 404 처리 )</p>
<blockquote>
<p>아무튼  <code>react-router-dom</code>에서 <strong><code>errorElement</code></strong> 에 컴포넌트를 연결해줌으로써 404 처리를 해줄 수 있다!</p>
</blockquote>
<pre><code class="language-js">const router = createBrowserRouter([
  {
    path: &#39;/&#39;,
    element: &lt;RootLayout /&gt;,
    errorElement: &lt;Error /&gt;, // 에러를 핸들링할 요소 ⭐️
    children: [
      { path: &#39;/&#39;, element: &lt;HomePage /&gt; }, // 도메인 뒤에 &#39;/&#39; 그리고 아무것도 없으면 path: &#39;/&#39; 가 실행
      { path: &#39;/products&#39;, element: &lt;Products /&gt; },
    ],
  },
]);
</code></pre>
<hr>
<h4 id="5️⃣-navlink-를-통한-네비게이션-링크-동적-스타일링-하기">5️⃣ NavLink 를 통한, 네비게이션 링크 동적 스타일링 하기</h4>
<blockquote>
<p><code>Link</code> 컴포넌트가 가장 기본적인 네비게이션 링크를 생성한다면,</p>
</blockquote>
<blockquote>
<p><code>NavLink</code> 는 <code>Link</code> 컴포넌트의 기능을 확장하여, <strong>현재 경로와 일치하는 경우 링크를 활성화된 상태로 만들 수 있는 기능을 추가</strong>하고 있다.</p>
</blockquote>
<p><strong>동적으로 스타일 추가</strong>하고 싶으면 <strong><code>NavLink</code> **
**그냥 기본적인 네비게이션</strong> 쓰고 싶으면 <strong><code>Link</code></strong></p>
<pre><code class="language-jsx">
&lt;NavLink to=&quot;/&quot; className={({ isActive }) =&gt; (isActive ? classes.active : undefined)} end&gt;
  HOME 🏠
&lt;/NavLink&gt;
</code></pre>
<p><code>isActive</code> 속성을 통해, 현재 경로와 일치하는 경로에 대해 특별한 스타일이나 클래스 적용 가능</p>
<p>또한, 본래는 링크의 경로가 현재 경로의 접두사와 일치하는 경우에도 활성화가 되었지만! 
<code>end</code> 속성을 통해, <code>to</code> 속성에 <strong>지정된 경로와 현재 경로가 완전히 일치할때에만 링크가 활성화되게 설정</strong>할 수 있다!</p>
<hr>
<h4 id="6️⃣-usenavigate-으로-특정-조건이나-이벤트에-따라-경로-변경하기">6️⃣ useNavigate 으로, 특정 조건이나 이벤트에 따라 경로 변경하기</h4>
<p>네브바에서 링크를 클릭해서 이동하는 경우가 아니라, *<em>폼 제출 후 리다이렉션이나 조건에 따라 리다이렉션을 줘야 하는 프로그램적으로 네이비게이션을 주는 경우 *</em>에 <code>useNavigate</code> 훅을 사용한다.</p>
<pre><code class="language-jsx">import { useNavigate } from &#39;react-router-dom&#39;;

function HomePage() {
  const navigation = useNavigate();

  const handleClick = () =&gt; {
    navigation(&#39;/products&#39;);
  };
  return (
    &lt;&gt;
      &lt;h1&gt; 홈페이지입니다 🏠 &lt;/h1&gt;
      &lt;p&gt;
        &lt;button onClick={handleClick}&gt;상품 보기 가기🏃🏻‍♀️&lt;/button&gt;
      &lt;/p&gt;
    &lt;/&gt;
  );
}
</code></pre>
<hr>
<h4 id="7️⃣-동적-라우트-설정">7️⃣ 동적 라우트 설정</h4>
<p>쇼핑몰 사이트를 제작한다고 가정할때, 우린 상품 리스트 중 하나를 누르면 그 상품에 대한 URL로 이동을 하게 된다. 이때 <strong>상품이 10개이고, 100개이고, 1000개일때 우린 일일이 주소를 route 하지 않는다!!</strong></p>
<blockquote>
<p><code>/:식별자</code> 형식으로 콜론 뒤에 원하는 식별자를 넣어 매개변수(params)를 설정해줄 수 있다!  </p>
</blockquote>
<p><strong>index.js</strong></p>
<pre><code class="language-js">const router = createBrowserRouter([
  {
    path: &#39;/&#39;,
    element: &lt;RootLayout /&gt;,
    errorElement: &lt;Error /&gt;, // 에러를 핸들링할 요소
    children: [
      { path: &#39;/&#39;, element: &lt;HomePage /&gt; }, // 도메인 뒤에 &#39;/&#39; 그리고 아무것도 없으면 path: &#39;/&#39; 가 실행
      { path: &#39;/products&#39;, element: &lt;Products /&gt; },
       // 콜론을 통해 경로 매개변수 받기 ⭐️
      { path: &#39;/product/:productId&#39;, element: &lt;ProductDetail /&gt; }, 
    ],
  },
]);</code></pre>
<p><strong>Products.jsx</strong></p>
<pre><code class="language-jsx">import { Link } from &#39;react-router-dom&#39;;

const PRODUCTS = [
  { id: &#39;p1&#39;, title: &#39;제품1&#39; },
  { id: &#39;p2&#39;, title: &#39;제품2&#39; },
  { id: &#39;p3&#39;, title: &#39;제품3&#39; },
];

function Products() {
  return (
    &lt;div&gt;
      &lt;h1&gt; 제품 페이지입니다 🛒&lt;/h1&gt;
      &lt;ul&gt;
        {PRODUCTS.map((prod) =&gt; (
          &lt;li&gt;
            &lt;Link to={`/product/${prod.id}`}&gt;{prod.title}&lt;/Link&gt;
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>ProductDetail.jsx</strong></p>
<pre><code class="language-jsx">import { useParams } from &#39;react-router-dom&#39;;

function ProductDetail() {
  const params = useParams(); // 1.useParams 훅 실행

  //2. /:productId -&gt; params.productId 로 꺼내오기
  return (
    &lt;&gt;
      &lt;h1&gt;제품 상세 페이지&lt;/h1&gt;
      &lt;p&gt; {params.productId}&lt;/p&gt;
    &lt;/&gt;
  );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/167ba0ed-cccf-4100-9147-7d32dbc62df1/image.png" alt=""></p>
<hr>
<p>*<em>+ 절대경로와 상대경로의 차이점 *</em></p>
<blockquote>
<p><strong>절대경로</strong>: 도메인 루트를 기준으로 명확하게 경로를 지정. 경로는 항상 /로 시작
<strong>상대경로</strong>: 현재 경로를 기준으로 상대적으로 이동하는 경로를 지정. 경로는 현재 위치에 따라 달라짐</p>
</blockquote>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import { useNavigate } from &#39;react-router-dom&#39;;

const ProductNavigation = () =&gt; {
  const navigate = useNavigate();

  const goToHomeAbsolute = () =&gt; {
    navigate(&#39;/&#39;); // 절대경로
  };

  const goToHomeRelative = () =&gt; {
    navigate(&#39;..&#39;); // 상대경로: 현재 경로의 부모 경로로 이동
  };

  return (
    &lt;div&gt;
      &lt;button onClick={goToHomeAbsolute}&gt;Go to Home &lt;/button&gt;
      &lt;button onClick={goToHomeRelative}&gt;Go to Parent&lt;/button&gt;
    &lt;/div&gt;
  );
};

export default ProductNavigation;</code></pre>
<hr>
<h2 id="router를-이용한-data-fetching과-submite-📥-📤">router를 이용한 data Fetching과 submite 📥 📤</h2>
<h4 id="1️⃣-loader-로-data-fetching---📥">1️⃣ <code>loader</code> 로 <strong>data Fetching</strong>   📥</h4>
<p><strong><code>loader</code></strong> 는 데이터 로딩을 route 와 통합하여, <strong>라우팅이 발생할때! 함께 데이터도 로드할 수 있도록 한다.</strong> <strong>(특정 URL로 라우팅이 발생할때, 데이터를 받아온 후에, 컴포넌트를 랜더링)</strong></p>
<p>[loading 과정]
각 라우트에 대해서, 전환이 개시되자마자 데이터 가져오기가 시작되고, 
<strong>라우터는 loader가 작업을 완료할때까지 대기 후, 가져온 데이터로 페이지를 랜더링한다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 완벽가이드] Section 20: 고급 리덕스]]></title>
            <link>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD%EA%B0%80%EC%9D%B4%EB%93%9C-Section-20-%EA%B3%A0%EA%B8%89-%EB%A6%AC%EB%8D%95%EC%8A%A4</link>
            <guid>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD%EA%B0%80%EC%9D%B4%EB%93%9C-Section-20-%EA%B3%A0%EA%B8%89-%EB%A6%AC%EB%8D%95%EC%8A%A4</guid>
            <pubDate>Fri, 12 Jul 2024 16:34:47 GMT</pubDate>
            <description><![CDATA[<p>본 포스트는 <a href="https://www.udemy.com/course/best-react">Udemy 리액트 완벽가이드 2024</a> 를 듣고 정리한 내용입니다.</p>
<blockquote>
<p>목차 🌳
1️⃣ redux로 비동기 작업
2️⃣ 🛒 <strong>컴포넌트 안</strong>에서 비동기 다루기
3️⃣ 🛒 <strong>action creators(액션 생성자) 안</strong> 에서 비동기 다루기 </p>
</blockquote>
<h2 id="redux로-비동기-작업">redux로 비동기 작업</h2>
<p>이전 강의에서 배운 리덕스에서 리듀서함수는 무조건 순수 함수여야만 하며, side-effect 을 다룰 수 없고, 동기적으로 작동해야 한다고 배웠다. 이에 따라, 동일한 input을 넣었을때 항상 같은 output을 내는 함수를 만들었다. 그치만 여기에서 드는 궁금증!</p>
<p><strong>리덕스로 작업할때, Http 요청은 어떻게 하나? / 비동기 코드는 어디에 넣어야 할까? 🤔</strong></p>
<blockquote>
<p><strong>방법은 두가지가 있어요! ✌️</strong>
1️⃣ <strong>컴포넌트 안</strong>에서 다루기 (useEffect 로 제어)
2️⃣ <strong>action creators(액션 생성자) 안</strong> 에서 다루기 </p>
</blockquote>
<p>firebase를 통해, 데베에 장바구니 정보를 저장하고 꺼내쓸 수 있도록 해보자! 
-&gt; 1️⃣ 장바구니 수량 추가 및 삭제시, <strong>put 요청 보내기</strong>
-&gt; 2️⃣ 디비에 저장된 장바구니 <strong>정보 get 요청 보내기</strong></p>
<h2 id="1️⃣-장바구니-수량-추가-및-삭제시-put-요청-보내기">1️⃣ 장바구니 수량 추가 및 삭제시, <strong>put 요청 보내기</strong></h2>
<h3 id="📁-컴포넌트-안에서--useeffect-로-제어">📁 컴포넌트 안에서  (useEffect 로 제어)</h3>
<p>*<em>단순 cart 값 PUT Req 보내기 *</em></p>
<pre><code class="language-js">function App() {
  // 카트 상태를 store에서 가져오기
  const cart = useSelector((state) =&gt; state.cart.items);

  // useEffect를 통해, cart 상태 변경시, store에서 cart를 꺼내와 fetch 함수를 통해 데베에 덮어쓰기
  useEffect(() =&gt; {
    fetch(&#39;https://redux-pracice-default-rtdb.firebaseio.com/cart.json&#39;, {
      method: &#39;PUT&#39;, // PUT method를 통해 데이터를 덮어씀
      body: JSON.stringify(cart),
    });
  }, [cart]);
  return (
    &lt;Layout&gt;
      &lt;Cart /&gt;
      &lt;Products /&gt;
    &lt;/Layout&gt;
  );
}</code></pre>
<p><strong>근데 이렇게 useEffect를 쓰는데 문제점!🚨</strong></p>
<p><strong>앱이 시작될때, 실행이 된다는 점!!</strong>
(이게 왜 문젠데요?) -&gt; 왜냐면, 비어있는 초기 카드를 백엔드에 보내고, 거기에 저장된 모든 데이터를 덮어쓰기 때문!!!! 우리는 카트를 클릭을 했을때만 작동하길 원하지만, 마운트시 useEffect가 작동되기 때문이다.</p>
<h3 id="useeffect-에서-마운트시-원치-않는-작동을-방지하는-법-🔍">useEffect 에서 마운트시 원치 않는 작동을 방지하는 법 🔍</h3>
<p><strong><code>boolean</code> 을 담은 변수를 하나 선언</strong>해서, 최초 랜더링인지를 판별하여 실행</p>
<pre><code class="language-js">const isInitial = true // 전역으로 선언해줌

function App () {
  useEffect(() =&gt; {
    const sendCartData = async () =&gt; {
      // ... 생략
    };

    // 초기 랜더링일 경우, isInitial를 뒤집고 return 
    if (isInitial) { 
      isInitial = false;
      return;
    }

    sendCartData().catch((error) =&gt; {
    // ... 생략
    });
  }, [cart, dispatch]);


}
</code></pre>
<p>useEffect에 side Effect을 다루다보니 상당히 component가 길어진 경향이 있다... 
2번째 방법을 통해 다른 방법도 찾아보자! </p>
<hr>
<h3 id="📁-action-creators액션-생성자안-에서-다루기">📁 action creators(액션 생성자)안 에서 다루기</h3>
<p><code>redux Toolkit</code>에서 우린 <code>createSlice</code>를 통해 자동으로 생성된 액션 생성자를 사용해왔다.</p>
<pre><code class="language-js">// 액션 생성자 자동 생성
export const cartActions = cartSlice.actions;</code></pre>
<p>위와 같이 <code>.acions</code> 를 이용했었지만, 사실은 개발자가 직접 <code>action</code>을 만들 수도 있다!
우린 action에 비동기 작업을 담기 위해, <strong><code>action creator</code>를 <code>thunk</code>를 이용</strong>하여 만들어보려고 한다.</p>
<blockquote>
<p><strong><code>thunk</code></strong> 란 ? 🥸
<strong>다른 작업이 완료될때까지 작업을 지연</strong>시키는 단순한 함수! (나중에 실행할 코드 조각이란 뜻)
-&gt; <strong>작업 객체를 즉시 반환하지 않는</strong> action creators를 작성할 수 있음
action 에 대한 return 값으로 함수를 넘겨주고, 그 함수 안에 <code>side Effect</code>을 넣는다. 
(이로써 지연을 통해, action을 보내기 전에 비동기적으로 작업을 수행할 수 있다)</p>
</blockquote>
<p><strong>slice 파일 안에서, thunk 함수 정의</strong></p>
<pre><code class="language-js">const cartSlice = createSlice({
  // .. 생략
});

// 슬라이스 외부에  Thunk 함수를 만듦
export const sendCartData = (cart) =&gt; {
  return async (dispatch) =&gt; {
    // 첫 번째 액션: 요청을 시작했다는 알림을 디스패치
    dispatch(
      modalActions.showNotification({
        status: &#39;pending&#39;,
        title: &#39;sending...&#39;,
        message: &#39;sending cart data!&#39;,
      })
    );

    const sendReq = async () =&gt; {
      const response = await fetch(&#39;https://redux-pracice-default-rtdb.firebaseio.com/cart.json&#39;, {
        method: &#39;PUT&#39;,
        body: JSON.stringify(cart),
      });

      if (!response.ok) {
        throw new Error(&#39;sending cart data failed&#39;);
      }
    };

    try {
      // 실제 비동기 Http Req
      await sendReq();
      // 두 번째 액션: 요청이 성공했음을 알리는 알림을 디스패치
      dispatch(
        modalActions.showNotification({
          status: &#39;success&#39;,
          title: &#39;Success!&#39;,
          message: &#39;send cart data successfully&#39;,
        })
      );
    } catch (error) {
      // 세 번째 액션: 요청이 실패했음을 알리는 알림을 디스패치
      dispatch(
        modalActions.showNotification({
          status: &#39;error&#39;,
          title: &#39;Failed!&#39;,
          message: &#39;fail to send cart data :(&#39;,
        })
      );
    }
  };
};
</code></pre>
<p>사용할땐  <code>useEffect</code> 훅 안에서 <code>dispatch</code>의 인자로<code>thunk</code> 함수를 호출하면 된다.
(이때 자동으로 <code>dispatch</code> 가 <code>thunk</code> 함수에 대한 인자로 전달됨)</p>
<pre><code class="language-js">
function App() {
  // ...생략

  useEffect(() =&gt; {
    // 마운트시 실행 방지
    if (isInitial) {
      isInitial = false;
      return;
    }

    dispatch(sendCartData(cart));
  }, [cart, dispatch]);

  return (
    &lt;&gt;
        // ...생략
    &lt;/&gt;
  );
}

export default App;
</code></pre>
<hr>
<h2 id="2️⃣-인제-디비에-저장된-장바구니-정보-fetch-하기">2️⃣ 인제 디비에 저장된 장바구니 정보 fetch 하기</h2>
<p><strong>fetch 를 담은 thunk 함수 정의</strong></p>
<pre><code class="language-js">export const fetchCartData = () =&gt; {
  return async (dispatch) =&gt; {
    const fetchData = async () =&gt; {
      const res = await fetch(&#39;https://redux-pracice-default-rtdb.firebaseio.com/cart.json&#39;);

      if (!res.ok) {
        throw new Error(&#39;could not fetch data&#39;);
      }

      const data = await res.json();
      return data;
    };

    try {
      const cartData = await fetchData();
      console.log(cartData);
      // 장바구니에 물건이 없을 경우를 대비하여, [] 설정
      dispatch(cartActions.replaceCart({ items: cartData.items || [], totalQuantity: cartData.totalQuantity }));
    } catch (err) {
      console.log(err);
      dispatch(
        modalActions.showNotification({
          status: &#39;error&#39;,
          title: &#39;Failed!&#39;,
          message: &#39;fail to get cart data :(&#39;,
        })
      );
    }
  };
};</code></pre>
<p><strong>component 안에서 useEffect로 실행</strong></p>
<pre><code class="language-js">    // 카트 정보 fetch 
  useEffect(() =&gt; {
    dispatch(fetchCartData());
  }, [dispatch]);

    // 카드 정보 put 
  useEffect(() =&gt; {
    if (isInitial) {
      isInitial = false;
      return;
    }

    // 장바구니에 변경 사항이 있을때만 작동되도록
    if (cart.changed) {
      dispatch(sendCartData(cart.items, cart.totalQuantity));
    }
  }, [cart, dispatch]);</code></pre>
<hr>
<h3 id="redux-devtools">Redux Devtools</h3>
<p>리덕스 slice가 복잡해질때, 전체 리덕스 스토어의 현재 상태를 살펴볼때 사용! </p>
<p>어떤 action 이 불려, 
상태에 어떠한 변화가 이루어졌는지 보기 쉬움 </p>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/6154f75c-88d2-4f70-9e2e-e3a2d88a176b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 완벽 가이드]  Section 19: Redux]]></title>
            <link>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-19-Redux</link>
            <guid>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-19-Redux</guid>
            <pubDate>Mon, 01 Jul 2024 10:43:20 GMT</pubDate>
            <description><![CDATA[<p>본 포스트는 <a href="https://www.udemy.com/course/best-react">Udemy 리액트 완벽가이드 2024</a> 를 듣고 정리한 내용입니다.</p>
<blockquote>
<p>목차 🌳
1️⃣ <code>Redux</code> 가 뭔가요? 👀
2️⃣ <code>Redux</code> 그거 어떻게 쓰는건데요? 🥸
3️⃣ 적용해보자! </p>
</blockquote>
<hr>
<h1 id="redux">Redux</h1>
<h2 id="redux가-뭔데요-👀">Redux가 뭔데요 👀</h2>
<blockquote>
<p><strong>“app-level state” management!</strong>
<code>Redux</code>는 <strong>여러 컴포넌트, 혹은 앱 전체에서 공유되는 상태를 관리하기 위한 라이브러리</strong></p>
</blockquote>
<p>어랏 전역 상태관리를 위해, Context API를 전까지 썼는데, 왜 우린 redux를 또 배워야 할까요?</p>
<h3 id="context-api-vs-redux">Context API vs Redux</h3>
<p><code>context API</code> 에는 잠재적인 단점이 존재함</p>
<blockquote>
<p><code>context API</code> 뭐가 문제가 될 수 있을까? 😑
1️⃣ <strong>복잡한 설정과 관리의 위험성</strong> : 대규모 애플리케이션에서 사용될 경우, 아래와 같은 코드가 나올 수 있음</p>
</blockquote>
<ul>
<li><code>ContextProvider</code> 가 너무 많아지면, <code>중첩에 중첩이 더해진 jsx</code> 코드가 나올 수 있음</li>
<li>하나의 Context 안에 다 넣어도, 그것도 문제. 내부가 너무 비대해지기 떄문 
<img src="https://velog.velcdn.com/images/gonn-i/post/5d45f9a6-e7dc-4f10-b374-00fd128be264/image.png" alt="">
2️⃣ <strong>잠재적 성능적 문제</strong> : 데이터가 자주 변경되는 경우에, 성능 저하를 일으킬 수 있다고도 함 (w. 2018 react 직원.. )</li>
</ul>
<p>소형 및 중형 애플리케이션에서 (어쩌면 대형에서도) context api 사용이 크게 문제 되지 않을 수 있으나, 한계가 있을 수 있음을 알고 그에 대한 <strong>대안으로 리덕스를 알아보자!</strong></p>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/8e0d38c0-886f-4259-86ee-fd1f1ee39c3b/image.png" alt=""></p>
<hr>
<h2 id="redux-그거-어떻게-쓰는건데요-🥸"><code>Redux</code> 그거 어떻게 쓰는건데요? 🥸</h2>
<blockquote>
<p>🔥 <strong>Redux 를 쓰는 3가지 원칙</strong>
1️⃣ <code>하나의 저장소로부터 비롯된다</code>
애플리케이션의 <strong>모든 상태</strong>는 <strong>하나의 저장소 안</strong>에 <strong>하나의 객체 트리 구조로 저장</strong>
2️⃣ <code>상태는 오직 읽기만 가능!</code>
<strong>상태를 변화시키는 유일한 방법</strong>은 무슨 일이 벌어지는지를 묘사하는 <strong>액션 객체를 전달하는 방법뿐!</strong>
3️⃣ <code>상태 변화는 리듀서 함수를 통해 작성된다</code>
액션에 의해 상태 트리가 어떻게 변화하는지를 지정하기 위해 리듀서를 통해 작성해야함!
<code>리듀서</code> == 이전 상태와 액션을 받아 다음 상태를 반환하는 함수</p>
</blockquote>
<p><strong><code>대략적인 매커니즘🔄</code></strong></p>
<blockquote>
<p><strong><code>Component</code></strong> -- dispatch -- <strong><code>Reducer Function</code></strong> -- change state -- <strong><code>Central Data(State) Store</code></strong> -- subscribe -- <strong><code>Component</code></strong></p>
</blockquote>
<p><strong><code>Component</code></strong> 에서 <code>dispatch</code> 함수를 통해, <code>action</code> 에 따라 <strong><code>Reducer Function</code></strong> 이 실행되면서 인제 중앙 저장소에 있는 <code>state</code>가 변경되는데. 또 인제 이걸 구독하는 <strong><code>Component</code></strong> 가 변경된 <code>state</code>로 인해 재랜더링된다 </p>
<hr>
<h3 id="createstore">createStore()</h3>
<p>원래 Redux toolkit 사용이 권장되나, 일단 createStore() 로 먼저 리덕스의 작동 방식과 기능을 살펴본다
(뒤에서 툴킷 다룰 예정)</p>
<h4 id="createstore로-사용해보자">createStore로 사용해보자!</h4>
<blockquote>
<p>1️⃣  📁 store 폴더 안에, <strong><code>store</code>와  <code>reducer function</code> 만들어주기</strong> </p>
</blockquote>
<p>단, 이때 <strong>리듀서 함수는 순수 함수로서, 동일한 input에 대해소 항상 동일한 output</strong>을 반환해야 한다. 따라서, <strong>함수 내부에서는 side Effect을 다룰 수 없는데</strong>, http 요청이나, 브라우저단에서 임시 저장(local storage, session storage .. ), 타이머 설정과 같은 것이 이에 포함된다.</p>
<p>또한, 리듀서는 항상 새로운 상태 객체를 반환해야 해야 하기 때문에 (새로운 state는 업데이트 사항을 overwrite 해서 반환된것!!),  <strong>🙅🏻‍♀️함수 내부에서 직접 상태를 변경하면 안된다 🙅🏻‍♀️</strong> 
<strong>원본 state를 절대절대절대절대 변형하지 말것 !!!</strong> (직접 변경하는 경우: <code>state.count += 1;</code> )
왜냐면, 
1️⃣ 리듀서 외부에서 상태 변경을 추적하기 어려워짐 + 
2️⃣ 참조 투명성을 잃어, 이전 상태와의 관계가 불분명해지기 때문이다. 이로 인해 예측 불가능한 동작이 발생할 수 있다.</p>
<p>따라서 다음과 같이 완전히 새로운 상태에 대한 객체를 반환해야 한다.
<code>count: state.count + 1</code>
(새로운 객체를 복사하고 생성해서 다룰 것)</p>
<p>상태를 여러개 다룰 경우, but 한가지 상태만을 바꾸고 싶을 경우 
요렇게 <strong><code>spread 연산자</code> 써서 유지할건 유지하고, 바꿀건 원래 다음과 같이 덮어써서</strong> 
<strong>새로운 객체를 반환해야한다!⭐️</strong></p>
<pre><code class="language-jsx">    return {
      ...state,
      counter: state.counter - 1,
    };</code></pre>
<p><strong><code>store -&gt; index.js</code></strong></p>
<pre><code class="language-js">import { createStore } from &#39;redux&#39;;

// 상태 업데이트를 담당할 리듀서 함수 (이때 내부에는 side Effect을 다룰 수 없음)
// 리듀서 함수는 새로운 상태에 대한 new 스냅샷을 생성 
// 1) 리듀서 함수의 인자로, 상태값과 액션을 담아서 작성
// 이때 state에는 초기 실행의 경우를 고려하여, default 값을 넣어줘야함 + action은 주문서라고 생각하기 -&gt; 어떻게 바꿔주기 주문서
function counterReducer(state = { counter: 0 }, action) {
  if (action.type === &#39;increment&#39;) {
    return {
      counter: state.counter + 1,
    };
  }
  if (action.type === &#39;decrement&#39;) {
    return {
      counter: state.counter - 1,
    };
  }
  return state;
}

// 상태를 저장할 저장소를 만드는 방법
// redux 에서 createStore 함수를 실행하고, 인자로 자장소를 변경시키는 리듀서 함수를 포인터로 넘겨줄 것
const store = createStore(counterReducer);

export default store;
</code></pre>
<blockquote>
<p>2️⃣ <strong><code>store</code> 넘겨주기</strong></p>
</blockquote>
<p>컴포넌트 트리의 최상단에서, <strong><code>Provider</code> 로 <code>루트 component</code> 감싸주기</strong>
꼭 최상위 컴포넌트를 감쌀 필요는 없지만, 이렇게 되면 하위 컴포넌트에서 저장소에 도달할 수 있게 된다~
<code>&lt;Provider&gt;</code> 로 감싸주면서, <code>store props</code>에 직접 만든 리덕스 저장소 꼭 전달해주기!</p>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import { Provider } from &#39;react-redux&#39;;
import store from &#39;./store&#39;;

import &#39;./index.css&#39;;
import App from &#39;./App&#39;;

const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  // store에서 제공하는 state를 전달해주고 싶은 컴포넌트에 감싸준다. (부모에 감싸면 하위 컴포넌트까지 저장소에서 데이터 받을 수 있겠죠)
  // 이때 store props 에 설정한 리덕스 저장소를 전달해줌
  &lt;Provider store={store}&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;
);</code></pre>
<blockquote>
<p>3️⃣ <strong><code>store</code> &amp; <code>reducer function</code> 실제 사용해보기</strong></p>
</blockquote>
<p><strong>🔍 Redux 스토어의 상태를 읽는 방법</strong>
<strong><code>useSelector</code></strong> :  특정 컴포넌트에서 <strong>특정 상태 조각(state slice)만 구독(subscribe)</strong> 하고, 해당 상태가 변경될 때만 리렌더링되게 한다.
-&gt; 자동으로 업데이트 되며, 최신 상태를 가져옴
<code>const counter = useSelector((state) =&gt; state.counter);</code></p>
<p><strong>🔍 action을 dispatch하여 스토어 안의 상태를 바꿔보자!</strong>
<strong><code>useDispatch</code></strong> : <code>Redux store</code>에 <code>action</code>을 <code>디스패치(dispatch)</code>하여, <strong>store의 상태를 변경해서 상태 변경을 트리거한다!</strong></p>
<pre><code class="language-jsx">import classes from &#39;./Counter.module.css&#39;;
import { useSelector, useDispatch } from &#39;react-redux&#39;;

const Counter = () =&gt; {
  // Redux store 구독 및 상태 조각 가져오기
  const counter = useSelector((state) =&gt; state.counter);

  const toggleCounterHandler = () =&gt; {};

  // useDispatch 훅을 통해, dispatch 함수를 반환하고
  // dispatch 함수에 action 을 전달하여, store 내부의 상태 변경을 트리거
  const dispatch = useDispatch();
  const IncrementHandler = () =&gt; {
    dispatch({ type: &#39;increment&#39; });
  };

  const DecrementHandler = () =&gt; {
    dispatch({ type: &#39;decrement&#39; });
  };

  return (
    &lt;main className={classes.counter}&gt;
      &lt;h1&gt;Redux Counter&lt;/h1&gt;
      &lt;div className={classes.value}&gt;{counter}&lt;/div&gt;
      &lt;div&gt;
        &lt;button onClick={IncrementHandler}&gt;Increment&lt;/button&gt;
        &lt;button onClick={DecrementHandler}&gt;Decrement&lt;/button&gt;
      &lt;/div&gt;
      &lt;button onClick={toggleCounterHandler}&gt;Toggle Counter&lt;/button&gt;
    &lt;/main&gt;
  );
};

export default Counter;</code></pre>
<blockquote>
<ul>
<li>3️⃣ <strong>클래스형 컴포넌트에서</strong> <code>store</code> &amp; <code>reducer function</code> 사용해보기</li>
</ul>
</blockquote>
<p>함수형 컴포넌트가 주로 사용되지만, <strong>그래도 클래스로 할줄도 알아겠죵</strong>
훅을 사용할 수 없기 때문에, <code>connect 함수</code>로 <code>Redux store</code>와 연결 후, <code>상태</code>와 <code>dispatch</code> 함수를 사용한다.</p>
<pre><code class="language-jsx">class Counter extends Component {
  // this.props.increment()를 호출하여 increment 액션을 디스패치
  IncrementHandler = () =&gt; {
    this.props.increment();
  };
  //this.props.decrement()를 호출하여 decrement 액션을 디스패치
  DecrementHandler = () =&gt; {
    this.props.decrement();
  };

  render() {
    return (
      &lt;main className={classes.counter}&gt;
        &lt;h1&gt;Redux Counter&lt;/h1&gt;
        &lt;div className={classes.value}&gt;{this.props.counter}&lt;/div&gt;
        &lt;div&gt;
          &lt;button onClick={this.IncrementHandler.bind(this)}&gt;Increment&lt;/button&gt;
          &lt;button onClick={this.DecrementHandler.bind(this)}&gt;Decrement&lt;/button&gt;
        &lt;/div&gt;
        &lt;button onClick={this.toggleCounterHandler}&gt;Toggle Counter&lt;/button&gt;
      &lt;/main&gt;
    );
  }
}

// Redux 스토어의 상태를 컴포넌트의 props로 매핑 -&gt; useSelector 대체
const mapStateToProps = (state) =&gt; {
  return {
    counter: state.counter,
  };
};
//액션을 디스패치하는 함수 -&gt; useDispatch 대체
const mapDispatchToProps = (dispatch) =&gt; {
  return {
    increment: () =&gt; dispatch({ type: &#39;increment&#39; }),
    decrement: () =&gt; dispatch({ type: &#39;decrement&#39; }),
  };
};

// connect 함수로 Counter 컴포넌트를 Redux 스토어에 연결
export default connect(mapStateToProps, mapDispatchToProps)(Counter);</code></pre>
<blockquote>
<p>4️⃣ <strong><code>action payload</code></strong> 를 통해, 상태 <strong>업데이트에 필요한 데이터를 추가로 포함</strong>시키기 </p>
</blockquote>
<pre><code class="language-jsx">// 일반적인 action 객체 구조 
dispatch ({
  type: &#39;ACTION_TYPE&#39;,
  payload: /* 추가 데이터 */
})
</code></pre>
<p>보통 action 안에는, 변경 방식인 액션의 <code>type</code> 과 추가적인 정보를 포함할 수 있는 <code>payload</code>를 전달한다.
이를 통해, <code>유연한 state</code> 변경을 할 수 있는데, 예를 들어 counter 를 1씩 증가시키는게 아니라 버튼에 따라 +5를 증가할 수도 있고, +10 을 할 수 있는 reducer 기능을 만든다고 생각해보자!</p>
<p><code>Reducer 함수</code></p>
<pre><code class="language-js">function counterReducer(state = { counter: 0 }, action) {
  if (action.type === &#39;increse&#39;) {
    return {
      counter: state.counter + action.payload.amount,
    };
  }

  return state;
}</code></pre>
<p><code>Counter.jsx 에서 dispatch 방법</code></p>
<pre><code class="language-jsx">  const IncreseHandler = () =&gt; {
    dispatch({ type: &#39;increse&#39;, payload: { amount: 5 } });
  };</code></pre>
<p>다음과 같이 action 인자를 넘겨주면, <code>action.payload.amount</code> 에 증가하고 싶은 수를 리듀서 함수에 전달할 수 있다!!</p>
<h3 id="그치만-redux-사용에는-잠재적인-위험이-있다고요-⚠️">그치만, Redux 사용에는 잠재적인 위험이 있다고요 ⚠️</h3>
<blockquote>
<p>1️⃣ <strong><code>action type</code></strong> 오타 이슈 + 충돌 이슈
식별자 사용시 오타가 발생하면 제대로된 리듀서의 처리가 이루어질 수 없으며,
서로 다른 액션이 많을때 식별자에 대해 충돌이 발생할 수 있다 💥</p>
</blockquote>
<blockquote>
<p>1️⃣ <strong><code>관리해야 하는 state</code></strong> 가 많아질 경우 파일일 비대해짐
state 변경시, Reducer Function 안에서 기존의 상태를 복사한 것을 토대로 완전히 새로운 객체를 만들어내면서 상태를 업데이트했다. 근데, 이제 다루는 데이터의 양이 많아진다면,  Reducer 파일이 엄청나게 거대해질 거다!!</p>
</blockquote>
<p>-&gt; 이때 등장하는 구세주, <code>Redux Toolkit!</code></p>
<hr>
<h3 id="redux-toolkit">Redux toolkit</h3>
<p>일단 기본적으로 설치 하시고용</p>
<pre><code>npm i @reduxjs/toolkit</code></pre><blockquote>
<p>1️⃣ <strong><code>store</code></strong>와 <strong><code>reducer function</code></strong> 만들기</p>
</blockquote>
<p><strong>🔍 state와 reducer 함수를 만드는 방법!</strong> 
<strong><code>createSlice</code></strong> : Redux 상태 슬라이스를 만드는 함수로, 내부엔 name과, initialState, reducers 함수를 정의한다. </p>
<p><strong><code>기본형태</code></strong></p>
<pre><code class="language-js">//createSlice 기본 형태
const Slice = createSlice({
  name: &#39;counter&#39;, // 슬라이스 이름
  initialState: { counter: 0, ... }, // 초깃값
  reducers: { // 상태를 변경할 리듀서 함수
    increment(state) {
      state.counter++; 
    },
    },
  },
});</code></pre>
<p>어랏 근데, reducer 함수를 보면 상태를 직접적으로 수정하는 것처럼 보이는데, 사실은 <strong><code>toolkit</code>이 <code>immer</code> 라이브러리를 사용하기 때문에 가능한 것이다~</strong></p>
<p><code>immer</code> 는 <code>reducer</code> 함수가 호출될때, <strong>기존 상태에 대한 복사본(드래프트)을 자동으로 생성</strong>하며,
드래프트가 <strong>변경되면 <code>immer</code>가 이를 기록</strong>하고, 
<code>reducer</code> 함수 종료시, 변경기록을 바탕으로 <strong>새로운 불변 상태 객체를 생성</strong>한다!!!</p>
<blockquote>
<p><strong>⭐️ 직접 상태를 변경하는 것처럼 보이지만 실제로는 불변상태를 유지하고 있단 말!! ⭐️</strong>
덕분에 reducer 함수가 간결해지고, 불변성을 직접 관리해야 하는 번거로움을 줄여줌</p>
</blockquote>
<p><strong>🔍 store 를 설정하는 방법!</strong> 
<strong><code>configureStore</code></strong> : Redux store 설정을 단순화 해주는 함수
리듀서 결합이 매우 쉽다는 점이 특징이다! <code>redux</code>에서 저장소는 단 하나이기 때문에, <code>configureStore</code>를 통해 만들어지는 저장소도 단 하나가 되어야 한다 </p>
<pre><code class="language-js">//configureStore기본 형태
const store = configureStore({
  reducer: counterSlice.reducer,
});

// 여러개의 리듀서를 결합한 경우
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer, 
    user: userSlice.reducer, 
  },
});</code></pre>
<p><strong><code>store -&gt; index.js</code></strong></p>
<pre><code class="language-js">import { createSlice, configureStore } from &#39;@reduxjs/toolkit&#39;;

const initialState = { counter: 0, showCounter: true };

// redux 상태 슬라이스를 통햐 state와 reducer 함수를 설정할 수 있음.
// 1) name은 슬라이스의 이름으로 자유롭게 설정가능
// 2) initialState 는 관리할 state의 초깃값을 설정
// 3) reducers 에는 상태를 변경하는 방법을 정의하는 객체로, 내부에 선언된 각 리듀서 함수는
// 인자로 state와 action을 받아서, 새로운 상태를 반환!!
const counterSlice = createSlice({
  name: &#39;counter&#39;,
  initialState: initialState,
  reducers: {
    increment(state) {
      state.counter++;
    },
    increse(state, action) {
      state.counter = state.counter + action.payload.amount;
    },
    decrement(state) {
      state.counter--;
    },
    toggleCounter(state) {
      state.showCounter = !state.showCounter;
    },
  },
});

const store = configureStore({
  reducer: counterSlice.reducer,
});

export default store;</code></pre>
<blockquote>
<p><strong>2️⃣ action을 전달하는 방법!</strong></p>
</blockquote>
<p>이전에는 if 문이나 switch 문으로 action type에 따라 reducer 함수에서 다른 동작을 실행했지만, <code>createSlice</code>에서는 그렇지 않다! </p>
<p><strong>1) 액션 만들기 🌱</strong></p>
<p><code>createSlice</code> 는 <code>reducer 함수</code>에 대해서 <strong><code>고유한 액션 identifier</code> 를 자동으로 생성</strong>해준다! 그렇기 때문에 <strong><code>counterSlice.action</code></strong> 안에, 객체로 우리가 만든 <code>reducer 함수</code>가 key 로 들어가 있다! </p>
<p><strong><code>counterSlice.actions.increment</code> 이렇게 사용하면 액션 객체가 자동으로 생성</strong>되어, 우린 이걸 그저 <strong>export 해서 dispatch 하는 곳(Component)에서 사용하면 된다!</strong></p>
<pre><code class="language-js">export const counterActions = counterSlice.actions;</code></pre>
<p><strong>2) dispatch에 action 넣기! 🌱</strong></p>
<p>위와 같이 export 한 값 import 해주고, 불러온 객체값(ex. <code>counterActions</code>)에서 key 값에 접근하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/7d65fe6a-1bc9-4075-88c2-9a169f7bce0c/image.png" alt=""></p>
<p>요렇게 <code>createSlice</code> 가 만들어준 <code>action 객체</code> 덕분에 오타날 걱정이 없단 말씀!</p>
<p>근데 이제, 추가적으로 <strong>payload 하고 싶은 값이 있다면</strong>, <strong>호출한 메소드에 인자로 데이터를 넣어서 전달</strong>해주면 된다! 전달할 데이터는 객체, 숫자, 문자 상관없다! (전달되어서는 <strong>payload 안에 자동으로 들어감</strong>)</p>
<pre><code class="language-js">  const IncreseHandler = () =&gt; {
    dispatch(counterActions.increase({ amount: 5 }));
    // dispatch({type: increase, payload:{ amount: 5} }); 와 동일!!!!</code></pre>
<blockquote>
<p><strong>3️⃣ 다중 슬라이스 다뤄보기!</strong></p>
</blockquote>
<p>한 파일에 모든 슬라이스를 넣으면 파일 부피가 커지기 때문에, slice 별로 파일을 나누는 것이 좋다고 한다!</p>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/199dd53c-0574-47dd-a66d-bbe18f63933a/image.png" alt=""></p>
<p>각 slice 안에는, createSlice 해서 만들어진 state와 Reducer 함수를 포함하며 export 할때는 reducer를 내보내주면 된다. 짜피 store 설정할때 reducer를 전달해줘야 하니까!!</p>
<pre><code class="language-js">// store -&gt; auth-slice.js
import { createSlice } from &#39;@reduxjs/toolkit&#39;;

const initialAuthState = {
  isAuthenticated: false,
};
const authSlice = createSlice({
  name: &#39;authentication&#39;,
  initialState: initialAuthState,
  reducers: {
    login(state) {
      state.isAuthenticated = true;
    },
    logout(state) {
      state.isAuthenticated = false;
    },
  },
});

// store에 연결할 reducer 함수 export 
export const authReducer = authSlice.reducer;
// dispatch 시 사용할, action export 
export const authActions = authSlice.actions;
</code></pre>
<p>그럼 이러한 slice 에서 나온 reducer 를 index.js 에 모아 store에 연결해주면 된다.</p>
<pre><code class="language-js">// store -&gt; index.js
import { configureStore } from &#39;@reduxjs/toolkit&#39;;
import { authReducer } from &#39;./auth-slice&#39;;
import { counterReducer } from &#39;./counter-slice&#39;;

const store = configureStore({
  reducer: {
    counter: counterReducer,
    auth: authReducer,
  },
});

export default store;

</code></pre>
<p>이로써 관리할 데이터가 늘어 파일 크기가 비대해지는 경우를 막을 수 있다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 1652 랜선 자르기]]></title>
            <link>https://velog.io/@gonn-i/%EB%B0%B1%EC%A4%80-1652-%EB%9E%9C%EC%84%A0-%EC%9E%90%EB%A5%B4%EA%B8%B0</link>
            <guid>https://velog.io/@gonn-i/%EB%B0%B1%EC%A4%80-1652-%EB%9E%9C%EC%84%A0-%EC%9E%90%EB%A5%B4%EA%B8%B0</guid>
            <pubDate>Sun, 30 Jun 2024 07:49:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/gonn-i/post/4c8247b0-109f-4c6e-bbf2-120af975ae3c/image.png" alt=""></p>
<p><a href="https://www.acmicpc.net/problem/1654">문제링크</a></p>
<h2 id="접근-방향-설명">접근 방향 설명</h2>
<blockquote>
<p>1️⃣ 줄의 길이를 <code>start(min) = 1</code>, <code>end(max) = 주어진 줄 배열중 최대 길이</code>로 잡고 이진 탐색을 진행한다.
2️⃣ 이때 <code>mid</code> 값으로 <strong>N 개의 줄을 만들 수 있는지를 판별한 후</strong>, 가능한 경우 <code>ans</code> 배열에 넣는다.
3️⃣ 이진탐색을 마친 후, <code>ans</code> 배열에서 <code>max</code> 값을 찾는다! </p>
</blockquote>
<h2 id="풀이-코드-해석">풀이 코드 해석</h2>
<pre><code class="language-java">let fs = require(&#39;fs&#39;);
let filePath = process.platform === &#39;linux&#39; ? &#39;/dev/stdin&#39; : &#39;/input.txt&#39;;
let input = fs
  .readFileSync(__dirname + filePath)
  .toString()
  .trim()
  .split(&#39;\n&#39;);

const [K, N] = input.shift().split(&#39; &#39;).map(Number);
let ans = [];

function Validation(mid) {
  let count = 0;
  input.forEach((e) =&gt; {
    count += Math.floor(e / mid);
  });
  return count &gt;= N;
}

function BinarySearch() {
  let start = 1;
  let end = Math.max(...input);

  while (start &lt;= end) {
    let mid = Math.floor((start + end) / 2);
    if (Validation(mid)) {
      start = mid + 1;
      ans.push(mid);
    } else {
      end = mid - 1;
    }
  }
  return Math.max(...ans);
}

console.log(BinarySearch());</code></pre>
<hr>
<h2 id="풀이-과정에서-새롭게-느낀점배운점">풀이 과정에서 새롭게 느낀점(배운점)</h2>
<blockquote>
<p>우여곡절 1) 
영식이 가진 줄을 굳이 다 쓸 필요가 없었는데, (영식&#39;s 줄 중에 일부만을 사용해서 N을 만들 수 있으면 작은 건 버려도 됨) 그걸 간과해서 <code>end</code> 에  Math.min을 사용한 죄... (두두둥..)</p>
</blockquote>
<blockquote>
<p>우여곡절2) 
ans 배열에 가능한 줄의 길이를 넣고 max를 뽑아낼 생각을 못하고, 무조건 start와 end가 엇갈리기 전, mid가 답일 것이라 생각한 죄.. (진짜.. 이건..)</p>
</blockquote>
<p>아무튼 문제를 풀다보니 점점 실수 모음집이 되어가는 것도 같지만, 오히려 좋아... 그래도 성장하고 있어 🌱</p>
<hr>
<h2 id="문제">문제</h2>
<p>집에서 시간을 보내던 오영식은 박성원의 부름을 받고 급히 달려왔다. 박성원이 캠프 때 쓸 N개의 랜선을 만들어야 하는데 너무 바빠서 영식이에게 도움을 청했다.</p>
<p>이미 오영식은 자체적으로 K개의 랜선을 가지고 있다. 그러나 K개의 랜선은 길이가 제각각이다. 박성원은 랜선을 모두 N개의 같은 길이의 랜선으로 만들고 싶었기 때문에 K개의 랜선을 잘라서 만들어야 한다. 예를 들어 300cm 짜리 랜선에서 140cm 짜리 랜선을 두 개 잘라내면 20cm는 버려야 한다. (이미 자른 랜선은 붙일 수 없다.)</p>
<p>편의를 위해 랜선을 자르거나 만들 때 손실되는 길이는 없다고 가정하며, 기존의 K개의 랜선으로 N개의 랜선을 만들 수 없는 경우는 없다고 가정하자. 그리고 자를 때는 항상 센티미터 단위로 정수길이만큼 자른다고 가정하자. N개보다 많이 만드는 것도 N개를 만드는 것에 포함된다. 이때 만들 수 있는 최대 랜선의 길이를 구하는 프로그램을 작성하시오.</p>
<h2 id="입력">입력</h2>
<p>첫째 줄에는 오영식이 이미 가지고 있는 랜선의 개수 K, 그리고 필요한 랜선의 개수 N이 입력된다. K는 1이상 10,000이하의 정수이고, N은 1이상 1,000,000이하의 정수이다. 그리고 항상 K ≦ N 이다. 그 후 K줄에 걸쳐 이미 가지고 있는 각 랜선의 길이가 센티미터 단위의 정수로 입력된다. 랜선의 길이는 231-1보다 작거나 같은 자연수이다.</p>
<h2 id="출력">출력</h2>
<p>첫째 줄에 N개를 만들 수 있는 랜선의 최대 길이를 센티미터 단위의 정수로 출력한다.</p>
<h2 id="예제-입력-1">예제 입력 1</h2>
<p>4 11
802
743
457
539</p>
<h2 id="예제-출력-1">예제 출력 1</h2>
<p>200</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 완벽 가이드] Section 17 : Form]]></title>
            <link>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-17-Form</link>
            <guid>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-17-Form</guid>
            <pubDate>Sat, 22 Jun 2024 15:06:11 GMT</pubDate>
            <description><![CDATA[<p>본 포스트는 <a href="https://www.udemy.com/course/best-react">Udemy 리액트 완벽가이드 2024</a> 를 듣고 정리한 내용입니다.</p>
<blockquote>
<p><strong>목차 🌳</strong>
1️⃣ 폼을 다루는 어려움? 
2️⃣ Form 제출을 다루는 방법 &amp; 유효성 검사 
3️⃣ Custom Hook으로 처리하기</p>
</blockquote>
<h2 id="폼-왜-다루기-어려울까-😵💫">폼 왜 다루기 어려울까? 😵‍💫</h2>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/f99c8d80-4141-4998-a49f-254a36604575/image.png" alt=""></p>
<blockquote>
<p>Form 양식은 입력창의 집합이다! 근데 입력값 받아주기 뿐만 아니라, 유효성 검사도 해줘야 한다. 
[Form 형식이 하는일] 
1️⃣ Form <strong>제출</strong>
-&gt; 입력된 값은 state 관리 / 아니면 refs로 참조 / 것도 아니면 FormData 객체 사용
2️⃣ Input <strong>유효성 검사</strong>
-&gt; 키를 누를때마다 올바르지 않는 입력값에 대해 검증 결과 알려줌 
-&gt; 사용자 경험에 따라 언제 오류를 출력할지는 조금씩 다름</p>
</blockquote>
<h2 id="form-제출을-다루는-방법--유효성-검사-🦾">Form 제출을 다루는 방법 &amp; 유효성 검사 🦾</h2>
<h3 id="form-제출">Form 제출</h3>
<pre><code class="language-jsx">    &lt;form onSubmit={handleSubmit}&gt;
      &lt;h2&gt;Login&lt;/h2&gt;

      &lt;div className=&quot;control-row&quot;&gt;
        &lt;div className=&quot;control no-margin&quot;&gt;
          &lt;label htmlFor=&quot;email&quot;&gt;Email&lt;/label&gt;
          &lt;input id=&quot;email&quot; type=&quot;email&quot; name=&quot;email&quot; /&gt;
        &lt;/div&gt;

         ...

      &lt;p className=&quot;form-actions&quot;&gt;
        &lt;button className=&quot;button button-flat&quot;&gt;Reset&lt;/button&gt;
        &lt;button className=&quot;button&quot;&gt;Login&lt;/button&gt;
      &lt;/p&gt;
    &lt;/form&gt;</code></pre>
<p>Form 형태에서 제출을 누르게 된다면, Form 양식 요소에서 요청을 발생시키고 -&gt; 그 요청이 서버로 전송되는게 일반적이다. 그치만 Form 제출 시, <strong>브라우저는 기본적으로 페이지를 새로 고침하게 된다.</strong></p>
<p>싱글 페이지 어플리케이션인 React에겐 전체 페이지를 로드하는 것은 날벼락 ! ⚡️⚡️</p>
<p>그렇기 때문에 필요한 부분만을 업데이트하는 방식을 고수하기 때문에, <code>handleSubmit</code> 안에 
<code>e.preventDefault()</code> 을 수행해준다</p>
<hr>
<h3 id="form-input-수집-및-관리">Form input 수집 및 관리</h3>
<blockquote>
<p>*<em>아무튼 Form의 input을 받아내는 방법은 ~ *</em></p>
</blockquote>
<blockquote>
<p>1️⃣ <strong><code>State</code></strong> 관리를 통한 input value 관리</p>
</blockquote>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;
export default function Login() {
  const [enteredValue, setEnteredValue] = useState({
    email: &#39;&#39;,
    passward: &#39;&#39;,
  });

  function handleSubmit(e) {
    e.preventDefault();
    console.log(enteredValue);
  }

  function handleInputChange(identifier, e) {
    setEnteredValue((prev) =&gt; ({
      ...prev,
      [identifier]: e.target.value,
    }));
  }

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;h2&gt;Login&lt;/h2&gt;

      &lt;div className=&quot;control-row&quot;&gt;
        &lt;div className=&quot;control no-margin&quot;&gt;
          &lt;label htmlFor=&quot;email&quot;&gt;Email&lt;/label&gt;
          &lt;input
            id=&quot;email&quot;
            type=&quot;email&quot;
            name=&quot;email&quot;
            onChange={(e) =&gt; handleInputChange(&#39;email&#39;, e)}
            value={enteredValue.email}
          /&gt;
        &lt;/div&gt;

        // ... 생략
    &lt;/form&gt;
  );
}</code></pre>
<blockquote>
<p>2️⃣ <strong><code>Ref</code></strong> v를 통한 input value 관리</p>
</blockquote>
<pre><code class="language-jsx">import { useRef } from &#39;react&#39;;
export default function Login() {
  const emailRef = useRef();
  const passwordRef = useRef();

  function handleSubmit(e) {
    e.preventDefault();
    const enteredEmail = emailRef.current.value;
    const enteredPassword = passwordRef.current.value;

    console.log(enteredEmail, enteredPassword);
  }

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;h2&gt;Login&lt;/h2&gt;

      &lt;div className=&quot;control-row&quot;&gt;
        &lt;div className=&quot;control no-margin&quot;&gt;
          &lt;label htmlFor=&quot;email&quot;&gt;Email&lt;/label&gt;
          &lt;input id=&quot;email&quot; type=&quot;email&quot; name=&quot;email&quot; ref={emailRef} /&gt;
        &lt;/div&gt;

      // ... 생략
  );
}</code></pre>
<blockquote>
<p><strong>두 방법 비교 🥊</strong></p>
</blockquote>
<p>사실 확실한 정답은 없지만, 따지고 봤을때 입력값의 갯수가 작고 간단할때는 <code>Ref</code>가 더 간결 </p>
<p><strong><code>ref</code>가 간결한 사유)</strong> <code>onChange</code>를 통해 입력에 대한 상태 변화를 제어할 필요가 없음 
-&gt; 그치만 단점, <code>DOM</code>을 다룰때, 참조로 재설정 하는 등의 경우엔 좀 신중해야함</p>
<p><strong>반대로 입력폼이 복잡할때)</strong> 그땐 <code>useRef</code> 로 연결할 값이 늘어나기 때문에 <strong><code>state</code> 관리가 더 좋지 않을까</strong></p>
<blockquote>
<p>3️⃣ <strong><code>FormData</code></strong> 객체 활용하기</p>
</blockquote>
<p><code>FormData</code> 객체란, 
<strong>HTML 폼 형태의 데이터를 쉽게 관리하고 전송</strong>할 수 있도록 해주는 API </p>
<ul>
<li>key-value로 데이터를 저장 ) 
폼 요소의 name과 value로 <code>key-value</code>를 쌍으로 저장 </li>
<li><code>&lt;input type=&quot;file&quot;&gt;</code> 를 통해 파일 전송도 쉽게 처리 가능</li>
</ul>
<p><code>FormData</code> 사용 예시,</p>
<pre><code class="language-jsx"> function handleSubmit(e) {
    e.preventDefault();

    const fd = new FormData(e.target); // form 요소를 전달
    const data = Object.fromEntries(fd.entries()); // ⬇️ 하단에 설명 추가 
    const acquisitionChannel = fd.getAll(&#39;acquisition&#39;); // name이 &#39;acquisition&#39;인 모든 값을 배열로 반환하여 저장    
    data.acquisition = acquisitionChannel; // acquisition 속성 추가 후, 그 값으로 acquisitionChannel 배열을 설정ㄴ
    console.log(data);
  }
</code></pre>
<p><code>entries()</code> 는 fd 에 들어있는 모든  key-value 쌍을 포함하는 배열을 반환. [name, value] 
<code>Object.fromEntries()</code> 는 위의 값을 사용해, key-value 쌍으로 구성된 객체로 반환 {name: value}</p>
<p>암튼 쉽게! 복잡한 형태의 Form을 다룰 수 있다! </p>
<h3 id="form-input-초기화">Form input 초기화</h3>
<blockquote>
<p>1️⃣ form 요소에 reset 호출
<strong><code>e.target.reset();</code></strong></p>
</blockquote>
<blockquote>
<p>2️⃣  state를 초기값으로 업데이트하기
<strong><code>setEnteredValue({ email: &#39;&#39;, passward: &#39;&#39;});</code></strong></p>
</blockquote>
<h2 id="유효성-검사하기-🔍">유효성 검사하기 🔍</h2>
<p>유효성 검사는 그 시점에 따라 접근법이 달라지는데, 
!제출시! 값들의 유효성을 검사할 경우에는 크게 방법에 구애받지 않지만,
만일 매번 !키 입력에 따라! 유효성 검사를 할 경우에는 =&gt; state를 이용해야 한다.</p>
<blockquote>
<p><strong>!키 입력에 따라!</strong> 유효성 검사를 할 경우</p>
</blockquote>
<p>근데 타이핑을 치는 순간순간에 유효성을 검사할 경우, (ex. 이메일 형식 @ 들어가는지) 
오류 문구가 일찍 보여지는 경향이 있다.</p>
<p><strong>그렇기 때문에, onBlur 이벤트리스너를 통해서, 포커스를 잃은 경우 검증하되, 값을 입력하기 시작하면 오류 메시지가 사라지게 하는 것이 Best!!!!</strong></p>
<pre><code class="language-jsx">function Login() {
  const [enteredValue, setEnteredValue] = useState({
    email: &#39;&#39;,
    passward: &#39;&#39;,
  });

  // 유효성 검증할 시점
  const [didEdit, setDifEdit] = useState({
    email: false,
    passward: false,
  });

  // 이메일 형식에 @가 있는지 
  const emailIsInvaild = didEdit.email &amp;&amp; !enteredValue.email.includes(&#39;@&#39;);

    // 입력시 (포커스 상태)일때, 검증 X
  function handleInputChange(identifier, e) {
    setEnteredValue((prev) =&gt; ({
      ...prev,
      [identifier]: e.target.value,
    }));
    setDifEdit((prev) =&gt; ({
      ...prev,
      [identifier]: false,
    }));
  }

  // 포커스 아웃시, 검증 ! 
  function handleEmailBlur(identifier) {
    setDifEdit((prev) =&gt; ({
      ...prev,
      [identifier]: true,
    }));
  }

  // 이하 함수 생략.. 
  return (
    &lt;form onSubmit={handleSubmit}&gt;

      &lt;div className=&quot;control-row&quot;&gt;
        &lt;div className=&quot;control no-margin&quot;&gt;
          &lt;label htmlFor=&quot;email&quot;&gt;Email&lt;/label&gt;
          &lt;input
            id=&quot;email&quot;
            type=&quot;email&quot;
            name=&quot;email&quot;
            onChange={(e) =&gt; handleInputChange(&#39;email&#39;, e)}
            onBlur={() =&gt; handleEmailBlur(&#39;email&#39;)}
            value={enteredValue.email}
          /&gt;
          &lt;div className=&quot;control-error&quot;&gt;{emailIsInvaild &amp;&amp; &lt;p&gt;plz enter invalid email&lt;/p&gt;}&lt;/div&gt;
        &lt;/div&gt;

        // ... 생략

    &lt;/form&gt;
  );
}
</code></pre>
<blockquote>
<p><strong>!제출시!</strong> 값들의 유효성을 검사할 경우</p>
</blockquote>
<blockquote>
<p>1️⃣ 방법1 : 제출시 유효한지를 판별한 상태 하나 더 선언</p>
</blockquote>
<pre><code class="language-jsx">  const [emailIsInvalid, setEmailIsInvalid] = useState(false);
  // .. 일부 생략

  function handleSubmit(e) {
    e.preventDefault();
    const enteredEmail = emailRef.current.value;

    console.log(enteredEmail);
    const emailIsValid = enteredEmail.includes(&#39;@&#39;);

    if (!emailIsValid) {
      setEmailIsInvalid(true);
      return;
    }

    setEmailIsInvalid(false);
  }</code></pre>
<blockquote>
<p>2️⃣ 방법2 : 내장된 Prop을 사용</p>
</blockquote>
<p><strong><code>required</code></strong> : input value가 공란인지 확인하고, input type에 따라 유효성 판별됨</p>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/1e11b9f8-6040-41bc-8ec7-3852eb07d117/image.png" alt=""></p>
<pre><code class="language-jsx">    &lt;div className=&quot;control&quot;&gt;
        &lt;label htmlFor=&quot;email&quot;&gt;Email&lt;/label&gt;
        &lt;input id=&quot;email&quot; type=&quot;email&quot; name=&quot;email&quot; required /&gt;
    &lt;/div&gt;</code></pre>
<p><strong><code>minLength={num}</code></strong> : 최소 입력글자수를 정하여, 유효하지 않을 경우 제출을 막음</p>
<p><img src="https://velog.velcdn.com/images/gonn-i/post/a18f1df9-316f-4ed0-b901-deef05e0f621/image.png" alt=""></p>
<pre><code class="language-jsx">     &lt;div className=&quot;control&quot;&gt;
          &lt;label htmlFor=&quot;password&quot;&gt;Password&lt;/label&gt;
          &lt;input id=&quot;password&quot; type=&quot;password&quot; name=&quot;password&quot; required minLength={8} /&gt;
    &lt;/div&gt;</code></pre>
<ul>
<li>물론, 빌드인 기능과 이외의 직접 만든 유효성 검사를 함께 써도 무방 !</li>
</ul>
<h2 id="custom-으로-처리하기">Custom 으로 처리하기</h2>
<blockquote>
<p>1️⃣ 중복되는 label + input + error 코드는 <strong><code>Custom Component</code>로!</strong></p>
</blockquote>
<p><code>Input.jsx</code></p>
<pre><code class="language-jsx">export default function Input({ label, id, error, ...props }) {
  return (
    &lt;div className=&quot;control no-margin&quot;&gt;
      &lt;label htmlFor={id}&gt;{label}&lt;/label&gt;
      &lt;input id={id} {...props} /&gt;
      &lt;div className=&quot;control-error&quot;&gt;{error &amp;&amp; &lt;p&gt;{error}&lt;/p&gt;}&lt;/div&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><code>custom Component 사용</code></p>
<pre><code class="language-jsx">&lt;Input
  label=&quot;Email&quot;
  id=&quot;email&quot;
  type=&quot;email&quot;
  name=&quot;email&quot;
  onChange={handleEmailChange}
  onBlur={handleEmailBlur}
  value={emailValue}
  error={emailHasError &amp;&amp; &#39;Please enter a valid email!&#39;}
  /&gt;</code></pre>
<blockquote>
<p>2️⃣ 유효성 검사 함수도 따로 모아두고 재사용하기!</p>
</blockquote>
<p>📁 utils -&gt; <code>validations.js</code></p>
<pre><code class="language-jsx">export function isEmail(value) {
  return value.includes(&#39;@&#39;);
}

export function isNotEmpty(value) {
  return value.trim() !== &#39;&#39;;
}

export function hasMinLength(value, minLength) {
  return value.length &gt;= minLength;
}

export function isEqualsToOtherValue(value, otherValue) {
  return value === otherValue;
}</code></pre>
<p>-&gt; 이러면 <code>boolean</code> 으로 값이 반환!</p>
<blockquote>
<p>3️⃣ input 에 대한 입력상태 관리 + 유효성 검증은 <strong><code>Custom Hook</code>으로!</strong></p>
</blockquote>
<p>📁 Hooks -&gt; <code>useInput.js</code></p>
<p><code>useInput.js</code></p>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;

export function useInput(defaultValue, validationFn) {
  const [enteredValue, setEnteredValue] = useState(defaultValue);
  const [didEdit, setDidEdit] = useState(false);

  const valueIsValid = validationFn(enteredValue);

  function handleInputChange(e) {
    setEnteredValue(e.target.value);
    setDidEdit(false);
  }

  function handleInputBlur() {
    setDidEdit(true);
  }

  return { value: enteredValue, handleInputBlur, handleInputChange, hasError: didEdit &amp;&amp; !valueIsValid };
}</code></pre>
<p><code>custom hook 사용</code></p>
<pre><code class="language-jsx">  const {
    value: emailValue,
    handleInputBlur: handleEmailBlur,
    handleInputChange: handleEmailChange,
    hasError: emailHasError,
  } = useInput(&#39;&#39;, (value) =&gt; isEmail(value) &amp;&amp; isNotEmpty(value));</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 완벽 가이드] Section 16 :  Custom Hook]]></title>
            <link>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-16</link>
            <guid>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-16</guid>
            <pubDate>Fri, 21 Jun 2024 17:48:25 GMT</pubDate>
            <description><![CDATA[<p>본 포스트는 <a href="https://www.udemy.com/course/best-react">Udemy 리액트 완벽가이드 2024</a> 를 듣고 정리한 내용입니다.</p>
<blockquote>
<p>목차 🌳
1️⃣ [복습] Hook 만드는 규칙
2️⃣ Custom Hook 만드는 이유
3️⃣ Custom Hook 만드는 방법 + 사용해보기</p>
</blockquote>
<h2 id="📁-복습-hook-만드는-규칙">📁 [복습] Hook 만드는 규칙</h2>
<blockquote>
<p>1️⃣ 리액트 Component function 안이나, 혹은 <strong>다른 hook 안에서 사용 가능</strong>!!
2️⃣ Component function 최상단에서 사용해야 함
-&gt; if 조건문 안에 넣는등 중첩 절대 안됨 ❌</p>
</blockquote>
<p>여기에서 중요한 포인트는 바로 1번인데, 
이전엔 <code>Component function</code> 안에서 작성해야 한다고 배웠지만, 사실은 다른 <code>Hook function</code> 안에서도 부를 수 있다!! </p>
<p>아무튼 우린 custom hook 을 만들어서 일정 기능을 수행해내는 친구들을 함수로 만들어, 재사용할 것이다!!
<del>(이제너해 플젝때 커스텀 훅 하나도 없었는데.. 무지의 폐해)</del></p>
<h2 id="📁-custom-hook-이-뭔가">📁 Custom Hook 이 뭔가</h2>
<blockquote>
<p> React에서 <strong>상태 관리와 로직을 재사용</strong>하도록 함!
<code>React Hook</code>과 동일하게, <strong>use로 시작하는 이름</strong>을 사용하며, 내부에서 <code>Hook</code>을 사용함</p>
</blockquote>
<h2 id="📁-custom-hook-만드는-방법--사용해보기">📁 Custom Hook 만드는 방법 + 사용해보기</h2>
<blockquote>
<p><strong>작성방법 🧳</strong>
1️⃣ <code>Custom Hook</code>의 이름은 <strong>무조건 <code>use~</code>으로 시작!</strong> 
-&gt; react hook 을 사용하기 위해서는, <code>hook</code>의 한 종류라고 알려줘야함
2️⃣  <code>Custom Hook</code> 내부에서 <code>useState</code>, <code>useEffect</code>, <code>useContext</code> 등 <strong>기존 React Hook을 사용</strong>할 수 있으며, <strong>API 호출과 같은 로직</strong>을 수행 가능
-&gt; 최대한 많은 컴포넌트에서 재사용될 수 있도록 설계
3️⃣ 리턴 값에, 반환할 상태나 상태 변경 함수 넣어주기</p>
</blockquote>
<p><strong><code>useFetch 예시</code></strong></p>
<pre><code class="language-jsx">import { useEffect, useState } from &#39;react&#39;;

export function useFetch(fetchFn, initalValue) {
  const [fetchedData, setFetchedData] = useState(initalValue);
  const [isFetching, setIsFetching] = useState(false);
  const [error, setError] = useState();

  useEffect(() =&gt; {
    async function fetchData() {
      setIsFetching(true);
      try {
        const places = await fetchFn();
        setFetchedData(places);
      } catch (error) {
        setError({ message: error.message || &#39;Failed to fetch places.&#39; });
      }

      setIsFetching(false);
    }

    fetchData();
  }, []);
  return {
    fetchedData,
    setFetchedData,
    isFetching,
    error,
  };
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 완벽 가이드] Section 15: Http 요청보내기]]></title>
            <link>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-15-Http-%EC%9A%94%EC%B2%AD%EB%B3%B4%EB%82%B4%EA%B8%B0</link>
            <guid>https://velog.io/@gonn-i/React-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-Section-15-Http-%EC%9A%94%EC%B2%AD%EB%B3%B4%EB%82%B4%EA%B8%B0</guid>
            <pubDate>Fri, 21 Jun 2024 14:37:37 GMT</pubDate>
            <description><![CDATA[<p>본 포스트는 <a href="https://www.udemy.com/course/best-react">Udemy 리액트 완벽가이드 2024</a> 를 듣고 정리한 내용입니다.</p>
<blockquote>
<p>목차 🌳
1️⃣ 리액트 앱을 백엔드/ DB와 연결하기 
2️⃣ 데이터 fetch 방법
3️⃣ 데이터 send 방법 </p>
</blockquote>
<h2 id="📁-리액트-앱을-백엔드-db와-연결하기">📁 리액트 앱을 백엔드/ DB와 연결하기</h2>
<blockquote>
<p><strong>HTTP 요청을 보내고 응답을 받음으로써</strong>, 프론트와 서버(DB)는 연결됨</p>
</blockquote>
<p>보안 이슈로 인해, 리액트 앱에서 직접적으로 서버와 데베는 연결될 수 없음 </p>
<h2 id="📁-데이터-fetch--http-에러-다루기">📁 데이터 fetch + HTTP 에러 다루기</h2>
<blockquote>
<p><strong>오류의 유형화</strong> 
1️⃣ 요청을 보내는 것에 실패하여, 백엔드에 전달되지 못 하는 경우 
2️⃣ 요청이 성공적으로 백엔드에 전달되었지만, 백엔드에서 에러 응답을 보내는 경우</p>
</blockquote>
<p>일단 응답이 에러인지 아닌지 확인 후, 핸들링하기</p>
<pre><code class="language-js">    async function fetchPlaces() {
      const res = await fetch(&#39;http://localhost:3000/places&#39;);
      const data = await res.json();

      if(res.ok) {} // 200 ~ 300
      if  (!res.ok) { // 400 ~ 500
          const eror = new Error (&#39;Failed to fetch places&#39;);
        throw error;
      } 

    }</code></pre>
<p>근데 이상태로 에러를 throw 하면 앱이 깨지기 때문에, 사용자에게 무척이나 좋지 못함.
try - catch문으로 에러가 발생할때 실행할 코드 작성하기 </p>
<p><code>try</code> -&gt; 실패할 수 있는 코드를 담고 있음
<code>catch</code> -&gt; error를 만났을때 실행할 코드를 담고 있음</p>
<p>또한, 에러를 다룰 수 있도록 err 상태를 관리해줘야함!!
<strong><code>if(err) -&gt; 특정 UI 도출!</code></strong></p>
<pre><code class="language-jsx"> const [error, setError] = useState();

  useEffect(() =&gt; {
    setIsFetching(true);
    async function fetchPlaces() {
      try {
        const res = await fetch(&#39;http://localhost:3000/places&#39;);
        const data = await res.json();

        if (!res.ok) throw new Error(&#39;Failed to fetch places&#39;);

        setAvailablePlaces(data.places);
      } catch (err) {
        setError({ message: err.message || &#39;장소를 못 가져왔어요&#39; });
      }
      setIsFetching(false);
    }
    fetchPlaces();
  }, []);

  if (error) {
    return &lt;Error title=&quot;오류 발생!&quot; message={error.message} /&gt;;
  }</code></pre>
<h2 id="📁-데이터-post-방법">📁 데이터 POST 방법</h2>
<pre><code class="language-jsx">export async function updateUserPlaces(places) {
  const res = await fetch(&#39;http://localhost:3000/user-places&#39;, {
    method: &#39;PUT&#39;,
    body: JSON.stringify({ places: places }),
    headers: {
      &#39;Content-Type&#39;: &#39;application/json&#39;,
    },
  });

  const resData = await res.json();
  if (!res.ok) throw new Error(&#39;Failed to update User Place&#39;);

  return resData.message;
}</code></pre>
<p>fetch는 기본적으로 get 메소드를 수행하기 때문에, 요청을 보낼떄 method를 수정해서 보내줘야 한다.
그리고 body에 넣을 값을 넣어주고, 헤더엔 보낼 내용의 유형이 JSON임을 알려야 함! </p>
<p>기본적인 내용이기에 이만 말줄임! 🐴</p>
<p><a href="https://github.com/gonn-i/React-complete-guide/tree/master/15%20HTTP%20Requests">Git hub LINK</a></p>
]]></description>
        </item>
    </channel>
</rss>