<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jo_love.log</title>
        <link>https://velog.io/</link>
        <description>Lv.1🌷</description>
        <lastBuildDate>Mon, 01 May 2023 09:54:21 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jo_love.log</title>
            <url>https://images.velog.io/images/jo_love/profile/15d50a6a-fa25-4ba6-b654-7c69725fd45c/개발지상주의.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jo_love.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jo_love" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[TIL102. Next js의 pre-rendering과 hydrarion]]></title>
            <link>https://velog.io/@jo_love/TIL102.-Next-js%EC%9D%98-pre-rendering%EA%B3%BC-hydrarion</link>
            <guid>https://velog.io/@jo_love/TIL102.-Next-js%EC%9D%98-pre-rendering%EA%B3%BC-hydrarion</guid>
            <pubDate>Mon, 01 May 2023 09:54:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>next.js pre-rendering 방식의 종류와 hydration개념 및 관련 오류에 대해 정리해보자.</p>
</blockquote>
<h2 id="렌더링-방식csr-ssr">렌더링 방식(CSR, SSR)</h2>
<h3 id="csrclient-side-rendering-client에서-렌더링">CSR(Client Side Rendering): client에서 렌더링</h3>
<p>유저가 웹사이트 방문 → 브라우저에서 서버로 소스(컨텐츠) 요청 → 서버에서 브라우저로 빈 html로 연결된 js 링크를 보내줌 → 브라우저에서 js 파일 다운로드 후 동적 dom 생성 </p>
<h4 id="csr의-특징">CSR의 특징</h4>
<ul>
<li>빈 HTML을 내려주기 때문에 서버 부하가 적다.</li>
<li>js까지 모두 다운로드 받은 후 동적 DOM을 생성하는 시간을 기다려야하기 때문에 초기 로딩속도가 느리다.</li>
<li>처음에 모두 받아왔기 때문에 서버에 해당 페이지의 데이터만 받아오면 되서 페이지 전환이 빠르다.</li>
<li>웹 크롤러봇는 html기반으로 요소를 찾는데 html이 비어있어서 정보를 얻을 수가 없기 때문에 SEO(검색엔진이 정보를 얻는 과정)에 불리하다.
(*구글 크롤러 봇은 js로도 크롤링할 수 있지만, 국내 검색봇들은 html기반으로 요소를 찾음)</li>
</ul>
<h3 id="ssrserver-side-rendering">SSR(Server Side Rendering)</h3>
<p>유저가 웹사이트 방문 → 브라우저에서 서버로 소스(컨텐츠) 요청 → 서버에서 브라우저로 렌더링 준비를 마친 html과 js code를 보내줌 → 브라우저는 전달받은 html 렌더하고, js code를 다운로드 후, html에 js로직을 연결</p>
<h4 id="ssr의-특징">SSR의 특징</h4>
<ul>
<li>js 다운로드를 받기 전에 html을 먼저 렌더링하기 때문에 초기 로딩속도가 빠르다.
(*하지만, 이 시점에 사용자가 버튼을 클릭하면, 인터렉션이 일어나지 않을 수 있다.)</li>
<li>SEO에 유리<h2 id="nextjs의-렌더링-과정hydrate">Next.js의 렌더링 과정(hydrate)</h2>
<h3 id="csr--ssr">CSR + SSR</h3>
: 첫페이지는 서버에서 받아 렌더링하고, 뒤에 발생하는 라우팅은 next/link, next/router 를 활용해서 내부적으로 CSR 방식을 사용함(url창에 직접 주소를 치고 들어갔을 시에는 SSR or SSG인데 link, router를 이용하면 CSR)</li>
</ul>
<h3 id="hydration">hydration</h3>
<p>: Next.js는 클라이언트에게 웹페이지를 보내기 전에 server side 단에서 Pre-Rendering 된 HTML 파일을 클라이언트에게 전송한다. 이후에 리액트가 번들링된 자바스크립트 코드들을 클라이언트에게 전송한다. 자바스크립트 코드 이전에 보내진 HTML DOM 요소 위에서 한번 더 렌더링을 하면서 각자 요소들을 찾아가며 매칭힌다.이 과정을 통해 웹 페이지가 정상적으로 동작을 하게 되고, 이 과정을 바로 ‘<strong>Hydration</strong>’ 이라고 한다.</p>
<p>→ 맨 처음 응답받는 요소 document Type의 HTML 파일-&gt; css 파일 → react 코드들이 렌더링된 js파일</p>
<p>서버 단에서 한 번 클라이언트 단에서 한 번 더 렌더링하면 비효율적이라고 생각할 수 있지만, pre-rendering한  document는 모든 js 요소들이 빠진 굉장히 가벼운 상태이므로 클라이언트에게 빠른 로딩이 가능하다. </p>
<p>*각 DOM 요소에 자바스크립트 속성을 매칭 시키기 위한 목적이므로 실제 웹 페이지를 다시 그리는 paint 함수는 호출하지 않는다.</p>
<h3 id="관련-코드">관련 코드</h3>
<pre><code class="language-js">// server/render.tsx

const renderDocument = async () =&gt; {
    // ...
    async function loadDocumentInitialProps(
      renderShell?: (_App: AppType, _Component: NextComponentType) =&gt; Promise&lt;ReactReadableStream&gt;
    ) {
      // ...
      const renderPage: RenderPage = (
        options: ComponentsEnhancer = {}
      ): RenderPageResult | Promise&lt;RenderPageResult&gt; =&gt; {
        // ...
        const html = ReactDOMServer.renderToString(
          &lt;Body&gt;
            &lt;AppContainerWithIsomorphicFiberStructure&gt;
              {renderPageTree(EnhancedApp, EnhancedComponent, {
                ...props,
                router,
              })}
            &lt;/AppContainerWithIsomorphicFiberStructure&gt;
          &lt;/Body&gt;
        );
        return { html, head };
      };
    }</code></pre>
<p> ReactDOMServer.renderToString(element) : reactNode를 HTML 문자열로 반환</p>
<pre><code class="language-js">// CLIENT/NEXT.JS

function renderReactElement(domEl: HTMLElement, fn: (cb: () =&gt; void) =&gt; JSX.Element): void {
  //...
  const reactEl = fn(shouldHydrate ? markHydrateComplete : markRenderComplete);

  // ...
  if (shouldHydrate) {
    ReactDOM.hydrate(reactEl, domEl);
    shouldHydrate = false;
  } else {
    ReactDOM.render(reactEl, domEl);
  }
}
</code></pre>
<p>ReactDOM.render(element, container[, callback]) : ReactElement 를 렌더링</p>
<p>ReactDOM.hydrate(element, container[, callback]) : DOM요소에 이벤트 리스너를 적용하고 렌더링을 진행</p>
<p>-&gt; Next.js는 서버에서 HTML 문서를 문자열로 가져온 후, 클라이언트에서는  ReactDom.render 함수를 이용하여 ReactElement 를 렌더링하고, , ReactDOM.hydrate라는 함수를 이용하여 DOM요소에 이벤트 리스너를 적용하고 렌더링을 진행한다.</p>
<h3 id="pre-rendering-종류">pre-rendering 종류</h3>
<h4 id="ssgstatic-site-generation">SSG(Static-Site-Generation)</h4>
<p>: HTML을 빌드 타임에 생성한다. 그 이후에는 CDN으로 캐시되어지고,  pre-render된 HTML은  매 요청마다 재사용 된다. 인터넷 연결이 느리거나 불안정한 경우에도 사용자에게 신속하게 제공될 수 있다.
ex)블로그 게시물, 제품 목록: 이커머스 사이트에서 상품 세부 정보, 포트폴리오</p>
<p>SSG 방식으로 데이터를 가져올 때 getStaticProps, getStaticPaths 사용한다.(*next.js 12 버전 기준)</p>
<pre><code class="language-js">function Blog({ posts }) {
  return (
    &lt;ul&gt;
      {posts.map((post) =&gt; (
        &lt;li&gt;{post.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}

export async function getStaticProps() {
// 빌드 시, api 호출해서 html을 만들어준다.
  const res = await fetch(&#39;https://.../posts&#39;);
  const posts = await res.json();

  return {
    props: {
      posts,
    },
  }
} 

export default Blog</code></pre>
<h4 id="ssr">SSR</h4>
<p>: 매 요청이 일어날 때 마다 HTML을 생성하는 방식이다. 데이터가 자주 업데이트되는 페이지에 사용된다. CDN에 캐쉬를 저장하지 않아 SSG와 비교해서 속도는 느리지만 항상 최신상태를 유지할 수 있다.</p>
<pre><code class="language-js">function DynamicPage({ data }) {
 ...jsx
}

export async function getServerSideProps() {

  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // Pass data to the page via props
  return { props: { data } }
} 

export default DynamicPage</code></pre>
<h4 id="isrincremental-static-regeneration">ISR(Incremental Static Regeneration)</h4>
<p>: SSG의 장점을 살리면서 비교적 최신 데이터를 반영할 수 있다. SSG와 동일하게 빌드 시점에 페이지를 생성하지만, 일정 시간이 지난 후 페이지를 새로 생성한다. 빌드를 배포한 이후에도 설정한 주기마다 데이터의 갱신여부를 검사하고 업데이트된 데이터로 페이지를 다시 정적으로 생성한다. 빌드된 후 이 페이지에 재방문이 없으면, revalidate 시간이 경과했더라도 백그라운드에서 재 빌드되지 않는다.</p>
<pre><code class="language-js">export async function getStaticProps() {

  const res = await fetch(&#39;https://.../posts&#39;);
  const posts = await res.json();

  return {
    props: {
      posts,
    },
     revalidate: 15,
  }
}</code></pre>
<h2 id="hydration-관련-오류">hydration 관련 오류</h2>
<p>서버에서 받아온 UI(pre-render)와 브라우저에서 자체적으로 렌더링한 UI tree 간의 차이때문에 hydration 오류가 발생한다. 구체적인 원인은 밑에 서술</p>
<h3 id="원인">원인</h3>
<ul>
<li>환경에 따라 다른 ui 나타내는 코드 </li>
</ul>
<p>node 서버에서 실행 시에는 최상위 객체가 window가 없고, 브라우저에서 실행 시에는 최상위 객체 window가 존재하기 때문에 서로 환경이 다르다.</p>
<pre><code class="language-js">function MyComponent() {
  // This condition depends on `window`. During the first render of the browser the `color` variable will be different
  const color = typeof window !== &#39;undefined&#39; ? &#39;red&#39; : &#39;blue&#39;
  // As color is passed as a prop there is a mismatch between what was rendered server-side vs what was rendered in the first render
  return &lt;h1 className={`title ${color}`}&gt;Hello World!&lt;/h1&gt;
}</code></pre>
<ul>
<li>element 순서 안지킬 경우<pre><code class="language-js">&lt;p&gt;
&lt;div&gt;This is test&lt;/div&gt;
&lt;/p&gt;</code></pre>
<h4 id="실제-겪었던-hydration-오류">실제 겪었던 hydration 오류</h4>
실제 개발환경에서는 반응형을 위해 사용했던 ‘useMediaQuery’ hook 때문에 hydration 문제가 발생했다.useMediaQuery는 window.innerWidth 등과 같은 window 객체를 참조하는 코드를 가지고 있는데 서버에서 렌더링한 html에는 window 객체가 초기화되지 않았기에 오류가 났었다.</li>
</ul>
<p>→ 컴포넌트가 마운트되고, 그 이후에 useEffect hook 내부에서 isMobile 변수를 업데이트시켜 컴포넌트를 재랜더링 시키는 방법으로 해결했다.</p>
<pre><code class="language-js">function App() {
    const [isMobile, setIsMobile] = useState(false);
    const mobile = useMediaQuery({ query: &quot;(max-width: 600px)&quot;});

    useEffect(() =&gt; {
        if(mobile) setIsMobile(mobile);
    }, [mobile])

    return &lt;div style={ { 
        display: &#39;flex&#39;,        
        flexDirection: isMobile ? &#39;column&#39; : &#39;row&#39;,
    } } /&gt;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL101. Web3 NFT dApp 만들기]]></title>
            <link>https://velog.io/@jo_love/TIL99.-Web3-NFT-dApp-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@jo_love/TIL99.-Web3-NFT-dApp-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 15 Dec 2022 08:46:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>nft minting, staking 페이지를 만들며 NFT dApp 만드는 일련의 과정들을 정리해보자.</p>
</blockquote>
<h1 id="minting-page">minting page</h1>
<p>nft에서 말하는 민팅은 두가지 뜻이 있다. 하나는 판매자 입장에서 가상화폐로 NFT를 만들어 낸다는 뜻이고, 다른 하나는 구매자 입장에서 NFT를 구매한다는 뜻도 된다. 
지금 다루고 있는 dapp상 민팅 페이지는 NFT를 구매할 수 있는 페이지를 말한다.</p>
<h2 id="minting-과정">minting 과정</h2>
<p><img src="https://velog.velcdn.com/images/jo_love/post/5878f109-b145-4075-a243-b42c6be1002a/image.png" alt=""></p>
<h3 id="1-web3-지갑-연결">1. web3 지갑 연결</h3>
<p>web3 지갑은 dpp과 상호작용하고 이더리움 블록체인에서 거래를 수행하는데 사용된다. 지갑 자체가 암호자체를 보관하는 것은 아니고, 디지털 자산에 액세스할 수 있는 정보를 저장한다.</p>
<p>web에서는 직접적으로 스마트 컨트랙트와 상호작용할 수는 없다. 그래서 JSON-RPC 프로토콜을 통해 이더리움 노드와 통신하는 기능을 포함한 JavaScript 라이브러리 <code>web3</code> 혹은 <code>ethers</code> 사용한다. 라이브러리는 다양한 기능을 포함하고 이더리움 지갑에 연결할 수 있게 해준다.</p>
<ul>
<li>Smart Contract
스마트 컨트랙트란 블록체인에 등록되어 작동하는 프로그램으로, 서면으로 이루어지던 금융거래, 부동산 계약, 공증 등 다양한 형태의 계약을 코드로 구현하고 특정 조건이 충족되었을 때 해당 계약이 이행되게 하는 script를 통해 계약을 체결하고 이행하는 것을 말한다.</li>
<li>JSON-RPC
대부분이 이더리움 환경 상에서 스마트 컨트랙트를 만드는데 이때 이더리움 노드들은 <strong>JSON-RPC</strong>라고 불리는 언어로만 소통할 수 있다.
<img src="https://velog.velcdn.com/images/jo_love/post/7858b26b-53be-477c-8c29-4e9ba9c5e173/image.png" alt=""></li>
</ul>
<h4 id="ethersjs">ethers.js</h4>
<p>(ethers를 사용하여 프로젝트를 구성하였기 때문에 ethers에 대한 기본적인 개념이나 사용법에 대해 작성해보았다.)
web3.js 이후에 나온 라이브러리로 provider와 signer를 주입하는 형태로 간편하고 유연한 코드 작성이 가능하다.</p>
<ul>
<li>Provider
이더리움 네트워크 연결을 위한 추상화(abstraction)를 제공하는 클래스로 블록 ,트랜잭션 등의 읽기 전용 액세스를 제공한다.
(개인이 노드가 될수없기 때문에 네트워크의 정보를 대신 제공해주는 역할을 한다.)</li>
<li>Signer
: 직간접적으로 private key에 대한 접근권을 가지고 있는 클래스로 계정의 ether를 사용하여 트랜잭션을 수행할 수 있도록 한다.
signer는 이더리움 계정을 통해서 트랜잭션에 사인을 해서 이더리움 네트워크 상의 정보를 변경하는 트랜잭션을 실행할 수 있게 해준다.</li>
<li>Contract
: 이더리움 네트워크 상의 특정한 컨트랙트와 연결하여 일반적인 js의 오브젝트처럼 사용할 수 있도록 한다. <pre><code class="language-js">const contract = new ethers.Contract(contractAddress, ABI, provider or signer);</code></pre>
signer대신 provider를 넘기면 읽기 전용</li>
</ul>
<h3 id="2구매-수단-선택">2.구매 수단 선택</h3>
<p>native token(Matic coin), ERC20 token 어떤 구매 수단을 선택하느냐에 따라 프로세스가 달라진다. native token선택 시 별도의 과정없이 바로 mint contract를 부를 수 있다.  </p>
<h3 id="3-approve-트랜잭션">3. approve 트랜잭션</h3>
<ul>
<li>유저의 지갑 내 토큰을 다른 주소가 가져갈 수 있는 권한을 승인하는 트랜젝션이다. </li>
<li>현재 토큰의 개수보다 더 많이 승인하는 것도 가능하다.
&#39;ethers.constants.MaxUint256&#39; 를 사용하면 무한대로 approve가 가능하다.<pre><code class="language-js">function approve(address _spender, uint256 _value) public returns (bool success)</code></pre>
유저지갑에서(from) <code>spender주소</code>(to)에 <code>value</code>만큼의 토큰을 이동할 수 있는 권한을 승인</li>
<li>allowance
spender(대리자)가 owner(실제 토큰 보유자) 대신 거래할 수 있는 금액<pre><code class="language-js">function allowance(address _owner, address _spender) public view returns (uint256) {
      return _allowances[owner][spender];
  }
</code></pre>
</li>
</ul>
<p>//return example 
{
  &#39;A&#39; : {
    &#39;B&#39;: 3000,
    &#39;C&#39;: 5000
  }
}
// -&gt; A가 가진 토큰 중 3000을 B에게 5000을 C에게 맡김</p>
<pre><code>구매 시마다 approve과정을 거친다면 사용자 편의상 좋지 않을 수 있다. 그래서 approve 금액을 max로 설정해놓고, allowance 함수를 통해 금액을 확인하고, 구매하려는 금액보다 allowance금액이 크다면 approve는 다시 진행하지 않는 방법이 있다.
(다만, 신뢰할 수 없는 스마트 컨트랙트에 max권한을 맡긴다는 것은 위험성이 있을 수 있다.)
```js
 useEffect(() =&gt; {
    (async () =&gt; {
      // 구매수단이 token일 경우
      if (selectedOpt === CRYPTOCURRENCY.TOKEN) {
     // &#39;allowance&#39;로 금액 확인
        const allowanceAmount = await erc20Contract.allowance(account, MINTER_CONTRACT_ADDRESS);
    // 총 가격 = 개당 가격 * 선택한 수량
        const currentPrice = ethers.utils.parseEther(String(getSelectedInfo().price * count));
    // allowance 금액이 &#39;currentPrice&#39;보다 크면 true
        const pass = ethers.BigNumber.from(allowanceAmount).sub(currentPrice) &gt;= ethers.BigNumber.from(0);
      }
    })();
  }, [selectedOpt, count]);</code></pre><h3 id="4-mint-nft">4. mint NFT</h3>
<p>transfer token + mint NFT 두 기능 합쳐져서 진행</p>
<ul>
<li><p>setMint 메소드 (갯수, 0 or 1-&gt; 코인=0 토큰=1, signer )</p>
</li>
<li><blockquote>
<p>임의의 smart contract를 이용한 메소드</p>
</blockquote>
</li>
<li><p>트랜젝션의 실패,성공 여부를 확인하기 위해서 폴링을 돌려주는 <code>waitForTransaction</code> 메소드를 사용한다. transaction.status가 &#39;0&#39;이면 실패, &#39;1&#39;이면 성공 </p>
<pre><code class="language-js">const mintNft = async () =&gt; {
  const minterContract = new ethers.Contract(MINTER_CONTRACT_ADDRESS, MINTER_CONTRACT_ABI, library?.getSigner());
  const response = await minterContract.setMint(count, 1, {
    gasLimit: 500000,
  });
  await checkTransactionStatus(response.hash);
};

const checkTransactionStatus = async (hash: string) =&gt; {
  const provider = new ethers.providers.JsonRpcProvider(process.env.REACT_APP_RPC_URL);
  setLoading(true);
  await provider.waitForTransaction(hash).then(function (transaction) {
    setLoading(false);
    if (transaction.status === 1) {
      setPhase(PHASE.FREE_OF_APPROVAL);
      setCount(1);
      alert(&quot;민팅에 성공하였습니다.&quot;);
    } else {
      alert(&quot;민팅에 실패하였습니다.&quot;);
    }
  });
};</code></pre>
</li>
<li><p>임의의 smart contract를 이용한 샘플 코드 예시</p>
<h2 id="staking-page">staking page</h2>
<p>보유한 NFT를 블록체인 네트워크에 고정시키는 것을 말한다. 일종의 은행예치시스템과 비슷하다고 생각하면 이해하기 쉽다.
반대로 unstaking을 하면 스테이킹한 자산을 해제하여 보유한 암호화폐를 출금 또는 거래 가능한 상태로 만들 수 있다.
<img src="https://velog.velcdn.com/images/jo_love/post/d9ea1c27-d665-4da9-9f50-8fb894f949a2/image.png" alt=""></p>
<h3 id="nft-목록-불러오기">nft 목록 불러오기</h3>
<p>(*지갑 연결과 approve 프로세스 이전과 동일하거나 비슷하기 때문에 생략)</p>
</li>
</ul>
<p>이전에 민팅해서 구매한 nft들은 각각의 tokenId를 가지고 있다. 보유자의 주소를 넣으면 tokenId를 담은 목록을 보여준다.</p>
<pre><code class="language-js"> nftContract.tokensURI(account);</code></pre>
<h3 id="stake">stake</h3>
<p>stake할 아이템의 tokenId를 넘겨준다.</p>
<pre><code class="language-js">const stakeToken = async () =&gt; {
  const stakingContract = new ethers.Contract(
    NFT_STAKING_CONTRACT_ADDRESS,
    NFT_STAKING_CONTRACT_ABI,
    library?.getSigner(),
  );
    const staking = await stakingContract.nftStaking(toNumber(toStakeItems), { gasLimit: 40000000 });
    setIsLoading(true);
    await provider.waitForTransaction(staking.hash).then((transaction) =&gt; {
      if (transaction.status === 1) {
        setAfterStaking((prev) =&gt; !prev);
      } else {
        alert(&#39;스테이킹 실패. Staking failed.&#39;);
      }
      setIsLoading(false);
    });
  };</code></pre>
<p>*임의의 smart contract를 이용한 샘플 코드 예시</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL100. Next.JS 13 새로운 특징들
]]></title>
            <link>https://velog.io/@jo_love/TIL100.-Next.JS-13-%EC%83%88%EB%A1%9C%EC%9A%B4-%ED%8A%B9%EC%A7%95%EB%93%A4</link>
            <guid>https://velog.io/@jo_love/TIL100.-Next.JS-13-%EC%83%88%EB%A1%9C%EC%9A%B4-%ED%8A%B9%EC%A7%95%EB%93%A4</guid>
            <pubDate>Mon, 12 Dec 2022 16:22:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>v13 변경사항에 대해 알아보자.</p>
</blockquote>
<h2 id="app-directory">app/ Directory</h2>
<p><strong>*app/ 디렉토리 구조는 beta버전이기 때문에 실제 프로덕션 레벨에서는 권장되지 않는다.</strong>
<img src="https://velog.velcdn.com/images/jo_love/post/1e3d4d3f-e83b-419f-899d-e6cf1596cd3f/image.png" alt="">
기존에는 pages 디렉토리 안에 파일을 생성하여 Automating Routing을 실행했지만, 13버전부터는 app/이라는 새로운 디렉토리가 등장하였다.
파일 시스템 기반에서 디렉터리 기반 라우팅 시스템으로 전환하였다.
현재는 pages와 app 디렉토리가 공존하는 베타 버전으로 제공된다.
app 디렉토리 안에는 <code>layout.js</code>, <code>page.js</code>, <code>head.js</code>파일이 존재한다.
 *dev server를 돌리면 자동적으로 head.js와 layout.js를 생성해준다.
<img src="https://velog.velcdn.com/images/jo_love/post/6f8d9330-c1d7-4e26-a80f-673f0b68ec0c/image.png" alt=""></p>
<p>-page.js : 고유한 ui를 정의하는데 사용(index.js같은 느낌)
-layout.js : 여러 경로에서 공유되는 ui를 정의하는데 사용(nav, footer 컴포넌트)</p>
<pre><code class="language-js">// page.js
const Home = () =&gt; {
  return &lt;div&gt;Homepage&lt;/div&gt;;
};
export default Home;</code></pre>
<pre><code class="language-js">// layout.js
import &quot;styles/globals.css&quot;;
import Header from &quot;./Header&quot;;

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;html&gt;
      &lt;body&gt;
        &lt;Header /&gt;
        {children}
      &lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<h2 id="data-fetching">Data Fetching</h2>
<p>react에서 데이터를 불러올 때 최초 렌더링 시 빈 배열의 형태를 가지며 그 이후 useEffect를 통해 값이 채워지게 된다. 그 다음 렌더링을 통해 그 렌더링된 값이 보여지는데 이 과정에서 화면에 깜빡임이 생길 수 있다. 
이를 방지하기 위해 next-js에서는 사전 렌더링(pre-rendering)을 위한 data fetching을 할 수 있는 기능으로 getStaticProps와 getServerSideProps가 있다. </p>
<h3 id="getstaticprops">getStaticProps</h3>
<p><code>빌드 시에 한 번</code>만 호출되고, 바로 static file로 빌드된다.
SSG (Static Site Generation) 수정될 일이 없는 경우에 사용하기 좋다.</p>
<h3 id="getserversideprops">getServerSideProps</h3>
<p>getServerSideProps는 getStaticProps와 다르게 page가 요청이 들어올 때마다 호출되며, 그 때마다 사전 렌더링을 진행한다.
요청 시마다 다시 호출되므로 자주 바뀌게 될 동적 데이터가 들어갈 때 더 사용하기 좋다.</p>
<h3 id="use">use</h3>
<p>13부터는 getServerSideProps, getStaticProps 대신에 <code>use</code>를 사용해야 한다.</p>
<ul>
<li>fetch 구문의 URL 뒤에 { cache: &#39;&#39; } 를 넣어줄 수 있는데, 안에 무엇이 들어가냐에 따라 기존의 getServerSideProps, getStaticProps와 유사한 기능을 쓸 수 있다.</li>
</ul>
<pre><code class="language-js">import { use } from &#39;react&#39;

const getData = async () =&gt; {
  const res = await fetch(&quot;www.example.com/movies/1&quot;);
  const data = await res.json();
  return data;
};

const page = () =&gt; {
 const movie = use(getData());

  return &lt;div&gt;{movie.title}&lt;div&gt;;
};

// getStaticProps
const getStaticProps = async () =&gt; {
  const res = await fetch(&quot;www.example.com/movies/1&quot;, {
    cache: &quot;force-cache&quot;,
  });
  const data = await res.json();
  return data;
};

// getServerSideProps
const getServerSideProps = async () =&gt; {
  const res = await fetch(&quot;www.example.com/movies/1&quot;, {
    cache: &quot;no-store&quot;,
  });
  const data = await res.json();
  return data;
};</code></pre>
<h2 id="기타-업그레이드된-기능들imagelinkfont">기타 업그레이드된 기능들(Image/Link/Font)</h2>
<h3 id="nextimage">next/Image</h3>
<ul>
<li><code>Image</code>컴포넌트를 통해서 이미지 파일을 넣을 수 있는데 자동으로 최적화된다. </li>
<li>이미지가 로딩중일 때, 자동적으로 width와 height를 설정해서  <strong>*<em>layout shift</em></strong>를 방지해준다.</li>
<li>페이지 내에서 실제로 필요할 때까지 리소스의 로딩을 미루는 &#39;lazy loading&#39;기법으로 로딩 시간을 단축하여 웹 성능을 향상시킨다.(with optional blur-up)</li>
</ul>
<p>*layout Shift: 이미지 사이즈가 정해져있지 않은 경우, 이미지를 기다리는 동안 기존의 레이아웃이 밀려나는 현상</p>
<pre><code class="language-js">import Image from &#39;next/image&#39;;

export default function Page() {
  return (
    // 1) Local Images
    &lt;Image
      src={profilePic}
      alt=&quot;profile&quot;
      // width={500} automatically provided
      // height={500} automatically provided
      // blurDataURL=&quot;data:...&quot; automatically provided
      // placeholder=&quot;blur&quot; // Optional blur-up while loading
    /&gt;
    // 2) Remote Images
    &lt;Image
      src=&quot;https://s3.amazonaws.com/my-bucket/profile.png&quot;
      alt=&quot;profile&quot;
      width={500}
      height={500}
    /&gt;
  );
}</code></pre>
<p>-&gt; <code>Remote image</code>를 사용하기 위해서는 width와 height 값을 지정해줘야 한다.</p>
<h3 id="nextfont">next/font</h3>
<ul>
<li>font 역시 자동으로 layout Shift가 방지된다.</li>
<li>⭐️구글 폰트가 내장되어 브라우저에서 구글 폰트 요청을 별도로 하지 않아도 사용할 수 있다.<h4 id="google-font">google font</h4>
<pre><code class="language-js">// app/layout.tsx
</code></pre>
</li>
</ul>
<p>import { Inter } from &#39;@next/font/google&#39;;</p>
<p>const inter = Inter({ subsets: [&#39;latin&#39;] })</p>
<p>export default function RootLayout({ children }: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={roboto.className}>
      <body>{children}</body>
    </html>
  );
}</p>
<pre><code>다양한 weight와 styles 사용 시 배열을 사용
```js
const roboto = Roboto({
  weight: [&#39;400&#39;, &#39;700&#39;],
  style: [&#39;normal&#39;, &#39;italic&#39;],
  subsets: [&#39;latin&#39;],
});</code></pre><h4 id="local-font">local font</h4>
<p>local font 역시 같은 방법으로 사용가능</p>
<pre><code class="language-js">import localFont from &#39;@next/font/local&#39;;

const myFont = localFont({ src: &#39;./my-font.woff2&#39; });

export default function RootLayout({ children }: {
  children: React.ReactNode;
}) {
  return (
    &lt;html lang=&quot;en&quot; className={myFont.className}&gt;
      &lt;body&gt;{children}&lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<h3 id="nextlink">next/link</h3>
<ul>
<li>Link Component 안에 더이상 <code>&lt;a&gt;</code> 태그를 사용하지 않아도 된다.</li>
<li>Link 태그에도 props나 함수를 사용할 수 있다.<pre><code class="language-js">// before
&lt;Link href=&quot;/about&quot;&gt;
&lt;a onclick={()=&gt;console.log(&#39;clicked&#39;)}&gt;About&lt;/a&gt;
&lt;/Link&gt;
</code></pre>
</li>
</ul>
<p>// after
&lt;Link href=&quot;/about&quot; onclick={()=&gt;console.log(&#39;clicked&#39;)}&gt;
  About
</Link>
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL99. Next.JS 13 환경설정]]></title>
            <link>https://velog.io/@jo_love/TIL99.-Next.JS-13</link>
            <guid>https://velog.io/@jo_love/TIL99.-Next.JS-13</guid>
            <pubDate>Fri, 09 Dec 2022 02:32:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>업데이트된 next.js 13를 사용해보자. (환경설정편)</p>
</blockquote>
<h2 id="nextjs란">Next.js란</h2>
<p>next.js는 react에서 <strong>SSR</strong>을 쉽게 구현할 수 있도록 Vercel에서 만든 프레임워크이다.</p>
<h2 id="nextjs를-사용하는-이유">Next.js를 사용하는 이유</h2>
<p><img src="https://velog.velcdn.com/images/jo_love/post/f4661249-0da5-4b24-8b8e-efb5ac10e5b2/image.png" alt=""></p>
<p>초기 웹사이트는 사용자가 다른 페이지로 이동할 때마다 서버에 요청을 해서 새로운 html을 불러와 매번 새로고침되는 방식이었다. 이를 보완하고자 나온 것이 단일 페이지로 최초 한 번 페이지 전체를 로드하는 SPA의 CSR방식이고, 대표적인 SPA 라이브러리가 react 이다. 
하지만, 검색엔진 최적화(seo)문제나 초기 페이지 로딩이 오래 걸린다는 문제점이 존재한다. 검색엔진봇은 js를 해석하기 힘들기 때문에 html에서 크롤링을 하는데,  CSR방식은 client에서 페이지를 구성하기 전에 빈 html을 보여주기 때문에 검색엔진이 어떠한 정보도 얻기 힘들다.
*구글은 브라우저 내에 Js엔진이 있기 때문에 CSR방식을 사용해도 SEO문제는 없다고 한다.
이때 next.js를 사용하면 앞에 말한 두가지 문제점을 해결할 수 있다. </p>
<h3 id="nextjs의-작동방식">Next.js의 작동방식</h3>
<ol>
<li>사용자가 초기 페이지 접속 요청 시, SSR방식으로 렌더링 될 html을 보낸다.</li>
<li>브라우저에서 js를 다운받고, react를 실행한다.</li>
<li>사용자가 페이지 이동할 경우, CSR방식으로 브라우저에서 처리한다.</li>
</ol>
<h2 id="nextjs-시작하기">Next.js 시작하기</h2>
<ul>
<li><h3 id="초기설정">초기설정</h3>
<h4 id="experimental">experimental</h4>
<pre><code class="language-js">// next.config.js
/** @type {import(&#39;next&#39;).NextConfig} */
module.exports = {
reactStrictMode: true,
experimental: {
  appDir:true,
}
}</code></pre>
: /app 경로는 아직 시험용이기 때문에 next.config.js파일에 <code>experimental</code> 설정이 필요</li>
</ul>
<h4 id="nextjs--css-in-js">Next.js &amp; CSS-in-JS</h4>
<p><code>css-in-js</code>형식으로 스타일링을 하면 js코드가 적용되기 전의 페이지가 렌더링되어서 스타일이 적용되는 않은 html 코드가 먼저 렌더링되기 때문에 초기설정이 필요하다.</p>
<p>기존설정</p>
<pre><code class="language-js">// _document.tsx
export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =&gt;
        originalRenderPage({
          enhanceApp: (App) =&gt; (props) =&gt;
            sheet.collectStyles(&lt;App {...props} /&gt;),
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: [initialProps.styles, sheet.getStyleElement()],
      }
    } finally {
      sheet.seal()
    }
  }
}</code></pre>
<p>-&gt; 13버전부터는 app디렉토리에 _app.tsx , _documents.tsx 는 사용하지 않기 때문에 다른 방식을 사용해야 한다.</p>
<ol>
<li>styled-components API를 사용하여 렌더링 중에 
생성된 모든 CSS 스타일 규칙과 해당 규칙을 반환하는 
함수를 수집하는 전역 레지스트리 구성 요소를 만든다.<pre><code class="language-js">// lib/styled-components.tsx
import React from &#39;react&#39;;
import { ServerStyleSheet, StyleSheetManager } from &#39;styled-components&#39;;
</code></pre>
</li>
</ol>
<p>export function useStyledComponentsRegistry() {
  const [styledComponentsStyleSheet] = React.useState(
    () =&gt; new ServerStyleSheet(),
  );</p>
<p>  const styledComponentsFlushEffect = () =&gt; {
    const styles = styledComponentsStyleSheet.getStyleElement();
    styledComponentsStyleSheet.instance.clearTag();
    return &lt;&gt;{styles}&lt;/&gt;;
  };</p>
<p>  const StyledComponentsRegistry = ({
    children,
  }: {
    children: React.ReactNode;
  }) =&gt; (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children as React.ReactElement}
    </StyleSheetManager>
  );</p>
<p>  return [StyledComponentsRegistry, styledComponentsFlushEffect] as const;
}</p>
<pre><code>2. 레지스트리에 수집된 스타일을 루트 레이아웃의 &lt; head &gt;태그에 삽입하는   `useServerInsertedHTML` hook을 사용하여 클라이언트 컴포넌트를 생성한다.
```js
&#39;use client&#39;;

import { useStyledComponentsRegistry } from &#39;../lib/styled-components&#39;;
import { useServerInsertedHTML } from &#39;next/navigation&#39;;

export default function RootStyleRegistry({
  children,
}: {
  children: React.ReactNode;
}) {
  const [StyledComponentsRegistry, styledComponentsFlushEffect] =
    useStyledComponentsRegistry();

  useServerInsertedHTML(() =&gt; {
    return &lt;&gt;{styledComponentsFlushEffect()}&lt;/&gt;;
  });

  return &lt;StyledComponentsRegistry&gt;{children}&lt;/StyledComponentsRegistry&gt;;
}</code></pre><p>3.root layout의 자식들을 style registry component로 감싼다. </p>
<pre><code class="language-js">import RootStyleRegistry from &#39;./RootStyleRegistry&#39;;

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    &lt;html&gt;
      &lt;body&gt;
        &lt;RootStyleRegistry&gt;{children}&lt;/RootStyleRegistry&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<ol start="4">
<li>styled-components를 사용하는 자식 컴포넌트
<code>use client</code>: 타사 npm 패키지의 구성요소에는 자체적으로 &quot;클라이언트 사용&quot;지침이 있기 때문에 서버 구성요소 내에서는 올바르게 작동하지 않는다.그렇기 때문에 css-in-js나 외부라이브러리 등을 사용 시에는 <code>client components</code>사용해 알려줘야한다.<pre><code class="language-js">// app/page.tsx
</code></pre>
</li>
</ol>
<p>&quot;use client&quot;; </p>
<p>import styled from &quot;styled-components&quot;;</p>
<p>function Home() {
  return <Container>I&#39;m homepage</Container>;
}
export default Home;
const Container = styled.div<code>background: red;</code>;</p>
<pre><code>*실습에서는 Tailwind CSS와 같은 CSS 파일을 출력하는 다른 방식을 사용하고 있다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[TIL98. Firebase V9로 CRUD 구현]]></title>
            <link>https://velog.io/@jo_love/TIL98.-Firebase-V9%EB%A1%9C-%EA%B2%8C%EC%8B%9C%ED%8C%90-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@jo_love/TIL98.-Firebase-V9%EB%A1%9C-%EA%B2%8C%EC%8B%9C%ED%8C%90-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 15 May 2022 07:17:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>firebase 사용해서 게시판 CRUD기능을 만들어보자.</p>
</blockquote>
<h2 id="firebase-v9">Firebase v9</h2>
<p>firebase는 벡엔드 기능을 클라우드 서비스 형태로 제공하기 때문에 백엔드없이 어플리케이션을 만들 때 사용하면 유용하다.
firebase가 버전 업데이트를 하면서 문법 등에 많은 변화가 생겼다. </p>
<h2 id="firebase-sdk-설정-및-구성">firebase SDK 설정 및 구성</h2>
<p>firebase 프로젝트 생성 후 Firebase를 초기화하여 사용하려는 제품의 SDK를 사용하기</p>
<pre><code class="language-js">import { initializeApp } from &#39;firebase/app&#39;;
import { getFirestore } from &#39;firebase/firestore&#39;;

const firebaseConfig = {
    apiKey: process.env.REACT_APP_API_KEY,
    authDomain: process.env.REACT_APP_AUTH_DOMAIN,
    projectId: process.env.REACT_APP_PROJECT_ID,
    storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_MESSAGIN_ID,
    appId: process.env.REACT_APP_APP_ID,
};

const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);</code></pre>
<h3 id="env">.env</h3>
<pre><code class="language-js">REACT_APP_API_KEY = abcdedf...
REACT_APP_AUTH_DOMAIN = abcdedf...
REACT_APP_PROJECT_ID = abcdedf...
REACT_APP_STORAGE_BUCKET = abcdedf...
REACT_APP_MESSAGIN_ID = abcdedf...
REACT_APP_APP_ID = abcdedf...</code></pre>
<p>환경변수는 노출되면 보안상 위험하기때문에 env파일을 활용해야 한다. </p>
<ul>
<li>변수명의 생성은 무조건 REACT_APP으로 시작</li>
<li>프로젝트 최상단에 위치할 것</li>
<li>개발 환경에서 개발서버가 실행중인 경우 환경변수 변경은 서버를 재시작해야 적용됨<h2 id="create">create</h2>
<code>addDoc</code> 메소드를 사용하여 구현한다.
addDoc(&#39; 키를 통해 가져온 DB &#39; , &#39; payload &#39;)<pre><code class="language-js">import { db } from &#39;../../lib/fbase&#39;;
import { addDoc, collection } from &#39;firebase/firestore&#39;;
</code></pre>
</li>
</ul>
<p>const handleSubmit = useCallback(
        async e =&gt; {
            e.preventDefault();
            try {
                const res = await addDoc(collection(db, &#39;board&#39;), boardContent);
                console.log(res);
            } catch (e) {
                console.log(e, &#39;error&#39;);
            }
        },
        [boardContent, viewContent],
    );</p>
<pre><code>## read
read는 query, getDocs 메소드가 있다. query에는 여러 조건을 넣을 수 있는데 공식문서에서 참조가능하다.
```js
import { getDocs, collection } from &#39;firebase/firestore&#39;;
import { db } from &#39;../../lib/fbase&#39;;

//전체 데이터 배열 가져오기
&lt;리스트 페이지&gt;
useEffect(() =&gt; {
        const fetchData = async () =&gt; {
            const snapshot = await getDocs(collection(db, &#39;board&#39;));
            const listArr = snapshot.docs.map(doc =&gt; ({
                id: doc.id,
                ...doc.data(),
            }));
            setViewContents(listArr);
        };
        fetchData().catch(e =&gt; console.log(e));
    }, []);

//특정 데이터만 가져오기 - firebase에서 생성된 id로 url id 만들어주고, params로 id get 
&lt;상세 페이지&gt;
import { doc, getDoc } from &#39;firebase/firestore&#39;;
import { db } from &#39;../../lib/fbase&#39;;
...
const { id } = useParams();

useEffect(() =&gt; {
        (async function fetchData() {
            const detailRef = doc(db, &#39;board&#39;, id);
            const res = await getDoc(detailRef);
            setData(res.data());
        })();
    }, []);</code></pre><h3 id="html-태그-제거-후-나타내는-방법">*html 태그 제거 후 나타내는 방법</h3>
<p>그대로 db에서 받아온 데이터를 바인딩하면 html태그가 그대로 적용되어 출력되는 현상이 발생한다. html이 적용된 데이터를 html로 변환해서 사용해야하는데 이때 <code>dangerouslySetInnerHTML</code> 를 사용한다.
리액트에서는 보안적인 문제로 html형태로 글을 출력하는 기능을 제공하지 않기 때문에 dangerouslySetInnerHTML를 사용해서 대신 요청하여 태그를 적용시킨다.</p>
<pre><code class="language-js">//사용법
&lt;main dangerouslySetInnerHTML={{ __html: data.content }} /&gt;</code></pre>
<h2 id="update">update</h2>
<p>updateDoc 메소드 사용하여 구현한다.</p>
<pre><code class="language-js">import { addDoc, collection, doc, updateDoc } from &#39;firebase/firestore&#39;;

const handleSubmit = useCallback(
        async e =&gt; {
            e.preventDefault();
            // 최초 등록 일 경우
            if (!editingMode) {
                try {
                    await addDoc(collection(db, &#39;board&#39;), boardContent);
                    navigate(&#39;/&#39;);
                } catch (e) {
                    console.log(e);
                }
            } else {
                // 수정 일 경우
                try {
                    const detailRef = doc(db, &#39;board&#39;, id);
                    await updateDoc(detailRef, { title: boardContent.title, content: boardContent.content });
                    navigate(&#39;/&#39;);
                } catch (e) {
                    console.log(e);
                }
            }
        },
        [boardContent],
    );</code></pre>
<h2 id="delete">delete</h2>
<p>deleteDoc 메소드 사용</p>
<pre><code class="language-js">import { doc, deleteDoc } from &quot;firebase/firestore&quot;;

const deletePost = async () =&gt; {
        const detailRef = doc(db, &#39;board&#39;, id);
        await deleteDoc(detailRef);
    };</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL97.Zustand]]></title>
            <link>https://velog.io/@jo_love/TIL97.Zustand</link>
            <guid>https://velog.io/@jo_love/TIL97.Zustand</guid>
            <pubDate>Mon, 02 May 2022 02:06:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>차세대 상태관리 라이브러리 &#39;zustand&#39;에 대해 알아보자. </p>
</blockquote>
<h2 id="왜-zustand인가">왜 Zustand인가?</h2>
<p>프론트 분야는 변화가 빠르게 성장한다. 그 중에서도 특히 상태관리는 다양한 툴들이 등장하고 발전해 나가고 있다. 가장 오래되고 유명한 상태관리를 떠올리면 단연 &#39;리덕스&#39;일 것이다. 
하지만 요즘에는 여러가지 이유로 &#39;리덕스&#39;보다는 다른 도구를 선택하는 추세다.</p>
<h4 id="리덕스의-단점">리덕스의 단점</h4>
<ol>
<li>개념이나 사용법 등 러닝 커브가 높은 편이기 때문에 빠른 시일내에 도입해야 하는 프로젝트의 경우 좋은 선택이 아니다.</li>
<li>보일러 플레이트 코드(액션 타입, 액션 생성함수, 리듀서)가 많다.</li>
<li>작은 기능이라도 리덕스를 구현하는 순간 몇 개의 파일들을 준비해야 한다.<h3 id="npm-trend">npm trend</h3>
<img src="https://velog.velcdn.com/images/jo_love/post/9c473819-d601-44de-90f7-0e2d9a528407/image.png" alt="">
주목받고 있는 라이브러리로 &#39;jotai&#39;,&#39;zustand&#39;,&#39;recoil&#39;이 있다. 
그 중 <code>zustand</code>가 가장 사용량이 높다. recoil은 페이스북 개발진이 만들었다는 신뢰성이 있지만 zustand에 비해 버전 업데이트가 매우 느리다.  </li>
</ol>
<p>-&gt; recoil: v0.7.2  zustand: v4.0.0  zotai: v1.6.5</p>
<h3 id="recoil-jotai-zustand-비교하기">recoil, jotai, zustand 비교하기</h3>
<h4 id="recoil">recoil</h4>
<ul>
<li>javascript로 작성 </li>
<li>bottom-up 방식 : atom 단위로 상태관리</li>
<li>최소 단위 Atom을 만들기 위해 key가 필요함</li>
<li>provider 필요<h4 id="jotai">jotai</h4>
</li>
<li>typescript로 작성</li>
<li>recoil에 영향을 받아 만들어짐</li>
<li>bottom-up 방식 : atom 단위로 상태관리</li>
<li>Atom key가 필요없음(코드가 미세하게 줄어듬)</li>
<li>리액트 state 함수인 useState 와 유사한 인터페이스</li>
<li>provider 일반적으로 필요<h4 id="zustand">zustand</h4>
<ul>
<li>typescript로 작성</li>
<li>리덕스를 축소화시킨 느낌으로  리덕스와 유사함</li>
</ul>
</li>
<li>스토어 형태</li>
<li>provider 필요없음 -&gt; 앱을 래핑하지 않아도 되기 때문에 불필요한 리렌더링 최소화<h2 id="zustand-사용법">Zustand 사용법</h2>
<h3 id="설치">설치</h3>
<pre><code class="language-js">npm install zustand</code></pre>
<h3 id="store-생성">store 생성</h3>
<pre><code class="language-js">// store.js
import create from &quot;zustand&quot;;
</code></pre>
</li>
</ul>
<p>const useStore = create((set) =&gt; ({
    count: 0,
    increase: () =&gt; set((state) =&gt; ({count: state.count + 1 }))
}));</p>
<p>export default useStore;</p>
<pre><code>zustand에서 create함수를 꺼내온다.
`useStore` 저장소는 상태에 관한 변수들을 저장할 수 있는 곳이다.
### store 값 사용하기 
```js
import useStore from &quot;store&quot;;

function App() {
  const { count } = useStore();

  return(
        &lt;div className=&quot;App&quot;&gt;
          &lt;p&gt;{count}&lt;/p&gt;
        &lt;/div&gt;
    )
}</code></pre><p>  셀렉터 함수를 전달하지 않으면 스토어 전체가 반환된다.</p>
<h3 id="state-변경하기">state 변경하기</h3>
<pre><code class="language-js">// store.js
  const useStore = create((set) =&gt; ({
    count: 0,
    increase: () =&gt; set((state) =&gt; ({count: state.count + 1 }))
}));

function App() {
  const { count, increase } = useStore();

  return(
        &lt;div className=&quot;App&quot;&gt;
          &lt;p&gt;{count}&lt;/p&gt;
          &lt;button onClick={()=&gt;{increase()}&gt;증가&lt;/button&gt;    
        &lt;/div&gt;
    )
}</code></pre>
<p>set함수는 상태를 변경하는 함수로 create 함수에 Set을 변수로 넣어 원하는 함수를 정의할 수 있다.
상태변수가 너무 많아서 관리하기 힘들 때에는 useStore를 나눠서 각 각 관리할 수도 있다. </p>
<pre><code class="language-js">const useStore1 = create((set) =&gt; ({
    ...
}));
const useStore2 = create((set) =&gt; ({
   ...
}));</code></pre>
<p>이외에도 ajax요청, 디버깅, 미들웨어도 가능하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL96. 메타마스크 지갑 연동(feat.web3-react)]]></title>
            <link>https://velog.io/@jo_love/TIL96.-%EB%A9%94%ED%83%80%EB%A7%88%EC%8A%A4%ED%81%AC-%EC%A7%80%EA%B0%91-%EC%97%B0%EB%8F%99feat.web3-react</link>
            <guid>https://velog.io/@jo_love/TIL96.-%EB%A9%94%ED%83%80%EB%A7%88%EC%8A%A4%ED%81%AC-%EC%A7%80%EA%B0%91-%EC%97%B0%EB%8F%99feat.web3-react</guid>
            <pubDate>Wed, 27 Apr 2022 09:19:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>메타마스크 테스트 네트워크의 지갑주소, 잔고를 화면에 뿌려보자.</p>
</blockquote>
<h2 id="web3-react란">Web3-react란?</h2>
<p>리액트에서 web3의 Dapp과 관련된 특정 데이터를 최신상태로 유지해주는 state machine으로 기본 블록체인 API가 ethers.js인 react 라이브러리이다.</p>
<h4 id="web3js">*web3js</h4>
<p>web3.js는 내부적으로 HTTP나 IPC(프로세스 사이에서 통신을 가능하게 하는 기술)를 통해 JSON-RPC API를 호출하도록 되어있다. 이더리움 네트워크는 노드로 구성되어 있고, 각 노드는 블록체인의 복사본을 가지고 있다. 이더리움 노드들은 JSON-RPC로만 소통할 수 있기 때문에 web3.js로 개발자들이 쉽게 개발할 수 있도록 자바스크립트 인터페이스로 상호작용할 수 있도록 도와주는 역할을 한다. 
<img src="https://velog.velcdn.com/images/jo_love/post/57c25edf-dcf2-47cd-8d55-a6a478b02656/image.png" alt=""></p>
<h3 id="설치-및-기본설정">설치 및 기본설정</h3>
<pre><code>npm i @web3-react/core @ethersproject/providers</code></pre><pre><code class="language-js">// index.js
import ReactDOM from &quot;react-dom&quot;;
import { Web3ReactProvider } from &quot;@web3-react/core&quot;;
import { Web3Provider } from &quot;@ethersproject/providers&quot;;
import App from &quot;./App&quot;;

const getLibrary = (provider: any) =&gt; new Web3Provider(provider);

const rootElement = document.getElementById(&quot;root&quot;);
ReactDOM.render(
  &lt;StrictMode&gt;
    &lt;Web3ReactProvider getLibrary={getLibrary}&gt;
      &lt;App /&gt;
    &lt;/Web3ReactProvider&gt;
  &lt;/StrictMode&gt;,
  rootElement
);</code></pre>
<p>web3를 사용하려면 리액트 앱 루트에 Web3ReactProvider컴포넌트로 감싸줘야한다. 이 컴포넌트에 getLibrary함수를 props로 전달하는데 web3-react가 사용할 web3 provider를 제공하는 역할을 한다.</p>
<h3 id="useweb3react">useWeb3React()</h3>
<p>context 값에 접근할 수 있도록 해주는 hook</p>
<pre><code class="language-js">const {connector, library, chainId, account, active, error, activate, deactivate} = useWeb3React();</code></pre>
<ul>
<li><p>connector: 현재 dapp에 연결된 지갑의 connector 값</p>
</li>
<li><p>*library: web3 provider 제공</p>
</li>
<li><p>chainId: dapp에 연결된 account의 chainId</p>
</li>
<li><p>account: dapp에 연결된 지갑 주소</p>
</li>
<li><p>active: dapp 유저가 로그인 상태</p>
</li>
<li><p>activate: dapp 월렛 연결 기능 함수</p>
</li>
<li><p>deactivate: dapp 월렛 연결 해제 함수</p>
<pre><code class="language-js">function Wallet() {
    const {account, library, active, activate, deactivate} = useWeb3React();

const connectWallet = async () =&gt; {
      try {
          await activate(injected);
      } catch (err) {
          console.log(err);
      }
  };
}</code></pre>
<h3 id="지갑-연동">지갑 연동</h3>
<p>지갑을  dapp에 연결하기 위해서는 해당 지갑과 맞는 connector를  activate 함수에 인자로 전달해야 한다.(metamask-injectedConnector)</p>
<pre><code class="language-js">//설치
npm i @web3-react/injected-connector
// connectors.js
import {InjectedConnector} from &#39;@web3-react/injected-connector&#39;;
</code></pre>
</li>
</ul>
<p>export const injected = new InjectedConnector();</p>
<pre><code>### 구현코드
```js
function Wallet() {
  const connectWallet = async () =&gt; {
        try {
            await activate(injected, (error) =&gt; {
                // 크롬 익스텐션 없을 경우 오류 핸들링
                if (&#39;/No Ethereum provider was found on window.ethereum/&#39;)
                    throw new Error(&#39;Metamask 익스텐션을 설치해주세요&#39;);
            });
        } catch (err) {
            alert(err);
            window.open(&#39;https://metamask.io/download.html&#39;);
        }
    };
  return (
        &lt;div&gt;
            {active ? (
                &lt;&gt;
                    &lt;WalletINfo&gt;
                        &lt;h2&gt;My Wallets&lt;/h2&gt;
                        &lt;span&gt;
                            {account?.substr(0, 8)}...{account?.substr(0, 8)}
                        &lt;/span&gt;
                    &lt;/WalletINfo&gt;
                &lt;/&gt;
            ) : (
                &lt;ConnectButton connectWallet={connectWallet} /&gt;
            )}
        &lt;/div&gt;
    );
}</code></pre><p>  연결 버튼을 누르면 메타마스크 지갑과 연동되고, 관련된 데이터들이 렌더링된다.
  기존에 메타마스크 익스텐션이 설치되어 있지 않은 경우, 에러 처리도 추가해 주었다.</p>
<h3 id="⭐️library">⭐️library</h3>
<p>  위에서 언급한 context 값
  &#39;library&#39;에서 <code>getbalance</code>에 접근할 수 있다. 
  getbalance 속성 { 
      _hex:string; 
    _isBigNumber:boolean;
    }
    <code>_hex</code> 16진수이기 때문에 formatEther 라이브러리를 사용해서 10진수로 변환</p>
<pre><code class="language-js">    function Wallet () {
        const {account, library, active} = useWeb3React();
          const [balance, setBalance] = useState(&#39;&#39;);

      useEffect(() =&gt; {
        if (account) {
            library
                ?.getBalance(account)
                .then((result: IResult) =&gt; setBalance(result._hex));
        }
    }, [account, library]);

      return(
      &lt;h1&gt;
         {balance &amp;&amp; Number(formatEther(balance)).toFixed(4)}
                            ETH
     &lt;/h1&gt;
      )
    }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL95.블록체인 이해하기]]></title>
            <link>https://velog.io/@jo_love/TIL.%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jo_love/TIL.%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 13 Apr 2022 08:42:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>블록체인의 개념과 관련 용어들에 대해 공부하고 정리해보자.</p>
</blockquote>
<h2 id="web-30">Web 3.0</h2>
<ul>
<li><h3 id="web의-역사">web의 역사</h3>
<p>-web 1.0시대 : 90년대 초~2000년대 중반, <code>read only</code> -&gt; 유저와의 상호작용이 없고, 정보전달의 목적이 강함. 
-web 2.0  : 2000년대 중반 ~ 현재, <code>read&amp;write</code> -&gt; 유저들이 컨텐츠를 자유롭게 올리고 수정 가능, 기업들이 데이터를 독점하고 활용함.</p>
<ul>
<li><h3 id="web-30-1">web 3.0</h3>
2세대의 문제점으로 지적되고 있는 중앙화, 보안문제를 해결하고자 탄생(특정 플랫폼 기업이 데이터를 가지고 있기때문에 해당 기업의 보안이 뚫리면 개인정보 유출 문제가 발생할 수 있음)
<code>read &amp; write &amp; own</code> </li>
<li><em>블록체인기술*</em>로 데이터를 분산 저장 -&gt; 데이터를 중앙 서버가 아닌 네트워크에 참여한 모두에게 나눠주는 기술이다. 이 기술을 위변조하려면 과반수의 장부를 해킹해야 하기때문에 기록을 안전하게 보관할 수 있다.<h2 id="블록체인의-동작원리">블록체인의 동작원리</h2>
<img src="https://velog.velcdn.com/images/jo_love/post/dd2354e4-91eb-438a-a173-f9fb79cd6631/image.png" alt="">
각 블록은 데이터, 해당 데이터의 해시값, 해당 블록의 바로 이전 블록의 해시값을 가지고 있다.<h3 id="해시">해시</h3>
어떤 블록을 고유 식별하는 값으로 해당 블록과 그 안에 있는 내용의 고유값이다. 매 생생된 블록마다 그 블록의 해시값이 계산된다. 만약 블록 안에 있는 데이터를 변경하면 해시값도 완전히 다른 값으로 변경된다.<h3 id="이전-블록의-해시값">이전 블록의 해시값</h3>
이전의 모든 블록들을 하나의 사슬로 묶어준다. 
어떤 블록의 데이터를 조작하면, 이 블록의 해시값이 변경된다.
다음 블록에 기록되어있는 직전 블록의 해시값이 다르기 때문에 무효가 된다. -&gt; 블록체인이 조작, 해킹이 어려운 이유</li>
</ul>
</li>
</ul>
<p>*블록체인 안전성을 높이는 다른 방법
: 컴퓨터의 해시값 계산이 빠르기때문에 블록을 조작한 후 다른 블록의 모든 해시를 재빨리 재계산할 가능성도 있다. 
이를 방지하기 위해서 새로운 블록을 만드는데 걸리는 시간을 10분이 소요되어 시간을 지연시킨다.</p>
<h2 id="블록체인-기본용어">블록체인 기본용어</h2>
<ul>
<li><h3 id="메인넷">메인넷</h3>
기존 플랫폼(이더리움, 솔라나...)에 속하지 않고 프로젝트를 수행할 수 있는 &#39;독립적인 플랫폼&#39;으로서 블록체인 프로젝트가 출시되면 해당 프로젝트를 구현하게 해주는 네트워크</li>
<li><h3 id="블록체인-종류">블록체인 종류</h3>
퍼블릭: 누구든지 블록체인 네트워크에 참여가 가능하다. 많은 사람들이 함께 참여하기 때문에 투명성이 강하고, 보안에 특화되어 있다. 하지만, 많은 사람들에 의해 합의가 진행되고 전체 네트워크에 전파하여 동기화해야하기 때문에 속도가 느리다. 
ex) 비트코인, 이더리움, 모네로 등
프라이빗: 허가받은 특정대상에게만 공개된다. 신뢰할 수 있는 사람들만 참여하여 트랜잭션 속도가 빠르다. 소수에 의해서 합의가 진행되기 때문에 일부 중앙화가 되어 보안성이 낮을 수 있다.
ex) Hyper ledger fabric, R3 CORDA 등
하이브리드: 퍼블릭 + 프라이빗 유연하게 혼합한 블록체인
모든 거래는 비공개로 진행할 수 있으며, 필요할 때에는 검증 가능성을 위해 거래 내역을 개방할 수 있다.
ex) 클레이튼</li>
<li><h3 id="dappdecentralized-application">DApp(Decentralized Application)</h3>
블록체인 메인넷을 기반으로 동작하는 서비스</li>
<li><h3 id="암호화폐토큰코인">암호화폐(토큰/코인)</h3>
블록체인 네트워크에서 거래가 기록된 장부를 검증하고 연결하는 대가로 검증자(노드)에게 주어지는 보상
토큰: 독자적인 메인넷 x (다른 블록체인 네트워크 기반을 사용)
코인: 독자적인 메인넷 o</li>
<li><h3 id="block-confirmation">block confirmation</h3>
코인을 전송하게되면 일종의 확인절차를 거치게 되는데 이것을 &#39;confirm&#39;이라고 한다. 각 코인마다 정해진 컨펌의 횟수가 채워지면 전송이 완료된다. 하나의 블록이 생생되어 기존의 블록에 연결될 때 이때 해당 거래에서 첫번째 컨펌이 발생한다. (비트코인 6컨펌-컨펌당 10분, 이더리움 12컨펌)</li>
<li><h3 id="dlt분산원장기술">DLT(분산원장기술)</h3>
거래정보를 기록한 원장을 중앙회된 서버가 아닌 분산화된 네트워크에 참여자 모두에게 저장 및 관리하는 기술로 블록체인은 분산원장기술이지만 모든 분산원장은 블록체인은 아니다.</li>
<li><h3 id="erc-20">ERC-20</h3>
Ethereum Request For Comment 20의 약자로써 이더리움 블록체인 네트워크에서 발행되는 토큰의 표준을 말한다. 제안서를 제출 후 사람들의 합의에 의해 통과되어 인터넷 표준이 되는 것을 RFC라고 하는데 ERC는 이더리움 RFC로 이더리움의 표준이 될 만한 내용들이다. </li>
</ul>
<h2 id="wallet">Wallet</h2>
<p>블록체인과 훨씬 쉽게 상호작용할 수 있게 해주는 소프트웨어로 코인을 보관하는 1차적인 보안이 적용된다.
메타마스크나 카이카스 등의 지갑을 만들면, 지갑의 <code>주소(public key)</code>와 <code>키(private key)</code>가 발급되고, 키 오너는 해당 지갑에 대한 접근 권한을 얻는다. 주소는 공개키에 해당하며, 은행의 계좌번호와 같은 역할을 한다. 키는 개인키에 해당하며 계좌의 비밀번호와 같다. 공개키와 개인키는 한 쌍을 이루며, 지갑에는 여러 쌍이 존재할 수 있다.
 ㅣㅡ <strong>public key</strong>: 누구에게나 공유가능하며, 코인을 전송받을 때 사용된다. 또한 거래 내역의 유효성을 판단할 수 있다. 
ㅣ
ㅣㅡ <strong>private key</strong>: 비밀유지되야 하는 키로 256비트의 난수(random number)로 구성된 값이다. 개인키를 통해 공개키를 생성한다.(반대의 경우는 불가능) 개인키를 한번 분실하게 되면 다시는 찾을 수 없고 개인키로 묶인 코인 역시 찾을 수 없기 때문에 주의가 필요하다. </p>
<h4 id="주소">주소</h4>
<p>공개키로부터 생성되며, 숫자와 문자로 구성되어 있으며 &#39;1&#39;로 시작한다. 비트코인 주소는 외부에 공개되는 주소이며, 코인을 주고받기 위해 사용된다. 주소에서 공개키를 알아내는 것은 불가능하다.</p>
<p>*메타마스크: 이더리움 기반 암호화폐 및 토큰을 주고받을 수 있는 지갑</p>
<h4 id="콜드월렛">콜드월렛</h4>
<p>암호화폐를 보관하기 위한 오프라인 데이터 저장 장치. 암호화폐 시스템 자체를 해킹하는 것은 불가능하지만 가상자산을 담아두는 거래소의 사용자 계정 및 지갑은 소유자 개인이 관리하는 방식이기 때문에 해킹의 대상이 될 수 있다. 
콜드월렛은 인터넷에 연결되어 있지않는 오프라인 데이터 저장 장치이기때문에 바이러스나 백도어 프로그램 등 해킹 요소에 영향을 받지 않는다. </p>
<h3 id="did">DID</h3>
<p>블록체인 기술을 기반으로 한 분산 신원증명 기술을 말한다. 
기존에 기술적으로 스마트폰에 담을 수 없었던 운전면허, 자격증 등은 필요할 때마다 증서를 발급받아 번거롭게 제출했었다.
DID는 탈중앙화된 ID를 부여하여 스마트폰에 저장해서 위조가 불가능한 증명을 가능하게 해준다.
실제로 백신접종증명서에 DID를 활용했었음.</p>
<h4 id="did-syntax">DID Syntax</h4>
<p>하나의 DID는 문자열로 표현가능하며 3개의 부분으로 이루어져 있다.</p>
<pre><code>did:sov:2k9dwkjrwioweio7a
scheme: method: DID Method-Specific Identifier</code></pre><p>세미콜론으로 구분가능하며, 맨 처음 부분이 <code>Scheme</code>- 문자열이 DID임을 나타내는 prefix, 항상 did
<code>DID Method</code>- DID document가 어떻게 생성되고 업데이트되는지를 정의한 것
<code>DID Method-Specific Identifier</code> - 메소드 내에서 사용되는 식별자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준2667.단지번호붙이기(js)]]></title>
            <link>https://velog.io/@jo_love/%EB%B0%B1%EC%A4%802667.%EB%8B%A8%EC%A7%80%EB%B2%88%ED%98%B8%EB%B6%99%EC%9D%B4%EA%B8%B0js</link>
            <guid>https://velog.io/@jo_love/%EB%B0%B1%EC%A4%802667.%EB%8B%A8%EC%A7%80%EB%B2%88%ED%98%B8%EB%B6%99%EC%9D%B4%EA%B8%B0js</guid>
            <pubDate>Sun, 20 Feb 2022 13:27:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>DFS개념을 사용해서 알고리즘 문제 풀어보자.</p>
</blockquote>
<h2 id="문제">문제</h2>
<p>&lt;그림 1&gt;과 같이 정사각형 모양의 지도가 있다. 1은 집이 있는 곳을, 0은 집이 없는 곳을 나타낸다. 철수는 이 지도를 가지고 연결된 집의 모임인 단지를 정의하고, 단지에 번호를 붙이려 한다. 여기서 연결되었다는 것은 어떤 집이 좌우, 혹은 아래위로 다른 집이 있는 경우를 말한다. 대각선상에 집이 있는 경우는 연결된 것이 아니다. &lt;그림 2&gt;는 &lt;그림 1&gt;을 단지별로 번호를 붙인 것이다. 지도를 입력하여 단지수를 출력하고, 각 단지에 속하는 집의 수를 오름차순으로 정렬하여 출력하는 프로그램을 작성하시오.
<img src="https://images.velog.io/images/jo_love/post/0215809f-32bf-43ac-9219-98be3d5c1916/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-16%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2012.00.11.png" alt=""></p>
<h2 id="풀이">풀이</h2>
<p>주어지는 입력값을 이중배열로 담고, 행과 열을 이중for문을 이용해서 요소 요소를 방문한다. 집이 있고( =1 ), 방문한 적이 없는 곳에 dfs를 실행한다.
dfs가 한번 수행되면 하나의 단지 전체가 방문처리 완료된 것이다.</p>
<p><img src="https://images.velog.io/images/jo_love/post/9b08e244-434a-4880-a0cf-0510cd0be92f/0.png" alt="">
현재 탐색타겟의 인접타겟으로 가기 위해 현탐색타켓을 중심축으로 동서남북 움직일 수 있는 범위를 나타낸 배열을 만들어준다.(위의 그림참고)
이때 x,y과 0보다 작거나 지도의 크기 N을 넘으면 안되기때문에 범위에 맞는 코드를 추가해준다. </p>
<h2 id="코드">코드</h2>
<pre><code class="language-js">const solution = (n, material) =&gt; {
  const visited = [];
  const complex = [];
  let number = 0;
  //x,y축으로 움직일 수 있는 범위
const dx = [0, 0, 1, -1]; // 동, 서, 남, 북
const dy = [1, -1, 0, 0]; // 동, 서, 남, 북

for (let i = 0; i &lt; n; i++) {
   visited.push(new Array(n).fill(false));
}

// 범위 체크
const rangeCheck = (i, j) =&gt; {
  if (i &gt;= 0 &amp;&amp; i &lt; n &amp;&amp; j &gt;= 0 &amp;&amp; j &lt; n) {
    return true;
  }
  return false;
};

const DFS = (i, j) =&gt; {
  if (rangeCheck(i, j) &amp;&amp; material[i][j] === 1 &amp;&amp;
    !visited[i][j]) {
    // 범위안에 들어가고 &amp;&amp; 방문한적이 없으면 DFS 탐색
    visited[i][j] = true; // 방문 처리
    number++;
    for (let n = 0; n &lt; dx.length; n++) {
      DFS(i+ dx[n], j + dy[n]);
    }
  }
};

  //이중그래프 전체 탐색
for (let i = 0; i &lt; n; i++) {
  for (let j = 0; j &lt; n; j++) {
    //집이 있고(1), 방문한 적이 없다면, 
    if (material[i][j] === 1 &amp;&amp; !visited[i][j]) {
      DFS(i, j);
      complex.push(number);
      //초기화
      number = 0;
    }
  }
}
  complex.sort((a, b) =&gt; a - b); // 오름차순으로 정렬!
 const answer = [complex.length, ...complex];
return answer;
}


const material = [
[0,1,1,0,1,0,0],
[0,1,1,0,1,0,1],
[1,1,1,0,1,0,1],
[0,0,0,0,1,1,1],
[0,1,0,0,0,0,0],
[0,1,1,1,1,1,0],
[0,1,1,1,0,0,0]
]
solution(7, material);
//[ 3, 7, 8, 9 ]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[94.BFS,DFS]]></title>
            <link>https://velog.io/@jo_love/BFSDFS</link>
            <guid>https://velog.io/@jo_love/BFSDFS</guid>
            <pubDate>Mon, 14 Feb 2022 10:30:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>완전탐색 종류 중 BFS, DFS에 대해서 알아보자.</p>
</blockquote>
<h2 id="dfs깊이우선탐색">DFS(깊이우선탐색)</h2>
<p>아래쪽 level로 내려갔다가 더이상 아래 level이 없으면 가장 가까운 부모 노드로 거슬러 올라간 뒤 탐색하지 않은 다른 자식 노드를 탐색하는 방식이다.  </p>
<p>탐색 시작 노드를 스택에 삽입하고 방문 처리를 한다. -&gt; 스택의 최상단 노드에 방문하지 않은 인접한 노드가 있으면 그 노드를 스택에 넣고 방문처리를 한다. 방문하지 않은 인접 노드가 없으면 스택에서 최상단 노드를 꺼낸다.
2번의 과정을 수행할 수 없을 때까지 반복한다.</p>
<h4 id="특징">특징</h4>
<p>BFS에 비해 메모리를 덜 잡아먹고, 특정 경로가 존재하는지를 판단할 때 유용하다.(ex)미로 게임)</p>
<h3 id="실전문제프로그래머스-네트워크">실전문제(프로그래머스 &#39;네트워크&#39;)</h3>
<p>컴퓨터의 개수 n, 연결에 대한 정보가 담긴 2차원 배열 computers가 매개변수로 주어질 때, 네트워크의 개수를 return 하도록 solution 함수를 작성하시오.</p>
<ul>
<li><p>i번 컴퓨터와 j번 컴퓨터가 연결되어 있으면 computers[i][j]를 1로 표현합니다.</p>
</li>
<li><p>computer[i][i]는 항상 1입니다.</p>
<pre><code class="language-js">function solution (n, computers)  {
let answer = 0;
// 방문 여부 체크
let visited = [];

function dfs (i) {
  visited[i] = true;
  for(let j = 0; j &lt; n; j++) {
    if(!visited[j] &amp;&amp; computers[i][j]) {
      dfs(j);
    }
  }
}
for(let i = 0; i &lt; n; i++) {
  // 특정 인덱스에 방문하지 않았다면 해당 인덱스를 넘겨주면서 dfs호출 
  if(!visited[i]) {
    dfs(i);
    answer++;
  }
}
return answer;
}

</code></pre>
</li>
</ul>
<p>const computers = [[1,1,0],[1,1,0],[0,0,1]];
solution(3,computers);</p>
<pre><code>반복문을 돌려서 방문유무를 확인한다. 방문하지 않았다면, dfs함수를 탄다.

## BFS(너비우선탐색)
시작점을 방문하고 인접한 정점을 차례대로 방문하는 검색방법으로 트리의 각 depth에 있는 노드의 인접노드를 탐색하는 방식이다.

탐색 시작 노드를 방문 처리를 하고, 큐(QUEUE) 자료구조에 노드를 삽입한다. -&gt;큐에서 최상단 노드를 꺼내고, 인접노드를 탐색 후, 방문처리하고, 큐에 넣는다. -&gt; 선입선출 구조에 따라 먼저 들어온 노드를 꺼낸 후, 꺼낸 노드의 인접 노드를 살핀다. -&gt; 방문처리하지않은 노드를 방문하고, 큐에 넣는다. -&gt; 큐가 빌 때까지 계속 반복한다.
#### 특징
찾고자 하는 target node가 시작점으로부터 가까이 있다고 예상될 경우 사용한다. (ex)지도 목적지 최단거리, 페이스북에서 친구 추천)
 ### 실전문제(프로그래머스 &#39;네트워크&#39;)
 ```js
function solution(n, computers) {
  let answer = 0;
  let visited = [];
  let queue = [];

  for(let i = 0; i &lt; n; i++) {
   if(!visited[i]) {
     bfs(i);
     answer++;
   }
  }

  function bfs(i) {
    visited[i] = true;
    queue.push(i);

    while(queue.length) {
      //제거한 node 
      const currentNode = queue.shift();

      for(let j = 0; j &lt; computers[currentNode].length; j++) {
        //i가 아니라  currrentNode를 사용해야 함, i를 사용하면 그냥 지나가는 숫자가 생김
        if(computers[currentNode][j] &amp;&amp; !visited[j]) {
          visited[j] = true;
          queue.push(j);
        }
      }
    }
  }
  return answer;
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[개인 프로젝트⏰<Time Log> ]]></title>
            <link>https://velog.io/@jo_love/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8Time-Log</link>
            <guid>https://velog.io/@jo_love/%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8Time-Log</guid>
            <pubDate>Wed, 09 Feb 2022 02:48:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>&quot;Time Log&quot; <a href="https://time-log.netlify.app/">https://time-log.netlify.app/</a></p>
</blockquote>
<p><img src="https://images.velog.io/images/jo_love/post/6e36bdbc-0de8-4ed9-8d0f-acce10a530d7/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2011.31.19.png" alt=""></p>
<h2 id="프로젝트-소개">프로젝트 소개</h2>
<p>시간을 효율적으로 집중하면서 쓰지 못한다는 생각에 유튜브에서 &#39;집중하는 법&#39;을 찾아봤다. 그 중에 눈에 들어왔던 방법이 꼼꼼하게 &#39;시간을 기록하기&#39; 였다. 몇가지 어플이 존재했지만, 마음에 드는 게 없어서 직접 만들어봐야지 생각했다. 나중는 기회가 되면 앱으로도 만들어보고싶다.</p>
<h2 id="소셜-로그인">소셜 로그인</h2>
<p>구글로그인을 통한 소셜로그인을 만들었다. 직접 로그인 기능을 만드는 것보다 간단하고 편했다.</p>
<h3 id="oauth">Oauth</h3>
<p>소셜 로그인의 핵심개념으로, 사용자의 패스워드없이도 권한을 위임받을 수 있는 기술을 말한다. 
사용자 정보를 가지고 있는 웹 서비스에서 사용자의 인증을 대신 처리해주고, 접근 권한에 대한 토큰을 발급하면, 이를 이용해서 해당 웹서비스 API를 사용해 사용자 정보를 받아오거나 이를 이용해 session을 부여해 로그인 상태를 유지시킬 수 있다.
<img src="https://images.velog.io/images/jo_love/post/27b6b116-fd39-47f7-95e1-caaa629b2978/%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%8C%E1%85%A1.png" alt="">
0. 그림에는 생략되어있지만 provider 내부에서 <code>resourece server</code>에서 <code>authorization server</code>로 인가책임을 위임하는 과정이 있다.</p>
<h2 id="serverless로-구현">serverless로 구현</h2>
<p>이번 프로젝트는 혼자 진행했기 때문에 serverless 서비스가 필요했다. firebase을 사용하여 CRUD 중 Create, Read를 구현해보았다.</p>
<h3 id="cloud-firestore에-데이터-추가하기add">cloud Firestore에 데이터 추가하기(add)</h3>
<p>버튼을 눌렀을 때 &#39;handleRecord&#39;함수를 작동시켜 파이어베이스로 데이터를 보내 저장</p>
<pre><code class="language-js">const handleRecord = () =&gt; {
    handleRemove(selectedIdx);

    const now = new Date();
    const endAt = now.getHours() + &#39;:&#39; + now.getMinutes();
    const result = {
      name: list.name,
      img: list.img,
      timer: timer,
      date: date,
      startAt: startAt,
      endAt: endAt,
      identifier: userEmail,
    };
    db.collection(&#39;logInfo&#39;).add(result);
  };</code></pre>
<h3 id="데이터-가져오기">데이터 가져오기</h3>
<pre><code class="language-js">const getMyHistory = useCallback(async () =&gt; {
    const firestoreData = await db
      .collection(&#39;logInfo&#39;)
      .where(&#39;identifier&#39;, &#39;==&#39;, userEmail)
      .get();
    const originData = firestoreData.docs.map(
      (doc) =&gt; doc.data() as IResultData,
    );
    setProcessingData(processData(originData));
  }, [userEmail]);
  useEffect(() =&gt; {
    getMyHistory();
  }, [getMyHistory]);</code></pre>
<h2 id="어려웠던-부분">어려웠던 부분</h2>
<p>firebase를 사용하다보니 데이터 형태 때문에 어려움을 겪었다. 내가 원하는 모양으로 db내에서 디테일하게 수정할 수가 없어서 데이터를 받아온 후, 클라이언트 내에서 데이터를 가공해주는 함수를 따로 만들어서 해결하였다. </p>
<pre><code>들어오는 데이터
{
    &quot;name&quot;: &quot;공부&quot;,
    &quot;startAt&quot;: &quot;22:44&quot;,
    &quot;identifier&quot;: &quot;jolove.dev@gmail.com&quot;,
    &quot;img&quot;: &quot;/static/media/study.bf2b89b3c7dfeb32a0a2.png&quot;,
    &quot;endAt&quot;: &quot;21:44&quot;,
    &quot;timer&quot;: 3600
}</code></pre><pre><code>원하는 데이터 형태
{
    &quot;date&quot;: &quot;2022.1.16&quot;,
    &quot;infoByDate&quot;: [
        {
            &quot;identifier&quot;: &quot;jolove.dev@gmail.com&quot;,
            &quot;img&quot;: &quot;/static/media/study.bf2b89b3c7dfeb32a0a2.png&quot;,
            &quot;endAt&quot;: &quot;21:44&quot;,
            &quot;timer&quot;: 3600,
            &quot;name&quot;: &quot;공부&quot;,
            &quot;startAt&quot;: &quot;22:44&quot;
        },
        {
            &quot;endAt&quot;: &quot;22:48&quot;,
            &quot;img&quot;: &quot;/static/media/reading.1f9650abd36f9d59751b.png&quot;,
            &quot;startAt&quot;: &quot;22:48&quot;,
            &quot;timer&quot;: 11,
            &quot;identifier&quot;: &quot;jolove.dev@gmail.com&quot;,
            &quot;name&quot;: &quot;독서&quot;
        },
    ]
}```
```js
export const processData = (rawData: IResultData[]) =&gt; {
  let processingData: IProcessingData[] = [];
  const uniqueDate: string[] = [];

  // 날짜 가져오기
  const getDate = rawData.map((el) =&gt; el.date);
  // 유니크 날짜와 infodata 객체 만들어 주기
  const combination = Array.from(new Set(getDate)).map((date) =&gt; {
    uniqueDate.push(date || &#39;&#39;);
    return { date: date, infoByDate: [] };
  });
  // 객체 틀 배열에 넣어주기
  processingData = [...combination] as IProcessingData[];

  rawData.map((item) =&gt; {
    const targetIdx = uniqueDate.indexOf(item.date || &#39;&#39;);
    delete item[&#39;date&#39;];
    return processingData[targetIdx].infoByDate.push(item);
  });
  return processingData;
};</code></pre><h2 id="새롭게-적용한-부분">새롭게 적용한 부분</h2>
<ul>
<li><h3 id="img-src-깔끔하게-정리하기">img src 깔끔하게 정리하기</h3>
기존에 img태그 src에 변수를 할당하는 방법을 적용시켰다. 수정도 쉽고, 관리하기 더 편한 것 같다. <pre><code class="language-js">// 적용 이전
&lt;img src=&quot;/assets/a.png&quot; /&gt;
&lt;img src=&quot;/assets/b.png&quot; /&gt;
&lt;img src=&quot;/assets/c.png&quot; /&gt;</code></pre>
<pre><code class="language-js">//적용 후
</code></pre>
</li>
</ul>
<p>//index.js -&gt; 모듈에 모아두기
export { default as a } from &#39;./a.png&#39;;
export { default as b } from &#39;./a.png&#39;;
export { default as c } from &#39;./a.png&#39;;</p>
<p>// content.js
<img src={a} />
<img src={b} />
<img src={c} /></p>
<pre><code>- ### code-splitting
맨처음 페이지에 진입하면 웹팩에서 압축한 번들파일을 다운받는다.spa의 특징으로 눈에 보이지 않는 페이지의 전체 리소스까지 전부 다운받게 되는데 이때문에 초기 페이지가 보이기까지 시간이 지체된다. code-splitting을 사용하면 덩치가 큰 번들파일을 분할하여, 작은 사이즈의 파일로 분할하여 로딩시간을 줄일 수 있다.
```js
import { Suspense, lazy } from &#39;react&#39;;
import { BrowserRouter, Routes, Route } from &#39;react-router-dom&#39;; 
import PrivateRoute from &#39;routes/PrivateRoute&#39;;
import { loading } from &#39;assets/index&#39;;

const Login = lazy(() =&gt; import(&#39;pages/Login&#39;));
const Record = lazy(() =&gt; import(&#39;pages/Record&#39;));
const History = lazy(() =&gt; import(&#39;pages/History&#39;));
const NotFound = lazy(() =&gt; import(&#39;pages/NotFound&#39;));

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;Suspense
        fallback={
          &lt;div&gt;
            &lt;img width={130} src={loading} alt=&quot;loading&quot; /&gt;
          &lt;/div&gt;
        }
      &gt;
        &lt;Routes&gt;
          &lt;Route path=&quot;/&quot; element={&lt;Login /&gt;} /&gt;
          &lt;Route
            path=&quot;/record&quot;
            element={
              &lt;PrivateRoute&gt;
                &lt;Record /&gt;
              &lt;/PrivateRoute&gt;
            }
          /&gt;
          &lt;Route
            path=&quot;/history&quot;
            element={
              &lt;PrivateRoute&gt;
                &lt;History /&gt;
              &lt;/PrivateRoute&gt;
            }
          /&gt;
          &lt;Route path=&quot;*&quot; element={&lt;NotFound /&gt;} /&gt;
        &lt;/Routes&gt;
      &lt;/Suspense&gt;
    &lt;/BrowserRouter&gt;
  );
}</code></pre><h4 id="lazy">lazy</h4>
<p>동적으로 import가 이루어진다.
ex) /record 페이지에 진입하면 <code>Record</code> 리소스만 다운받도록 <code>lazy-loading</code>시킨다.</p>
<h4 id="suspense">suspense</h4>
<p>컴포넌트가 동적으로 로드될 때 아무것도 로드되지 않는 순간, 에러를 발생시키지않고 suspense에 적용한 컴포넌트를 렌더링해준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL93. Recoil]]></title>
            <link>https://velog.io/@jo_love/TIL92.-Recoil</link>
            <guid>https://velog.io/@jo_love/TIL92.-Recoil</guid>
            <pubDate>Wed, 29 Dec 2021 05:21:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>또 다른 상태관리 라이브러리 &#39;Recoil&#39;에 대해서 알아보자.</p>
</blockquote>
<h2 id="recoil">Recoil?</h2>
<p>페이스북에서 만든 상태 관리 라이브러리이다. 대표적인 상태관리 라이브러리 &#39;redux&#39;는 react만을 위한 상태관리 도구가 아니기에 react에 최적화되지도 않았고, 러닝커브도 높았다. 이에 비해 recoil은 hook을 써봤다면 쉽게 사용할 수 있는 수준이다.</p>
<h3 id="recoil-사용하기">Recoil 사용하기</h3>
<pre><code class="language-js">...
import { RecoilRoot } from &#39;recoil&#39;;

ReactDOM.render(
  &lt;React.StrictMode&gt;
    &lt;RecoilRoot&gt;
        &lt;App /&gt;
    &lt;/RecoilRoot&gt;
  &lt;/React.StrictMode&gt;,
  document.getElementById(&#39;root&#39;),
);</code></pre>
<p>전역에서 사용할 수 있도록 root에 <code>recoilRoot</code> 감싸주기</p>
<h3 id="atom">atom</h3>
<pre><code class="language-js">// atom.ts
import { atom } from &quot;recoil&quot;;

export const isDarkAtom = atom({
  key: &#39;isDark&#39;, // 식별자 역할
  default: false, // 초기값
});</code></pre>
<p>기존의 상태관리 라이브러리에서 쓰이는 store와 유사한 개념으로, 상태의 단위를 말한다.
atom의 상태가 업데이트되면, atom을 구독하고 있던 모든 컴포넌트들의 state가 새로운 값으로 리렌더링된다. </p>
<h3 id="userecoilvalue">useRecoilValue</h3>
<pre><code class="language-js">mport { useRecoilValue } from &#39;recoil&#39;;
import { isDarkAtom } from &#39;./atoms&#39;;

function App() {
  const isDark = useRecoilValue(isDarkAtom); //atom 값 받아오기
  return (
    &lt;&gt;
      &lt;ThemeProvider theme={isDark ? darkTheme : lightTheme}&gt;
        &lt;GlobalStyle /&gt;
        &lt;Router /&gt;
      &lt;/ThemeProvider&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>컴포넌트와 atom을 연결하기 위한 도구</p>
<h3 id="usesetrecoilstate">useSetRecoilState</h3>
<pre><code class="language-js">// child.js
import { useSetRecoilState } from &#39;recoil&#39;;

const setDarkAtom = useSetRecoilState(isDarkAtom);
  const toggleMode = () =&gt; {
    setDarkAtom((val) =&gt; !val);
  };</code></pre>
<p>vaule를 수정할 수 있는 함수로, setState와 같은 방식으로 작동한다.</p>
<h3 id="userecoilstate">useRecoilState</h3>
<pre><code class="language-js">import { useRecoilState } from &#39;recoil&#39;;

  const [isDark, setIsDark] = useRecoilState(isDarkAtom);</code></pre>
<p>상태를 가져오거나 변경하는 행동, 둘 다 하고 싶을 때 사용한다. 
즉, useRecoilValue + useSetRecoilState</p>
<ul>
<li>selector값도 사용가능하다.<h3 id="selector">selector</h3>
recoil에서 파생된 상태로 selector는 순수함수여야한다.</li>
<li><h4 id="get-recoilvaluereadonly">get <code>RecoilValueReadOnly</code></h4>
<pre><code class="language-js">// A input에 &#39;minute&#39;을 입력하면 B input창에 &#39;hour&#39;로 바꿔주는 경우
export const minuteState = atom({
key: &#39;minutes&#39;,
default: 0,
});
</code></pre>
</li>
</ul>
<p>export const hourSelector = selector({
  key: &#39;hours&#39;,
  get: ({ get }) =&gt; {
    const minutes = get(minuteState);
    return minutes / 60;
  },
});</p>
<pre><code>`get프로퍼티`를 통해 원래의 state를 가공하여 반환한다. **read-only **한 selector는 의존성을 기준으로 의존성 중 어떠한 것이 업데이트 되면 selector는 다시 평가된다. 
- #### set `writeable`

```js
// 반대로 B input창에 &#39;hour&#39;을 입력하면 A input창에서 &#39;minute&#39;으로 바꿔주는 경우 
export const minuteState = atom({
  key: &#39;minutes&#39;,
  default: 0,
});

export const hourSelector = selector&lt;number&gt;({
  key: &#39;hours&#39;,
  get: ({ get }) =&gt; {
    const minutes = get(minuteState);
    return minutes / 60;
  },
  set: ({ set }, newValue) =&gt; {
    const minutes = Number(newValue) * 60;
    // set함수의 2가지 인자: 수정하고 싶은 recoil atom, 새로운 값
    set(minuteState, minutes);
  },
});

// useRecoilState - selector
const [minutes, setMinutes] = useRecoilState(minuteState);
//  array의 첫번째 요소는 위 selector의 get함수의 값, 두번째 요소는 set property를 실행시키는 함수
const [hours, setHours] = useRecoilState(hourSelector);</code></pre><p><code>hourSelector</code> 에서 minuteState를 변경하도록 만들었기에 두 곳에서 모두 값을 설정할 수 있게 되었다. 두 개의 atom을 각각 만드는 대신, 2개의 속성을 가지는 selector를 사용해서 atom의 값을 set하도록 만들 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[92.React Query]]></title>
            <link>https://velog.io/@jo_love/92.React-Query</link>
            <guid>https://velog.io/@jo_love/92.React-Query</guid>
            <pubDate>Tue, 21 Dec 2021 16:54:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>리액트 쿼리를 사용해서 server와의 비동기 로직코드를 줄여보자.</p>
</blockquote>
<p><img src="https://images.velog.io/images/jo_love/post/bf54eee2-7229-47f9-a7b7-8f87216b433d/react-query.svg" alt=""></p>
<h2 id="react-query">React Query?</h2>
<p>state를 관리하는 라이브러리로 server와 client 사이의 비동기 로직들을 쉽게 만들어주는 도구이다.</p>
<p>그래서 React Query를 사용하면 <strong>뭐가 좋은데?</strong></p>
<p>첫번째, <strong>일련의 코드를 확 줄여준다.</strong> 
데이터를 가져오기 위한 일련의 필수적인 코드들이 있다. 
예를들어, 서버와 통신하기 위해서 fetch함수를 만들고, 받아온 데이터를 담을 state를 만들어주고, 받아오는 상태에 따라 loading 화면을 보여주기 위해 state를 만들고 세팅해주는 등의 세트 코드들이 있다. 하지만, react query와 함께라면 코드를 획기적으로 줄일 수 있다.
두번째, <strong>데이터를 캐시에 저장해둔다.</strong>
전형적인 기존 코드를 사용했다면 뒤로가기를 눌렀을 때 loading화면이 보이지만,
react-query 사용 시 로딩이 보이지 않는다. api를 다시 불러오는 행동을 하지 않고, api로부터 받아온 response를 캐싱해두기 때문이다.</p>
<h2 id="사용방법">사용방법</h2>
<pre><code class="language-js">//index.tsx
import { QueryClient } from &#39;react-query&#39;;

// client 생성 
const queryClient = new QueryClient();

ReactDOM.render(
  &lt;React.StrictMode&gt;
  //Provider 생성, props로 client 필요
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;ThemeProvider theme={theme}&gt;
        &lt;App /&gt;
      &lt;/ThemeProvider&gt;
    &lt;/QueryClientProvider&gt;
  &lt;/React.StrictMode&gt;,
  document.getElementById(&#39;root&#39;),
);
</code></pre>
<p> <code>QueryClientProvider</code> 안에 있는 모든 것은 쿼리 클라이언트에 접근할 수 있다. </p>
<pre><code class="language-js">react query 사용 전 코드
// app.tsx
  const [info, setInfo] = useState&lt;IInfo[]&gt;([]);
  const [loading, setLoading] = useState(false);

  useEffect(() =&gt; {
    (async () =&gt; {
      const response = await fetch(&#39;https://api.com/information&#39;);
      const json = await response.json();
      setInfo(json.slice(0, 100));
      setLoading(false);
    })();
  }, []);
...
{loading ? (
        &lt;div&gt;Loading...&lt;/div&gt;
      ) : (
        &lt;Wrapper&gt;
          {info.map((item) =&gt; (
            &lt;Card key={item.id}/&gt;         
          ))}
        &lt;/Wrapper&gt;
      )}
 ...      </code></pre>
<pre><code class="language-js">react query 사용 후 코드
// api.ts
export const fetchData = () =&gt; {
  return fetch(&#39;https://api.com/information&#39;).then((res) =&gt; res.json());
};

// app.tsx
  const {isLoading, data} = useQuery&lt;IInfo&gt;(&#39;allInfo&#39;, fetchData);

...
{isLoading ? (
        &lt;div&gt;Loading...&lt;/div&gt;
      ) : (
        &lt;Wrapper&gt;
          {data?.map((item) =&gt; (
            &lt;Card key={item.id}/&gt;         
          ))}
        &lt;/Wrapper&gt;
      )}
 ...      </code></pre>
<p>useQuery는 두 가지의 인자를 받는데, 첫번째는 queryKey(고유식별자), fetcher함수이다.</p>
<p>위에서 말했듯이, react query를 사용 후 코드가 줄어들었다. 필요한 요소들은 useQuery가 return하는 것에서 받아올 수 있다. 
useQuery는 fetch함수(fetchData)를 불러오고 함수가 isLoading이면, true
함수가 끝나면, false를 알려준다.
또한, fetcher함수가 끝나면, 데이터를 data에 넣어준다.</p>
<h4 id="querykey-같은-값을-갖는-경우">*queryKey 같은 값을 갖는 경우</h4>
<p><code>queryKey</code>는 앞서말했듯이 고유한 이름을 가져야 한다. 퀴리키가 겹치는 경우에는 고유한 이름을 갖도록 바꿔줘야 한다.
 useQuery는 쿼리키를 배열로 취급하기 때문에 아래 예시와 같이 배열 안에 고유한 이름을 추가해준다.</p>
<h4 id="2개-이상의-usequery-hook사용할-경우">*2개 이상의 useQuery hook사용할 경우</h4>
<p>useQuery를 2개 이상 사용할 경우 isLoading,data 등의 리턴값들이 2개 이상이 된다. 이름이 중복되면 안되기 때문에 아래와 같이 이름을 따로 설정해야 한다.</p>
<pre><code class="language-js">//api.ts

export const fetchInfoData = (Id: string) =&gt; {
  return fetch(`https://api.com/&#39;${Id}`).then((res) =&gt; res.json());
};

//app.tsx
const { Id } = useParams&lt;{ Id: string }&gt;();
const { isLoading: infoLoading, data: infoData } = useQuery&lt;IInfo&gt;(
    [&#39;info&#39;, Id],
    () =&gt; fetchCoinInfo(Id),
  );
  const { isLoading: productLoading, data: productData } = useQuery&lt;IProduct&gt;(
    [&#39;product&#39;, Id],
    () =&gt; fetchCoinTickers(Id),
  );
const loading = infoLoading || productLoading;
...
{loading ? (
        &lt;div&gt;Loading...&lt;/div&gt;
      ) : (
        &lt;&gt;
            &lt;span&gt;{infoData.name}&lt;/span&gt;
            &lt;span&gt;{productData.price}&lt;/span&gt;
        &lt;/&gt;
      )};
...</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL91.완전탐색]]></title>
            <link>https://velog.io/@jo_love/TIL91.%EC%99%84%EC%A0%84%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@jo_love/TIL91.%EC%99%84%EC%A0%84%ED%83%90%EC%83%89</guid>
            <pubDate>Tue, 14 Dec 2021 03:43:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>완전탐색  알고리즘에 대해 공부해보자.</p>
</blockquote>
<h2 id="완전탐색">완전탐색</h2>
<p>모든 경우의 수를 탐색하여 결과를 찾아내는 방법이다. 
모두 탐색하기 때문에 정답을 100% 찾아낼 수 있지만, 경우의 수에 따라 실행시간이 비례된다. 즉, 탐색 범위가 넓다면 그만큼 찾아내는 일을 해야하기 때문에 효율적이지 못할 수 있다.</p>
<h2 id="완전탐색-알고리즘-종류">완전탐색 알고리즘 종류</h2>
<ul>
<li>Brute-force</li>
<li>비트 마스크</li>
<li>순열</li>
<li>백트래킹</li>
<li>DFS, BFS</li>
<li><h3 id="brute-force">Brute force</h3>
단순히 for문과 if문을 사용하여 모든 case들을 만들어 답을 구하는 방법이다.<pre><code class="language-js">function sequentialSearch (arr,x) {
for (let i = 0; i &lt; arr.length; i++) { 
  if(arr[i] === x) {
    return i;
  }
}
return -1;
}  
sequentialSearch([1,2,4,8,18,27,37],2);</code></pre>
<h3 id="조합">*조합</h3>
순열과 함께 자주 나오는 개념으로, 서로 다른 n개 중 순서를 생각하지 않고 r개를 택할 때, 이것은 n개에서 r개를 택하는 조합이라 한다. 선택 순서는 상관없다(ex)[1,2] =[2,1])</li>
</ul>
<h4 id="❓-n4개-중에-2개-뽑는-경우를-구해보자">❓ n(4)개 중에 2개 뽑는 경우를 구해보자.</h4>
<p>  -접근법
  &#39;1&#39; 고정하고 -&gt; 나머지 [2, 3, 4] 중에서 2개 조합
  [1, 2, 3] [1, 2, 4] [1, 3, 4]
  &#39;2&#39; 고정하고 -&gt; 나머지 [3, 4] 중에서 2개 조합
  [2, 3, 4]
  &#39;3&#39; 고정하고 -&gt; 나머지 [4] 중에서 2개 조합
  [] 
  &#39;4&#39; 고정하고 -&gt; 나머지 [] 중에서 2개 조합
  []</p>
<p><img src="https://images.velog.io/images/jo_love/post/d2206b30-4ea9-46c1-87cd-93a1922006cb/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-12-14%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%202.36.03.png" alt="">
&#39;인셉션&#39; 같다...</p>
<p>-코드 </p>
<pre><code class="language-js">const getCombinations = (arr, selectNum) =&gt; {
  const result = [];
  if(selectNum === 1) {
    return arr.map((el) =&gt; [el])
  };
  arr.forEach((fixed, idx, origin) =&gt; {
    const rest = origin.slice(idx + 1);
    // arr 배열에서 해당 fixed를 제외한 나머지 값  

   const combinations = getCombinations(rest, selectNum - 1);
  // 현재 fixed를 제외한 나머지 숫자들을 재귀함수로 돌려 조합을 구한다.

    const attached = combinations.map((combination) =&gt; [fixed,...combination]);
      //  돌아온 조합에 떼어 놓은(fixed) 값 합치기

    result.push(...attached);
  })
  return result;
}

getCombinations([1,2,3,4],3);

// output
[
  [ 1, 2, 3 ],
  [ 1, 2, 4 ],
  [ 1, 3, 4 ],
  [ 2, 3, 4 ]
]</code></pre>
<ul>
<li><h3 id="순열">순열</h3>
<p>순열은 서로 다른 n개에서 r개를 선택해서 정렬하는 가짓수이고, 순열의 수를 기호로 nPr로 나타낸다.선택 순서가 결과에 영향을 미친다.(ex)[1,2] ≠ [2,1])</p>
<h4 id="1fixed을-이용한-순열">1.fixed을 이용한 순열</h4>
<p>❓ n개 중에 2개 뽑는 경우 구하기
-코드</p>
<pre><code class="language-js">const getPermutations = (arr, selectNum) =&gt; {
const result = [];
if(selectNum === 1) {
 return arr.map((el)=&gt; [el]);
}

arr.forEach((fixed, idx, origin) =&gt; {
  const rest = [...origin.slice(0,idx), ...origin.slice(idx + 1)];
  const permutations = getPermutations(rest,selectNum - 1 );
  const attached = permutations.map((el) =&gt; [fixed,...el])
  result.push(...attached);
})  
return result;
}
</code></pre>
</li>
</ul>
<p>getPermutations([1,2,3,4], 3);
// output
[
  [ 1, 2, 3 ], [ 1, 2, 4 ],
  [ 1, 3, 2 ], [ 1, 3, 4 ],
  [ 1, 4, 2 ], [ 1, 4, 3 ],
  [ 2, 1, 3 ], [ 2, 1, 4 ],
  [ 2, 3, 1 ], [ 2, 3, 4 ],
  [ 2, 4, 1 ], [ 2, 4, 3 ],
  [ 3, 1, 2 ], [ 3, 1, 4 ],
  [ 3, 2, 1 ], [ 3, 2, 4 ],
  [ 3, 4, 1 ], [ 3, 4, 2 ],
  [ 4, 1, 2 ], [ 4, 1, 3 ],
  [ 4, 2, 1 ], [ 4, 2, 3 ],
  [ 4, 3, 1 ], [ 4, 3, 2 ]
]</p>
<pre><code>#### 차이점 
조합코드와 거의 똑같고, rest부분만 다르다.</code></pre><pre><code> |--- 현재 index보다 작으면 잘라낸다면, 조합</code></pre><p>rest |     fixed:1 =&gt; [2,3,4] =&gt; fixed:2 =&gt; [3,4] =&gt; ...
     |     fixed:2 =&gt; [3,4] =&gt; fixed:1 =&gt; [4] =&gt; ...
     |--- 현재 index보다 작아도 존재해야한다면, 순열
            fixed:1 =&gt; [2,3,4] =&gt; fixed:2 =&gt; [3,4] =&gt; ...
           fixed:2 =&gt; [1,3,4] =&gt; fixed:1 =&gt; [3,4] =&gt; ...</p>
<pre><code>#### 2.swap을 이용한 순열
 ❓ 나올 수 있는 전체 경우 구하기
```js
const getPermutations = (arr) =&gt; {
  const result = []; //(4) result = [[1,2]]
  //(6) [1,2] =&gt; [2,1]
  const swap = (arrToSwap, idxA, idxB) =&gt; {
    const temp = arrToSwap[idxA];
    arrToSwap[idxA] = arrToSwap[idxB];
    arrToSwap[idxB] = temp;
  };

  const generate = (n , array) =&gt; {
    //(3) n = 1이 되었기때문에, result에 담아줌 
    //(8) result에 [2,1] push
    if(n === 1){
       result.push([…array]);
      return;
    }
    generate( n-1, array); //(2)  n = 2, -&gt; generate(1,[1,2])

    //(5) 다시 g(2, [1,2])케이스로 돌아와서 for문을 탄다. n이 짝수이기 때문에 swap([1,2],0,1)
    for(let i = 0; i &lt; n - 1; i++) {
      if(n % 2 === 0) {
        swap(array, i, n - 1);
      }else {
        swap(array, 0, n - 1 );
      }
      generate( n - 1, array); //(7) generate(1, [2,1])
    }
  }
  generate(arr.length, arr.slice()); //(1) generate 함수 호출 (2,[1,2])
  return result;
}

getPermutations([1,2,3]);</code></pre><p><img src="https://images.velog.io/images/jo_love/post/78a040fa-02be-42bb-b991-9b43e4a7bc79/KakaoTalk_Photo_2021-12-12-18-11-43.jpeg" alt="">
이 방법은 순열의 경우의 수가 사전적으로 정렬되어 나오지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL90. Adapter Pattern]]></title>
            <link>https://velog.io/@jo_love/TIL90.-Adapter-Pattern</link>
            <guid>https://velog.io/@jo_love/TIL90.-Adapter-Pattern</guid>
            <pubDate>Tue, 26 Oct 2021 09:17:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>B를 A처럼 변경해서 A로 사용하자.</p>
</blockquote>
<p><img src="https://images.velog.io/images/jo_love/post/cfec2bcf-ec7d-4b8b-9fdb-c123d29bed6e/%E1%84%83%E1%85%AB%E1%84%8C%E1%85%B5%E1%84%8F%E1%85%A9.jpeg" alt=""></p>
<p>여행갈 때 전압이 다른 곳에서 기계를 충전하려면 전압을 바꿔주는 어댑터가 필요하다.
프로그래밍에서도 다른 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 경우, 즉, 다른 인터페이스로의 변환이 필요할 때 어댑터가 필요하다.이때 사용할 수 있는 것이 <code>어댑터 패턴</code>이다.</p>
<h2 id="예시">예시</h2>
<p>text를 입력하면 text를 그대로 프린트를 하는 코드</p>
<pre><code class="language-js">class Printer {
  constructor() {
    this.textArr = [];
  }

  pushText(text) {
    this.textArr.push(text);
  }

  print() {
    return this.textArr.join(&#39;&#39;);
  }
}

const instance = new Printer();
instance.pushText(&#39;Hello&#39;);
instance.pushText(&#39;World&#39;);

instance.print(); // &#39;HelloWorld&#39;</code></pre>
<p>만약 이 코드에서 텍스트마다 &#39;#&#39;를 붙여주는 수정사항이 있을 경우에는 어떡할까?</p>
<pre><code class="language-js">class HashTagPrinter {
  constructor() {
    this.textArr = [];
  }

  pushText(text) {
    this.textArr.push(text);
  }

  printWithHash() {
    return this.textArr.map(text =&gt; `#${text}`).join(&#39;&#39;);
  }
}

const instance = new HashTagPrinter();
instance.pushText(&#39;Hello&#39;);
instance.printWithHashTag(); // #Hello</code></pre>
<p>만약 위의 예제와 달리 좀 더 복잡하고 긴 코드에서라면 비효율적인 방법일 것이다.</p>
<p>-&gt; adapter pattern를 사용해서 관리해보자.</p>
<h4 id="adapter-pattern">adapter pattern</h4>
<pre><code class="language-js">class Adapter {

  constructor(printer) {
    this.printer = printer;
  }

  pushText(text) {
    this.printer.pushText(text);
  }

  print() {
    return this.printer.printWithHash();
  }
}

const instance = new Adapter(new HashTagPrinter());
instance.pushText(Hello);
instance.pushText(World);
// #Hello#World</code></pre>
<h2 id="실제로-사용하기">실제로 사용하기</h2>
<p>서버에서 프로토콜 버퍼로 받아온 타입을 클라이언트 타입으로 바꿔줘야 하는 경우가 생겼다. 
처음에는 컨버팅하지 않고 클라이언트 타입으로 바로 바꿔줬다. 하지만 나중에 다른 컴포넌트에서도 사용할 경우가 많아진다면 일일히 코드를 작성해줘야하기 때문에 좋은 코드가 아니라는 소리를 들었다.
그래서 이때 adapter pattern을 사용하면 사용하고 있는 모든 컴포넌트를 고치지 않고 adapter pattern만 수정하면 되기 때문에 유용하다.</p>
<h4 id="타입-코드">타입 코드</h4>
<pre><code class="language-js">// 서버 타입
type ServerType = {
      typeCode: string;
      identifier: string;
      state: string;
};

//클라이언트 타입
type ClientType = {
    type: string;
      id: string;
      status: string;
}; </code></pre>
<h4 id="전체-코드">전체 코드</h4>
<pre><code class="language-js">// 변경 전
...생략
axios.get(&quot;url&quot;).then(({data}) =&gt; {
  setDataContainer(data.items.map((item: ServerType) =&gt; ({
    type: item.typeCode,
    id: item.identifier,
    status: item.state
  })),
 );
});

//변경 후
...생략
axios.get(&quot;url&quot;).then(({data}) =&gt; {
  setDataContainer(data.items.map(convert)
 );
});

const convert = (item: ServerType): ClientType =&gt; {
  return {
    type: item.type,
    id: item.identifier,
    status: item.state
  }
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL89. ES모듈]]></title>
            <link>https://velog.io/@jo_love/TIL89.-ES%EB%AA%A8%EB%93%88</link>
            <guid>https://velog.io/@jo_love/TIL89.-ES%EB%AA%A8%EB%93%88</guid>
            <pubDate>Tue, 28 Sep 2021 05:08:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>&quot;import { Module } from&quot; 과 &quot;import Module from&quot; 의 차이점에 대해 알아보자. </p>
</blockquote>
<h2 id="es-moduleimport--export">ES module(import &amp; export)</h2>
<p>import, export 문은 JavaScript 모듈에서 함수, 객체, 원시 값을 내보내거나 불러올 때 사용한다.</p>
<h3 id="named-exports">Named Exports</h3>
<p>여러 값들을 export/import하고 싶을 때, 사용한다.
export 이름과 import 이름이 꼭 일치해야한다.</p>
<pre><code class="language-js">// module.js
export const plus = (a,b) =&gt; a + b;
export const minus = (a,b) =&gt; a - b; 

// main.js
import {plus, minus} from &#39;./module&#39;;

plus(1,2);</code></pre>
<h3 id="default-export">Default Export</h3>
<p>모듈 당 한 개의 default export를 사용할 수 있다.
각 함수를 하나 하나 import하는 방식이 아니라 이 함수 전체를 포함하고 있는 한객의 객체를 디폴트로 export하는 방식이다.
export 이름과 달리 원하는 이름으로 import할 수 있다.</p>
<pre><code class="language-js">// module.js
const plus = (a,b) =&gt; a + b;
const minus = (a,b) =&gt; a - b; 
export default {plus, minus};

//main.js
import mymodule from &quot;./module&quot;

module.plus(1,2);</code></pre>
<h3 id="star-import">star import</h3>
<p>default export가 없는 파일을 한개의 파일로 모두 import 할 수 있다.</p>
<pre><code class="language-js">export const plus = (a,b) =&gt; a + b;
export const minus = (a,b) =&gt; a - b; 

//main.js
import * as myMath from &quot;./module&quot;</code></pre>
<h3 id="dynamic-import">dynamic import</h3>
<p>정적 import는 파일의 위에서부터 모든걸 import한다. 즉, 사용자가 보는 해당 페이지에 필요하지 않은 코드까지 다운받아야한다는 것이다. 필요한 최소한의 코드를 다운받아야 로딩 속도를 올릴 수 있다. 
이때 Dynamic Import 를 사용하면, 런타임시에 필요한 module만 import 할 수 있다.</p>
<pre><code class="language-js">function doMatch() {
  import(&quot;./module.js&quot;) //모듈 import(2)
  .then(module =&gt; module.plus(1, 2)); // 모듈 함수 사용(3)
}
btn.addEventListener(&quot;click&quot;, doMath); //유저 버튼 클릭(1)</code></pre>
<p>유저가 버튼을 클릭하면, 모듈을 import하고 사용</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL88. zip array]]></title>
            <link>https://velog.io/@jo_love/TIL88.-zip-array</link>
            <guid>https://velog.io/@jo_love/TIL88.-zip-array</guid>
            <pubDate>Fri, 10 Sep 2021 06:17:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>다른 두 배열을 한 배열처럼 만들어보자.</p>
</blockquote>
<h2 id="해결상황">해결상황</h2>
<p>하드코딩된 부분을 맵을 돌려서 예쁘게 만들어보자.
목표: 서로 다른 두 배열을 가지고 하나의 맵을 돌려 화면에 나타내야 한다. 
-&gt; <code>zip array</code> 활용하기</p>
<pre><code class="language-Js">하드코딩, 수정해야 하는 부분

&lt;div className=&quot;estimate&quot;&gt;
          &lt;span&gt;위 : {summaryOfEvaluation?.count[0].value}&lt;/span&gt;
          &lt;span&gt;아래 : {summaryOfEvaluation?.count[1].value} &lt;/span&gt;
          &lt;span&gt;왼쪽 : {summaryOfEvaluation?.count[2].value} &lt;/span&gt;
          &lt;span&gt;오른쪽 : {summaryOfEvaluation?.count[3].value} &lt;/span&gt;
&lt;/div&gt;</code></pre>
<h2 id="zip-기본-예제">zip 기본 예제</h2>
<pre><code class="language-js">const arr1 = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;];
const arr2 = [1, 2, 3];

const arr3 = arr1.map((el, i) =&gt; {
  return [el, arr2[i]];
});

console.log(arr3);
// [ [ &#39;a&#39;, 1 ], [ &#39;b&#39;, 2 ], [ &#39;c&#39;, 3 ] ] </code></pre>
<p>요소의 갯수가 정해져 있는 배열, 튜플형태</p>
<h2 id="zip-적용하기">zip 적용하기</h2>
<pre><code class="language-json">  //데이터 형태
 &quot;summary&quot;: {
        &quot;count&quot;: [
          {
            &quot;type&quot;: &quot;TOP&quot;,
            &quot;value&quot;: 3
          },
          {
            &quot;type&quot;: &quot;BOTTOM&quot;
            &quot;value&quot;: 1
          },
            &quot;type&quot;: &quot;LEFT&quot;
          &quot;value&quot;: 0
          },
          {
            &quot;type&quot;: &quot;RIGHT&quot;
            &quot;value&quot;: 1
          }
        ],
          ...생략
      },
</code></pre>
<pre><code class="language-js">const organizeData = () =&gt; {
    const direction = [&quot;위&quot;, &quot;아래&quot;, &quot;왼쪽&quot;, &quot;오른쪽&quot;];
    const value = summary?.count?.map((item) =&gt; {
      return item.value;
    });
    const zip = direction.map((key, i) =&gt; {
      return [key, value?.[i]];
    });
    return zip;
  };

//jsx 부분
 &lt;div className=&quot;estimate&quot;&gt;
          {organizeData().map(([key, value]) =&gt; {
            return (
              &lt;span&gt;
                {key} : {value}
              &lt;/span&gt;
            );
          })}
&lt;/div&gt;</code></pre>
<p>우선 데이터에서 필요한 부분을 뽑아 배열형태로 만들어준다.
첫번째 배열에 map을 돌려서 배열의 첫번째 요소로 넣어주고, 두번째 요소로 인덱스값을 통해 두번째 배열의 값을 뽑아주는 튜플형태로 리턴해준다. </p>
<h3 id="리팩토링">리팩토링</h3>
<p>한번 더 간결하게!</p>
<pre><code class="language-js">const organizeData = () =&gt; {
  return direction.map(( key, i ) =&gt; [key, summary?.count?.[i]?.value])  
};</code></pre>
<h2 id="완성된-화면">완성된 화면</h2>
<p>위 : 3</p>
<p>아래 : 1</p>
<p>왼쪽 : 0</p>
<p>오른쪽 : 1</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL87.forwardRef ]]></title>
            <link>https://velog.io/@jo_love/TIL.-forwardRef-with%EC%BA%98%EB%A6%B0%EB%8D%94</link>
            <guid>https://velog.io/@jo_love/TIL.-forwardRef-with%EC%BA%98%EB%A6%B0%EB%8D%94</guid>
            <pubDate>Mon, 09 Aug 2021 04:45:38 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>ref를 prop으로 전달하는 방법에 대해 알아보자.</p>
</blockquote>
<h2 id="ref">ref</h2>
<p>forwardRef 알아보기 이전에 ref개념에 대해 간단하게 짚고 넘어가자.
react를 사용할 때 DOM에 접근해야 하는 상황이 종종 발생한다. js에서 특정 DOM에 접근하기 위해서 getElementById와 같은 DOM Selector를 사용하는 것처럼 react에서는 ref를 사용한다.</p>
<pre><code class="language-js">//부모 컴포넌트
import React, { useRef } from &quot;react&quot;;
import Input from &quot;./components/Input&quot;;

const App = () =&gt; {
  const inputRef = useRef(null);

  const handleFocus = () =&gt; {
    inputRef.current.focus();
  }

  return (
    &lt;&gt;
      &lt;Input ref={inputRef} /&gt;
      &lt;button onClick={handleFocus}&gt;입력란 포커스&lt;/button&gt;
    &lt;/&gt;
  );
}</code></pre>
<pre><code class="language-js">//자식 컴포넌트
const Input = ({ ref }) =&gt; {
  return &lt;input type=&quot;text&quot; ref={ref} /&gt;;
}</code></pre>
<p>위의 예시와 같이 부모컴포넌트에서 inputRef객체를 자식인 <code>Input</code>컴포넌트에 <code>ref</code> prop으로 넘긴다. 
자식 Input컴포넌트는 ref로 넘어온 inputRef 객체를 다시 내부에 있는 &lt; input &gt;엘레먼트의 ref prop으로 넘겨주게 된다.<br>하지만 실행시켜보면 <code>ref는 prop이 아니다</code> 라는 오류가 뜬다.
리액트에서는 key,ref처럼 특수목적으로 사용되어 일반적인 용도로 사용할 수 없는 prop이 있기 때문이다. 
이때 React컴포넌트에서 ref를 prop으로 사용하려면 react에서 저공하는 forwardRef()라는 함수를 사용하면 된다.</p>
<h2 id="forwardref">forwardRef</h2>
<p>forwardRef를 사용하면 부모 컴포넌트로부터 하위 컴포넌트로 ref를 전달할 수 있게 된다. 이렇게 전달받은 ref를 HTML요소의 속성으로 넘겨줌으로써 함수 컴포넌트 역시 ref를 통한 제어가 가능해진다.</p>
<pre><code class="language-js">//부모 컴포넌트 코드 동일

//자식 컴포넌트
import React, { forwardRef } from &quot;react&quot;;

 //인자로 props이외에 별도로 ref를 두번째 매개변수로 받는다.
const Input = forwardRef((props, ref) =&gt; {
  return &lt;input type=&quot;text&quot; ref={ref} /&gt;;
});</code></pre>
<h4 id="주의점">*주의점</h4>
<p>forwardRef는 최하단 컴포넌트에 사용하는 것이 일반적이다. 상위 컴포넌트에 사용했을 경우, 상위 컴포넌트에 딸린 하위 컴포넌트가 많기때문에 그만큼 영향을 많이 끼칠 수 있기 때문이다. 컴포넌트 간의 결합도를 증가시켜 어플리케이션의 유지보수를 어렵게 만듦...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL86.useSelector, useDispatch, useCreate]]></title>
            <link>https://velog.io/@jo_love/86.selector-redux</link>
            <guid>https://velog.io/@jo_love/86.selector-redux</guid>
            <pubDate>Mon, 02 Aug 2021 01:19:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>useSelector, useDispatch, useCreate를 사용해서 리덕스 상태를 조회하고 사용해보자.</p>
</blockquote>
<h2 id="useselector">useSelector</h2>
<p>useSelector Hook을 사용하면 connect함수를 사용하지 않고도 리덕스의 상태를 조회할 수 있다. 
개념적으로 mapStateToProps와 비슷하다.</p>
<h3 id="connect함수와-useselector-차이점">connect함수와 useSelector 차이점</h3>
<p>connect 함수는 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화된다.
이에비해, useSelector를 사용하여 리덕스 상태를 조회했을 때는 성능 최적화가 이루어지지 않기때문에 React.memo를 컨테이너 컴포넌트에 사용해줘야한다.
<code>export default React.memo(Container);</code></p>
<pre><code class="language-js">import { useSelector } from &#39;react-redux&#39;;

const ExampleContainer = () =&gt; {
    const target = useSelector(state =&gt; state.target);
    return &lt;Example target={target} /&gt;;
};</code></pre>
<h2 id="usedispatch">useDispatch</h2>
<p>useDispatch Hook은 컴포넌트 내부에서 스토어의 내장함수 dispatch를 사용할 수 있게 도와준다. </p>
<pre><code class="language-js">import { useDispatch } from &#39;react-redux&#39;;
import { increase } from &#39;../modules/counter&#39;;

const ExampleContainer = () =&gt; {
    const dispatch = useDispatch();
      return &lt;Example onIncrease = { () =&gt; dispatch(increase()) } /&gt;
}</code></pre>
<h4 id="-usecallback과-함께-사용하기">* useCallback과 함께 사용하기</h4>
<p>state가 바뀌어 컴포넌트가 리렌더링될 때마다 정의해놓은 함수들이 새롭게 만들어진다. 성능을 최적화시키기 위해서 useCallback과 함께 사용하는 것이 좋다. </p>
<pre><code class="language-js">const increase = useCallback( () =&gt; dispatch(increase()), [dispatch];</code></pre>
<h2 id="useselector-최적화">useSelector 최적화</h2>
<p>리덕스 상태들을 조회할 때 비구조화 할당을 사용하여 객체를 다시 생성하는 방식을 취했기 때문에, react에서는 각각의 상태가 바뀌는 것의 여부를 파악하지 못하고 무조건 다시 렌더링을 하게된다. = 즉, 최적화에 불리하다는 말이다.</p>
<pre><code class="language-js">const { count, prevCount } =useSelector((state: RootState) =&gt; ({
  count: state.countReducer.count,
  prevCount: state.countReducer.prevCount,
}));</code></pre>
<h4 id="1-독립-선언">1) 독립 선언</h4>
<p>공식문서의 해결방법 중 하나는 비구조화 할당을 사용하지 않고, 아래와 같이 각각의 값을 독립적으로 선언하는 방법이다.</p>
<pre><code class="language-js">const count = useSelector((state: RootState) =&gt; state.countReducer.count);
const prevCount = useSelector((state: RootState) =&gt; state.countReducer.prevCount);</code></pre>
<p>각각의 상태변경여부를 파악할 수 있어 상태 최적화가 가능해진다. 위의 예제처럼 상태가 많지 않으면 사용할 법하지만, 속성이 많은 경우에 일일히 나열하게되면 코드가 길어져 가독성이 별로 좋지 않을 것이다.</p>
<h4 id="2-shallowequal">2) shallowEqual</h4>
<p>shallowEqual함수는 selector로 선언한 값의 최상위 값들의 비교여부를 대신 작업해준다.</p>
<pre><code class="language-js"> const { count, prevCount } = useSelector((state: RootState) =&gt; ({
    count : state.countReducer.count,
    prevCount: state.countReducer.prevCount,
  }),shallowEqual);</code></pre>
<p>*주의사항 : 최상위 값만 비교한다는 점에 주목해야한다.
구조가 아래 예시와 같다면 prevCount.a,prevCount.b,prevCount.c는 비교대상이지만, prevCount.c.d는 비교대상이 아니다.</p>
<pre><code class="language-js">prevCount = {
   a : 0,
   b : 1,
   c : {d :2}
}</code></pre>
<h4 id="3createselector">3)<em>createSelector</em></h4>
<p>reselect는 넘어오는 인자 중 하나라도 변경이 되어야만 재계산을 실행한다. createSelector에 정의된 
selector들의 반환값을 마지막 인자인 함수 형태의 인자들로 순서대로 넘겨받게 된다.
객체 형태로 반환하는 코드를 유지하면서 최적화를 가능하게 해준다. 
또한, selector를 분리함으로써 재사용할 수 있고, 다른 셀렉터를 조합하여 사용할 수 있다. </p>
<pre><code class="language-js">import { createSelector } from &#39;reselect&#39;;

const getCount = state =&gt; state.count;
const getPrevCount = state =&gt; state.prevCount;

const { count, prevCount } = createSelector(
    getCount,
      getPrevCount,
      (count, prevCount) =&gt; {
        return {
           count,
           prevCount                            
         }                               
    }
)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL85.Redux Toolkit]]></title>
            <link>https://velog.io/@jo_love/TIL85.Redux-Toolkit</link>
            <guid>https://velog.io/@jo_love/TIL85.Redux-Toolkit</guid>
            <pubDate>Tue, 13 Jul 2021 14:53:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>리덕스를 편리하게 사용할 수 있게 도와주는 🎁종합선물세트 &#39;리덕스 툴킷&#39;에 대해 알아보자.</p>
</blockquote>
<h2 id="redux-toolkit이란">Redux Toolkit이란?</h2>
<p>리덕스를 사용하다보면 액션타입,액션생성함수, 리듀서 등 반복되는 코드,즉 보일러플레이트 코드때문에 피로감을 느낄 수 있다.
이때 리덕스 툴킷을 사용하면 코드를 좀 더 간결하게 만들어 줄 수 있다.
리덕스 툴킷은 리덕스의 종합선물세트로 redux thunk, immer, createaction 등이 내장되어 있다.</p>
<h2 id="configurestore">configureStore</h2>
<p>toolkit을 사용하기 전에는 createStore()를 호출하고 root reducer함수를 전달하여 redux store를 사용했다. Toolkit의 configureStore는 createStore를 활용한API로 createStore를 래핑하여 만들어진 함수이기때문에 기본적으로 createStore와 동일한 기능을 제공한다.(store 생성)
(no toolkit)</p>
<pre><code class="language-js">import { createStore } from &#39;redux&#39;;
import rootReducer from &#39;./module/rootReducer&#39;;

const devTools = window.__REDUX_DEVTOOLS_EXTENSION__ &amp;&amp; window.__REDUX_DEVTOOLS_EXTENSION__();
const store = createStore(rootReducer, devTools);</code></pre>
<p>(with toolkit)
createStore -&gt; configureStore. 
configureStore함수는 여러 개의 인자 대신 지정된 하나의 object를 인자로 받으므로, reducer함수를 reducer라는 이름으로 전달해야 한다. 리듀서만 전달하면, 기본적으로 redux devtool와 middleware가 포함되어 있다.</p>
<pre><code class="language-js">import { configureStore } from &quot;@reduxjs/toolkit&quot;;

const store = configureStore({ reducer: rootReducer });</code></pre>
<h4 id="middleware의-값을-배열로-전달하기">middleware의 값을 배열로 전달하기</h4>
<p>사용하고싶은 미들웨어를 지정하려면 middleware의 값을 아래와 같은 배열로 전달하면 된다.기본으로 제공하는 미들웨어에 logger를 추가하려면 getDefaultMiddleware를 사용하면 된다.</p>
<pre><code class="language-js">const store = configureStore({
  reducer: rootReducer,
  middleware: [thunk, logger],
});

//getDefaultMiddleware 사용하는 경우
onst store = configureStore({
  reducer: rootReducer,
  middleware: [...getDefaultMiddleware(), logger],
});</code></pre>
<h2 id="createaction">createAction</h2>
<p>createAction은 action을 간결하게 만들어 준다.
(no toolkit)</p>
<pre><code class="language-js">const INCREMENT = &quot;counter/increment&quot;; // 액션
const DECREMENT = &quot;DECREMENT&quot;;

// 액션 크리에이터 함수
const increment = () =&gt; {
  return {
    type: INCREMENT,
  };
}

const decrement = () =&gt; {
  return {
    type: DECREMENT,
  };
}
// or 인자가 있는 경우
function increment = (amount) =&gt; {
  return {
    type: INCREMENT,
    payload: amount,
  };
}

const action = increment(2);
// { type: &#39;INCREMENT&#39;, payload: 2 }</code></pre>
<p>(with toolkit)
createAction으로 액션을 전달하기만 하면 액션과 액션 크리에이터 함수가 만들어진다.
만약 이 생성함수를 호출할 때 prameter를 추가로 넣어준다면 알아서 payload에 값이 들어가게 된다.</p>
<pre><code class="language-js">import { createAction } from &quot;@reduxjs/toolkit&quot;;

const INCREMENT = &quot;INCREAMENT&quot;; // 액션
const DECREMENT = &quot;DECREMENT&quot;;

export const increment = createAction(INCREMENT);
export const decrement = createAction(DECREMENT);

// 파라미터가 있는 경우, 확실히 명시해주기위해 코드상으로 명시해줄 수도 있다.
export const increment = createAction(INCREMENT, (amount) =&gt; amount);

increment(); { type: &#39;INCREMENT&#39; }
increment(2); { type: &#39;INCREMENT&#39;, payload:2 }</code></pre>
<h2 id="createreducer">createReducer</h2>
<p>기존에 reducer를 사용하기 위해서 switch 조건문으로 액션타입을 구분해 수행해야 했지만, toolkit을 사용하면 switch와 default를 사용하지 않고, 가독성을 향상시킬 수 있다.
(no toolkit)</p>
<pre><code class="language-js">const counterReducer = (state = 0, action) =&gt; {
  switch (action.type) {
    case INCREMENT:
      return state + 1;
    case DECREMENT:
      return state - 1;

    default:
      return state;
  }
};</code></pre>
<p>(with toolkit)</p>
<pre><code class="language-js">// 액션타입 집어넣는 경우 ok
const counterReducer = createReducer(0, {
  [INCREMENT]: state =&gt; state + 1,
  [DECREMENT]: state =&gt; state - 1
})

//or 액션생성함수 집어넣는 경우 ok
const counterReducer = createReducer(0, {
  [increment]: state =&gt; state + 1,
  [decrement]: state =&gt; state - 1
})</code></pre>
<p>=&gt; 액션생성함수를 바로 집어넣을 수 있는 이유는 createAction함수가 toString메소드를 오버라이드했기 때문( ex)incerement 함수 =&gt; return INCREMENT)</p>
<h2 id="createslice">createSlice</h2>
<p>리듀서, 액션타입, 액션 생섬함수, 초기상태를 하나의 함수로 편하게 선언할 수 있게 해준다. 쉽게말해 action과 reducer를 다 가지고 있는 함수.</p>
<pre><code class="language-js">const counterSlice = createSlice({
  name: &#39;counter&#39;, //액션 경로를 잡아줄 해당 이름
  initialState: 0,
  reducers: {
    increment: state =&gt; state + 1,
    decrement: state =&gt; state - 1
  }
})

const store = configureStore({
  reducer: counterSlice.reducer
})</code></pre>
<p>기존에는 액션생섬함수와 액션타입을 선언해 사용했지만, createSlice에서는 Action을 선언하고, 해당 action이 dispatch되면 state를 가지고 action을 처리한다.reducers 안에 모든 기능이 합쳐져 있는 것이다.</p>
<h3 id="immer">immer</h3>
<p>기존에 불변성을 유지하기 위해 전개연산자나 concat의 메소드를 사용했어야 했는데 Toolkit에서는 자동으로 불변성을 관리해주는 유틸을 가지고 있다.
immer 라이브러리를 내장하고 있기때문에 더이상 리듀서에서 새로운 state객체를 만들어 리턴하지않고, state를 직접 변경해도 된다.</p>
]]></description>
        </item>
    </channel>
</rss>