<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>쏘니의 개발블로그</title>
    <link>https://juntcom.tistory.com/</link>
    <description>개발 블로그입니다. 차근차근 많은 내용 정리해보고 싶습니다.</description>
    <language>ko</language>
    <pubDate>Thu, 14 May 2026 09:06:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>곰돌이쿤</managingEditor>
    <image>
      <title>쏘니의 개발블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/3898612/attach/c4e796e7ac394a06824b9b2050877411</url>
      <link>https://juntcom.tistory.com</link>
    </image>
    <item>
      <title>코볼트 RC카 아이와 함께 배우는 코딩 RC카 체험기!</title>
      <link>https://juntcom.tistory.com/330</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 요즘 아이 코딩 교육에 관심이 많아서 &lt;b&gt;코볼트 RC카&lt;/b&gt;를 체험해보게 되었어요. 마이크로비트를 활용한 &lt;b&gt;코딩 RC카&lt;/b&gt;라고 해서 정말 기대가 컸는데, 직접 사용해보니 기대 이상이었답니다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  개봉기 &amp;amp; 구성품 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;택배로 받은 코볼트 RC카 박스를 열어보니 정말 깔끔하게 포장되어 있더라구요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구성품 확인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코볼트 RC카 본체 1대&lt;/li&gt;
&lt;li&gt;USB 충전 케이블&lt;/li&gt;
&lt;li&gt;사용 설명서&lt;/li&gt;
&lt;li&gt;추가 액세서리 (별도 주문한 마이크로비트 v2.21)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;박스를 열자마자 블랙과 블루 컬러 조합의 세련된 디자인이 눈에 띄었어요. 플라스틱 재질이지만 전혀 저렴해 보이지 않고, 마이크로비트가 딱 맞게 들어가는 설계가 인상적이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞쪽에 있는 초음파 센서 2개가 마치 귀여운 눈처럼 생겨서 아이가 보자마자 &quot;로봇 같다!&quot;며 좋아했어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rDEyY/btsO1xvqmJ0/46MdS9744fPPdMIFC5k8G1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rDEyY/btsO1xvqmJ0/46MdS9744fPPdMIFC5k8G1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rDEyY/btsO1xvqmJ0/46MdS9744fPPdMIFC5k8G1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrDEyY%2FbtsO1xvqmJ0%2F46MdS9744fPPdMIFC5k8G1%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;4000&quot; height=&quot;2252&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2252&quot;/&gt;&lt;/span&gt;&lt;/figure&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/SABPQ/btsO1w4mYOi/bVamlKBCTW9mTOtQpRl050/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SABPQ/btsO1w4mYOi/bVamlKBCTW9mTOtQpRl050/img.jpg&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2252&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-16 001.jpeg&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SABPQ/btsO1w4mYOi/bVamlKBCTW9mTOtQpRl050/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSABPQ%2FbtsO1w4mYOi%2FbVamlKBCTW9mTOtQpRl050%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;4000&quot; height=&quot;2252&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chbwf9/btsO3UhRDBG/2uPz5jeYOIqHE9jbwOmlc0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chbwf9/btsO3UhRDBG/2uPz5jeYOIqHE9jbwOmlc0/img.jpg&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2252&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-17 002.jpeg&quot; width=&quot;259&quot; height=&quot;146&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chbwf9/btsO3UhRDBG/2uPz5jeYOIqHE9jbwOmlc0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchbwf9%2FbtsO3UhRDBG%2F2uPz5jeYOIqHE9jbwOmlc0%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;4000&quot; height=&quot;2252&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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/cUlCb5/btsO2FT4W9H/6sb6q5pPy2yIPZlDAJ4TS1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUlCb5/btsO2FT4W9H/6sb6q5pPy2yIPZlDAJ4TS1/img.jpg&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2252&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-17 003.jpeg&quot; width=&quot;448&quot; height=&quot;252&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUlCb5/btsO2FT4W9H/6sb6q5pPy2yIPZlDAJ4TS1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUlCb5%2FbtsO2FT4W9H%2F6sb6q5pPy2yIPZlDAJ4TS1%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;4000&quot; height=&quot;2252&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxJPC2/btsO3DOsl2V/fwVZnwWFjcjethjNF2KSN1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxJPC2/btsO3DOsl2V/fwVZnwWFjcjethjNF2KSN1/img.jpg&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2252&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-17 004.jpeg&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxJPC2/btsO3DOsl2V/fwVZnwWFjcjethjNF2KSN1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxJPC2%2FbtsO3DOsl2V%2FfwVZnwWFjcjethjNF2KSN1%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;4000&quot; height=&quot;2252&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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/eaXJXI/btsO2q39E8n/XI2kqDM0sr3V1o7j0wW970/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaXJXI/btsO2q39E8n/XI2kqDM0sr3V1o7j0wW970/img.jpg&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2252&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-18 006.jpeg&quot; width=&quot;382&quot; height=&quot;215&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaXJXI/btsO2q39E8n/XI2kqDM0sr3V1o7j0wW970/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaXJXI%2FbtsO2q39E8n%2FXI2kqDM0sr3V1o7j0wW970%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;4000&quot; height=&quot;2252&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dsxky/btsO4fGhhAK/PmhEoZUMSjTk8uF2NT8Vk1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dsxky/btsO4fGhhAK/PmhEoZUMSjTk8uF2NT8Vk1/img.jpg&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2252&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-18 005.jpeg&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dsxky/btsO4fGhhAK/PmhEoZUMSjTk8uF2NT8Vk1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDsxky%2FbtsO4fGhhAK%2FPmhEoZUMSjTk8uF2NT8Vk1%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;4000&quot; height=&quot;2252&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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/dGiqnK/btsO2oSQfrw/umAATTibQfkAtvM0tgK6J1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGiqnK/btsO2oSQfrw/umAATTibQfkAtvM0tgK6J1/img.jpg&quot; data-origin-width=&quot;2252&quot; data-origin-height=&quot;4000&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-30 001.jpeg&quot; width=&quot;387&quot; height=&quot;687&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGiqnK/btsO2oSQfrw/umAATTibQfkAtvM0tgK6J1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGiqnK%2FbtsO2oSQfrw%2FumAATTibQfkAtvM0tgK6J1%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;2252&quot; height=&quot;4000&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs3zPS/btsO1FtqFj8/Y1lSEwq3TU4LNbS9rfdc70/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs3zPS/btsO1FtqFj8/Y1lSEwq3TU4LNbS9rfdc70/img.jpg&quot; data-origin-width=&quot;2252&quot; data-origin-height=&quot;4000&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-30 002.jpeg&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs3zPS/btsO1FtqFj8/Y1lSEwq3TU4LNbS9rfdc70/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs3zPS%2FbtsO1FtqFj8%2FY1lSEwq3TU4LNbS9rfdc70%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;2252&quot; height=&quot;4000&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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/rVMRH/btsO3LMgsrE/jDrjmk7pVjyZO2zTFRlvU1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rVMRH/btsO3LMgsrE/jDrjmk7pVjyZO2zTFRlvU1/img.jpg&quot; data-origin-width=&quot;2252&quot; data-origin-height=&quot;4000&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-31 003.jpeg&quot; width=&quot;312&quot; height=&quot;554&quot; style=&quot;width: 23.7882%; margin-right: 10px;&quot; data-widthpercent=&quot;24.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rVMRH/btsO3LMgsrE/jDrjmk7pVjyZO2zTFRlvU1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrVMRH%2FbtsO3LMgsrE%2FjDrjmk7pVjyZO2zTFRlvU1%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;2252&quot; height=&quot;4000&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQ1Jmp/btsO4etPUC7/n6jxupuSKNll53ikdhyWQk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQ1Jmp/btsO4etPUC7/n6jxupuSKNll53ikdhyWQk/img.jpg&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2252&quot; data-is-animation=&quot;false&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-31 005.jpeg&quot; style=&quot;width: 75.049%;&quot; data-widthpercent=&quot;75.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQ1Jmp/btsO4etPUC7/n6jxupuSKNll53ikdhyWQk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQ1Jmp%2FbtsO4etPUC7%2Fn6jxupuSKNll53ikdhyWQk%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;4000&quot; height=&quot;2252&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  RC카 조립 방식 및 리모컨 조작법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조립 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조립은 정말 간단했어요!&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;마이크로비트 장착&lt;/b&gt;: RC카 상단에 있는 슬롯에 마이크로비트를 꽂기만 하면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배터리 삽입&lt;/b&gt;: 18650 리튬 배터리를 본체 하단에 넣어주세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전원 켜기&lt;/b&gt;: 측면의 전원 스위치를 ON으로 돌리면 준비 완료!&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 3분도 안 걸리는 간단한 과정이에요. 별도의 나사나 복잡한 조립이 필요 없어서 아이도 쉽게 할 수 있었답니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리모컨 조작법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코딩 RC카&lt;/b&gt;인 코볼트는 일반 RC카와 달리 프로그래밍으로 움직입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MakeCode 에디터 사용법:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;마이크로비트 MakeCode 웹사이트 접속&lt;/li&gt;
&lt;li&gt;확장 URL 추가: &lt;a href=&quot;https://github.com/LLY16888/Microbit_Car&quot;&gt;https://github.com/LLY16888/Microbit_Car&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;코볼트 전용 블록들이 나타남&lt;/li&gt;
&lt;li&gt;원하는 동작을 블록으로 조합&lt;/li&gt;
&lt;li&gt;마이크로비트에 다운로드&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 단순한 전진, 후진, 좌회전, 우회전부터 시작했는데 반응이 정말 빨랐어요!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-31 004.jpeg&quot; data-origin-width=&quot;2252&quot; data-origin-height=&quot;4000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mtSo6/btsO2W84tSq/SE3oaWXQaGvIRKVyLjyqa0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mtSo6/btsO2W84tSq/SE3oaWXQaGvIRKVyLjyqa0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mtSo6/btsO2W84tSq/SE3oaWXQaGvIRKVyLjyqa0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmtSo6%2FbtsO2W84tSq%2FSE3oaWXQaGvIRKVyLjyqa0%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;2252&quot; height=&quot;4000&quot; data-filename=&quot;KakaoTalk_Photo_2025-07-03-14-47-31 004.jpeg&quot; data-origin-width=&quot;2252&quot; data-origin-height=&quot;4000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 &lt;b&gt;코딩 RC카&lt;/b&gt; 코볼트가 움직이는 모습을 보니 정말 신기했어요. 150RPM 기어드 모터 덕분에 생각보다 속도감도 있고 파워풀하더라구요.&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;&lt;b&gt;야외 주행 테스트:&lt;/b&gt; 베란다에서도 테스트해봤는데 실내보다 더 시원하게 달리더라구요. 바닥이 매끄러워서 더 빠른 속도로 움직일 수 있었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주행 중에 아이가 가장 신기해했던 건 초음파 센서로 장애물을 감지하는 기능이었어요. 벽 앞에서 자동으로 멈추고 방향을 바꾸는 모습을 보고 &quot;진짜 스스로 생각하는 것 같다&quot;며 감탄했답니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  자율주행 도전기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;아이와 함께 &quot;만약 여기서 멈추면 어떻게 할까?&quot; &quot;벽이 너무 가까우면 어떻게 판단할까?&quot; 이런 것들을 생각해보면서 코딩하는 재미를 알아가는 것 같아서 뿌듯했어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  레고 호환성 체험&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코딩 RC카&lt;/b&gt; 코볼트의 가장 큰 장점 중 하나가 바로 레고 호환성이에요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집에 있던 레고 블록들을 위에 붙여서 나만의 RC카를 만들어봤는데 정말 재미있었어요. 레고 테크닉 부품들을 활용해서 굴삭기 모양으로 꾸며보니 완전 멋있더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이가 직접 레고로 작은 집을 만들어서 RC카 위에 올려놓고 &quot;이사가는 차&quot;라고 하면서 가지고 놀더라구요. 이런 창의적인 놀이가 가능한 게 정말 좋은 것 같아요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚡ 충전과 배터리 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USB 충전이 정말 편리해요! 별도 충전기 없이 핸드폰 충전기 그대로 사용할 수 있어서 좋았어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완충하는데 약 2시간 정도 걸리고, 한 번 충전하면 1시간 이상 연속으로 사용할 수 있었어요. 아이와 충분히 놀기에는 부족함이 없는 시간이었답니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배터리 잔량도 마이크로비트 화면에 표시되어서 언제 충전해야 할지 미리 알 수 있어서 좋았어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  교육적 효과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &lt;b&gt;코딩 RC카&lt;/b&gt;로 놀기만 하는 게 아니라 정말 많은 걸 배울 수 있어요.&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;&lt;b&gt;문제해결능력&lt;/b&gt;: 원하는 대로 안 움직일 때 어디가 문제인지 찾아서 수정하는 과정이 정말 교육적이에요.&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;&lt;b&gt;과학적 호기심&lt;/b&gt;: 초음파 센서가 어떻게 작동하는지, 모터는 어떻게 바퀴를 돌리는지 자연스럽게 관심을 갖게 되더라구요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  다양한 활용 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코볼트 &lt;b&gt;코딩 RC카&lt;/b&gt;는 정말 다양하게 활용할 수 있어요.&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;&lt;b&gt;경주 게임&lt;/b&gt;: 친구들과 함께 누가 더 빠르고 정확하게 목표점에 도착하는지 경쟁해볼 수 있어요.&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;&lt;b&gt;탐험 로봇&lt;/b&gt;: 집 안 곳곳을 돌아다니면서 사진을 찍는다는 설정으로 놀아봤어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  확장성과 업그레이드&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rd6SI/btsO16K9OlG/alCDSpcK5R9jIL5NIByJD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rd6SI/btsO16K9OlG/alCDSpcK5R9jIL5NIByJD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rd6SI/btsO16K9OlG/alCDSpcK5R9jIL5NIByJD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frd6SI%2FbtsO16K9OlG%2FalCDSpcK5R9jIL5NIByJD1%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;1840&quot; height=&quot;986&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 위키도 제공되고, MakeCode뿐만 아니라 파이썬으로도 프로그래밍할 수 있다는 점이 정말 마음에 들어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 아이가 더 커서 복잡한 프로그래밍을 배우고 싶어할 때도 계속 사용할 수 있을 것 같아요. 단순한 장난감이 아니라 성장하는 교육 도구라는 느낌이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 센서나 액세서리도 구매할 수 있어서 더 다양한 기능을 추가해볼 수 있다는 점도 좋았어요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  총평&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이와 함께 주말 내내 정말 재미있게 가지고 놀았어요. 처음엔 단순한 RC카 정도로 생각했는데, 직접 코딩해서 움직이는 걸 보니까 성취감이 정말 크더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩을 전혀 모르는 상태에서 시작해도 MakeCode 블록 코딩이 직관적이라 금방 익힐 수 있었어요. 아이보다 제가 더 빠졌다는 게 함정이긴 하지만요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 더 복잡한 미션들도 만들어보고 싶고, 레고랑 결합해서 더 멋진 모양으로도 만들어보고 싶어요.&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;코볼트 RC카 덕분에 아이가 코딩에 대한 관심이 생겼고, 무엇보다 함께 문제를 해결해나가는 과정이 정말 의미 있는 시간이었어요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&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;#코볼트RC카 #코딩RC카 #교육용RC카 #RC카 #마이크로비트 #마이크로비트RC카 #ICBANQ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅은 아이씨뱅큐로부터 제품을 제공받아 직접 체험 후 작성한 솔직한 후기입니다.&lt;/p&gt;</description>
      <category>코볼트rc카</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/330</guid>
      <comments>https://juntcom.tistory.com/330#entry330comment</comments>
      <pubDate>Thu, 3 Jul 2025 14:50:49 +0900</pubDate>
    </item>
    <item>
      <title>aws ec2 ubuntu 서버 생성 및 mysql 설치 그리고 java 17 설치</title>
      <link>https://juntcom.tistory.com/329</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vv0iw/btsJvnEvask/1z5kqzkGn0HKBTiyAJMAu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vv0iw/btsJvnEvask/1z5kqzkGn0HKBTiyAJMAu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vv0iw/btsJvnEvask/1z5kqzkGn0HKBTiyAJMAu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVv0iw%2FbtsJvnEvask%2F1z5kqzkGn0HKBTiyAJMAu0%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;1196&quot; height=&quot;936&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1196&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. launch instance 로 ec2 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rO2YK/btsJwaLBBGY/ojBpt3bL7CxLvBlhAiJdak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rO2YK/btsJwaLBBGY/ojBpt3bL7CxLvBlhAiJdak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rO2YK/btsJwaLBBGY/ojBpt3bL7CxLvBlhAiJdak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrO2YK%2FbtsJwaLBBGY%2FojBpt3bL7CxLvBlhAiJdak%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;825&quot; height=&quot;699&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;699&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ubuntu 로 서버 이미지 생성&amp;nbsp;&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;654&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rHcAH/btsJwRYVaAw/qlE3PodxsHKkLZfW7h58v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rHcAH/btsJwRYVaAw/qlE3PodxsHKkLZfW7h58v1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rHcAH/btsJwRYVaAw/qlE3PodxsHKkLZfW7h58v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrHcAH%2FbtsJwRYVaAw%2FqlE3PodxsHKkLZfW7h58v1%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;654&quot; height=&quot;305&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. security group&amp;nbsp;&lt;br /&gt;처음 생성하는 경우 security group 생성, 이전에 만든 적 있으면 이전에 만든 security group 생성&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 security group 에서 inbound 편집을 추후 해줘야 합니다.&amp;nbsp;&lt;br /&gt;ex ) 3306 port 추가 및 ssh 접속시 ip 허용&amp;nbsp;&lt;br /&gt;anywhere 로 추가하면 어떤 ip 에서도 접속 가능&amp;nbsp;&lt;br /&gt;&lt;br /&gt;4. key pair 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z8lk7/btsJvbYAONi/SPZ3bIuk4lVl5tDcfFudnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z8lk7/btsJvbYAONi/SPZ3bIuk4lVl5tDcfFudnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z8lk7/btsJvbYAONi/SPZ3bIuk4lVl5tDcfFudnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ8lk7%2FbtsJvbYAONi%2FSPZ3bIuk4lVl5tDcfFudnk%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;798&quot; height=&quot;243&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key 파일이 없으면 신규로 생성. 앞으로 해당 파일로 접속해야해서 분실하면 안됩니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Ubuntu EC2 인스턴스에 MySQL 8.0을 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. EC2 인스턴스에 접속&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;먼저 EC2 인스턴스에 SSH로 접속합니다. 로컬에서 다음 명령어를 사용하여 SSH 접속합니다:&lt;/p&gt;
&lt;pre id=&quot;code_1725858757039&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ssh -i /path/to/your-key.pem ubuntu@your-ec2-public-ip&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Ubuntu 패키지 업데이트&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;MySQL을 설치하기 전에, 패키지 목록을 업데이트합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858769171&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update&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;&lt;b&gt;3. MySQL 8.0 설치&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;MySQL 8.0은 Ubuntu의 기본 패키지 저장소에서 제공됩니다. 다음 명령어로 MySQL 8.0을 설치할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858783650&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt install mysql-server -y&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;&lt;b&gt;4. MySQL 서비스 시작&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;MySQL 서버가 설치되면, MySQL 서버를 시작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858795666&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl start mysql&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;MySQL 서버가 부팅 시 자동으로 시작되도록 설정하려면 다음 명령어를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858808271&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl enable mysql&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;&lt;b&gt;5. MySQL 기본 설정 및 보안&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;MySQL을 설치하면 보안 관련 기본 설정을 진행할 수 있습니다. 이를 위해 &lt;span&gt;mysql_secure_installation&lt;/span&gt; 스크립트를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725858823303&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mysql_secure_installation&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;루트 사용자 비밀번호 설정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;익명 사용자 삭제.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;원격 루트 로그인 비활성화.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;테스트 데이터베이스 삭제.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;보안 설정 재로드.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. MySQL 루트 사용자 비밀번호 설정&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;MySQL이 설치되면 루트 사용자의 비밀번호를 설정해야 합니다. 먼저 MySQL에 접속합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858838550&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 프롬프트에서 다음 명령어를 사용하여 루트 사용자 비밀번호를 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858848602&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'YourNewPassword';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 MySQL 프롬프트에서 빠져나갑니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858861300&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EXIT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. MySQL 접속 테스트&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;설정한 비밀번호를 사용하여 MySQL에 접속할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858877010&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql -u root -p&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호를 입력한 후 MySQL 프롬프트로 접속할 수 있습니다.&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;8. 방화벽 및 보안 그룹 설정 (필요한 경우)&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;MySQL의 기본 포트인 &lt;b&gt;3306&lt;/b&gt;을 외부에서 접근할 수 있도록 EC2 인스턴스의 &lt;b&gt;보안 그룹&lt;/b&gt;에서 &lt;b&gt;3306 포트&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;AWS 콘솔에서 EC2 인스턴스의 &lt;b&gt;보안 그룹&lt;/b&gt;으로 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;**인바운드 규칙 (Inbound Rules)**에서 &lt;b&gt;MySQL/Aurora&lt;/b&gt; 또는 &lt;b&gt;TCP 포트 3306&lt;/b&gt;을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Source&lt;/b&gt;는 허용할 IP 주소를 설정합니다 (예: &lt;span&gt;0.0.0.0/0&lt;/span&gt;은 모든 IP에서 접근 가능, 보안을 위해 특정 IP만 허용하는 것이 좋습니다).&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;9. MySQL 외부 접속 허용 (선택 사항)&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;기본적으로 MySQL은 외부 접속을 허용하지 않습니다. 외부에서 MySQL에 접속하려면 MySQL 설정 파일을 수정해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858901580&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 파일에서 &lt;span&gt;bind-address&lt;/span&gt; 항목을 찾은 후, &lt;span&gt;127.0.0.1&lt;/span&gt;을 EC2 인스턴스의 공인 IP로 변경하거나 모든 IP에서 접속 가능하게 하려면 &lt;span&gt;0.0.0.0&lt;/span&gt;으로 변경합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858913130&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bind-address = 0.0.0.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 저장하고 MySQL 서버를 재시작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725858922241&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl restart mysql&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;br /&gt;java 설치&lt;/p&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;&lt;b&gt;1. Ubuntu 패키지 목록 업데이트&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;/p&gt;
&lt;pre id=&quot;code_1725859015221&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt update&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Java 17 설치&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;Java 17은 Ubuntu의 기본 패키지 저장소에서 설치할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725859027429&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt install openjdk-17-jdk -y&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어는 OpenJDK 17 JDK(Java Development Kit)를 설치합니다.&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;3. Java 설치 확인&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;Java 17이 정상적으로 설치되었는지 확인하려면 다음 명령어를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725859039152&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java -version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과는 다음과 유사하게 나타날 것입니다:&lt;/p&gt;
&lt;pre id=&quot;code_1725859052061&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;openjdk version &quot;17.0.x&quot; 
OpenJDK Runtime Environment (build 17.0.x+xx)
OpenJDK 64-Bit Server VM (build 17.0.x+xx, mixed mode)&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;</description>
      <category>인프라/AWS</category>
      <category>aws ec2 ubuntu</category>
      <category>ubuntu java17</category>
      <category>Ubuntu mysql</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/329</guid>
      <comments>https://juntcom.tistory.com/329#entry329comment</comments>
      <pubDate>Mon, 9 Sep 2024 16:49:28 +0900</pubDate>
    </item>
    <item>
      <title>spring boot 예외처리 핸들러 @ControolerAdvice</title>
      <link>https://juntcom.tistory.com/328</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot에서 예외 처리 및 전역 예외 핸들러를 구현하는 코드를 통해, 다양한 예외 상황에서 일관된 응답을 제공하는 방법을 학습할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;@ControllerAdvice&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;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;Spring Boot에서는 기본적으로 예외가 발생하면 HTTP 상태 코드와 함께 오류 메시지를 반환합니다. 하지만 이 기본 방식은 사용자 경험(UX)을 향상시키기 위해 커스터마이징이 필요할 수 있습니다.&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;2. 전역 예외 처리 클래스 생성&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;span&gt;@ControllerAdvice&lt;/span&gt;를 사용하여 전역 예외 처리기를 생성합니다. 이 클래스는 애플리케이션 전반에서 발생하는 예외를 한 곳에서 처리하고, 일관된 방식으로 응답을 반환할 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1724301473003&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
@RestController
public class GlobalExceptionHandler {

    // 1. 기본적인 예외 처리
    @ExceptionHandler(Exception.class)
    public final ResponseEntity&amp;lt;Object&amp;gt; handleAllExceptions(Exception ex, WebRequest request) {
        Map&amp;lt;String, Object&amp;gt; response = new HashMap&amp;lt;&amp;gt;();
        response.put(&quot;message&quot;, ex.getMessage());
        response.put(&quot;details&quot;, request.getDescription(false));
        return new ResponseEntity&amp;lt;&amp;gt;(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    // 2. 특정 예외 처리 (예: NullPointerException)
    @ExceptionHandler(NullPointerException.class)
    public final ResponseEntity&amp;lt;Object&amp;gt; handleNullPointerException(NullPointerException ex, WebRequest request) {
        Map&amp;lt;String, Object&amp;gt; response = new HashMap&amp;lt;&amp;gt;();
        response.put(&quot;message&quot;, &quot;Null value encountered!&quot;);
        response.put(&quot;details&quot;, request.getDescription(false));
        return new ResponseEntity&amp;lt;&amp;gt;(response, HttpStatus.BAD_REQUEST);
    }

    // 3. 유효성 검사 실패 예외 처리 (예: MethodArgumentNotValidException)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public final Map&amp;lt;String, String&amp;gt; handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map&amp;lt;String, String&amp;gt; errors = new HashMap&amp;lt;&amp;gt;();
        ex.getBindingResult().getFieldErrors().forEach(error -&amp;gt; 
            errors.put(error.getField(), error.getDefaultMessage()));
        return errors;
    }

    // 4. 사용자 정의 예외 처리
    @ExceptionHandler(ResourceNotFoundException.class)
    public final ResponseEntity&amp;lt;Object&amp;gt; handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
        Map&amp;lt;String, Object&amp;gt; response = new HashMap&amp;lt;&amp;gt;();
        response.put(&quot;message&quot;, ex.getMessage());
        response.put(&quot;details&quot;, request.getDescription(false));
        return new ResponseEntity&amp;lt;&amp;gt;(response, HttpStatus.NOT_FOUND);
    }
}&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;&lt;b&gt;3. 사용자 정의 예외 클래스 생성&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/main/java/com/example/demo/exception/ResourceNotFoundException.java&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724301500697&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {

    public ResourceNotFoundException(String message) {
        super(message);
    }
}&lt;/code&gt;&lt;/pre&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예외 클래스에 &lt;span&gt;@ResponseStatus&lt;/span&gt; 어노테이션을 추가하면, 해당 예외가 발생할 때 Spring이 자동으로 지정된 HTTP 상태 코드를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예를 들어, &lt;span&gt;@ResponseStatus(HttpStatus.NOT_FOUND)&lt;/span&gt;를 사용하면, 이 예외가 발생할 때 자동으로 404 Not Found 상태 코드가 반환됩니다.&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@ResponseStatus&lt;/span&gt; 어노테이션을 사용하지 않으면, 예외 처리 클래스에서 &lt;span&gt;ResponseEntity&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;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;span&gt;ResourceNotFoundException&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;b&gt;src/main/java/com/example/demo/service/PostService.java&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724301513323&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.service;

import com.example.demo.entity.Post;
import com.example.demo.exception.ResourceNotFoundException;
import com.example.demo.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class PostService {

    @Autowired
    private PostRepository postRepository;

    public Post getPostById(Long id) {
        Optional&amp;lt;Post&amp;gt; post = postRepository.findById(id);
        if (!post.isPresent()) {
            throw new ResourceNotFoundException(&quot;Post not found with id: &quot; + id);
        }
        return post.get();
    }

    // 기타 CRUD 메서드들...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 컨트롤러에서의 사용 예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러에서 서비스 메서드를 호출할 때, 예외가 발생하면 &lt;span&gt;GlobalExceptionHandler&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;b&gt;src/main/java/com/example/demo/controller/PostController.java&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724301528606&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.controller;

import com.example.demo.entity.Post;
import com.example.demo.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(&quot;/api/posts&quot;)
public class PostController {

    @Autowired
    private PostService postService;

    // GET /api/posts/{id} - Get a post by id
    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&amp;lt;Post&amp;gt; getPostById(@PathVariable Long id) {
        Post post = postService.getPostById(id);
        return ResponseEntity.ok(post);
    }

    // 기타 CRUD 엔드포인트들...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/spring boot 및 기타</category>
      <category>Spring Boot Exception</category>
      <category>spring boot 예외처리</category>
      <category>spring 예외처리</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/328</guid>
      <comments>https://juntcom.tistory.com/328#entry328comment</comments>
      <pubDate>Thu, 22 Aug 2024 13:40:52 +0900</pubDate>
    </item>
    <item>
      <title>vue lifecycle 라이프사이클 정리</title>
      <link>https://juntcom.tistory.com/327</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue.js의 생성주기(lifecycle)는 Vue 인스턴스가 생성되어 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;Vue 2와 Vue 3 모두 유사한 생성주기를 가지지만, Vue 3에서는 Composition API가 도입되면서 라이프사이클 훅이 약간 다른 형태로 사용됩니다. 여기서는 Options API와 Composition API 두 가지 스타일 모두에서 생성주기를 어떻게 활용하는지 설명하겠습니다.&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;1. Vue 인스턴스의 생명주기 (Options API 기준)&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;Vue 컴포넌트는 다음과 같은 주요 생명주기 단계를 거칩니다:&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;1) Before Create (beforeCreate)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: Vue 인스턴스가 초기화되기 전, data와 methods가 정의되기 전에 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;용도&lt;/b&gt;: 초기화 전에 실행할 코드를 작성할 수 있지만, data나 methods에 접근할 수 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991542043&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;beforeCreate() {
  console.log('beforeCreate: 데이터와 메서드가 정의되기 전입니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) Created (created)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: Vue 인스턴스가 생성된 후, data와 methods가 초기화된 상태에서 호출됩니다. 이 시점에서 인스턴스는 반응형 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;용도&lt;/b&gt;: API 호출, 초기 데이터 설정 등의 작업을 할 수 있습니다. 하지만 DOM에 접근할 수 없습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991562168&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;created() {
  console.log('created: 인스턴스가 생성되고 data와 methods가 초기화되었습니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) Before Mount (beforeMount)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 템플릿이 렌더링되기 전에 호출됩니다. DOM에 접근할 수 있지만, 아직 Vue가 DOM에 연결되지 않은 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;용도&lt;/b&gt;: 렌더링 전에 DOM에 대해 작업할 필요가 있을 때 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991574855&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;beforeMount() {
  console.log('beforeMount: 템플릿이 렌더링되기 전입니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) Mounted (mounted)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: Vue 인스턴스가 DOM에 마운트된 후 호출됩니다. 이 시점에서 템플릿은 DOM에 반영되었으며, 컴포넌트가 화면에 나타납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;용도&lt;/b&gt;: 컴포넌트가 DOM에 연결된 후 초기화 작업을 하거나, 외부 라이브러리를 초기화할 때 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991586834&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mounted() {
  console.log('mounted: 인스턴스가 DOM에 마운트되었습니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) Before Update (beforeUpdate)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 반응형 데이터가 변경되어 DOM이 업데이트되기 직전에 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;용도&lt;/b&gt;: 데이터가 변경되었지만 DOM에 반영되기 전에 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991603755&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;beforeUpdate() {
  console.log('beforeUpdate: 데이터가 변경되었고 DOM이 업데이트되기 전입니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6) Updated (updated)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 데이터가 변경되어 DOM이 다시 렌더링된 후 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;용도&lt;/b&gt;: 데이터 변경에 따른 DOM 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991616026&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;updated() {
  console.log('updated: 데이터가 변경되고 DOM이 업데이트되었습니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7) Before Destroy (beforeDestroy)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: Vue 인스턴스가 파괴되기 전에 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;용도&lt;/b&gt;: 인스턴스가 파괴되기 전에 정리 작업을 수행할 수 있습니다. 이벤트 리스너 해제, 타이머 제거 등이 필요할 때 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991627304&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;beforeDestroy() {
  console.log('beforeDestroy: 인스턴스가 파괴되기 전입니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8) Destroyed (destroyed)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: Vue 인스턴스가 파괴된 후 호출됩니다. 인스턴스의 모든 바인딩과 이벤트 리스너가 해제됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;용도&lt;/b&gt;: 인스턴스 파괴 후의 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991643289&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;destroyed() {
  console.log('destroyed: 인스턴스가 파괴되었습니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Vue 3에서의 Composition API 생성주기&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;Vue 3에서는 Composition API가 도입되면서 라이프사이클 훅도 &lt;span&gt;setup&lt;/span&gt; 함수 내에서 사용할 수 있도록 변경되었습니다. 각 훅은 Vue 2의 Options API에서와 동일한 시점에 호출되지만, 이름이 조금씩 다릅니다.&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&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;onBeforeMount&lt;/b&gt;&lt;span&gt;: &lt;/span&gt;beforeMount&lt;span&gt;와 동일한 타이밍에 호출됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;onMounted&lt;/b&gt;&lt;/span&gt;: &lt;span&gt;mounted&lt;/span&gt;와 동일한 타이밍에 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;onBeforeUpdate&lt;/b&gt;&lt;span&gt;: &lt;/span&gt;beforeUpdate&lt;span&gt;와 동일한 타이밍에 호출됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;onUpdated&lt;/b&gt;&lt;/span&gt;: &lt;span&gt;updated&lt;/span&gt;와 동일한 타이밍에 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;onBeforeUnmount&lt;/b&gt;&lt;span&gt;: &lt;/span&gt;beforeDestroy&lt;span&gt;와 동일한 타이밍에 호출됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;onUnmounted&lt;/b&gt;&lt;/span&gt;: &lt;span&gt;destroyed&lt;/span&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;577&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IK0Bn/btsI7WTSaQ5/kOBWK3wpngP4fosGkoNE71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IK0Bn/btsI7WTSaQ5/kOBWK3wpngP4fosGkoNE71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IK0Bn/btsI7WTSaQ5/kOBWK3wpngP4fosGkoNE71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIK0Bn%2FbtsI7WTSaQ5%2FkOBWK3wpngP4fosGkoNE71%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;577&quot; height=&quot;854&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1723991662102&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    onMounted(() =&amp;gt; {
      console.log('mounted: 컴포넌트가 마운트되었습니다.');
    });

    onBeforeUnmount(() =&amp;gt; {
      console.log('beforeUnmount: 컴포넌트가 파괴되기 전입니다.');
    });
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 생성주기 훅의 활용 예시&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;API 호출&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;created&lt;/span&gt; 또는 &lt;span&gt;onMounted&lt;/span&gt;에서 초기 데이터를 가져오기 위해 API를 호출할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991687859&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;created() {
  fetch('/api/data')
    .then(response =&amp;gt; response.json())
    .then(data =&amp;gt; {
      this.data = data;
    });
}

// Composition API 방식
onMounted(() =&amp;gt; {
  fetch('/api/data')
    .then(response =&amp;gt; response.json())
    .then(data =&amp;gt; {
      state.data = data;
    });
});&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;외부 라이브러리나 플러그인을 초기화하기 위해 &lt;span&gt;mounted&lt;/span&gt;나 &lt;span&gt;onMounted&lt;/span&gt;를 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723991705712&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mounted() {
  this.$nextTick(() =&amp;gt; {
    this.initializePlugin();
  });
}

// Composition API 방식
onMounted(() =&amp;gt; {
  nextTick(() =&amp;gt; {
    initializePlugin();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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;Vue의 생명주기는 컴포넌트의 생성, 렌더링, 업데이트, 파괴 과정에서 특정 시점에 코드 실행을 가능하게 합니다. Options API에서는 메서드 형태로 각 훅을 정의하며, Composition API에서는 &lt;span&gt;setup&lt;/span&gt; 함수 내부에서 훅을 호출하는 방식으로 관리합니다. 이 생성주기 훅들은 컴포넌트의 초기화, DOM 접근, 외부 자원 관리, 데이터 업데이트 등에 중요한 역할을 합니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;생성주기 문서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.vuejs.org/guide/essentials/lifecycle.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/essentials/lifecycle.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue</category>
      <category>Vue lifecycle</category>
      <category>vue 생성주기</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/327</guid>
      <comments>https://juntcom.tistory.com/327#entry327comment</comments>
      <pubDate>Sun, 18 Aug 2024 23:37:20 +0900</pubDate>
    </item>
    <item>
      <title>vue to-do 리스트 구현 7 - composition api 비교</title>
      <link>https://juntcom.tistory.com/326</link>
      <description>&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;1. Option API와 Composition API 비교 설명&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Option API&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;전통적인 Vue.js 방식으로, 컴포넌트의 로직을 &lt;span&gt;data&lt;/span&gt;, &lt;span&gt;methods&lt;/span&gt;, &lt;span&gt;computed&lt;/span&gt;, &lt;span&gt;watch&lt;/span&gt; 등으로 구분합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;코드의 구조가 명확하게 나누어져 있어, 이해하기 쉬운 것이 장점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;큰 컴포넌트에서는 로직이 흩어져 있어 관리가 어려워질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Composition API&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue 3에서 도입된 새로운 방식으로, 함수형 프로그래밍 스타일에 가까운 접근법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;관련 로직을 &lt;span&gt;setup&lt;/span&gt; 함수 내에서 함께 정의할 수 있어, 큰 컴포넌트에서도 관련 로직을 그룹화하여 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;, &lt;span&gt;computed&lt;/span&gt;, &lt;span&gt;watch&lt;/span&gt;, &lt;span&gt;onMounted&lt;/span&gt; 등의 기능을 활용하여 상태와 라이프사이클을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 data-ke-size=&quot;size16&quot;&gt;Composition API를 사용하여 &lt;span&gt;post&lt;/span&gt; 게시판을 구현하면서 기존의 Option API 방식과 비교할 수 있었습니다. Composition API는 보다 유연하고 코드 재사용성을 높이는 데 유리하며, Vuex와의 결합도 자연스럽습니다. 이를 통해 학생들이 Vue의 두 가지 API 스타일을 이해하고, 필요에 따라 적절히 선택할 수 있도록 도울 수 있습니다.&lt;br /&gt;&lt;br /&gt;todoList 를 postList 로 동일한 코드로 composition api 로 변형 해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;PostList.vue&lt;/p&gt;
&lt;pre id=&quot;code_1723962540990&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;v-container&amp;gt;
      &amp;lt;v-row&amp;gt;
        &amp;lt;v-col&amp;gt;
          &amp;lt;h1&amp;gt;Vue.js Post Board&amp;lt;/h1&amp;gt;
          &amp;lt;AddPost @add-post=&quot;addPost&quot; /&amp;gt;
          &amp;lt;v-list&amp;gt;
            &amp;lt;v-list-item
              v-for=&quot;(post, index) in posts&quot;
              :key=&quot;index&quot;
              @click=&quot;navigateToDetail(index)&quot;
            &amp;gt;
              &amp;lt;template v-slot:prepend&amp;gt;
                &amp;lt;v-checkbox
                  v-model=&quot;post.completed&quot;
                  @click.stop=&quot;togglePostStatus(index, !post.completed)&quot;
                  hide-details
                /&amp;gt;
              &amp;lt;/template&amp;gt;
              
              &amp;lt;v-list-item-title&amp;gt;{{ post.text }}&amp;lt;/v-list-item-title&amp;gt;
              
              &amp;lt;template v-slot:append&amp;gt;
                &amp;lt;v-btn icon=&quot;mdi-delete&quot; size=&quot;small&quot; @click.stop=&quot;removePost(index)&quot;&amp;gt;
                &amp;lt;/v-btn&amp;gt;
              &amp;lt;/template&amp;gt;
            &amp;lt;/v-list-item&amp;gt;
          &amp;lt;/v-list&amp;gt;
        &amp;lt;/v-col&amp;gt;
      &amp;lt;/v-row&amp;gt;
    &amp;lt;/v-container&amp;gt;
  &amp;lt;/template&amp;gt;
  
  &amp;lt;script&amp;gt;
  import { computed, onMounted } from 'vue';
  import { useRouter } from 'vue-router';  // useRouter를 import
  import { useStore } from 'vuex';
  import AddPost from './AddPost.vue';
  
  export default {
    name: 'PostList',
    components: { AddPost },
    setup() {
      const store = useStore();
      const router = useRouter();
  
      const posts = computed(() =&amp;gt; store.getters.posts);
      const addPost = (postText) =&amp;gt; store.dispatch('addPost', postText);
      const removePost = (index) =&amp;gt; store.dispatch('removePost', index);
      const togglePostStatus = (index, completed) =&amp;gt;
        store.dispatch('togglePostStatus', { index, completed });
  
      const navigateToDetail = (index) =&amp;gt; {
        // Navigate to post detail page
        router.push({ name: 'PostDetail', params: { id: index } });
      };
  
      onMounted(() =&amp;gt; {
        store.dispatch('loadPosts');
      });
  
      return {
        posts,
        addPost,
        removePost,
        togglePostStatus,
        navigateToDetail,
      };
    },
  };
  &amp;lt;/script&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;AddPost.vue&lt;/p&gt;
&lt;pre id=&quot;code_1723962561889&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;v-text-field
      v-model=&quot;newPost&quot;
      label=&quot;Add a new post&quot;
      @keyup.enter=&quot;submitPost&quot;
      outlined
      dense
    /&amp;gt;
    &amp;lt;v-btn color=&quot;primary&quot; @click=&quot;submitPost&quot;&amp;gt;Add&amp;lt;/v-btn&amp;gt;
  &amp;lt;/template&amp;gt;
  
  &amp;lt;script&amp;gt;
  import { ref } from 'vue';
  
  export default {
    name: 'AddPost',
    emits: ['add-post'],  // 커스텀 이벤트를 정의합니다.
    setup(props, { emit }) {
      const newPost = ref('');
  
      const submitPost = () =&amp;gt; {
        if (newPost.value.trim()) {
          emit('add-post', newPost.value);
          newPost.value = '';
        }
      };
  
      return {
        newPost,
        submitPost,
      };
    },
  };
  &amp;lt;/script&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;PostDetail.vue&lt;/p&gt;
&lt;pre id=&quot;code_1723963107528&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;v-container&amp;gt;
      &amp;lt;h2&amp;gt;Post Detail&amp;lt;/h2&amp;gt;
      &amp;lt;v-card&amp;gt;
        &amp;lt;v-card-text&amp;gt;
          &amp;lt;p&amp;gt;ID: {{ id }}&amp;lt;/p&amp;gt;
          &amp;lt;p v-if=&quot;post&quot;&amp;gt;Text: {{ post.text }}&amp;lt;/p&amp;gt;
          &amp;lt;p v-else&amp;gt;Post not found&amp;lt;/p&amp;gt;
        &amp;lt;/v-card-text&amp;gt;
      &amp;lt;/v-card&amp;gt;
      &amp;lt;v-btn @click=&quot;$router.push('/post')&quot;&amp;gt;Back&amp;lt;/v-btn&amp;gt;
    &amp;lt;/v-container&amp;gt;
  &amp;lt;/template&amp;gt;
  
  &amp;lt;script&amp;gt;
  import { computed } from 'vue';
  import { useStore } from 'vuex';
  import { useRoute } from 'vue-router';
  
  export default {
    setup() {
      const store = useStore();
      const route = useRoute();
  
      const id = computed(() =&amp;gt; route.params.id);
      const post = computed(() =&amp;gt; store.getters['posts'][id.value]);
  
      return {
        id,
        post,
      };
    },
  };
  &amp;lt;/script&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;이전에 만들었던 todo List 와 비교하면서 보면 이해가 빠를 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Composition API 구성 요소&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;1) setup 함수&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;span&gt;setup&lt;/span&gt; 함수는 Composition API의 핵심으로, 컴포넌트가 생성될 때 호출됩니다. 이 함수 내에서 상태 관리, 메서드 정의, 라이프사이클 훅 등을 설정할 수 있습니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;리턴값&lt;/b&gt;: &lt;span&gt;setup&lt;/span&gt; 함수에서 반환하는 객체는 템플릿에서 사용됩니다. 이 함수에서 정의한 모든 데이터와 메서드는 &lt;span&gt;setup&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;b&gt;2) useStore와 useRouter&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;useStore&lt;/b&gt;&lt;/span&gt;: Vuex의 스토어에 접근하기 위해 사용됩니다. &lt;span&gt;store&lt;/span&gt; 객체를 반환하며, 이를 통해 상태 관리와 관련된 모든 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;useRouter&lt;/b&gt;&lt;/span&gt;: Vue Router에서 제공하는 훅으로, 현재 라우터 인스턴스에 접근하여 라우팅을 처리할 수 있습니다. 예를 들어, 페이지 이동을 위해 &lt;span&gt;router.push&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;b&gt;3) computed&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;span&gt;computed&lt;/span&gt; 속성은 반응형 데이터를 정의할 때 사용됩니다. 여기서 &lt;span&gt;posts&lt;/span&gt;는 Vuex 스토어에서 가져온 할 일 목록으로, &lt;span&gt;computed&lt;/span&gt;로 선언하여 Vue의 반응형 시스템과 연결됩니다.&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;4) onMounted&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;span&gt;onMounted&lt;/span&gt;는 Vue의 라이프사이클 훅 중 하나로, 컴포넌트가 DOM에 마운트된 직후 호출됩니다. 이 훅을 사용하여 컴포넌트가 초기화될 때 필요한 작업을 수행할 수 있습니다. 예를 들어, 이 코드에서는 컴포넌트가 마운트될 때 &lt;span&gt;loadPosts&lt;/span&gt; 액션을 디스패치하여 로컬 스토리지에서 데이터를 불러옵니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 구현된 기능 설명&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Post 목록 가져오기 (&lt;/b&gt;&lt;span&gt;&lt;b&gt;posts&lt;/b&gt;&lt;/span&gt;&lt;b&gt;)&lt;/b&gt;: &lt;span&gt;posts&lt;/span&gt;는 Vuex의 &lt;span&gt;getters.posts&lt;/span&gt;를 &lt;span&gt;computed&lt;/span&gt;로 받아와, 템플릿에서 렌더링됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Post 추가하기 (&lt;/b&gt;&lt;span&gt;&lt;b&gt;addPost&lt;/b&gt;&lt;/span&gt;&lt;b&gt;)&lt;/b&gt;: 사용자가 새로운 포스트를 추가하면, 해당 텍스트가 Vuex 액션을 통해 &lt;span&gt;posts&lt;/span&gt; 상태에 추가됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Post 상태 토글 (&lt;/b&gt;&lt;span&gt;&lt;b&gt;togglePostStatus&lt;/b&gt;&lt;/span&gt;&lt;b&gt;)&lt;/b&gt;: 체크박스를 클릭하여 포스트의 완료 상태를 토글할 수 있습니다. 이 상태는 &lt;span&gt;v-model&lt;/span&gt;과 &lt;span&gt;store.dispatch&lt;/span&gt;를 통해 Vuex와 동기화됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Post 삭제하기 (&lt;/b&gt;&lt;span&gt;&lt;b&gt;removePost&lt;/b&gt;&lt;/span&gt;&lt;b&gt;)&lt;/b&gt;: 삭제 버튼을 클릭하면 해당 포스트가 삭제됩니다. 이 작업 역시 Vuex 액션을 통해 처리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Post 상세보기 페이지로 이동 (&lt;/b&gt;&lt;span&gt;&lt;b&gt;navigateToDetail&lt;/b&gt;&lt;/span&gt;&lt;b&gt;)&lt;/b&gt;: 포스트 항목을 클릭하면 해당 포스트의 상세 페이지로 라우팅됩니다. &lt;span&gt;router.push&lt;/span&gt;를 통해 &lt;span&gt;PostDetail&lt;/span&gt; 페이지로 이동하며, &lt;span&gt;id&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;b&gt;4. Composition API와 Options API 비교&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;유연성&lt;/b&gt;: Composition API는 로직과 상태 관리를 한 곳에 모아서 정의할 수 있으므로, 관련된 코드를 쉽게 그룹화하고 재사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;재사용성&lt;/b&gt;: Composition API는 함수로 로직을 캡슐화하여 재사용할 수 있어, 복잡한 컴포넌트에서 더 나은 코드 조직화를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;명확성&lt;/b&gt;: &lt;span&gt;setup&lt;/span&gt; 함수 안에서 모든 로직을 관리하므로, 상태와 메서드가 어디에서 정의되고 어떻게 사용되는지 명확히 알 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;이전 블로그&amp;nbsp;&lt;br /&gt;vuex 를 이용한 todo list 작성&lt;br /&gt;&lt;a href=&quot;https://juntcom.tistory.com/323&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://juntcom.tistory.com/323&lt;/a&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;vue 가이드 - option, composition api&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://ko.vuejs.org/guide/introduction.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.vuejs.org/guide/introduction.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue</category>
      <category>vue composition api</category>
      <category>vue option api</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/326</guid>
      <comments>https://juntcom.tistory.com/326#entry326comment</comments>
      <pubDate>Sun, 18 Aug 2024 15:43:57 +0900</pubDate>
    </item>
    <item>
      <title>Vuetify 문서 레퍼런스 가이드</title>
      <link>https://juntcom.tistory.com/325</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vuetify는 Material Design 가이드를 따르는 Vue.js UI 라이브러리로, 다양한 사전 제작된 컴포넌트와 유틸리티를 제공하여 반응형이고 미적으로 우수한 인터페이스를 빠르게 개발할 수 있도록 도와줍니다. 아래는 Vuetify를 효과적으로 사용하는 데 필요한 주요 개념과 문서에 대한 가이드입니다.&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;Vuetify 시작하기&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;공식 설치 가이드: &lt;a href=&quot;https://vuetifyjs.com/en/getting-started/installation/#installation&quot;&gt;Vuetify 설치&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723958608446&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vue add vuetify&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723958622142&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install vuetify&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;설치 후 &lt;span&gt;main.js&lt;/span&gt; 파일에서 Vuetify를 설정합니다:&lt;/p&gt;
&lt;pre id=&quot;code_1723958634357&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createApp } from 'vue';
import App from './App.vue';
import vuetify from './plugins/vuetify';

createApp(App)
  .use(vuetify)
  .mount('#app');&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;&lt;b&gt;스타일링 및 레이아웃&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vuetify는 Material Design의 레이아웃 시스템을 따릅니다. &lt;span&gt;v-container&lt;/span&gt;, &lt;span&gt;v-row&lt;/span&gt;, &lt;span&gt;v-col&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;v-container&lt;/b&gt;&lt;/span&gt;: 레이아웃의 기본 컨테이너로, 내부에 &lt;span&gt;v-row&lt;/span&gt;와 &lt;span&gt;v-col&lt;/span&gt;을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;v-row&lt;/b&gt;&lt;/span&gt;: 수평 행을 생성하며, 여러 &lt;span&gt;v-col&lt;/span&gt;을 포함할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;v-col&lt;/b&gt;&lt;/span&gt;: 열을 생성하며, 그리드 시스템에서 비율에 따라 폭을 차지합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723958657819&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;v-container&amp;gt;
  &amp;lt;v-row&amp;gt;
    &amp;lt;v-col cols=&quot;12&quot; md=&quot;8&quot;&amp;gt;Main content&amp;lt;/v-col&amp;gt;
    &amp;lt;v-col cols=&quot;12&quot; md=&quot;4&quot;&amp;gt;Sidebar&amp;lt;/v-col&amp;gt;
  &amp;lt;/v-row&amp;gt;
&amp;lt;/v-container&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify는 &lt;span&gt;text-h1&lt;/span&gt;에서 &lt;span&gt;text-h6&lt;/span&gt;까지 다양한 텍스트 스타일과 크기를 제공하며, 이러한 클래스들을 사용해 쉽게 텍스트 스타일을 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/styles/text-and-typography/#typography&quot;&gt;텍스트 및 타이포그래피&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723958686144&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;v-typography variant=&quot;h1&quot;&amp;gt;This is a headline&amp;lt;/v-typography&amp;gt;
&amp;lt;v-typography variant=&quot;body-1&quot;&amp;gt;This is body text&amp;lt;/v-typography&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;&lt;b&gt;컬러 팔레트&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify는 Material Design을 기반으로 한 색상 팔레트를 포함하고 있어, 애플리케이션 전반에 걸쳐 일관된 색상 테마를 쉽게 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/styles/colors/#material-colors&quot;&gt;Material Colors&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;&lt;b&gt;아이콘 폰트&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify는 다양한 아이콘 폰트를 지원하며, Material Design Icons(MDI)와 쉽게 통합할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;아이콘 검색 및 사용: &lt;a href=&quot;https://vuetifyjs.com/en/features/icon-fonts/#mdi-js-svg&quot;&gt;Vuetify 아이콘 폰트&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;MDI 전체 라이브러리 보기: &lt;a href=&quot;https://pictogrammers.com/library/mdi/&quot;&gt;MDI 아이콘 라이브러리&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;&lt;b&gt;테마 커스터마이징&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify에서 애플리케이션의 테마를 커스터마이징할 수 있습니다. 테마 값은 &lt;span&gt;myCustomLightTheme&lt;/span&gt; 객체를 수정하여 변경할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/features/theme/#javascript&quot;&gt;테마 설정&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723958799550&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const vuetify = createVuetify({
  theme: {
    themes: {
      light: {
        primary: '#3f51b5',
        secondary: '#b0bec5',
        accent: '#8c9eff',
        error: '#b71c1c',
      },
    },
  },
});&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify의 컬러 속성을 사용하여 Material Design의 색상 시스템으로 컴포넌트를 스타일링할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/styles/colors/#material-colors&quot;&gt;컬러 속성&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;&lt;b&gt;간격(Spacing) 유틸리티&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify는 마진과 패딩을 쉽게 관리할 수 있는 간격 유틸리티를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/styles/spacing/#how-it-works&quot;&gt;간격 유틸리티&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;&lt;b&gt;그리드 시스템 (Col 속성)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify의 그리드 시스템은 Flexbox를 기반으로 하며, 강력한 레이아웃 시스템을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/components/grids/#offset-breakpoint&quot;&gt;그리드 및 Col 속성&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;&lt;b&gt;컴포넌트&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;텍스트 필드는 사용자로부터 텍스트 입력을 받기 위한 주요 폼 요소입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/components/text-fields/&quot;&gt;텍스트 필드&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723958732415&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;v-form&amp;gt;
  &amp;lt;v-text-field label=&quot;Name&quot; v-model=&quot;name&quot;&amp;gt;&amp;lt;/v-text-field&amp;gt;
  &amp;lt;v-select :items=&quot;['Option 1', 'Option 2']&quot; label=&quot;Select an option&quot;&amp;gt;&amp;lt;/v-select&amp;gt;
  &amp;lt;v-checkbox label=&quot;Accept Terms&quot; v-model=&quot;accepted&quot;&amp;gt;&amp;lt;/v-checkbox&amp;gt;
&amp;lt;/v-form&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;라디오 버튼은 목록에서 하나의 옵션을 선택할 때 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/components/radio-buttons/#model-group&quot;&gt;라디오 버튼&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;&lt;b&gt;다이얼로그(Dialog)&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;다이얼로그는 중요한 정보나 사용자의 결정을 요청하는 모달 창입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예시: &lt;a href=&quot;https://vuetifyjs.com/en/components/dialogs/#usage&quot;&gt;다이얼로그 사용법&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723958777679&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;v-dialog v-model=&quot;dialog&quot;&amp;gt;
  &amp;lt;v-card&amp;gt;
    &amp;lt;v-card-title&amp;gt;Dialog Title&amp;lt;/v-card-title&amp;gt;
    &amp;lt;v-card-text&amp;gt;
      This is a dialog content.
    &amp;lt;/v-card-text&amp;gt;
    &amp;lt;v-card-actions&amp;gt;
      &amp;lt;v-btn @click=&quot;dialog = false&quot;&amp;gt;Close&amp;lt;/v-btn&amp;gt;
    &amp;lt;/v-card-actions&amp;gt;
  &amp;lt;/v-card&amp;gt;
&amp;lt;/v-dialog&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;데이터 테이블은 구조화된 데이터를 표 형식으로 표시하는 데 사용되며, 정렬, 페이징, 검색 등의 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/components/data-tables/server-side-tables/#server-side-search&quot;&gt;데이터 테이블&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723958754306&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;v-data-table :headers=&quot;headers&quot; :items=&quot;items&quot; :search=&quot;search&quot;&amp;gt;
  &amp;lt;template v-slot:top&amp;gt;
    &amp;lt;v-text-field v-model=&quot;search&quot; label=&quot;Search&quot;&amp;gt;&amp;lt;/v-text-field&amp;gt;
  &amp;lt;/template&amp;gt;
&amp;lt;/v-data-table&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;&lt;b&gt;카드&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;카드는 다양한 유형의 콘텐츠를 표시하는 데 유용한 유연한 컨테이너입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예시: &lt;a href=&quot;https://vuetifyjs.com/en/components/cards/#basics&quot;&gt;카드 사용법&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723958763582&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;v-card&amp;gt;
  &amp;lt;v-card-title&amp;gt;Card Title&amp;lt;/v-card-title&amp;gt;
  &amp;lt;v-card-subtitle&amp;gt;Card Subtitle&amp;lt;/v-card-subtitle&amp;gt;
  &amp;lt;v-card-text&amp;gt;
    This is some text within a card.
  &amp;lt;/v-card-text&amp;gt;
  &amp;lt;v-card-actions&amp;gt;
    &amp;lt;v-btn text&amp;gt;Action&amp;lt;/v-btn&amp;gt;
  &amp;lt;/v-card-actions&amp;gt;
&amp;lt;/v-card&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;셀렉트 입력은 목록에서 옵션을 선택할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/components/selects/#usage&quot;&gt;셀렉트 입력&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;&lt;b&gt;내비게이션 드로어&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;내비게이션 드로어는 사이드 메뉴를 제공하며, 토글로 표시하거나 숨길 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/components/navigation-drawers/&quot;&gt;내비게이션 드로어&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;&lt;b&gt;리스트&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;리스트는 관련된 콘텐츠를 그룹화하여 표시하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;문서: &lt;a href=&quot;https://vuetifyjs.com/en/components/lists/&quot;&gt;리스트&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;&lt;b&gt;칩&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;칩은 복잡한 엔티티를 컴팩트한 형태로 표현하는 작은 컴포넌트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예시: &lt;a href=&quot;https://vuetifyjs.com/en/components/chips/&quot;&gt;칩 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;API 문서: &lt;a href=&quot;https://vuetifyjs.com/en/api/v-chip/&quot;&gt;v-chip API&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue</category>
      <category>vuetify</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/325</guid>
      <comments>https://juntcom.tistory.com/325#entry325comment</comments>
      <pubDate>Sat, 17 Aug 2024 21:18:13 +0900</pubDate>
    </item>
    <item>
      <title>vue vuex 란</title>
      <link>https://juntcom.tistory.com/324</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vuex는 Vue.js 애플리케이션에서 상태 관리를 중앙 집중화할 수 있도록 도와주는 공식적인 상태 관리 패턴 및 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vuex를 사용하면 애플리케이션의 상태를 중앙에서 관리하고, 이를 다양한 컴포넌트들이 공유할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vuex는 특히 규모가 큰 애플리케이션에서 컴포넌트 간의 상태 관리를 단순화하고, 일관된 상태 관리를 가능하게 합니다.&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;Vuex의 주요 개념&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;State (상태)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;는 애플리케이션의 중앙에서 관리되는 상태를 정의합니다. Vue 컴포넌트의 &lt;span&gt;data&lt;/span&gt;와 유사하지만, 여러 컴포넌트가 공유할 수 있는 중앙 집중식 상태입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866436780&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const store = createStore({
  state: {
    todos: []
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Mutations (변이)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mutations&lt;/span&gt;는 상태를 변경하는 메서드입니다. 상태를 변경할 때는 반드시 &lt;span&gt;mutations&lt;/span&gt;를 통해 변경해야 합니다. 이는 Vuex의 핵심 개념 중 하나로, 상태 변이를 추적하고 디버깅을 쉽게 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866457389&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mutations: {
  setTodos(state, todos) {
    state.todos = todos;
  },
  addTodo(state, todo) {
    state.todos.push(todo);
  },
  removeTodo(state, index) {
    state.todos.splice(index, 1);
  },
  updateTodoStatus(state, { index, completed }) {
    state.todos[index].completed = completed;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Actions (액션)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;actions&lt;/span&gt;는 &lt;span&gt;mutations&lt;/span&gt;와 유사하지만, 비동기 작업을 처리할 수 있습니다. 액션은 &lt;span&gt;mutations&lt;/span&gt;를 커밋하여 상태를 변경합니다. 예를 들어, API 호출 후 응답 데이터를 상태에 반영하는 작업을 &lt;span&gt;actions&lt;/span&gt;에서 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723866491273&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actions: {
  loadTodos({ commit }) {
    const savedTodos = localStorage.getItem('todos');
    if (savedTodos) {
      commit('setTodos', JSON.parse(savedTodos));
    }
  },
  saveTodos({ state }) {
    localStorage.setItem('todos', JSON.stringify(state.todos));
  },
  addTodo({ commit, dispatch }, todoText) {
    const newTodo = { text: todoText, completed: false };
    commit('addTodo', newTodo);
    dispatch('saveTodos');
  },
  removeTodo({ commit, dispatch }, index) {
    commit('removeTodo', index);
    dispatch('saveTodos');
  },
  toggleTodoStatus({ commit, dispatch }, payload) {
    commit('updateTodoStatus', payload);
    dispatch('saveTodos');
  }
}&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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Getters (게터)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getters&lt;/span&gt;는 상태에서 값을 가져올 때 사용됩니다. Vue 컴포넌트에서 &lt;span&gt;computed&lt;/span&gt; 속성과 유사하게 동작합니다. 상태를 가공하거나 필터링하여 반환할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866501953&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;getters: {
  todos(state) {
    return state.todos;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Store (스토어)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuex &lt;span&gt;store&lt;/span&gt;는 Vuex의 중심입니다. 상태(&lt;span&gt;state&lt;/span&gt;), 변이(&lt;span&gt;mutations&lt;/span&gt;), 액션(&lt;span&gt;actions&lt;/span&gt;), 게터(&lt;span&gt;getters&lt;/span&gt;) 등을 포함하여 애플리케이션의 전체 상태를 관리합니다. 컴포넌트는 이 스토어를 통해 중앙 상태에 접근하고, 변경할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866511249&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createStore } from 'vuex';

const store = createStore({
  state: {
    todos: []
  },
  mutations: { /* 변이들 */ },
  actions: { /* 액션들 */ },
  getters: { /* 게터들 */ }
});

export default store;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW2KOk/btsI5ZkhKNq/bYMYDkTgirismd2iCKgm61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW2KOk/btsI5ZkhKNq/bYMYDkTgirismd2iCKgm61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW2KOk/btsI5ZkhKNq/bYMYDkTgirismd2iCKgm61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW2KOk%2FbtsI5ZkhKNq%2FbYMYDkTgirismd2iCKgm61%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;1024&quot; height=&quot;768&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&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;&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;Vuex의 사용 예시&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;다음은 Vuex를 활용하여 To-Do 리스트 애플리케이션의 상태를 관리하는 방법에 대한 예시입니다.&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&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;상태 정의 및 초기화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;todos&lt;/span&gt; 배열을 상태로 정의하고, 로컬 스토리지에서 저장된 할 일 목록을 불러와 &lt;span&gt;todos&lt;/span&gt;에 저장합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866525107&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;state: {
  todos: []
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;상태 업데이트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 새로운 할 일을 추가하거나, 할 일의 상태를 변경하면 Vuex의 &lt;span&gt;mutations&lt;/span&gt;를 통해 상태가 업데이트됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866540653&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mutations: {
  addTodo(state, todo) {
    state.todos.push(todo);
  },
  updateTodoStatus(state, { index, completed }) {
    state.todos[index].completed = completed;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;액션을 통한 상태 관리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;actions&lt;/span&gt;를 통해 비동기 작업과 상태 변이를 처리합니다. 예를 들어, 로컬 스토리지에 할 일 목록을 저장하는 작업은 &lt;span&gt;actions&lt;/span&gt;에서 처리됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866557087&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actions: {
  saveTodos({ state }) {
    localStorage.setItem('todos', JSON.stringify(state.todos));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;컴포넌트에서 Vuex 상태 접근&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트에서는 &lt;span&gt;mapGetters&lt;/span&gt;, &lt;span&gt;mapActions&lt;/span&gt;를 사용하여 Vuex 상태와 메서드를 간단히 연결할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866567621&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import { mapGetters, mapActions } from 'vuex';

export default {
  computed: {
    ...mapGetters(['todos'])
  },
  methods: {
    ...mapActions(['addTodo', 'toggleTodoStatus', 'removeTodo'])
  }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;중앙 집중화된 상태 관리&lt;/b&gt;: Vuex를 사용하면 상태를 중앙에서 관리하여 컴포넌트 간의 상태 공유와 변경을 쉽게 추적할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예측 가능한 상태 관리&lt;/b&gt;: 모든 상태 변경은 &lt;span&gt;mutations&lt;/span&gt;를 통해 이루어지므로, 상태 변경을 쉽게 예측하고 디버깅할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;비동기 작업 관리&lt;/b&gt;: 비동기 작업은 &lt;span&gt;actions&lt;/span&gt;를 통해 관리되어, 복잡한 비동기 로직도 쉽게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Vue 컴포넌트와의 통합&lt;/b&gt;: &lt;span&gt;mapGetters&lt;/span&gt;와 &lt;span&gt;mapActions&lt;/span&gt;를 통해 Vue 컴포넌트와 Vuex 상태 및 메서드를 간단히 연결할 수 있습니다.&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;Vuex는 규모가 큰 애플리케이션에서 특히 유용하며, 상태를 일관되게 유지하고 컴포넌트 간의 상태 관리 복잡성을 줄여줍니다.&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue</category>
      <category>vue vuex</category>
      <category>Vuex</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/324</guid>
      <comments>https://juntcom.tistory.com/324#entry324comment</comments>
      <pubDate>Sat, 17 Aug 2024 12:51:11 +0900</pubDate>
    </item>
    <item>
      <title>vue to-do 리스트 구현 6 - vuex 구현</title>
      <link>https://juntcom.tistory.com/323</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vuex를 사용하여 To-Do 리스트 애플리케이션을 리팩토링하는 방법을 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vuex는 Vue.js 애플리케이션에서 상태 관리를 중앙에서 처리할 수 있도록 도와주는 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서는 Vuex를 사용하여 할 일 목록(&lt;span&gt;todos&lt;/span&gt;)을 중앙에서 관리하고, 컴포넌트들이 Vuex 스토어를 통해 상태를 접근하고 수정하도록 하겠습니다.&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;1. Vuex 설치&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;먼저 Vuex를 설치해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723865979085&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install vuex@next&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Vuex 스토어 설정 (store/index.js)&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;span&gt;src/store/index.js&lt;/span&gt; 파일을 생성하고, Vuex 스토어를 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866005942&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createStore } from 'vuex'

const store = createStore({
  state: {
    todos: []
  },
  mutations: {
    setTodos(state, todos) {
      state.todos = todos;
    },
    addTodo(state, todo) {
      state.todos.push(todo);
    },
    removeTodo(state, index) {
      if (index &amp;gt;= 0 &amp;amp;&amp;amp; index &amp;lt; state.todos.length) {
        state.todos.splice(index, 1);
      }
    },
    updateTodoStatus(state, { index, completed }) {
      if (index &amp;gt;= 0 &amp;amp;&amp;amp; index &amp;lt; state.todos.length) {
        state.todos[index].completed = completed;
      }
    }
  },
  actions: {
    loadTodos({ commit }) {
      const savedTodos = localStorage.getItem('todos');
      if (savedTodos) {
        commit('setTodos', JSON.parse(savedTodos));
      }
    },
    saveTodos({ state }) {
      localStorage.setItem('todos', JSON.stringify(state.todos));
    },
    addTodo({ commit, dispatch }, todoText) {
      const newTodo = { text: todoText, completed: false };
      commit('addTodo', newTodo);
      dispatch('saveTodos');
    },
    removeTodo({ commit, dispatch }, index) {
      commit('removeTodo', index);
      dispatch('saveTodos');
    },
    toggleTodoStatus({ commit, dispatch }, payload) {
      const { index, completed } = payload;
      console.log('toggleTodoStatus', index, completed);
      commit('updateTodoStatus', { index, completed });
      dispatch('saveTodos');
    }
  },
  getters: {
    todos(state) {
      return state.todos;
    }
  }
});

export default store;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Vue 애플리케이션에 Vuex 추가 (main.js)&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;span&gt;main.js&lt;/span&gt; 파일에서 Vuex 스토어를 Vue 애플리케이션에 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866026492&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createApp } from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify'
import { loadFonts } from './plugins/webfontloader'
import router from './router'  // Vue Router를 추가
import store from './store'  // Vuex 스토어를 가져옵니다.


loadFonts()

createApp(App)
  .use(vuetify)
  .use(router)  // Vue Router 사용
  .use(store)  // Vuex 스토어를 사용합니다.
  .mount('#app')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. ToDoList.vue 컴포넌트 수정&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;span&gt;ToDoList.vue&lt;/span&gt; 컴포넌트를 수정하여 Vuex 스토어에서 상태를 가져오고, 할 일을 추가 및 삭제하는 기능을 구현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866078757&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;v-container&amp;gt;
    &amp;lt;v-row&amp;gt;
      &amp;lt;v-col&amp;gt;
        &amp;lt;h1&amp;gt;Vue.js To-Do App&amp;lt;/h1&amp;gt;
        &amp;lt;AddTodo @add-todo=&quot;addTodo&quot; /&amp;gt;
        &amp;lt;v-list&amp;gt;
          &amp;lt;v-list-item
            v-for=&quot;(todo, index) in todos&quot;
            :key=&quot;index&quot;
            @click=&quot;$router.push({ name: 'ToDoDetail', params: { id: index } })&quot;
          &amp;gt;
            &amp;lt;template v-slot:prepend&amp;gt;
              &amp;lt;v-checkbox
                v-model=&quot;todo.completed&quot;
                @click.stop=&quot;toggleTodoStatus({ index, completed: !todo.completed })&quot;
                hide-details
              /&amp;gt;
            &amp;lt;/template&amp;gt;
            
            &amp;lt;v-list-item-title&amp;gt;{{ todo.text }}&amp;lt;/v-list-item-title&amp;gt;
            
            &amp;lt;template v-slot:append&amp;gt;
              &amp;lt;v-btn icon=&quot;mdi-delete&quot; size=&quot;small&quot; @click.stop=&quot;removeTodo(index)&quot;&amp;gt;
              &amp;lt;/v-btn&amp;gt;
            &amp;lt;/template&amp;gt;
          &amp;lt;/v-list-item&amp;gt;
        &amp;lt;/v-list&amp;gt;
      &amp;lt;/v-col&amp;gt;
    &amp;lt;/v-row&amp;gt;
  &amp;lt;/v-container&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import AddTodo from './AddTodo.vue'
import { mapActions, mapGetters } from 'vuex'

export default {
  name: 'ToDoList',
  components: { AddTodo },
  computed: {
    ...mapGetters(['todos'])  // Vuex에서 todos 상태를 가져옵니다.
  },
  methods: {
    ...mapActions(['addTodo', 'removeTodo', 'toggleTodoStatus']),  // Vuex 액션들을 가져옵니다.
  },
  created() {
    this.$store.dispatch('loadTodos')  // Vuex 스토어에서 할 일을 로드합니다.
  }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. TodoDetail.vue 컴포넌트 수정&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;span&gt;TodoDetail.vue&lt;/span&gt;에서 Vuex 상태를 사용하도록 수정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723866100162&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;v-container&amp;gt;
      &amp;lt;h2&amp;gt;To-Do Detail&amp;lt;/h2&amp;gt;
      &amp;lt;v-card&amp;gt;
        &amp;lt;v-card-text&amp;gt;
          &amp;lt;p&amp;gt;ID: {{ id }}&amp;lt;/p&amp;gt;
          &amp;lt;p v-if=&quot;todo&quot;&amp;gt;Text: {{ todo.text }}&amp;lt;/p&amp;gt;
          &amp;lt;p v-else&amp;gt;Todo not found&amp;lt;/p&amp;gt;
        &amp;lt;/v-card-text&amp;gt;
      &amp;lt;/v-card&amp;gt;
      &amp;lt;v-btn @click=&quot;$router.push('/')&quot;&amp;gt;Back&amp;lt;/v-btn&amp;gt;
    &amp;lt;/v-container&amp;gt;
  &amp;lt;/template&amp;gt;
  
  &amp;lt;script&amp;gt;
  import { mapGetters } from 'vuex'
  
  export default {
    props: {
      id: {
        type: String,
        required: true
      }
    },
    computed: {
      ...mapGetters(['todos']),
      todo() {
        return this.todos[this.id]
      }
    }
  }
  &amp;lt;/script&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;&lt;span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vuex 상태 관리&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;: &lt;span&gt;todos&lt;/span&gt; 배열을 중앙에서 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;mutations&lt;span&gt;: 상태를 직접적으로 변경하는 로직을 포함합니다(&lt;/span&gt;addTodo&lt;span&gt;, &lt;/span&gt;removeTodo&lt;span&gt;, &lt;/span&gt;updateTodoStatus&lt;span&gt;).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;actions&lt;/span&gt;: 비동기 작업이나 여러 변형을 처리하고, 상태를 변경하는 메서드를 포함합니다(&lt;span&gt;loadTodos&lt;/span&gt;, &lt;span&gt;saveTodos&lt;/span&gt; 등).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;ToDoList.vue&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt; 컴포넌트&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;computed&lt;/b&gt;&lt;/span&gt;: &lt;span&gt;mapGetters&lt;/span&gt;를 통해 Vuex에서 &lt;span&gt;todos&lt;/span&gt; 상태를 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;methods&lt;/b&gt;&lt;/span&gt;: &lt;span&gt;mapActions&lt;/span&gt;를 통해 Vuex 액션을 사용하여 할 일을 추가, 삭제, 상태를 변경하고, 로컬 스토리지와 동기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;created&lt;/b&gt;&lt;/span&gt;: 컴포넌트가 생성될 때 &lt;span&gt;loadTodos&lt;/span&gt; 액션을 호출하여 로컬 스토리지에서 저장된 할 일 목록을 불러옵니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;이전 블로그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; href=&quot;https://juntcom.tistory.com/321&quot;&gt;vue to-do 리스트 구현 5 - router 구현&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://juntcom.tistory.com/321&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://juntcom.tistory.com/321&lt;/a&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue</category>
      <category>vue store</category>
      <category>vue todoList</category>
      <category>vue vuex</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/323</guid>
      <comments>https://juntcom.tistory.com/323#entry323comment</comments>
      <pubDate>Sat, 17 Aug 2024 12:45:55 +0900</pubDate>
    </item>
    <item>
      <title>Vue Router란?</title>
      <link>https://juntcom.tistory.com/322</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vue Router란?&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;Vue Router는 Vue.js 애플리케이션에서 &lt;b&gt;라우팅&lt;/b&gt;을 관리하는 공식 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅이란 사용자가 특정 URL을 요청했을 때, 그 URL에 맞는 페이지나 컴포넌트를 제공하는 기능을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue Router를 사용하면 단일 페이지 애플리케이션(SPA)에서 페이지 간 네비게이션을 쉽게 구현할 수 있습니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;라우터(Router)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue Router는 &lt;span&gt;createRouter&lt;/span&gt; 함수를 사용하여 라우터 인스턴스를 생성합니다. 이 라우터 인스턴스는 애플리케이션의 경로(라우트)와 해당 경로에 연결된 컴포넌트들을 정의하는 곳입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723862847482&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createRouter, createWebHistory } from 'vue-router'
import Home from './components/Home.vue'
import About from './components/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 &lt;span&gt;routes&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;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;라우트(Route)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우트는 URL과 해당 URL에서 렌더링할 컴포넌트를 매핑하는 객체입니다. 예를 들어, &lt;span&gt;/about&lt;/span&gt; 경로에 접근하면 &lt;span&gt;About&lt;/span&gt; 컴포넌트가 렌더링됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723862858419&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]&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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;라우터 히스토리 모드(History Mode)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue Router는 기본적으로 &lt;span&gt;Hash 모드&lt;/span&gt;와 &lt;span&gt;HTML5 History 모드&lt;/span&gt;를 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Hash 모드&lt;/b&gt;: URL에 &lt;span&gt;#&lt;/span&gt;을 포함하여 경로를 구분합니다. 예: &lt;span&gt;&lt;a href=&quot;http://example.com/#/about&quot;&gt;http://example.com/#/about&lt;/a&gt;&lt;/span&gt;. 브라우저에서 URL의 해시 부분은 서버로 전달되지 않기 때문에, 서버 측에서 추가 설정이 필요 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;HTML5 History 모드&lt;/b&gt;: URL에 해시(&lt;span&gt;#&lt;/span&gt;) 없이 깔끔한 경로를 제공합니다. 예: &lt;span&gt;&lt;a href=&quot;http://example.com/about&quot;&gt;http://example.com/about&lt;/a&gt;&lt;/span&gt;. 이 모드를 사용할 경우 서버 측 설정을 통해 모든 경로를 &lt;span&gt;index.html&lt;/span&gt;로 리디렉션하도록 해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723862883959&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const router = createRouter({
  history: createWebHistory(), // HTML5 History 모드
  routes
})&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;&lt;span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;라우터 뷰(Router View)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;router-view&lt;/span&gt;는 현재 활성화된 경로에 맞는 컴포넌트를 표시하는 Vue의 내장 컴포넌트입니다. 애플리케이션에서 이 컴포넌트를 어디에 두느냐에 따라, 해당 위치에 라우터에 의해 결정된 컴포넌트가 렌더링됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723862934999&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;router-view&amp;gt;&amp;lt;/router-view&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&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;&lt;span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;라우터 링크(Router Link)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;router-link&lt;/span&gt;는 다른 페이지나 컴포넌트로 네비게이션할 수 있는 링크를 만드는 Vue의 내장 컴포넌트입니다. 이를 사용하면 HTML의 &lt;span&gt;&amp;lt;a&amp;gt;&lt;/span&gt; 태그처럼 작동하지만, SPA 특성상 페이지 전체를 다시 로드하지 않고도 URL을 변경할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723863111678&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;router-link to=&quot;/&quot;&amp;gt;Home&amp;lt;/router-link&amp;gt;
    &amp;lt;router-link to=&quot;/about&quot;&amp;gt;About&amp;lt;/router-link&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&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;&lt;span&gt;6.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;동적 라우트 매칭(Dynamic Route Matching)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 라우트 매칭은 URL 경로의 일부를 변수로 처리하여, 다양한 URL 패턴에 대해 하나의 컴포넌트를 사용할 수 있게 합니다. 예를 들어, 블로그 포스트 페이지를 생각해 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723863120808&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const routes = [
  { path: '/posts/:id', component: PostDetail }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;span&gt;:id&lt;/span&gt;는 동적 세그먼트로, 실제로는 &lt;span&gt;posts/1&lt;/span&gt;, &lt;span&gt;posts/2&lt;/span&gt; 등의 URL이 이 경로와 매칭됩니다. 이 &lt;span&gt;id&lt;/span&gt;는 &lt;span&gt;props&lt;/span&gt;나 &lt;span&gt;this.$route.params&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;span&gt;&lt;span&gt; &lt;/span&gt;7.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;네비게이션 가드(Navigation Guards)&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;전역 가드&lt;/b&gt;: 모든 경로 변경에 대해 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;라우트별 가드&lt;/b&gt;: 특정 라우트에만 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;컴포넌트 내 가드&lt;/b&gt;: 컴포넌트 내에서 정의되고, 해당 컴포넌트가 활성화될 때 적용됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723863130809&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;router.beforeEach((to, from, next) =&amp;gt; {
  if (to.path === '/protected' &amp;amp;&amp;amp; !isAuthenticated) {
    next('/login')
  } else {
    next()
  }
})&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;&lt;span&gt;8.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;중첩된 라우트(Nested Routes)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue Router는 중첩된 라우트를 지원합니다. 이는 라우트가 중첩된 컴포넌트를 렌더링할 수 있도록 해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723863140317&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const routes = [
  { 
    path: '/user/:id', 
    component: User,
    children: [
      { path: 'profile', component: UserProfile },
      { path: 'posts', component: UserPosts }
    ]
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우, &lt;span&gt;UserProfile&lt;/span&gt;이나 &lt;span&gt;UserPosts&lt;/span&gt; 컴포넌트는 &lt;span&gt;User&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;b&gt;결론&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;Vue Router는 Vue.js 애플리케이션에서 페이지 간 이동을 관리하고, URL을 통해 상태를 관리할 수 있는 강력한 도구입니다. 이를 통해 SPA에서의 복잡한 네비게이션 구조를 간편하게 관리할 수 있으며, 사용자 경험을 향상시키는 데 매우 유용합니다. Vue Router를 잘 이해하고 사용하면, Vue.js 애플리케이션의 네비게이션과 상태 관리를 훨씬 더 효율적으로 수행할 수 있습니다.&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/322</guid>
      <comments>https://juntcom.tistory.com/322#entry322comment</comments>
      <pubDate>Sat, 17 Aug 2024 11:52:48 +0900</pubDate>
    </item>
    <item>
      <title>vue to-do 리스트 구현 5 - router 구현</title>
      <link>https://juntcom.tistory.com/321</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue Router는 Vue.js 애플리케이션에서 &lt;b&gt;라우팅&lt;/b&gt;을 관리하는 공식 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅이란 사용자가 특정 URL을 요청했을 때, 그 URL에 맞는 페이지나 컴포넌트를 제공하는 기능을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue Router를 사용하면 단일 페이지 애플리케이션(SPA)에서 페이지 간 네비게이션을 쉽게 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;1. Vue Router 설치&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;먼저 Vue Router를 프로젝트에 설치해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723859410776&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install vue-router@next&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어는 Vue 3용 Vue Router를 설치합니다. 설치가 완료되면 Vue Router를 프로젝트에 추가할 수 있습니다.&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;2. 라우터 설정 (router/index.js)&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;span&gt;src&lt;/span&gt; 디렉토리 아래에 &lt;span&gt;router&lt;/span&gt; 폴더를 만들고, 그 안에 &lt;span&gt;index.js&lt;/span&gt; 파일을 생성합니다. 이 파일에서 라우터를 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723859429419&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createRouter, createWebHistory } from 'vue-router'
import ToDoList from '../components/ToDoList.vue'
import TodoDetail from '../components/TodoDetail.vue'

const routes = [
  { path: '/', name: 'Home', component: ToDoList },
  { path: '/todo/:id', name: 'TodoDetail', component: TodoDetail, props: true }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;createRouter&lt;/b&gt;&lt;/span&gt;: Vue 3에서 라우터를 생성하는 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;createWebHistory&lt;/b&gt;&lt;/span&gt;: HTML5의 &lt;span&gt;history&lt;/span&gt; 모드를 사용하는 함수로, 브라우저의 URL을 깔끔하게 유지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;routes&lt;/b&gt;&lt;/span&gt;: 경로와 그에 연결된 컴포넌트를 정의하는 배열입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;props: true&lt;/b&gt;&lt;/span&gt;: 라우트 파라미터를 컴포넌트의 &lt;span&gt;props&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;b&gt;3. 라우터를 Vue 애플리케이션에 추가 (main.js)&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;span&gt;main.js&lt;/span&gt; 파일에서 Vue 애플리케이션에 라우터를 추가해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723860509168&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createApp } from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify'
import { loadFonts } from './plugins/webfontloader'
import router from './router'  // Vue Router를 가져옵니다.

loadFonts()

createApp(App)
  .use(vuetify)
  .use(router)  // Vue Router를 사용합니다.
  .mount('#app')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;span&gt;router&lt;/span&gt;를 Vue 애플리케이션에 &lt;span&gt;use&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;b&gt;4. App.vue에서 라우터 뷰 설정&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;span&gt;App.vue&lt;/span&gt; 파일에서 &lt;span&gt;router-view&lt;/span&gt; 컴포넌트를 사용하여 현재 라우터에 맞는 컴포넌트를 렌더링할 수 있도록 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723860529816&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;v-app&amp;gt;
    &amp;lt;v-container&amp;gt;
      &amp;lt;router-view /&amp;gt;  &amp;lt;!-- 라우터가 렌더링할 컴포넌트가 여기에 표시됩니다. --&amp;gt;
    &amp;lt;/v-container&amp;gt;
  &amp;lt;/v-app&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: 'App',
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;router-view&lt;/b&gt;&lt;/span&gt;: Vue Router에서 제공하는 컴포넌트로, 현재 라우트에 맞는 컴포넌트를 렌더링합니다.&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;5. 페이지 컴포넌트 작성&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;span&gt;ToDoList.vue&lt;/span&gt;와 &lt;span&gt;TodoDetail.vue&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;b&gt;ToDoList.vue&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723860553108&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;v-container&amp;gt;
    &amp;lt;v-row&amp;gt;
      &amp;lt;v-col&amp;gt;
        &amp;lt;h1&amp;gt;Vue.js To-Do App&amp;lt;/h1&amp;gt;
        &amp;lt;AddTodo @add-todo=&quot;addTodo&quot; /&amp;gt;
        &amp;lt;v-list&amp;gt;
          &amp;lt;v-list-item
            v-for=&quot;(todo, index) in todos&quot;
            :key=&quot;index&quot;
            @click=&quot;$router.push({ name: 'ToDoDetail', params: { id: index } })&quot;
          &amp;gt;
            &amp;lt;template v-slot:prepend&amp;gt;
              &amp;lt;v-checkbox
                v-model=&quot;todo.completed&quot;
                @change=&quot;saveTodos&quot;
                hide-details
              /&amp;gt;
            &amp;lt;/template&amp;gt;
            
            &amp;lt;v-list-item-title&amp;gt;{{ todo.text }}&amp;lt;/v-list-item-title&amp;gt;
            
            &amp;lt;template v-slot:append&amp;gt;
              &amp;lt;v-btn icon=&quot;mdi-delete&quot; size=&quot;small&quot; @click.stop=&quot;removeTodo(index)&quot;&amp;gt;
              &amp;lt;/v-btn&amp;gt;
            &amp;lt;/template&amp;gt;
          &amp;lt;/v-list-item&amp;gt;
        &amp;lt;/v-list&amp;gt;
      &amp;lt;/v-col&amp;gt;
    &amp;lt;/v-row&amp;gt;
  &amp;lt;/v-container&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import AddTodo from './AddTodo.vue'

export default {
  name: 'ToDoList',
  components: { AddTodo },
  data() {
    return {
      todos: []
    }
  },
  mounted() {
    const savedTodos = localStorage.getItem('todos')
    if (savedTodos) {
      this.todos = JSON.parse(savedTodos)
    }
  },
  methods: {
    addTodo(newTodo) {
      this.todos.push({ text: newTodo, completed: false })
      this.saveTodos()
    },
    removeTodo(index) {
      this.todos.splice(index, 1)
      this.saveTodos()
    },
    saveTodos() {
      localStorage.setItem('todos', JSON.stringify(this.todos))
    }
  }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;$router.push&lt;/b&gt;&lt;/span&gt;: 사용자가 클릭할 때 특정 라우트로 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;params: { id: index }&lt;/b&gt;&lt;span&gt;: 라우트 파라미터로 &lt;/span&gt;id&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ToDoDetail.vue&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723860592431&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;v-container&amp;gt;
    &amp;lt;h2&amp;gt;To-Do Detail&amp;lt;/h2&amp;gt;
    &amp;lt;v-card&amp;gt;
      &amp;lt;v-card-text&amp;gt;
        &amp;lt;p&amp;gt;ID: {{ id }}&amp;lt;/p&amp;gt;
      &amp;lt;/v-card-text&amp;gt;
    &amp;lt;/v-card&amp;gt;
    &amp;lt;v-btn @click=&quot;$router.push('/')&quot;&amp;gt;Back&amp;lt;/v-btn&amp;gt;
  &amp;lt;/v-container&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  props: {
    id: {
      type: String,
      required: true
    }
  }
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;props: { id }&lt;/b&gt;&lt;/span&gt;: 라우트 파라미터로 전달된 &lt;span&gt;id&lt;/span&gt;를 컴포넌트에서 사용합니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;이전 블로그&lt;a style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot; href=&quot;https://juntcom.tistory.com/319&quot;&gt;&lt;br /&gt;vue to-do 리스트 구현 4 - vuetify 적용&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://juntcom.tistory.com/319&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://juntcom.tistory.com/319&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue</category>
      <category>vue router</category>
      <category>vue todo</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/321</guid>
      <comments>https://juntcom.tistory.com/321#entry321comment</comments>
      <pubDate>Sat, 17 Aug 2024 11:28:10 +0900</pubDate>
    </item>
    <item>
      <title>AWS DMS 를 통한 EC2 -&amp;gt; RDS 데이터 마이그레이션</title>
      <link>https://juntcom.tistory.com/320</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. AWS DMS 설정 준비&lt;/b&gt;&lt;/h3&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&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;RDS 인스턴스 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;이미 RDS 인스턴스가 생성되어 있다면 이 단계를 건너뛰세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;RDS에서 MySQL 인스턴스를 생성합니다. 인스턴스 생성 과정에서 적절한 VPC, 서브넷, 보안 그룹 설정을 확인해야 합니다.&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&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;IAM 역할 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;AWS DMS에 필요한 IAM 역할을 생성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;필요한 권한은 &lt;/span&gt;AmazonDMSVPCManagementRole&lt;span&gt;, &lt;/span&gt;AmazonDMSCloudWatchLogsRole&lt;span&gt;, &lt;/span&gt;AmazonDMSRedshiftS3Role&lt;span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. AWS DMS 구성&lt;/b&gt;&lt;/h3&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&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Replication Instance 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;AWS Management Console에서 DMS 서비스로 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Replication Instances&lt;/span&gt;에서 새로운 레플리케이션 인스턴스를 생성합니다. -&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Replication instances&lt;/b&gt;를 선택한 후 &lt;b&gt;Create replication instance&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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;인스턴스 식별자: &lt;/span&gt;dms-replication-instance&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;인스턴스 클래스: 요구 사항에 맞는 인스턴스 유형 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;할당된 저장소: 요구 사항에 맞는 스토리지 크기 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;VPC: EC2와 RDS가 있는 VPC 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;EC2와 RDS가 있는 동일한 VPC에 레플리케이션 인스턴스를 배치하고, 퍼블릭 액세스 여부를 선택합니다.&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&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Endpoint 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;DMS 콘솔에서 &lt;span&gt;Endpoints&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;b&gt;Source Endpoint (EC2 MySQL)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Endpoint type: Source&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Engine type: MySQL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Server name: EC2 인스턴스의 퍼블릭 IP 주소 또는 도메인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Port: MySQL 기본 포트 (3306)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Username: MySQL 사용자 이름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Password: MySQL 비밀번호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Database name: MySQL 데이터베이스 이름 (특정 데이터베이스만 마이그레이션하려면)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Target Endpoint (RDS MySQL)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Endpoint type: Target&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Engine type: MySQL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Server name: RDS 인스턴스의 엔드포인트 (콘솔에서 확인 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Port: MySQL 기본 포트 (3306)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Username: RDS MySQL 사용자 이름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Password: RDS MySQL 비밀번호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Database name: MySQL 데이터베이스 이름&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&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Migration Task 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Database migration tasks&lt;/span&gt;에서 새 마이그레이션 태스크를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Source와 Target Endpoint를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Migration type을 &lt;/span&gt;Migrate existing data&lt;span&gt; 또는 &lt;/span&gt;Migrate existing data and replicate ongoing changes&lt;span&gt;로 선택합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Migrate existing data&lt;/span&gt;: 기존 데이터만 마이그레이션합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;Migrate existing data and replicate ongoing changes&lt;span&gt;: 기존 데이터 마이그레이션과 동시에 변경 사항을 실시간으로 복제합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;테이블 매핑 설정에서 모든 스키마와 테이블을 포함하도록 설정합니다 (&lt;span&gt;%&lt;/span&gt; 사용).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;태스크 설정에서 데이터 검증, LOB 모드 등의 옵션을 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Migration Task 시작&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;태스크를 생성한 후 &lt;span&gt;Start&lt;/span&gt; 버튼을 눌러 태스크를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Table mappings&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Schema&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mysql&lt;/span&gt;, &lt;span&gt;information_schema&lt;/span&gt;, &lt;span&gt;performance_schema&lt;/span&gt;, &lt;span&gt;sys&lt;/span&gt; 등 시스템 데이터베이스를 제외하려면 각각의 스키마 이름을 &lt;span&gt;Schema&lt;/span&gt; 필드에 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Source name&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;각 스키마에 대해 시스템 테이블이 위치한 스키마 이름을 입력합니다. 예를 들어, &lt;span&gt;mysql&lt;/span&gt; 스키마를 제외하려면 &lt;span&gt;mysql&lt;/span&gt;을 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Source table name&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;테이블 이름을 제외하려면, &lt;span&gt;%&lt;/span&gt;를 사용하여 모든 테이블을 선택할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Action&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Exclude&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;span&gt;db&lt;/span&gt; 데이터베이스만 가져오도록 DMS 테이블 매핑 규칙을 설정하려면, 다음과 같이 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Schema 필드&lt;/b&gt;: &lt;span&gt;db&lt;/span&gt; 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Source table name&lt;/b&gt;: &lt;span&gt;%&lt;/span&gt; (모든 테이블을 의미)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;Action&lt;/b&gt;: &lt;/span&gt;Include&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;위 설정으로 DMS가 &lt;span&gt;db&lt;/span&gt; 데이터베이스의 모든 테이블을 포함하여 마이그레이션하도록 구성됩니다. 다른 데이터베이스는 이 설정으로 제외됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db 라는 네임스페이스의 db 만 가져옵니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723812016615&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;rules&quot;: [
    {
      &quot;rule-type&quot;: &quot;selection&quot;,
      &quot;rule-id&quot;: &quot;1&quot;,
      &quot;rule-name&quot;: &quot;include-db&quot;,
      &quot;object-locator&quot;: {
        &quot;schema-name&quot;: &quot;db&quot;,
        &quot;table-name&quot;: &quot;%&quot;
      },
      &quot;rule-action&quot;: &quot;include&quot;,
      &quot;filters&quot;: []
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Mysql 타임존 수정 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. MySQL 파라미터 그룹 수정&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;RDS의 MySQL 인스턴스에서 타임존을 변경하려면 먼저 파라미터 그룹에서 &lt;span&gt;time_zone&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;파라미터 그룹 생성 또는 수정&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;AWS Management Console에 로그인한 후, RDS 콘솔로 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;왼쪽 탐색 창에서 **&amp;ldquo;Parameter groups&amp;rdquo;**를 클릭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;새로운 파라미터 그룹을 생성하거나 기존의 파라미터 그룹을 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;파라미터 그룹에서 &lt;span&gt;time_zone&lt;/span&gt; 파라미터를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;time_zone&lt;/span&gt; 파라미터 값을 원하는 타임존으로 설정합니다 (예: &lt;span&gt;Asia/Seoul&lt;/span&gt;, &lt;span&gt;UTC&lt;/span&gt;, &lt;span&gt;Europe/London&lt;/span&gt; 등).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;파라미터 그룹 적용&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;RDS 인스턴스에 해당 파라미터 그룹을 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;파라미터 그룹을 적용한 후에는 RDS 인스턴스를 재부팅해야 새로운 타임존이 적용됩니다&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;2. RDS 인스턴스에 파라미터 그룹 적용&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;RDS 인스턴스 수정&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;RDS 콘솔에서 타임존을 변경할 RDS 인스턴스를 선택한 후 **&amp;ldquo;Modify&amp;rdquo;**를 클릭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DB parameter group&lt;/span&gt;에서 새로 생성한 파라미터 그룹을 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;인스턴스를 수정한 후, **&amp;ldquo;Apply immediately&amp;rdquo;**를 선택하여 변경 사항을 즉시 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;재부팅 필요&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;파라미터 그룹이 적용된 후, RDS 인스턴스를 재부팅해야 변경된 타임존이 적용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.&lt;/p&gt;</description>
      <category>인프라/AWS</category>
      <category>AWS</category>
      <category>AWS DMS</category>
      <category>dms</category>
      <category>rds timezone</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/320</guid>
      <comments>https://juntcom.tistory.com/320#entry320comment</comments>
      <pubDate>Fri, 16 Aug 2024 21:53:28 +0900</pubDate>
    </item>
    <item>
      <title>vue to-do 리스트 구현 4 - vuetify 적용</title>
      <link>https://juntcom.tistory.com/319</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vuetify는 Vue.js 기반의 Material Design 컴포넌트 프레임워크로, 반응형 UI를 쉽게 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 To-Do 리스트 애플리케이션에 Vuetify를 적용하여 스타일을 개선하고, Vuetify의 컴포넌트를 활용하여 UI를 향상시켜보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Vuetify 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Vuetify를 프로젝트에 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;터미널에서 Vuetify 설치&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1723789883394&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vue add vuetify&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VmJ00/btsI6dPoBuI/wJkGNmi3Vdpsx0ShQXWb60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VmJ00/btsI6dPoBuI/wJkGNmi3Vdpsx0ShQXWb60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VmJ00/btsI6dPoBuI/wJkGNmi3Vdpsx0ShQXWb60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVmJ00%2FbtsI6dPoBuI%2FwJkGNmi3Vdpsx0ShQXWb60%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;613&quot; height=&quot;142&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;vue add vuetify 를 할 경우 4가지 선택지가 나오는데&lt;br /&gt;vuetify2 를 할 것인지, 그리고 vue-cli 를 할 것인지에 대한 선택지가 나오는데&lt;br /&gt;vuetify3 및 vite 라는 빌드툴을 사용하기로 했습니다. (최신버젼이기 때문)&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vuetify 3 + Vite의 장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;A. &lt;/span&gt;&lt;/span&gt;&lt;b&gt;빠른 빌드 속도&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Vite&lt;/b&gt;는 빌드 도구로서 매우 빠른 속도를 자랑합니다. 특히 개발 중에 실시간으로 코드 변경 사항을 반영하는 속도가 빠르기 때문에, 대규모 프로젝트에서 매우 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vite는 기본적으로 ES 모듈을 기반으로 작동하므로, 빌드 과정이 간소화되고 빠릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;B.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;최신 기능&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Vuetify 3&lt;/b&gt;는 Vuetify의 최신 버전으로, 최신 Vue.js 기능들을 활용할 수 있습니다. 특히, Composition API와 함께 사용할 때 더욱 강력한 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify 3은 새로운 디자인 시스템과 개선된 컴포넌트 구조를 제공하여 UI 개발에 더 나은 경험을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;C.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;미래 지향적&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vite와 Vuetify 3은 모두 Vue.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;&lt;b&gt;Vuetify 3 + Vite의 단점 및 고려사항&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;A.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;호환성 문제&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Preview 단계&lt;/b&gt;: Vuetify 3은 아직 preview 단계이므로, 일부 기능이 불안정할 수 있으며, 공식적으로 모든 기능이 완전히 지원되지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;플러그인 호환성&lt;/b&gt;: Vuetify 3과 Vite를 사용하면, 기존의 일부 Vue.js 플러그인 또는 Vuetify 2에서 사용하던 플러그인들이 호환되지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;B.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;러닝 커브&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vite는 Webpack과는 다른 접근 방식을 취하기 때문에, 기존에 Webpack에 익숙한 개발자라면 Vite의 설정과 동작 방식에 적응하는 데 약간의 시간이 걸릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C.&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;안정성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vite와 Vuetify 3의 조합은 최신 기술 스택이기 때문에, 프로젝트에 사용하기 전에 충분한 테스트가 필요할 수 있습니다. 대규모 프로젝트나 프로덕션 환경에서 사용할 때는 사전 검토가 중요합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;프로젝트 실행&lt;/b&gt;:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료된 후, 프로젝트를 다시 실행하여 Vuetify가 정상적으로 적용되었는지 확인합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723789907432&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run dev&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;&lt;b&gt;1). App.vue에서 Vuetify 레이아웃 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vuetify의 기본 레이아웃 컴포넌트인 &lt;span&gt;v-app&lt;/span&gt;, &lt;span&gt;v-container&lt;/span&gt;, &lt;span&gt;v-row&lt;/span&gt;, &lt;span&gt;v-col&lt;/span&gt; 등을 사용하여 레이아웃을 설정합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723790138907&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;v-app&amp;gt;
    &amp;lt;v-container&amp;gt;
      &amp;lt;v-row&amp;gt;
        &amp;lt;v-col&amp;gt;
          &amp;lt;h1 class=&quot;text-center&quot;&amp;gt;Vue.js To-Do App&amp;lt;/h1&amp;gt;
          &amp;lt;AddTodo @add-todo=&quot;addTodo&quot; /&amp;gt;
          &amp;lt;ToDoList :todos=&quot;todos&quot; @remove-todo=&quot;removeTodo&quot; @save-todos=&quot;saveTodos&quot; /&amp;gt;
        &amp;lt;/v-col&amp;gt;
      &amp;lt;/v-row&amp;gt;
    &amp;lt;/v-container&amp;gt;
  &amp;lt;/v-app&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import AddTodo from './components/AddTodo.vue';
import ToDoList from './components/ToDoList.vue';

export default {
  name: 'App',
  components: {
    AddTodo,
    ToDoList
  },
  data() {
    return {
      todos: []
    };
  },
  mounted() {
    const savedTodos = localStorage.getItem('todos');
    if (savedTodos) {
      this.todos = JSON.parse(savedTodos);
    }
  },
  methods: {
    addTodo(newTodo) {
      this.todos.push({ text: newTodo, completed: false });
      this.saveTodos();
    },
    removeTodo(index) {
      this.todos.splice(index, 1);
      this.saveTodos();
    },
    saveTodos() {
      localStorage.setItem('todos', JSON.stringify(this.todos));
    }
  }
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
.text-center {
  text-align: center;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. AddTodo.vue에 Vuetify 컴포넌트 적용&lt;/b&gt;&lt;/h3&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&gt;AddTodo.vue&lt;/span&gt; 컴포넌트에서 Vuetify의 입력 및 버튼 컴포넌트를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723790241161&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;v-row class=&quot;mb-3&quot;&amp;gt;
      &amp;lt;v-col&amp;gt;
        &amp;lt;v-text-field
          v-model=&quot;newTodo&quot;
          label=&quot;Add a new to-do&quot;
          outlined
          dense
          @keyup.enter=&quot;submitTodo&quot;
        /&amp;gt;
      &amp;lt;/v-col&amp;gt;
      &amp;lt;v-col cols=&quot;auto&quot;&amp;gt;
        &amp;lt;v-btn color=&quot;primary&quot; @click=&quot;submitTodo&quot;&amp;gt;Add&amp;lt;/v-btn&amp;gt;
      &amp;lt;/v-col&amp;gt;
    &amp;lt;/v-row&amp;gt;
  &amp;lt;/template&amp;gt;
  
  &amp;lt;script&amp;gt;
  export default {
    name: 'AddTodo',
    data() {
      return {
        newTodo: ''
      };
    },
    methods: {
      submitTodo() {
        if (this.newTodo.trim()) {
          this.$emit('add-todo', this.newTodo);
          this.newTodo = '';
        }
      }
    }
  };
  &amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. TodoList.vue에 Vuetify 컴포넌트 적용&lt;/b&gt;&lt;/h3&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&gt;TodoList.vue&lt;/span&gt; 컴포넌트에서도 Vuetify의 리스트, 체크박스, 버튼 컴포넌트를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723790231846&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;v-list&amp;gt;
    &amp;lt;v-list-item
      v-for=&quot;(todo, index) in todos&quot;
      :key=&quot;index&quot;
      :class=&quot;{ 'completed': todo.completed }&quot;
    &amp;gt;
      &amp;lt;v-row align=&quot;center&quot; no-gutters&amp;gt;
        &amp;lt;v-col cols=&quot;auto&quot; class=&quot;mr-3&quot;&amp;gt;
          &amp;lt;v-checkbox
            v-model=&quot;todo.completed&quot;
            @change=&quot;saveTodos&quot;
            hide-details
          /&amp;gt;
        &amp;lt;/v-col&amp;gt;
        &amp;lt;v-col&amp;gt;
          &amp;lt;v-list-item-content&amp;gt;
            &amp;lt;v-list-item-title&amp;gt;{{ todo.text }}&amp;lt;/v-list-item-title&amp;gt;
          &amp;lt;/v-list-item-content&amp;gt;
        &amp;lt;/v-col&amp;gt;
        &amp;lt;v-col cols=&quot;auto&quot;&amp;gt;
          &amp;lt;v-btn icon small @click=&quot;removeTodo(index)&quot;&amp;gt;
            &amp;lt;v-icon&amp;gt;mdi-delete&amp;lt;/v-icon&amp;gt;
          &amp;lt;/v-btn&amp;gt;
        &amp;lt;/v-col&amp;gt;
      &amp;lt;/v-row&amp;gt;
    &amp;lt;/v-list-item&amp;gt;
  &amp;lt;/v-list&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: 'TodoList',
  props: {
    todos: {
      type: Array,
      required: true
    }
  },
  methods: {
    removeTodo(index) {
      this.$emit('remove-todo', index);
    },
    saveTodos() {
      this.$emit('save-todos');
    }
  }
};
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
.completed .v-list-item__title {
  text-decoration: line-through;
  color: grey;
}
&amp;lt;/style&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;App.vue&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify의 레이아웃 컴포넌트(&lt;span&gt;v-app&lt;/span&gt;, &lt;span&gt;v-container&lt;/span&gt;, &lt;span&gt;v-row&lt;/span&gt;, &lt;span&gt;v-col&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;b&gt;AddTodo.vue&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify의 &lt;span&gt;v-text-field&lt;/span&gt;와 &lt;span&gt;v-btn&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;b&gt;TodoList.vue&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vuetify의 &lt;span&gt;v-list&lt;/span&gt;, &lt;span&gt;v-checkbox&lt;/span&gt;, &lt;span&gt;v-btn&lt;/span&gt;, &lt;span&gt;v-icon&lt;/span&gt;을 사용하여 할 일 리스트와 삭제 버튼을 구현합니다.&lt;br /&gt;&lt;br /&gt;이전 블로그&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://juntcom.tistory.com/318&quot;&gt;vue to-do 리스트 구현 3 - 컴포넌트 분리 및 emit 실습&lt;br /&gt;&lt;/a&gt;&lt;/b&gt;&lt;a href=&quot;https://juntcom.tistory.com/318&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://juntcom.tistory.com/318&lt;/a&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue</category>
      <category>vuetify</category>
      <category>vuetify 적용</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/319</guid>
      <comments>https://juntcom.tistory.com/319#entry319comment</comments>
      <pubDate>Fri, 16 Aug 2024 15:51:45 +0900</pubDate>
    </item>
    <item>
      <title>vue to-do 리스트 구현 3 - 컴포넌트 분리 및 emit 실습</title>
      <link>https://juntcom.tistory.com/318</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;To-Do 리스트 애플리케이션에서 &lt;span&gt;add&lt;/span&gt; 컴포넌트와 &lt;span&gt;list&lt;/span&gt; 컴포넌트를 분리하는 작업을 하겠습니다. &lt;br /&gt;컴포넌트를 분리하면 코드의 모듈화가 가능해지고, 각 컴포넌트가 자신의 역할에만 집중할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 Vue.js의 컴포넌트 간 데이터 전달(&lt;span&gt;props&lt;/span&gt;와 &lt;span&gt;events&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;b&gt;1. AddTodo.vue 컴포넌트 생성&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;AddTodo.vue&lt;/b&gt;&lt;span&gt;&lt;b&gt; 파일 생성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;src/components&lt;span&gt; 디렉토리 안에 &lt;/span&gt;AddTodo.vue&lt;span&gt; 파일을 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;AddTodo.vue&lt;/b&gt;&lt;span&gt;&lt;b&gt; 초기 코드 작성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;이 컴포넌트는 새로운 할 일을 입력하고 추가하는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723784905996&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;input v-model=&quot;newTodo&quot; placeholder=&quot;Add a new to-do&quot; @keyup.enter=&quot;submitTodo&quot; /&amp;gt;
    &amp;lt;button @click=&quot;submitTodo&quot;&amp;gt;Add&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: 'AddTodo',
  data() {
    return {
      newTodo: ''
    };
  },
  methods: {
    submitTodo() {
      if (this.newTodo.trim()) {
        this.$emit('add-todo', this.newTodo);
        this.newTodo = ''; // 입력 필드 초기화
      }
    }
  }
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;기능 설명&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;newTodo&lt;/span&gt;라는 로컬 데이터 속성을 가지고 있으며, 사용자가 할 일 텍스트를 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;사용자가 &lt;span&gt;Enter&lt;/span&gt; 키를 누르거나 &amp;ldquo;Add&amp;rdquo; 버튼을 클릭하면 &lt;span&gt;submitTodo&lt;/span&gt; 메서드가 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;submitTodo&lt;/span&gt; 메서드는 &lt;span&gt;add-todo&lt;/span&gt; 이벤트를 상위 컴포넌트로 발송(&lt;span&gt;emit&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;b&gt;2. TodoList.vue 컴포넌트 생성&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;TodoList.vue&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt; 파일 생성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;src/components&lt;span&gt; 디렉토리 안에 &lt;/span&gt;TodoList.vue&lt;span&gt; 파일을 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;TodoList.vue&lt;/b&gt;&lt;span&gt;&lt;b&gt; 초기 코드 작성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;이 컴포넌트는 할 일 리스트를 렌더링하고, 할 일 항목을 삭제하는 역할을 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723784932580&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;ul&amp;gt;
    &amp;lt;li v-for=&quot;(todo, index) in todos&quot; :key=&quot;index&quot;&amp;gt;
      &amp;lt;input type=&quot;checkbox&quot; v-model=&quot;todo.completed&quot; @change=&quot;saveTodos&quot; /&amp;gt;
      &amp;lt;span :class=&quot;{ completed: todo.completed }&quot;&amp;gt;{{ todo.text }}&amp;lt;/span&amp;gt;
      &amp;lt;button @click=&quot;removeTodo(index)&quot;&amp;gt;Delete&amp;lt;/button&amp;gt;
    &amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: 'ToDoList',
  props: {
    todos: {
      type: Array,
      required: true
    }
  },
  methods: {
    removeTodo(index) {
      this.$emit('remove-todo', index);
    },
    saveTodos() {
      this.$emit('save-todos');
    }
  }
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
.completed {
  text-decoration: line-through;
  color: grey;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;기능 설명&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;todos&lt;/span&gt; 배열을 &lt;span&gt;props&lt;/span&gt;로 받아와서 리스트를 렌더링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;각 할 일 항목의 체크박스 상태를 변경하면 &lt;span&gt;saveTodos&lt;/span&gt; 메서드가 호출되어, 상위 컴포넌트로 상태 저장 이벤트(&lt;span&gt;save-todos&lt;/span&gt;)를 발송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&amp;ldquo;Delete&amp;rdquo; 버튼을 클릭하면 해당 항목을 삭제하기 위한 &lt;span&gt;remove-todo&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;b&gt;3. App.vue 파일 수정&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;AddTodo&lt;/b&gt;&lt;span&gt;&lt;b&gt;와 &lt;/b&gt;&lt;/span&gt;&lt;b&gt;TodoList&lt;/b&gt;&lt;span&gt;&lt;b&gt; 컴포넌트 가져오기&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;App.vue&lt;/span&gt; 파일에서 새로 생성한 두 컴포넌트를 가져오고, 이를 사용해 To-Do 리스트를 완성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723784996164&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;h1&amp;gt;Vue.js To-Do App&amp;lt;/h1&amp;gt;
  &amp;lt;add-todo @add-todo=&quot;addTodo&quot; /&amp;gt;
  &amp;lt;to-do-list :todos=&quot;todos&quot; @remove-todo=&quot;removeTodo&quot; @save-todos=&quot;saveTodos&quot; /&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import AddTodo from './components/AddTodo.vue';
import ToDoList from './components/ToDoList.vue';


export default {
  name: 'App',
  components: {
    AddTodo,
    ToDoList
  },
  data() {
    return {
      todos: []
    };
  },
  mounted() {
    const savedTodos = localStorage.getItem('todos');
    if (savedTodos) {
      this.todos = JSON.parse(savedTodos);
    }
  },
  methods: {
    addTodo(newTodo) {
      this.todos.push({ text: newTodo, completed: false });
      this.saveTodos();
    },
    removeTodo(index) {
      this.todos.splice(index, 1);
      this.saveTodos();
    },
    saveTodos() {
      localStorage.setItem('todos', JSON.stringify(this.todos));
    }
  }
}
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
&amp;lt;/style&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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;기능 설명&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AddTodo&lt;/span&gt; 컴포넌트에서 발송된 &lt;span&gt;add-todo&lt;/span&gt; 이벤트를 받아 &lt;span&gt;addTodo&lt;/span&gt; 메서드를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TodoList&lt;/span&gt; 컴포넌트에서 발송된 &lt;span&gt;remove-todo&lt;/span&gt;와 &lt;span&gt;save-todos&lt;/span&gt; 이벤트를 처리하여 할 일 리스트를 관리하고 로컬 스토리지에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;로컬 스토리지에서 할 일 목록을 불러오고, 이를 &lt;span&gt;todos&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;/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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;AddTodo.vue&lt;/b&gt;&lt;/span&gt;: 사용자가 할 일을 입력하고 추가하는 기능을 담당하는 컴포넌트.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;TodoList.vue&lt;/b&gt;&lt;/span&gt;: 할 일 목록을 렌더링하고, 항목을 삭제하거나 완료 상태를 관리하는 컴포넌트.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;App.vue&lt;/b&gt;&lt;/span&gt;: 상위 컴포넌트로서, &lt;span&gt;AddTodo&lt;/span&gt;와 &lt;span&gt;TodoList&lt;/span&gt; 컴포넌트를 포함하고, 할 일 데이터를 중앙에서 관리하는 역할을 수행.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;이전블로그&amp;nbsp;&lt;br /&gt;로컬스토리지 통한 to-do list 구현&lt;br /&gt;&lt;a href=&quot;https://juntcom.tistory.com/317&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://juntcom.tistory.com/317&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue</category>
      <category>vue emit</category>
      <category>vue 컴포넌트화</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/318</guid>
      <comments>https://juntcom.tistory.com/318#entry318comment</comments>
      <pubDate>Fri, 16 Aug 2024 14:10:59 +0900</pubDate>
    </item>
    <item>
      <title>vuejs to-do 리스트 구현 2 - 로컬스토리지</title>
      <link>https://juntcom.tistory.com/317</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;To-Do 리스트 항목을 로컬 스토리지에 저장하고, 페이지 새로고침 후에도 할 일 목록이 유지되도록 기능을 추가하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 &lt;span&gt;ToDoList.vue&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;b&gt;수정된 ToDoList.vue 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723778483559&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;To-Do List&amp;lt;/h2&amp;gt;
    &amp;lt;input v-model=&quot;newTodo&quot; placeholder=&quot;Add a new to-do&quot; @keyup.enter=&quot;addTodo&quot; /&amp;gt;
    &amp;lt;button @click=&quot;addTodo&quot;&amp;gt;Add&amp;lt;/button&amp;gt;

    &amp;lt;ul&amp;gt;
      &amp;lt;li v-for=&quot;(todo, index) in todos&quot; :key=&quot;index&quot;&amp;gt;
        &amp;lt;input type=&quot;checkbox&quot; v-model=&quot;todo.completed&quot; @change=&quot;saveTodos&quot; /&amp;gt;
        &amp;lt;span :class=&quot;{ completed: todo.completed }&quot;&amp;gt;{{ todo.text }}&amp;lt;/span&amp;gt;
        &amp;lt;button @click=&quot;removeTodo(index)&quot;&amp;gt;Delete&amp;lt;/button&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: 'ToDoList',
  data() {
    return {
      newTodo: '',
      todos: []
    };
  },
  mounted() {
    // 컴포넌트가 마운트될 때 로컬 스토리지에서 할 일 목록을 불러옵니다.
    const savedTodos = localStorage.getItem('todos');
    if (savedTodos) {
      this.todos = JSON.parse(savedTodos);
    }
  },
  methods: {
    addTodo() {
      if (this.newTodo.trim()) {
        this.todos.push({ text: this.newTodo, completed: false });
        this.newTodo = '';
        this.saveTodos(); // 새로운 할 일 추가 후 로컬 스토리지에 저장
      }
    },
    removeTodo(index) {
      this.todos.splice(index, 1);
      this.saveTodos(); // 할 일 삭제 후 로컬 스토리지에 저장
    },
    saveTodos() {
      // 현재 할 일 목록을 로컬 스토리지에 저장합니다.
      localStorage.setItem('todos', JSON.stringify(this.todos));
    }
  }
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
.completed {
  text-decoration: line-through;
  color: grey;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;로컬 스토리지에서 할 일 목록 불러오기&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mounted()&lt;/span&gt; 훅에서 컴포넌트가 마운트될 때 &lt;span&gt;localStorage.getItem('todos')&lt;/span&gt;를 통해 로컬 스토리지에 저장된 할 일 목록을 불러옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;만약 저장된 할 일 목록이 있으면, 이를 JSON으로 파싱하여 &lt;span&gt;todos&lt;/span&gt; 배열에 할당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;할 일 목록 저장&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;addTodo&lt;/span&gt;와 &lt;span&gt;removeTodo&lt;/span&gt; 메소드가 실행될 때, &lt;span&gt;saveTodos()&lt;/span&gt; 메소드를 호출하여 현재 &lt;span&gt;todos&lt;/span&gt; 배열을 로컬 스토리지에 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;saveTodos()&lt;span&gt; 메소드는 &lt;/span&gt;localStorage.setItem('todos', JSON.stringify(this.todos))&lt;span&gt;를 통해 &lt;/span&gt;todos&lt;span&gt; 배열을 문자열로 변환하여 로컬 스토리지에 저장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;할 일 완료 상태 변경 시 저장&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;할 일의 체크박스를 클릭하여 완료 상태를 변경할 때도 &lt;span&gt;saveTodos()&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자도구 &amp;gt; Application &amp;gt; Local storage &amp;gt; 도메인 &amp;gt; todos 에 데이터가 쌓인 걸 볼 수 있습니다.&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;1457&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddXNV6/btsI4koo30u/ig0kiE09hbUPklkXxqqIxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddXNV6/btsI4koo30u/ig0kiE09hbUPklkXxqqIxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddXNV6/btsI4koo30u/ig0kiE09hbUPklkXxqqIxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddXNV6%2FbtsI4koo30u%2Fig0kiE09hbUPklkXxqqIxk%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;1457&quot; height=&quot;582&quot; data-origin-width=&quot;1457&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;키-값(Key-Value) 쌍으로 저장&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;로컬 스토리지는 데이터를 키-값 쌍으로 저장합니다. 즉, 데이터에 접근할 때 특정 키를 사용하여 해당 값을 가져오거나 수정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;영구 저장&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;로컬 스토리지에 저장된 데이터는 사용자가 명시적으로 삭제하지 않는 한 영구적으로 저장됩니다. 쿠키와 달리 만료 시간이 없으며, 브라우저를 닫거나 컴퓨터를 재부팅해도 유지됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;도메인 별 저장&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;로컬 스토리지는 도메인 단위로 데이터를 저장합니다. 이는 특정 웹사이트에서 저장된 데이터가 다른 도메인에서는 접근할 수 없음을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;5MB 정도의 용량 제한&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;대부분의 브라우저에서는 로컬 스토리지에 약 5MB 정도의 데이터를 저장할 수 있습니다. 이는 비교적 큰 용량으로, 텍스트 기반 데이터나 간단한 설정 값을 저장하는 데 충분합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;동기적 접근&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 스토리지는 JavaScript를 통해 접근할 수 있으며, 다음과 같은 메서드를 사용합니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;데이터 저장&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;localStorage.setItem(key, value)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예:&lt;/p&gt;
&lt;pre id=&quot;code_1723778672320&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;localStorage.setItem('username', 'JohnDoe');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;데이터 가져오기&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;localStorage.getItem(key)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예:&lt;/p&gt;
&lt;pre id=&quot;code_1723778685287&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const username = localStorage.getItem('username');
console.log(username); // 'JohnDoe'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;데이터 삭제&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;특정 데이터 삭제: &lt;/span&gt;localStorage.removeItem(key)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;모든 데이터 삭제: &lt;/span&gt;localStorage.clear()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예:&lt;/p&gt;
&lt;pre id=&quot;code_1723778695698&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;localStorage.removeItem('username');
localStorage.clear(); // 모든 데이터 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;저장된 데이터 개수 확인&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;localStorage.length&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예:&lt;/p&gt;
&lt;pre id=&quot;code_1723778716184&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const length = localStorage.length;
console.log(length);&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;&lt;b&gt;로컬 스토리지의 활용 사례&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;사용자 설정 저장&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예를 들어, 웹사이트의 테마 설정이나 글꼴 크기 등의 개인 설정을 로컬 스토리지에 저장하여, 사용자가 브라우저를 닫았다가 다시 열어도 같은 설정을 유지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;간단한 데이터 캐싱&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;네트워크 요청 결과를 로컬 스토리지에 저장하여, 동일한 데이터를 다시 요청할 필요 없이 빠르게 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;폼 데이터 임시 저장&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;보안 문제&lt;/b&gt;: 로컬 스토리지는 클라이언트 측에 저장되므로, 악의적인 사용자가 쉽게 접근할 수 있습니다. 따라서 민감한 정보(예: 비밀번호, 개인 정보)를 저장하는 데는 적합하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;용량 제한&lt;/b&gt;: 로컬 스토리지는 보통 5MB 정도의 용량 제한이 있습니다. 이 때문에 대용량 데이터를 저장하는 데는 적합하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;브라우저 호환성&lt;/b&gt;: 대부분의 현대 브라우저는 로컬 스토리지를 지원하지만, 매우 오래된 브라우저에서는 지원되지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;이전 블로그&lt;/p&gt;
&lt;div&gt;&lt;a style=&quot;color: #111111; text-align: left;&quot; href=&quot;https://juntcom.tistory.com/316&quot;&gt;vuejs 로 to-do 리스트 구현 1&lt;/a&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://juntcom.tistory.com/316&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://juntcom.tistory.com/316&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue</category>
      <category>vue todo list</category>
      <category>vue 로컬스토리지</category>
      <category>로컬스토리지</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/317</guid>
      <comments>https://juntcom.tistory.com/317#entry317comment</comments>
      <pubDate>Fri, 16 Aug 2024 12:26:37 +0900</pubDate>
    </item>
    <item>
      <title>vuejs 로 to-do 리스트 구현 1</title>
      <link>https://juntcom.tistory.com/316</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;To-Do 리스트를 구현하기 위해 새로운 컴포넌트를 만드는 방법을 단계별로 설명드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설명을 따라 새로운 컴포넌트를 만들고 &lt;span&gt;App.vue&lt;/span&gt;에서 이를 사용해 To-Do 리스트를 구현할 수 있습니다.&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;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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;ToDoList.vue&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt; 파일 생성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;src/components&lt;span&gt; 디렉토리 안에 &lt;/span&gt;ToDoList.vue&lt;span&gt; 파일을 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ToDoList.vue&lt;/b&gt;&lt;span&gt;&lt;b&gt; 초기 코드 작성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;아래 코드를 &lt;span&gt;ToDoList.vue&lt;/span&gt; 파일에 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;이 코드는 To-Do 리스트 항목을 추가하고, 리스트를 렌더링하는 기본적인 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723777655518&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;To-Do List&amp;lt;/h2&amp;gt;
    &amp;lt;input v-model=&quot;newTodo&quot; placeholder=&quot;Add a new to-do&quot; @keyup.enter=&quot;addTodo&quot; /&amp;gt;
    &amp;lt;button @click=&quot;addTodo&quot;&amp;gt;Add&amp;lt;/button&amp;gt;
    
    &amp;lt;ul&amp;gt;
      &amp;lt;li v-for=&quot;(todo, index) in todos&quot; :key=&quot;index&quot;&amp;gt;
        &amp;lt;input type=&quot;checkbox&quot; v-model=&quot;todo.completed&quot; /&amp;gt;
        &amp;lt;span :class=&quot;{ completed: todo.completed }&quot;&amp;gt;{{ todo.text }}&amp;lt;/span&amp;gt;
        &amp;lt;button @click=&quot;removeTodo(index)&quot;&amp;gt;Delete&amp;lt;/button&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: 'ToDoList',
  data() {
    return {
      newTodo: '',
      todos: []
    };
  },
  methods: {
    addTodo() {
      if (this.newTodo.trim()) {
        this.todos.push({ text: this.newTodo, completed: false });
        this.newTodo = '';
      }
    },
    removeTodo(index) {
      this.todos.splice(index, 1);
    }
  }
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
.completed {
  text-decoration: line-through;
  color: grey;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. App.vue 파일 수정&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;ToDoList&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 컴포넌트 가져오기&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;App.vue&lt;/span&gt; 파일에서 &lt;span&gt;ToDoList.vue&lt;/span&gt; 컴포넌트를 가져오고, 등록합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723777668091&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div id=&quot;app&quot;&amp;gt;
    &amp;lt;h1&amp;gt;Vue.js To-Do App&amp;lt;/h1&amp;gt;
    &amp;lt;ToDoList /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import ToDoList from './components/ToDoList.vue';

export default {
  name: 'App',
  components: {
    ToDoList
  }
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;컴포넌트 실행 및 테스트&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;프로젝트를 실행하여 &lt;span&gt;ToDoList.vue&lt;/span&gt; 컴포넌트가 정상적으로 렌더링되고, 할 일 항목을 추가, 삭제할 수 있는지 확인합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v6tTO/btsI5tq6pPI/G1AD2X9nELQ03kseokgTP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v6tTO/btsI5tq6pPI/G1AD2X9nELQ03kseokgTP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v6tTO/btsI5tq6pPI/G1AD2X9nELQ03kseokgTP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv6tTO%2FbtsI5tq6pPI%2FG1AD2X9nELQ03kseokgTP1%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;533&quot; height=&quot;569&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;569&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 스타일 및 추가 기능&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;스타일 개선&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ToDoList.vue&lt;/span&gt; 파일에 추가 스타일을 적용하여 UI를 더 예쁘게 꾸밀 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예를 들어, 인풋 필드, 버튼, 리스트 항목 등에 스타일을 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;추가 기능 구현&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;할 일 목록을 로컬 스토리지에 저장하여 새로고침 후에도 유지되도록 하거나, 완료된 항목을 숨기거나 필터링하는 기능을 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;이전 블로그&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vue.js 프로젝트 설정 - vue cli 시작하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://juntcom.tistory.com/284&quot;&gt;https://juntcom.tistory.com/284&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue todo 리스트</category>
      <category>vue 개발</category>
      <category>vuejs</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/316</guid>
      <comments>https://juntcom.tistory.com/316#entry316comment</comments>
      <pubDate>Fri, 16 Aug 2024 12:09:28 +0900</pubDate>
    </item>
    <item>
      <title>vuejs 개발시 참고할 레퍼런스 모음</title>
      <link>https://juntcom.tistory.com/315</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue.js로 개발할 때 유용한 레퍼런스와 리소스를 정리해드리겠습니다. 이들은 Vue.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;&lt;b&gt;공식 문서 및 가이드&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue.js 공식 문서&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://vuejs.org/&quot;&gt;Vue.js 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js의 모든 기능과 개념에 대한 공식 가이드입니다. 기초부터 고급 주제까지 잘 정리되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue.js 스타일 가이드&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://vuejs.org/style-guide/&quot;&gt;Vue.js 공식 스타일 가이드&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 프로젝트에서 일관된 코딩 스타일을 유지하기 위한 권장사항이 담긴 가이드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue.js API 레퍼런스&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://vuejs.org/api/&quot;&gt;Vue.js API 레퍼런스&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js의 모든 API와 관련된 설명을 제공합니다. 특정 기능이나 메서드를 찾을 때 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue.js 3 공식 문서&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://v3.vuejs.org/&quot;&gt;Vue 3 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 3의 새로운 기능들과 변경된 사항들을 포함한 공식 가이드입니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue Mastery&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://www.vuemastery.com/&quot;&gt;Vue Mastery&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 공식 학습 파트너로, 초급부터 고급까지 다양한 튜토리얼 동영상을 제공합니다. 일부 강의는 무료로 제공됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue School&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://vueschool.io/&quot;&gt;Vue School&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 학습을 위한 온라인 교육 플랫폼으로, 심화된 Vue.js 주제들을 다루는 동영상 강의를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Scrimba: Learn Vue.js for free&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://scrimba.com/learn/vue&quot;&gt;Scrimba: Learn Vue.js&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;무료로 제공되는 Vue.js 강의 시리즈로, 인터랙티브한 코딩 환경에서 학습할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Laracasts Vue.js 시리즈&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://laracasts.com/series/learn-vue-2-step-by-step&quot;&gt;Laracasts Vue.js 시리즈&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js와 관련된 강좌를 제공하는 사이트로, 특히 Laravel과 함께 사용하는 방법을 다룹니다. 일부는 무료로 제공됩니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue CLI&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://cli.vuejs.org/&quot;&gt;Vue CLI&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 프로젝트 생성을 위한 강력한 CLI 도구입니다. 다양한 설정을 자동으로 처리해주며, 플러그인 시스템을 통해 확장이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue Router&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://router.vuejs.org/&quot;&gt;Vue Router 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js에서 SPA(Single Page Application)의 라우팅을 담당하는 라이브러리로, 공식 문서를 통해 설정과 사용법을 자세히 배울 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;Vuex&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://vuex.vuejs.org/&quot;&gt;Vuex 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js에서 상태 관리를 위한 중앙 집중식 저장소 역할을 하는 라이브러리입니다. 특히 대규모 애플리케이션에서 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Axios&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://axios-http.com/&quot;&gt;Axios 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js와 함께 자주 사용되는 HTTP 클라이언트 라이브러리로, API 통신을 쉽게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vuetify&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://vuetifyjs.com/&quot;&gt;Vuetify 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 기반의 Material Design 컴포넌트 프레임워크로, 아름답고 반응형인 UI를 쉽게 구축할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;6.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Quasar Framework&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://quasar.dev/&quot;&gt;Quasar Framework 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.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;&lt;b&gt;커뮤니티 및 포럼&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue.js 공식 포럼&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://forum.vuejs.org/&quot;&gt;Vue.js 공식 포럼&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 커뮤니티가 활발하게 활동하는 공식 포럼입니다. 질문과 답변, 다양한 토론이 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Stack Overflow&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://stackoverflow.com/questions/tagged/vue.js&quot;&gt;Stack Overflow Vue.js 태그&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 관련 질문과 답변을 찾을 수 있는 커뮤니티입니다. 개발 중 발생하는 문제를 해결할 때 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Reddit&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://www.reddit.com/r/vuejs/&quot;&gt;Reddit Vue.js 커뮤니티&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 관련 토론과 정보 공유가 활발히 이루어지는 Reddit 커뮤니티입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Discord Vue.js 커뮤니티&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://vue.land/&quot;&gt;Vue Land (Discord 서버)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 개발자들이 실시간으로 소통할 수 있는 공식 Discord 커뮤니티입니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Awesome Vue&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://github.com/vuejs/awesome-vue&quot;&gt;Awesome Vue&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue.js 관련 리소스와 라이브러리를 모아놓은 깃허브 리포지토리입니다. 다양한 플러그인, 튜토리얼, 프로젝트 예제를 찾을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Vue.js Examples&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://vuejsexamples.com/&quot;&gt;Vue.js Examples&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;다양한 Vue.js 애플리케이션 예제와 코드를 제공하는 사이트입니다. 실제 프로젝트에 응용할 수 있는 샘플 코드들이 많습니다.&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue 레퍼런스</category>
      <category>vuejs</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/315</guid>
      <comments>https://juntcom.tistory.com/315#entry315comment</comments>
      <pubDate>Fri, 16 Aug 2024 11:28:53 +0900</pubDate>
    </item>
    <item>
      <title>vue 개발 시작 환경설정</title>
      <link>https://juntcom.tistory.com/314</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Visual Studio Code 설치&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;a href=&quot;https://code.visualstudio.com/&quot;&gt;Visual Studio Code&lt;/a&gt;를 설치합니다. VS Code는 무료로 제공되며, 다양한 운영 체제(Windows, macOS, Linux)에서 사용할 수 있습니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설치 방법&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;2. Node.js 설치&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;Vue CLI와 관련된 도구들을 사용하려면 Node.js가 설치되어 있어야 합니다. Node.js 설치 시 npm(Node Package Manager)이 함께 설치됩니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설치 방법&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://nodejs.org/&quot;&gt;&lt;span&gt;Node.js 공식 웹사이트&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;에서 최신 LTS(Long Term Support) 버전을 다운로드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;설치 프로그램을 실행하여 Node.js와 npm을 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;설치 완료 후, 터미널에서 Node.js와 npm의 버전을 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723773608923&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;node -v
npm -v&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Vue CLI 설치&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;Vue.js 프로젝트를 쉽게 생성하고 관리하기 위해 Vue CLI(Command Line Interface)를 설치합니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설치 방법&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;터미널을 열고 다음 명령어를 입력하여 Vue CLI를 전역으로 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723773623921&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g @vue/cli&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;설치가 완료되면, Vue CLI의 버전을 확인합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723773633879&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vue --version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Visual Studio Code 플러그인 설치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue 라는 플러그인 설치. 그 외에 다른 확장도구 설치해도 무방&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BVvKl/btsI5YdiYE1/NZRKevE0A2TPGNS3rrc5L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BVvKl/btsI5YdiYE1/NZRKevE0A2TPGNS3rrc5L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BVvKl/btsI5YdiYE1/NZRKevE0A2TPGNS3rrc5L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBVvKl%2FbtsI5YdiYE1%2FNZRKevE0A2TPGNS3rrc5L0%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;1271&quot; height=&quot;374&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;374&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;5. Debugger for Chrome&lt;/b&gt;:&lt;br /&gt;&lt;a href=&quot;https://chromewebstore.google.com/search/vue?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chromewebstore.google.com/search/vue?hl=ko&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 vue 개발을 도와주는 디버그 툴을 설치해주셔야 합니다.&amp;nbsp;&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;1515&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTUGSZ/btsI6k1uAYb/fQUYaKdBkrBzm8ox92GPok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTUGSZ/btsI6k1uAYb/fQUYaKdBkrBzm8ox92GPok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTUGSZ/btsI6k1uAYb/fQUYaKdBkrBzm8ox92GPok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTUGSZ%2FbtsI6k1uAYb%2FfQUYaKdBkrBzm8ox92GPok%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;1515&quot; height=&quot;363&quot; data-origin-width=&quot;1515&quot; data-origin-height=&quot;363&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;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue 개발</category>
      <category>Vue 환경설정</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/314</guid>
      <comments>https://juntcom.tistory.com/314#entry314comment</comments>
      <pubDate>Fri, 16 Aug 2024 11:04:04 +0900</pubDate>
    </item>
    <item>
      <title>flutter 디자인 패턴 모음</title>
      <link>https://juntcom.tistory.com/313</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter는 다양한 디자인 패턴을 사용하여 애플리케이션 개발을 더욱 구조적이고 유지보수가 쉽게 만들 수 있습니다. 주요 패턴들을 정리해보겠습니다.&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;1. Provider 패턴&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: Flutter 애플리케이션에서 상태 관리를 간단하고 효율적으로 수행하기 위해 사용됩니다. InheritedWidget을 기반으로 하여, 상위 위젯 트리에서 상태를 하위 위젯 트리에 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;사용법&lt;/b&gt;: &lt;span&gt;provider&lt;/span&gt; 패키지를 사용하여 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1722154836324&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Counter with ChangeNotifier {
  int _count = 0;
  int get count =&amp;gt; _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) =&amp;gt; Counter(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Provider Example')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: &amp;lt;Widget&amp;gt;[
              Text(
                'You have pushed the button this many times:',
              ),
              Consumer&amp;lt;Counter&amp;gt;(
                builder: (context, counter, _) {
                  return Text(
                    '${counter.count}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                },
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            Provider.of&amp;lt;Counter&amp;gt;(context, listen: false).increment();
          },
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. BLoC (Business Logic Component) 패턴&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: BLoC 패턴은 비즈니스 로직을 UI에서 분리하여 유지보수성과 테스트 용이성을 높입니다. 이벤트와 상태 스트림을 사용하여 비동기 데이터 흐름을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;사용법&lt;/b&gt;: &lt;span&gt;flutter_bloc&lt;/span&gt; 패키지를 사용하여 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1722154854669&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterEvent {}

class CounterState {
  final int counter;
  CounterState(this.counter);
}

class CounterBloc extends Bloc&amp;lt;CounterEvent, CounterState&amp;gt; {
  CounterBloc() : super(CounterState(0));

  @override
  Stream&amp;lt;CounterState&amp;gt; mapEventToState(CounterEvent event) async* {
    if (event is IncrementEvent) {
      yield CounterState(state.counter + 1);
    }
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (context) =&amp;gt; CounterBloc(),
        child: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('BLoC Example')),
      body: Center(
        child: BlocBuilder&amp;lt;CounterBloc, CounterState&amp;gt;(
          builder: (context, state) {
            return Text(
              '${state.counter}',
              style: Theme.of(context).textTheme.headline4,
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          BlocProvider.of&amp;lt;CounterBloc&amp;gt;(context).add(IncrementEvent());
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Redux 패턴&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 상태 관리 라이브러리로, 애플리케이션의 상태를 하나의 중앙 저장소에서 관리합니다. 액션을 통해 상태를 업데이트하고 리듀서를 통해 새로운 상태를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;사용법&lt;/b&gt;: &lt;span&gt;flutter_redux&lt;/span&gt; 패키지를 사용하여 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1722154867219&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// State
class AppState {
  final int counter;
  AppState(this.counter);
}

// Actions
class IncrementAction {}

// Reducer
AppState counterReducer(AppState state, action) {
  if (action is IncrementAction) {
    return AppState(state.counter + 1);
  }
  return state;
}

// Store
final store = Store&amp;lt;AppState&amp;gt;(
  counterReducer,
  initialState: AppState(0),
);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreProvider&amp;lt;AppState&amp;gt;(
      store: store,
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Redux Example')),
      body: Center(
        child: StoreConnector&amp;lt;AppState, String&amp;gt;(
          converter: (store) =&amp;gt; store.state.counter.toString(),
          builder: (context, counter) {
            return Text(
              counter,
              style: Theme.of(context).textTheme.headline4,
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          StoreProvider.of&amp;lt;AppState&amp;gt;(context).dispatch(IncrementAction());
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. MVVM (Model-View-ViewModel) 패턴&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 애플리케이션의 UI를 모델과 분리하여 유지보수성과 테스트 용이성을 높입니다. &lt;span&gt;ViewModel&lt;/span&gt;은 모델 데이터를 처리하고 이를 뷰에 바인딩합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;사용법&lt;/b&gt;: &lt;/span&gt;provider&lt;span&gt;와 &lt;/span&gt;ChangeNotifier&lt;span&gt;를 사용하여 구현합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1722154880732&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterViewModel extends ChangeNotifier {
  int _counter = 0;
  int get counter =&amp;gt; _counter;

  void increment() {
    _counter++;
    notifyListeners();
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) =&amp;gt; CounterViewModel(),
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('MVVM Example')),
      body: Center(
        child: Consumer&amp;lt;CounterViewModel&amp;gt;(
          builder: (context, viewModel, _) {
            return Text(
              '${viewModel.counter}',
              style: Theme.of(context).textTheme.headline4,
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of&amp;lt;CounterViewModel&amp;gt;(context, listen: false).increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple&quot;&gt;Flutter Documentation - Provider&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://bloclibrary.dev/#/&quot;&gt;Flutter Documentation - BLoC&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://pub.dev/packages/flutter_redux&quot;&gt;Redux in Flutter&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://medium.com/flutter-community/flutter-with-mvvm-4adb0a8a187&quot;&gt;MVVM in Flutter&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/313</guid>
      <comments>https://juntcom.tistory.com/313#entry313comment</comments>
      <pubDate>Sun, 28 Jul 2024 17:23:03 +0900</pubDate>
    </item>
    <item>
      <title>flutter future 키워드 및 aysnc await 를 통한 비동기처리</title>
      <link>https://juntcom.tistory.com/312</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Dart의 &lt;span&gt;Future&lt;/span&gt;와 &lt;span&gt;async&lt;/span&gt;/&lt;span&gt;await&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Future&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;Future&lt;/b&gt;는 비동기 작업의 결과를 나타내는 객체입니다. 이 객체는 작업이 완료되었을 때 결과를 제공하거나 오류를 전달합니다. &lt;span&gt;Future&lt;/span&gt;는 JavaScript의 &lt;span&gt;Promise&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;b&gt;Future 사용 예제&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Future 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Future 사용 예제&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Future 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722150399591&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Future&amp;lt;String&amp;gt; fetchUserOrder() {
  // 2초 후에 'Order Complete' 반환
  return Future.delayed(Duration(seconds: 2), () =&amp;gt; 'Order Complete');
}&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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;then() 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722150418535&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  fetchUserOrder().then((order) {
    print(order); // 'Order Complete' 출력
  }).catchError((error) {
    print('Error: $error');
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;async 및 await&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;span&gt;async&lt;/span&gt;와 &lt;span&gt;await&lt;/span&gt; 키워드는 비동기 코드를 더 읽기 쉽게 작성할 수 있도록 도와줍니다. &lt;span&gt;async&lt;/span&gt; 함수는 &lt;span&gt;Future&lt;/span&gt;를 반환하며, &lt;span&gt;await&lt;/span&gt; 키워드는 &lt;span&gt;Future&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;b&gt;async 및 await 사용 예제&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;async 함수 정의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722150432048&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; fetchUserOrder() async {
  try {
    String order = await Future.delayed(Duration(seconds: 2), () =&amp;gt; 'Order Complete');
    print(order); // 'Order Complete' 출력
  } catch (e) {
    print('Error: $e');
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;async 함수 호출&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722150462947&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  fetchUserOrder();
  print('Fetching user order...'); // 먼저 출력됨
}&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;&lt;b&gt;자세한 설명&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Future&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;비동기 작업을 표현&lt;/b&gt;: &lt;span&gt;Future&lt;/span&gt;는 비동기 작업의 완료(성공/실패)를 표현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;메서드&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;: Future가 완료될 때 호출될 콜백을 등록합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;catchError&lt;/span&gt;: Future가 실패할 때 호출될 콜백을 등록합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;whenComplete&lt;/span&gt;: Future가 완료되면 성공 여부와 상관없이 호출될 콜백을 등록합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;async&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;함수를 비동기로 표시&lt;/b&gt;: 함수에 &lt;span&gt;async&lt;/span&gt;를 붙이면 해당 함수는 &lt;span&gt;Future&lt;/span&gt;를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;비동기 코드 작성&lt;/b&gt;: &lt;span&gt;async&lt;/span&gt; 키워드를 사용하면 코드가 비동기로 실행되며, 함수는 즉시 &lt;span&gt;Future&lt;/span&gt;를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;await&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Future가 완료될 때까지 기다림&lt;/b&gt;: &lt;span&gt;await&lt;/span&gt; 키워드를 사용하면 &lt;span&gt;Future&lt;/span&gt;가 완료될 때까지 기다리고, 완료 후 결과를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;try-catch와 함께 사용&lt;/b&gt;: 비동기 작업에서 발생하는 오류를 처리하기 위해 &lt;span&gt;try-catch&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;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://dart-ko.dev/codelabs/async-await&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dart-ko.dev/codelabs/async-await&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/312</guid>
      <comments>https://juntcom.tistory.com/312#entry312comment</comments>
      <pubDate>Sun, 28 Jul 2024 16:25:35 +0900</pubDate>
    </item>
    <item>
      <title>API란 무엇인가?</title>
      <link>https://juntcom.tistory.com/311</link>
      <description>&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;API&lt;/b&gt;(Application Programming Interface)는 소프트웨어 애플리케이션 간의 상호 작용을 가능하게 하는 인터페이스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API는 다양한 기능을 다른 프로그램에서 사용할 수 있도록 정의된 규칙 및 도구의 집합을 제공합니다.&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;1280&quot; data-origin-height=&quot;661&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqh0Z1/btsIQva8H0p/gLKEmnrJLjsLgkpOEltmxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqh0Z1/btsIQva8H0p/gLKEmnrJLjsLgkpOEltmxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqh0Z1/btsIQva8H0p/gLKEmnrJLjsLgkpOEltmxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdqh0Z1%2FbtsIQva8H0p%2FgLKEmnrJLjsLgkpOEltmxK%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;1280&quot; height=&quot;661&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;661&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;인터페이스&lt;/b&gt;: API는 소프트웨어 구성 요소가 서로 상호작용할 수 있도록 하는 인터페이스입니다. 이 인터페이스는 명령, 함수, 프로토콜 등으로 구성될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;표준화&lt;/b&gt;: API는 특정 작업을 수행하기 위한 표준화된 방법을 제공합니다. 이를 통해 다양한 소프트웨어가 일관된 방식으로 상호작용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;재사용성&lt;/b&gt;: 개발자는 API를 사용하여 기존 기능을 재사용할 수 있습니다. 새로운 애플리케이션을 개발할 때 처음부터 모든 기능을 구현할 필요 없이, API를 통해 필요한 기능을 빠르게 통합할 수 있습니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;웹 API&lt;/b&gt;: 웹 서비스 상호작용을 위한 API로, HTTP 프로토콜을 사용하여 데이터를 주고받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful API와 SOAP API가 대표적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;RESTful API&lt;/b&gt;: REST(Representational State Transfer) 아키텍처 스타일을 따르는 API로, 자원을 URL로 표현하고 HTTP 메서드(GET, POST, PUT, DELETE 등)를 통해 조작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;SOAP API&lt;/b&gt;: SOAP(Simple Object Access Protocol)를 사용하는 API로, XML 형식을 통해 메시지를 교환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;라이브러리 API&lt;/b&gt;: 소프트웨어 라이브러리에서 제공하는 API로, 특정 프로그래밍 언어로 작성된 함수와 절차를 사용하여 기능을 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;운영 체제 API&lt;/b&gt;: 운영 체제가 제공하는 API로, 애플리케이션이 운영 체제의 기능을 사용할 수 있도록 합니다. 예를 들어, 파일 시스템 접근, 프로세스 관리 등이 있습니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Google Maps API&lt;/b&gt;: 개발자가 Google Maps 서비스를 자신의 애플리케이션에 통합할 수 있도록 하는 API입니다. 경로 찾기, 장소 정보 제공, 지도 표시 등의 기능을 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722059337148&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET https://maps.googleapis.com/maps/api/geocode/json?address=Seattle&amp;amp;key=YOUR_API_KEY&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Twitter API&lt;/b&gt;: Twitter 플랫폼의 데이터를 접근하고 조작할 수 있게 해주는 API입니다. 트윗 작성, 사용자 정보 가져오기, 트윗 검색 등의 기능을 제공합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722059348719&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET https://api.twitter.com/2/tweets?ids=1453489038376136711&amp;amp;tweet.fields=created_at&amp;amp;expansions=author_id&amp;amp;user.fields=username&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;API 사용의 장점&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;빠른 개발&lt;/b&gt;: 기존의 기능을 재사용함으로써 개발 속도를 높일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;유지보수 용이&lt;/b&gt;: 중앙화된 API를 통해 기능 업데이트와 유지보수가 용이합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;호환성&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/REST&quot;&gt;RESTful API란 무엇인가?&lt;/a&gt;&lt;/p&gt;</description>
      <category>웹 기본지식</category>
      <category>API란</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/311</guid>
      <comments>https://juntcom.tistory.com/311#entry311comment</comments>
      <pubDate>Sat, 27 Jul 2024 14:49:51 +0900</pubDate>
    </item>
    <item>
      <title>flutter firestore 시작하기 - firebase 데이터베이스</title>
      <link>https://juntcom.tistory.com/310</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firestore를 사용하여 To-Do 앱을 만드는 방법을 설명하겠습니다. 이 예제에서는 Firebase Firestore를 사용하여 할 일 데이터를 저장하고, 읽고, 업데이트하고, 삭제하는 기능을 구현합니다.&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;1. firebase 내 데이터베이스 만들기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1229&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCasIl/btsIN0vIBAx/FOHUIyxB9mOPteFVBAgYrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCasIl/btsIN0vIBAx/FOHUIyxB9mOPteFVBAgYrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCasIl/btsIN0vIBAx/FOHUIyxB9mOPteFVBAgYrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCasIl%2FbtsIN0vIBAx%2FFOHUIyxB9mOPteFVBAgYrK%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;1229&quot; height=&quot;474&quot; data-origin-width=&quot;1229&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbv5pE/btsIOBvrKLm/MpkktASVthttbD8p1kZ1Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbv5pE/btsIOBvrKLm/MpkktASVthttbD8p1kZ1Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbv5pE/btsIOBvrKLm/MpkktASVthttbD8p1kZ1Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdbv5pE%2FbtsIOBvrKLm%2FMpkktASVthttbD8p1kZ1Nk%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;862&quot; height=&quot;566&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;597&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wsYck/btsINUPZcI2/jU9FWoPpR1vOXxiJjOmITK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wsYck/btsINUPZcI2/jU9FWoPpR1vOXxiJjOmITK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wsYck/btsINUPZcI2/jU9FWoPpR1vOXxiJjOmITK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwsYck%2FbtsINUPZcI2%2FjU9FWoPpR1vOXxiJjOmITK%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;833&quot; height=&quot;597&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;597&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 firestore 를 생성해줍니다.&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;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;&lt;b&gt;pubspec.yaml 파일 업데이트&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;최신 Firebase 패키지를 &lt;span&gt;pubspec.yaml&lt;/span&gt; 파일에 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721912196113&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  flutter:
    sdk: flutter
  firebase_core: 
  firebase_auth: 
  cloud_firestore:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Firebase 설정&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;span&gt;google-services.json&lt;/span&gt; 파일을 다운로드하여 &lt;span&gt;android/app&lt;/span&gt; 디렉토리에 복사합니다. Firebase 콘솔에서 프로젝트를 설정하고 앱을 등록한 후 이 파일을 받을 수 있습니다.&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;android/build.gradle 파일 업데이트&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721912217050&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:8.0.0'
        classpath 'com.google.gms:google-services:4.4.2'
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;android/app/build.gradle 파일 업데이트&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721912252022&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'

android {
    compileSdkVersion 34

    defaultConfig {
        applicationId &quot;com.example.flutter_todo&quot;
        minSdkVersion 21
        targetSdkVersion 34
        versionCode 1
        versionName &quot;1.0&quot;
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation platform('com.google.firebase:firebase-bom:32.2.2')
}

apply plugin: 'com.google.gms.google-services'&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;&lt;b&gt;3. Firebase 초기화&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;main.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721912266059&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'screens/todo_list_screen.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Firebase Todo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TodoListScreen(),
    );
  }
}&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;&lt;b&gt;4. Firestore 데이터 모델&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;models/todo.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721912276264&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Todo {
  final String id;
  final String title;
  final bool isDone;

  Todo({
    required this.id,
    required this.title,
    this.isDone = false,
  });

  factory Todo.fromMap(Map&amp;lt;String, dynamic&amp;gt; data, String documentId) {
    return Todo(
      id: documentId,
      title: data['title'] ?? '',
      isDone: data['isDone'] ?? false,
    );
  }

  Map&amp;lt;String, dynamic&amp;gt; toMap() {
    return {
      'title': title,
      'isDone': isDone,
    };
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. Firestore 서비스&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;services/firestore_service.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721912287104&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/todo.dart';

class FirestoreService {
  final FirebaseFirestore _db = FirebaseFirestore.instance;

  Stream&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt; getTodos() {
    return _db.collection('todos').snapshots().map((snapshot) =&amp;gt;
        snapshot.docs.map((doc) =&amp;gt; Todo.fromMap(doc.data(), doc.id)).toList());
  }

  Future&amp;lt;void&amp;gt; addTodo(Todo todo) {
    return _db.collection('todos').add(todo.toMap());
  }

  Future&amp;lt;void&amp;gt; updateTodo(Todo todo) {
    return _db.collection('todos').doc(todo.id).update(todo.toMap());
  }

  Future&amp;lt;void&amp;gt; deleteTodo(String id) {
    return _db.collection('todos').doc(id).delete();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 프로바이더 설정&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;providers/todo_provider.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721912296487&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/todo.dart';
import '../services/firestore_service.dart';

class TodoProvider with ChangeNotifier {
  final FirestoreService _firestoreService = FirestoreService();

  late Stream&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt; _todos;
  Stream&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt; get todos =&amp;gt; _todos;

  TodoProvider() {
    _todos = _firestoreService.getTodos();
  }

  Future&amp;lt;void&amp;gt; addTodo(Todo todo) async {
    await _firestoreService.addTodo(todo);
  }

  Future&amp;lt;void&amp;gt; updateTodo(Todo todo) async {
    await _firestoreService.updateTodo(todo);
  }

  Future&amp;lt;void&amp;gt; deleteTodo(String id) async {
    await _firestoreService.deleteTodo(id);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. UI 작성&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;screens/todo_list_screen.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721912306488&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo.dart';
import '../providers/todo_provider.dart';
import 'edit_todo_screen.dart';

class TodoListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('To-Do List'),
      ),
      body: StreamBuilder&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt;(
        stream: Provider.of&amp;lt;TodoProvider&amp;gt;(context).todos,
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return Center(child: CircularProgressIndicator());
          }
          final todos = snapshot.data!;
          return ListView.builder(
            itemCount: todos.length,
            itemBuilder: (ctx, i) =&amp;gt; ListTile(
              title: Text(todos[i].title),
              trailing: Checkbox(
                value: todos[i].isDone,
                onChanged: (value) {
                  Provider.of&amp;lt;TodoProvider&amp;gt;(context, listen: false).updateTodo(
                    Todo(
                      id: todos[i].id,
                      title: todos[i].title,
                      isDone: value!,
                    ),
                  );
                },
              ),
              onLongPress: () {
                Provider.of&amp;lt;TodoProvider&amp;gt;(context, listen: false)
                    .deleteTodo(todos[i].id);
              },
              onTap: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) =&amp;gt; EditTodoScreen(todo: todos[i]),
                  ),
                );
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showDialog(
            context: context,
            builder: (ctx) {
              TextEditingController controller = TextEditingController();
              return AlertDialog(
                title: Text('Add Todo'),
                content: TextField(
                  controller: controller,
                  decoration: InputDecoration(labelText: 'Title'),
                ),
                actions: [
                  TextButton(
                    onPressed: () {
                      if (controller.text.isNotEmpty) {
                        final newTodo = Todo(
                          id: '',
                          title: controller.text,
                        );
                        Provider.of&amp;lt;TodoProvider&amp;gt;(context, listen: false)
                            .addTodo(newTodo);
                        Navigator.of(ctx).pop();
                      }
                    },
                    child: Text('Add'),
                  ),
                ],
              );
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;screens/edit_todo_screen.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721912318749&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo.dart';
import '../providers/todo_provider.dart';

class EditTodoScreen extends StatelessWidget {
  final Todo todo;

  EditTodoScreen({required this.todo});

  @override
  Widget build(BuildContext context) {
    TextEditingController titleController = TextEditingController(text: todo.title);

    return Scaffold(
      appBar: AppBar(
        title: Text('Edit Todo'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: titleController,
              decoration: InputDecoration(labelText: 'Title'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                if (titleController.text.isNotEmpty) {
                  final updatedTodo = Todo(
                    id: todo.id,
                    title: titleController.text,
                    isDone: todo.isDone,
                  );
                  Provider.of&amp;lt;TodoProvider&amp;gt;(context, listen: false)
                      .updateTodo(updatedTodo);
                  Navigator.of(context).pop();
                }
              },
              child: Text('Update'),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;Firestore 데이터 모델(&lt;span&gt;Todo&lt;/span&gt;)을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;Firestore 서비스(&lt;span&gt;FirestoreService&lt;/span&gt;)를 작성하여 Firestore와 상호작용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;프로바이더(&lt;span&gt;TodoProvider&lt;/span&gt;)를 작성하여 상태 관리를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;UI를 작성하여 할 일 목록을 표시하고 추가, 수정, 삭제 기능을 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;Firebase를 초기화하고 앱을 실행합니다.&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;이제 Firestore를 사용하여 할 일 데이터를 관리하는 간단한 To-Do 앱이 완성되었습니다. 필요에 따라 추가 기능을 구현하거나 UI를 개선할 수 있습니다.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Firebase</category>
      <category>Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/310</guid>
      <comments>https://juntcom.tistory.com/310#entry310comment</comments>
      <pubDate>Thu, 25 Jul 2024 21:59:45 +0900</pubDate>
    </item>
    <item>
      <title>flutter SQLite 데이터베이스 저장</title>
      <link>https://juntcom.tistory.com/309</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SQLite를 사용하여 To-Do 데이터를 저장하는 방법을 설명하겠습니다. Flutter 프로젝트에서 SQLite를 사용하기 위해 &lt;span&gt;sqflite&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;b&gt;프로젝트 설정&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;pubspec.yaml&lt;/b&gt;&lt;span&gt; 파일에 &lt;/span&gt;sqflite&lt;span&gt;와 &lt;/span&gt;path&lt;span&gt; 패키지를 추가합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721821907650&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  flutter:
    sdk: flutter
  sqflite: ^2.0.0+4
  path: ^1.8.0&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;&lt;b&gt;디렉토리 및 파일 구조&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721821923856&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lib/
  main.dart
  models/
    todo.dart
  screens/
    todo_list_screen.dart
    edit_todo_screen.dart
  services/
    database_helper.dart
  providers/
    todo_provider.dart&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;&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;&lt;b&gt;models/todo.dartㅣㅇve&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;class Todo {
  String id;
  String title;
  bool isDone;

  Todo({
    required this.id,
    required this.title,
    this.isDone = false,
  });

  // 데이터베이스에서 읽은 데이터를 객체로 변환
  factory Todo.fromMap(Map&amp;lt;String, dynamic&amp;gt; json) =&amp;gt; new Todo(
    id: json[&quot;id&quot;],
    title: json[&quot;title&quot;],
    isDone: json[&quot;isDone&quot;] == 1,
  );

  // 객체 데이터를 데이터베이스에 저장할 수 있는 형태로 변환
  Map&amp;lt;String, dynamic&amp;gt; toMap() =&amp;gt; {
    &quot;id&quot;: id,
    &quot;title&quot;: title,
    &quot;isDone&quot;: isDone ? 1 : 0,
  };
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 데이터베이스 헬퍼 클래스 작성&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;services/database_helper.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721822371930&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:async';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../models/todo.dart';

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  factory DatabaseHelper() =&amp;gt; _instance;

  static Database? _database;

  DatabaseHelper._internal();

  Future&amp;lt;Database&amp;gt; get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  Future&amp;lt;Database&amp;gt; _initDatabase() async {
    String path = join(await getDatabasesPath(), 'todo_database.db');
    return await openDatabase(
      path,
      version: 1,
      onCreate: _onCreate,
    );
  }

  Future _onCreate(Database db, int version) async {
    await db.execute('''
      CREATE TABLE todos(
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT,
        isDone INTEGER
      )
    ''');
  }

  Future&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt; getTodos() async {
    final db = await database;
    var res = await db.query('todos');
    List&amp;lt;Todo&amp;gt; list =
        res.isNotEmpty ? res.map((c) =&amp;gt; Todo.fromMap(c)).toList() : [];
    return list;
  }

  Future&amp;lt;int&amp;gt; insertTodo(Todo todo) async {
    final db = await database;
    return await db.insert('todos', todo.toMap());
  }

  Future&amp;lt;int&amp;gt; updateTodo(Todo todo) async {
    final db = await database;
    return await db.update(
      'todos',
      todo.toMap(),
      where: 'id = ?',
      whereArgs: [todo.id],
    );
  }

  Future&amp;lt;int&amp;gt; deleteTodo(int id) async {
    final db = await database;
    return await db.delete(
      'todos',
      where: 'id = ?',
      whereArgs: [id],
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 프로바이더 설정&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;providers/todo_provider.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721822380365&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import '../models/todo.dart';
import '../services/database_helper.dart';

class TodoProvider with ChangeNotifier {
  List&amp;lt;Todo&amp;gt; _todos = [];

  List&amp;lt;Todo&amp;gt; get todos =&amp;gt; _todos;

  Future&amp;lt;void&amp;gt; loadTodos() async {
    _todos = await DatabaseHelper().getTodos();
    notifyListeners();
  }

  Future&amp;lt;void&amp;gt; addTodo(Todo todo) async {
    await DatabaseHelper().insertTodo(todo);
    await loadTodos();
  }

  Future&amp;lt;void&amp;gt; updateTodo(Todo todo) async {
    await DatabaseHelper().updateTodo(todo);
    await loadTodos();
  }

  Future&amp;lt;void&amp;gt; removeTodo(int id) async {
    await DatabaseHelper().deleteTodo(id);
    await loadTodos();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. UI 작성&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;screens/todo_list_screen.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721822390276&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo.dart';
import '../providers/todo_provider.dart';
import 'edit_todo_screen.dart';

class TodoListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('To-Do List'),
      ),
      body: FutureBuilder(
        future: Provider.of&amp;lt;TodoProvider&amp;gt;(context, listen: false).loadTodos(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          }
          return Consumer&amp;lt;TodoProvider&amp;gt;(
            builder: (context, todoProvider, child) {
              return ListView.builder(
                itemCount: todoProvider.todos.length,
                itemBuilder: (ctx, i) =&amp;gt; ListTile(
                  title: Text(todoProvider.todos[i].title),
                  trailing: Checkbox(
                    value: todoProvider.todos[i].isDone,
                    onChanged: (_) {
                      todoProvider.updateTodo(
                        Todo(
                          id: todoProvider.todos[i].id,
                          title: todoProvider.todos[i].title,
                          isDone: !todoProvider.todos[i].isDone,
                        ),
                      );
                    },
                  ),
                  onLongPress: () {
                    todoProvider.removeTodo(todoProvider.todos[i].id!);
                  },
                  onTap: () {
                    Navigator.of(context).push(
                      MaterialPageRoute(
                        builder: (context) =&amp;gt; EditTodoScreen(todo: todoProvider.todos[i]),
                      ),
                    );
                  },
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showDialog(
            context: context,
            builder: (ctx) {
              TextEditingController controller = TextEditingController();
              return AlertDialog(
                title: Text('Add Todo'),
                content: TextField(
                  controller: controller,
                  decoration: InputDecoration(labelText: 'Title'),
                ),
                actions: [
                  TextButton(
                    onPressed: () {
                      if (controller.text.isNotEmpty) {
                        final newTodo = Todo(
                          title: controller.text,
                        );
                        Provider.of&amp;lt;TodoProvider&amp;gt;(context, listen: false).addTodo(newTodo);
                        Navigator.of(ctx).pop();
                      }
                    },
                    child: Text('Add'),
                  ),
                ],
              );
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;screens/edit_todo_screen.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721822404220&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/todo.dart';
import '../providers/todo_provider.dart';

class EditTodoScreen extends StatelessWidget {
  final Todo todo;

  EditTodoScreen({required this.todo});

  @override
  Widget build(BuildContext context) {
    TextEditingController titleController = TextEditingController(text: todo.title);

    return Scaffold(
      appBar: AppBar(
        title: Text('Edit Todo'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: titleController,
              decoration: InputDecoration(labelText: 'Title'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                if (titleController.text.isNotEmpty) {
                  final updatedTodo = Todo(
                    id: todo.id,
                    title: titleController.text,
                    isDone: todo.isDone,
                  );
                  Provider.of&amp;lt;TodoProvider&amp;gt;(context, listen: false).updateTodo(updatedTodo);
                  Navigator.of(context).pop();
                }
              },
              child: Text('Update'),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 메인 파일 설정&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;main.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721822420743&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'screens/todo_list_screen.dart';
import 'providers/todo_provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) =&amp;gt; TodoProvider()),
      ],
      child: MaterialApp(
        title: 'To-Do App',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: TodoListScreen(),
      ),
    );
  }
}&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;&lt;span&gt;1&lt;/span&gt;. `sqflite`와 `path` 패키지를 사용하여 SQLite 데이터베이스를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2&lt;/span&gt;. 데이터 모델(`Todo`)을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3&lt;/span&gt;. 데이터베이스 헬퍼 클래스(`DatabaseHelper`)를 작성하여 CRUD 작업을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;4&lt;/span&gt;. 프로바이더(`TodoProvider`)를 설정하여 데이터베이스와 UI 간의 상태를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;5&lt;/span&gt;. UI를 작성하여 할 일 목록을 표시하고, 할 일을 추가, 수정, 삭제할 수 있는 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;6&lt;/span&gt;. 메인 파일에서 MultiProvider를 설정하여 앱을 실행합니다.&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;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/309</guid>
      <comments>https://juntcom.tistory.com/309#entry309comment</comments>
      <pubDate>Thu, 25 Jul 2024 01:15:34 +0900</pubDate>
    </item>
    <item>
      <title>flutter에서 바텀 네비게이션 바(Bottom Navigation Bar)</title>
      <link>https://juntcom.tistory.com/308</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 바텀 네비게이션 바(Bottom Navigation Bar)는 주로 앱의 주요 네비게이션을 제공하는 데 사용됩니다. 사용자에게 앱의 주요 섹션을 탐색할 수 있는 인터페이스를 제공합니다. 아래는 바텀 네비게이션 바에 대한 주요 특징과 예제들을 정리한 것입니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;사용 용도&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;앱의 주요 섹션 간 빠른 네비게이션을 제공.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;일반적으로 3~5개의 탭을 포함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;각 탭에는 아이콘과 선택적으로 텍스트 레이블을 포함할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;구성 요소&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;items&lt;/b&gt;: 네비게이션 항목 목록으로, 각각 &lt;span&gt;BottomNavigationBarItem&lt;/span&gt; 객체를 포함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;currentIndex&lt;/b&gt;: 현재 선택된 탭의 인덱스.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;onTap&lt;/b&gt;: 사용자가 탭을 선택할 때 호출되는 콜백 함수.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;type&lt;/b&gt;: 네비게이션 바의 타입(&lt;span&gt;fixed&lt;/span&gt; 또는 &lt;span&gt;shifting&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;b&gt;예제 코드&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;아래는 Flutter에서 바텀 네비게이션 바를 구현하는 기본적인 예제입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721808781087&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  int _selectedIndex = 0;

  static const List&amp;lt;Widget&amp;gt; _widgetOptions = &amp;lt;Widget&amp;gt;[
    Text('Home Page'),
    Text('Search Page'),
    Text('Profile Page'),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bottom Navigation Bar Example'),
      ),
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const &amp;lt;BottomNavigationBarItem&amp;gt;[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;items&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BottomNavigationBarItem&lt;/span&gt; 객체의 리스트로, 각 항목은 아이콘과 텍스트 레이블을 가짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예제:&lt;/p&gt;
&lt;pre id=&quot;code_1721808790545&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;items: const &amp;lt;BottomNavigationBarItem&amp;gt;[
  BottomNavigationBarItem(
    icon: Icon(Icons.home),
    label: 'Home',
  ),
  BottomNavigationBarItem(
    icon: Icon(Icons.search),
    label: 'Search',
  ),
  BottomNavigationBarItem(
    icon: Icon(Icons.person),
    label: 'Profile',
  ),
],&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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;currentIndex&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;현재 선택된 탭의 인덱스를 나타냄.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예제:&lt;/p&gt;
&lt;pre id=&quot;code_1721808800663&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;currentIndex: _selectedIndex,&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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;onTap&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;사용자가 탭을 선택할 때 호출되는 콜백 함수.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예제:&lt;/p&gt;
&lt;pre id=&quot;code_1721808813448&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;onTap: _onItemTapped,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;type&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;네비게이션 바의 타입을 설정. &lt;/span&gt;BottomNavigationBarType.fixed&lt;span&gt; 또는 &lt;/span&gt;BottomNavigationBarType.shifting&lt;span&gt;을 사용.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예제:&lt;/p&gt;
&lt;pre id=&quot;code_1721808824724&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type: BottomNavigationBarType.fixed,&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html&quot;&gt;Flutter Documentation - BottomNavigationBar&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs/cookbook/design/bottom-navigation-bar&quot;&gt;Flutter Cookbook - BottomNavigationBar&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/308</guid>
      <comments>https://juntcom.tistory.com/308#entry308comment</comments>
      <pubDate>Wed, 24 Jul 2024 17:14:20 +0900</pubDate>
    </item>
    <item>
      <title>flutter 주요 버튼 위젯 모음</title>
      <link>https://juntcom.tistory.com/307</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서는 다양한 버튼 위젯을 제공하여 사용자가 다양한 상호작용을 할 수 있도록 지원합니다. 각 버튼 위젯은 고유한 스타일과 기능을 가지고 있어 다양한 사용 사례에 적합합니다. 주요 버튼 위젯과 그 특징을 정리해보겠습니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ElevatedButton&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 이전의 RaisedButton에 해당하며, 입체감을 제공하는 기본 버튼입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;사용자가 버튼을 누를 때 시각적인 효과를 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;스타일을 통해 색상, 모양 등을 커스터마이징 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721807220824&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ElevatedButton(
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
  child: Text('Elevated Button'),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;TextButton&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 이전의 FlatButton에 해당하며, 배경 없이 텍스트로만 구성된 버튼입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;버튼을 누를 때 배경 색상이 변경됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;간단한 텍스트 버튼을 구현할 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721807240486&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TextButton(
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
  child: Text('Text Button'),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;OutlinedButton&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 외곽선이 있는 버튼으로, 이전의 OutlineButton에 해당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;버튼을 누를 때 외곽선 색상이 변경됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;기본 버튼에 외곽선을 추가하여 강조할 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721807251680&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;OutlinedButton(
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
  child: Text('Outlined Button'),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;IconButton&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 아이콘을 포함하는 버튼으로, 아이콘만으로 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;아이콘을 클릭할 때 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;버튼에 텍스트 대신 아이콘을 사용하여 직관적인 인터페이스 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721807273019&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IconButton(
  icon: Icon(Icons.thumb_up),
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;FloatingActionButton&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 둥근 모양의 떠 있는 버튼으로, 일반적으로 주요 동작을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;화면 위에 떠 있는 듯한 시각적 효과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;주로 주요 액션 버튼으로 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721807286998&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FloatingActionButton(
  onPressed: () {
    // 버튼 클릭 시 실행되는 코드
  },
  child: Icon(Icons.add),
);&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter의 버튼 위젯들은 스타일링 옵션을 제공하여 다양한 커스터마이징이 가능합니다. 예를 들어, &lt;span&gt;ElevatedButton&lt;/span&gt;의 스타일을 설정하는 방법은 다음과 같습니다:&lt;/p&gt;
&lt;pre id=&quot;code_1721807305087&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ElevatedButton(
  onPressed: () {},
  child: Text('Styled Button'),
  style: ElevatedButton.styleFrom(
    primary: Colors.blue, // 배경 색상
    onPrimary: Colors.white, // 텍스트 색상
    padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
    textStyle: TextStyle(fontSize: 20),
  ),
);&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/307</guid>
      <comments>https://juntcom.tistory.com/307#entry307comment</comments>
      <pubDate>Wed, 24 Jul 2024 16:48:48 +0900</pubDate>
    </item>
    <item>
      <title>flutter 입력 위젯 모음</title>
      <link>https://juntcom.tistory.com/306</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서는 다양한 입력 위젯을 제공하여 사용자의 입력을 처리할 수 있습니다.&lt;/p&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;&lt;b&gt;주요 입력 위젯&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;TextField&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 텍스트 입력 필드로, 사용자가 텍스트를 입력할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;기본 텍스트 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;다수의 속성을 통해 입력 형식, 스타일 등을 제어 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;controller&lt;/span&gt;를 통해 입력된 텍스트를 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806954690&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TextField(
  decoration: InputDecoration(
    border: OutlineInputBorder(),
    labelText: 'Enter your name',
  ),
);&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;&lt;span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;TextFormField&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: &lt;span&gt;Form&lt;/span&gt;과 함께 사용되는 텍스트 입력 필드로, 폼 유효성 검사를 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TextField&lt;/span&gt;와 유사하지만 폼 유효성 검사 기능이 추가됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validator&lt;/span&gt;를 통해 유효성 검사 로직 구현 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806965389&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TextFormField(
  decoration: InputDecoration(
    border: OutlineInputBorder(),
    labelText: 'Enter your email',
  ),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please enter some text';
    }
    return null;
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Checkbox&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 체크박스 위젯으로, 사용자가 옵션을 선택하거나 해제할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;와 &lt;span&gt;onChanged&lt;/span&gt;를 통해 상태 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806984609&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Checkbox(
  value: true,
  onChanged: (bool? newValue) {
    // Handle change
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Radio&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 라디오 버튼 위젯으로, 그룹 내에서 하나의 옵션을 선택할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;여러 라디오 버튼이 같은 그룹에 속할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;value&lt;span&gt;와 &lt;/span&gt;groupValue&lt;span&gt;, &lt;/span&gt;onChanged&lt;span&gt;를 통해 상태 관리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721807006250&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Radio&amp;lt;int&amp;gt;(
  value: 1,
  groupValue: selectedValue,
  onChanged: (int? newValue) {
    // Handle change
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Switch&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 스위치 위젯으로, 이진 상태를 나타내는 토글 버튼입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;와 &lt;span&gt;onChanged&lt;/span&gt;를 통해 상태 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721807016238&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Switch(
  value: true,
  onChanged: (bool newValue) {
    // Handle change
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;6.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Slider&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 슬라이더 위젯으로, 사용자가 값을 선택할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;최소 및 최대 값 설정 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;와 &lt;span&gt;onChanged&lt;/span&gt;를 통해 상태 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721807071472&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Slider(
  value: 50,
  min: 0,
  max: 100,
  onChanged: (double newValue) {
    // Handle change
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;7.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;DropdownButton&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 드롭다운 버튼 위젯으로, 사용자가 목록에서 하나의 옵션을 선택할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;items&lt;/span&gt;와 &lt;span&gt;onChanged&lt;/span&gt;를 통해 상태 관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721807080988&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DropdownButton&amp;lt;String&amp;gt;(
  value: selectedValue,
  items: &amp;lt;String&amp;gt;['One', 'Two', 'Three'].map&amp;lt;DropdownMenuItem&amp;lt;String&amp;gt;&amp;gt;((String value) {
    return DropdownMenuItem&amp;lt;String&amp;gt;(
      value: value,
      child: Text(value),
    );
  }).toList(),
  onChanged: (String? newValue) {
    // Handle change
  },
);&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/TextField-class.html&quot;&gt;Flutter Documentation - TextField&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/TextFormField-class.html&quot;&gt;Flutter Documentation - TextFormField&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Checkbox-class.html&quot;&gt;Flutter Documentation - Checkbox&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Radio-class.html&quot;&gt;Flutter Documentation - Radio&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Switch-class.html&quot;&gt;Flutter Documentation - Switch&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/Slider-class.html&quot;&gt;Flutter Documentation - Slider&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/DropdownButton-class.html&quot;&gt;Flutter Documentation - DropdownButton&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/306</guid>
      <comments>https://juntcom.tistory.com/306#entry306comment</comments>
      <pubDate>Wed, 24 Jul 2024 16:45:58 +0900</pubDate>
    </item>
    <item>
      <title>flutter 레이아웃 위젯 모음</title>
      <link>https://juntcom.tistory.com/305</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서는 다양한 레이아웃 위젯을 제공하여 UI 요소들을 배치하고 정렬할 수 있습니다. 레이아웃 위젯은 화면의 구조를 정의하고, 자식 위젯들을 어떻게 배치할지 결정하는 데 사용됩니다. 주요 레이아웃 위젯과 그 설명을 아래에 정리했습니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Container&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 단일 자식을 포함할 수 있는 상자 위젯. 패딩, 마진, 경계선, 색상 등을 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806567600&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Container(
  padding: EdgeInsets.all(16.0),
  margin: EdgeInsets.all(8.0),
  decoration: BoxDecoration(
    border: Border.all(color: Colors.black),
  ),
  child: Text('Hello, Container!'),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;Row&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 자식 위젯들을 가로로 나란히 배치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806583279&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: &amp;lt;Widget&amp;gt;[
    Icon(Icons.star),
    Icon(Icons.star),
    Icon(Icons.star),
  ],
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Column&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 자식 위젯들을 세로로 나란히 배치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806598603&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: &amp;lt;Widget&amp;gt;[
    Text('First Line'),
    Text('Second Line'),
    Text('Third Line'),
  ],
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Stack&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 자식 위젯들을 겹쳐서 배치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806609127&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stack(
  children: &amp;lt;Widget&amp;gt;[
    Container(
      width: 100,
      height: 100,
      color: Colors.red,
    ),
    Container(
      width: 50,
      height: 50,
      color: Colors.green,
    ),
  ],
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Expanded&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: Flex 위젯(Row, Column) 내에서 남은 공간을 차지하도록 자식 위젯을 확장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806620033&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Row(
  children: &amp;lt;Widget&amp;gt;[
    Expanded(
      child: Container(
        color: Colors.red,
      ),
    ),
    Expanded(
      child: Container(
        color: Colors.green,
      ),
    ),
  ],
);&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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;6.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;SizedBox&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 특정 크기의 상자를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806633315&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SizedBox(
  width: 100,
  height: 100,
  child: Container(
    color: Colors.blue,
  ),
);&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;&lt;span&gt;7.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Padding&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 자식 위젯 주위에 패딩을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806655504&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Padding(
  padding: EdgeInsets.all(16.0),
  child: Text('Padded Text'),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;8.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Align&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 자식 위젯을 부모 위젯 내에서 정렬합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806665945&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Align(
  alignment: Alignment.center,
  child: Text('Centered Text'),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;9.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Center&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설명&lt;/b&gt;: 자식 위젯을 중앙에 배치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721806680338&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Center(
  child: Text('Centered Text'),
);&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs/development/ui/widgets/layout&quot;&gt;Flutter Documentation - Layout Widgets&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs/development/ui/widgets&quot;&gt;Flutter Widget Catalog&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Flutter</category>
      <category>flutter 레이아웃 위젯</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/305</guid>
      <comments>https://juntcom.tistory.com/305#entry305comment</comments>
      <pubDate>Wed, 24 Jul 2024 16:38:46 +0900</pubDate>
    </item>
    <item>
      <title>flutter state 개념 및 정리</title>
      <link>https://juntcom.tistory.com/304</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 &lt;b&gt;State&lt;/b&gt;는 위젯의 상태를 관리하는 데 사용됩니다. State는 두 가지 주요 유형으로 나뉩니다: &lt;b&gt;StatelessWidget&lt;/b&gt;과 &lt;b&gt;StatefulWidget&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;1. StatelessWidget&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;StatelessWidget&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;pre id=&quot;code_1721805928871&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class MyStatelessWidget extends StatelessWidget {
  final String title;

  MyStatelessWidget({required this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Text('Hello, $title!'),
      ),
    );
  }
}

void main() =&amp;gt; runApp(MaterialApp(home: MyStatelessWidget(title: 'Stateless Widget')));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. StatefulWidget&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;StatefulWidget&lt;/b&gt;은 변경 가능한 상태를 가지는 위젯입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StatefulWidget은 자체적으로 상태를 관리하며, 상태가 변경될 때마다 다시 빌드됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StatefulWidget은 두 개의 클래스가 함께 작동합니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;StatefulWidget 클래스&lt;/b&gt;와 &lt;b&gt;State 클래스&lt;/b&gt;.&lt;/p&gt;
&lt;pre id=&quot;code_1721805996300&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() =&amp;gt; _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State&amp;lt;MyStatefulWidget&amp;gt; {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stateful Widget'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

void main() =&amp;gt; runApp(MaterialApp(home: MyStatefulWidget()));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;StatefulWidget의 동작 방식&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;createState()&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;StatefulWidget은 &lt;span&gt;createState&lt;/span&gt; 메서드를 통해 State 객체를 생성합니다. 이 객체는 위젯의 상태를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;State 객체&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;State 객체는 위젯의 상태를 포함하며, 상태가 변경될 때 &lt;span&gt;setState&lt;/span&gt; 메서드를 호출하여 UI를 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setState&lt;/span&gt;는 상태를 변경하고, 변경된 상태를 기반으로 위젯을 다시 빌드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;initState()&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initState&lt;/span&gt;는 State 객체가 처음 생성될 때 호출되며, 초기화 작업을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;dispose()&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dispose&lt;/span&gt;는 State 객체가 소멸될 때 호출되며, 리소스를 정리하는 작업을 수행합니다.&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;State의 생명주기&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;initState()&lt;/b&gt;: State 객체가 처음 생성될 때 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;build()&lt;/b&gt;: 상태가 변경될 때마다 호출되어 위젯을 다시 빌드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;didUpdateWidget()&lt;/b&gt;: 위젯이 업데이트될 때 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;dispose()&lt;/b&gt;: State 객체가 소멸될 때 호출됩니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 위젯의 상태를 변경하여 UI를 업데이트하는 예제입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721806024530&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class CounterWidget extends StatefulWidget {
  @override
  _CounterWidgetState createState() =&amp;gt; _CounterWidgetState();
}

class _CounterWidgetState extends State&amp;lt;CounterWidget&amp;gt; {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            Text(
              'Button pressed $_count times',
              style: Theme.of(context).textTheme.headline4,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _increment,
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

void main() =&amp;gt; runApp(MaterialApp(home: CounterWidget()));&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs/development/ui/interactive&quot;&gt;Flutter Documentation - State&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html&quot;&gt;Flutter Documentation - StatefulWidget&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html&quot;&gt;Flutter Documentation - StatelessWidget&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/304</guid>
      <comments>https://juntcom.tistory.com/304#entry304comment</comments>
      <pubDate>Wed, 24 Jul 2024 16:28:00 +0900</pubDate>
    </item>
    <item>
      <title>flutter controller 로 위젯 및 상태 제어</title>
      <link>https://juntcom.tistory.com/303</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 &lt;span&gt;Controller&lt;/span&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;아래는 일반적으로 &lt;span&gt;Controller&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;b&gt;1. ScrollController&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;: ListView, GridView, SingleChildScrollView 등&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;스크롤 위치를 제어하고 모니터링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721803409920&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ListView.builder(
  controller: ScrollController(),
  itemBuilder: (context, index) {
    return ListTile(title: Text('Item $index'));
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721803925473&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _ScrollControllerExampleState extends State&amp;lt;ScrollControllerExample&amp;gt; {
  final ScrollController _scrollController = ScrollController();
  double _scrollPosition = 0;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      setState(() {
        _scrollPosition = _scrollController.position.pixels;
      });
    });
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Scroll Position: $_scrollPosition'),
      ),
      body: ListView.builder(
        controller: _scrollController,
        itemCount: 100,
        itemBuilder: (context, index) {
          return ListTile(title: Text('Item $index'));
        },
      ),
    );
  }
}&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;&lt;b&gt;2. PageController&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;: PageView, TabBarView 등&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;페이지 뷰어의 현재 페이지를 제어하고 모니터링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721803424236&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PageView(
  controller: PageController(initialPage: 0),
  children: &amp;lt;Widget&amp;gt;[
    Container(color: Colors.red),
    Container(color: Colors.green),
    Container(color: Colors.blue),
  ],
);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1721803956913&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _PageControllerExampleState extends State&amp;lt;PageControllerExample&amp;gt; {
  final PageController _pageController = PageController(initialPage: 0);
  int _currentPage = 0;

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  void _onPageChanged(int page) {
    setState(() {
      _currentPage = page;
    });
  }

  void _nextPage() {
    _pageController.nextPage(duration: Duration(milliseconds: 300), curve: Curves.easeIn);
  }

  void _previousPage() {
    _pageController.previousPage(duration: Duration(milliseconds: 300), curve: Curves.easeIn);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Page View Example')),
      body: Column(
        children: [
          Expanded(
            child: PageView(
              controller: _pageController,
              onPageChanged: _onPageChanged,
              children: &amp;lt;Widget&amp;gt;[
                Container(color: Colors.red),
                Container(color: Colors.green),
                Container(color: Colors.blue),
              ],
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: &amp;lt;Widget&amp;gt;[
              IconButton(
                icon: Icon(Icons.arrow_back),
                onPressed: _previousPage,
              ),
              Text('Page $_currentPage'),
              IconButton(
                icon: Icon(Icons.arrow_forward),
                onPressed: _nextPage,
              ),
            ],
          ),
        ],
      ),
    );
  }
}&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;&lt;b&gt;3. TextEditingController&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;: TextField, TextFormField&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;텍스트 입력 필드의 값을 제어하고 모니터링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721803438252&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TextField(
  controller: TextEditingController(),
  decoration: InputDecoration(labelText: 'Enter text'),
);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1721803972200&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _TextEditingControllerExampleState extends State&amp;lt;TextEditingControllerExample&amp;gt; {
  final TextEditingController _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _printLatestValue() {
    print('Second text field: ${_controller.text}');
  }

  @override
  void initState() {
    super.initState();
    _controller.addListener(_printLatestValue);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('TextEditingController Example')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: &amp;lt;Widget&amp;gt;[
            TextField(
              controller: _controller,
              decoration: InputDecoration(labelText: 'Enter text'),
            ),
            ElevatedButton(
              onPressed: () {
                print('First text field: ${_controller.text}');
              },
              child: Text('Print Text'),
            ),
          ],
        ),
      ),
    );
  }
}&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;&lt;b&gt;4. AnimationController&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;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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;애니메이션을 제어하고 모니터링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721803461481&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;AnimationController(
  duration: const Duration(seconds: 2),
  vsync: this,
);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1721803995134&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _AnimationControllerExampleState extends State&amp;lt;AnimationControllerExample&amp;gt; with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('AnimationController Example')),
      body: Center(
        child: FadeTransition(
          opacity: _controller,
          child: FlutterLogo(size: 100.0),
        ),
      ),
    );
  }
}&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;&lt;b&gt;5. TabController&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;: TabBar, TabBarView&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;탭 바의 현재 탭을 제어하고 모니터링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721803489976&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TabController(
  length: 3,
  vsync: this,
);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1721804077043&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _TabControllerExampleState extends State&amp;lt;TabControllerExample&amp;gt; with SingleTickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TabController Example'),
        bottom: TabBar(
          controller: _tabController,
          tabs: [
            Tab(icon: Icon(Icons.directions_car), text: 'Car'),
            Tab(icon: Icon(Icons.directions_transit), text: 'Transit'),
            Tab(icon: Icon(Icons.directions_bike), text: 'Bike'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          Center(child: Text('Car Tab')),
          Center(child: Text('Transit Tab')),
          Center(child: Text('Bike Tab')),
        ],
      ),
    );
  }
}&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;&lt;b&gt;요약&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;ScrollController&lt;/b&gt;: 스크롤 위치를 제어 (ListView, GridView 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;PageController&lt;/b&gt;: 페이지 뷰어의 현재 페이지를 제어 (PageView, TabBarView)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;TextEditingController&lt;/b&gt;: 텍스트 입력 필드의 값을 제어 (TextField, TextFormField)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;AnimationController&lt;/b&gt;: 애니메이션을 제어 (애니메이션 관련 위젯)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;TabController&lt;/b&gt;: 탭 바의 현재 탭을 제어 (TabBar, TabBarView)&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ScrollController-class.html&quot;&gt;ScrollController 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/PageController-class.html&quot;&gt;PageController 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/TextEditingController-class.html&quot;&gt;TextEditingController 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/animation/AnimationController-class.html&quot;&gt;AnimationController 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/material/TabController-class.html&quot;&gt;TabController 문서&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/303</guid>
      <comments>https://juntcom.tistory.com/303#entry303comment</comments>
      <pubDate>Wed, 24 Jul 2024 15:55:17 +0900</pubDate>
    </item>
    <item>
      <title>flutter photo gallery 사용하기</title>
      <link>https://juntcom.tistory.com/302</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 Photo Gallery는 사용자가 여러 이미지를 탐색할 수 있게 해주는 기능을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 이미지를 확대하거나 축소할 수 있고, 스와이프하여 다음 이미지로 넘어갈 수 있으며, 이미지에 대한 메타데이터를 볼 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 Photo Gallery를 구현하기 위해 다양한 위젯과 패키지를 사용할 수 있으며, 그 중 &lt;span&gt;photo_view&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;b&gt;Photo Gallery의 주요 구성 요소&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;이미지 데이터 소스&lt;/b&gt;: 이미지를 저장하거나 가져오는 곳입니다. 로컬 디렉토리, 원격 서버, 또는 데이터베이스 등이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;이미지 로딩 및 캐싱&lt;/b&gt;: 대량의 이미지를 부드럽게 처리하기 위해 효율적인 로딩 및 캐싱 메커니즘이 필요합니다. &lt;span&gt;cached_network_image&lt;/span&gt; 패키지와 같은 도구를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;이미지 표시&lt;/b&gt;: 이미지를 화면에 표시하기 위해 Flutter의 다양한 위젯을 사용합니다. 특히 &lt;span&gt;photo_view&lt;/span&gt; 패키지는 이미지 확대/축소, 드래그, 회전 등의 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;UI 및 사용자 상호작용&lt;/b&gt;: 사용자가 이미지를 탐색하고 상호작용할 수 있는 UI를 설계합니다. 예를 들어, 스와이프를 통한 이미지 전환, 버튼을 통한 이미지 삭제 등입니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;photo_view&lt;/b&gt;: 이미지를 확대/축소하고 드래그할 수 있는 기능을 제공하는 패키지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;cached_network_image&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;span&gt;photo_view&lt;/span&gt; 패키지를 사용하여 Photo Gallery를 구현하는 예제 코드입니다.&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;PhotoViewPage.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721802039715&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';

class PhotoViewPage extends StatefulWidget {
  final List&amp;lt;String&amp;gt; imagePaths;
  final int currentIndex;

  PhotoViewPage({required this.imagePaths, required this.currentIndex});

  @override
  _PhotoViewPageState createState() =&amp;gt; _PhotoViewPageState();
}

class _PhotoViewPageState extends State&amp;lt;PhotoViewPage&amp;gt; {
  late PageController _controller;
  late int currentPageIndex;

  @override
  void initState() {
    super.initState();
    _controller = PageController(initialPage: widget.currentIndex);
    currentPageIndex = widget.currentIndex;
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          PhotoViewGallery.builder(
            itemCount: widget.imagePaths.length,
            pageController: _controller,
            onPageChanged: (index) {
              setState(() {
                currentPageIndex = index;
              });
            },
            builder: (context, index) {
              return PhotoViewGalleryPageOptions(
                imageProvider: NetworkImage(widget.imagePaths[index]),
                maxScale: PhotoViewComputedScale.covered * 3,
                minScale: PhotoViewComputedScale.contained,
              );
            },
            loadingBuilder: (context, event) {
              return Center(
                child: CircularProgressIndicator(
                  strokeWidth: 2,
                ),
              );
            },
          ),
          Align(
            alignment: Alignment.topLeft,
            child: Container(
              margin: const EdgeInsets.only(left: 25, top: 25),
              child: IconButton(
                onPressed: () =&amp;gt; Navigator.of(context).pop(),
                icon: const Icon(
                  Icons.close,
                  color: Colors.white,
                ),
              ),
            ),
          ),
          Align(
            alignment: Alignment.bottomCenter,
            child: Container(
              margin: const EdgeInsets.only(bottom: 20),
              child: Text(
                '${currentPageIndex + 1} / ${widget.imagePaths.length}',
                style: const TextStyle(color: Colors.white),
              ),
            ),
          ),
        ],
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 사용하기 위해 &lt;span&gt;pubspec.yaml&lt;/span&gt; 파일에 &lt;span&gt;photo_view&lt;/span&gt; 패키지를 추가합니다:&lt;/p&gt;
&lt;pre id=&quot;code_1721802062940&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  flutter:
    sdk: flutter
  photo_view: ^0.13.0 # 최신 버전을 확인하세요&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;&lt;b&gt;Main.dart&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721802084391&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'photo_view_page.dart';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: GalleryPage(),
    );
  }
}

class GalleryPage extends StatelessWidget {
  final List&amp;lt;String&amp;gt; imagePaths = [
    'https://example.com/image1.jpg',
    'https://example.com/image2.jpg',
    'https://example.com/image3.jpg',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Gallery')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) =&amp;gt; PhotoViewPage(
                  imagePaths: imagePaths,
                  currentIndex: 0,
                ),
              ),
            );
          },
          child: Text('Open PhotoView Gallery'),
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vmR2n/btsIKSk2RBA/6xXqAcNovY0RgS7kFPQtgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vmR2n/btsIKSk2RBA/6xXqAcNovY0RgS7kFPQtgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vmR2n/btsIKSk2RBA/6xXqAcNovY0RgS7kFPQtgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvmR2n%2FbtsIKSk2RBA%2F6xXqAcNovY0RgS7kFPQtgK%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;382&quot; height=&quot;738&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;738&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://pub.dev/packages/photo_view&quot;&gt;photo_view 패키지 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://pub.dev/packages/cached_network_image&quot;&gt;cached_network_image 패키지 공식 문서&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;&lt;b&gt;결론&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;Flutter에서 Photo Gallery를 구현하는 것은 &lt;span&gt;photo_view&lt;/span&gt;와 같은 패키지를 사용하면 매우 간단합니다. 이 패키지를 사용하면 사용자에게 고급 이미지 뷰어 기능을 제공할 수 있으며, 다양한 소스에서 이미지를 로드하고 캐시할 수 있습니다. 이로 인해 사용자 경험이 향상됩니다.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/302</guid>
      <comments>https://juntcom.tistory.com/302#entry302comment</comments>
      <pubDate>Wed, 24 Jul 2024 15:27:22 +0900</pubDate>
    </item>
    <item>
      <title>flutter StatelessWidget 와 StatefulWidget 차이 및 설명</title>
      <link>https://juntcom.tistory.com/301</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 UI를 구성하는 주요 요소인 &lt;span&gt;StatelessWidget&lt;/span&gt;과 &lt;span&gt;StatefulWidget&lt;/span&gt;에 대해 설명하고, 이들이 상태(state)를 어떻게 관리하는지 정리해 보겠습니다.&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;1. StatelessWidget&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;span&gt;StatelessWidget&lt;/span&gt;**은 불변의 상태를 가지며, 빌드할 때마다 동일한 UI를 렌더링합니다. 상태가 변하지 않기 때문에 한 번 빌드된 후에는 UI가 변경되지 않습니다. 이는 주로 정적인 UI 요소를 렌더링할 때 사용됩니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;상태가 변하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;생성자에서 필요한 값을 받아 빌드 메서드에서 UI를 렌더링.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;상태를 가지지 않는 간단한 위젯에 적합.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721787943999&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class MyStatelessWidget extends StatelessWidget {
  final String title;

  MyStatelessWidget({required this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(
        child: Text('This is a stateless widget'),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: MyStatelessWidget(title: 'Stateless Example'),
  ));
}&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;2. StatefulWidget&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;span&gt;StatefulWidget&lt;/span&gt;**은 동적인 상태를 가지며, 상태가 변할 때마다 UI를 재빌드할 수 있습니다. 상태는 위젯 자체가 아닌 &lt;span&gt;State&lt;/span&gt; 객체에서 관리됩니다. &lt;span&gt;State&lt;/span&gt; 객체는 &lt;span&gt;StatefulWidget&lt;/span&gt;과 연결되어 있으며, 상태 변경 시 &lt;span&gt;setState&lt;/span&gt; 메서드를 호출하여 UI를 업데이트합니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;상태가 변할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;State&lt;/span&gt; 객체에서 상태를 관리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;UI가 상태에 따라 동적으로 변경될 필요가 있는 경우 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721787955938&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class MyStatefulWidget extends StatefulWidget {
  final String title;

  MyStatefulWidget({required this.title});

  @override
  _MyStatefulWidgetState createState() =&amp;gt; _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State&amp;lt;MyStatefulWidget&amp;gt; {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: MyStatefulWidget(title: 'Stateful Example'),
  ));
}&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;&lt;b&gt;3. State&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;span&gt;&lt;b&gt;State&lt;/b&gt;&lt;/span&gt; 클래스는 &lt;span&gt;StatefulWidget&lt;/span&gt;과 연결된 상태를 정의합니다. &lt;span&gt;State&lt;/span&gt; 객체는 &lt;span&gt;StatefulWidget&lt;/span&gt;의 생명주기 동안 유지되며, 상태가 변경될 때마다 &lt;span&gt;setState&lt;/span&gt; 메서드를 호출하여 UI를 재빌드합니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;생명주기&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;initState()&lt;/span&gt;: 처음으로 상태를 초기화할 때 호출.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;didChangeDependencies()&lt;/span&gt;: 종속성 또는 상위 객체가 변경될 때 호출.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build()&lt;/span&gt;: UI를 렌더링할 때 호출.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setState()&lt;/span&gt;: 상태를 변경하고 UI를 재빌드할 때 호출.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dispose()&lt;/span&gt;: 위젯이 소멸될 때 호출.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;예제&lt;/b&gt; (위에서 사용한 예제에 포함됨):&lt;/p&gt;
&lt;pre id=&quot;code_1721787985465&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _MyStatefulWidgetState extends State&amp;lt;MyStatefulWidget&amp;gt; {
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    // 초기화 작업 수행
  }

  @override
  void dispose() {
    // 정리 작업 수행
    super.dispose();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&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;638&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9B7k7/btsIKAjY1xc/WR3qpFkSb4DJMMBvlFOVcK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9B7k7/btsIKAjY1xc/WR3qpFkSb4DJMMBvlFOVcK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9B7k7/btsIKAjY1xc/WR3qpFkSb4DJMMBvlFOVcK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9B7k7%2FbtsIKAjY1xc%2FWR3qpFkSb4DJMMBvlFOVcK%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;638&quot; height=&quot;359&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;359&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;&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;874&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A9EQ9/btsIKX6WuUz/qCsRxFGrG8nvgpYeLI7Qp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A9EQ9/btsIKX6WuUz/qCsRxFGrG8nvgpYeLI7Qp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A9EQ9/btsIKX6WuUz/qCsRxFGrG8nvgpYeLI7Qp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA9EQ9%2FbtsIKX6WuUz%2FqCsRxFGrG8nvgpYeLI7Qp1%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;874&quot; height=&quot;786&quot; data-origin-width=&quot;874&quot; data-origin-height=&quot;786&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;StatelessWidget&lt;/b&gt;: 불변의 상태를 가지며, 정적인 UI를 렌더링하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;StatefulWidget&lt;/b&gt;: 동적인 상태를 가지며, 상태 변경에 따라 UI를 재빌드할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;State&lt;/b&gt;: &lt;span&gt;StatefulWidget&lt;/span&gt;과 연결된 상태를 관리하며, 상태 변경 시 UI를 업데이트합니다.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/301</guid>
      <comments>https://juntcom.tistory.com/301#entry301comment</comments>
      <pubDate>Wed, 24 Jul 2024 11:27:41 +0900</pubDate>
    </item>
    <item>
      <title>flutter firebase 를 이용한 이메일 로그인 구현</title>
      <link>https://juntcom.tistory.com/300</link>
      <description>&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;1. Firebase 프로젝트 설정&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;1.1 Firebase 콘솔에서 프로젝트 생성&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://console.firebase.google.com/&quot;&gt;&lt;span&gt;Firebase 콘솔&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;에 접속하여 새 프로젝트를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;프로젝트 이름을 입력하고, 필요한 설정을 완료합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5wdj1/btsIKMjGhFb/UGa8LefdEljSRFQQ35eWAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5wdj1/btsIKMjGhFb/UGa8LefdEljSRFQQ35eWAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5wdj1/btsIKMjGhFb/UGa8LefdEljSRFQQ35eWAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5wdj1%2FbtsIKMjGhFb%2FUGa8LefdEljSRFQQ35eWAk%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;506&quot; height=&quot;468&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;468&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YHEgW/btsIKIIlTV7/KSixeTRM5oxLNCHRD89RB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YHEgW/btsIKIIlTV7/KSixeTRM5oxLNCHRD89RB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YHEgW/btsIKIIlTV7/KSixeTRM5oxLNCHRD89RB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYHEgW%2FbtsIKIIlTV7%2FKSixeTRM5oxLNCHRD89RB1%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;457&quot; height=&quot;425&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;425&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;1.2 앱에 Firebase 추가&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;Firebase 프로젝트의 대시보드에서 Android 또는 iOS 앱을 추가합니다.&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;525&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bueFTv/btsIIjDzogR/bwXpcnWkkyb9Sz3O5wIcOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bueFTv/btsIIjDzogR/bwXpcnWkkyb9Sz3O5wIcOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bueFTv/btsIIjDzogR/bwXpcnWkkyb9Sz3O5wIcOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbueFTv%2FbtsIIjDzogR%2FbwXpcnWkkyb9Sz3O5wIcOk%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;525&quot; height=&quot;371&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;firebase cli 를 먼저 설치를 해줍니다.&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;a href=&quot;https://firebase.google.com/docs/cli?hl=ko&amp;amp;authuser=0&amp;amp;_gl=1*i16l2u*_ga*MTY1Njg1OTM5My4xNzIxNzEyMzA2*_ga_CW55HF8NVT*MTcyMTcxNjg4NS4yLjEuMTcyMTcxNzYxMi4zNy4wLjA.#windows-standalone-binary&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://firebase.google.com/docs/cli?hl=ko&amp;amp;authuser=0&amp;amp;_gl=1*i16l2u*_ga*MTY1Njg1OTM5My4xNzIxNzEyMzA2*_ga_CW55HF8NVT*MTcyMTcxNjg4NS4yLjEuMTcyMTcxNzYxMi4zNy4wLjA.#windows-standalone-binary&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 os 에 맞는 방식을 클릭 후 그대로 따라합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDLdDS/btsIKUPwX6Z/9jqoFfdKxJ2FRZlunKTlQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDLdDS/btsIKUPwX6Z/9jqoFfdKxJ2FRZlunKTlQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDLdDS/btsIKUPwX6Z/9jqoFfdKxJ2FRZlunKTlQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDLdDS%2FbtsIKUPwX6Z%2F9jqoFfdKxJ2FRZlunKTlQK%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;449&quot; height=&quot;323&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;564&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;firebase cli 를 설치했다면&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;firebase login&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;으로 구글 계정 로그인 하면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 로그인이 잘 되었는지 확인하기 위해 프로젝트 리스트를 조회합니다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot; style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;code&gt;firebase projects:list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;814&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd2Hmw/btsIJTjpeQ4/XGlFUh4EncsCXQa6OBScE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd2Hmw/btsIJTjpeQ4/XGlFUh4EncsCXQa6OBScE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd2Hmw/btsIJTjpeQ4/XGlFUh4EncsCXQa6OBScE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd2Hmw%2FbtsIJTjpeQ4%2FXGlFUh4EncsCXQa6OBScE1%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;814&quot; height=&quot;504&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;504&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;flutterfire ~~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flutterfire configure --project=todo-프로젝트아이디&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어를 치면 아래와 같이 나오고 원하는 os 선택 후 설치 진행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIFiTF/btsILuC1Cnj/SrOmn1Gdb8zahkIFhW0wHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIFiTF/btsILuC1Cnj/SrOmn1Gdb8zahkIFhW0wHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIFiTF/btsILuC1Cnj/SrOmn1Gdb8zahkIFhW0wHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIFiTF%2FbtsILuC1Cnj%2FSrOmn1Gdb8zahkIFhW0wHk%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;747&quot; height=&quot;183&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;183&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;해당 명령어가 잘 실행된다면 콘솔에는 아래와 같이 앱을 등록했다는 로그가 남습니다.&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;400&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LcViu/btsIIHRT0TM/0YMzO8HzXWOkHK5erWKAK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LcViu/btsIIHRT0TM/0YMzO8HzXWOkHK5erWKAK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LcViu/btsIIHRT0TM/0YMzO8HzXWOkHK5erWKAK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLcViu%2FbtsIIHRT0TM%2F0YMzO8HzXWOkHK5erWKAK1%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;400&quot; height=&quot;252&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M02m9/btsIJc5bs39/2jxJRq7HLaml51osdHH0c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M02m9/btsIJc5bs39/2jxJRq7HLaml51osdHH0c1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M02m9/btsIJc5bs39/2jxJRq7HLaml51osdHH0c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM02m9%2FbtsIJc5bs39%2F2jxJRq7HLaml51osdHH0c1%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;853&quot; height=&quot;474&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 작업이 완료되면 &lt;br /&gt;lib 폴더에 firebase_options.dart 가 생성된다고 하는데, 필자는 뜨지 않아서 새로 수동으로 만들어주었습니다. 아래 적었습니다.&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;안드로이드 에 firebase 권한 추가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;google-services.json 파일을 다운 후&amp;nbsp;&lt;br /&gt;android &amp;gt; app &amp;gt; 경로에 google-services.json 을 넣어줍니다.&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;802&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfiE3E/btsIKcXts4u/VidgaAw0zwMkaCDGiwQuvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfiE3E/btsIKcXts4u/VidgaAw0zwMkaCDGiwQuvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfiE3E/btsIKcXts4u/VidgaAw0zwMkaCDGiwQuvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfiE3E%2FbtsIKcXts4u%2FVidgaAw0zwMkaCDGiwQuvk%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;802&quot; height=&quot;538&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;538&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;프로젝트에 firebase sdk 추가&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;830&quot; data-origin-height=&quot;1112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUF250/btsIJvcpiQL/3fFUYWoMsvx3KgWKNkHWMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUF250/btsIJvcpiQL/3fFUYWoMsvx3KgWKNkHWMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUF250/btsIJvcpiQL/3fFUYWoMsvx3KgWKNkHWMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUF250%2FbtsIJvcpiQL%2F3fFUYWoMsvx3KgWKNkHWMk%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;731&quot; height=&quot;979&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;1112&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;settings.gradle 의 plugins 내부에 아래 코드를 넣어줍니다. 가이드에는 루트프로젝트의 build.gradle 라고 나옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;settings.gradle 에 수정&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;id &quot;com.google.gms.google-services&quot; version &quot;4.3.15&quot; apply false&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 과정에서 혹시 kotlin 버젼 이슈가 있다면 (아래 와 같이 버젼 이슈)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;integ.client.measurement_api_measurement_api.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.9.0, expected version is 1.7.1.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버젼을 settings.gradle 에서 올려주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;id &quot;org.jetbrains.kotlin.android&quot; version &quot;1.9.0&quot; apply false&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 프로젝트내 build.gradle 에 firebae 라이브러리 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;android &amp;gt; app &amp;gt; build.gradle&lt;/p&gt;
&lt;pre id=&quot;code_1721723302361&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation platform('com.google.firebase:firebase-bom:33.1.2')
    implementation 'com.google.firebase:firebase-analytics'
}&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;여기까지 추가 후 실행이 되면 firebase 사용준비는 완료되었습니다.&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;Authentication 시작&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;firebase 를 이용한 회원가입 및 로그인 기능을 두기 위해서는 firebase 에서 authentication 을 시작해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Authentication 설정&lt;/b&gt;: 좌측 메뉴에서 &lt;b&gt;Authentication&lt;/b&gt;을 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpSyuT/btsIKXZUnHD/ODWfUTcEaqCGGIKw4E4ucK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpSyuT/btsIKXZUnHD/ODWfUTcEaqCGGIKw4E4ucK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpSyuT/btsIKXZUnHD/ODWfUTcEaqCGGIKw4E4ucK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpSyuT%2FbtsIKXZUnHD%2FODWfUTcEaqCGGIKw4E4ucK%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;1434&quot; height=&quot;560&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;560&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;Sign-in Method&lt;/b&gt;: 상단 탭에서 &lt;b&gt;Sign-in method&lt;/b&gt;를 선택합니다. (로그인 방법 클릭)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2EM59/btsIKFLY0t7/CGhmSqsNfjjR14OUnHfnC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2EM59/btsIKFLY0t7/CGhmSqsNfjjR14OUnHfnC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2EM59/btsIKFLY0t7/CGhmSqsNfjjR14OUnHfnC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2EM59%2FbtsIKFLY0t7%2FCGhmSqsNfjjR14OUnHfnC0%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;1216&quot; height=&quot;551&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Email/Password&lt;/b&gt;: 이메일/비밀번호 인증이 활성화되어 있는지 확인합니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1039&quot; data-origin-height=&quot;469&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rIBsy/btsILwOm2Ds/J79Kv220lkjcIVW4I2L5Zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rIBsy/btsILwOm2Ds/J79Kv220lkjcIVW4I2L5Zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rIBsy/btsILwOm2Ds/J79Kv220lkjcIVW4I2L5Zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrIBsy%2FbtsILwOm2Ds%2FJ79Kv220lkjcIVW4I2L5Zk%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;1039&quot; height=&quot;469&quot; data-origin-width=&quot;1039&quot; data-origin-height=&quot;469&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&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; text-align: start;&quot;&gt;firebase_options.dart&lt;span&gt;&amp;nbsp;수동생성&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;lib 폴더에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;firebase_options.dart&lt;span&gt;&amp;nbsp; 파일을 생성합니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721734626866&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// lib/firebase_options.dart

import 'package:firebase_core/firebase_core.dart';

class DefaultFirebaseOptions {
  static FirebaseOptions get currentPlatform {
    return const FirebaseOptions(
      apiKey: 'AIzaSyBbHR~~~~~~~~~~~~~',
      appId: '1:508050~~~~:android:7201919812b~~~~~~',
      messagingSenderId: '50805~~~~~~',
      projectId: 'todo-~~~~',
    );
  }
}&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;google-services.json 파일안에 해당 값들이 존재 합니다.&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;br /&gt;register_page.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'login_page.dart';

class RegisterPage extends StatefulWidget {
  @override
  _RegisterPageState createState() =&amp;gt; _RegisterPageState();
}

class _RegisterPageState extends State&amp;lt;RegisterPage&amp;gt; {
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final FirebaseAuth _auth = FirebaseAuth.instance;

  Future&amp;lt;void&amp;gt; _register() async {
    try {
      UserCredential userCredential = await _auth.createUserWithEmailAndPassword(
        email: _emailController.text,
        password: _passwordController.text,
      );
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Successfully registered!')),
      );
      // 회원가입 후 원하는 페이지로 이동
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) =&amp;gt; LoginPage()),
      );
    } on FirebaseAuthException catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to register: ${e.message}')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Register')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              decoration: InputDecoration(labelText: 'Email'),
            ),
            TextField(
              controller: _passwordController,
              decoration: InputDecoration(labelText: 'Password'),
              obscureText: true,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _register,
              child: Text('Register'),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;/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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;login_page.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() =&amp;gt; _LoginPageState();
}

class _LoginPageState extends State&amp;lt;LoginPage&amp;gt; {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  Future&amp;lt;void&amp;gt; _login() async {
    try {
      UserCredential userCredential = await _auth.signInWithEmailAndPassword(
        email: _emailController.text,
        password: _passwordController.text,
      );
      // 로그인 성공 시 처리
      Navigator.of(context).pushReplacement(
        MaterialPageRoute(builder: (context) =&amp;gt; TodoListScreen()), // ListPage로 이동
      );
    } on FirebaseAuthException catch (e) {
      String message;
      switch (e.code) {
        case 'user-not-found':
          message = 'No user found for that email.';
          break;
        case 'wrong-password':
          message = 'Wrong password provided for that user.';
          break;
        default:
          message = 'An error occurred. Please try again.';
      }
      // 에러 메시지를 스낵바로 출력
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(message)),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('An error occurred. Please try again.')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextField(
              controller: _emailController,
              decoration: InputDecoration(labelText: 'Email'),
            ),
            TextField(
              controller: _passwordController,
              decoration: InputDecoration(labelText: 'Password'),
              obscureText: true,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _login,
              child: Text('Login'),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main 메소드&amp;nbsp;&lt;br /&gt;firebase 메소드를 추가해줍니다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;void main() async{

  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(
      child: MyApp(),
    ),
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Firebase</category>
      <category>Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/300</guid>
      <comments>https://juntcom.tistory.com/300#entry300comment</comments>
      <pubDate>Tue, 23 Jul 2024 20:53:29 +0900</pubDate>
    </item>
    <item>
      <title>flutter todo-app 만들기</title>
      <link>https://juntcom.tistory.com/299</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 앱은 &amp;ldquo;할 일 목록 (To-Do List)&amp;rdquo; 앱으로, 기본적인 CRUD(생성, 읽기, 업데이트, 삭제) 작업을 포함할 것입니다.&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;샘플 앱: To-Do List&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;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;Flutter 프로젝트를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721698204045&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter create todo_app
cd todo_app&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;&lt;b&gt;2. 주요 패키지 추가&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;span&gt;pubspec.yaml&lt;/span&gt; 파일에 필요한 패키지를 추가합니다. 이 예제에서는 &lt;span&gt;provider&lt;/span&gt;를 상태 관리를 위해 사용합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;dev_dependencies:
  flutter_test:
    sdk: flutter
  provider:
  uuid:&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &lt;span&gt;flutter pub get&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;b&gt;3. 모델 정의&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;/p&gt;
&lt;pre id=&quot;code_1721698264682&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// lib/models/todo.dart
class Todo {
  String id;
  String title;
  bool isDone;

  Todo({
    required this.id,
    required this.title,
    this.isDone = false,
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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;span&gt;ChangeNotifier&lt;/span&gt; 클래스를 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721698299367&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/foundation.dart';
import '../model/todo.dart';

// 할 일 목록을 관리하는 ChangeNotifier 클래스
class TodoProvider with ChangeNotifier {
  List&amp;lt;Todo&amp;gt; _todos = [];

  List&amp;lt;Todo&amp;gt; get todos =&amp;gt; _todos;

  // 할 일 항목을 추가하는 메서드
  void addTodo(Todo todo) {
    _todos.add(todo);
    notifyListeners();
  }

  // 할 일 항목의 완료 상태를 토글하는 메서드
  void toggleTodoStatus(String id) {
    final index = _todos.indexWhere((todo) =&amp;gt; todo.id == id);
    if (index != -1) {
      _todos[index].isDone = !_todos[index].isDone;
      notifyListeners();
    }
  }

  // 할 일 항목을 제거하는 메서드
  void removeTodo(String id) {
    _todos.removeWhere((todo) =&amp;gt; todo.id == id);
    notifyListeners();
  }
// 업데이트
  void updateTodo(Todo updatedTodo) {
    final index = _todos.indexWhere((todo) =&amp;gt; todo.id == updatedTodo.id);
    if (index != -1) {
      _todos[index] = updatedTodo;
      notifyListeners();
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 if 문에 -1 확인 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f 문을 사용하여 &lt;span&gt;index&lt;/span&gt;가 &lt;span&gt;-1&lt;/span&gt;이 아닌지 확인하는 이유는 &lt;span&gt;indexWhere&lt;/span&gt; 메서드가 일치하는 항목을 찾지 못할 때 &lt;span&gt;-1&lt;/span&gt;을 반환하기 때문입니다. &lt;span&gt;index&lt;/span&gt;가 &lt;span&gt;-1&lt;/span&gt;이면 &lt;span&gt;_todos[index]&lt;/span&gt;는 유효하지 않으며, 이 경우 배열에 접근하려고 하면 오류가 발생합니다. 따라서 if 문은 안전한 코드를 작성하는 데 중요합니다.&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&gt;indexWhere&lt;/span&gt;가 항상 올바른 값을 반환한다고 확신할 수 있다면 이 조건문을 생략할 수 있지만, 이는 코드의 안정성과 유연성을 감소시킬 수 있습니다. 일반적으로는 if 문을 포함하는 것이 좋습니다.&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;5. 메인 파일 설정&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;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_todo/screen/todo_list_screen.dart';
import 'package:provider/provider.dart';
import 'package:flutter_todo/providers/todo_provider.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        // TodoProvider를 ChangeNotifierProvider로 제공
        ChangeNotifierProvider(create: (_) =&amp;gt; TodoProvider()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'To-Do App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TodoListScreen(),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. UI 작성&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;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;// lib/screens/todo_list_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_todo/screen/todo_edit_screen.dart';
import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart';

import '../model/todo.dart';
import '../providers/todo_provider.dart';

class TodoListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final todoProvider = Provider.of&amp;lt;TodoProvider&amp;gt;(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('To-Do List'),
      ),
      body: ListView.builder(
        itemCount: todoProvider.todos.length, // 할 일 목록의 개수
        itemBuilder: (ctx, i) =&amp;gt; ListTile(
          title: Text(todoProvider.todos[i].title), // 할 일 제목
          trailing: Checkbox(
            value: todoProvider.todos[i].isDone, // 완료 상태
            onChanged: (_) {
              todoProvider.toggleTodoStatus(todoProvider.todos[i].id); // 완료 상태 토글
            },
          ),
          onTap: () {
            // 수정 페이지로 이동
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) =&amp;gt; EditTodoScreen(todo: todoProvider.todos[i]),
              ),
            );
          },
          onLongPress: () {
            todoProvider.removeTodo(todoProvider.todos[i].id); // 할 일 항목 제거
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showDialog(
            context: context,
            builder: (ctx) {
              TextEditingController controller = TextEditingController();
              return AlertDialog(
                title: Text('Add Todo'),
                content: TextField(
                  controller: controller,
                  decoration: InputDecoration(labelText: 'Title'),
                ),
                actions: [
                  TextButton(
                    onPressed: () {
                      if (controller.text.isNotEmpty) {
                        final newTodo = Todo(
                          id: Uuid().v4(),
                          title: controller.text,
                        );
                        todoProvider.addTodo(newTodo);
                        Navigator.of(ctx).pop();
                      }
                    },
                    child: Text('Add'),
                  ),
                ],
              );
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&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;span&gt;새로운 파일 &lt;/span&gt;edit_todo_screen.dart&lt;span&gt;를 생성합니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/todo.dart';
import '../providers/todo_provider.dart';

class EditTodoScreen extends StatelessWidget {
  final Todo todo;

  EditTodoScreen({required this.todo});

  @override
  Widget build(BuildContext context) {
    TextEditingController titleController = TextEditingController(text: todo.title);

    return Scaffold(
      appBar: AppBar(
        title: Text('Edit Todo'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: titleController,
              decoration: InputDecoration(labelText: 'Title'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                if (titleController.text.isNotEmpty) {
                  final updatedTodo = Todo(
                    id: todo.id,
                    title: titleController.text,
                    isDone: todo.isDone,
                  );
                  Provider.of&amp;lt;TodoProvider&amp;gt;(context, listen: false).updateTodo(updatedTodo);
                  Navigator.of(context).pop();
                }
              },
              child: Text('Update'),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p 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/pT5p8/btsIIi49i0r/I1OUd1Jm1pNHpCGNrAQRR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pT5p8/btsIIi49i0r/I1OUd1Jm1pNHpCGNrAQRR1/img.png&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;662&quot; data-is-animation=&quot;false&quot; style=&quot;width: 27.2426%; margin-right: 10px;&quot; data-widthpercent=&quot;27.56&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pT5p8/btsIIi49i0r/I1OUd1Jm1pNHpCGNrAQRR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpT5p8%2FbtsIIi49i0r%2FI1OUd1Jm1pNHpCGNrAQRR1%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;332&quot; height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQkzFt/btsIHXAfMwb/4qFTt6aHCZyE84CSELcjN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQkzFt/btsIHXAfMwb/4qFTt6aHCZyE84CSELcjN0/img.png&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;239&quot; data-is-animation=&quot;false&quot; style=&quot;width: 71.5946%;&quot; data-widthpercent=&quot;72.44&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQkzFt/btsIHXAfMwb/4qFTt6aHCZyE84CSELcjN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQkzFt%2FbtsIHXAfMwb%2F4qFTt6aHCZyE84CSELcjN0%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;315&quot; height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Flutter</category>
      <category>flutter todo app</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/299</guid>
      <comments>https://juntcom.tistory.com/299#entry299comment</comments>
      <pubDate>Tue, 23 Jul 2024 10:42:19 +0900</pubDate>
    </item>
    <item>
      <title>flutter scrollview pagination 예제 및 정리</title>
      <link>https://juntcom.tistory.com/298</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 스크롤 뷰를 사용한 페이지네이션은 앱이 많은 양의 데이터를 표시할 때 유용한 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ScrollController&lt;/b&gt;: 스크롤 위치를 추적하고 스크롤 이벤트를 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ListView.builder&lt;/b&gt;: 동적으로 리스트 아이템을 생성하여 성능을 최적화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;HTTP 요청&lt;/b&gt;: 데이터를 페이징하여 서버에서 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;상태 관리&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;프로젝트 설정&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;필요한 패키지 추가: &lt;span&gt;http&lt;/span&gt;, &lt;span&gt;provider&lt;/span&gt; (옵션)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ScrollController 설정&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;스크롤 이벤트를 감지하고, 사용자가 리스트 끝에 도달하면 새로운 데이터를 로드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ListView.builder&lt;/b&gt; 사용**:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;리스트 아이템을 동적으로 생성하여 메모리 사용을 최적화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;데이터 로드&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;서버에서 데이터를 페이징하여 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;상태 관리&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;StatefulWidget&lt;/span&gt; 또는 상태 관리 패키지(예: &lt;span&gt;provider&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;b&gt;코드 예제&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721654641807&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  final ScrollController _scrollController = ScrollController();
  List _data = [];
  int _page = 1;
  bool _isLoading = false;
  bool _hasMoreData = true;

  @override
  void initState() {
    super.initState();
    _fetchData();
    _scrollController.addListener(() {
      if (_scrollController.position.extentAfter &amp;lt; 300 &amp;amp;&amp;amp; !_isLoading &amp;amp;&amp;amp; _hasMoreData) {
        _fetchData();
      }
    });
  }

  Future&amp;lt;void&amp;gt; _fetchData() async {
    setState(() {
      _isLoading = true;
    });

    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/albums?_page=$_page&amp;amp;_limit=10'));
    if (response.statusCode == 200) {
      List newData = json.decode(response.body);
      setState(() {
        _data.addAll(newData);
        _isLoading = false;
        if (newData.length &amp;lt; 10) {
          _hasMoreData = false;
        } else {
          _page++;
        }
      });
    } else {
      setState(() {
        _isLoading = false;
        _hasMoreData = false;
      });
    }
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pagination Example'),
      ),
      body: _data.isEmpty &amp;amp;&amp;amp; _isLoading
          ? Center(child: CircularProgressIndicator())
          : ListView.builder(
              controller: _scrollController,
              itemCount: _data.length + (_hasMoreData ? 1 : 0),
              itemBuilder: (context, index) {
                if (index == _data.length) {
                  return Center(child: CircularProgressIndicator());
                }
                return ListTile(
                  title: Text('Item ${_data[index]['id']}'),
                  subtitle: Text(_data[index]['title']),
                );
              },
            ),
    );
  }
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ScrollController 설정&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;스크롤 이벤트를 감지하고, 사용자가 리스트 끝에 도달하면 &lt;span&gt;_fetchData&lt;/span&gt; 함수를 호출하여 다음 페이지 데이터를 로드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ListView.builder&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;itemCount&lt;/span&gt;를 설정하여 로딩 중인 상태 표시를 위한 추가 아이템을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;itemBuilder&lt;/span&gt;에서 로딩 인디케이터를 표시하여 더 많은 데이터를 로드 중임을 사용자에게 알립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;데이터 로드 및 상태 관리&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;데이터를 로드하는 동안 &lt;span&gt;_isLoading&lt;/span&gt; 상태를 업데이트하여 UI에서 로딩 인디케이터를 표시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;새로운 데이터를 가져와서 &lt;span&gt;_data&lt;/span&gt; 리스트에 추가하고, 페이지 수를 증가시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;더 이상 가져올 데이터가 없을 경우 &lt;span&gt;_hasMoreData&lt;/span&gt;를 &lt;span&gt;false&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;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt;  {

  final _url = 'https://jsonplaceholder.typicode.com/albums';
  int _page = 1;
  final int _limit = 20;
  bool _hasNextPage = true; // 다음 페이지가 있는지 여부
  bool _isFirstLoadRunning = false; // 첫번째 페이지 로딩중
  bool _isLoadMoreRunning = false; // 다음페이지 로딩중
  List _albumList = [];
  late ScrollController _controller;

  @override
  void initState() {
    super.initState();
    initLoad();
    _controller = ScrollController()..addListener(_nextLoad);
  }


  void initLoad() async {
    setState(() {
      _isFirstLoadRunning = true;
    });
    try {
      final res = await http.get(Uri.parse(&quot;$_url?_page=$_page&amp;amp;_limit=$_limit&quot;));
      setState(() {
        _albumList = jsonDecode(res.body);
      });

    } catch(e) {
      print(e.toString());
    }

    setState(() {
      _isFirstLoadRunning = false;
    });
  }

  void _nextLoad() async {
    print(&quot;nextLoad&quot;);
    if(_hasNextPage &amp;amp;&amp;amp; !_isFirstLoadRunning &amp;amp;&amp;amp; !_isLoadMoreRunning
      &amp;amp;&amp;amp; _controller.position.extentAfter &amp;lt; 100) {
      setState(() {
        _isLoadMoreRunning = true;
      });
      _page += 1;
      try {
        final res = await http.get(Uri.parse(&quot;$_url?_page=$_page&amp;amp;_list=$_limit&quot;));
        final List fetchedAlbums = json.decode(res.body);
        if(fetchedAlbums.isNotEmpty) {
          setState(() {
            _albumList.addAll(fetchedAlbums);
          });
        } else { // 데이터가 비어있는 경우
          setState(() {
            _hasNextPage = false;
          });
        }
      } catch(e) {
        print(e.toString());
      }

      setState(() {
        _isLoadMoreRunning = false;
      });

    }
  }

  // 페이지 종료 시 종료해 주는 기능
  @override
  void dispose() {
    super.dispose();
    _controller.removeListener(_nextLoad);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text(&quot;test title&quot;),
        ),
        body: _isFirstLoadRunning
        ? const Center(
          child: CircularProgressIndicator(),
        )
        : Column(
          children: [
            Expanded(
                child: ListView.builder(
                  controller: _controller,
                  itemCount: _albumList.length,
                  itemBuilder: (context, index) =&amp;gt; Card(
                    margin: const EdgeInsets.symmetric(vertical: 0, horizontal: 10),
                    child: ListTile(
                      title: Text(_albumList[index][&quot;id&quot;].toString()),
                      subtitle: Text(_albumList[index][&quot;title&quot;]),
                    ),
                  )
                )
            ),
            if (_isLoadMoreRunning == true)
              Container(
                padding: const EdgeInsets.all(30),
                child: const Center(
                  child: CircularProgressIndicator(),
                )
              ),
            if (_hasNextPage == false)
              Container(
                  padding: const EdgeInsets.all(20),
                  child: const Center(
                    child: Text(
                      &quot;No more data to be fetched&quot;,
                      style: TextStyle(
                        color: Colors.white
                      )
                    ),
                  )
              )
          ],
        )
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs/cookbook/lists/long-lists&quot;&gt;Flutter Documentation - ListView&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/ScrollController-class.html&quot;&gt;Flutter Documentation - ScrollController&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://pub.dev/packages/http&quot;&gt;Flutter HTTP Package&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/298</guid>
      <comments>https://juntcom.tistory.com/298#entry298comment</comments>
      <pubDate>Mon, 22 Jul 2024 22:34:38 +0900</pubDate>
    </item>
    <item>
      <title>flutter mvvm 패턴 정리 및 예제</title>
      <link>https://juntcom.tistory.com/297</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM(Model-View-ViewModel) 패턴은 UI와 비즈니스 로직을 분리하는 소프트웨어 아키텍처 패턴입니다. Flutter에서 MVVM 패턴을 사용하면 코드의 가독성과 유지보수성을 높이고, 테스트 용이성을 개선할 수 있습니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Model&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;데이터 구조와 비즈니스 로직을 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;데이터 소스(예: 데이터베이스, API)와 상호작용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;View&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;사용자 인터페이스를 담당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;사용자 입력을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;ViewModel에서 제공하는 데이터를 표시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ViewModel&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;View와 Model 간의 중개자 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Model에서 데이터를 가져와서 View에 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;사용자 입력을 처리하고, Model을 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChangeNotifier&lt;/span&gt;를 상속받아 데이터 변경 시 View에 알립니다.&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;MVVM 패턴의 장점&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;유지보수성 향상&lt;/b&gt;: UI와 비즈니스 로직이 분리되어 코드를 더 쉽게 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;재사용성&lt;/b&gt;: ViewModel과 Model은 특정 UI에 의존하지 않으므로 재사용이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;테스트 용이성&lt;/b&gt;: UI와 독립적으로 ViewModel과 Model을 테스트할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;유연성&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;Flutter에서 MVVM 패턴 구현 예제&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;1. Model&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;Model 클래스는 데이터를 정의하고, 데이터와 상호작용하는 로직을 포함합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721650281492&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User {
  String username;
  String email;

  User({required this.username, required this.email});
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. ViewModel&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;ViewModel 클래스는 &lt;span&gt;ChangeNotifier&lt;/span&gt;를 상속받아 상태 변화를 알립니다. View와 Model 간의 데이터 처리를 담당합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721650300454&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'model/user.dart';

class UserViewModel extends ChangeNotifier {
  User _user = User(username: '', email: '');

  String get username =&amp;gt; _user.username;
  String get email =&amp;gt; _user.email;

  void setUsername(String username) {
    _user.username = username;
    notifyListeners();
  }

  void setEmail(String email) {
    _user.email = email;
    notifyListeners();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. View&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;View 클래스는 사용자 인터페이스를 정의하고, ViewModel과 상호작용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721650318449&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'viewmodel/user_viewmodel.dart';

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('MVVM Example')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              onChanged: (value) {
                context.read&amp;lt;UserViewModel&amp;gt;().setUsername(value);
              },
              decoration: InputDecoration(labelText: 'Username'),
            ),
            TextField(
              onChanged: (value) {
                context.read&amp;lt;UserViewModel&amp;gt;().setEmail(value);
              },
              decoration: InputDecoration(labelText: 'Email'),
            ),
            SizedBox(height: 20),
            Consumer&amp;lt;UserViewModel&amp;gt;(
              builder: (context, viewModel, child) {
                return Column(
                  children: [
                    Text('Username: ${viewModel.username}'),
                    Text('Email: ${viewModel.email}'),
                  ],
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) =&amp;gt; UserViewModel(),
      child: MaterialApp(home: MyHomePage()),
    ),
  );
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Model&lt;/b&gt;: 데이터 구조와 비즈니스 로직을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;View&lt;/b&gt;: 사용자 인터페이스를 담당하며, 사용자 입력을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;ViewModel&lt;/b&gt;: Model과 View 간의 중개자로서, 데이터를 처리하고 View에 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;br /&gt;datasource&amp;nbsp;&lt;br /&gt;- datasource.dart&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;repository&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- album_datasource_repository.dart&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewModel&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- album_view_model.dart&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- album_mvvm_view.dart&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;datasource.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:flutter_lecture/model/album.dart';

class Datasource {

  Future&amp;lt;List&amp;lt;Album&amp;gt;&amp;gt; getAlbumList() async {
    final response = await http
        .get(Uri.parse(&quot;https://jsonplaceholder.typicode.com/albums&quot;));

    return jsonDecode(response.body)
        .map&amp;lt;Album&amp;gt;((json) =&amp;gt; Album.fromJson(json))
        .toList();
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;album_datasource_repository.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;import 'package:flutter_lecture/datasource/datasource.dart';

import '../model/album.dart';

class AlbumDatasourceRepository {
  final Datasource _datasource = Datasource();


  Future&amp;lt;List&amp;lt;Album&amp;gt;&amp;gt; getAlbumList() {
    return _datasource.getAlbumList();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;album_view_model.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;
import 'package:flutter/cupertino.dart';
import 'package:flutter_lecture/repository/album_datasource_repository.dart';

import '../model/album.dart';

class AlbumViewModel with ChangeNotifier {
  late final AlbumDatasourceRepository _albumRepository;
  List&amp;lt;Album&amp;gt; _albumList = List.empty(growable: true);
  List&amp;lt;Album&amp;gt; get albumList =&amp;gt; _albumList;



  AlbumViewModel() {
    _albumRepository = AlbumDatasourceRepository();
    _getAlbumList();
  }

  Future&amp;lt;void&amp;gt; _getAlbumList() async{
    _albumList = await _albumRepository.getAlbumList();
    notifyListeners();
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;album_mvvm_view.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_lecture/model/albums.dart';
import 'package:flutter_lecture/bloc/album_bloc.dart';
import 'package:flutter_lecture/viewModel/album_view_model.dart';
import 'package:provider/provider.dart';

import '../model/album.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  late List&amp;lt;Album&amp;gt; albumList;

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider&amp;lt;AlbumViewModel&amp;gt;(
        create: ((context) =&amp;gt; AlbumViewModel()),
        child: Scaffold(
          appBar: AppBar(
            title: const Text(&quot;test title&quot;),
          ),
          body: Consumer&amp;lt;AlbumViewModel&amp;gt;(
            builder: (context, provider, child) {
                albumList = provider.albumList;
                return ListView.builder(
                  itemCount: albumList.length,
                  itemBuilder: (context, index) {
                    return Container(
                      padding: const EdgeInsets.all(10),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(&quot;ID: ${albumList[index].id.toString()}&quot;),
                          Text(&quot;Title: ${albumList[index].title}&quot; )
                        ],
                      ),
                    );
                  }
                );
              },
          ),
        ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs&quot;&gt;Flutter 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel&quot;&gt;MVVM 패턴&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;이 패턴을 사용하면 Flutter 애플리케이션의 구조를 더 명확하게 만들고, 유지보수와 테스트를 더 쉽게 할 수 있습니다.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/297</guid>
      <comments>https://juntcom.tistory.com/297#entry297comment</comments>
      <pubDate>Mon, 22 Jul 2024 21:19:55 +0900</pubDate>
    </item>
    <item>
      <title>flutter form 제출 및 라우터 이동</title>
      <link>https://juntcom.tistory.com/296</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 Form 데이터를 저장하고 성공 페이지로 이동하는 과정은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 Form, TextFormField, Navigator 등을 사용합니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Form 생성&lt;/b&gt;: 사용자로부터 입력을 받을 Form을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Form 데이터 저장&lt;/b&gt;: 사용자가 입력한 데이터를 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;성공 페이지로 이동&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. main.dart 파일&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;span&gt;main.dart&lt;/span&gt; 파일에서는 초기 화면을 설정하고, 라우트를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721640899932&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'form_page.dart';
import 'success_page.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) =&amp;gt; MyHomePage(),
        '/success': (context) =&amp;gt; SuccessPage(),
      },
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. form_page.dart 파일&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;span&gt;form_page.dart&lt;/span&gt; 파일에서는 Form을 정의하고, 데이터를 저장한 후 성공 페이지로 이동합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721640930635&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  final _key = GlobalKey&amp;lt;FormState&amp;gt;();
  late String _username, _email;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&quot;Form Page&quot;),
      ),
      body: Container(
        padding: const EdgeInsets.all(15),
        child: Form(
          key: _key,
          child: Column(
            children: [
              usernameInput(),
              const SizedBox(height: 15),
              emailInput(),
              const SizedBox(height: 15),
              submitButton()
            ],
          ),
        ),
      ),
    );
  }

  Widget usernameInput() {
    return TextFormField(
      autofocus: true,
      validator: (val) {
        if (val == null || val.isEmpty) {
          return 'The input is empty';
        } else {
          return null;
        }
      },
      onSaved: (username) =&amp;gt; _username = username ?? '',
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        hintText: 'Input your username',
        labelText: 'username',
        labelStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
      ),
    );
  }

  Widget emailInput() {
    return TextFormField(
      autofocus: true,
      validator: (val) {
        if (val == null || val.isEmpty) {
          return 'The input is empty';
        } else {
          return null;
        }
      },
      onSaved: (email) =&amp;gt; _email = email ?? '',
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        hintText: 'Input your email address',
        labelText: 'email address',
        labelStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
      ),
    );
  }

  Widget submitButton() {
    return ElevatedButton(
      onPressed: () {
        if (_key.currentState!.validate()) {
          _key.currentState!.save();
          Navigator.pushNamed(
            context,
            '/success',
            arguments: {'username': _username, 'email': _email},
          );
        }
      },
      child: Container(
        padding: const EdgeInsets.all(15),
        child: const Text('Submit'),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. success_page.dart 파일&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;span&gt;success_page.dart&lt;/span&gt; 파일에서는 성공적으로 데이터를 저장한 후 이동하는 페이지를 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721640966794&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class SuccessPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final Map&amp;lt;String, String&amp;gt; args =
        ModalRoute.of(context)!.settings.arguments as Map&amp;lt;String, String&amp;gt;;

    return Scaffold(
      appBar: AppBar(
        title: const Text(&quot;Success Page&quot;),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(&quot;Username: ${args['username']}&quot;),
            Text(&quot;Email: ${args['email']}&quot;),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('Back to Home'),
            ),
          ],
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Form 위젯 사용&lt;/b&gt;: &lt;span&gt;Form&lt;/span&gt; 위젯을 사용하여 사용자 입력을 받습니다. &lt;span&gt;GlobalKey&amp;lt;FormState&amp;gt;&lt;/span&gt;를 사용하여 Form의 상태를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Validator 및 onSaved 사용&lt;/b&gt;: &lt;span&gt;TextFormField&lt;/span&gt; 위젯의 &lt;span&gt;validator&lt;/span&gt; 속성을 사용하여 입력 검증을 수행하고, &lt;span&gt;onSaved&lt;/span&gt; 속성을 사용하여 데이터를 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Navigator 사용&lt;/b&gt;: &lt;span&gt;Navigator.pushNamed&lt;/span&gt;를 사용하여 성공 페이지로 이동합니다. 이때 &lt;span&gt;arguments&lt;/span&gt;를 사용하여 입력된 데이터를 성공 페이지로 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;성공 페이지에서 데이터 표시&lt;/b&gt;: 성공 페이지에서 &lt;/span&gt;ModalRoute.of(context)!.settings.arguments&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 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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 Flutter에서 Form 데이터를 저장하고, 성공 페이지로 이동하는 일반적인 방법을 설명합니다. 이를 통해 사용자로부터 입력을 받고, 입력된 데이터를 다음 페이지로 전달하여 표시할 수 있습니다.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/296</guid>
      <comments>https://juntcom.tistory.com/296#entry296comment</comments>
      <pubDate>Mon, 22 Jul 2024 18:41:05 +0900</pubDate>
    </item>
    <item>
      <title>flutter webview 사용방법</title>
      <link>https://juntcom.tistory.com/295</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 WebView를 사용하는 방법을 정리하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebView는 애플리케이션 내에서 웹 콘텐츠를 표시할 수 있게 해주는 위젯입니다. 이를 통해 Flutter 앱에서 웹 페이지를 렌더링할 수 있습니다.&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;1. WebView 패키지 추가&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;span&gt;먼저 &lt;/span&gt;webview_flutter&lt;span&gt; 패키지를 &lt;/span&gt;pubspec.yaml&lt;span&gt; 파일에 추가합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721635155863&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  webview_flutter: ^4.0.0  # 최신 버전 확인 필요&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2 플랫폼별 설정&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;iOS (ios/Runner/Info.plist):&lt;/h4&gt;
&lt;pre id=&quot;code_1721635178702&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;key&amp;gt;NSAppTransportSecurity&amp;lt;/key&amp;gt;
&amp;lt;dict&amp;gt;
  &amp;lt;key&amp;gt;NSAllowsArbitraryLoads&amp;lt;/key&amp;gt;
  &amp;lt;true/&amp;gt;
&amp;lt;/dict&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Android (android/app/build.gradle):&lt;/h4&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1721635190962&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {
    defaultConfig {
        minSdkVersion 19
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 기본 사용법&lt;/p&gt;
&lt;pre id=&quot;code_1721635220204&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewExample extends StatefulWidget {
  @override
  _WebViewExampleState createState() =&amp;gt; _WebViewExampleState();
}

class _WebViewExampleState extends State&amp;lt;WebViewExample&amp;gt; {
  late WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse('https://flutter.dev'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('WebView Example')),
      body: WebViewWidget(controller: controller),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 주요 기능&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 JavaScript 활성화/비활성화&lt;/h3&gt;
&lt;pre id=&quot;code_1721635240716&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;controller.setJavaScriptMode(JavaScriptMode.unrestricted);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 로딩 프로그레스 모니터링&lt;/h3&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1721635250298&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;NavigationDelegate(
  onProgress: (int progress) {
    print('WebView is loading (progress : $progress%)');
  },
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 페이지 로딩 이벤트&lt;/h3&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1721635263663&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;NavigationDelegate(
  onPageStarted: (String url) { print('Page started loading: $url'); },
  onPageFinished: (String url) { print('Page finished loading: $url'); },
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.4 에러 처리&lt;/h3&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1721635273692&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;NavigationDelegate(
  onWebResourceError: (WebResourceError error) {
    print('Error: ${error.description}');
  },
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.5 쿠키 관리&lt;/h3&gt;
&lt;pre id=&quot;code_1721635291599&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final cookieManager = WebViewCookieManager();
await cookieManager.setCookie(WebViewCookie(name: 'foo', value: 'bar', domain: 'example.com'));&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 고급 기능&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 JavaScript과 Flutter 간 통신&lt;/h3&gt;
&lt;pre id=&quot;code_1721635308059&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;controller.addJavaScriptChannel(
  'Toaster',
  onMessageReceived: (JavaScriptMessage message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message.message)),
    );
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.2 사용자 지정 사용자 에이전트&lt;/p&gt;
&lt;pre id=&quot;code_1721635320133&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;controller.setUserAgent('My Custom User Agent');&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;NavigationDelegate 예시&lt;/h2&gt;
&lt;pre id=&quot;code_1721635366269&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WebViewExample extends StatefulWidget {
  const WebViewExample({Key? key}) : super(key: key);

  @override
  State&amp;lt;WebViewExample&amp;gt; createState() =&amp;gt; _WebViewExampleState();
}

class _WebViewExampleState extends State&amp;lt;WebViewExample&amp;gt; {
  late WebViewController controller;

  @override
  void initState() {
    super.initState();
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            // 웹 페이지 로딩 진행률 업데이트
            print('WebView is loading (progress : $progress%)');
          },
          onPageStarted: (String url) {
            // 페이지 로딩 시작
            print('Page started loading: $url');
          },
          onPageFinished: (String url) {
            // 페이지 로딩 완료
            print('Page finished loading: $url');
          },
          onWebResourceError: (WebResourceError error) {
            // 웹 리소스 로딩 중 오류 발생
            print('Error: ${error.description}');
          },
          onNavigationRequest: (NavigationRequest request) {
            // 특정 도메인으로의 이동을 막고 싶을 때 사용
            if (request.url.startsWith('https://www.youtube.com/')) {
              print('Blocking navigation to ${request.url}');
              return NavigationDecision.prevent;
            }
            print('Allowing navigation to ${request.url}');
            return NavigationDecision.navigate;
          },
        ),
      )
      ..loadRequest(Uri.parse('https://flutter.dev'));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WebView Example'),
      ),
      body: WebViewWidget(controller: controller),
    );
  }
}&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;NavigationDelegate는 WebViewController를 설정할 때 setNavigationDelegate 메서드를 통해 설정합니다. 일반적으로 WebViewController를 초기화하는 부분에 넣습니다. 주로 initState 메서드 내에서 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 NavigationDelegate를 올바르게 설정하는 방법을 보여주는 예제입니다:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;WebView NavigationDelegate 예제&lt;/div&gt;
&lt;div&gt;Click to open code&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서 주목해야 할 점들은 다음과 같습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;NavigationDelegate는 WebViewController의 setNavigationDelegate 메서드를 통해 설정됩니다.&lt;/li&gt;
&lt;li&gt;initState 메서드 내에서 WebViewController를 초기화하고 설정합니다.&lt;/li&gt;
&lt;li&gt;NavigationDelegate는 여러 콜백 함수를 포함할 수 있습니다:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;onProgress: 페이지 로딩 진행률을 추적합니다.&lt;/li&gt;
&lt;li&gt;onPageStarted: 페이지 로딩이 시작될 때 호출됩니다.&lt;/li&gt;
&lt;li&gt;onPageFinished: 페이지 로딩이 완료될 때 호출됩니다.&lt;/li&gt;
&lt;li&gt;onWebResourceError: 웹 리소스 로딩 중 오류가 발생했을 때 호출됩니다.&lt;/li&gt;
&lt;li&gt;onNavigationRequest: 새로운 URL로 이동하기 전에 호출됩니다. 여기서 특정 URL로의 이동을 차단할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;각 콜백 함수 내에서 원하는 로직을 구현할 수 있습니다. 예를 들어, 로딩 진행률을 UI에 표시하거나, 특정 URL로의 이동을 차단하는 등의 작업을 수행할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정된 NavigationDelegate는 WebView의 다양한 이벤트를 처리하고 사용자 경험을 향상시키는 데 도움이 됩니다. 특정 요구사항에 따라 각 콜백 함수의 내용을 커스터마이즈할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;br /&gt;5. 보안 고려사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTPS 사용을 권장합니다.&lt;/li&gt;
&lt;li&gt;신뢰할 수 있는 웹사이트만 로드하세요.&lt;/li&gt;
&lt;li&gt;필요한 경우에만 JavaScript를 활성화하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 성능 최적화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹뷰는 네이티브 위젯보다 성능이 떨어질 수 있으므로 필요한 경우에만 사용하세요.&lt;/li&gt;
&lt;li&gt;복잡한 웹 콘텐츠를 로드할 때는 로딩 인디케이터를 표시하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 사용자 경험 개선&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뒤로 가기 기능 구현&lt;/li&gt;
&lt;li&gt;새로고침 버튼 추가&lt;/li&gt;
&lt;li&gt;로딩 진행률 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 플랫폼별 고려사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS와 Android에서 웹뷰의 동작이 약간 다를 수 있으므로 두 플랫폼에서 모두 테스트하세요.&lt;/li&gt;
&lt;li&gt;플랫폼별 특정 기능이 필요한 경우 조건부 코드를 사용하세요.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/295</guid>
      <comments>https://juntcom.tistory.com/295#entry295comment</comments>
      <pubDate>Mon, 22 Jul 2024 17:21:24 +0900</pubDate>
    </item>
    <item>
      <title>flutter local notifications - 알림</title>
      <link>https://juntcom.tistory.com/294</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 로컬 알림(Local Notifications)을 사용하면 앱이 백그라운드에 있거나 사용자가 앱을 사용하고 있지 않은 경우에도 사용자에게 알림을 보낼 수 있습니다. 이를 위해 &lt;span&gt;flutter_local_notifications&lt;/span&gt; 패키지를 사용할 수 있습니다. 이 패키지는 Android와 iOS 모두에서 로컬 알림을 지원합니다.&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;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;&lt;span&gt;먼저 &lt;/span&gt;pubspec.yaml&lt;span&gt; 파일에 &lt;/span&gt;flutter_local_notifications&lt;span&gt; 패키지를 추가합니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721631020002&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  flutter:
    sdk: flutter
  flutter_local_notifications: ^12.0.3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 &lt;span&gt;flutter pub get&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;b&gt;2. Android 설정&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;Android에서 로컬 알림을 사용하려면 &lt;span&gt;AndroidManifest.xml&lt;/span&gt; 파일에 필요한 권한과 리시버(receiver)를 추가해야 합니다.&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;android/app/src/main/AndroidManifest.xml&lt;span&gt; 파일을 다음과 같이 수정합니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721631030943&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;com.example.your_app&quot;&amp;gt;

    &amp;lt;!-- 알림 관련 권한 --&amp;gt;
    &amp;lt;uses-permission android:name=&quot;android.permission.RECEIVE_BOOT_COMPLETED&quot;/&amp;gt;

    &amp;lt;application
        android:label=&quot;flutter_lecture&quot;
        android:name=&quot;${applicationName}&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;&amp;gt;

        &amp;lt;!-- 알림 관련 서비스 --&amp;gt;
        &amp;lt;receiver
            android:name=&quot;com.dexterous.flutterlocalnotifications.receivers.NotificationBroadcastReceiver&quot;
            android:exported=&quot;true&quot;/&amp;gt;
        &amp;lt;receiver
            android:name=&quot;com.dexterous.flutterlocalnotifications.receivers.ActionReceiver&quot;
            android:exported=&quot;true&quot;/&amp;gt;
        &amp;lt;receiver
            android:name=&quot;com.dexterous.flutterlocalnotifications.receivers.DismissedReceiver&quot;
            android:exported=&quot;true&quot;/&amp;gt;
        &amp;lt;receiver
            android:name=&quot;com.dexterous.flutterlocalnotifications.receivers.ScheduledNotificationBootReceiver&quot;
            android:permission=&quot;android.permission.RECEIVE_BOOT_COMPLETED&quot;
            android:exported=&quot;true&quot;&amp;gt;
            &amp;lt;intent-filter&amp;gt;
                &amp;lt;action android:name=&quot;android.intent.action.BOOT_COMPLETED&quot;/&amp;gt;
                &amp;lt;action android:name=&quot;android.intent.action.MY_PACKAGE_REPLACED&quot;/&amp;gt;
            &amp;lt;/intent-filter&amp;gt;
        &amp;lt;/receiver&amp;gt;

        &amp;lt;!-- 기타 설정들 --&amp;gt;
        &amp;lt;activity
            android:name=&quot;.MainActivity&quot;
            android:exported=&quot;true&quot;
            android:launchMode=&quot;singleTop&quot;
            android:theme=&quot;@style/LaunchTheme&quot;
            android:configChanges=&quot;orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode&quot;
            android:hardwareAccelerated=&quot;true&quot;
            android:windowSoftInputMode=&quot;adjustResize&quot;
            android:showWhenLocked=&quot;true&quot;
            android:turnScreenOn=&quot;true&quot;&amp;gt;
            &amp;lt;meta-data
              android:name=&quot;io.flutter.embedding.android.NormalTheme&quot;
              android:resource=&quot;@style/NormalTheme&quot;/&amp;gt;
            &amp;lt;intent-filter&amp;gt;
                &amp;lt;action android:name=&quot;android.intent.action.MAIN&quot;/&amp;gt;
                &amp;lt;category android:name=&quot;android.intent.category.LAUNCHER&quot;/&amp;gt;
            &amp;lt;/intent-filter&amp;gt;
        &amp;lt;/activity&amp;gt;

        &amp;lt;!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --&amp;gt;
        &amp;lt;meta-data
            android:name=&quot;flutterEmbedding&quot;
            android:value=&quot;2&quot;/&amp;gt;
    &amp;lt;/application&amp;gt;

    &amp;lt;!-- 패키지 가시성 설정 --&amp;gt;
    &amp;lt;queries&amp;gt;
        &amp;lt;intent&amp;gt;
            &amp;lt;action android:name=&quot;android.intent.action.PROCESS_TEXT&quot;/&amp;gt;
            &amp;lt;data android:mimeType=&quot;text/plain&quot;/&amp;gt;
        &amp;lt;/intent&amp;gt;
    &amp;lt;/queries&amp;gt;
&amp;lt;/manifest&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. iOS 설정&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;iOS에서 로컬 알림을 사용하려면 추가적인 설정이 필요합니다.&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;ios/Runner/Info.plist&lt;span&gt; 파일을 다음과 같이 수정합니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721631043325&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;key&amp;gt;UIBackgroundModes&amp;lt;/key&amp;gt;
&amp;lt;array&amp;gt;
    &amp;lt;string&amp;gt;fetch&amp;lt;/string&amp;gt;
    &amp;lt;string&amp;gt;remote-notification&amp;lt;/string&amp;gt;
&amp;lt;/array&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. FlutterLocalNotification 클래스&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;Flutter에서 로컬 알림을 관리할 클래스를 정의합니다. 이 클래스는 알림을 초기화하고, 알림 권한을 요청하며, 알림을 보내는 기능을 포함합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721631054181&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class FlutterLocalNotification {
  FlutterLocalNotification._();

  static final FlutterLocalNotificationsPlugin flutterLocalNotificationPlugin = FlutterLocalNotificationsPlugin();

  static Future&amp;lt;void&amp;gt; init() async {
    const AndroidInitializationSettings androidInitializationSettings =
        AndroidInitializationSettings('@mipmap/ic_launcher');

    const DarwinInitializationSettings iosInitializationSettings =
        DarwinInitializationSettings(
            requestAlertPermission: false,
            requestBadgePermission: false,
            requestSoundPermission: false
        );

    final InitializationSettings initializationSettings = InitializationSettings(
      android: androidInitializationSettings,
      iOS: iosInitializationSettings,
    );

    await flutterLocalNotificationPlugin.initialize(
      initializationSettings,
      onSelectNotification: (String? payload) async {
        // 알림을 클릭했을 때의 동작 정의
      },
    );
  }

  static void requestNotificationPermission() {
    flutterLocalNotificationPlugin
        .resolvePlatformSpecificImplementation&amp;lt;IOSFlutterLocalNotificationsPlugin&amp;gt;()
        ?.requestPermissions(alert: true, badge: true, sound: true);
  }

  static Future&amp;lt;void&amp;gt; showNotification() async {
    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
            'channel id',
            'channel name',
            channelDescription: 'channel description',
            importance: Importance.max,
            priority: Priority.high,
            showWhen: false
        );

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
      iOS: DarwinNotificationDetails(badgeNumber: 1),
    );

    await flutterLocalNotificationPlugin.show(
      0,
      'test title',
      'test body',
      notificationDetails,
    );
  }
}&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;&lt;b&gt;5. main.dart 파일&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;/p&gt;
&lt;pre id=&quot;code_1721631072716&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_lecture/notification.dart'; // 경로를 실제 경로로 변경하세요.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await FlutterLocalNotification.init();  // 플러그인 초기화
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  @override
  void initState() {
    super.initState();
    initializeNotifications();
  }

  Future&amp;lt;void&amp;gt; initializeNotifications() async {
    await FlutterLocalNotification.init();
    Future.delayed(
      const Duration(seconds: 3),
      () =&amp;gt; FlutterLocalNotification.requestNotificationPermission()
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&quot;알림 목록&quot;),
      ),
      body: Center(
        child: TextButton(
          onPressed: () =&amp;gt; FlutterLocalNotification.showNotification(),
          child: const Text(&quot;알림보내기&quot;)
        )
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 프로젝트 정리 및 빌드&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;/p&gt;
&lt;pre id=&quot;code_1721631093561&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter clean
flutter pub get
flutter run&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/294</guid>
      <comments>https://juntcom.tistory.com/294#entry294comment</comments>
      <pubDate>Mon, 22 Jul 2024 15:52:11 +0900</pubDate>
    </item>
    <item>
      <title>flutter get_it 패턴</title>
      <link>https://juntcom.tistory.com/293</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;get_it&lt;/span&gt; 패턴은 Flutter 애플리케이션에서 의존성 주입(Dependency Injection)을 간단하고 효율적으로 관리하기 위해 사용되는 패턴입니다. &lt;span&gt;get_it&lt;/span&gt; 패키지는 서비스 로케이터(Service Locator) 패턴을 구현하여, 애플리케이션의 상태와 서비스를 전역적으로 접근할 수 있게 합니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;서비스 로케이터(Service Locator)&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;의존성 주입의 한 형태로, 애플리케이션의 다양한 부분에서 공통된 서비스를 쉽게 가져올 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;서비스 로케이터를 통해 인스턴스를 전역적으로 관리하고 재사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;의존성 주입(Dependency Injection)&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;클래스 간의 의존성을 주입하여 코드의 재사용성과 테스트 용이성을 높입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;등록 및 해제&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;서비스를 등록하고 필요 시 해제할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;싱글톤 및 팩토리&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;싱글톤 인스턴스와 팩토리 인스턴스를 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;전역 접근&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 예제를 통해 &lt;span&gt;get_it&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;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;pubspec.yaml&lt;span&gt; 파일에 &lt;/span&gt;get_it&lt;span&gt; 패키지를 추가합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721623191259&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;get_it&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 서비스 클래스 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721623202801&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterService {
  int _count = 0;

  int get count =&amp;gt; _count;

  void increment() {
    _count++;
  }

  void decrement() {
    _count--;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 서비스 등록&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;/p&gt;
&lt;pre id=&quot;code_1721623222187&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'counter_service.dart';

void setupLocator() {
  GetIt.I.registerLazySingleton(() =&amp;gt; CounterService());
}

void main() {
  setupLocator();
  runApp(MyApp());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721623237717&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'counter_service.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterPage(),
    );
  }
}

class CounterPage extends StatelessWidget {
  final CounterService _counterService = GetIt.I&amp;lt;CounterService&amp;gt;();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Get It Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            Text(
              'Counter: ${_counterService.count}',
              style: TextStyle(fontSize: 24),
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                FloatingActionButton(
                  onPressed: _counterService.increment,
                  child: Icon(Icons.add),
                ),
                SizedBox(width: 20),
                FloatingActionButton(
                  onPressed: _counterService.decrement,
                  child: Icon(Icons.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;span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;서비스 등록&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;GetIt.I.registerLazySingleton(() =&amp;gt; CounterService());&lt;span&gt;를 사용하여 서비스를 등록합니다. 이는 싱글톤 인스턴스를 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;registerFactory&lt;/span&gt;를 사용하면 요청할 때마다 새로운 인스턴스를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;서비스 사용&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetIt.I&amp;lt;CounterService&amp;gt;()&lt;/span&gt;를 사용하여 등록된 서비스를 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;locator 패키지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- locator.dart&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;service 패키지&lt;br /&gt;- album_service.dart&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view 패키지&lt;br /&gt;- album_getit_view.dart&lt;br /&gt;메인 메소드&amp;nbsp;&lt;br /&gt;- locator 메소드 호출&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;album_service.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;import 'dart:convert';

import '../model/album.dart';
import 'package:http/http.dart' as http;

abstract class AlbumService {
  Future&amp;lt;List&amp;lt;Album&amp;gt;&amp;gt; fetchAlbums();
}


class AlbumServiceImplementation implements AlbumService {
  @override
  Future&amp;lt;List&amp;lt;Album&amp;gt;&amp;gt; fetchAlbums() async {
    final reponse = await http.get(
        Uri.parse(&quot;https://jsonplaceholder.typicode.com/albums&quot;)
    );
    final List&amp;lt;Album&amp;gt; result = jsonDecode(reponse.body)
        .map&amp;lt;Album&amp;gt;((json) =&amp;gt; Album.fromJson(json))
        .toList();

    return result;
  }
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;locator.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import 'package:flutter_lecture/service/album_service.dart';
import 'package:get_it/get_it.dart';

GetIt locator = GetIt.instance;

initLocator() {
  locator.registerLazySingleton&amp;lt;AlbumService&amp;gt;(() =&amp;gt; AlbumServiceImplementation());

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;album_getit_view.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_lecture/locator/locator.dart';
import 'package:flutter_lecture/model/albums.dart';
import 'package:flutter_lecture/bloc/album_bloc.dart';
import 'package:flutter_lecture/service/album_service.dart';

import '../model/album.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  final AlbumService _service = locator&amp;lt;AlbumService&amp;gt;();


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&quot;test title&quot;),
      ),
      body: FutureBuilder(
        future: _service.fetchAlbums(),
        builder: (context, snapshot) {
          if(snapshot.hasData) {
            List&amp;lt;Album&amp;gt;? list = snapshot.data;
            return ListView.builder(
                itemCount: list?.length,
                itemBuilder: (context, index) {
                  return Container(
                    padding: const EdgeInsets.all(10),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(&quot;ID: ${list?[index].id.toString()}&quot;),
                        Text(&quot;Title: ${list?[index].title}&quot; )
                      ],
                    ),
                  );
                }
            );
          } else if(snapshot.hasError){
            return Center(
              child: Text(snapshot.error.toString()),
            );
          } else {
            return const Center(
              child: CircularProgressIndicator(
                strokeWidth: 2,
              ),
            );
          }
        },
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;pre id=&quot;code_1721623547935&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  initLocator();
  runApp(const MyApp());
}&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WXaGL/btsIG0wWQxX/nM4XAySIIJZlSUdz6eqZYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WXaGL/btsIG0wWQxX/nM4XAySIIJZlSUdz6eqZYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WXaGL/btsIG0wWQxX/nM4XAySIIJZlSUdz6eqZYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWXaGL%2FbtsIG0wWQxX%2FnM4XAySIIJZlSUdz6eqZYK%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;346&quot; height=&quot;671&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;간단함&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;의존성 주입을 간단하게 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;유연성&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;다양한 유형의 서비스 인스턴스를 등록하고 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;테스트 용이성&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://pub.dev/packages/get_it&quot;&gt;Get It 패키지 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple#dependencies-injection&quot;&gt;Flutter 공식 의존성 주입 가이드&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;이 정보를 통해 Flutter 애플리케이션에서 &lt;span&gt;get_it&lt;/span&gt; 패턴을 사용하여 의존성 주입을 효율적으로 관리할 수 있습니다. &lt;span&gt;get_it&lt;/span&gt; 패턴을 활용하면 코드의 구조를 개선하고 유지보수성을 높일 수 있습니다.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/293</guid>
      <comments>https://juntcom.tistory.com/293#entry293comment</comments>
      <pubDate>Mon, 22 Jul 2024 13:45:04 +0900</pubDate>
    </item>
    <item>
      <title>flutter provider 패턴</title>
      <link>https://juntcom.tistory.com/292</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Provider 패턴은 Flutter 애플리케이션에서 상태 관리를 효율적으로 수행하기 위해 사용되는 패턴입니다. 이 패턴은 &lt;span&gt;InheritedWidget&lt;/span&gt;을 기반으로 하며, 상태를 위젯 트리에서 쉽게 공유하고 관리할 수 있게 도와줍니다. Provider 패턴은 Flutter의 공식 상태 관리 솔루션 중 하나로, 단순하고 강력한 상태 관리 방법을 제공합니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Provider&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;상태를 제공하는 역할을 합니다. Provider는 다양한 유형이 있으며, &lt;span&gt;ChangeNotifierProvider&lt;/span&gt;, &lt;span&gt;Provider&lt;/span&gt;, &lt;span&gt;FutureProvider&lt;/span&gt;, &lt;span&gt;StreamProvider&lt;/span&gt; 등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ChangeNotifier&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;상태 변경을 알리기 위해 사용되는 클래스입니다. &lt;span&gt;ChangeNotifier&lt;/span&gt;를 상속받아 상태를 관리하고, &lt;span&gt;notifyListeners()&lt;/span&gt;를 호출하여 리스너들에게 상태 변경을 알립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Consumer&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Provider&lt;/span&gt;에서 제공하는 상태를 구독하고, 상태가 변경될 때마다 UI를 업데이트하는 역할을 합니다.&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;Provider의 유형&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;ChangeNotifierProvider&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChangeNotifier&lt;/span&gt;를 사용하여 상태를 제공하고 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Provider&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;기본적인 상태 제공자로, 변경되지 않는 데이터를 제공할 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;FutureProvider&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;비동기 작업의 결과를 제공하는 Provider입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;StreamProvider&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;스트림 데이터를 제공하는 Provider입니다.&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;예제: ChangeNotifierProvider 사용&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;간단한 카운터 애플리케이션을 Provider 패턴으로 구현한 예제를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;dependencies 에 추가&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;provider:&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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_1721619356138&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/foundation.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get count =&amp;gt; _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void decrement() {
    _count--;
    notifyListeners();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메인 애플리케이션&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721619369309&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter.dart'; // Counter 모델 클래스 가져오기

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) =&amp;gt; Counter()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterPage(),
    );
  }
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of&amp;lt;Counter&amp;gt;(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Provider Counter Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &amp;lt;Widget&amp;gt;[
            Text(
              'Counter: ${counter.count}',
              style: TextStyle(fontSize: 24),
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: counter.increment,
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: counter.decrement,
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Counter 모델 클래스&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ChangeNotifier&lt;/span&gt;를 상속받아 상태를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;increment&lt;/span&gt;와 &lt;span&gt;decrement&lt;/span&gt; 메서드를 통해 상태를 변경하고, &lt;span&gt;notifyListeners()&lt;/span&gt;를 호출하여 리스너에게 상태 변경을 알립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;MultiProvider&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;여러 Provider를 동시에 제공할 때 사용합니다. 이 예제에서는 &lt;span&gt;ChangeNotifierProvider&lt;/span&gt;를 사용하여 &lt;span&gt;Counter&lt;/span&gt; 상태를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Provider.of(context)&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;현재 컨텍스트에서 Provider가 제공하는 상태를 구독합니다. 여기서는 &lt;span&gt;Counter&lt;/span&gt; 상태를 구독하여 카운터 값을 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;FloatingActionButton&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;onPressed&lt;span&gt; 콜백에서 &lt;/span&gt;counter.increment&lt;span&gt;와 &lt;/span&gt;counter.decrement&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 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요 파일&lt;br /&gt;provider 패키지&lt;br /&gt;- album_provider.dart&lt;br /&gt;model 패키지&lt;br /&gt;- album.dart&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view 패키지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- album_provider_view.dart&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;album.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;class Album {
  int? userId;
  int? id;
  String? title;

  Album({this.userId, this.id, this.title});

  factory Album.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;album_provider.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;import 'dart:convert';

import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;

import '../model/album.dart';

class AlbumProvider with ChangeNotifier {
  final List&amp;lt;Album&amp;gt; _albumList = List.empty(growable: true);

  List&amp;lt;Album&amp;gt; getAlbumList() {
    _fetchAlbums();
    return _albumList;
  }

  void _fetchAlbums() async {
    final response = await http.get(Uri.parse(&quot;https://jsonplaceholder.typicode.com/albums&quot;));
    final List&amp;lt;Album&amp;gt; result = jsonDecode(response.body)
        .map&amp;lt;Album&amp;gt;((json) =&amp;gt; Album.fromJson(json))
        .toList();
    _albumList.clear();
    _albumList.addAll(result);
    notifyListeners();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;album_provider_view.dart&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_lecture/provider/album_provider.dart';
import 'package:provider/provider.dart';

import '../model/album.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {

  late List&amp;lt;Album&amp;gt; albumList;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider&amp;lt;AlbumProvider&amp;gt;(
      create: ((context) =&amp;gt; AlbumProvider()),
      child: Scaffold(
        appBar: AppBar(
          title: const Text(&quot;test title&quot;),
        ),
        body: Consumer&amp;lt;AlbumProvider&amp;gt;(
          builder: (context, provider, child) {
            albumList = provider.getAlbumList();
            return ListView.builder(
                itemCount: albumList.length,
                itemBuilder: (context, index) {
                  return Container(
                    padding: const EdgeInsets.all(10),
                    child: Text(&quot;${albumList[index].id} : ${albumList[index].title}&quot;),
                  );
                }
            );
          },
        ),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;Provider 패턴의 장점&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;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;단순성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Provider 패턴은 Flutter의 상태 관리 솔루션 중 가장 단순하면서도 강력한 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;유연성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;다양한 유형의 Provider를 제공하여 다양한 상태 관리 요구를 충족할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;성능 최적화&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Provider는 효율적으로 상태를 구독하고 업데이트하여 성능을 최적화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;재사용성&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://pub.dev/packages/provider&quot;&gt;Provider 패키지 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple&quot;&gt;Flutter 공식 상태 관리 문서&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;이 정보를 통해 Flutter 애플리케이션에서 Provider 패턴을 효과적으로 사용하여 상태 관리를 수행할 수 있습니다. Provider 패턴을 활용하면 코드의 구조를 개선하고 유지보수성을 높일 수 있습니다.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/292</guid>
      <comments>https://juntcom.tistory.com/292#entry292comment</comments>
      <pubDate>Mon, 22 Jul 2024 13:18:51 +0900</pubDate>
    </item>
    <item>
      <title>flutter bloc 패턴 적용</title>
      <link>https://juntcom.tistory.com/291</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;BLoC(Business Logic Component) 패턴은 Flutter 애플리케이션에서 비즈니스 로직을 UI 코드와 분리하는 데 사용되는 설계 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴은 &lt;span&gt;Stream&lt;/span&gt;과 &lt;span&gt;Sink&lt;/span&gt;를 활용하여 데이터의 흐름을 관리하고, 상태 관리를 효율적으로 수행할 수 있게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BLoC 패턴은 Flutter의 공식 상태 관리 솔루션 중 하나로, 재사용성과 테스트 가능성을 높이는 데 기여합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;BLoC (Business Logic Component)&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;비즈니스 로직을 담당하는 컴포넌트입니다. &lt;span&gt;Stream&lt;/span&gt;을 통해 UI에 데이터를 전달하고, &lt;span&gt;Sink&lt;/span&gt;를 통해 UI로부터 이벤트를 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Stream&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;비동기 데이터의 흐름을 나타내는 Dart의 클래스입니다. BLoC 패턴에서는 상태 변경을 UI로 전달하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;Sink&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;데이터를 &lt;span&gt;Stream&lt;/span&gt;에 전달하는 입력 통로입니다. BLoC 패턴에서는 UI에서 발생한 이벤트를 BLoC로 전달하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Event&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;UI에서 발생하는 사용자 상호작용을 나타냅니다. 버튼 클릭, 폼 입력 등 다양한 이벤트가 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;State&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;UI에서 표시할 데이터를 나타냅니다. BLoC는 상태를 관리하고 &lt;span&gt;Stream&lt;/span&gt;을 통해 UI로 전달합니다.&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;BLoC 패턴의 구성 요소&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Event 클래스&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;사용자가 수행하는 동작을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;State 클래스&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;애플리케이션의 상태를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;BLoC 클래스&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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 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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 카운터 애플리케이션을 BLoC 패턴으로 구현한 예제를 살펴보겠습니다.&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;Event 클래스&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721617022135&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class CounterEvent {}

class Increment extends CounterEvent {}

class Decrement extends CounterEvent {}&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;&lt;b&gt;State 클래스&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721617032574&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterState {
  final int count;

  CounterState(this.count);
}&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;&lt;b&gt;BLoC 클래스&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721617051188&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'dart:async';

class CounterBloc {
  final _stateController = StreamController&amp;lt;CounterState&amp;gt;();
  StreamSink&amp;lt;CounterState&amp;gt; get _inCounter =&amp;gt; _stateController.sink;
  Stream&amp;lt;CounterState&amp;gt; get counter =&amp;gt; _stateController.stream;

  final _eventController = StreamController&amp;lt;CounterEvent&amp;gt;();
  Sink&amp;lt;CounterEvent&amp;gt; get counterEventSink =&amp;gt; _eventController.sink;

  CounterBloc() {
    _eventController.stream.listen(_mapEventToState);
  }

  void _mapEventToState(CounterEvent event) {
    if (event is Increment) {
      _inCounter.add(CounterState(_stateController.stream.value.count + 1));
    } else if (event is Decrement) {
      _inCounter.add(CounterState(_stateController.stream.value.count - 1));
    }
  }

  void dispose() {
    _stateController.close();
    _eventController.close();
  }
}&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;&lt;b&gt;UI 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721617065901&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'counter_bloc.dart'; // BLoC 파일을 가져옴

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterPage(),
    );
  }
}

class CounterPage extends StatelessWidget {
  final CounterBloc _bloc = CounterBloc();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BLoC Counter Example'),
      ),
      body: Center(
        child: StreamBuilder&amp;lt;CounterState&amp;gt;(
          stream: _bloc.counter,
          initialData: CounterState(0),
          builder: (context, snapshot) {
            if (!snapshot.hasData) {
              return CircularProgressIndicator();
            }
            return Text(
              'Counter: ${snapshot.data.count}',
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {
              _bloc.counterEventSink.add(Increment());
            },
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () {
              _bloc.counterEventSink.add(Decrement());
            },
            child: Icon(Icons.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;&lt;b&gt;예시(실습)&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;257&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3ZSgV/btsIJeAdZns/KOnVOxzsaAlNEe3rPdiqi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3ZSgV/btsIJeAdZns/KOnVOxzsaAlNEe3rPdiqi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3ZSgV/btsIJeAdZns/KOnVOxzsaAlNEe3rPdiqi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3ZSgV%2FbtsIJeAdZns%2FKOnVOxzsaAlNEe3rPdiqi0%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;257&quot; height=&quot;270&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;270&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;rxdart:
http:&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;model 부터 생성해줍니다.&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;class Album {
  int? userId;
  int? id;
  String? title;

  Album({this.userId, this.id, this.title});

  factory Album.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1721617177742&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import './album.dart';

class Albums {
  late List&amp;lt;Album&amp;gt; albums;

  Albums({required this.albums});

  Albums.fromJSON(List&amp;lt;dynamic&amp;gt; json) {
    albums = List&amp;lt;Album&amp;gt;.empty(growable: true);
    for (dynamic val in json) {
      albums.add(Album.fromJson(val));
    }
  }
}&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;&lt;b&gt;provider 를 생성해줍니다. &lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import 'dart:convert';

import 'package:http/http.dart' show Client;
import 'package:flutter_lecture/model/albums.dart';


class AlbumApiProvider {
  Client client = Client();
  
  Future&amp;lt;Albums&amp;gt; fetchAlbumList() async {
    final response = await client
        .get(Uri.parse(&quot;https://jsonplaceholder.typicode.com/albums&quot;));

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      return Albums.fromJSON(data);
    } else {
      throw Exception(&quot;Failed&quot;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;repository 를 생성합니다.&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;import 'package:flutter_lecture/data_provider/api_provider.dart';
import 'package:flutter_lecture/model/albums.dart';

class AlbumRepository {

  final AlbumApiProvider _albumApiProvider = AlbumApiProvider();

  Future&amp;lt;Albums&amp;gt; fetchAllAlbums() async =&amp;gt; _albumApiProvider.fetchAlbumList();
  
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;bloc 파일을 만들어줍니다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;import 'package:flutter_lecture/repository/ablum_repository.dart';
import 'package:rxdart/rxdart.dart';

import '../model/albums.dart';

class AlbumBloc {
  final AlbumRepository _albumRepository = AlbumRepository();
  final PublishSubject&amp;lt;Albums&amp;gt; _albumFetcher = PublishSubject&amp;lt;Albums&amp;gt;();

  Stream&amp;lt;Albums&amp;gt; get allAlbums =&amp;gt; _albumFetcher.stream;

  Future&amp;lt;void&amp;gt; fetchAllAlbums() async {
    Albums albums = await _albumRepository.fetchAllAlbums();
    _albumFetcher.sink.add(albums);
  }

  dispose() {
    _albumFetcher.close();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;bloc 파일을 적용할 view 파일을 마지막으로 만들어줍니다. &lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_lecture/model/albums.dart';
import 'package:flutter_lecture/bloc/album_bloc.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  final AlbumBloc _albumBloc = AlbumBloc();


  @override
  void initState() {
    _albumBloc.fetchAllAlbums();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&quot;test title&quot;),
      ),
      body: StreamBuilder&amp;lt;Albums&amp;gt;(
        stream: _albumBloc.allAlbums,
        builder: (context, snapshot) {
          if(snapshot.hasData) {
            Albums? albumList = snapshot.data;
            return ListView.builder(
              itemCount: albumList?.albums.length,
              itemBuilder: (context, index) {
                return Container(
                  padding: const EdgeInsets.all(10),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(&quot;ID: ${albumList?.albums[index].id.toString()}&quot;),
                      Text(&quot;Title: ${albumList?.albums[index].title}&quot; )
                    ],
                  ),
                );
              }
            );
          } else if(snapshot.hasError){
              return Center(
                child: Text(snapshot.error.toString()),
              );

          } else {
            return const Center(
              child: CircularProgressIndicator(
                strokeWidth: 2,
              ),
            );
          }
        },
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QRNQI/btsIGV3uYUo/klfpeOHraJ2k0JGErk8MxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QRNQI/btsIGV3uYUo/klfpeOHraJ2k0JGErk8MxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QRNQI/btsIGV3uYUo/klfpeOHraJ2k0JGErk8MxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQRNQI%2FbtsIGV3uYUo%2FklfpeOHraJ2k0JGErk8MxK%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;339&quot; height=&quot;666&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;666&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BLoC 패턴의 장점&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;UI와 비즈니스 로직의 분리&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;UI 코드와 비즈니스 로직을 명확히 분리하여 코드의 가독성과 유지보수성을 높입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;재사용성과 테스트 용이성&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;BLoC 컴포넌트는 독립적으로 동작하므로 다른 프로젝트나 화면에서 재사용할 수 있습니다. 또한, 비즈니스 로직이 UI와 분리되어 있어 단위 테스트가 용이합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;비동기 데이터 처리&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Stream&lt;/span&gt;과 &lt;span&gt;Sink&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;b&gt;참고 문서&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://bloclibrary.dev/#/&quot;&gt;Bloc 패턴 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://flutter.dev/docs/development/data-and-backend/state-mgmt&quot;&gt;Flutter의 상태 관리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.dart.dev/stable/dart-async/Stream-class.html&quot;&gt;Dart의 Stream 클래스 문서&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;이 정보를 통해 Flutter 애플리케이션에서 BLoC 패턴을 효과적으로 사용하여 상태 관리를 수행할 수 있습니다. BLoC 패턴을 활용하면 코드의 구조를 개선하고 유지보수성을 높일 수 있습니다.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/291</guid>
      <comments>https://juntcom.tistory.com/291#entry291comment</comments>
      <pubDate>Mon, 22 Jul 2024 12:02:46 +0900</pubDate>
    </item>
    <item>
      <title>flutter 리프레쉬 인디케이터</title>
      <link>https://juntcom.tistory.com/290</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;RefreshIndicator&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RefreshIndicator는 Flutter에서 사용자가 목록을 끌어 당겨서 새로 고침(pull-to-refresh) 동작을 수행할 수 있게 하는 위젯입니다. 주로 스크롤 가능한 목록에서 사용되며, 사용자에게 데이터를 새로 고치는 기능을 제공합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 속성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;child&lt;/b&gt;: RefreshIndicator가 감싸는 스크롤 가능한 위젯입니다. 일반적으로 ListView와 같은 위젯이 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;onRefresh&lt;/b&gt;: 새로 고침 동작이 발생할 때 호출되는 콜백 함수입니다. 이 함수는 Future&amp;lt;void&amp;gt;를 반환하여 비동기 작업을 수행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;color&lt;/b&gt;: 인디케이터의 색상을 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;backgroundColor&lt;/b&gt;: 인디케이터의 배경색을 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;displacement&lt;/b&gt;: 인디케이터가 표시될 때 위쪽에서 떨어진 거리입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;strokeWidth&lt;/b&gt;: 인디케이터의 선 두께입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용 예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 RefreshIndicator를 사용하여 목록을 새로 고침하는 예제입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721610422647&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('RefreshIndicator Example'),
        ),
        body: RefreshIndicatorExample(),
      ),
    );
  }
}

class RefreshIndicatorExample extends StatefulWidget {
  @override
  _RefreshIndicatorExampleState createState() =&amp;gt; _RefreshIndicatorExampleState();
}

class _RefreshIndicatorExampleState extends State&amp;lt;RefreshIndicatorExample&amp;gt; {
  final List&amp;lt;String&amp;gt; _items = List.generate(20, (index) =&amp;gt; 'Item ${index + 1}');

  Future&amp;lt;void&amp;gt; _refresh() async {
    await Future.delayed(Duration(seconds: 2)); // 2초 동안 대기

    setState(() {
      _items.shuffle(); // 아이템을 섞어서 목록을 새로 고침
    });
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _refresh,
      child: ListView.builder(
        itemCount: _items.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(_items[index]),
          );
        },
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 포인트&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;RefreshIndicator 사용&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RefreshIndicator 위젯은 스크롤 가능한 위젯을 감싸서 사용합니다.&lt;/li&gt;
&lt;li&gt;onRefresh 콜백은 데이터를 새로 고침하는 로직을 포함해야 하며, Future&amp;lt;void&amp;gt;를 반환해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 새로 고침&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;onRefresh 콜백 내에서 비동기 작업을 수행하여 데이터를 새로 고칩니다.&lt;/li&gt;
&lt;li&gt;비동기 작업이 완료되면 setState를 호출하여 UI를 업데이트합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UI 업데이트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setState를 사용하여 데이터 변경 사항을 반영하고 UI를 업데이트합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가 예제: 커스텀 인디케이터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 사용 예제 외에도 RefreshIndicator의 속성을 사용하여 커스텀 인디케이터를 만들 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721610454324&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Custom RefreshIndicator Example'),
        ),
        body: CustomRefreshIndicatorExample(),
      ),
    );
  }
}

class CustomRefreshIndicatorExample extends StatefulWidget {
  @override
  _CustomRefreshIndicatorExampleState createState() =&amp;gt; _CustomRefreshIndicatorExampleState();
}

class _CustomRefreshIndicatorExampleState extends State&amp;lt;CustomRefreshIndicatorExample&amp;gt; {
  final List&amp;lt;String&amp;gt; _items = List.generate(20, (index) =&amp;gt; 'Item ${index + 1}');

  Future&amp;lt;void&amp;gt; _refresh() async {
    await Future.delayed(Duration(seconds: 2));

    setState(() {
      _items.shuffle();
    });
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _refresh,
      color: Colors.white,
      backgroundColor: Colors.blue,
      displacement: 40,
      strokeWidth: 3,
      child: ListView.builder(
        itemCount: _items.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(_items[index]),
          );
        },
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dio 를 이용한 데이터 가져오기 예시 (실습)&lt;/h3&gt;
&lt;pre id=&quot;code_1721611328801&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

import 'product.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; {
  final Dio dio = Dio();

  Future&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt; getProductData() async {
    try {
      print(&quot;데이터 가져오기 시작&quot;);
      var response = await dio.get(&quot;https://dummyjson.com/products&quot;);
      List&amp;lt;Product&amp;gt; products = (response.data['products'] as List)
          .map&amp;lt;Product&amp;gt;((json) =&amp;gt; Product.fromJson(json))
          .toList();
      print(&quot;데이터 가져오기 완료&quot;);
      return products;
    } catch (e) {
      print(&quot;오류 발생: $e&quot;);
      return []; // 에러 발생 시 빈 리스트 반환
    }
  }

  Future&amp;lt;void&amp;gt; refreshData() async {
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&quot;제품 목록&quot;),
      ),
      body: RefreshIndicator(
        onRefresh: refreshData,
        child: FutureBuilder&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt;(
          future: getProductData(),
          builder: (BuildContext context, AsyncSnapshot&amp;lt;List&amp;lt;Product&amp;gt;&amp;gt; snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const Center(child: CircularProgressIndicator());
            } else if (snapshot.hasError) {
              return Center(child: Text('오류 발생: ${snapshot.error}'));
            } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
              return const Center(child: Text('데이터가 없습니다'));
            } else {
              return ListView.builder(
                itemCount: snapshot.data!.length,
                itemBuilder: (BuildContext context, int index) {
                  var data = snapshot.data![index];
                  return Container(
                    padding: const EdgeInsets.all(10),
                    decoration: BoxDecoration(
                      border: Border.all(width: 1, color: Colors.black12),
                    ),
                    child: Text(&quot;${data.title} (${data.description})&quot;),
                  );
                },
              );
            }
          },
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;319&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEdV5i/btsIIHvOs4g/INRbkTnuNug5Nks0wwibUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEdV5i/btsIIHvOs4g/INRbkTnuNug5Nks0wwibUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEdV5i/btsIIHvOs4g/INRbkTnuNug5Nks0wwibUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEdV5i%2FbtsIIHvOs4g%2FINRbkTnuNug5Nks0wwibUk%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;319&quot; height=&quot;556&quot; data-origin-width=&quot;319&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RefreshIndicator는 Flutter에서 사용자가 목록을 끌어 당겨서 새로 고침 동작을 수행할 수 있게 하는 유용한 위젯입니다. 이 위젯을 사용하면 사용자 경험을 향상시키고, 데이터 업데이트를 쉽게 처리할 수 있습니다. RefreshIndicator를 사용하여 간단한 pull-to-refresh 기능을 구현하고, 다양한 속성을 통해 커스터마이징할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 문서&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a&gt;RefreshIndicator 클래스 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>Flutter</category>
      <category>flutter refresh</category>
      <category>flutter refresh indicator</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/290</guid>
      <comments>https://juntcom.tistory.com/290#entry290comment</comments>
      <pubDate>Mon, 22 Jul 2024 10:23:12 +0900</pubDate>
    </item>
    <item>
      <title>dart 반복문 정리</title>
      <link>https://juntcom.tistory.com/289</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Dart의 반복문은 특정 조건이 참일 때까지 또는 특정 횟수만큼 코드를 반복 실행하는 데 사용됩니다. Dart에서 사용할 수 있는 주요 반복문에는 &lt;span&gt;for&lt;/span&gt; 문, &lt;span&gt;for-in&lt;/span&gt; 문, &lt;span&gt;while&lt;/span&gt; 문, &lt;span&gt;do-while&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;b&gt;1. for 문&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;span&gt;for&lt;/span&gt; 문은 반복 횟수가 명확할 때 주로 사용됩니다. 초기화, 조건 검사, 증감식을 한 줄에 작성할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565824939&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  // 0부터 4까지 출력
  for (int i = 0; i &amp;lt; 5; i++) {
    print(i);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. for-in 문&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;span&gt;for-in&lt;/span&gt; 문은 컬렉션(리스트, 셋 등)의 요소를 하나씩 순회할 때 사용됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565837790&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  var numbers = [1, 2, 3, 4, 5];

  // 각 요소를 출력
  for (var number in numbers) {
    print(number);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. while 문&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;span&gt;while&lt;/span&gt; 문은 조건이 참인 동안 코드를 반복 실행합니다. 조건이 거짓이 되면 반복을 종료합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565858322&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int i = 0;

  // i가 5보다 작을 때까지 반복
  while (i &amp;lt; 5) {
    print(i);
    i++;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. do-while 문&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;span&gt;do-while&lt;/span&gt; 문은 조건을 검사하기 전에 코드를 먼저 한 번 실행한 후, 조건이 참인 동안 반복 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565880378&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int i = 0;

  // 최소 한 번은 실행 후, i가 5보다 작을 때까지 반복
  do {
    print(i);
    i++;
  } while (i &amp;lt; 5);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 중첩 반복문&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;/p&gt;
&lt;pre id=&quot;code_1721565893615&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  // 구구단 출력
  for (int i = 1; i &amp;lt;= 9; i++) {
    for (int j = 1; j &amp;lt;= 9; j++) {
      print('$i * $j = ${i * j}');
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 반복문 제어문&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;span&gt;break&lt;/span&gt;와 &lt;span&gt;continue&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;b&gt;break&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;span&gt;break&lt;/span&gt; 문은 반복문을 즉시 종료합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565905528&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  for (int i = 0; i &amp;lt; 5; i++) {
    if (i == 3) {
      break; // 반복문 종료
    }
    print(i);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;continue&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;span&gt;continue&lt;/span&gt; 문은 현재 반복 주기의 나머지 부분을 건너뛰고 다음 반복 주기로 넘어갑니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565920012&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  for (int i = 0; i &amp;lt; 5; i++) {
    if (i == 3) {
      continue; // 다음 반복 주기로 건너뜀
    }
    print(i);
  }
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;for&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 문&lt;/b&gt;: 초기화, 조건 검사, 증감식을 한 줄에 작성하여 반복.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;for-in&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 문&lt;/b&gt;: 컬렉션의 요소를 순회.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;while&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 문&lt;/b&gt;: 조건이 참인 동안 반복.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;do-while&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 문&lt;/b&gt;: 최소 한 번 실행 후 조건이 참인 동안 반복.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;중첩 반복문&lt;/b&gt;: 반복문 내에 다른 반복문을 포함하여 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;break&lt;/b&gt;&lt;/span&gt;: 반복문을 즉시 종료.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;continue&lt;/b&gt;&lt;/span&gt;: 현재 반복 주기를 건너뛰고 다음 주기로 넘어감.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>DART</category>
      <category>dart 반복문</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/289</guid>
      <comments>https://juntcom.tistory.com/289#entry289comment</comments>
      <pubDate>Sun, 21 Jul 2024 21:46:03 +0900</pubDate>
    </item>
    <item>
      <title>dart 조건문 정리</title>
      <link>https://juntcom.tistory.com/288</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Dart의 조건문은 프로그램의 흐름을 제어하고 특정 조건에 따라 다른 코드를 실행하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 조건문에는 &lt;span&gt;if&lt;/span&gt;, &lt;span&gt;else if&lt;/span&gt;, &lt;span&gt;else&lt;/span&gt;, &lt;span&gt;switch&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;b&gt;1. if 문&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;span&gt;if&lt;/span&gt; 문은 주어진 조건이 &lt;span&gt;true&lt;/span&gt;일 때 코드를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565356333&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int number = 10;

  if (number &amp;gt; 5) {
    print('Number is greater than 5');
  }
}&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;&lt;b&gt;2. if-else 문&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;span&gt;if-else&lt;/span&gt; 문은 &lt;span&gt;if&lt;/span&gt; 조건이 &lt;span&gt;false&lt;/span&gt;일 때 &lt;span&gt;else&lt;/span&gt; 블록의 코드를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565367199&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int number = 3;

  if (number &amp;gt; 5) {
    print('Number is greater than 5');
  } else {
    print('Number is not greater than 5');
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. else if 문&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;span&gt;else if&lt;/span&gt; 문은 여러 조건을 검사할 때 사용됩니다. 첫 번째 &lt;span&gt;if&lt;/span&gt; 조건이 &lt;span&gt;false&lt;/span&gt;일 때, 그 다음 &lt;span&gt;else if&lt;/span&gt; 조건을 검사합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565385706&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int number = 7;

  if (number &amp;gt; 10) {
    print('Number is greater than 10');
  } else if (number &amp;gt; 5) {
    print('Number is greater than 5 but less than or equal to 10');
  } else {
    print('Number is 5 or less');
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 중첩 if 문&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;span&gt;if&lt;/span&gt; 문은 다른 &lt;span&gt;if&lt;/span&gt; 문 안에 중첩될 수 있습니다. 이는 복잡한 조건을 검사할 때 유용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565401417&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int number = 8;

  if (number &amp;gt; 5) {
    if (number &amp;lt; 10) {
      print('Number is between 5 and 10');
    } else {
      print('Number is 10 or greater');
    }
  } else {
    print('Number is 5 or less');
  }
}&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;&lt;b&gt;5. 삼항 연산자&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;span&gt;if-else&lt;/span&gt; 문을 한 줄로 표현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565419630&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int number = 4;
  String result = number &amp;gt; 5 ? 'Number is greater than 5' : 'Number is not greater than 5';
  print(result);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. switch 문&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;span&gt;switch&lt;/span&gt; 문은 하나의 변수에 대한 여러 조건을 검사할 때 사용됩니다. 각 조건은 &lt;span&gt;case&lt;/span&gt; 키워드로 표시됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565436669&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  String grade = 'B';

  switch (grade) {
    case 'A':
      print('Excellent!');
      break;
    case 'B':
      print('Good!');
      break;
    case 'C':
      print('Fair');
      break;
    case 'D':
      print('Poor');
      break;
    default:
      print('Invalid grade');
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. switch 문에서 fall-through 방지&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;Dart에서는 &lt;span&gt;case&lt;/span&gt; 블록이 끝나면 &lt;span&gt;break&lt;/span&gt; 문을 사용하여 &lt;span&gt;switch&lt;/span&gt; 문을 종료해야 합니다. 그렇지 않으면 다음 &lt;span&gt;case&lt;/span&gt; 블록으로 넘어가는 &lt;span&gt;fall-through&lt;/span&gt;가 발생하지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721565467871&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int number = 2;

  switch (number) {
    case 1:
      print('One');
      break;
    case 2:
      print('Two');
      break;
    case 3:
      print('Three');
      break;
    default:
      print('Other number');
  }
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;if&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 문&lt;/b&gt;: 조건이 참일 때 코드 실행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;if-else&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 문&lt;/b&gt;: 조건이 참이면 &lt;span&gt;if&lt;/span&gt; 블록, 거짓이면 &lt;span&gt;else&lt;/span&gt; 블록 실행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;else if&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 문&lt;/b&gt;: 여러 조건 검사.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;중첩 &lt;/b&gt;&lt;span&gt;&lt;b&gt;if&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 문&lt;/b&gt;: 복잡한 조건 검사.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;삼항 연산자&lt;/b&gt;: 간단한 &lt;span&gt;if-else&lt;/span&gt; 문을 한 줄로 표현.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;switch&lt;/b&gt;&lt;/span&gt;&lt;b&gt; 문&lt;/b&gt;: 하나의 변수에 대한 여러 조건 검사.&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>DART</category>
      <category>dart 조건문</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/288</guid>
      <comments>https://juntcom.tistory.com/288#entry288comment</comments>
      <pubDate>Sun, 21 Jul 2024 21:38:27 +0900</pubDate>
    </item>
    <item>
      <title>dart collection 설명 및 예시</title>
      <link>https://juntcom.tistory.com/287</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Dart의 컬렉션(Collection)은 여러 요소를 관리하고 조작할 수 있는 데이터 구조를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 컬렉션 타입으로는 리스트(List), 셋(Set), 맵(Map)이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 컬렉션은 다양한 용도로 사용될 수 있으며, Dart는 이러한 컬렉션을 쉽게 사용할 수 있도록 다양한 메서드와 연산자를 제공합니다.&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;1. List&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;리스트(List)는 순서가 있는 요소의 집합으로, 배열과 유사합니다. 요소의 순서를 유지하며, 동일한 값을 중복해서 가질 수 있습니다.&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_1721547225147&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  // 빈 리스트 생성
  List&amp;lt;int&amp;gt; numbers = [];

  // 초기값을 가지는 리스트 생성
  List&amp;lt;String&amp;gt; fruits = ['Apple', 'Banana', 'Orange'];

  // 리스트에 요소 추가
  numbers.add(1);
  numbers.add(2);
  numbers.add(3);

  // 인덱스를 사용하여 요소 접근
  print(fruits[0]); // Apple

  // 리스트 길이
  print(fruits.length); // 3
}&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_1721547253790&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  List&amp;lt;String&amp;gt; fruits = ['Apple', 'Banana', 'Orange'];

  // 요소 추가
  fruits.add('Grape');

  // 여러 요소 추가
  fruits.addAll(['Mango', 'Pineapple']);

  // 요소 제거
  fruits.remove('Banana');

  // 인덱스로 요소 제거
  fruits.removeAt(1);

  // 조건에 맞는 모든 요소 제거
  fruits.removeWhere((fruit) =&amp;gt; fruit.startsWith('A'));

  // 요소 포함 여부 확인
  print(fruits.contains('Orange')); // true

  // 요소의 인덱스 찾기
  print(fruits.indexOf('Mango')); // 2
}&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;&lt;b&gt;2. Set&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;셋(Set)은 순서가 없는 고유한 요소의 집합입니다. 동일한 값을 중복해서 가질 수 없습니다.&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_1721547265498&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  // 빈 셋 생성
  Set&amp;lt;int&amp;gt; numbers = {};

  // 초기값을 가지는 셋 생성
  Set&amp;lt;String&amp;gt; fruits = {'Apple', 'Banana', 'Orange'};

  // 셋에 요소 추가
  numbers.add(1);
  numbers.add(2);
  numbers.add(3);

  // 요소 추가 (중복된 요소는 추가되지 않음)
  fruits.add('Apple');

  // 셋 길이
  print(fruits.length); // 3
}&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;&lt;b&gt;주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721547276849&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  Set&amp;lt;String&amp;gt; fruits = {'Apple', 'Banana', 'Orange'};

  // 요소 추가
  fruits.add('Grape');

  // 여러 요소 추가
  fruits.addAll({'Mango', 'Pineapple'});

  // 요소 제거
  fruits.remove('Banana');

  // 조건에 맞는 모든 요소 제거
  fruits.removeWhere((fruit) =&amp;gt; fruit.startsWith('A'));

  // 요소 포함 여부 확인
  print(fruits.contains('Orange')); // true
}&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;&lt;b&gt;3. Map&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;맵(Map)은 키-값 쌍으로 이루어진 컬렉션입니다. 각 키는 고유하며, 키를 통해 값에 접근할 수 있습니다.&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_1721547287461&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  // 빈 맵 생성
  Map&amp;lt;String, int&amp;gt; ages = {};

  // 초기값을 가지는 맵 생성
  Map&amp;lt;String, int&amp;gt; scores = {
    'Alice': 90,
    'Bob': 85,
    'Charlie': 95,
  };

  // 맵에 요소 추가
  ages['John'] = 25;
  ages['Doe'] = 30;

  // 키를 사용하여 값 접근
  print(scores['Alice']); // 90

  // 맵 길이
  print(scores.length); // 3
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721547301597&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  Map&amp;lt;String, int&amp;gt; scores = {
    'Alice': 90,
    'Bob': 85,
    'Charlie': 95,
  };

  // 요소 추가
  scores['David'] = 88;

  // 요소 제거
  scores.remove('Bob');

  // 모든 키 가져오기
  print(scores.keys); // (Alice, Charlie, David)

  // 모든 값 가져오기
  print(scores.values); // (90, 95, 88)

  // 특정 키의 값 업데이트
  scores.update('Alice', (value) =&amp;gt; 92);

  // 키 포함 여부 확인
  print(scores.containsKey('Charlie')); // true

  // 값 포함 여부 확인
  print(scores.containsValue(88)); // true
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart의 컬렉션은 데이터를 저장하고 조작하는 데 매우 유용한 도구입니다. 리스트(List)는 순서가 있는 요소의 집합을 다루며, 셋(Set)은 고유한 요소의 집합을 다루고, 맵(Map)은 키-값 쌍을 다룹니다. 이러한 컬렉션을 사용하여 데이터를 효율적으로 관리하고 처리할 수 있습니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.dart.dev/stable/dart-core/List-class.html&quot;&gt;Dart List 클래스 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.dart.dev/stable/dart-core/Set-class.html&quot;&gt;Dart Set 클래스 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://api.dart.dev/stable/dart-core/Map-class.html&quot;&gt;Dart Map 클래스 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://dart.dev/guides/language/language-tour#collections&quot;&gt;Dart Language Tour - Collections&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>DART</category>
      <category>dart collections</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/287</guid>
      <comments>https://juntcom.tistory.com/287#entry287comment</comments>
      <pubDate>Sun, 21 Jul 2024 16:35:42 +0900</pubDate>
    </item>
    <item>
      <title>dart 연산자</title>
      <link>https://juntcom.tistory.com/286</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Dart에서는 다양한 연산자를 제공하여 변수와 값에 대해 다양한 작업을 수행할 수 있습니다.&lt;/p&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;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;산술 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;증감 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;관계 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;논리 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;비트 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;6.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;할당 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;7.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;조건 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;8.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;형 변환 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;9.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;기타 연산자&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;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;산술 연산자는 기본적인 산술 계산을 수행합니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt; (덧셈)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt; (뺄셈)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt; (곱셈)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt; (나눗셈)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/&lt;/span&gt; (몫 연산)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&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;pre id=&quot;code_1721546021054&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int a = 10;
  int b = 3;

  print(a + b); // 13
  print(a - b); // 7
  print(a * b); // 30
  print(a / b); // 3.3333333333333335
  print(a ~/ b); // 3
  print(a % b); // 1
}&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;&lt;b&gt;2. 증감 연산자&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;/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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;++a&lt;/span&gt; (전위 증가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a++&lt;/span&gt; (후위 증가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--a&lt;/span&gt; (전위 감소)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a--&lt;/span&gt; (후위 감소)&lt;/p&gt;
&lt;pre id=&quot;code_1721546037975&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int a = 10;

  print(++a); // 11
  print(a++); // 11
  print(a); // 12
  print(--a); // 11
  print(a--); // 11
  print(a); // 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;&lt;b&gt;3. 관계 연산자&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;관계 연산자는 두 피연산자 간의 관계를 비교합니다. 결과는 boolean 값입니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt; (같다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt; (같지 않다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt; (크다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt; (작다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt; (크거나 같다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt; (작거나 같다)&lt;/p&gt;
&lt;pre id=&quot;code_1721546050239&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int a = 10;
  int b = 3;

  print(a == b); // false
  print(a != b); // true
  print(a &amp;gt; b); // true
  print(a &amp;lt; b); // false
  print(a &amp;gt;= b); // true
  print(a &amp;lt;= b); // 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;&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;논리 연산자는 boolean 값에 대해 논리 연산을 수행합니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt; (그리고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt; (또는)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt; (부정)&lt;/p&gt;
&lt;pre id=&quot;code_1721546061456&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  bool a = true;
  bool b = false;

  print(a &amp;amp;&amp;amp; b); // false
  print(a || b); // true
  print(!a); // false
  print(!b); // true
}&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;&lt;b&gt;5. 비트 연산자&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;/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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt; (AND)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt; (OR)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;^&lt;/span&gt; (XOR)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~&lt;/span&gt; (NOT)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt; (왼쪽 시프트)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt; (오른쪽 시프트)&lt;/p&gt;
&lt;pre id=&quot;code_1721546071371&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int a = 10; // 1010
  int b = 3; // 0011

  print(a &amp;amp; b); // 2 (0010)
  print(a | b); // 11 (1011)
  print(a ^ b); // 9 (1001)
  print(~a); // -11 (2의 보수)
  print(a &amp;lt;&amp;lt; 1); // 20 (10100)
  print(a &amp;gt;&amp;gt; 1); // 5 (0101)
}&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;&lt;b&gt;6. 할당 연산자&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;/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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt; (할당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt; (더한 후 할당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-=&lt;/span&gt; (뺀 후 할당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*=&lt;/span&gt; (곱한 후 할당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/=&lt;/span&gt; (나눈 후 할당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;~/=&lt;/span&gt; (몫을 구한 후 할당)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;%=&lt;/span&gt; (나머지를 구한 후 할당)&lt;/p&gt;
&lt;pre id=&quot;code_1721546087505&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int a = 10;
  int b = 3;

  a += b; // a = a + b
  print(a); // 13

  a -= b; // a = a - b
  print(a); // 10

  a *= b; // a = a * b
  print(a); // 30

  a /= b; // a = a / b
  print(a); // 10.0

  a ~/= b; // a = a ~/ b
  print(a); // 3

  a %= b; // a = a % b
  print(a); // 0
}&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;&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;/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&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;condition ? expr1 : expr2&lt;span&gt; (삼항 연산자)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;??&lt;/span&gt; (널 병합 연산자)&lt;/p&gt;
&lt;pre id=&quot;code_1721546099469&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int a = 10;
  int b = 3;

  var result = a &amp;gt; b ? 'a가 크다' : 'b가 크다';
  print(result); // a가 크다

  String? name;
  String userName = name ?? 'Guest';
  print(userName); // Guest
}&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;&lt;b&gt;8. 형 변환 연산자&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;/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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;as&lt;/span&gt; (형 변환)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is&lt;/span&gt; (타입 검사)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is!&lt;/span&gt; (타입 검사 - 부정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721546122807&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  dynamic a = 10;

  // 타입 검사
  if (a is int) {
    print('a는 int 타입입니다.');
  }

  // 타입 검사 - 부정
  if (a is! String) {
    print('a는 String 타입이 아닙니다.');
  }

  // 형 변환
  var b = a as int;
  print(b); // 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;&lt;b&gt;9. 기타 연산자&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[]&lt;/span&gt; (리스트 및 맵 접근 연산자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt; (멤버 접근 연산자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?.&lt;/span&gt; (널 안전 멤버 접근 연산자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;..&lt;/span&gt; (계단식 표기법)&lt;/p&gt;
&lt;pre id=&quot;code_1721546134720&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  List&amp;lt;int&amp;gt; numbers = [1, 2, 3];
  Map&amp;lt;String, int&amp;gt; ages = {'John': 25, 'Doe': 30};

  // 리스트 및 맵 접근
  print(numbers[0]); // 1
  print(ages['John']); // 25

  // 멤버 접근
  String text = 'Hello';
  print(text.length); // 5

  // 널 안전 멤버 접근
  String? nullableText;
  print(nullableText?.length); // null

  // 계단식 표기법
  var buffer = StringBuffer()
    ..write('Hello')
    ..write(' ')
    ..write('World');
  print(buffer.toString()); // Hello World
}&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;&lt;b&gt;결론&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;Dart 연산자는 다양한 작업을 수행할 수 있도록 돕는 중요한 도구입니다. 연산자의 올바른 사용법을 익히면 코드의 효율성과 가독성을 높일 수 있습니다. 이 문서에서 설명한 다양한 연산자를 잘 활용하여 더욱 효과적인 Dart 프로그래밍을 수행할 수 있습니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://dart.dev/guides/language/language-tour#operators&quot;&gt;Dart Language Tour - Operators&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>DART</category>
      <category>dart 연산자</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/286</guid>
      <comments>https://juntcom.tistory.com/286#entry286comment</comments>
      <pubDate>Sun, 21 Jul 2024 16:16:04 +0900</pubDate>
    </item>
    <item>
      <title>Vuejs 에서 환경 변수 설정 방법</title>
      <link>https://juntcom.tistory.com/285</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue.js 프로젝트에서는 환경 변수를 사용하여 개발, 테스트, 프로덕션 환경에 따라 설정을 다르게 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue CLI를 사용하면 이러한 환경 변수를 쉽게 설정하고 관리할 수 있습니다.&lt;/p&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;&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;Vue CLI를 사용하면 &lt;span&gt;.env&lt;/span&gt; 파일을 통해 환경 변수를 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue CLI는 프로젝트 루트 디렉토리에 위치한 &lt;span&gt;.env&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;b&gt;환경 변수 파일 종류&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;.env&lt;/b&gt;: 모든 환경에 적용되는 기본 환경 변수.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;.env.local&lt;/b&gt;: 모든 환경에 적용되지만, 버전 관리를 하지 않는 로컬 환경 변수.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;.env.development&lt;/b&gt;: 개발 환경에 적용되는 환경 변수.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;.env.development.local&lt;/b&gt;: 개발 환경에 적용되지만, 버전 관리를 하지 않는 로컬 환경 변수.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;.env.production&lt;/b&gt;: 프로덕션 환경에 적용되는 환경 변수.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;.env.production.local&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;프로젝트 루트에 &lt;/span&gt;.env.development&lt;span&gt;와 &lt;/span&gt;.env.production&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;.env.development&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721479632638&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VUE_APP_API_URL=http://localhost:3000
VUE_APP_DEBUG=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;.env.production&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721479658687&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VUE_APP_API_URL=https://api.example.com
VUE_APP_DEBUG=false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 환경 변수 사용&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;Vue 애플리케이션에서 환경 변수를 사용하려면 &lt;span&gt;process.env&lt;/span&gt; 객체를 통해 접근할 수 있습니다. Vue CLI는 &lt;span&gt;VUE_APP_&lt;/span&gt; 접두사가 붙은 환경 변수만을 인식하므로, 모든 환경 변수 이름은 &lt;span&gt;VUE_APP_&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;b&gt;예시: 환경 변수 사용&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;span&gt;src/main.js&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;b&gt;src/main.js&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721479683519&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Vue from 'vue';
import App from './App.vue';

Vue.config.productionTip = false;

console.log('API URL:', process.env.VUE_APP_API_URL);
console.log('Debug mode:', process.env.VUE_APP_DEBUG);

new Vue({
  render: h =&amp;gt; h(App),
}).$mount('#app');&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&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;&lt;b&gt;src/components/ExampleComponent.vue&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721479704879&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;API URL: {{ apiUrl }}&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;Debug Mode: {{ debugMode }}&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      apiUrl: process.env.VUE_APP_API_URL,
      debugMode: process.env.VUE_APP_DEBUG
    };
  }
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 환경 변수의 동작 확인&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;/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_1721479726168&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run serve&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;npm run serve&lt;span&gt; 명령어는 &lt;/span&gt;.env.development&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로덕션 모드 실행&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721479753882&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;npm run build&lt;span&gt; 명령어는 &lt;/span&gt;.env.production&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 data-ke-size=&quot;size16&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;span&gt;.env&lt;/span&gt; 파일과 &lt;span&gt;.env.production&lt;/span&gt; 파일은 일반적으로 버전 관리 시스템에 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 로컬 환경 변수 파일(&lt;span&gt;.env.local&lt;/span&gt;, &lt;span&gt;.env.development.local&lt;/span&gt;, &lt;span&gt;.env.production.local&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;b&gt;예시: .gitignore 파일 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;.gitignore&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721479775313&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# local env files
.env.local
.env.*.local&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;환경 변수 파일 설정&lt;/b&gt;: 프로젝트 루트에 &lt;span&gt;.env&lt;/span&gt; 파일을 생성하여 환경 변수를 정의합니다. 환경에 따라 &lt;span&gt;.env.development&lt;/span&gt;, &lt;span&gt;.env.production&lt;/span&gt; 파일을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;환경 변수 사용&lt;/b&gt;: &lt;span&gt;process.env&lt;/span&gt; 객체를 통해 환경 변수에 접근합니다. 모든 환경 변수는 &lt;span&gt;VUE_APP_&lt;/span&gt; 접두사로 시작해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;환경 변수의 동작 확인&lt;/b&gt;: 개발 또는 프로덕션 모드로 애플리케이션을 실행하여 환경 변수가 올바르게 로드되는지 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;환경 변수 파일 버전 관리&lt;/b&gt;: &lt;/span&gt;.env.local&lt;span&gt;, &lt;/span&gt;.env.development.local&lt;span&gt;, &lt;/span&gt;.env.production.local&lt;span&gt; 파일은 버전 관리에서 제외합니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue 환경변수</category>
      <category>vuejs</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/285</guid>
      <comments>https://juntcom.tistory.com/285#entry285comment</comments>
      <pubDate>Sat, 20 Jul 2024 21:51:57 +0900</pubDate>
    </item>
    <item>
      <title>Vue.js 프로젝트 설정 - vue cli 시작하기</title>
      <link>https://juntcom.tistory.com/284</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Vue.js 프로젝트를 설정하는 과정은 Vue CLI를 사용하면 매우 간단하고 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에 Vue CLI를 사용하여 프로젝트를 설정하는 단계별 가이드를 제공하겠습니다.&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;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;Vue.js 프로젝트를 시작하기 전에 Node.js와 Vue CLI가 설치되어 있어야 합니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Node.js 설치&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js 공식 웹사이트&lt;/a&gt;에서 최신 LTS 버전을 다운로드하고 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;설치가 완료되면, 터미널에서 Node.js와 npm(Node Package Manager)의 버전을 확인합니다.&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;node&amp;nbsp;-v&lt;br /&gt;npm&amp;nbsp;-v&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Vue CLI 설치&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue CLI는 Vue.js 애플리케이션을 생성하고 관리하는 데 도움을 주는 명령줄 도구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;npm을 사용하여 Vue CLI를 전역 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721477239543&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g @vue/cli&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue CLI 설치가 완료되면, 터미널에서 버전을 확인합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721477262411&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vue --version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 새로운 Vue 프로젝트 생성&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;Vue CLI를 사용하여 새로운 Vue 프로젝트를 생성합니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;프로젝트 생성 명령어&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721477279649&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vue create my-project&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;프로젝트 생성 과정&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;프로젝트 이름을 지정하고, Vue CLI에서 제공하는 기본 템플릿을 선택하거나, 커스텀 설정을 선택할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;기본 템플릿을 선택할 경우, &amp;ldquo;Default (Vue 2)&amp;rdquo; 또는 &amp;ldquo;Default (Vue 3)&amp;rdquo; 중 하나를 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;커스텀 설정을 선택할 경우, 필요한 기능들을 선택할 수 있습니다. (예: Babel, TypeScript, Vue Router, Vuex, CSS Pre-processors, Linter/Formatter, Unit Testing, E2E Testing)&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;3. 프로젝트 디렉토리 구조&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721477308795&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;my-project/
├── node_modules/       // 프로젝트의 의존성 모듈들이 설치되는 디렉토리
├── public/             // 정적 파일들이 위치하는 디렉토리
│   └── index.html      // 애플리케이션의 기본 HTML 파일
├── src/                // 소스 코드가 위치하는 디렉토리
│   ├── assets/         // 이미지, 스타일 등의 정적 파일
│   ├── components/     // Vue 컴포넌트들이 위치하는 디렉토리
│   ├── App.vue         // 루트 컴포넌트
│   ├── main.js         // 애플리케이션의 진입 파일
├── .gitignore          // Git에서 무시할 파일들을 지정하는 파일
├── babel.config.js     // Babel 설정 파일
├── package.json        // 프로젝트 메타데이터 및 의존성 정보
├── README.md           // 프로젝트 설명서&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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;/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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;개발 서버 실행&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721477361092&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd my-project
npm run serve&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm run serve&lt;/span&gt; 명령어를 실행하면, 로컬 개발 서버가 시작되고, 애플리케이션을 브라우저에서 확인할 수 있습니다. 일반적으로 &lt;span&gt;http://localhost:8080&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;b&gt;5. 주요 파일 및 설정&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;/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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;package.json&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;프로젝트의 메타데이터 및 의존성 정보를 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;스크립트 섹션에서 다양한 명령어를 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예:&lt;/p&gt;
&lt;pre id=&quot;code_1721479344702&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;my-project&quot;,
  &quot;version&quot;: &quot;0.1.0&quot;,
  &quot;scripts&quot;: {
    &quot;serve&quot;: &quot;vue-cli-service serve&quot;,
    &quot;build&quot;: &quot;vue-cli-service build&quot;,
    &quot;lint&quot;: &quot;vue-cli-service lint&quot;
  },
  &quot;dependencies&quot;: {
    &quot;vue&quot;: &quot;^2.6.11&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@vue/cli-service&quot;: &quot;~4.5.0&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;main.js&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;Vue 애플리케이션의 진입 파일로, Vue 인스턴스를 생성하고, 루트 컴포넌트를 마운트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예:&lt;/p&gt;
&lt;pre id=&quot;code_1721479358519&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Vue from 'vue';
import App from './App.vue';

Vue.config.productionTip = false;

new Vue({
  render: h =&amp;gt; h(App),
}).$mount('#app');&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;App.vue&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;루트 컴포넌트로, 애플리케이션의 주요 구조를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;예:&lt;/p&gt;
&lt;pre id=&quot;code_1721479370468&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div id=&quot;app&quot;&amp;gt;
    &amp;lt;img alt=&quot;Vue logo&quot; src=&quot;./assets/logo.png&quot;&amp;gt;
    &amp;lt;HelloWorld msg=&quot;Welcome to Your Vue.js App&quot;/&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import HelloWorld from './components/HelloWorld.vue';

export default {
  name: 'App',
  components: {
    HelloWorld
  }
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;개발 환경 설정&lt;/b&gt;: Node.js와 Vue CLI 설치.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;새로운 Vue 프로젝트 생성&lt;/b&gt;: Vue CLI를 사용하여 프로젝트 생성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;프로젝트 디렉토리 구조&lt;/b&gt;: 프로젝트의 기본 구조 이해.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;프로젝트 실행&lt;/b&gt;: 로컬 개발 서버를 통해 애플리케이션 확인.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;5.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;주요 파일 및 설정&lt;/b&gt;: &lt;/span&gt;package.json&lt;span&gt;, &lt;/span&gt;main.js&lt;span&gt;, &lt;/span&gt;App.vue&lt;span&gt; 등의 주요 파일 이해.&lt;/span&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vue cli</category>
      <category>vuejs</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/284</guid>
      <comments>https://juntcom.tistory.com/284#entry284comment</comments>
      <pubDate>Sat, 20 Jul 2024 21:43:36 +0900</pubDate>
    </item>
    <item>
      <title>Vue.js 소개 및 기본 개념</title>
      <link>https://juntcom.tistory.com/283</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Vue.js 소개&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;Vue.js는 경량의 점진적 프레임워크로, 사용자 인터페이스를 구축하는 데 사용됩니다. Vue는 주요 라이브러리 부분만으로도 충분히 활용할 수 있지만, 복잡한 애플리케이션을 개발할 때는 다양한 도구와 라이브러리들을 포함하는 생태계를 제공합니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;컴포넌트 기반 구조&lt;/b&gt;: 재사용 가능한 컴포넌트를 통해 복잡한 UI를 효율적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;반응형 데이터 바인딩&lt;/b&gt;: 데이터와 DOM이 자동으로 동기화되어 개발자 경험을 향상시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;유연성&lt;/b&gt;: 프로젝트의 크기와 복잡도에 맞게 필요한 기능만 선택적으로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;친화적인 학습 곡선&lt;/b&gt;: 기본적인 HTML, CSS, JavaScript 지식만으로 쉽게 학습할 수 있습니다.&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;2. Vue.js 기본 개념&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;2.1 Vue 인스턴스&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;Vue 애플리케이션의 모든 것은 Vue 인스턴스에서 시작합니다. Vue 인스턴스를 생성하여 특정 DOM 요소에 연결하고, 데이터와 메소드를 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721380636668&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.2 템플릿 문법&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;Vue는 HTML 기반 템플릿 문법을 사용하여 DOM에 데이터를 바인딩합니다. Vue의 템플릿 문법은 선언적 렌더링을 가능하게 합니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Mustache 구문&lt;/b&gt;: 데이터를 HTML로 바인딩하는 구문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721380659076&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;span&amp;gt;{{ message }}&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;디렉티브&lt;/b&gt;: v- 접두사를 사용하는 특수 속성으로, HTML 요소에 Vue의 반응형 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;v-bind: 속성 바인딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;v-model: 양방향 데이터 바인딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;v-if, v-else, v-show: 조건부 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;v-for: 리스트 렌더링&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;2.3 데이터 바인딩과 이벤트 처리&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;Vue는 데이터 바인딩과 이벤트 처리를 통해 UI와 데이터의 동기화를 쉽게 할 수 있습니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;데이터 바인딩&lt;/b&gt;: Vue 인스턴스의 데이터 속성을 HTML에 바인딩합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721380689543&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;span&amp;gt;{{ message }}&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;이벤트 처리&lt;/b&gt;: v-on 디렉티브를 사용하여 이벤트를 처리할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721380701372&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button v-on:click=&quot;reverseMessage&quot;&amp;gt;Reverse Message&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721380712633&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  },
  methods: {
    reverseMessage: function () {
      this.message = this.message.split('').reverse().join('');
    }
  }
});&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;&lt;b&gt;2.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;컴포넌트는 Vue의 핵심 기능으로, 재사용 가능한 UI 블록을 생성합니다. 컴포넌트를 통해 복잡한 애플리케이션을 모듈화하여 관리할 수 있습니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;전역 등록&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721380727093&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Vue.component('my-component', {
  template: '&amp;lt;div&amp;gt;A custom component!&amp;lt;/div&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;로컬 등록&lt;/b&gt;:&lt;/p&gt;
&lt;pre id=&quot;code_1721380738366&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var Child = {
  template: '&amp;lt;div&amp;gt;A custom component!&amp;lt;/div&amp;gt;'
};

new Vue({
  el: '#app',
  components: {
    'my-component': Child
  }
});&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;&lt;b&gt;2.5 Vue 인스턴스의 생명주기&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;Vue 인스턴스는 생성부터 소멸까지 여러 생명주기 단계를 거칩니다. 각 단계에서 특정 이벤트가 발생하며, 이 이벤트를 통해 특정 시점에 로직을 실행할 수 있습니다.&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&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;생명주기 훅&lt;/b&gt;: created, mounted, updated, destroyed 등 다양한 훅을 통해 애플리케이션의 생명주기 동안 특정 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721380750574&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  },
  created: function () {
    console.log('Vue instance is created');
  }
});&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Vue 인스턴스&lt;/b&gt;: Vue 애플리케이션의 기본 단위로, 데이터와 DOM 요소를 연결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;템플릿 문법&lt;/b&gt;: 선언적 렌더링을 위한 HTML 기반 문법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;데이터 바인딩&lt;/b&gt;: Vue의 반응형 데이터 바인딩으로 데이터와 UI를 동기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;컴포넌트&lt;/b&gt;: 재사용 가능한 UI 블록을 통해 애플리케이션을 모듈화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;생명주기&lt;/b&gt;: Vue 인스턴스의 생성부터 소멸까지의 단계로, 각 단계에서 특정 로직을 실행할 수 있습니다.&lt;/p&gt;</description>
      <category>프론트엔드/Vuejs</category>
      <category>vuejs</category>
      <category>vuejs 기본 및 이론</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/283</guid>
      <comments>https://juntcom.tistory.com/283#entry283comment</comments>
      <pubDate>Fri, 19 Jul 2024 18:20:12 +0900</pubDate>
    </item>
    <item>
      <title>구글 로그인 google oauth2 - 프론트 및 백엔드 별도</title>
      <link>https://juntcom.tistory.com/282</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Google Cloud Console 접속:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://console.cloud.google.com/&quot;&gt;https://console.cloud.google.com/&lt;/a&gt; 에 접속합니다.&lt;/li&gt;
&lt;li&gt;Google 계정으로 로그인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프로젝트 선택 또는 생성:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 프로젝트가 있다면 선택합니다.&lt;/li&gt;
&lt;li&gt;새 프로젝트를 만들려면 상단의 프로젝트 선택 드롭다운 메뉴에서 &quot;새 프로젝트&quot;를 선택합니다.&lt;/li&gt;
&lt;/ul&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;447&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uHTOP/btsIFkIfCCN/yFq2YRNJwlOkgwUEGlcxP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uHTOP/btsIFkIfCCN/yFq2YRNJwlOkgwUEGlcxP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uHTOP/btsIFkIfCCN/yFq2YRNJwlOkgwUEGlcxP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuHTOP%2FbtsIFkIfCCN%2FyFq2YRNJwlOkgwUEGlcxP0%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;345&quot; height=&quot;274&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;355&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;3. API 및 서비스 대시보드로 이동:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽 메뉴에서 &quot;API 및 서비스&quot;를 선택합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. OAuth 동의 화면 설정:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽 메뉴에서 &quot;OAuth 동의 화면&quot;을 선택합니다.&lt;/li&gt;
&lt;li&gt;외부 또는 내부 사용자 유형을 선택하고 &quot;만들기&quot;를 클릭합니다.&lt;/li&gt;
&lt;li&gt;필요한 정보를 입력하고 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extrenal 을 사용해줍니다.&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/cSou7U/btsIF4EQEjQ/nYs8IKvKJ5K4G7cwKA5H6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSou7U/btsIF4EQEjQ/nYs8IKvKJ5K4G7cwKA5H6K/img.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;503&quot; data-is-animation=&quot;false&quot; style=&quot;width: 51.2614%; margin-right: 10px;&quot; data-widthpercent=&quot;51.86&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSou7U/btsIF4EQEjQ/nYs8IKvKJ5K4G7cwKA5H6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSou7U%2FbtsIF4EQEjQ%2FnYs8IKvKJ5K4G7cwKA5H6K%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;840&quot; height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5N68n/btsJrbqwhSN/AmxgYGTmPqRDOuyZkZPiBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5N68n/btsJrbqwhSN/AmxgYGTmPqRDOuyZkZPiBK/img.png&quot; style=&quot;width: 47.5758%;&quot; data-widthpercent=&quot;48.14&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;551&quot; data-origin-width=&quot;854&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5N68n/btsJrbqwhSN/AmxgYGTmPqRDOuyZkZPiBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5N68n%2FbtsJrbqwhSN%2FAmxgYGTmPqRDOuyZkZPiBK%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;854&quot; height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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-filename=&quot;blob&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;1103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4tTD5/btsIFpqaL0b/Raq6OKrgawH3a0cNlj3Itk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4tTD5/btsIFpqaL0b/Raq6OKrgawH3a0cNlj3Itk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4tTD5/btsIFpqaL0b/Raq6OKrgawH3a0cNlj3Itk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4tTD5%2FbtsIFpqaL0b%2FRaq6OKrgawH3a0cNlj3Itk%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;553&quot; height=&quot;900&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;1103&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 이름 및 연락처만 등록 후 [저장 후 계속]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVLx4Z/btsIEmmvtpX/iJauVTPEFwk9NTfJlRF4n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVLx4Z/btsIEmmvtpX/iJauVTPEFwk9NTfJlRF4n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVLx4Z/btsIEmmvtpX/iJauVTPEFwk9NTfJlRF4n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVLx4Z%2FbtsIEmmvtpX%2FiJauVTPEFwk9NTfJlRF4n1%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;535&quot; height=&quot;654&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[저장 후 계속]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhXpxn/btsIE28ZAxL/69mGdtkS9yZoD7mShKRT9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhXpxn/btsIE28ZAxL/69mGdtkS9yZoD7mShKRT9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhXpxn/btsIE28ZAxL/69mGdtkS9yZoD7mShKRT9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhXpxn%2FbtsIE28ZAxL%2F69mGdtkS9yZoD7mShKRT9k%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;453&quot; height=&quot;446&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 할 계정을 추가해주세요.&lt;br /&gt;테스트 user 의 경우 오픈전에 설정해주어야 계정 테스트가 가능합니다.&amp;nbsp;&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ArvD0/btsIFpWZMeg/l9w89hvcoRJRKpoWkzLpG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ArvD0/btsIFpWZMeg/l9w89hvcoRJRKpoWkzLpG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ArvD0/btsIFpWZMeg/l9w89hvcoRJRKpoWkzLpG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FArvD0%2FbtsIFpWZMeg%2Fl9w89hvcoRJRKpoWkzLpG1%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;647&quot; height=&quot;128&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정까지 입력해주고 넘어갑니다.&lt;br /&gt;&lt;br /&gt;여기까지 OAuth 동의 화면 완료&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 사용자 인증 정보 생성:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽 메뉴에서 &quot;사용자 인증 정보&quot;를 선택합니다.&lt;/li&gt;
&lt;li&gt;&quot;사용자 인증 정보 만들기&quot; &amp;gt; &quot;OAuth 클라이언트 ID&quot;를 클릭합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baapNW/btsIEj4rMma/6YMe6yvlRjSvKIh3zIWiKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baapNW/btsIEj4rMma/6YMe6yvlRjSvKIh3zIWiKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baapNW/btsIEj4rMma/6YMe6yvlRjSvKIh3zIWiKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaapNW%2FbtsIEj4rMma%2F6YMe6yvlRjSvKIh3zIWiKk%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;925&quot; height=&quot;515&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[상단에 사용자 인증 정보 만들기] [OAuth 클라이언트 ID] 버튼 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VMNNw/btsIDW2Ns5A/j3mQZ91HhxJj4JxsekKIT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VMNNw/btsIDW2Ns5A/j3mQZ91HhxJj4JxsekKIT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VMNNw/btsIDW2Ns5A/j3mQZ91HhxJj4JxsekKIT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVMNNw%2FbtsIDW2Ns5A%2Fj3mQZ91HhxJj4JxsekKIT1%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;925&quot; height=&quot;305&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 유형 선택:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;애플리케이션 유형&quot;에서 &quot;웹 애플리케이션&quot;을 선택합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;769&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vLEYC/btsIGKs3R9E/WxN3fHsLnDvGpk5NufikMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vLEYC/btsIGKs3R9E/WxN3fHsLnDvGpk5NufikMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vLEYC/btsIGKs3R9E/WxN3fHsLnDvGpk5NufikMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvLEYC%2FbtsIGKs3R9E%2FWxN3fHsLnDvGpk5NufikMK%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;513&quot; height=&quot;668&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;769&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 정보 입력:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름을 입력합니다 (예: &quot;PopupGo Web Client&quot;).&lt;/li&gt;
&lt;li&gt;&quot;승인된 JavaScript 원본&quot;에 개발 서버 URL을 추가합니다 (예: &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:&lt;/a&gt;5173). -&amp;gt; 프론트&lt;/li&gt;
&lt;li&gt;&quot;승인된 리디렉션 URI&quot;에도 개발 서버 URL을 추가합니다 (예: &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:5173/oauth2/callback&lt;/a&gt;). -&amp;gt; 인증 후 redirect 할 주소. 해당 리다이렉트 파라미터에 access_token 을 받아올 수 있음. - 필자는 프론트 리다이렉트로 처리함.&lt;/li&gt;
&lt;li&gt;실제 배포 시에는 프로덕션 URL도 추가해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리다이렉션 URI 까지 입력 후 [만들기] 버튼 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0ykQA/btsIFcRa32Q/OVVNYe05Vz399jvPKvFCpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0ykQA/btsIFcRa32Q/OVVNYe05Vz399jvPKvFCpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0ykQA/btsIFcRa32Q/OVVNYe05Vz399jvPKvFCpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0ykQA%2FbtsIFcRa32Q%2FOVVNYe05Vz399jvPKvFCpK%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;239&quot; height=&quot;411&quot; data-origin-width=&quot;239&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘려진 화면 우측에 id 및 비밀번호가 보입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 ID 확인:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성된 클라이언트 ID가 표시됩니다. 이것이 YOUR_GOOGLE_CLIENT_ID 대신 사용할 값입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클라이언트 ID 보관:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 클라이언트 ID를 안전하게 보관하세요.&lt;/li&gt;
&lt;li&gt;언제든 &quot;사용자 인증 정보&quot; 페이지에서 다시 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 주의사항:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 ID는 공개될 수 있는 정보이지만, 클라이언트 시크릿은 절대 공개되어서는 안 됩니다.&lt;/li&gt;
&lt;li&gt;프로덕션 환경에서는 클라이언트 ID를 환경 변수로 관리하는 것이 좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 얻은 클라이언트 ID를 로그인 버튼 있는 파일의 YOUR_GOOGLE_CLIENT_ID 부분에 붙여넣으면 됩니다. 예를 들어:&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;/p&gt;
&lt;pre id=&quot;code_1721376857513&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;loginWithGoogle() {
      const clientId = '~~~~.apps.googleusercontent.com'; // Replace with your actual client ID
      const redirectUri = 'http://localhost:5173/oauth2/callback'; // The URL where you want to handle the response
      const scope = 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile';
      const responseType = 'token';

      const authUrl = `https://accounts.google.com/o/oauth2/auth?` +
                      `client_id=${clientId}&amp;amp;` +
                      `redirect_uri=${redirectUri}&amp;amp;` +
                      `response_type=${responseType}&amp;amp;` +
                      `scope=${scope}`;

      window.location.href = authUrl;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메소드를 실행하면 구글 로그인 할 수 있는 계정이 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1qKQJ/btsIGysSQHe/uOwO67k09k5OBzSLX3KJKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1qKQJ/btsIGysSQHe/uOwO67k09k5OBzSLX3KJKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1qKQJ/btsIGysSQHe/uOwO67k09k5OBzSLX3KJKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1qKQJ%2FbtsIGysSQHe%2FuOwO67k09k5OBzSLX3KJKK%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;446&quot; height=&quot;390&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;515&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clbh2n/btsIFUXCw7l/oB2rLNQHlW7Uq3MMdmjUeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clbh2n/btsIFUXCw7l/oB2rLNQHlW7Uq3MMdmjUeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clbh2n/btsIFUXCw7l/oB2rLNQHlW7Uq3MMdmjUeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fclbh2n%2FbtsIFUXCw7l%2FoB2rLNQHlW7Uq3MMdmjUeK%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;315&quot; height=&quot;294&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;543&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;계속을 누르면 링크가 이동 됩니다.&amp;nbsp;&lt;br /&gt;여기서 리다이렉트를 서버 api 로 받아도 되고, 다시 프론트로 받고 백엔드로 다시 요청해서 처리해도 됩니다.&lt;br /&gt;저는 리다이렉트 url 을 프론트로 했기 때문에 해당 프론트 페이지에서 토큰을 추출 후 서버로 요청했습니다.&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;프론트 리다이렉트 url&amp;nbsp;&lt;br /&gt;http://localhost:5173/oauth2/callback&amp;nbsp;&lt;br /&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;해당 리다이렉트 페이지로 오면 토큰 추출 후 백엔드 api 를 호출해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721377256482&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;handleOAuth2Callback() {
    const hashParams = new URLSearchParams(window.location.hash.substring(1));
    const accessToken = hashParams.get('access_token');
    console.log(accessToken)
    if (accessToken) {
      fetch('http://localhost:8080/api/oauth2/google', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ token: accessToken })
      })
      .then(response =&amp;gt; response.json())
      .then(data =&amp;gt; {
        if (data.success) {
          localStorage.setItem('jwt', data.jwt); // JWT 토큰을 로컬 스토리지에 저장
          this.$router.push('/home');
        } else {
          alert('Authentication failed');
        }
      });
    } else {
      alert('Failed to get access token from URL');
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;응답 후 처리 방식은 각자 코드에 맞게 수정해주시면 됩니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;그리고 프론트 리다이렉트 페이지에서 요청하는 백엔드 api 호출하면 됩니다.&amp;nbsp;&lt;br /&gt;해당 백엔드 api 에서는 가져온 토큰으로 서버에서 구글 토큰에 해당하는 유저정보를 가지고 옵니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721377378068&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@RestController
@CrossOrigin(origins = &quot;*&quot;)
@RequestMapping(&quot;/api/oauth2&quot;)
public class OAuth2Controller {

    @PostMapping(&quot;/google&quot;)
    public ResponseEntity&amp;lt;?&amp;gt; googleLogin(@RequestBody Map&amp;lt;String, String&amp;gt; body) {
        log.info(&quot;start google login&quot;);
        String token = body.get(&quot;token&quot;);
        log.info(&quot;Received token: {}&quot;, token);
        String url = &quot;https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=&quot; + token;
        RestTemplate restTemplate = new RestTemplate();
        try {
            log.info(&quot;Requesting token info from Google API&quot;);
            Map&amp;lt;String, Object&amp;gt; tokenInfo = restTemplate.getForObject(url, Map.class);
            log.info(&quot;Token info received: {}&quot;, tokenInfo);
            String email = (String) tokenInfo.get(&quot;email&quot;);
            log.info(email);
            // 사용자 정보를 통해 인증 및 세션 관리 로직 추가
            return ResponseEntity.ok(Map.of(&quot;success&quot;, true, &quot;email&quot;, email));
        } catch (Exception e) {
            log.error(&quot;Error during Google login&quot;, e);
            return ResponseEntity.status(401).body(Map.of(&quot;success&quot;, false, &quot;error&quot;, e.getMessage()));
        }
    }
}&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;security 가 적용되어 있다면 해당 api 허용되게 수정해주시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721377442794&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.requestMatchers(&quot;/api/oauth2/**&quot;).permitAll()&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;혹여나 안된다면 controller 에 cors 허용 되어있는지 확인해주시면 됩니다.&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;위의 코드로 작성해준다면 백엔드 api 에서 oauth 라이브러리를 import 하지 않아도 됩니다.&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;1306&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byl2kY/btsJteT89zK/7vMO6lSlEvH72IAMoNtze1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byl2kY/btsJteT89zK/7vMO6lSlEvH72IAMoNtze1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byl2kY/btsJteT89zK/7vMO6lSlEvH72IAMoNtze1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyl2kY%2FbtsJteT89zK%2F7vMO6lSlEvH72IAMoNtze1%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;1306&quot; height=&quot;558&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>서비스평가 및 사용/API사용</category>
      <category>google oauth2</category>
      <category>oauth</category>
      <category>spring boot oauth2</category>
      <category>구글 로그인</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/282</guid>
      <comments>https://juntcom.tistory.com/282#entry282comment</comments>
      <pubDate>Fri, 19 Jul 2024 17:35:25 +0900</pubDate>
    </item>
    <item>
      <title>Dart에서 변수 초기화 null Safety 와 late 키워드</title>
      <link>https://juntcom.tistory.com/281</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;변수의 초기화는 변수에 값을 할당하여 사용할 준비를 하는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart에서는 변수를 선언할 때 초기화하지 않으면 기본값이 할당되지만, 명시적으로 초기화하는 것이 좋습니다.&lt;/p&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;&lt;b&gt;Dart에서 변수 초기화&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;1. 기본 초기화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수를 선언할 때 초기값을 바로 할당할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721280972856&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  int number = 10;       // 정수형 변수 초기화
  double decimal = 3.14; // 실수형 변수 초기화
  String text = &quot;Hello&quot;; // 문자열 변수 초기화
  bool isTrue = true;    // 불리언 변수 초기화

  print(number);  // 10
  print(decimal); // 3.14
  print(text);    // Hello
  print(isTrue);  // true
}&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;&lt;b&gt;2. Null Safety와 late 키워드&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;Dart의 Null Safety 기능을 사용하면 변수를 선언할 때 초기화를 강제할 수 있습니다. &lt;span&gt;late&lt;/span&gt; 키워드를 사용하면 변수를 나중에 초기화할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721280995600&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  late String description;

  description = &quot;This is a description&quot;; // 나중에 초기화
  print(description); // This is a description
}&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;&lt;b&gt;3. final과 const&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;span&gt;final&lt;/span&gt;과 &lt;span&gt;const&lt;/span&gt; 키워드를 사용하면 상수 값을 초기화할 수 있습니다. &lt;span&gt;final&lt;/span&gt;은 런타임에 초기화할 수 있지만, &lt;span&gt;const&lt;/span&gt;는 컴파일 타임에 초기화해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721281050535&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  final int number = 42;       // 런타임 상수
  const double pi = 3.14159;   // 컴파일 타임 상수

  print(number); // 42
  print(pi);     // 3.14159
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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;Dart에서는 &lt;span&gt;var&lt;/span&gt;, &lt;span&gt;dynamic&lt;/span&gt; 키워드를 사용하여 동적 타입의 변수를 선언하고 초기화할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721281088905&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  var number = 10;          // Dart가 타입을 추론 (int)
  dynamic text = &quot;Hello&quot;;   // 동적 타입, 초기값에 따라 타입이 결정됨

  print(number); // 10
  print(text);   // Hello

  text = 42;     // dynamic 타입이므로 다른 타입의 값 할당 가능
  print(text);   // 42
}&lt;/code&gt;&lt;/pre&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;명확성&lt;/b&gt;: 변수를 선언할 때 초기값을 할당하면, 코드의 의도를 명확히 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;안정성&lt;/b&gt;: 초기화되지 않은 변수를 사용하면 예기치 않은 오류가 발생할 수 있습니다. 초기화를 통해 이러한 오류를 방지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Null Safety&lt;/b&gt;: Dart의 Null Safety 기능을 통해 변수를 초기화하지 않으면 컴파일 타임에 오류가 발생합니다. 이를 통해 Null 참조 오류를 방지할 수 있습니다.&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스의 인스턴스 변수를 초기화하는 예제입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721281104480&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person {
  String name;
  int age;

  // 생성자를 통해 변수 초기화
  Person(this.name, this.age);

  // 네임드 생성자를 통해 초기화
  Person.withDefaultName(this.age) : name = 'Unknown';
}

void main() {
  var person1 = Person('John', 30);
  var person2 = Person.withDefaultName(25);

  print('${person1.name}, ${person1.age}'); // John, 30
  print('${person2.name}, ${person2.age}'); // Unknown, 25
}&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;&lt;b&gt;결론&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;변수 초기화는 코드의 명확성, 안정성, Null Safety를 보장하기 위해 중요합니다. Dart에서는 다양한 방법으로 변수를 초기화할 수 있으며, &lt;span&gt;final&lt;/span&gt;, &lt;span&gt;const&lt;/span&gt;, &lt;span&gt;late&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;span&gt;&lt;span&gt; &lt;/span&gt;1.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Dart Language Tour - Variables&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;이 문서는 Dart에서 변수 선언과 초기화에 대한 기본적인 개념과 사용법을 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://dart.dev/guides/language/language-tour#variables&quot;&gt;Dart Language Tour - Variables&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;2.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Dart Language Tour - Null Safety&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;이 문서는 Null Safety와 관련된 개념을 다루며, 변수 초기화와 관련된 사항을 자세히 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://dart.dev/null-safety&quot;&gt;Dart Language Tour - Null Safety&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;3.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Dart Language Tour - Late Variables&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;이 문서는 &lt;span&gt;late&lt;/span&gt; 키워드를 사용하여 변수를 나중에 초기화하는 방법에 대해 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;a href=&quot;https://dart.dev/guides/language/language-tour#late-variables&quot;&gt;Dart Language Tour - Late Variables&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드/Flutter</category>
      <category>DART</category>
      <category>dart late</category>
      <author>곰돌이쿤</author>
      <guid isPermaLink="true">https://juntcom.tistory.com/281</guid>
      <comments>https://juntcom.tistory.com/281#entry281comment</comments>
      <pubDate>Thu, 18 Jul 2024 14:45:58 +0900</pubDate>
    </item>
  </channel>
</rss>