<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>성조의 기술 블로그</title>
    <link>https://okeybox.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 17 Aug 2026 02:12:40 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>SeongJo</managingEditor>
    <item>
      <title>[PostgreSQL] 정렬, 그룹화 그리고 집합 연산 (Oracle 비교)</title>
      <link>https://okeybox.tistory.com/517</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Database.png&quot; data-origin-width=&quot;2100&quot; data-origin-height=&quot;1500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpoT4F/dJMcaio9mnf/N9oQWchdTdoHcywp4HuMt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpoT4F/dJMcaio9mnf/N9oQWchdTdoHcywp4HuMt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpoT4F/dJMcaio9mnf/N9oQWchdTdoHcywp4HuMt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpoT4F%2FdJMcaio9mnf%2FN9oQWchdTdoHcywp4HuMt1%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;2100&quot; height=&quot;1500&quot; data-filename=&quot;Database.png&quot; data-origin-width=&quot;2100&quot; data-origin-height=&quot;1500&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 포스트그레 디비에서 ORDER BY, GROUP BY, HAVING과 집합 연산에 대한 간략한 정리를 진행해 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅은 PostgreSQL을 기준으로 설명하며, 마이그레이션을 오라클로 하거나 다른 데이터베이스 환경으로 넘길 때 차이점 등을 아주 간략하게 반영하여 공유드려봅니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;ORDER BY란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY절은 조회된 데이터를 특정 컬럼을 기준으로 정렬할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 다른 이기종 DB들도 동일하게 작동한다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기본 정렬 및 다중 정렬 방법&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1774196182496&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- PostgreSQL &amp;amp; Oracle 동일
SELECT emp_id, emp_name, salary, department_id
FROM employees
ORDER BY department_id ASC, salary DESC;&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_1774196194764&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1차 기준: 부서 ID 오름차순(ASC)
-- 2차 기준: 같은 부서 내에서는 급여 내림차순(DESC)
SELECT emp_id, emp_name, salary, department_id
FROM employees
ORDER BY department_id ASC, salary DESC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; 오름차순 ASC -&amp;gt; 작은 값부터 큰 값 순서로 정렬하며, 가장 큰 값인 NULL은 맨 마지막으로 밀려나는 구조이며, 내부적으로는 NULLS LAST가 기본 적용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내림차순 DESC -&amp;gt; 큰 값부터 작은 값 순서로 정렬하며, 가장 큰 값인 NULL은 맨 처음에 노출되는 구조이다. 동일하게 내부적으로는 NULLS FIRST가 기본 적용.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;NULLS FIRST OR NULLS LAST 예약어&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1774196378787&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 요구사항: 급여가 낮은 순(ASC)으로 정렬하되, 
-- 급여가 아직 책정되지 않은(NULL) 신규 입사자를 리스트 맨 위에 보여주고 싶을 때
SELECT emp_name, salary
FROM employees
ORDER BY salary ASC NULLS FIRST;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NULL을 처리하는 기본 동작은 두 데이터베이스가 동일하지만, 문자열 정렬에서는 다음과 같은 이유로 결과가 미세하게 달라질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 데이터베이스가 생성될 때 설정된 Collation (언어 및 정렬 규칙, LC_COLLATE)의 영향을 매우 강하게 받기 때문인데 오라클 환경에서 조회하던 한글 데이터의 정렬 순서와 100% 일치하지 않을 수 있는 규칙이 존재하기 때문이다. 두 DB는 마이그레이션 하는 경우가 많기에 주의를 갖고 체크해야 하는 포인트이다. 또한, 아래 이어지는 설명처럼 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;포스트그레와 오라클의 NULL 값의 정렬 처리 포인트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 오라클 -&amp;gt; 빈 문자열 ('') = NULL이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오라클은 값이 비어있는 문자열('')을 입력하면 이를 자동으로 NULL로 변환하여 저장한다. 이런 로직으로 인해 ORDER BY로 정렬할 때 빈 문자열 데이터들도 모두 NULL 취급되어서 NULLS LAST 규칙에 따라 맨 끝으로 밀려난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 포스트그레스큐엘 -&amp;gt; 빈 문자열 ('') != NULL이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오라클도 깐깐하지만, PostgreSQL은 더 깐깐하다. ''은 그저 '길이가 0인 텍스트'일뿐이고, NULL 은 '데이터 자체가 없음'으로 완전히 구분지어서 저장하기 때문에 Oracle의 NULL 저장 방식과는 다르게 데이터를 처리한다. 따라서 ORDER BY 시 빈 문자열은 일반 문자열로 취급되어 알파벳순 정렬 규칙을 따르고, NULL 데이터가 위 NULLS LAST 규칙을 따르는 것처럼 이동하지 않는다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GROUP BY이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY 절은 특정 컬럼의 값이 같은 행(ROW)들을 하나의 그룹으로 묶어주는 역할을 한다. 주로 SUM, AVG, COUNT 같은 집계 함수와 짝을 이루어 통계 데이터를 뽑아낼 때 사용하는 절이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예시 SQL&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774196806779&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 부서별(department_id) 직원 수와 총 급여액 구하기
SELECT department_id, COUNT(*) as emp_count, SUM(salary) as total_salary
FROM employees
GROUP BY department_id;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GROUP BY와 NULL (오라클과 포스트그레)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL의 대원칙 중 하나는 NULL은 알 수 없는 값이다. NULL = NULL은 거짓이 나오는 구조라는 게 기본 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, GROUP BY 절에서만큼은 예외적으로 여러 개의 NULL을 '동일한 하나의 그룹'으로 취급한다. (두 데이터베이스 모두 동일.)&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;size18&quot;&gt;&lt;b&gt;PostgreSQL&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- NULL 그룹 (1번)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; '' 빈 문자열 그룹 (2번)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Oracle&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- NULL 그룹 (단일)&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GROUP BY와 공집합(Empty Set)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건(WHERE)에 맞는 데이터가 아예 없는 '공집합' 상태에서 통계를 낼 때, GROUP BY를 썼느냐 안 썼느냐에 따라 서버가 받는 응답 형식이 다르게 변하는 것을 주의해야 한다. (이거 잘못하면 NullPointerException 에러가 발생되는 원리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;케이스 A&lt;/b&gt;: GROUP BY가 없는 단순 집계(Scalar Aggregation)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 정말 아예 없어도 DB는 억지로 통계 결과를 만들어 무조건 1건의 행(ROW)을 반환하는 집계&lt;/p&gt;
&lt;pre id=&quot;code_1774197150953&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 조건(1=0)을 만족하는 직원이 아예 없는 경우
SELECT COUNT(*), SUM(salary) 
FROM employees 
WHERE 1 = 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 COUNT(*): 0, SUM(salary): 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;케이스 B&lt;/b&gt;: GROUP BY 가 있는 집계 (Group Aggregation)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹을 지을 원본 데이터 자체가 없으므로, 그룹 생성 자체를 포기하는 방법으로 아무런 행도 반환하지 않는 케이스.&lt;/p&gt;
&lt;pre id=&quot;code_1774197315573&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT department_id, COUNT(*)
FROM employees
WHERE 1 = 0
GROUP BY department_id;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* B 케이스처럼 데이터베이스에서 GROUP BY를 썼을 때 데이터가 아예 안 넘어오는 상황을 백엔드 코드에서 인지하지 못해서 데이터 연산 처리를 잘못하고, '결과배열[0].카운트' 이런 코드로 접근하려다가 서버 에러가 터지는 경우도 많아서 중요 학습 포인트로 인지하면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY를 사용할 때는 API의 조회 결과가 0건일 수 있다는 예외 처리를 기본적으로 설정해 놓는 것이 좋다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그룹화된 결과의 필터링 HAVING&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 필터링한다는 목적이 WHERE와 같지만, WHERE 절과 HAVING 절은 데이터베이스 내부에서 실행되는 순서와 대상이 완전히 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- WHERE: 데이터를 그룹으로 묶기 전에, 원본 테이블의 개별 행(Row)들을 걸러내는 역할을 하는 절이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- HAVING: 데이터를 GROUP BY로 묶고 집계(Sum, Avg 등)를 끝낸 후에, 그 결과 그룹들을 걸러내는 절이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;예시 SQL&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774197519554&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT department_id, AVG(salary) as avg_salary
FROM employees
WHERE status = 'ACTIVE'       -- 1. [그룹화 전] 재직 중인 직원만 먼저 추려냄
GROUP BY department_id        -- 2. 부서별로 그룹화 및 평균 급여 계산
HAVING AVG(salary) &amp;gt;= 5000    -- 3. [그룹화 후] 평균 급여가 5000 이상인 '부서'만 남김
ORDER BY avg_salary DESC;     -- 4. 최종 결과 정렬&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;성능 튜닝 팁 정의&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE 절에 써야 할 일반 조건을 HAVNIG 절로 사용하는 경우가 있다. 그리고, 결과 데이터는 같지만, 이런 데이터들은 사실 데이터베이스에 필요 없는 데이터들까지 전부 그룹화 연산을 한 다음에 진행되는 방식이 되기에 조금만 큰 규모에 있는 서비스에서 이런 구조의 형식으로 코드를 작성하고 실행하는 경우 쿼리 성능이 크게 저하되는 문제가 있다. 그래서, 일반 조건은 반드시 WHERE 절에 배치하여 '*'를 때려 넣는 것이 아니라 정확하게 데이터를 먼저 연산할 수 있도록 사전에 데이터들을 줄여놓는 처리 후에 작업하는 것이 데이터베이스 성능에 관여하게 된다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;집합 연산 (Set Operation)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개 이상의 서로 다른 SELECT 쿼리 결과를 수학의 벤 다이어그렘처럼 합치거나 빼는 기능으로 사용하기 위한 연산 절인데 필수 전제 조건이 필요하다. -&amp;gt;&amp;nbsp;연산하려는 쿼리들끼리 반환하는 컬럼의 개수와 데이터 타입이 정확히 일치해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;합집합 UNION vs UNION ALL&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 쿼리의 결과를 위아래로 이어 붙이는 것인데 성능 차이가 심하기 때문에 둘을 명확하게 구분해서 사용해야 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UNION: 두 결과를 합친 뒤, 중복된 행을 찾아 제거한다. 중복을 찾기 위해 내부적으로 전체 데이터를 정렬(Sort)하는 무거운 작업이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UNION ALL: 중복 확인 없이 그냥 두 결과를 가져다 붙인다. 정렬 작업이 없으므로 속도는 빠른데 중복 데이터를 매우 많이 가져갈 수 있는 문제가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1774197842727&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 특별히 중복 제거가 필요한 상황이 아니라면, UNION ALL을 사용하는 것을 권장하지만 실제 중복 제거가 필요한 경우가 많기에 조심스러운 부분이다.
SELECT emp_id, emp_name FROM current_employees
UNION ALL 
SELECT emp_id, emp_name FROM retired_employees;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;교집합 INTERSECT&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 쿼리 결과에 공통으로 존재하는 행만 남기고 나머지는 버린다. (기본적으로 중복된 행은 1개로 압축하여 반환한다.)&lt;/p&gt;
&lt;pre id=&quot;code_1774197915037&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 우수 직원 명단과 보너스 수령자 명단에 둘 다 속하는(교집합) 직원 찾기
SELECT emp_id FROM top_performers
INTERSECT
SELECT emp_id FROM bonus_receivers;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;차집합 PostrgreSQL(EXCEPT)와 Oracle(MINUS)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법은 첫 번째 쿼리 결과에서 두 번째 쿼리에 있는 내용과 겹치는 부분을 빼버리는(제외하는) 연산으로 두 DB의 문법이 갈리는 포인트를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준 SQL을 잘 지키는 PostgreSQL은 표준 예약어인 EXCEPT를 사용한다. (이러니 인기 많고 좋아할 수밖에..)&lt;/p&gt;
&lt;pre id=&quot;code_1774198215193&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 전체 직원 중 특정 프로젝트(A) 참여자를 제외한 나머지 직원 찾기
SELECT emp_id FROM all_employees
EXCEPT
SELECT emp_id FROM project_a_members;&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;오라클은 독자적인 예약어로 MINUS를 사용해 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 포스트그레 인기가 점점 커져서 그런지.. 21c 버전 이후는 표준인 EXCEPT를 지원하고 있다. 하지만 대부분의 실무 레거시는 21c 버전 이전 코드들이 돌아가고 있을 테니 이런 데이터베이스 전환 부채들을 놓치면 매우 곤란해질 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1774198220969&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT emp_id FROM all_employees
MINUS
SELECT emp_id FROM project_a_members;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;인지 &lt;/b&gt;정리하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) ORDER BY + NULL -&amp;gt; 포스트그레나 오라클 모두 NULL이 크다고 판단하지만, 실제 데이터를 저장 처리하는 것이 다른 차이점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) GROUP BY + NULL -&amp;gt; 오라클은 여러 NULL 값은 하나의 그룹으로 묶이지만 포스트그레는 각 다른 그룹으로 분리한다. (위 참조)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) GROUP BY + 공집합 -&amp;gt; WHERE 조건에 의해 공집합이 발생하면, GROUP BY가 없는 집계는 1행(Null, 0 등)을 반환하고, GROUP BY가 있는 집계는 아예 결과가 없게 된다.(Empty Set).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) HAVING -&amp;gt; 그룹화가 완료된 결과 집합에 대한 필터를 진행하는 것이라 데이터베이스 성능을 저하시키고 싶지 않다면 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 차집합 연산 -&amp;gt; SQL 표준을 따르는 포스트그레와 독자 예약어를 사용하지만 최신 버전들은 SQL 표준도 지원하는 오라클을 인지하기&lt;/p&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;</description>
      <category>Database</category>
      <author>SeongJo</author>
      <guid isPermaLink="true">https://okeybox.tistory.com/517</guid>
      <comments>https://okeybox.tistory.com/517#entry517comment</comments>
      <pubDate>Mon, 23 Mar 2026 18:30:12 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Riverpod 알아보기 한 스푼</title>
      <link>https://okeybox.tistory.com/516</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;flutter.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cj03GL/dJMcabKlq5D/G8eFVrRofxAp7LY5CUxXgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cj03GL/dJMcabKlq5D/G8eFVrRofxAp7LY5CUxXgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cj03GL/dJMcabKlq5D/G8eFVrRofxAp7LY5CUxXgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcj03GL%2FdJMcabKlq5D%2FG8eFVrRofxAp7LY5CUxXgK%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;720&quot; data-filename=&quot;flutter.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&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;이번 포스팅은 상태관리에 대해 공부한 내용들을 작성하는 것을 중점으로 해요.&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;1. &lt;a href=&quot;https://riverpod.dev/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://riverpod.dev/ko/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774143973328&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Riverpod&quot; data-og-description=&quot;어디서나 공유 상태 선언하기 더 이상 main.dart과 UI 파일 사이를 오갈 필요가 없습니다. 공유 상태의 코드를 별도의 패키지에 넣든, 필요한 위젯 바로 옆에 넣든, 테스트 가능성을 잃지 않고 적절&quot; data-og-host=&quot;riverpod.dev&quot; data-og-source-url=&quot;https://riverpod.dev/ko/&quot; data-og-url=&quot;https://riverpod.dev/ko/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdwKtg/dJMb8YpUIAx/dopJUk76GwqFC8duVdgPy0/img.png?width=851&amp;amp;height=315&amp;amp;face=0_0_851_315,https://scrap.kakaocdn.net/dn/qV6jR/dJMb8SXw4dP/cs9uJ6MVc86WjYWWYorFkk/img.png?width=851&amp;amp;height=315&amp;amp;face=0_0_851_315,https://scrap.kakaocdn.net/dn/G8qYD/dJMb8Z3qzDn/MkwiBhQzogvYYxBZ44SoM1/img.png?width=2000&amp;amp;height=2000&amp;amp;face=0_0_2000_2000&quot;&gt;&lt;a href=&quot;https://riverpod.dev/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://riverpod.dev/ko/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdwKtg/dJMb8YpUIAx/dopJUk76GwqFC8duVdgPy0/img.png?width=851&amp;amp;height=315&amp;amp;face=0_0_851_315,https://scrap.kakaocdn.net/dn/qV6jR/dJMb8SXw4dP/cs9uJ6MVc86WjYWWYorFkk/img.png?width=851&amp;amp;height=315&amp;amp;face=0_0_851_315,https://scrap.kakaocdn.net/dn/G8qYD/dJMb8Z3qzDn/MkwiBhQzogvYYxBZ44SoM1/img.png?width=2000&amp;amp;height=2000&amp;amp;face=0_0_2000_2000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Riverpod&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;어디서나 공유 상태 선언하기 더 이상 main.dart과 UI 파일 사이를 오갈 필요가 없습니다. 공유 상태의 코드를 별도의 패키지에 넣든, 필요한 위젯 바로 옆에 넣든, 테스트 가능성을 잃지 않고 적절&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;riverpod.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;2. &lt;a href=&quot;https://techblog.lycorp.co.jp/ko/how-to-make-the-most-of-flutter-riverpod&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.lycorp.co.jp/ko/how-to-make-the-most-of-flutter-riverpod&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774143978033&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Flutter Riverpod 200% 활용하기&quot; data-og-description=&quot;Flutter에는 다양한 상태 관리 라이브러리가 존재합니다. 대표적으로 Provider와 GetX, BLoC 등이 있는데요. 이전에 Flutter 인기 아키텍처 라이브러리 3종 비...&quot; data-og-host=&quot;techblog.lycorp.co.jp&quot; data-og-source-url=&quot;https://techblog.lycorp.co.jp/ko/how-to-make-the-most-of-flutter-riverpod&quot; data-og-url=&quot;https://techblog.lycorp.co.jp/ko/how-to-make-the-most-of-flutter-riverpod&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dCusTT/dJMb83Sh7aU/O3LMHghynY8WxfcNKxDI9k/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/xWjbK/dJMb82MCn4N/qDLaY5bK2P2s3nZCzjDKk0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/2fVCm/dJMb8Z3qzDq/TuXiLw82AYeCWCTcUHkkSk/img.png?width=1702&amp;amp;height=772&amp;amp;face=0_0_1702_772&quot;&gt;&lt;a href=&quot;https://techblog.lycorp.co.jp/ko/how-to-make-the-most-of-flutter-riverpod&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.lycorp.co.jp/ko/how-to-make-the-most-of-flutter-riverpod&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dCusTT/dJMb83Sh7aU/O3LMHghynY8WxfcNKxDI9k/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/xWjbK/dJMb82MCn4N/qDLaY5bK2P2s3nZCzjDKk0/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/2fVCm/dJMb8Z3qzDq/TuXiLw82AYeCWCTcUHkkSk/img.png?width=1702&amp;amp;height=772&amp;amp;face=0_0_1702_772');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Flutter Riverpod 200% 활용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flutter에는 다양한 상태 관리 라이브러리가 존재합니다. 대표적으로 Provider와 GetX, BLoC 등이 있는데요. 이전에 Flutter 인기 아키텍처 라이브러리 3종 비...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.lycorp.co.jp&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라인에서 작성한 기술 블로그 콘텐츠로 동작 원리와 실무 팁 등을 잘 작성한 자료로 보기 좋은 자료라 생각된다. 버전에 따라 상황에 따라 사용 방법들이 다르게 적용되겠지만, 한글로 친절하게 어떤 것이다 하고 gif로 동작하는 것까지 보여주는 경우는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Riverpod(리버팟)이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StateProvider, FutureProvider, StateNotifierProvider와 같은 메서드들 때문에 진입 장벽이 높아지면서 복잡한 상태 관리가 불편함을 전하는 것 때문에 Provider의&amp;nbsp; 창시자 Remi Rousselet가 직접 단점을 개선하기 위해 만든 새로운 상태 관리 라이브러리이다.&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;Provider나 BLOC 등의 상태 관리 라이브러리도 좋지만, 중형급 프로젝트에는 리버팟이 효율성이 가장 좋다. 개발 문서에서는 영문은 '&lt;b&gt;A Reactive Caching and Data-binding Framework&lt;/b&gt;' 한글로는 '&lt;b&gt;리액티브 캐싱 및 바인딩 프레임워크&lt;/b&gt;'라 정의되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 한글로 풀어 작성하면 캐싱(Caching)은 자주 사용하는 데이터를 고속 메모리에 임시 저장하여 시스템 성능을 향상시키는 기술이고, 바인딩(Binding)은 프로그램의 변수나 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;&amp;nbsp;&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;Provider의 단점이 뭐였는데?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) UI 트리 밖에서상태를 읽거나 수정하기 어려운 BuildContext에 대한 의존성 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) ProviderNotFoundException과 같이 앱을 실행해 봐야지만 알 수 있는 에러가 존재하여 런타임 에러 발생 가능성이 높아지는 문제가 존재.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 두 개의 Provider&amp;lt;String&amp;gt;을 동시에 사용하는 게 어렵고, 같은 타입의 Provider를 중복 사용할 수 없는 문제가 있다.&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;리버팟은 무엇을 해결했나?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) BuildContext 없이도 어디서든 안전하게 상태에 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 서버 데이터의 최적화.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 동일한 타입의 상태를 여러 개 만들어도 충돌하지 않는다. (중복성 관리가 어려울 수 있기에 주의가 필요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 다만, 보일러플레이트(수정 없이 반복적으로 재사용되는 표준화된 코드 블록이나 템플릿들을 의미하는 정의) 코드를 풀어주는 코드 제너레이션(Riverpod Generator)을 공식 지원.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 컴파일 타임에 안정성(Compile-time safety)을 보장하여 런타임 에러를 방지하여 안정성을 향상한다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코드 제너레이션을 왜 사용하는 가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 문법의 단순화 -&amp;gt; FutureProvider, NotifierProvider 등 상황에 맞는 Provider를 고민할 필요 없이 함수나 클래스 위에 @riverpod 만 붙이면 자동으로 생성해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Family의 한계 극복 -&amp;gt; 기존 프로바이더는 하나의 파라미터만 넘기는 게 가능했는데 리버팟을 통해 여러 개의 파라미터를 전달하는데 위치 기반, 네임드 파라미터 등 모두 자유롭게 전달할 수 있게 되는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) AutoDispose 기본 적용 -&amp;gt; 메모리 누수 방지를 위해 상태가 더 이상 사용되지 않으면 자동으로 메모리 해제되는 것이 기본적인 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 디버깅 향상 -&amp;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;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기본 세팅 방법&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1774147072154&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(
    // ProviderScope로 앱 전체를 감싸준다.
    const ProviderScope(
      child: MyApp(),
    ),
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지를 가져오고, 정의만 처리하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;리버팟의 상태 정의? 그리고, 사용 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 정의를 진행하려면 코드를 작성한 다음 반드시 flutter pub run build_runner watch -d를 실행하여 코드 자동 생성(. g.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) 읽기 전용 데이터(또는 비동기 Fetch)의 함수형&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API에서 데이터를 가져오거나, 변하지 않는 설정값을 제공할 때는 일반 함수에 @riverpod을 붙인다. 내부적으로는 FutureProvider와 동일하게 작동한다.&lt;/p&gt;
&lt;pre id=&quot;code_1774147524714&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:riverpod_annotation/riverpod_annotation.dart';

// 코드 제너레이터가 생성할 파일명
part 'user_provider.g.dart';

// @riverpod 어노테이션을 붙이면 자동으로 UserProvider가 생성.
@riverpod
Future&amp;lt;String&amp;gt; fetchUser(FetchUserRef ref, {required int userId}) async {
  // 가상의 API 호출
  await Future.delayed(const Duration(seconds: 2));
  return 'User_$userId';
}&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;UI에서 버튼을 눌러 값을 바꾸는 등의 로직이 필요하다면 다트 코드로 클래스를 작성하고 riverpod을 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 구성하면 NotifierProvider로 작동한다.&lt;/p&gt;
&lt;pre id=&quot;code_1774147555594&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'counter_provider.g.dart';

@riverpod
class Counter extends _$Counter {
  // 초기 상태값을 설정하는 build 메서드 (필수)
  @override
  int build() {
    return 0; 
  }

  // 상태를 변경하는 커스텀 메서드
  void increment() {
    state++; // state 변수에 접근하여 값을 변경하면 UI가 자동 업데이트됨
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 이름이 Counter라면 반드시 _$Counter를 상속(extends) 받는 구조여야 한다.&lt;/p&gt;
&lt;p 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;코드 제너레이션을 사용하면 기본적으로 화면을 벗어날 때 상태가 파괴(autoDispose)가 되는데 탭이 이동되어도 상태나 캐시 데이터를 유지하고 싶다면 KeepAliver를 통해 상태를 유지할 수 있다. &lt;b&gt;@Riverpod(keepAlive: true)&lt;/b&gt; 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 유저 데이터의 상태가 변경되지 않아야 하는 특정 상태 이거나 사라지면 안 되는 데이터를 들고 있을 때 이 조건이 특정 조건으로 변경됐을 때 해제의 기준이 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1774148062909&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Riverpod(keepAlive: true)
class AuthToken extends _$AuthToken {
  @override
  String? build() =&amp;gt; null;
  // ...
}&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) UI에서 상태 읽고 작성하는 방법 - ConsumerWidget&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI에서 상태를 사용할 때는 &lt;b&gt;ConsumerWidget&lt;/b&gt;과 WidgetRef를 사용하고 이전과 동일&lt;/p&gt;
&lt;pre id=&quot;code_1774148160279&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_provider.dart'; // 생성된 provider import

class CounterPage extends ConsumerWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 1. 상태 읽기 (watch - 값이 바뀌면 UI 재빌드)
    // 코드 제너레이터가 클래스 이름에 'Provider'를 붙인 객체를 자동 생성 (counterProvider)
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Modern Riverpod')),
      body: Center(
        child: Text('Count: $count', style: const TextStyle(fontSize: 24)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 2. 상태 변경 메서드 호출 (read)
          // 클래스 내부에 정의한 메서드를 호출할 때는 .notifier를 붙여 접근.
          ref.read(counterProvider.notifier).increment();
        },
        child: const 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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 비동기 데이터 처리 (AsyncValue)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수형으로 만든 비동기 Provider (fetchUserProvider)를 UI에서 사용할 때는 when 패턴을 사용해 로딩, 에러, 성공 상태를 깔끔하게 분기처리 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1774148262558&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Widget build(BuildContext context, WidgetRef ref) {
  // 파라미터를 넘김.
  final asyncUser = ref.watch(fetchUserProvider(userId: 1));

  return asyncUser.when(
    data: (user) =&amp;gt; Text('안녕하세요, $user님'),
    error: (err, stack) =&amp;gt; Text('에러 발생: $err'),
    loading: () =&amp;gt; const CircularProgressIndicator(),
  );
}&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;b&gt;5) Provider&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774148827825&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final helloWorldProvider = Provider&amp;lt;String&amp;gt;((ref) {
  return 'Hello, Riverpod!';
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본으로 사용하는 방법으로 변하지 않는 값(Read-only)이나 객체를 제공할 때 사용하고, 주로 의존성 주입(DI), 설정 값, 저장소 (Repository) 인스턴스 등을 제공할 때 사용된다.&lt;/p&gt;
&lt;p 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) StateProvider&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774148973523&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final counterProvider = StateProvider&amp;lt;int&amp;gt;((ref) =&amp;gt; 0);

// 값 변경 시: ref.read(counterProvider.notifier).state++;&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;정수(int), 문자열(String), 불리언(boolean), 열거형(enum) 등 간단한 상태(예시 - 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;7) NotifierProvider&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1774149024287&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. Notifier 클래스 정의
class CounterNotifier extends Notifier&amp;lt;int&amp;gt; {
  @override
  int build() {
    return 0; // 초기 상태 값 반환
  }

  // 상태를 변경하는 비즈니스 로직 (메서드)
  void increment() {
    state++;
  }
}

// 2. Provider 생성
final counterNotifierProvider = NotifierProvider&amp;lt;CounterNotifier, int&amp;gt;(() {
  return CounterNotifier();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 작업(API 호출, DB 읽기 등)의 상태를 관리할 때 필수적으로 사용되며, 데이터가 로딩 중인지(loading), 에러가 났는지(error), 성공적으로 데이터를 가져왔는지(data)를 AsyncValue라는 객체로 안전하게 래핑 하여 제공&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;Ref 객체 활용 방법 정리 한 스푼&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. ref.watch(provider) UI 렌더링 용도&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상태의 값을 읽어오고, 해당 상태가 변경될 때마다 위젯을 재빌드(rebuild)한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반드시 build 메서드 내부에서만 사용해야 하는 것을 주의해야 된다.&lt;/p&gt;
&lt;p 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. ref.read(provider) 1회성 읽기의 이벤트 핸들러 용도&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;- 버튼의 onPressed 등 콜백 함수나 이벤트 핸들러 내부에서만 사용해야 한다. watch처럼 build 메서드 내에서 쓰면 안 된다&lt;/p&gt;
&lt;p 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. ref.listen(provider) 부수 효과 처리 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 상태가 변경될 때마다 특정 액션을 실행한다. (ex. 스낵바 띄우기나 화면 이동, 모달 띄우기 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- build 메서드 안에서 호출하지만 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 Riverpod은 Provider가 가졌던 태생적 한계(런타임 에러, 중복 사용 불가, BuildContext 의존성 등)를 완벽히 극복한 가장 모던한 상태 관리 프레임워크에 가깝다고 판단한다.&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;초기 세팅이나 build_runner를 주기적으로 실행하여 생성한다는 조금 낯설지만 컴파일 타임의 강력한 안정성과 유연성 덕분에 중대형급의 프로젝트에도 적절하고 생산성이나 코드 컨벤션 등이 매우 유연하고 좋은 방향으로 잘 구성되어 있다 판단되어 유지보수성과 개발 속도를 개선해 줄 수 있는 든든한 상태관리 라이브러리라고 생각하게 됐다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서 뵙겠습니다.&lt;/p&gt;</description>
      <author>SeongJo</author>
      <guid isPermaLink="true">https://okeybox.tistory.com/516</guid>
      <comments>https://okeybox.tistory.com/516#entry516comment</comments>
      <pubDate>Sun, 22 Mar 2026 18:30:43 +0900</pubDate>
    </item>
    <item>
      <title>[리뷰, 독서] 사무실의 도른자들 후기</title>
      <link>https://okeybox.tistory.com/515</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;2250&quot; data-origin-height=&quot;3000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qpQd7/dJMcahDJySn/AT1PcRzPabKJNIwAoNWe3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qpQd7/dJMcahDJySn/AT1PcRzPabKJNIwAoNWe3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qpQd7/dJMcahDJySn/AT1PcRzPabKJNIwAoNWe3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqpQd7%2FdJMcahDJySn%2FAT1PcRzPabKJNIwAoNWe3k%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;2250&quot; height=&quot;3000&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;3000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 성조입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;도서관에 들렸을 때 가볍게 읽을 책을 고르다가 우연찮게 책을 고르게 됐고, 해당 책을 읽은 내용들을 기록해 보는 포스팅을 작성해 봤어요.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;제가 작성한 부분들은 주관적 의견을 담고 있기에 책에서 설명하는 글과 다를 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사무실의 도른자들이란 무슨 책인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;본인은 상대방의 심리 또는 이런 유형이 있다는 형식의 책으로 느꼈다. 실제 사무실에는 문제를 정의할 수 없을 만큼 다양한 사람과 인간관계 태생들이 존재하는데 이 도서에서는 범용적인 무수히 많은 케이스들을 축약하여 최대한 행동 유형에 따른 필자의 생각들을 작성한 책이라 정의할 수 있을 것이라 생각한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;책을 읽으면서&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;도서는 읽을 때마다 경험하는 것이지만, 모든 해답을 정의하지 않는다. 이 도서는 필자가 독자에게 전하고 싶은 사항들 어떻게 유형들을 대처할 것인지에 대해 가이드를 정리해 주는데 조금 생각이 더 들어갔던 부분이 존재했다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;실제 사무실에서 다양한 경험으로 사람들을 만나봤을지언정 모든 심리학을 꿰차고 있더라도 어려운 간파 항목들의 정의들을 해당 도서에서 다룬다는 것을 가정했다 생각된다. A가 할 때 B 유형으로 대응 방법들을 조심하는 것 또는 대처 방법 등이 도서에서 나온 정의이다. 내가 생각했던 부분과의 다름은 이런 사람이 짧은 파트 이내에서 A가 B 유형으로 대처할 수 있을까라는 의문이었다. 사람을 만나면 만날 수록 유사 유형들은 판별할 수 있는 생각하는 힘들은 키워진 것 같지만, 실제로 딥한 부분까지 가면 사람마다 가지고 있는 깊이를 판단하기 매우 어려운 게 인간관계이기에 '정확한 판별을 할 수 있을까?'라는 생각이 들었다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다양한 사람들을 만나다 보면 내가 정말 놓치고 있는 것에 대해 근본적으로 모르고 지나가는 경우가 많다. 좋은 예제 케이스가 된 책으로 나에게 학습될 수 있겠지만 나는 이번 도서를 읽으면서 편파적인 생각과 한정적 지식 고립에 대해 빠질 위험이 있겠다는 생각이 들었다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 도서 하나로 모든 사람들을 정의하지 않는 것을 주의하는 것이 좋을 것이라고 생각이 깊게 와닿았다. 실제 도움 되는 말도 작성되어 있었던 것도 명확하기에 추천하지 않는다는 것은 아니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다만, 이 도서를 불특정 한 독자들 중 그 사람의 신념이 되어버렸을 때 영향도가 가볍게 생각되지 않을 부분이라 판단되어서 열린 생각을 가지고 고민해 볼 수 있는 사람일 때 또는 그런 모임으로 주제를 선정하여 책을 읽었으면 좋겠다. 이 한 권만 읽고 다음 도서를 읽는 기간이 너무 길어져서 이 책이 삶의 1순위 책이 되기엔 한번 정의된 mbti를 평생 내 성격이고 개선 개량할 수 없다는 편견이 생길 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;장점이라 생각했던 부분들은 일반 도서들에서도 접할 수 있는 내용들을 기본적으로 공유하는 부분도 있었다는 점이고, 단점이라 판단된 부분은 이 도서가 선행되었을 때 책을 읽으면서 주관적 판단을 내릴 수 있는 힘을 기르고 있는 독자에겐 편견을 제시할 수 있지 않을까라는 우려가 있었던 부분이다. 도서에서 리스크를 발생시키는 것은 불필요한 과정이라 판단되어서 언급 정리를 해보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그래도, 재미로 읽을 수 있는 장점도 있으니 도서를 좋아하는 사람이고, 주관적 판별을 내릴 수 있는 사람이라면 한 번 읽어보면 좋겠다. 도서의 마지막 부분을 참조하면 본인이 사무실의 어떤 도른자들인지 테스트하는 항목군도 존재한다. 이런 항목군들이 내가 타인에게 어떻게 비쳤을지 100% 객관적으로 표기할 수는 없지만 어느 정도 성찰하고 파악할 수 있는 지표가 될 테니 1~2주일 정도 가볍게 시간 내서 출퇴근 시간에 읽어보는 것도 좋다고 본다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;앞으로도 독서의 리뷰들은 다양한 형태로 진행될 예정이에요.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 부분이 마음에 들었는지 안 들었는지 아쉬웠던 부분이나 다른 부분이 재밌다고 판단한 내용 등을 종합적으로 작성하는 것들을 진행할 예정입니다. 제 삶에 실천할 설루션이 될 수 있지만, 일반적인 도서로 아무것도 남지 않는 일은 발생되지 않도록 차곡차곡 잘 쌓아보겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서 뵙겠습니다.&lt;/p&gt;</description>
      <category>리뷰</category>
      <category>도서</category>
      <category>독서</category>
      <category>리뷰</category>
      <category>사무실의도른자들</category>
      <author>SeongJo</author>
      <guid isPermaLink="true">https://okeybox.tistory.com/515</guid>
      <comments>https://okeybox.tistory.com/515#entry515comment</comments>
      <pubDate>Sat, 21 Mar 2026 18:30:57 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Provider 상태 관리 기본 정리</title>
      <link>https://okeybox.tistory.com/514</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;flutter.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGLh4o/dJMcabcqdUf/L5Q5Uk1GbaQllF04m1WtY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGLh4o/dJMcabcqdUf/L5Q5Uk1GbaQllF04m1WtY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGLh4o/dJMcabcqdUf/L5Q5Uk1GbaQllF04m1WtY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGLh4o%2FdJMcabcqdUf%2FL5Q5Uk1GbaQllF04m1WtY1%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;720&quot; data-filename=&quot;flutter.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 플러터의 상태관리 개념 중 하나인 Provider에 대해 간략히 정리하는 포스트를 작성해 봤습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Provider란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Provider는 Flutter의 내장 기능인 InheritedWidget을 기반으로 만들어진 상태 관리 패키지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터(상태)를 위젯 트리(Widget Tree)의 상단에 올려두고 데이터가 필요한 하위 위젝들이 어디서든 쉽게 접근하고 사용할 수 있도록 도와주는 역할을 한다.&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;123.png&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceZlf0/dJMcagSmN6W/KpHOLPgNzjuKnyV4q2s2F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceZlf0/dJMcagSmN6W/KpHOLPgNzjuKnyV4q2s2F1/img.png&quot; data-alt=&quot;플러터 공식 문서의 설명 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceZlf0/dJMcagSmN6W/KpHOLPgNzjuKnyV4q2s2F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceZlf0%2FdJMcagSmN6W%2FKpHOLPgNzjuKnyV4q2s2F1%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;884&quot; height=&quot;574&quot; data-filename=&quot;123.png&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;플러터 공식 문서의 설명 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[공식 문서 링크]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/data-and-backend/state-mgmt/simple&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.flutter.dev/data-and-backend/state-mgmt/simple&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773910749222&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Simple app state management&quot; data-og-description=&quot;A simple form of state management.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/data-and-backend/state-mgmt/simple&quot; data-og-url=&quot;https://docs.flutter.dev/data-and-backend/state-mgmt/data-and-backend/state-mgmt/simple.md&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mMgCJ/dJMb84XXPec/16CckJQONrWzYnf3Smn0r0/img.png?width=320&amp;amp;height=320&amp;amp;face=0_0_320_320&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/data-and-backend/state-mgmt/simple&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/data-and-backend/state-mgmt/simple&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mMgCJ/dJMb84XXPec/16CckJQONrWzYnf3Smn0r0/img.png?width=320&amp;amp;height=320&amp;amp;face=0_0_320_320');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Simple app state management&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A simple form of state management.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;어떤 경우에 사용되는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Prop Drilling 방지 -&amp;gt; 부모 위젯에서 매우 먼 자식 위젯으로 데이터를 전달하기 위해 불필요한 생성자들을 통해 전달 전달해 주는 게 아닌 바로 전달할 수 있도록 기능한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 관심사 분리 -&amp;gt; UI를 그리는 코드와 비즈니스 로직(데이터 처리) 코드를 완전히 분리할 수 있어 코드의 가독성과 유지보수성이 크게 향상된다. 다만, AI주도 개발을 진행하는 요즘 같은 시대는 AI가 자기 멋대로 한 코드에 모두 때려 놓으니 스킬(skill) 같은 폴더 셋을 만들어서 구분 짓는 것이 아니라면, 상시적으로 리팩터링 하거나 문법을 미리인지하여 작업들을 사전에 작업들을 진행할 수 있도록 개선 반영들을 미리 구상해 놓는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 랜더링 효율화 -&amp;gt; 원하는 위젯만 선택적으로 다시 Rebuild 할 수 있는 장점이 있지만, 다른 상태관리 코드들도 해당 방식을 못하는 것이 아니기 때문에 올바른 값들의 반영들을 모두 고려하는 것이 좋다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Provider 상태 관리 3가지&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) ChangeNotifier (상태 보관 &amp;amp; 알림)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할 : 실제 데이터(상태)를 저장하고 관리하는 클래스로, Flutter에서 제공하는 기본 클래스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활용 방법: 데이터가 변경되었을 때 notifyListeners() 메서드를 호출하면, 이 데이터를 바라보고 있는 위젯들에게 &quot;데이터가 바뀌었으니 화면을 다시 그려줘&quot;라고 신호를 보내는 용도로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) ChangeNotifierProvider (상태 제공자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할: ChangeNotifier를 위젯 트리에 등록하여 하위 위젯들이 이 데이터에 접근할 수 있게 해주는 위젯이다.&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;3) Consumer &amp;amp; Context (상태 구독자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할: 제공된 데이터(상태)를 가져와서 화면에 보여주거나 데이터를 수정하는 위젯이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활용 방법: Consumer&amp;lt;T&amp;gt; 위젯을 사용하거나, context.watch&amp;lt;T&amp;gt;(), context.read&amp;lt;T&amp;gt;() 등의 확장 메서드를 사용하여 데이터에 접근하여 사용한다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Provider 사용 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 패키지 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773911432180&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies:
  flutter:
    sdk: flutter
  provider: # 최신 버전 또는 본인의 프로젝트에 맞는 패키지 버전을 반영&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.ChangeNotifer를 활용한 상태 클래스 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773911408540&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class CounterProvider with ChangeNotifier {
  int _count = 0; // 프라이빗 변수로 상태 보호

  int get count =&amp;gt; _count; // 외부에서 값을 읽기 위한 getter

  void increment() {
    _count++;
    notifyListeners(); // 값이 변경되었음을 알림 (UI 업데이트 트리거)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태를 관리할 클래스를 생성하고 ChangeNotifier를 mixin(or 상속) 한다&lt;/p&gt;
&lt;p 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. ChangeNotifierProvider를 활용 및 적용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773911498656&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    // MultiProvider를 사용하면 여러 개의 Provider를 한 번에 등록할 수 있습니다.
    ChangeNotifierProvider(
      create: (context) =&amp;gt; CounterProvider(),
      child: const MyApp(),
    ),
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 사용할 위젯들의 공통 부모들을 ChangeNotifierProvider로 감싸준다.&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;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;main() 쪽에서 생성 및 초기화를 많이 하다 보니 MaterialApp이 최상위로 올라가는 형식이다&lt;/p&gt;
&lt;p 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. watch, read를 활용하여 UI에서 데이터 상태 관리 및 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773911604893&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterScreen extends StatelessWidget {
  const CounterScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // context.watch: 데이터가 변경될 때마다 화면을 다시 그림
    final counter = context.watch&amp;lt;CounterProvider&amp;gt;();

    return Scaffold(
      appBar: AppBar(title: const Text('Provider 예제')),
      body: Center(
        child: Text(
          '현재 카운트: ${counter.count}',
          style: const TextStyle(fontSize: 30),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // context.read: 데이터 변경 함수만 호출할 때 사용 (화면 리빌드 X)
          context.read&amp;lt;CounterProvider&amp;gt;().increment();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;상태 관리 사용 팁&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) context.watch&amp;lt;T&amp;gt;() or Consumer&amp;lt;T&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-1) 언제 사용하는가? -&amp;gt; 화면에 상태 값을 보여주어 상태가 변할 때 화면도 같이 업데이트(rebuild) 되도록 구성할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-2) 주의할 사항은? -&amp;gt; 이벤트 콜백 안에서는 사용할 수 없다. (onPressed, onTap 내부 등)&lt;/p&gt;
&lt;p 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) context.read&amp;lt;T&amp;gt;()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-1) 언제 사용하는가? -&amp;gt; 버튼 클릭 등 이벤트를 발생시켜 상태를 변경하는 메서드를 호출할 때 사용한다. 단순히 값을 한 번만 읽고 더 이상 구독하지 않을 때를 말함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-2) 주의할 사항은? -&amp;gt; build 메서드 내에서 변수에 할당하여 UI를 그릴 때 사용하면, 데이터가 바뀌어도 화면 업데이트가 되지 않는 문제가 존재한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 참조링크 -&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;a href=&quot;https://pub.dev/packages/provider&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/provider&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773910693155&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;provider | Flutter package&quot; data-og-description=&quot;A wrapper around InheritedWidget to make them easier to use and more reusable.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/provider&quot; data-og-url=&quot;https://pub.dev/packages/provider&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhnw7o/dJMb8RRQYeZ/5Sf64bEperYGsC4QF1RsKk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/0oSf9/dJMb86O0SPp/RL2R6e79cAEyPOneUR86T1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/IsRB2/dJMb8867Ukw/q7fcqC7Yesp34FZ1VRoec0/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/provider&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/provider&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhnw7o/dJMb8RRQYeZ/5Sf64bEperYGsC4QF1RsKk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/0oSf9/dJMb86O0SPp/RL2R6e79cAEyPOneUR86T1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/IsRB2/dJMb8867Ukw/q7fcqC7Yesp34FZ1VRoec0/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;provider | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A wrapper around InheritedWidget to make them easier to use and more reusable.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&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;</description>
      <category>Flutter</category>
      <author>SeongJo</author>
      <guid isPermaLink="true">https://okeybox.tistory.com/514</guid>
      <comments>https://okeybox.tistory.com/514#entry514comment</comments>
      <pubDate>Fri, 20 Mar 2026 18:30:50 +0900</pubDate>
    </item>
    <item>
      <title>[TypeScript] 기본 타입 정리</title>
      <link>https://okeybox.tistory.com/513</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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트에서 자주 사용되는 기본 타입에 대해 간략히 정리하는 시간을 가져보려 해요.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;타입스크립트의 기본 타입의 정의&lt;/b&gt;&lt;/h3&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;&amp;nbsp;&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;원시 타입(Primitive Types)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) string (문자열): 텍스트 데이터를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) number (숫자): 정수, 실수를 포함한 모든 숫자 데이터를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) boolean (논리형): true(참) 또는 false(거짓) 값만 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) null: 의도적으로 값이 '비어있음'을 나타낸다. (개발자가 의도하여 만든 것인지가 핵심이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) undefined: 변수를 선언만 하고 값을 할당하지 않은 상태를 나타낸다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;객체 / 배열 타입 (Object Types)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) object: 원시 타입을 제외한 모든 참조 타입(객체, 배열, 함수 등)을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) Array (배열): 같은 타입의 요소들을 모아놓은 배열이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) Tuple (튜플): 배열의 길이가 고정되고, 각 요소마다 타입이 지정된 특수한 배열이다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;특수 타입 (Special Types)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) Any: 모든 타입 허용 (타입 검사를 해제하고 싶을 때 사용하며, 겉 타입스크립트를 사용할 때 해당 값을 활용하게 된다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) unknown: '알 수 없는' 타입이다. (any보다 안전한 타입 체크를 유도하는 용도로 사용된다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) void: 반환 값이 없는 함수의 리턴 타입을 가져오는데 'return'이 없을 때 사용된다 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) never: 절대 발생할 수 없는 값의 타입을 정의하는데 무한 루프에 항상 에러를 던지는 값들의 반환 타입으로 사용되는 타입 정의이다.&lt;/p&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;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;</description>
      <category>Javascript</category>
      <author>SeongJo</author>
      <guid isPermaLink="true">https://okeybox.tistory.com/513</guid>
      <comments>https://okeybox.tistory.com/513#entry513comment</comments>
      <pubDate>Thu, 19 Mar 2026 18:30:47 +0900</pubDate>
    </item>
  </channel>
</rss>