<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발로그</title>
    <link>https://clohoon.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 00:49:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>clohoon</managingEditor>
    <image>
      <title>개발로그</title>
      <url>https://tistory1.daumcdn.net/tistory/6541913/attach/35547531f85e4323851740bbc1656eb1</url>
      <link>https://clohoon.tistory.com</link>
    </image>
    <item>
      <title>군 장병 AI&amp;middot;SW 역량강화 교육을 마치며</title>
      <link>https://clohoon.tistory.com/19</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;845&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AQm2G/btsAxG0v2R1/DlePco85L0aJAj9ufmg2lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AQm2G/btsAxG0v2R1/DlePco85L0aJAj9ufmg2lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AQm2G/btsAxG0v2R1/DlePco85L0aJAj9ufmg2lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAQm2G%2FbtsAxG0v2R1%2FDlePco85L0aJAj9ufmg2lK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;845&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;845&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;군 장병 AI&amp;middot;SW 역량강화 교육을 모두 수료했다. 교육기간 동안 자바스크립트를 이용해 프로그래밍 언어가 웹에서 어떻게 동작하는지를 배웠다. 그리고 마지막으로는 리액트와 파이어베이스를 이용해 다양한 기능을 가진 채팅 애플리케이션을 구현하여 배포하였다. 교육도 끝났으니 이번 글에서는 나의 목표와 앞으로의 계획에 대해 간단히 적어보려고 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;데이터 엔지니어&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;나는 빅데이터 분야를 공부해보고 싶어서 산업경영공학부에 들어왔다. 진로를 정하기 위해 데이터와 관련된 여러 직무들을 조사해 보았고, 그중 하나인 데이터 엔지니어에 관심을 가지게 됐다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qAv5Q/btsEiAJKwaa/DvvUaKpJ6oF7NvdmWluLi0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qAv5Q/btsEiAJKwaa/DvvUaKpJ6oF7NvdmWluLi0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qAv5Q/btsEiAJKwaa/DvvUaKpJ6oF7NvdmWluLi0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqAv5Q%2FbtsEiAJKwaa%2FDvvUaKpJ6oF7NvdmWluLi0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;292&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터 엔지니어는 데이터를 수집, 가공, 적재하여 데이터가 흐르는 파이프라인을 설계 및 구축하는 직무로, &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;분석가/기획자의 니즈에 부합하는 데이터를 제공하는 것이 목표이다. 여러 직무들 중 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터 엔지니어를 선택한 이유는 내가 데이터 분석뿐만 아니라 소프트웨어 개발에도 흥미를 가지고 있기 때문이다. 데이터 엔지니어의 핵심 업무인 ETL은 대부분 서버에서 이루어지기 때문에 백엔드 엔지니어의 역량과 상당 부분이 일치한다고 한다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nhEtT/btsD4j7RY6J/qq9ZARGj9FkWU2KjzEV3Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nhEtT/btsD4j7RY6J/qq9ZARGj9FkWU2KjzEV3Pk/img.png&quot; data-alt=&quot;카카오페이증권 채용 공고&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nhEtT/btsD4j7RY6J/qq9ZARGj9FkWU2KjzEV3Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnhEtT%2FbtsD4j7RY6J%2Fqq9ZARGj9FkWU2KjzEV3Pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;561&quot; height=&quot;314&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;카카오페이증권 채용 공고&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;여러 기업들의 채용 공고를 살펴보면 데이터 엔지니어로서 갖추어야 할 기술 스택들을 확인할 수 있다. 대략적으로는 다음과 같다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터 수집 (Apache Kafka, Fluntd, Embulk, Logstash, Redis, Pub/Sub, Kinesis)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터 저장 (H&lt;span style=&quot;background-color: #ffffff;&quot;&gt;DFS, json, Parquet, AWS S3 or GCP Storage, RDB, NoSQL, Amazon Redshift, Google BigQuery)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터 처리 (A&lt;span style=&quot;background-color: #ffffff;&quot;&gt;pache Hadoop, Apache Spark, Apache Hive, SQL&lt;/span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;프로그래밍 언어 (Java, Python, Scala)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;리눅스&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라우드 서비스 (AWS, GCP, Azure)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Dashboard (Metabase, Superset, Zeppelin, Redash, Tableau)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Task Management Tool (Luigi, Airflow)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Docker&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;앞으로의 계획&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;전역 전까지는 혼자 공부하기 쉬운 백엔드 개발 위주로 공부할 생각이다. 데이터 엔지니어링 관련 도구들은 대부분 자바 기반으로 작성되어 있기 때문에 Java(Spring)를 집중적으로 공부하려고 한다. 그 외에도 데이터베이스, 알고리즘, 머신러닝 등을 추가적으로 공부하여 데이터 엔지니어가 되기 위한 기본기를 갖추겠다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/d2MQrg/btsI6jbzrv2/KhK6fjIVaP2XvkxTF9Iw2k/GEC_RlKijUXxjA_sat-aug-05-2023-09-56-04_kor.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;GEC_RlKijUXxjA_sat-aug-05-2023-09-56-04_kor.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.44MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/sE2pr/btsI5zMLBXl/5s0kWgdrhHWklZipOAc4YK/GEC_OPbcINQrKZ_fri-aug-11-2023-13-42-09_kor.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;GEC_OPbcINQrKZ_fri-aug-11-2023-13-42-09_kor.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.42MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bFv14q/btsI5NRrIm4/wsGyXDMioth8W1EiAINcak/GEC_csbxpMpQWS_tue-oct-10-2023-18-56-44_kor.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;GEC_csbxpMpQWS_tue-oct-10-2023-18-56-44_kor.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.42MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bqEkYA/btsI6SEtbNy/dp3I6uQpk3RJ2igzO6QA51/GEC_FAlmKdimHj_wed-jan-03-2024-14-16-13_kor.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;GEC_FAlmKdimHj_wed-jan-03-2024-14-16-13_kor.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.42MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT</category>
      <category>데이터 엔지니어</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/19</guid>
      <comments>https://clohoon.tistory.com/19#entry19comment</comments>
      <pubDate>Sat, 3 Feb 2024 09:57:24 +0900</pubDate>
    </item>
    <item>
      <title>React와 Firebase를 이용한 채팅 앱 만들기 - (2)</title>
      <link>https://clohoon.tistory.com/18</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. DM 채팅&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Side Panel Direct Message UI&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705390573021&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;span style={{ display: 'flex', alignItems: 'center' }}&amp;gt;
        &amp;lt;FaRegSmile style={{ marginRight: 3 }}/&amp;gt; DIRECT MESSAGES(1)
    &amp;lt;/span&amp;gt;

    &amp;lt;ul style={{ listStyleType: 'none', padding: 0 }}&amp;gt;
        {this.renderDirectMessages()}
    &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;user 목록 가져오기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;접속 중인 유저 파란색으로 표시&lt;/p&gt;
&lt;pre id=&quot;code_1705409368435&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;addUsersListeners = (currentUserId) =&amp;gt; {
    const {usersRef} = this.state;
    let usersArray = [];
    usersRef.on(&quot;child_added&quot;, DataSnapshot =&amp;gt; {
        if(currentUserId !== DataSnapshot.key) {
            let user = DataSnapshot.val()
            user[&quot;uid&quot;] = DataSnapshot.key
            user[&quot;status&quot;] = &quot;offline&quot;;
            usersArray.push(user)
            this.setState({users: usersArray})
        }
    })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해당 유저와 대화 할 방 아이디 생성&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나의 아이디와 상대방의 아이디를 이용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;상대가 방을 만들든 내가 만들든 아이디가 동일해야 하므로 userId &amp;lt; currentUserId 로직 사용&lt;/p&gt;
&lt;pre id=&quot;code_1705573870493&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;getChatRoomId = (userId) =&amp;gt; {
    const currentUserId = this.props.user.uid

    return userId &amp;gt; currentUserId
        ? `${userID}/${currentUserId}`
        : `${currentUserId}/${userId}`
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;currentChatRoom을 클릭한 방으로 변경&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705574260594&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;changeChatRoom = (user) =&amp;gt; {
    const chatRoomId = this.getChatRoomId(user.uid);
    const chatRoomData  = {
        id: chatRoomId,
        name: user.name
    }
    this.props.dispatch(setCurrentChatRoom(chatRoomData))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클릭한 방 private 방 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705577596716&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;this.props.dispatch(setPrivateChatRoom(true));&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클릭한 방 Active 방으로 표시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705577644094&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;this.setActiveChatRoom(user.uid);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;private, public icons&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705729170099&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{isPrivateChatRoom ? 
    &amp;lt;FaLock style={{marginBottom: '10px'}} /&amp;gt; 
    : 
    &amp;lt;FaLockOpen style={{marginBottom: '10px'}} /&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파일 올릴 때 prviate, public 구분&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705737692111&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const filePath = '${getPath()}/${file.name}';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6. 알림, 즐겨찾기 방&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Notification&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;알림 기능 : 여러 채팅 방 중 내가 현재 있는 채팅방이 아닌 곳에서 다른 사람이 채팅하면 하나의 쪽지당 하나의 count가 올라가는 알림&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-bootstrap.netlify.app/docs/components/badge/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap.netlify.app/docs/components/badge/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;채팅룸 정보를 가져올 때 알림 정보 리스너도 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705740211746&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// chatRooms.js AddChatRoomsListeners 메소드
this.addNotificationListener(DataSnapshot.key);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ChatRoom Collection에서 해당 ChatRoom Id에 해당하는 정보를 가져오기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;목표는 방 하나 하나에 맞는 알림 정보를 notifications state에 넣어주는 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이미 notifications state 안에 알림 정보가 들어있는 채팅방과 그렇지 않은 채팅방을 나누어준다.&lt;/p&gt;
&lt;pre id=&quot;code_1705836875561&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(index === -1) {
    notifications.push({
        id: chatRoomId,
        total: DataSnapshot.numChildren(),
        lastKnownTotal: DataSnapshot.numChildren(),
        count: 0
    })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;id : 채팅방 아이디&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;total : 해당 채팅방 전체 메시지 개수&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;lastKnownTotal : 이전에 확인한 전체 메시지 개수&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;count : 알림으로 사용될 숫자&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DataSnapshot.numChildren() : 전체 children 개수 = 전체 메시지 개수&lt;/p&gt;
&lt;pre id=&quot;code_1705929924371&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;else {
    // 상대방이 메세지를 보낸 채팅방에 있지 않을 때
    if(chatRoomId !== currentChatRoomId) {
        lastTotal = notifications[index].lastKnownTotal
        if(DataSnapshot.numChildren() - lastTotal &amp;gt; 0) {
            notifications[index].count = DataSnapshot.numChildren() - lastTotal;
        }
    }

    notifications[index].total = DataSnapshot.numChildren();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;notification 클릭해서 없애주기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705931478770&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;this.clearNotifications();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;messageRef 정리해주기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705931652053&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;componentWillUnmount() {
    this.state.chatRoomsRef.off();

    this.state.chatRooms.forEach(chatRoom =&amp;gt; {
        this.state.messagseRef.child(chatRoom.id).off();
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;즐겨찾기 방&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Favorite 클릭 버튼 UI&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Favorite 버튼 클릭 시 is Not Favorited면 user collection에서 추가, 아니면 제거&lt;/p&gt;
&lt;pre id=&quot;code_1705934034029&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleFavorite = () =&amp;gt; {
    if(isFavorited) {
        usersRef
            .child(`${user.uid}/favorited`)
            .child(chatRoom.id)
            .remove(err =&amp;gt; {
                if(err !== null) {
                    console.error(err);
                }
            })
        setIsFavorited(prev =&amp;gt; !prev);
    } else {
        usersRef
            .child(`${user.uid}/favorited`).update({
                [chatRoom.id]: {
                    name: chatRoom.name,
                    description: chatRoom.description,
                    createdBy: {
                        name: chatRoom.createdBy.name,
                        image: chatRoom.createdBy.image
                    }
                }
            })
        setIsFavorited(prev =&amp;gt; !prev);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DB : 클릭한 유저 id -&amp;gt; favorited -&amp;gt; 채팅방 id -&amp;gt; createdBy(image, name), description, name&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;새로고침해도 Favorited가 남아있게 하기&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;once() : it triggers once and then does not trigger again&lt;/p&gt;
&lt;pre id=&quot;code_1706006535670&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const addFavoriteListener = (chatRoomId, userId) =&amp;gt; {
    usersRef
        .child(userId)
        .child(&quot;favorited&quot;)
        .once(&quot;value&quot;)
        .then(data =&amp;gt; {
            if(data.val() !== null) {
                const chatRoomIds = Object.keys(data.val());
                const isAlreadyFavorited = chatRoomIds.includes(chatRoomId)
                setIsFavorited(isAlreadyFavorited)
            }
        })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Favorite 리스트 리스너 추가하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706019493331&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;addListeners = (userId) =&amp;gt; {
    const {usersRef} = this.state;

    usersRef
        .child(userId)
        .child(&quot;favorited&quot;)
        .on(&quot;child_added&quot;, DataSnapshot =&amp;gt; {
            const favoritedChatRoom = { id: DataSnapshot.key, ...DataSnapshot.val() }
            this.setState({
                favoritedChatRooms: [...this.state.favoritedChatRooms, favoritedChatRoom]
            })
        })

    usersRef
        .child(userId)
        .child(&quot;favorited&quot;)
        .on(&quot;child_removed&quot;, DataSnapshot =&amp;gt; {
            const chatRoomToRemove = { id: DataSnapshot.key, ...DataSnapshot.val() }
            const filteredChatRooms = this.state.favoritedChatRooms.filter(chatRoom =&amp;gt; {
                return chatRoom.id !== chatRoomToRemove.id;
            })
            this.setState({favoritedChatRooms: filteredChatRooms})
        })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Favorited를 위한 리스너 제거하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706019761084&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;componentWillUnmount() {
    if(this.props.user) {
        this.removeListener(this.props.user.uid);
    }
}

removeListener = (userId) =&amp;gt; {
    this.state.usersRef.child(`${userId}/favorited`).off();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;7. 채팅방 정보&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;각 사람당 글 쓴 데이터 가져오기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 userPosts에는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;key로&lt;/span&gt; 유저 이름이, value로 count, image가 들어간다.&lt;/p&gt;
&lt;pre id=&quot;code_1706538886453&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;userPostsCount = (messages) =&amp;gt; {
    let userPosts = messages.reduce((acc, message) =&amp;gt; {
        if(message.user.name in acc) {
            acc[message.user.name].count += 1;
        } else {
            acc[message.user.name] = {
                image: message.user.image,
                count: 1
            }
        }
        return acc;
    }, {})
    this.props.dispatch(setUserPosts(userPosts))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;userPosts 데이터 count 큰 순서대로 보여주기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/entries&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/entries&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706539830147&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const renderUserPosts = (userPosts) =&amp;gt;
    Object.entries(userPosts)
        .sort((a,b) =&amp;gt; b[1].count - a[1].count)
        .map(([key, val], i) =&amp;gt; (
            &amp;lt;Media key={i}&amp;gt;
                &amp;lt;img
                    style={{borderRadius: 25}}
                    width={48}
                    height={48}
                    className=&quot;mr-3&quot;
                    src={val.image}
                    alt={val.name}
                /&amp;gt;
                &amp;lt;Media.Body&amp;gt;
                    &amp;lt;h6&amp;gt;{key}&amp;lt;/h6&amp;gt;
                    &amp;lt;p&amp;gt;
                        {val.count} 개
                    &amp;lt;/p&amp;gt;
                &amp;lt;/Media.Body&amp;gt;
            &amp;lt;/Media&amp;gt;
        ))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;8. Typing&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Typing 시작 시 Typing 정보 데이터베이스에 저장&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706660868088&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleKeyDown = () =&amp;gt; {
    if(event.ctrlKey &amp;amp;&amp;amp; event.keyCode === 13) {
        handleSubmit();
    }

    if(content) {
        typingRef.child(chatRoom.id).child(user.uid).set(user.displayName)
    } else {
        typingRef.child(chatRoom.id).child(user.uid).remove();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;채팅 보내면 typing 정보 데이터베이스에서 지우기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706660764147&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typingRef.child(chatRoom.id).child(user.uid).remove();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리스너를 이용해 데이터베이스에서 Typing 정보를 가져오기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가져올 때 나의 Typing은 제외&lt;/p&gt;
&lt;pre id=&quot;code_1706663059409&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;addTypingListeners = (chatRoomId) =&amp;gt; {
    let typingUsers= [];
    this.state.typingRef.child(chatRoomId).on(&quot;child_added&quot;,
        DataSnapshot =&amp;gt; {
            if(DataSnapshot.key !== this.props.user.uid) {
                typingUsers = typingUsers.concat({
                    id: DataSnapshot.key,
                    name: DataSnapshot.val()
                });
                this.setState({ typingUsers });
            }
        })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataSnapshot.key : 타이핑하는 유저, this.props.user.uid : 현재 로그인한 유저&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터베이스에서 Typing 정보 제거되면 State에서도 지우기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706663533280&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;this.state.typingRef.child(chatRoomId).on(&quot;child_removed&quot;,
    DataSnapshot =&amp;gt; {
        const index = typingUsers.findIndex(user =&amp;gt; user.id === DataSnapshot.key);
        if(index !== -1) {
            typingUsers = typingUsers.filter(user =&amp;gt; user.id !== DataSnapshot.key);
            this.setState({ typingUsers });
        }
    }
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Typing UI 추가하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706669296165&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;renderTypingUsers = (typingUsers) =&amp;gt; 
    typingUsers.length &amp;gt; 0 &amp;amp;&amp;amp;
    typingUsers.map(user =&amp;gt; (
        &amp;lt;span&amp;gt;{user.name}님이 채칭을 입력하고 있습니다...&amp;lt;/span&amp;gt;
    ))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Typing 리스너 제거하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706674462631&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;addToListenerLists = (id, ref, event) =&amp;gt; {
    // 이미 등록된 리스너인지 확인
    const index = this.state.listenerLists.findIndex(listener =&amp;gt; {
        return (
            listener.id === id &amp;amp;&amp;amp;
            listener.ref === ref &amp;amp;&amp;amp;
            listener.event === event
        )

        if(index === -1) {
            const newListener = {id, ref, event}
            this.setState({
                listenerLists: this.state.listenerLists.concat(newListener)
            })
        }
    })
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1706674512014&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;componentWillUnmount() {
    this.state.messagesRef.off();
    this.removeListeners(this.state.listenerLists);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;9. Auto Scroll &amp;amp; Skeleton&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지 보낼 때 자동으로 스크롤 내리기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡은 자리 계속 참조할 수 있게 -&amp;gt; Refs를 생성 React.createRef() -&amp;gt; 생성한 Refs를 ref attribute를 통해 넣어주기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 메시지를 보낼 때마다 참조하고 있는 곳으로 스크롤 내려주기&lt;/p&gt;
&lt;pre id=&quot;code_1706677579742&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div ref = {node =&amp;gt; (this.messageEndRef = node)}/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1706677620020&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;componentDidUpdate() {
    if(this.messageEndRef) {
        this.messageEndRef.scrollIntoView({ behavior : 'smooth' })
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지 로딩 중 스켈레톤 처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스켈레톤 10개 만들기 -&amp;gt; 컴포넌트 10개 넣어도 되지만 Array Constructor를 이용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1706705563956&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;renderMessageSkeleton = (loading) =&amp;gt;
    loading &amp;amp;&amp;amp; (
        &amp;lt;&amp;gt;
            {[...Array(10)].map((v,i) =&amp;gt; (
                &amp;lt;Skeleton key={i} /&amp;gt;
            ))}
        &amp;lt;/&amp;gt;
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ctrl + 엔터키로 메시지 보내기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706705757767&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(event.ctrlKey &amp;amp;&amp;amp; event.keyCode === 13) {
	handleSubmit();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;10. 데이터베이스 규칙, 스토리지 규칙, 배포&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Firebase Storage Rule&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;악성 유저들로부터 파일을 보호하기 위해 사용&lt;/p&gt;
&lt;pre id=&quot;code_1703754009846&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /message {
      allow read : if request.auth != null;
      allow write : request.auth != null &amp;amp;&amp;amp;
      request.resource.contentType.matches('image/.*) &amp;amp;&amp;amp;
      request.resource.size &amp;lt; 10*1024*1024;
    }
    match /user_image {
      match / {userId} {
        allow read : if request.auth != null;
        allow write : request.auth != null &amp;amp;&amp;amp;
        request.auth.uid == userId &amp;amp;&amp;amp;
        request.resource.contentType.matches('image/.*') &amp;amp;&amp;amp;
        request.resource.size &amp;lt; 10*1024*1024;
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/message 경로 해당 파일들, 인증된 유저 read, write 가능, 이미지 파일만 업로드 가능, 파일 사이즈 10mb 이하 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Firebase Database Rule&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1703758754242&quot; class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;{
    &quot;rules&quot; : {
        &quot;chatRooms&quot; : {
            &quot;.read&quot; : &quot;auth != null&quot;,
            &quot;$chatRoomId&quot; : {
                &quot;.write&quot; : &quot;auth != null&quot;,
                &quot;.validate&quot; : &quot;newData.hasChildren(['id', 'name', 'createdBy', 'description'])&quot;,
                &quot;id&quot; : {
                    &quot;.validate&quot; : &quot;newData.val() === $chatRoomId&quot;
                },
                &quot;name&quot; : {
                    &quot;.validate&quot; : &quot;newData.val().length &amp;gt; 0&quot;
                },
                &quot;description&quot; : {
                    &quot;.validate&quot; : &quot;newData.val().length &amp;gt; 0&quot;
                }
            }
        },
        &quot;messages&quot; : {
            &quot;.read&quot; : &quot;auth != null&quot;,
            &quot;.write&quot; : &quot;auth != null&quot;,
            &quot;content&quot; : {
                &quot;.validate&quot; : &quot;newData.val().length &amp;gt; 0&quot;
            },
            &quot;image&quot; : {
                &quot;.validate&quot; : &quot;newData.val().length &amp;gt; 0&quot;
            },
            &quot;user&quot; : {
                &quot;.validate&quot; : &quot;newData.hasChildren(['id', 'image', 'name'])&quot;
            }
        },
        &quot;presence&quot; : {
            &quot;.read&quot; : &quot;auth != null&quot;,
            &quot;.write&quot; : &quot;auth != null&quot;
        },
        &quot;typing&quot; : {
            &quot;.read&quot; : &quot;auth != null&quot;,
            &quot;.write&quot; : &quot;auth != null&quot;
        },
        &quot;users&quot; : {
            &quot;.read&quot; : &quot;auth != null&quot;,
            &quot;$uid&quot; : {
                &quot;write&quot; : &quot;auth != null &amp;amp;&amp;amp; auth.uid === $uid&quot;,
                &quot;.validate&quot; : &quot;newData.hasChildren(['name', 'image'])&quot;,
                &quot;name&quot; : {
                	&quot;.validate&quot; : &quot;newData.val().length &amp;gt; 0&quot;
            	},
            	&quot;image&quot; : {
                	&quot;.validate&quot; : &quot;newData.val().length &amp;gt; 0&quot;
            	}
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;애플리케이션 배포&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. firebase tool 설치 : npm install firebase-tools -g&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. firebase login -&amp;gt; social Oauth 로그인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. firebase 프로젝트 시작 :&amp;nbsp; firebase init&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Database, Storage 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 배포를 위한 빌드 : npm run build&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. firebase.json&lt;/p&gt;
&lt;pre id=&quot;code_1703762349252&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;hosting&quot; : {
	&quot;public&quot; : &quot;./build&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 빌드 : firebase deploy&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 코드 수정하면 5번부터 다시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Storage&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; message&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; user_image&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터베이스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; chatRooms&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;gt; 채팅방 아이디&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; createdBy : image, name&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; description&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; id&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; name&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; messages&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;gt; 채팅방 아이디&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; 메세지&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;gt; content / image&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;gt; timestamp&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;gt; user : id, image, name&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; presence&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; typing&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; users&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;gt; 유저 아이디&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; image&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; name&lt;/p&gt;</description>
      <category>IT</category>
      <category>Firebase</category>
      <category>react</category>
      <category>Redux</category>
      <category>채팅 앱</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/18</guid>
      <comments>https://clohoon.tistory.com/18#entry18comment</comments>
      <pubDate>Thu, 1 Feb 2024 16:43:51 +0900</pubDate>
    </item>
    <item>
      <title>React와 Firebase를 이용한 채팅 앱 만들기 - (1)</title>
      <link>https://clohoon.tistory.com/16</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;전체 코드 : GitHub&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;0. 개념&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Redux란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baM2tX/btsBGk2Fgw0/VaRsbVSm18XUynjIO44AwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baM2tX/btsBGk2Fgw0/VaRsbVSm18XUynjIO44AwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baM2tX/btsBGk2Fgw0/VaRsbVSm18XUynjIO44AwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaM2tX%2FbtsBGk2Fgw0%2FVaRsbVSm18XUynjIO44AwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;189&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redux is a predictable state container for JavaScript apps.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgurdD/btsBMVtP8VT/PfJCRlIcklktmYBRk2xu41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgurdD/btsBMVtP8VT/PfJCRlIcklktmYBRk2xu41/img.png&quot; data-alt=&quot;Redux 데이터 Flow&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgurdD/btsBMVtP8VT/PfJCRlIcklktmYBRk2xu41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgurdD%2FbtsBMVtP8VT%2FPfJCRlIcklktmYBRk2xu41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;293&quot; height=&quot;172&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Redux 데이터 Flow&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Action : a plain object describing what happened&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reducer : a function describing how the application's state changes&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store : the object that brings them together&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reducer는 action이 store를 어떻게 업데이트 하는지 기술하는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reducer는 항상 state와 action을 받아 state를 return 해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dispatch를 사용하여 store의 reducer에 action을 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Action은 객체 형식이어야 되는데 언제나 객체 형식으로 된 action을 받지 않음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; redux-promise / redux-thunk :&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;teaches dispatch how to accept promises / functions&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useSelector&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useSelector는 React의 리덕스 스토어 관련 Hook 중 하나로, 스토어의 상태값을 반환해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useSelector를 이용한 함수에서 리덕스 스토어의 상태값이 바뀐다면 바뀐 스토어의 상태값을 다시 가져와 컴포넌트를 렌더링시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useSelector는 함수형 컴포넌트에서만 사용 가능하고 클래스형 컴포넌트는 connect를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useEffect&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 형태 : useEffect( function, deps )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록 하는 Hook&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React Router DOM&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React로 생성된 SPA 내부에서 페이지 이동이 가능하도록 만들어주는 라이브러리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- BrowserRouter : history API를 이용해 history 객체를 생성, 라우팅을 진행할 컴포넌트 상위에 BrowserRouter 컴포넌트를 생성하고 감싸주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Route : 현재 브라우저의 location 상태에 따라 다른 element를 렌더링한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Routes : location 변경 시 하위에 있는 모든 Route를 조회해 현재 location과 맞는 Route를 찾아준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Firebase&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증, 데이터베이스, 스토리지, 푸시 알림, 배포 등등 어플리케이션을 만들 때 필요한 부분들을 자동으로 만들어주는 플랫폼이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase에서 사용하는 데이터베이스는 NoSQL 기반의 데이터베이스로, RTSP라는 Real Time Stream Protocol 방식을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 기본 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; assets/images : 이미지, CSS, JS 파일들 보관&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; commons&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; components : 여러 페이지에서 쓰이는 것들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Modals&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Tooltips&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; types : typescript 사용시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; components&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; Chatpage&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; Loginpage&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; Registerpage&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; redux&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; actions&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; reducers&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; App.css&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; App.js : Routing 관련&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; firebase.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;노드 설치&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;react-firebase-chat-app&amp;nbsp; 폴더 생성&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리액트 설치 : npx create-react-app ./&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ES7 React/Redux/GraphQL/React-Native snippets 설치 (rfce 사용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://reactrouter.com/en/main/start/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://reactrouter.com/en/main/start/overview&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm install react-router-dom --save&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 설치 : npm install redux react-redux redux-promise redux-thunk --save&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Add Firebase to your web app -&amp;gt; SDK 코드 이용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm install firebase --save&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src 폴더 안에 firebase.js 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import&amp;nbsp;&quot;firebase/auth&quot;; &lt;br /&gt;import&amp;nbsp;&quot;firebase/database&quot;; &lt;br /&gt;import&amp;nbsp;&quot;firebase/storage&quot;;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 인증 처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 프레임워크로 React Bootstrap 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-bootstrap.github.io/docs/getting-started/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap.github.io/docs/getting-started/introduction&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm install react-bootstrap bootstrap --save&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; letter-spacing: 0px;&quot;&gt;import 'bootstrap/dist/css/bootstrap.min.css';&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-icons.github.io/react-icons/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-icons.github.io/react-icons/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm install react-icons --save&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codesandbox.io/p/sandbox/react-hook-form-get-started-kw7z2q2n15?file=%2Fsrc%2Findex.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codesandbox.io/p/sandbox/react-hook-form-get-started-kw7z2q2n15?file=%2Fsrc%2Findex.js&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css 코드 사용 : RegisterPage에만 적용하기 위해서 .auth-wrapper 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-hook-form을 이용한 회원 가입 유효성 체크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm install react-hook-form --save&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특정 DOM 선택하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS : getElementById 같은 DOM Selector 함수 이용해서 DOM 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React : ref 이용해서 DOM을 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 컴포넌트 -&amp;gt; React.createRef&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수형 컴포넌트 -&amp;gt; useRef&lt;/p&gt;
&lt;pre id=&quot;code_1704286648072&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const password = useRef();
password.current = watch(&quot;password&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1704286717585&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;input 
	name=&quot;password_confirm&quot; 
	type=&quot;password&quot;
	ref={register({ 
		required: true, 
		validate: (value) =&amp;gt;
			value === password.current
	})}
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;firebase에서 이메일로 유저 생성 + 생성 중 &lt;b&gt;버튼을 누르지 못하게 막기&lt;/b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704290563494&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const onSubmit = async (data) =&amp;gt; {
    try{
        setLoading(true)
        let createdUser = await firebase
            .auth()
            .createUserWithEmailAndPassword(data.email, data.password)
        console.log('createdUser', createdUser)
        setLoading(false)
    } catch(error) {
        setErrorFromSubmit(error.message)
        setLoading(false)
        setTimeout(() =&amp;gt; {
            setErrorFromSubmit(&quot;&quot;)
        }, 5000);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1704290957719&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;input type=&quot;submit&quot; disabled={loading} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성된 유저에 정보 추가하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;updateprofile 메소드를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MD5 : 유니크한 값을 가지려고 사용되는 모듈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm install md5 --save&lt;/p&gt;
&lt;pre id=&quot;code_1704291668442&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await createdUser.user.updateProfile({
    displayname: data.name,
    photoURL: `http://gravatar.com/avatar/${md5(createdUser.user.email)}?d=identicon`
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성한 유저 데이터베이스에 저장&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704292855149&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await firebase.database().ref(&quot;users&quot;).child(createdUser.user.uid).set({
    name: createdUser.user.displayName,
    image: createdUser.user.photoURL
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그인 페이지 만들기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1704351879814&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await firebase.auth().signInWithEmailAndPassword(data.email, data.password);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 한 유저는 Chatting Page만 이용 가능 -&amp;gt; Login Page/Register Page 접근 시 Chatting Page로 이동&lt;/p&gt;
&lt;pre id=&quot;code_1704358515880&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
    firebase.auth().onAuthStateChanged(user =&amp;gt; {
        console.log('user', user)
        // 로그인이 된 상태
        if(user) {
            history.push(&quot;/&quot;);
        } else {
            history.push(&quot;/login&quot;);
        }
    })
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class Component는 withRouter, Functional Component는 useHistory&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BrowserRouter를 index.js에서 감싸주기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Redux 스토어에 로그인 유저 정보 저장&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705129387392&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(user) {
    history.push(&quot;/&quot;);
    dispatch(setUser(user));
} else {
    history.push(&quot;/login&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 채팅 페이지 기본 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpIQUi/btsDqQFsg36/o53u3S2gYiej0vNUSYU9XK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpIQUi/btsDqQFsg36/o53u3S2gYiej0vNUSYU9XK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpIQUi/btsDqQFsg36/o53u3S2gYiej0vNUSYU9XK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpIQUi%2FbtsDqQFsg36%2Fo53u3S2gYiej0vNUSYU9XK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;556&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatPage&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;gt; MainPanel&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; MainPanel.js&amp;nbsp; (클래스 컴포넌트, rce)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; Message.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; MessageForm.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; MessageHeader.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;gt; SidePanel&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; ChatRooms.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; DirectMessages.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; Favorited.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; Sidepanel.js&amp;nbsp; (함수형 컴포넌트, rfce)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; UserPanel.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;gt; ChatPage.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UserPanel Logo&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import { IoIosChatboxes } from 'react-icons/io';&lt;/p&gt;
&lt;pre id=&quot;code_1702798008796&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;h3 style = {{ color : 'white' }}&amp;gt;
	&amp;lt;IoIosChatboxes/&amp;gt;
	{&quot; &quot;} Chat App
&amp;lt;/h3&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;User Dropdown&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import Dropdown from 'react-bootstrap/Dropdown';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;import Image from &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;'react-bootstrap/Image';&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;유저 정보 가져오기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702798790770&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const user = useSelector(state =&amp;gt; state.user.currentUser)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그아웃 구현&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702798949940&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const handleLogout = () =&amp;gt; {
	firebase.auth().signOut();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그아웃 버튼 누르면 App.js에서 user 정보가 없어서 history.push(&quot;/login&quot;); 실행&lt;/p&gt;
&lt;pre id=&quot;code_1704635813821&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function clearUser() {
	return {
		type: CLEAR_USER
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스토어에 있는 유저 정보도 지워주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로필 이미지 업로드 패널&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702800006704&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleOpenImageRef = () =&amp;gt; {
	inputOpenImageRef.current.click();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1702800040865&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;input
	type = &quot;file&quot;
	accept = &quot;image/jpeg, image/png&quot;
	ref = {inputOpenImageRef}
	style = {{display : &quot;none&quot;}}
	onChange = {handleUploadImage}
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;firebase는 파일은&lt;span&gt;&amp;nbsp;&lt;/span&gt;Storage에, 파일에 대한 정보는 DB에 저장한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Firebase storage 이미지 업로드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase storage -&amp;gt; location : asia-northeast1 (가까운 곳)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mime.lookup(path) : Lookup the content type associated with a file&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm install mime-types --save&lt;/p&gt;
&lt;pre id=&quot;code_1702803171812&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const file = event.target.files[0];

if(!file) return;
const metadata = {contentType : mime.lookup(file.name)};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1702803244044&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;let uploadTaskSnapshot = await firebase.storage().ref()
	.child(`user_image/${user.uid}`)
	.put(file, metadata)
    
let downloadURL = await uploadTaskSnapshot.ref.getDownloadURL();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metadata 예시 : {contentType: &quot;image/png&quot;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Auth 서비스 프로필 수정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702805963611&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;firebase.auth().currentUser.updateProfile({
	photoURL : downloadURL
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Redux 유저 이미지 교체&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702806034429&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dispatch(setPhotoURL(downloadURL))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터베이스에 이미지 URL 저장&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702806060655&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;firebase.database.ref(&quot;users&quot;)
	.child(user.uid)
	.update({image : downloadURL})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;채팅 룸 리스트 UI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react bootstrap - modals - live demo 이용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-bootstrap.netlify.app/docs/components/modal/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap.netlify.app/docs/components/modal/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react bootstrap - forms 이용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-bootstrap.netlify.app/docs/forms/overview/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap.netlify.app/docs/forms/overview/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ChatRoom 생성하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702996042833&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;handleSubmit = (e) =&amp;gt; {
	e.preventDefault();
	const {name, description} = this.state;
   
	if(this.isFormValid(name, description)) {
		this.addChatRoom();
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push()로 auto-generated key 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Add 된 데이터 Listener로 받기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1703071197330&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;AddChatRoomsListeners = () =&amp;gt; {
	let chatRoomsArray = [];
	this.state.chatRoomsRef.on(&quot;child_added&quot;, DataSnapshot =&amp;gt; {
		chatRoomsArray.push(DataSnapshot.val());
		this.setState({chatRooms : chatRoomsArray});
	})
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;chatRooms 데이터 화면에 보여주기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Set current chat room&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chatroom 클릭 시 해당 데이터 가져오기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가져온 current chat room 데이터 리덕스 스토어에 넣어주기&lt;/p&gt;
&lt;pre id=&quot;code_1705121570388&quot; class=&quot;coffeescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;changeChatRoom = (room) =&amp;gt; {
    this.props.dispatch(setCurrentChatRoom(room));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 load 시 첫 번째 chat room을 current chat room으로&lt;/p&gt;
&lt;pre id=&quot;code_1705121809963&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setFirstChatRoom = () =&amp;gt; {
    const firstChatRoom = this.state.chatRooms[0];
    if(this.state.firstLoad &amp;amp;&amp;amp; this.state.chatRooms.length &amp;gt; 0) {
        this.props.dispatch(setCurrentChatRoom(firstChatRoom));
    }
    this.setState({firstLoad : false});
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;active chat room은 다른 색상 주기&lt;/p&gt;
&lt;pre id=&quot;code_1705121483735&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;style={{ 
    backgroundColor: room.id === this.state.activeChatRoomId &amp;amp;&amp;amp;
        '#ffffff45'
}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴포넌트 제거 시 Listener도 제거&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1703066718768&quot; class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;componentWillUnmount() {
	this.state.chatRooms.off();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 공개방 채팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지 Header 부분 UI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-bootstrap auto-layout columns, input group, accordion 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-bootstrap.netlify.app/docs/layout/grid&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap.netlify.app/docs/layout/grid&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-bootstrap.netlify.app/docs/forms/input-group/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap.netlify.app/docs/forms/input-group/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-bootstrap.netlify.app/docs/components/accordion/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap.netlify.app/docs/components/accordion/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지 Body, Form UI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-bootstrap Form controls : example, progress bars 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-bootstrap.netlify.app/docs/forms/form-control/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap.netlify.app/docs/forms/form-control/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react-bootstrap.netlify.app/docs/components/progress/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap.netlify.app/docs/components/progress/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메세지 데이터 하나를 데이터 베이스에 넣기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB : messages -&amp;gt; 채팅방 아이디 -&amp;gt; 채팅 메시지 아이디 -&amp;gt; content, timestamp, user&lt;/p&gt;
&lt;pre id=&quot;code_1705238751542&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleSubmit = async () =&amp;gt; {
    if(!content) {
        setErrors(prev=&amp;gt;prev.concat(&quot;Type contents first&quot;))
        return;
    }
    setLoading(true);

    // firebase에 메시지 저장
    try {
        await messagesRef.child(chatRoom.id).push().set(createMessage())
        setLoading(false);
        setContent(&quot;&quot;);
        setErrors([]);
    } catch(error) {
        setErrors(pre =&amp;gt; pre.concat(error.message))
        setLoading(false);
        setTimeout(() =&amp;gt; {
            setErrors([]);
        }, 5000);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지 데이터 실시간으로 가져오기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705242554674&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;addMessagesListeners = (chatRoomId) =&amp;gt; {
    let messagesArray = [];
    this.state.messageRef.child(chatRoomId).on(&quot;child_added&quot;, DataSnapshot =&amp;gt; {
        messagesArray.push(DataSnapshot.val());
        this.setState({
            messages: messagesArray,
            messagesLoading: false
        })
    })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지 보여주기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705299865248&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;renderMessages = (messages) =&amp;gt; {
    messages.length &amp;gt; 0 &amp;amp;&amp;amp;
    messages.map(message =&amp;gt; (
        &amp;lt;Message 
            key={message.timestamp}	
            message={message}
            user={this.props.user}
        /&amp;gt;
    ))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지 컴포넌트 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;media heading 사용 :&amp;nbsp;&lt;a href=&quot;https://react-bootstrap-v4.netlify.app/layout/media/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://react-bootstrap-v4.netlify.app/layout/media/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm install moment --save&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;moment(timestamp).fromNow()&amp;nbsp; &amp;lt;-&amp;nbsp; ~hours ago 로 바꾸어줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미지 파일 저장&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705303088259&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleUploadImage = async (event) =&amp;gt; {
    const file = event.target.files[0];

    const filePath = '/message/public/${file.name}';
    const metadata = { contentType : mime.lookup(file.name)};

    try {
        await storageRef.child(filePath).put(file, metadata)
    } catch(error) {
        alert(error)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저장 퍼센티지&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705315925455&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let uploadTask = storageRef.child(filePath).put(file, metadata)

// 퍼센티지 구하기
uploadTask.on(&quot;state_changed&quot;, UploadTaskSnapshot =&amp;gt; {
    const percentage = Math.round(
        (UploadTaskSnapshot.bytesTransferred / UploadTaskSnapshot.totalBytes)*100
    )
    setpercentage(percentage)
}),
err =&amp;gt; {
    console.error(err);
    setLoading(false);
},
() =&amp;gt; {
    // 저장이 다 된 후 파일 메시지 전송(DB에 저장)
    // 저장된 파일을 다운로드 받을 수 있는 URL 가져오기
    uploadTask.snapshot.ref.getDownloadURL()
    .then(downloadURL =&amp;gt; {
        messagesRef.child(chatRoom.id).push().set(createMessage(downloadURL))
        setLoading(false);
    })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메시지 검색&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규 표현식 만들기, match() 사용 : &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Regular_expressions&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Regular_expressions&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;g modifier : global, i modifier : insensitive (대소문자 구분 X)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Array.reduce() : 배열의 각 요소에 대해 주어진 reducer 함수를 실행하고, 하나의 결과값을 반환한다&lt;/p&gt;
&lt;pre id=&quot;code_1705329851906&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;handleSearchMessages = () =&amp;gt; {
    const chatRoomMessages = [...this.state.messages];
    const regex = new RegExp(this.state.searchTerm, &quot;gi&quot;);
    const searchResults = chatRoomMessages.reduce((acc, message) =&amp;gt; {
        if(
            (message.content &amp;amp;&amp;amp; message.content.match(regex)) ||
            message.user.name.match(regex)
        ) {
            acc.push(message)
        }
        return acc;
    }, [])
    this.setState({ searchResults })
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT</category>
      <category>Firebase</category>
      <category>react</category>
      <category>Redux</category>
      <category>채팅 앱</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/16</guid>
      <comments>https://clohoon.tistory.com/16#entry16comment</comments>
      <pubDate>Mon, 15 Jan 2024 23:46:23 +0900</pubDate>
    </item>
    <item>
      <title>크롬 확장 앱 만들기</title>
      <link>https://clohoon.tistory.com/15</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;직접 링크를 입력해 접속한 사이트들 중 가장 많이 접속한 10개의 사이트들을 나열해주는 Chrome Extension App을 만들어보자!&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; &lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;  &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;Chrome Extension 구성 요소&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Chrome Extension App을 만드는 데 필요한 세 가지 요소는 manifest.json, Icon, HTML/CSS/JS이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;manifest.json&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701255669649&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;Typed URL History&quot;,
  &quot;version&quot;: &quot;1.2&quot;,
  &quot;description&quot;: &quot;Reads your history, and shows the top ten pages you go to by typing the URL.&quot;,
  &quot;permissions&quot;: [
    &quot;history&quot;
  ],
  &quot;browser_action&quot;: {
    &quot;default_popup&quot;: &quot;typedUrls.html&quot;,
    &quot;default_icon&quot;: &quot;clock.png&quot;
  },
  &quot;manifest_version&quot;: 2 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Icon&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;a href=&quot;https://www.shopify.com/tools/logo-maker&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.shopify.com/tools/logo-maker&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701442410368&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;FREE Logo Maker - Create a Logo in Seconds - Shopify (2023)&quot; data-og-description=&quot;Free Logo MakerCreate professional logos in secondsIntroducing Hatchful - the custom logo maker.Create a Logo NowDesign a logo from hundreds of templatesHow to make logo in 5 simple stepsBrowse and customize templates:Explore the logo templates and persona&quot; data-og-host=&quot;www.shopify.com&quot; data-og-source-url=&quot;https://www.shopify.com/tools/logo-maker&quot; data-og-url=&quot;https://www.shopify.com/tools/logo-maker&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b3hS9m/hyUFeWTxsD/R0sZrxlkyPmQL74O2e6NbK/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/UChzG/hyUE317P1J/HC6C6OLVoQjVbbF80R5TAk/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/doOagn/hyUFdp7c50/iiwhLtk58K6FXK0piOmpv0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500&quot;&gt;&lt;a href=&quot;https://www.shopify.com/tools/logo-maker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.shopify.com/tools/logo-maker&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b3hS9m/hyUFeWTxsD/R0sZrxlkyPmQL74O2e6NbK/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/UChzG/hyUE317P1J/HC6C6OLVoQjVbbF80R5TAk/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500,https://scrap.kakaocdn.net/dn/doOagn/hyUFdp7c50/iiwhLtk58K6FXK0piOmpv0/img.png?width=500&amp;amp;height=500&amp;amp;face=0_0_500_500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;FREE Logo Maker - Create a Logo in Seconds - Shopify (2023)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Free Logo MakerCreate professional logos in secondsIntroducing Hatchful - the custom logo maker.Create a Logo NowDesign a logo from hundreds of templatesHow to make logo in 5 simple stepsBrowse and customize templates:Explore the logo templates and persona&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.shopify.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;무료 로고 이미지를 생성할 수 있다. clock.png를 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;typedUrls.html&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701429687913&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE HTML&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Recently Typed URLs&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
      body {min-width: 250px;}
    &amp;lt;/style&amp;gt;
    &amp;lt;script src='typedUrls.js'&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;

  &amp;lt;body&amp;gt;
    &amp;lt;h2&amp;gt;Recently Typed URLs:&amp;lt;/h2&amp;gt;
    &amp;lt;div id=&quot;typedUrl_div&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;typedUrls.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701429726545&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 클릭 시 해당 url을 새 탭에서 열어주는 함수
function onAnchorClick(event) {
  chrome.tabs.create({
    selected: true,
    url: event.srcElement.href
  });
  return false;
}

// Top 10 Visitied url 배열이 주어졌을 때 각각의 url들을 popup.html에 띄워주기
function buildPopupDom(divName, data) {
  var popupDiv = document.getElementById(divName);

  var ul = document.createElement('ul');
  popupDiv.appendChild(ul);

  for (var i = 0, ie = data.length; i &amp;lt; ie; ++i) {
    var a = document.createElement('a');
    a.href = data[i];
    a.appendChild(document.createTextNode(data[i]));
    a.addEventListener('click', onAnchorClick);

    var li = document.createElement('li');
    li.appendChild(a);
    ul.appendChild(li);
  }
}

// 가장 핵심이 되는 함수. history를 검색하여 top 10 url을 찾아내어 
// 위에서 정의한 함수로 popup.html에 해당 링크들을 띄워주는 함수
function buildTypedUrlList(divName) {
  var microsecondsPerWeek = 1000 * 60 * 60 * 24 * 7;
  var oneWeekAgo = (new Date).getTime() - microsecondsPerWeek;

  var numRequestsOutstanding = 0;

  chrome.history.search({
      'text': '',
      'startTime': oneWeekAgo
    },
    function(historyItems) {
      for (var i = 0; i &amp;lt; historyItems.length; ++i) {
        var url = historyItems[i].url;
        
        var processVisitsWithUrl = function(url) {
          return function(visitItems) {
            processVisits(url, visitItems);
          };
        };

        chrome.history.getVisits({url: url}, processVisitsWithUrl(url));
        numRequestsOutstanding++;
      }
      if (!numRequestsOutstanding) {
        onAllVisitsProcessed();
      }
    });

  var urlToCount = {};

  var processVisits = function(url, visitItems) {
    for (var i = 0, ie = visitItems.length; i &amp;lt; ie; ++i) {
      if (visitItems[i].transition != 'typed') {
        continue;
      }
      if (!urlToCount[url]) {
        urlToCount[url] = 0;
      }
      urlToCount[url]++;
    }

    if (!--numRequestsOutstanding) {
      onAllVisitsProcessed();
    }
  };

  var onAllVisitsProcessed = function() {
    urlArray = [];
    for (var url in urlToCount) {
      urlArray.push(url);
    }

    urlArray.sort(function(a, b) {
      return urlToCount[b] - urlToCount[a];
    });

    buildPopupDom(divName, urlArray.slice(0, 10));
  };
}

document.addEventListener('DOMContentLoaded', function () {
  buildTypedUrlList(&quot;typedUrl_div&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;b&gt; &lt;b&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;&amp;nbsp;Chrome Extension 등록&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;chrome://extensions 접속 -&amp;gt; 개발자 모드 활성화 -&amp;gt; 압축해제된 확장 프로그램을 로드합니다. -&amp;gt; 폴더 선택&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; &lt;b&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt; 참고자료&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.chrome.com/docs/extensions/reference/tabs/#method-create&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.chrome.com/docs/extensions/reference/tabs/#method-create&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.chrome.com/docs/extensions/reference/history/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.chrome.com/docs/extensions/reference/history/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/sort&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/sort&lt;/a&gt;&lt;/p&gt;</description>
      <category>IT</category>
      <category>크롬확장앱</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/15</guid>
      <comments>https://clohoon.tistory.com/15#entry15comment</comments>
      <pubDate>Fri, 1 Dec 2023 23:56:46 +0900</pubDate>
    </item>
    <item>
      <title>Redis에 대해 알아보자!</title>
      <link>https://clohoon.tistory.com/14</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;b&gt;&lt;b&gt;&lt;/b&gt;&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;Redis란?&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Redis는 Remote Dictionary Server의 약자로서, 키-값 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터베이스 관리 시스템이다. 메모리 기반의 저장소이기 때문에 필요한 정보를 빠르게 저장하고 가져올 수 있는 실시간 서비스에 적합한 저장소이다. 따라서 전체 데이터를 영구히 저장하기보다는, 캐시처럼 휘발성 데이터를 저장하는 데 많이 사용된다. 데이터를 지속적으로 유지하기 위해 모든 작업을 로그에 기록해서 디스크에 저장한 후, 시스템을 구동할 때 로그를 기반으로 데이터를 다시 메모리에 올린다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;   &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;Redis 사용 방법&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;npm install redis&amp;nbsp; :&amp;nbsp; Redis 모듈 설치&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;npm install hiredis redis&amp;nbsp; :&amp;nbsp; Non-Blocking의 빠른 모듈 설치&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;sudo apt-get install redis-server&amp;nbsp; :&amp;nbsp; Redis 서버 설치&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;Redis 서버와 연결하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697946536259&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var redis = require('redis');
var client = redis.createClient();           
client.on('error', function (err) {
    console.log('Error ' + err);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;데이터 조작하기(키/값)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;set은 일반적인 key, value 한 쌍을 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697947050945&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;client.set('String Key', 'String Value', redis.print);  // redis.print : 수행결과 출력 혹은 오류 출력. redis.print는 없어도 상관없음. 없으면 결과 출력은 되지 않고 값만 저장
client.get('String Key', function(err, value){
    if(err) throw err;
    console.log(value);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;코드를 실행시키기 전에 redis-server 코드를 이용해 레디스 서버를 실행시켜준 후 코드를 실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;&lt;b&gt;   Publisher / Subscriber&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Publisher/Subscriber은 메시지를 특정 수신자에게 직접 발송하지 못하는 곳에서 사용되는 모델이다. 메시지를 발송하고 채널에서 구독하는 명령을 내린다. Redis 모듈도 하나의 클라이언트가 메시지를 발생시켜 연결된 다수의 클라이언트에게 메시지를 전달할 수 있는 구조인 Publisher/Subscriber 모델을 제공한다. 메시지를 던지는 것이기 때문에 메시지를 보관하지는 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697946212007&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// redis.js

var redis = require('redis');
var subscriber = redis.createClient();
var publisher = redis.createClient();
var msg_count = 0;

     // subscriber 객체가 구독을 시작할 때 발생하는 콜백 함수
     subscriber.on('subscribe', function (channel, count) {
         // 구독 시작 후 publisher 객체가 발행 하도록 함. (일치하는 채널만)
         publisher.publish('Goorm Channel', '발행자 첫번째 메시지');
         publisher.publish('Goorm Channel', '발행자 두번째 메시지');
         publisher.publish('Goorm Channel', '발행자 마지막 메시지');
     });

     // subscriber 객체가 메시지를 받으면 호출되는 함수
     subscriber.on('message', function (channel, message) {
         console.log('채널명: ' + channel + ', 메시지: ' + message);
         msg_count += 1;

         // 메시지를 3번 보냈을 시 subscriber 구독 종료, 구독/발행자 종료
         if (msg_count == 3) {
            subscriber.unsubscribe();
            subscriber.end();
            publisher.end();
         }
     });

     // Goorm Channel의 구독을 시작
     subscriber.subscribe('Goorm Channel');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;b&gt;   Redis vs MongoDB&lt;/b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1697961764781&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;redis vs mongoDB&quot; data-og-description=&quot;Redis는 Key-Value 형태의 데이터 구조를 가지는 인메모리 데이터 저장소입니다. 반면, MongoDB는 Document 형태의 데이터 구조를 가지며, 디스크 기반으로 데이터를 저장합니다.&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@moonblue/redis-vs-mongoDB&quot; data-og-url=&quot;https://velog.io/@moonblue/redis-vs-mongoDB&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mYKYj/hyUklOaH1X/O9zNRrK6MOmNuPTB05VkI0/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/bu7mZv/hyUklADo09/poMaVry7CkLaFcfFpQwyHK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/HBVzo/hyUkjphMhr/vAfnezN0lbqyI4YmhWMyTK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://velog.io/@moonblue/redis-vs-mongoDB&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@moonblue/redis-vs-mongoDB&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mYKYj/hyUklOaH1X/O9zNRrK6MOmNuPTB05VkI0/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/bu7mZv/hyUklADo09/poMaVry7CkLaFcfFpQwyHK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/HBVzo/hyUkjphMhr/vAfnezN0lbqyI4YmhWMyTK/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;redis vs mongoDB&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Redis는 Key-Value 형태의 데이터 구조를 가지는 인메모리 데이터 저장소입니다. 반면, MongoDB는 Document 형태의 데이터 구조를 가지며, 디스크 기반으로 데이터를 저장합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT</category>
      <category>NoSQL</category>
      <category>Redis</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/14</guid>
      <comments>https://clohoon.tistory.com/14#entry14comment</comments>
      <pubDate>Sun, 22 Oct 2023 17:08:51 +0900</pubDate>
    </item>
    <item>
      <title>mongoose에 대해 알아보자!</title>
      <link>https://clohoon.tistory.com/13</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚡&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;mongoDB&lt;/span&gt;란?&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tgqiS/btsyn1GrIcy/kE2WpYIiOfkqb06xAB2Njk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tgqiS/btsyn1GrIcy/kE2WpYIiOfkqb06xAB2Njk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tgqiS/btsyn1GrIcy/kE2WpYIiOfkqb06xAB2Njk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtgqiS%2Fbtsyn1GrIcy%2FkE2WpYIiOfkqb06xAB2Njk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;447&quot; height=&quot;113&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;mongoose는 mongoDB라는 NoSQL 데이터베이스를 지원하는 노드의 확장모듈이다. mongoose는 mongoDB의 ODM(Object Data Mapping)으로, 문서를 DB에서 조회할 때 자바스크립트 객체로 바꾸어준다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;NoSQL은 관계형 데이터 모델과 SQL을 사용하지 않는 모든 데이터베이스 시스템이다. 무한에 가까운 확장성을 제공하는데, 이를 위해 NoSQL 데이터베이스는 단순한 키와 값의 쌍으로 이루어져 있다. 인덱스와 데이터는 분리되어 운영되며 고정된 스키마도 없다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;mongoDB는 문서지향 저장소를 제공하는 NoSQL 데이터베이스 시스템이다. 객체지향 프로그래밍과 잘 맞고 JSON을 사용할 때 유용하기 때문에 Node.js에서 가장 많이 사용하는 데이터베이스이다. 문서지향 데이터베이스는 행 개념보다 유연한 모델인 문서를 이용해 복잡한 객체의 계층 관계를 하나의 레코드로 표현한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚡&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt; mongoose&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;mongoose는 데이터를 만들고 관리하기 위해 스키마를 만들고, 그 스키마로 모델을 만든다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스 연결&lt;/p&gt;
&lt;pre id=&quot;code_1697349941203&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var mongoose = require('mongoose');
var connection1 = mongoose.createConnection('mongodb://localhost/mydb1');
var connection2 = mongoose.createConnection('mongodb://localhost/mydb2');&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;스키마 정의하기&lt;/p&gt;
&lt;pre id=&quot;code_1697349832950&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var Schema = mongoose.Schema, ObjectId = Schema.ObjectId;
var ArticleSchema = new Schema({
    author: ObjectId,
    title: String,
    body: String,
    date: Date
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모델 정의하기&lt;/p&gt;
&lt;pre id=&quot;code_1697349878290&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var ArticleModel = mongoose.model('Article', ArticleScheme);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모델 사용하기&lt;/p&gt;
&lt;pre id=&quot;code_1697350311020&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var instance = new ArticleModel();
instance.title = 'hello';
instance.save(function (err){
            // save 실행 후 콜백 함수의 내용
});
instance.find({}, function(err, docs){
            // find 실행 후 콜백 함수의 내용
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;검색하기&lt;/p&gt;
&lt;pre id=&quot;code_1697350590166&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Model.find(query, fields, options, callback)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 추가&lt;/p&gt;
&lt;pre id=&quot;code_1697812561626&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var article = new ArticleModel({title: &quot;Title&quot;, body: &quot;Contents&quot;});
article.date = new Date();
article.save(function (err) {
    if(err) {
        return handleError(err);
    }
            // save() 성공 후 수행할 내용
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도큐먼트 삭제&lt;/p&gt;
&lt;pre id=&quot;code_1697812554751&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ArticleModel.remove({title: &quot;Title&quot;}, function(err){
    if(err){
        return handleError(err);
    }
            // remove() 성공후 수행할 내용
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;remove 메소드보다 더 세분화된 deleteOne, deleteMany 메소드를 사용하는 것을 권장한다.&lt;/p&gt;</description>
      <category>IT</category>
      <category>MongoDB</category>
      <category>MONGOOSE</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/13</guid>
      <comments>https://clohoon.tistory.com/13#entry13comment</comments>
      <pubDate>Fri, 20 Oct 2023 23:47:56 +0900</pubDate>
    </item>
    <item>
      <title>socket.io를 이용한 빙고 게임 구현</title>
      <link>https://clohoon.tistory.com/12</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;채팅 앱에 이어 빙고 게임도 socket.io를 이용해 구현해보자!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;userlmn_213ee7ccbc16c8756176b6ff0d4e6d5b.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;659&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbERIz/btsyH2QOtjH/ZfxzsFNbtmOrQTkOEnOKM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbERIz/btsyH2QOtjH/ZfxzsFNbtmOrQTkOEnOKM0/img.png&quot; data-alt=&quot;완성된 빙고 게임&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbERIz/btsyH2QOtjH/ZfxzsFNbtmOrQTkOEnOKM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbERIz%2FbtsyH2QOtjH%2FZfxzsFNbtmOrQTkOEnOKM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;390&quot; data-filename=&quot;userlmn_213ee7ccbc16c8756176b6ff0d4e6d5b.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;659&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완성된 빙고 게임&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 빙고 게임 기능&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. 유저 이름 입력받기&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. 접속 중인 유저 이름 출력&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3. 한 명이 게임을 시작하려 하면 막기, 알림 출력 (두 명 이상)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;4. 게임 진행을 위해 턴 넘기기&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;5. 자기 턴이 아닐 경우 진행 불가능, 알림 출력&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;6. 숫자가 선택되면 상대방에게 알림 출력&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;7. 선택된 숫자에 글자 효과 주기&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;8. 게임이 진행 중일 때 상대방이 떠난 경우, 게임을 종료시키고 알림 출력하기&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;server.js&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697549166653&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var express = require('express');
var app = express();
var http = require('http').Server(app); 
var io = require('socket.io')(http);    
var path = require('path');


// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(express.static(path.join(__dirname, 'public')));


app.get('/', (req, res) =&amp;gt; {  
	res.render('main', { title: '온라인 빙고 게임', username: req.query.username });
});

var users = {};
var user_count = 0;
var turn_count = 0;


io.on('connection', function(socket){ 
	
	console.log('user connected : ', socket.id);
	
	socket.on('join', function (data) {
		var username = data.username;
		socket.username = username;
		
		users[user_count] = {};
		users[user_count].id = socket.id;
		users[user_count].name = username;
		users[user_count].turn = false;
		user_count++;
		
		io.emit('update_users', users, user_count);
	});
	
	socket.on('game_start', function (data) {
		socket.broadcast.emit(&quot;game_started&quot;, data);
		users[turn_count].turn = true;
		
		io.emit('update_users', users);
	});
	
	socket.on('select', function (data) {
		socket.broadcast.emit(&quot;check_number&quot;, data);
		
		users[turn_count].turn = false;
		turn_count++;
		
		if(turn_count &amp;gt;= user_count) {
			turn_count = 0;
		}
		users[turn_count].turn = true;
		
		io.sockets.emit('update_users', users);
	});
	
	socket.on('disconnect', function() {
		console.log('user disconnected : ', socket.id, socket.username);
		for(var i=0; i&amp;lt;user_count; i++){
			if(users[i].id == socket.id)
				delete users[i];
		}	
		
		user_count--;
		io.emit('update_users', users, user_count);
		io.emit('user_gone');
	});
});

http.listen(3000, function(){ 
	console.log('server on!');
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;main.js&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697549209442&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var bingo = {
	is_my_turn: Boolean,
	socket: null,
		
	init: function(socket){
		var self = this;
		var user_cnt = 0;
		
		this.is_my_turn = false;
		
		socket = io();

		socket.on(&quot;check_number&quot;, function (data) {
			self.where_is_it(data.num);
			self.print_msg(data.username + &quot;님이 '&quot; + data.num + &quot;'을 선택했습니다.&quot;);
		});
		
		socket.on(&quot;game_started&quot;, function(data){
			console.log(&quot;enter the game_started&quot;);
			self.print_msg(data.username + &quot; 님이 게임을 시작했습니다.&quot;);
			$(&quot;#start_button&quot;).hide();
		});
		
		socket.on(&quot;update_users&quot;, function (data, user_count) {
			console.log(data);
			user_cnt = user_count;
			self.update_userlist(data, socket);
		});
		
		socket.on(&quot;user_gone&quot;, function(){
			self.print_msg(&quot;참가자 한 명이 게임을 종료했습니다.&quot;);
		});
		
		//join
		
		$(&quot;#un&quot;).click(function (){
			$(&quot;#un&quot;).hide();
			socket.emit(&quot;join&quot;, { username: $('#username').val() });
			$(&quot;#un&quot;).hide();
		});
		
		var numbers = [];
		for(var i=1; i&amp;lt;=25; i++){
			numbers.push(i);
		}
		
		numbers.sort(function (a,b) {
			var temp = parseInt(Math.random() * 10);
			var isOddOrEven = temp%2;
			var isPosOrNeg = temp &amp;gt; 5 ? 1 : -1;
			return (isOddOrEven*isPosOrNeg);
		});
		
		$(&quot;table.bingo-board td&quot;).each(function (i) {
			$(this).html(numbers[i]);
			
			$(this).click(function (){
				if(user_cnt == 1){
					self.print_msg(&quot;&amp;lt;알림&amp;gt; 최소 2명부터 게임이 가능합니다.&quot;);
				}
				else{
					self.select_num(this, socket);
				}
			});
		});
		
		$(&quot;#start_button&quot;).click(function () {
			if(user_cnt == 1){
			   self.print_msg(&quot;&amp;lt;알림&amp;gt; 최소 2명부터 게임이 가능합니다.&quot;);
			}
			else{
				socket.emit('game_start', { username: $('#username').val() });
				self.print_msg(&quot;&amp;lt;알림&amp;gt; 게임을 시작했습니다.&quot;);
				$(&quot;#start_button&quot;).hide();
			}
		});
		
	},
	
	// init 끝
	select_num: function (obj, socket) {
		if(this.is_my_turn &amp;amp;&amp;amp; !$(obj).attr(&quot;checked&quot;)) {
			//send num to other players
			socket.emit(&quot;select&quot;, { username: $('#username').val(), num: $(obj).text() });		
			this.check_num(obj);
			
			this.is_my_turn = false;
		}
		else {
			this.print_msg(&quot;&amp;lt;알림&amp;gt; 차례가 아닙니다!&quot;);
		}
	},
	
	where_is_it: function (num) {
		var self = this;
		var obj = null;
		
		$(&quot;table.bingo-board td&quot;).each(function (i) {
			if ($(this).text() == num) {
				self.check_num(this);
			}
		});
	},
	
	check_num: function (obj) {
		$(obj).css(&quot;text-decoration&quot;, &quot;line-through&quot;);
		$(obj).css(&quot;color&quot;, &quot;lightgray&quot;);
		$(obj).attr(&quot;checked&quot;, true);
	},
	
	update_userlist: function (data, this_socket) {
		var self = this;
		$(&quot;#list&quot;).empty();
		console.log(data);
		
		$.each(data, function (key, value) {
			var turn = &quot;(-) &quot;;
			if(value.turn === true) {
				turn = &quot;(*) &quot;;
				
				if(value.id == this_socket.id ) {
					self.is_my_turn = true;
				}
			}
			if(value.id == this_socket.id){
				$(&quot;#list&quot;).append(&quot;&amp;lt;font color='DodgerBlue'&amp;gt;&quot; + turn + value.name + &quot;&amp;lt;br&amp;gt;&amp;lt;/font&amp;gt;&quot;);
			}
			else{
				$(&quot;#list&quot;).append(&quot;&amp;lt;font color='black'&amp;gt;&quot; + turn + value.name + &quot;&amp;lt;br&amp;gt;&amp;lt;/font&amp;gt;&quot;);
			}
		});
	},
	
	
	print_msg: function (msg) {
		$(&quot;#logs&quot;).append(msg + &quot;&amp;lt;br /&amp;gt;&quot;);
		$('#logs').scrollTop($('#logs')[0].scrollHeight);
	}
};

$(document).ready(function () {
	bingo.init();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;main.pug&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697549257018&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extends layout

block content
 h1= title
 p 
  font(color='DodgerBlue') #{username}
  input#username(type= 'text', value= username)
  input#un(type='button' value='참가')
  | 님 환영합니다.
 
 #game
  #bingo
   table.bingo-board
    tr
     td
     td
     td
     td
     td
    tr
     td
     td
     td
     td
     td
    tr
     td
     td
     td
     td
     td
    tr
     td
     td
     td
     td
     td
    tr
     td
     td
     td
     td
     td

   #user
    #list
    #logs
    input#start_button(type='button', value='Game Start!')&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;layout.pug&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697549278817&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;doctype html
html
	head
		title= title
		link(rel='stylesheet', href='/stylesheets/style.css')
		script(src='/socket.io/socket.io.js')
		script(src='//code.jquery.com/jquery.min.js')
		script(src='/javascripts/main.js')
	body
		center
			block content&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;style.css&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1697549331130&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;body {
  padding: 50px;
  font: 14px &quot;Lucida Grande&quot;, Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

#game {
	width: 320px;
	height: 300px;
}

#bingo {
	float: center;
}

#user {
	float: center;
}

#list {
	border: 1px solid gray;
	width: 300px;
	height: 100px;
	overflow: auto;
	margin-bottom: 10px;
}

#logs {
	border: 1px solid gray;
	width: 300px;
	height: 100px;
	overflow: auto;
	margin-bottom: 10px;
}

.bingo-board {
	border-left: 1px solid gray;
	border-top: 1px solid gray;
	border-spacing: 0px;
	margin-bottom: 20px;
	text-align: center;
}

.bingo-board td {
	width: 35px;
	height: 35px;
	border-right: 1px solid gray;
	border-bottom: 1px solid gray;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>IT</category>
      <category>socket.io</category>
      <category>빙고</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/12</guid>
      <comments>https://clohoon.tistory.com/12#entry12comment</comments>
      <pubDate>Tue, 17 Oct 2023 23:22:37 +0900</pubDate>
    </item>
    <item>
      <title>socket.io를 이용한 채팅 앱 구현</title>
      <link>https://clohoon.tistory.com/11</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;socket.io를 활용해 실시간 웹 통신을 구현해보자!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFvwAe/btsxtmwi9CH/BIMMYhaQfolM3XvmtB7OX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFvwAe/btsxtmwi9CH/BIMMYhaQfolM3XvmtB7OX1/img.png&quot; data-alt=&quot;완성된 채팅 앱&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFvwAe/btsxtmwi9CH/BIMMYhaQfolM3XvmtB7OX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFvwAe%2Fbtsxtmwi9CH%2FBIMMYhaQfolM3XvmtB7OX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;579&quot; height=&quot;530&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완성된 채팅 앱&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;클라이언트 / 서버 통신&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이언트 / 서버 통신은 서버에 있는 풍부한 자원들과 서비스를 통합된 방식으로 제공받기 위한 통신이다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;통신 프로토콜은 통신 서비스 또는 기능 수행을 위해 관련 통신 당사자 간 교환하는 정보의 종류와 표현 형식, 교환 절차, 그리고 교환 과정에서 실행해야 할 행위에 대한 규약이다. 대표적인 예로 IBM의 폐쇄형 망 구조인 SNA, 개방형 망 구조인 TCP/IP가 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7BjgD/btsxtmv47w6/5cn5PylXadIwLPhFW10bM0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7BjgD/btsxtmv47w6/5cn5PylXadIwLPhFW10bM0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7BjgD/btsxtmv47w6/5cn5PylXadIwLPhFW10bM0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/7BjgD/btsxtmv47w6/5cn5PylXadIwLPhFW10bM0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;158&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Polling 방식은 클라이언트가 서버에 주기적으로 요청 후 응답을 받는 방식이다. 구현이 간단하지만 쓸모없는 요청/응답 때문에 많은 트래픽이 낭비되며, 다음 폴링이 이루어지기 전까지 어떤 이벤트가 왔는지 모르기 떄문에 실시간성이 보장되지 않는다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b03wg2/btsxieTYICO/rOK9YkMrmwEzwKxPbNhB20/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b03wg2/btsxieTYICO/rOK9YkMrmwEzwKxPbNhB20/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b03wg2/btsxieTYICO/rOK9YkMrmwEzwKxPbNhB20/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b03wg2/btsxieTYICO/rOK9YkMrmwEzwKxPbNhB20/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;158&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;Long Poll 방식은 클라이언트가 서버에 대한 요청을 유지하여 반복적인 요청을 없애고 유효한 이벤트가 발생하면 응답을 해주는 방식이다. 즉 접속을 오래 유지하는 것인데, 일정 시간을 초과하면 접속을 완료하고 새로 요청한다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Polling, Long Poll, Stream 등의 방법은 브라우저가 HTTP 요청을 보내고 서버가 이에 대한 HTTP 응답을 보내는 단방향의 메시지 교환 방식을 유지한다. 서버와 클라이언트가 실시간으로 상호작용하는 웹 서비스를 개발하기 위해 일종의 트릭을 사용한 셈이다. 이로 인해 발생하는 불편함을 해결하기 위해 HTML5 표준 기술인 WebSocket이 등장했다. WebSocket은 사용자의 브라우저와 서버 사이의 동적인 양방향 연결 채널을 구성한다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;Socket.io&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;오래된 버전의 웹 브라우저는 웹소켓을 지원하지 않는다. 따라서 브라우저에 상관없이 실시간 웹을 구현할 수 있는 Socket.io가 많이 사용된다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Socket.io는 node.js 기반으로 만들어진 기술로, 거의 모든 웹 브라우저와 모바일 장치를 지원하는 실시간 웹 애플리케이션 지원 라이브러리이다. 만약 브라우저에 FlashSocket이라는 기술을 지원하는 플러그인이 설치되어 있으면 그것을 사용하고 없으면 AJAX Long Polling 방식을 사용한다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;채팅 앱 구현&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;구현할 채팅 프로그램의 기능은 다음과 같다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. 유저가 접속하면 알림 출력&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. 유저가 떠나면 알림 출력&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. 닉네임 변경했을 때 알림 출력&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4. 실시간으로 접속 중인 유저 이름 보여주기&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;프로젝트 폴더 이름은 socket-test이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;유저들 간 닉네임 중복 ❌&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;유저 닉네임이 중복되면 다양한 문제가 발생한다. 예를 들면, 한 유저가 채팅방을 나갔을 때 같은 닉네임의 다른 유저들도 (접속자 버튼을 눌렀을 때) 채팅방을 나간 것처럼 보여진다. 이 문제를 해결하고 싶다면 (다음 게시물에 올라올) 빙고 게임처럼 users를 객체로 바꾸어 각 유저별로 socket.id와 name을 함께 보관하면 된다. 그리고 한 유저가 나가거나 닉네임을 변경하면 해당 socket.id를 가진 객체만 삭제하거나 수정한다. 해당 게시물에서는 이 기능을 구현하지 않겠다.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;npm init&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;npm install express-generator -g&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;express socket-test&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;cd socket-test &amp;amp;&amp;amp; npm install&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;npm install socket.io&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;npm install pug&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;server.js&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;var express = require('express');
var app = express();
var http = require('http').Server(app); 
var io = require('socket.io')(http);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
var path = require('path');

app.set('views', './views');
app.set('view engine', 'pug');
app.use(express.static(path.join(__dirname, 'public')));

app.get('/', (req, res) =&amp;gt; {&amp;nbsp;&amp;nbsp;
	res.render('chat');
});

var count=1;
var users=[];
io.on('connection', function(socket){
	console.log('user connected: ', socket.id);
	var name = &quot;익명&quot; + count++;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 
	socket.name = name;
	io.to(socket.id).emit('create name', name);&amp;nbsp;&amp;nbsp; 
	io.emit('new_connect', name);
	users.push(name);
	
	socket.on('disconnect', function(){ 
		users = users.filter((value, index, arr) =&amp;gt; {
			return value != socket.name;
		});
		console.log('user disconnected: '+ socket.id + ' ' + socket.name);
		io.emit('new_disconnect', socket.name);
	});

	socket.on('send message', function(name, text){ 
		var msg = name + ' : ' + text;
		if(name != socket.name)
			io.emit('change name', socket.name, name);
			users = users.filter((value, index, arr) =&amp;gt; {
				return value != socket.name;
			});
			users.push(name);
		socket.name = name;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	console.log(msg);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	io.emit('receive message', msg);
	});
	
	socket.on('check users', function(){
		io.emit('print users', users);
	});
	
});

http.listen(3000, function(){ 
	console.log('server on..');
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;server.js에 서버 코드를 모두 작성하므로 npm start가 아닌 node server로 서버를 구동시킨다.&lt;br /&gt;socket.name은 닉네임이 변경될 때 비교해주기 위한 값이다.&lt;br /&gt;io.to(socket.id).emit은 서버가 해당 socket.id에만 이벤트를 전달한다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;chat.pug&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;doctype 5
html
&amp;nbsp;&amp;nbsp;head
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title= 'Chat'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;link(rel='stylesheet', href='/stylesheets/style.css')
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;link(rel=&quot;stylesheet&quot;, href=&quot;https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css&quot;, integrity=&quot;sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO&quot; crossorigin=&quot;anonymous&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;script(src=&quot;https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js&quot;, integrity=&quot;sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy&quot; crossorigin=&quot;anonymous&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;script(src='/socket.io/socket.io.js')
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;script(src='//code.jquery.com/jquery-1.11.1.js')
&amp;nbsp;&amp;nbsp;body
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;center
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;div
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;button.btn.btn-info(type='button') clohoon 채팅방
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;div
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;textarea#chatLog.form-control(readonly='')
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;form#chat
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;input#name(type='text')
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;input#message(type='text')
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;button.btn.btn-primary(type='submit') 전송
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;button#connected-users(type='button') 접속자
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#box.box
	&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;script.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var socket = io(); 
	&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#chat').on('submit', function(e){ 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;socket.emit('send message', $('#name').val(), $('#message').val());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#message').val('');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#message').focus();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;e.preventDefault();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
	&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#connected-users').on('click', function(){ 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;socket.emit('check users');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
	&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;socket.on('create name', function(name){ 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#name').val(name);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
	&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;socket.on('change name', function(oldname, name){ 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#chatLog').append('&amp;lt;알림&amp;gt; ' + oldname + '님이 ' + name +'님으로 닉네임을 변경했습니다.\n');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
	&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;socket.on('receive message', function(msg){ 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#chatLog').append(msg+'\n');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#chatLog').scrollTop($('#chatLog')[0].scrollHeight);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
	&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;socket.on('new_disconnect', function(name){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#chatLog').append('&amp;lt;알림&amp;gt; ' + name + '님이 채팅창을 떠났습니다.\n');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
	&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;socket.on('new_connect', function(name){
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#chatLog').append('&amp;lt;알림&amp;gt; ' + name + '님이 채팅창에 접속했습니다.\n');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;socket.on('print users', function(users){ 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$('#chatLog').append('&amp;lt;알림&amp;gt; ' + users + '이 접속 중입니다.\n');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;style.css&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;#chatLog{
	width: 50%; 
	height: 700px;
}
#name{ 
	width: 10%; 
}
#message{ 
	width: 35%; 
}
#connected-users{
	border: 1px solid skyblue;
	background-color: rgba(0,0,0,0);
	color: skyblue;
	border-radius: 5px;
}
.btn.btn-info{
	width: 50%;
	height: 45px;
	font-size: 20px;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>IT</category>
      <category>Ajax</category>
      <category>socket.io</category>
      <category>Websocket</category>
      <category>채팅앱</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/11</guid>
      <comments>https://clohoon.tistory.com/11#entry11comment</comments>
      <pubDate>Mon, 9 Oct 2023 09:26:07 +0900</pubDate>
    </item>
    <item>
      <title>Node.js에 대해 알아보자!</title>
      <link>https://clohoon.tistory.com/10</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyi9Kt/btsv8cvVBlZ/ydrvPChgnJkehZpTE3g2y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyi9Kt/btsv8cvVBlZ/ydrvPChgnJkehZpTE3g2y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyi9Kt/btsv8cvVBlZ/ydrvPChgnJkehZpTE3g2y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcyi9Kt%2Fbtsv8cvVBlZ%2FydrvPChgnJkehZpTE3g2y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;414&quot; height=&quot;207&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚡&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;span&gt; Node.js&lt;/span&gt;란?&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Node.js란 서버사이드 자바스크립트이며 구글의 자바스크립트 엔진인 V8을 기반으로 구성된 일종의 소프트웨어 시스템이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zvnVd/btswbchyIeH/fM4V4Chnq8Hnvb4aTotWuK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zvnVd/btswbchyIeH/fM4V4Chnq8Hnvb4aTotWuK/img.gif&quot; data-alt=&quot;Blocking I/O&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zvnVd/btswbchyIeH/fM4V4Chnq8Hnvb4aTotWuK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/zvnVd/btswbchyIeH/fM4V4Chnq8Hnvb4aTotWuK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;267&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Blocking I/O&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 대부분의 애플리케이션은 Blocking I/O를 사용했기 때문에 멀티 쓰레드를 사용할 수밖에 없었다. 이 방식은 다음 두 가지 문제점이 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;1. I/O 요청을 하고 응답이 올 때까지 시간을 낭비한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;2. 스케줄링을 위한 처리 시간과 문맥 전환 비용이 발생한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;노드는 이러한 문제들을 &lt;u&gt;싱글 쓰레드&lt;/u&gt;와 &lt;u&gt;이벤트 기반의 비동기 I/O 처리&lt;/u&gt;로 해결한다. 싱글 쓰레드를 가진 노드는 I/O 작업이 시작되면 응답을 기다리지 않고 바로 다음 작업을 실행한다. I/O 작업이 종료되면 이벤트가 발생하고 노드로 개발된 프로세스가 이벤트 큐에 등록된 새로운 이벤트를 감지하여 해당 이벤트 발생 시 수행해야 될 작업을 실행하게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚡&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;이벤트 루프&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이벤트 루프는 작업을 요청하면서 그 작업이 완료되었을 때 어떤 작업을 진행할지에 대해 콜백 함수를 지정하여 동작이 완료되었을 때 해당 콜백 함수를 실행하는 동작 방식이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLndYa/btswcI8kRkN/hW75KCezt0iKScgF1q08OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLndYa/btswcI8kRkN/hW75KCezt0iKScgF1q08OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLndYa/btswcI8kRkN/hW75KCezt0iKScgF1q08OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLndYa%2FbtswcI8kRkN%2FhW75KCezt0iKScgF1q08OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;491&quot; height=&quot;275&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 서버에 HTTP 요청을 보내면 서버에서 계속 돌고 있던 이벤트 루프가 이를 감지하고 알맞은 작업을 워커 쓰레드를 생성하여 실행한다. 그리고 이벤트 루프는 다시 루프로 복귀하여 다른 요청을 기다리게 된다. 작업을 할당받은 워커 쓰레드가 작업을 마치면 미리 전달받은 콜백 함수를 실행하도록 이벤트 루프로 응답하게 되며 이벤트 루프를 이를 실행해 클라이언트에게 결과를 응답한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚡ 모듈&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;노드는 확장성을 위해 모듈 구조를 통해 애플리케이션을 구성한다. 모듈은 애플리케이션을 이루는 기본 단위로, 보통 다수의 클래스와 이를 통해 생성된 객체들로 구성된다. 기본적으로 노드의 모듈은 자바스크립트 파일 하나와 1대 1로 맵핑된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모듈 개념에서 require() 메소드와 module.exports는 중요하다. 노드는 HTML을 사용하지 않으므로 서로 다른 자바스크립트 파일들이 서로 참조하고 호출하는 방법이 필요하다. 이때 require() 메소드는 모듈 식별자인 module.exports를 이용해 모듈이 제공하는 함수나 객체 등을 반환한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;OS 모듈 : 운영체제와 시스템의 정보를 가져올 수 있는 모듈. CPU나 메모리, 디스크 용량이 얼마나 남았는지 확인할 때 사용한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Utility 모듈 : node.js의 보조적인 기능 중 유용한 기능만을 모아놓은 모듈. util.inherits() 메소드를 이용하면 쉽게 상속할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;File System 모듈 : 파일 처리와 관련된 작업을 하는 모듈&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Event 모듈 : 이벤트 모듈과 EventEmitter 클래스를 사용하여 이벤트와 이벤트 핸들러를 연동시킬 수 있다. 이벤트 모듈을 사용하려면 require() 메소드를 이용해 로드하고, 그 객체를 통해 EventEmitter 클래스를 로드하여 사용하는 것이 일반적이다.&lt;/p&gt;</description>
      <category>IT</category>
      <category>node.js</category>
      <category>모듈</category>
      <category>이벤트 루프</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/10</guid>
      <comments>https://clohoon.tistory.com/10#entry10comment</comments>
      <pubDate>Sat, 30 Sep 2023 22:51:25 +0900</pubDate>
    </item>
    <item>
      <title>객체지향 자바스크립트</title>
      <link>https://clohoon.tistory.com/9</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf9QK5/btswasEDODa/Fh0Y4igMTvO4WK1KfbQEQ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf9QK5/btswasEDODa/Fh0Y4igMTvO4WK1KfbQEQ1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf9QK5/btswasEDODa/Fh0Y4igMTvO4WK1KfbQEQ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf9QK5%2FbtswasEDODa%2FFh0Y4igMTvO4WK1KfbQEQ1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;542&quot; height=&quot;285&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;&lt;b&gt;프로토타입 패턴&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;자바스크립트는 Java나 C++ 같은 클래스 기반의 객체지향 언어와는 달리 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;프로토타입 기반의 객체지향 언어&lt;/span&gt;이다. 때문에 자바스크립트에서 클래스 개념은 존재하지 않는다. 많은 개발자들이 이로 인해 자바스크립트를 처음 접할 때 어려움을 겪자 ES6부터 클래스 문법이 새로 추가되었다. 다만 이는 클래스를 흉내내 구현한 프로토타입 기반의 함수이지, 자바스크립트가 클래스 기반의 언어가 되었다는 의미는 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt; &lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;프로토타입 패턴이란 객체를 생성할 때 원본 객체를 복제하여 생성하는 방법이다. &lt;/span&gt;자바스크립트는 객체를 생성할 때 프로토타입 패턴을 사용한다. 객체가 함수를 사용해서 만들어지면 객체는 함수의 프로토타입 객체를 복제하여 생성된다. (자바스크립트는 함수가 생성될 때 자동으로 그 함수의 프로토타입 객체를 생성하고 해당 함수의 prototype 프로퍼티에 연결해둔다!) 즉 new User() 문법으로 새 객체를 생성하면 User 함수 자체가 아닌 User 함수의 프로토타입 객체를&lt;/span&gt; 복제해 새 객체를 만든다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;&lt;b&gt;스코프&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;스코프는 유효 범위로, 작성된 코드를 둘러싼 환경을 의미한다. 스코프는 전역 스코프와 지역 스코프로 정의할 수 있다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;전역 스코프는 함수 안에 포함되지 않은 곳에 정의하는 것으로 코드 어디에서든지 참조할 수 있고, 지역 스코프는 함수 내에 정의된 것으로 정의된 함수 내에서만 참조할 수 있다. &lt;/span&gt;const와 let은 블록 레벨 스코프, var과 같은 전통적인 자바스크립트의 변수는 함수 레벨 스코프이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;b&gt;호이스팅&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: justify;&quot;&gt;호이스팅은 함수 안에서 변수를 선언할 때 어떤 위치에 있든 함수의 시작 위치로 끌어올리는 현상이다. 아래의 두 코드는 같은 코드이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1695966783734&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function foo() {
	console.log(a);  // undefined
	var a = 100;
	console.log(a);  // 100
}

foo();&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1695966791934&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function foo() {
	var a;
	console.log(a);  // undefined
	var a = 100;
	console.log(a);  // 100
}

foo();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;자바스크립트는 호이스팅을 통해 a의 선언을 제일 위에서 해주기 때문에 에러 없이 undefined가 출력된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;&lt;b&gt;클로저&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: justify;&quot;&gt;클로저는 외부 함수의 실행이 끝나고 외부 함수가 소멸된 이후에도 내부 함수가 외부 함수의 변수에 접근할 수 있는 구조이다. 다음 코드를 보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1695970274631&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function f() {
    var a = [];
    var i;
	
    for(i = 0; i &amp;lt; 3; i++){
    	a[i] = function() {
    		return i;
    	}
    }
	
    return a;
}
  
var b = f();

console.log( b[0]() ); 
console.log( b[1]() ); 
console.log( b[2]() );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;코드를 실행하면 0 1 2가 출력될 것 같지만 실제로 실행해보면 3 3 3이 출력된다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;원인은 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;var b= f(); 문장에서 for문의 실행이 다 끝나고 나서야 실제 참조가 이루어지기 때문에 이미 3으로 증가한 i 값이 출력되는 것이다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: justify;&quot;&gt;(for문 실행 이후 a[0], a[1], a[2]에는 function(){ return i; }가 저장된다.) 즉 클로저는 그 순간의 값을 저장하는 것이 아니라 연결된 함수 범위에서 최종 처리된 값을 가지게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: justify;&quot;&gt;정상적으로 출력되는 아래 코드를 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1695973833896&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function f() {
    var a = [];
    var i;

    for(i = 0; i &amp;lt; 3; i++){
    	a[i] = (function(x) { 
        	return function() {
          		return x;
        	}
      	})(i);
    }
    return a;
}
  
var b = f();

console.log( b[0]() );
console.log( b[1]() );
console.log( b[2]() );&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: justify;&quot;&gt;function 내부의 변수인 i를 바로 리턴하지 않고, 파라미터를 받는 function을 정의한 다음에 파라미터로 내부 변수 i를 넘겨서 클로저가 내부 변수 i가 아니라 파라미터를 리턴하도록 한다. 클로저 함수에 의해 계속 참조되고 있기 때문에 파라미터 값인 0, 1, 2를 파기하지 않고 계속 보관하고 있는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클로저를 사용하면 함수를 호출할 때마다 기존에 생성했던 값을 유지할 수 있기 때문에 전역 변수의 잘못된 사용 없이 깔끔한 코드 작성을 할 수 있다. 그리고 외부에 해당 변수를 노출시키지 않아서 안정성을 보장해준다. 하지만 클로저로 참조하는 변수는 프로그램 종료 시까지 계속 메모리에 할당되어 있기 때문에 메모리 누수로 인해 성능 저하의 원인이 될 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;&lt;b&gt;참고한 자료&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://evan-moon.github.io/2019/10/23/js-prototype/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[JS 프로토타입] 자바스크립트의 프로토타입 훑어보기&lt;/a&gt;&lt;/p&gt;</description>
      <category>IT</category>
      <category>스코프</category>
      <category>클로저</category>
      <category>프로토타입패턴</category>
      <category>호이스팅</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/9</guid>
      <comments>https://clohoon.tistory.com/9#entry9comment</comments>
      <pubDate>Fri, 29 Sep 2023 20:11:15 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript 크롤링 : 뉴스</title>
      <link>https://clohoon.tistory.com/8</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;다음 뉴스를 크롤링해보자!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처.PNG&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;643&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5h8G5/btsuqztgDin/2rBqya790ISDgCRKzIPtSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5h8G5/btsuqztgDin/2rBqya790ISDgCRKzIPtSk/img.png&quot; data-alt=&quot;크롤링 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5h8G5/btsuqztgDin/2rBqya790ISDgCRKzIPtSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5h8G5%2FbtsuqztgDin%2F2rBqya790ISDgCRKzIPtSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;221&quot; data-filename=&quot;캡처.PNG&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;크롤링 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt; 시작하기 전에&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. 강의에서 사용하는 url이 존재하지 않아서 &lt;a href=&quot;https://entertain.daum.net/ranking/popular&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://entertain.daum.net/ranking/popular&lt;/a&gt; 로 대체했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;2. axios, cheerio 패키지를 설치한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;npm install axios cheerio&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3. 아래와 같은 오류가 발생하면 npm i axios@0.21 을 터미널에 입력하여 해결할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1695044298036&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import axios from './lib/axios.js';
       ^^^^^
       
SyntaxError: Unexpected identifier&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;npm이란?&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;npm은 대표적인 자바스크립트 패키지 매니저 중 하나로, 외부 라이브러리/패키지의 설치 및 삭제, 관리를 해 주는 도구이다. npm을 이용해 무언가를 설치했다면 package.json, package-lock.json, node_modules 폴더가 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;package.json 파일은 프로젝트 정보와 의존성을 관리해주는 문서이다.&lt;/li&gt;
&lt;li&gt;package-lock.json 파일은 node_modules 디렉터리나 package.json 파일이 변경될 때마다 자동으로 생성되고 업데이트되는 파일이다.&lt;/li&gt;
&lt;li&gt;node_modules 폴더는 npm으로 다운로드한 외부 라이브러리들이 담기는 폴더이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; HTTP Request Method&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;HTTP는 클라이언트와 서버 간 데이터를 주고 받을 때 사용하는 프로토콜이다. HTTP Request Method에는 대표적으로 GET 요청과 POST 요청이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;GET 요청은 클라이언트에서 서버로 어떠한 리소스로부터 정보를 요청하기 위해 사용되는 메서드이다.&lt;/li&gt;
&lt;li&gt;POST 요청은 클라이언트에서 서버로 리소스를 생성하거나 업데이트하기 위해 데이터를 보낼 때 사용되는 메서드이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span&gt;&lt;span&gt; 뉴스 크롤링 코드&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1695129706963&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 모듈 가져오기
const axios = require('axios');
const cheerio = require('cheerio');

function crawler(){
	const url = `https://entertain.daum.net/ranking/popular`;
	
	// axios로 get 요청 보내기
	axios.get(url)
	  .then(res =&amp;gt; {
		if(res.status == 200) {
			let crawledNews = [];
			// [ {title: &quot;....&quot;, img: &quot;....&quot;}, {}, {} ]
			
			// res.data에 있는 tag를 cheerio로 검색하여 변수에 담기
			const $ = cheerio.load(res.data);
			const $newsList = $('#mArticle &amp;gt; div.ranking_list &amp;gt; ol &amp;gt; li');
			
			$newsList.each(function(i){
				crawledNews[i] = {
					title : $(this).find('li &amp;gt; div &amp;gt; div &amp;gt; div &amp;gt; strong &amp;gt; a').text(),
					img : $(this).find('li &amp;gt; a &amp;gt; img').attr('src')
				};
			});
			console.log(crawledNews);
		}
	});
}

crawler();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT</category>
      <category>Get</category>
      <category>HTTP</category>
      <category>npm</category>
      <category>post</category>
      <category>크롤링</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/8</guid>
      <comments>https://clohoon.tistory.com/8#entry8comment</comments>
      <pubDate>Tue, 19 Sep 2023 23:44:36 +0900</pubDate>
    </item>
    <item>
      <title>웹 게임 제작 : 공 피하기</title>
      <link>https://clohoon.tistory.com/7</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;제이쿼리를 사용해 공을 피하는 웹 게임을 제작해 보자!&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W3ssy/btst6NyDH00/4JLVd3w12nZcstG5BAkou0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W3ssy/btst6NyDH00/4JLVd3w12nZcstG5BAkou0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1875&quot; data-origin-height=&quot;927&quot; data-filename=&quot;캡처.PNG&quot; style=&quot;width: 32.4945%; margin-right: 10px;&quot; data-widthpercent=&quot;33.27&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W3ssy/btst6NyDH00/4JLVd3w12nZcstG5BAkou0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW3ssy%2Fbtst6NyDH00%2F4JLVd3w12nZcstG5BAkou0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1875&quot; height=&quot;927&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mH0E1/btsudMZYEXW/k3XKdKT8FBEl06nrg5d7Wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mH0E1/btsudMZYEXW/k3XKdKT8FBEl06nrg5d7Wk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;924&quot; data-filename=&quot;캡처3.PNG&quot; style=&quot;width: 32.687%; margin-right: 10px;&quot; data-widthpercent=&quot;33.47&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mH0E1/btsudMZYEXW/k3XKdKT8FBEl06nrg5d7Wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmH0E1%2FbtsudMZYEXW%2Fk3XKdKT8FBEl06nrg5d7Wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1880&quot; height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFxoEm/btsugVaHeAO/58uKIDrOIcQghwdx7wECCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFxoEm/btsugVaHeAO/58uKIDrOIcQghwdx7wECCk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1883&quot; data-origin-height=&quot;931&quot; data-filename=&quot;캡처2.PNG&quot; style=&quot;width: 32.493%;&quot; data-widthpercent=&quot;33.26&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFxoEm/btsugVaHeAO/58uKIDrOIcQghwdx7wECCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFxoEm%2FbtsugVaHeAO%2F58uKIDrOIcQghwdx7wECCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1883&quot; height=&quot;931&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;공 피하기 게임&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt; &lt;span&gt; 게임 소개&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;마우스가 화면 밖으로 나가거나 공에 닿으면 게임이 끝난다. 게임 플레이 시간이 순위별로 기록된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;  &lt;/span&gt;&lt;/span&gt;JQuery란?&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #4d5156;&quot;&gt;JQuery&lt;/span&gt;&lt;span style=&quot;color: #4d5156;&quot;&gt;는 자바스크립트를 이용한 HTML 조작을 더 편하게 하기 위해 만들어진 자바스크립트 라이브러리이다. JQuery를 설치하려면 명령어 등을 통해 직접 JQuery 라이브러리를 다운로드하거나, CDN을 이용하여 웹 네트워크 상으로 JQuery 라이브러리를 받아오면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #4d5156;&quot;&gt;JQuery 문법은 대부분 $ 기호로 표현된다. 주로 아래와 같은 형식으로 사용한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$(Selector).JQueryAPI();&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt; &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;HTML 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694918469758&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
	&amp;lt;head&amp;gt;
		&amp;lt;title&amp;gt;공피하기 게임&amp;lt;/title&amp;gt;
		&amp;lt;script type = &quot;text/javascript&quot; src = &quot;https://code.jquery.com/jquery-latest.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
		&amp;lt;link rel = &quot;stylesheet&quot; type = &quot;text/css&quot; href = &quot;style.css&quot; /&amp;gt;
	&amp;lt;/head&amp;gt;
	&amp;lt;body&amp;gt;
		&amp;lt;div class = &quot;space&quot;&amp;gt;&amp;lt;/div&amp;gt;
		&amp;lt;div class = &quot;screen&quot;&amp;gt;
			&amp;lt;div class = &quot;startbutton&quot;&amp;gt;
				&amp;lt;h1&amp;gt;
					&amp;lt;div class = &quot;center&quot;&amp;gt;공피하기 게임&amp;lt;/div&amp;gt;
				&amp;lt;/h1&amp;gt;
				&amp;lt;h3&amp;gt;
					&amp;lt;div class = &quot;center&quot;&amp;gt;클릭하면 시작&amp;lt;/div&amp;gt;
				&amp;lt;/h3&amp;gt;
			&amp;lt;/div&amp;gt;
		&amp;lt;/div&amp;gt;
		&amp;lt;div class = &quot;timer&quot;&amp;gt;
			&amp;lt;h1&amp;gt;
				&amp;lt;div class = &quot;center&quot;&amp;gt;0.00&amp;lt;/div&amp;gt;
			&amp;lt;/h1&amp;gt;
		&amp;lt;/div&amp;gt;
		&amp;lt;div id = &quot;highscores&quot;&amp;gt;
			&amp;lt;h1&amp;gt;
				&amp;lt;div class = &quot;center&quot;&amp;gt;Game Over&amp;lt;/div&amp;gt;
			&amp;lt;/h1&amp;gt;
		&amp;lt;/div&amp;gt;
		&amp;lt;script type = &quot;text/javascript&quot; src = &quot;game.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
	&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt; &lt;span&gt;&lt;span&gt; CSS&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&amp;nbsp;코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694918535315&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;body {
	height : 100%;
	width : 100%;
}
.space {
	height : 100vh;
	width : 100vw;
	border : 1px black solid;
	margin : auto;
	position : relative;
	z-index : 1;
}
.screen {
	background-color : #facb96;
	height : 75vh;
	width : 75vw;
	border : 2px black solid;
	margin : auto;
	margin-top : -95vh;
	position : relative;
	z-index : 2;
}
.startbutton {
	padding : 2vh;
	background-color : #57db48;
	height : 50vh;
	width : 30vw;
	border : 2px black solid;
	border-radius : 10px;
	margin : auto;
	margin-top : 10vh;
	position : relative;
	z-index : 3;
}
.center {
	text-align : center;
}
h1 {
	z-index : 1;
	font-size : 5vmin;
}
h2 {
	z-index : 1;
	font-size : 3vmin;
}
.circle {
	position : absolute;
	z-index : 3;
	border : 2px black solid;
}
.redcircle {
	position : absolute;
	z-index : 3;
	border : 2px black solid;
}
#highscores {
	background-color : #e3dc14;
	height : 70vh;
	width : 30vw;
	margin : auto;
	margin-top : -80vh;
	position : relative;
	z-index : 9;
	display : none;
	border : 2px black solid;
}
.resetButton {
	padding : 2vh;
	background-color : #57db48;
	height : 10vh;
	width : 10vw;
	border : 2px black solid;
	border-radius : 10px;
	margin : auto;
	margin-top : 0px;
	position : relative;
	z-index : 10;
}
.timer {
	margin-top : 0px;
	position : relative;
	z-index : 10;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt; &lt;span&gt;&lt;span&gt;&lt;span&gt; Javascript&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&amp;nbsp;코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694918839386&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$(document).ready(function(){
	
	//공의 개수
	var circleNumber = 0;
	
	// 색 / 크기(지름) / 크기(반지름) / 속도
	var circleTypes = {
		//speed는 속도가 아니고, 한 지점에서 다른 지점으로 움직일 때 걸리는 ms
		&quot;option&quot; : [&quot;color&quot;, &quot;width&quot;, &quot;border-radius&quot;, &quot;speed&quot;],
		&quot;small&quot; : [&quot;black&quot;, 5, 2.5, 3000], 
		&quot;medium&quot; : [&quot;blue&quot;, 15, 7.5, 4000],
		&quot;large&quot; : [&quot;yellow&quot;, 30, 15, 5000]
	};
	
	// 시간을 찍어주는 변수
	var t = 0;
	
	// 게임 실행 여부
	var gameOn = false;
	
	// 마우스 좌표
	var mouseX;
	var mouseY;
	
	// 마우스 움직임을 감지해서 마우스 좌표를 좌표 변수에 담는 함수
	$(&quot;body&quot;).mousemove(function(){
		mouseX = event.pageX;
		mouseY = event.pageY;
	});
	
	// 타이머
	function timer() {
		if(gameOn == true) {
			setTimeout(function() {
				t = t + 0.01;
				$(&quot;.timer&quot;).html(`&amp;lt;h1&amp;gt;&amp;lt;div class = &quot;center&quot;&amp;gt;${t.toFixed(2)}&amp;lt;/div&amp;gt;&amp;lt;/h1&amp;gt;`);
				timer();
			}, 10)
		}
	}
	
	// 시작 기능
	$(&quot;.startbutton&quot;).click(function(){
    	// .fadeToggle():보이는 요소는 보이지 않게, 보이지 않는 요소는 보이게 하는 메소드
		$(&quot;.startbutton&quot;).fadeToggle(500, function() {
			gameOn = true;
			timer();
			$(&quot;.space&quot;).mouseenter(function(){
				// 게임을 끝내는 함수
				endgame();
			});
			createCircle();
		});
	});
	
	// createCircle 함수
	
	// animateCircle 함수
	
	// endgame 함수
    
	var resetButton = &quot;&amp;lt;div class='resetButton center'&amp;gt;&amp;lt;h2&amp;gt;Play Again&amp;lt;/h2&amp;gt;&amp;lt;/div&amp;gt;&quot;;
	var highScore1 = 0.00;
	var highScore2 = 0.00;
	var highScore3 = 0.00;
	var highScore4 = 0.00;
	var highScore5 = 0.00;
    
	// updateScores 함수
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;createCircle 함수&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694921381330&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function createCircle() {
	circleNumber++;

	// 1, 2, 3 중 하나
	var randomOneThree = Math.floor(3 * Math.random()) + 1;

	if(randomOneThree == 1) {
		var circleChoice = &quot;small&quot;;
	} else if(randomOneThree == 2) {
		var circleChoice = &quot;medium&quot;;
	} else {
		var circleChoice = &quot;large&quot;;
	}

	// 공의 id
	var circleName = &quot;circle&quot; + circleNumber;

	var circleColor = circleTypes[circleChoice][0];
	var circleSize = circleTypes[circleChoice][1];
	var circleRadius = circleTypes[circleChoice][2];
	var circleSpeed = circleTypes[circleChoice][3];

	// 공이 움직이는 범위
	var moveableWidth = $(&quot;body&quot;).width() - circleSize;
	var moveableHeight = $(&quot;body&quot;).height() - circleSize;

	// 공의 초기 시작 좌표
	var circlePositionLeft = (moveableWidth * Math.random()).toFixed();
	var circlePositionTop = (moveableHeight * Math.random()).toFixed();

	var newCircle = `&amp;lt;div class = 'circle' id = &quot;${circleName}&quot;&amp;gt;&amp;lt;/div&amp;gt;`;
	$(&quot;body&quot;).append(newCircle);

	$(&quot;#&quot;+circleName).css({
		&quot;background-color&quot; : circleColor,
		&quot;width&quot; : circleSize + &quot;vmin&quot;,
		&quot;height&quot; : circleSize + &quot;vmin&quot;,
		&quot;border-radius&quot; : circleRadius + &quot;vmin&quot;,
		&quot;top&quot; : circlePositionTop + &quot;px&quot;,
		&quot;left&quot; : circlePositionLeft + &quot;px&quot;
	});

	// 1ms마다 마우스와의 거리를 계산하는 함수
	function timeCirclePosition(circleTrackId){
		setTimeout(function(){
			var currentCirclePosition = $(circleTrackId).position();
			var calculateRadius = parseInt($(circleTrackId).css(&quot;width&quot;)) * 0.5;

			var distanceX = mouseX - (currentCirclePosition.left + calculateRadius);
			var distanceY = mouseY - (currentCirclePosition.top + calculateRadius);

			if(Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2)) &amp;lt;= calculateRadius){
				//부딪힌 공 빨간색으로 표시
				$(circleTrackId).removeClass('circle').addClass('redcircle');
				$(circleTrackId).css(&quot;background-color&quot;, &quot;red&quot;);

				console.log(&quot;게임 끝&quot;);
				endgame();
			};
			timeCirclePosition(circleTrackId);
		}, 1);
	}
	timeCirclePosition('#'+circleName);

	animateCircle(circleName, circleSpeed, circleSize);

	// 3초에 한 번 공 랜덤 생성
	setTimeout(function(){
		if(gameOn == true) {
			createCircle();
		}
	}, 3000);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;animateCircle 함수&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694921664004&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function animateCircle(circleId, speed, circleSize) {
	// animate() 위치를 이동시키는 함수
	var moveableWidth = $(&quot;body&quot;).width() - circleSize;
	var moveableHeight = $(&quot;body&quot;).height() - circleSize;
	var circleMoveLeft = (moveableWidth * Math.random()).toFixed();
	var circleMoveTop = (moveableHeight * Math.random()).toFixed();

	$(&quot;#&quot; + circleId).animate({
		left : circleMoveLeft,
		top : circleMoveTop
	}, speed, function(){
		animateCircle(circleId, speed, circleSize);
	});
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;endgame 함수&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694921690355&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function endgame(){
	if(gameOn == true) {
		gameOn = false;
		updateScores(t);
		$(&quot;.circle&quot;).remove();
		$(&quot;.redcircle&quot;).stop();
	};
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;updateScores 함수&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694921743327&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function updateScores(newScore){
	newScore += 0.01;

	if(newScore &amp;gt; highScore1){
		var redScore = &quot;score1&quot;;
		highScore5 = highScore4;
		highScore4 = highScore3;
		highScore3 = highScore2;
		highScore2 = highScore1;
		highScore1 = newScore;
	}

	else if(newScore &amp;gt; highScore2){
		var redScore = &quot;score2&quot;;
		highScore5 = highScore4;
		highScore4 = highScore3;
		highScore3 = highScore2;
		highScore2 = newScore;
	}

	else if(newScore &amp;gt; highScore3){
		var redScore = &quot;score3&quot;;
		highScore5 = highScore4;
		highScore4 = highScore3;
		highScore3 = newScore;
	}

	else if(newScore &amp;gt; highScore4){
		var redScore = &quot;score4&quot;;
		highScore5 = highScore4;
		highScore4 = newScore;
	}

	else if(newScore &amp;gt; highScore5){
		var redScore = &quot;score5&quot;;
		highScore5 = newScore;
	}

	var highScorePlace1 = &quot;&amp;lt;div class='score center' id='score1'&amp;gt;&amp;lt;h2&amp;gt;&quot; + highScore1.toFixed(2) + &quot;&amp;lt;/h2&amp;gt;&amp;lt;/div&amp;gt;&quot;;
	var highScorePlace2 = &quot;&amp;lt;div class='score center' id='score2'&amp;gt;&amp;lt;h2&amp;gt;&quot; + highScore2.toFixed(2) + &quot;&amp;lt;/h2&amp;gt;&amp;lt;/div&amp;gt;&quot;;
	var highScorePlace3 = &quot;&amp;lt;div class='score center' id='score3'&amp;gt;&amp;lt;h2&amp;gt;&quot; + highScore3.toFixed(2) + &quot;&amp;lt;/h2&amp;gt;&amp;lt;/div&amp;gt;&quot;;
	var highScorePlace4 = &quot;&amp;lt;div class='score center' id='score4'&amp;gt;&amp;lt;h2&amp;gt;&quot; + highScore4.toFixed(2) + &quot;&amp;lt;/h2&amp;gt;&amp;lt;/div&amp;gt;&quot;;
	var highScorePlace5 = &quot;&amp;lt;div class='score center' id='score5'&amp;gt;&amp;lt;h2&amp;gt;&quot; + highScore5.toFixed(2) + &quot;&amp;lt;/h2&amp;gt;&amp;lt;/div&amp;gt;&quot;;

	$(&quot;#highscores&quot;).append(highScorePlace1, highScorePlace2, highScorePlace3, highScorePlace4, highScorePlace5, resetButton);
	$(&quot;#&quot;+redScore).css(&quot;color&quot;, &quot;red&quot;);
	$(&quot;#highscores&quot;).toggle();

	$(&quot;.resetButton&quot;).click(function(){
		gameReset();
	});

	function gameReset(){
		$(&quot;#highscores&quot;).fadeToggle(100, function(){
			t = 0;
			$(&quot;.timer&quot;).html(`&amp;lt;h1&amp;gt;&amp;lt;div class=&quot;center&quot;&amp;gt;${t.toFixed(2)}&amp;lt;/div&amp;gt;&amp;lt;/h1&amp;gt;`);
			$(&quot;.resetButton&quot;).remove();
			$(&quot;.score&quot;).remove();
			$(&quot;.startbutton&quot;).toggle();
			$(&quot;.redcircle&quot;).remove();
		});
	};
};&lt;/code&gt;&lt;/pre&gt;</description>
      <category>IT</category>
      <category>JQuery</category>
      <category>공 피하기 게임</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/7</guid>
      <comments>https://clohoon.tistory.com/7#entry7comment</comments>
      <pubDate>Sun, 17 Sep 2023 13:23:06 +0900</pubDate>
    </item>
    <item>
      <title>프레임워크 없이 CRUD 구현하기</title>
      <link>https://clohoon.tistory.com/6</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;CRUD가 구현된 수강관리 앱을 만들어보자!&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nK1fq/btstvOLL22U/pzslwOpvOc5tNZDfnbcm70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nK1fq/btstvOLL22U/pzslwOpvOc5tNZDfnbcm70/img.png&quot; data-alt=&quot;완성된 수강관리 앱&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nK1fq/btstvOLL22U/pzslwOpvOc5tNZDfnbcm70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnK1fq%2FbtstvOLL22U%2FpzslwOpvOc5tNZDfnbcm70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;524&quot; height=&quot;179&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완성된 수강관리 앱&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚡&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt; CRUD란?&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;CRUD&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #4d5156;&quot;&gt;는 대부분의 컴퓨터 소프트웨어가 가지는 기본적인 데이터 처리 기능인 Create(생성), Read(읽기), Update(갱신), Delete(삭제)를 묶어서 일컫는 말이다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚡&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt; HTML&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&amp;nbsp;코드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
	&amp;lt;head&amp;gt;
		&amp;lt;title&amp;gt;수강관리 앱&amp;lt;/title&amp;gt;
		&amp;lt;link rel = &quot;stylesheet&quot; href = &quot;style.css&quot;&amp;gt;
	&amp;lt;/head&amp;gt;
	&amp;lt;body&amp;gt;
		&amp;lt;div id = &quot;container&quot;&amp;gt;&amp;lt;/div&amp;gt;
	&amp;lt;/body&amp;gt;
	&amp;lt;script src = &quot;./script.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚡&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt; CSS&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&amp;nbsp;코드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;.title {
	font-weight : bold;
	font-size : 30px;
}

table, th, td {
	border : solid 1px #DDD;
	border-collapse : collapse;
	padding : 2px 3px;
	text-align : center;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚡&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;b&gt; Javascript&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&amp;nbsp;코드&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;가독성을 위해 CreateNew, Save, Update, Delete 메소드를 따로 나누어 작성하였다. 코드가 길어서 설명은 주석으로 넣었다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;var crudApp = new function() {
	this.myClass = [
		{ID : '1', Class_Name : '운영체제', Category : '전공필수', Credit : 3},
		{ID : '2', Class_Name : '컴퓨터구조론', Category : '전공선택', Credit : 4},
		{ID : '3', Class_Name : '심리학의 이해', Category : '교양필수', Credit : 2},
	];
	this.Category = ['전공필수', '전공선택', '교양필수', '교양선택'];
    
    // 확장성을 위해 th에 담길 데이터를 col 배열에 담는다
	this.col = [];
	
	this.createTable = () =&amp;gt; {
		for(var i = 0; i &amp;lt; this.myClass.length; i++){
			for(var key in this.myClass[i]){
				if(this.col.indexOf(key) === -1) this.col.push(key);
			}
		}
	
		var table = document.createElement('table');
		table.setAttribute('id', 'classTable');
		
		var tr = table.insertRow(-1);
		
		// th 작성
		for(var h = 0; h &amp;lt; this.col.length; h++) {
			var th = document.createElement('th');
			th.innerHTML = this.col[h];
			tr.appendChild(th);
		}
		
		// td 작성
		for(var i = 0; i &amp;lt; this.myClass.length; i++) {
			tr = table.insertRow(-1);
			for(var j = 0; j &amp;lt; this.col.length; j++) {
				var tabCell = tr.insertCell(-1);
				tabCell.innerHTML = this.myClass[i][this.col[j]];
			}
			
			// update 버튼 만들기
			this.td = document.createElement('td');
			tr.appendChild(this.td);
			var btUpdate = document.createElement('input');
			btUpdate.setAttribute('type', 'button');
			btUpdate.setAttribute('value', 'Update');
			btUpdate.setAttribute('id', 'Edit' + i);
			btUpdate.setAttribute('style', 'background-color : #44CCEB;');
			btUpdate.setAttribute('onclick', 'crudApp.Update(this)');
			this.td.appendChild(btUpdate);
			
			// save 버튼 만들기
			tr.appendChild(this.td);
			var btSave = document.createElement('input');
			btSave.setAttribute('type', 'button');
			btSave.setAttribute('value', 'Save');
			btSave.setAttribute('id', 'Save' + i);
			btSave.setAttribute('style', 'display : none;');
			btSave.setAttribute('onclick', 'crudApp.Save(this)');
			this.td.appendChild(btSave);
			
			// delete 버튼 만들기
			this.td = document.createElement('td');
			tr.appendChild(this.td);
			var btDelete = document.createElement('input');
			btDelete.setAttribute('type', 'button');
			btDelete.setAttribute('value', 'Delete');
			btDelete.setAttribute('id', 'Delete' + i);
			btDelete.setAttribute('style', 'background-color : #ED5650;');
			btDelete.setAttribute('onclick', 'crudApp.Delete(this)');
			this.td.appendChild(btDelete);
		}
		
		// 입력 행 추가
		tr = table.insertRow(-1);
		for(var j = 0; j &amp;lt; this.col.length; j++) {
			var newCell = tr.insertCell(-1);
            // ID 값(j = 0일 때)은 입력받지 않는다
			if(j &amp;gt;= 1) {
				if(j == 2) {
					var select = document.createElement('select');
					select.innerHTML = `&amp;lt;option value = &quot;&quot;&amp;gt;&amp;lt;/option&amp;gt;`;
					for(var k = 0; k &amp;lt; this.Category.length; k++) {
						select.innerHTML += 
							`&amp;lt;option value = &quot;${this.Category[k]}&quot;&amp;gt;${this.Category[k]}&amp;lt;/option&amp;gt;`;
					}
					newCell.appendChild(select);
				}
				else {
					var tBox = document.createElement('input');
					tBox.setAttribute('type', 'text');
					tBox.setAttribute('value', '');
					newCell.appendChild(tBox);
				}
			}
		}
		
		// create 버튼 만들기
		this.td = document.createElement('td');
		tr.appendChild(this.td);
		var btCreate = document.createElement('input');
		btCreate.setAttribute('type', 'button');
		btCreate.setAttribute('value', 'Create');
		btCreate.setAttribute('id', 'New' + i);
		btCreate.setAttribute('style', 'background-color : #207DD1;');
		btCreate.setAttribute('onclick', 'crudApp.CreateNew(this)');
		this.td.appendChild(btCreate);
	
		var div = document.getElementById('container');
		div.innerHTML = '수강관리 앱';
		div.appendChild(table);
	}
	
	// Delete 메소드 코드

	// CreateNew 메소드 코드
	
	// Update 메소드 코드
	
	// Save 메소드 코드
}

crudApp.createTable();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;Delete 메소드 코드&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;this.Delete = (oButton) =&amp;gt; {
	var targetIdx = oButton.parentNode.parentNode.rowIndex;
	this.myClass.splice((targetIdx - 1), 1);
	this.createTable();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Delete 버튼을 누르면 실행된다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;oButton의 상위 노드는 td이고, td의 상위 노드는 tr이다. 때문에&lt;span&gt; target&lt;/span&gt;&lt;/span&gt;Idx에 버튼을 누른 행의 인덱스 값이 저장되고, myClass에서 해당 행을 제거한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CreateNew 메소드 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1694443727174&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;this.CreateNew = (oButton) =&amp;gt; {
	var writtenIdx = oButton.parentNode.parentNode.rowIndex;
	var trData = document.getElementById('classTable').rows[writtenIdx];
	
	var obj = {};
	
	// tr 데이터에서 td 속의 key:value 뽑아서 obj 안에 저장
	for(var i = 1; i &amp;lt; this.col.length; i++) {
		var td = trData.getElementsByTagName(&quot;td&quot;)[i];
		if(td.childNodes[0].getAttribute('type') === 'text' || td.childNodes[0].tagName === 'SELECT') {
			var txtVal = td.childNodes[0].value;
			
			if(txtVal != '') {
				obj[this.col[i]] = txtVal;
			}
			else {
				obj = '';
				alert(&quot;all fields are compulsory&quot;);
				break;
			}
		}
	}
	
	// Credit에 숫자 자료형이 입력되지 않으면 에러 출력
	if(obj != '') {
		if(isNaN(obj['Credit']) !== false) {
			obj = '';
			alert(&quot;Please enter number data type for Credit!&quot;);
		}
	}
	
	obj[this.col[0]] = this.myClass.length + 1; // 자동으로 ID 값 부여
	this.myClass.push(obj);
	this.createTable();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Create 버튼을 누르면 실행된다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;writtenIdx에 버튼을 누른 행(마지막 행)의 인덱스 값이 저장되고, trData에 해당 행의 데이터들이 저장된다. 비어 있는 데이터가 존재하면 obj를 초기화시킨 후 에러를 출력한다. 모든 데이터가 입력되었다면 obj를 myClass에 추가한다. Credit에 숫자 자료형이 입력되지 않으면 에러를 출력하는 기능을 추가로 구현했다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update 메소드 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;this.Update = (oButton) =&amp;gt; {
	var writtenIdx = oButton.parentNode.parentNode.rowIndex;
	var trData = document.getElementById('classTable').rows[writtenIdx];
		
	for(var i = 1; i &amp;lt; this.col.length; i++) {
		if(i === 2) {
			var td = trData.getElementsByTagName(&quot;td&quot;)[i];
			var select = document.createElement(&quot;select&quot;);
			select.innerHTML = `&amp;lt;option value = &quot;${td.innerText}&quot;&amp;gt;${td.innerText}&amp;lt;/option&amp;gt;`;
			for(var k = 0; k &amp;lt; this.Category.length; k++) {
				select.innerHTML +=
					`&amp;lt;option value = &quot;${this.Category[k]}&quot;&amp;gt;${this.Category[k]}&amp;lt;/option&amp;gt;`;
			}
			td.innerText = '';
			td.appendChild(select);
		}
		else {
			var td = trData.getElementsByTagName(&quot;td&quot;)[i];
			var input = document.createElement(&quot;input&quot;);
			input.setAttribute(&quot;type&quot;, &quot;text&quot;);
			input.setAttribute(&quot;value&quot;, td.innerText);
			td.innerText = '';
			td.appendChild(input);
		}
	}
		
	var btSave = document.getElementById('Save' + (writtenIdx - 1));
	btSave.setAttribute('style', 'display : block; background-color : #2DBF64;');
	oButton.setAttribute('style', 'display : none;');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Update 버튼을 누르면 실행된다. input 태그와 select 태그를 활성화시킨다. Update 버튼 대신 Save 버튼을 화면에 띄운다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Save 메소드 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;this.Save = (oButton) =&amp;gt; {
	var writtenIdx = oButton.parentNode.parentNode.rowIndex;
	var trData = document.getElementById('classTable').rows[writtenIdx];
		
	for(var i = 1; i &amp;lt; this.col.length; i++) {
		var td = trData.getElementsByTagName(&quot;td&quot;)[i];
		if(td.childNodes[0].getAttribute('type') === 'text' || td.childNodes[0].tagName === 'SELECT') {
			this.myClass[writtenIdx-1][this.col[i]] = td.childNodes[0].value;
		}
	}
	this.createTable();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Save 버튼을 누르면 실행된다. myClass에 저장되어 있던 기존의 값들을 새로 수정된 값들로 변경한다.&lt;/p&gt;</description>
      <category>IT</category>
      <category>crud</category>
      <category>수강관리앱</category>
      <author>clohoon</author>
      <guid isPermaLink="true">https://clohoon.tistory.com/6</guid>
      <comments>https://clohoon.tistory.com/6#entry6comment</comments>
      <pubDate>Tue, 12 Sep 2023 18:48:24 +0900</pubDate>
    </item>
  </channel>
</rss>