티스토리 뷰
Redux(리덕스)란 무엇인가요?
Redux는 React(뿐만 아니라 다른 JavaScript 라이브러리/프레임워크)에서 **애플리케이션의 상태(State)를 효율적으로 관리하기 위한 예측 가능한 상태 컨테이너(Predictable State Container)**입니다.
쉽게 말해, 웹사이트가 복잡해지면서 여러 컴포넌트들이 데이터를 공유하고 변경해야 할 때, 데이터를 여기저기 흩뿌려놓지 않고 한 곳에 모아서 체계적으로 관리하게 해주는 도구라고 생각하시면 됩니다.
왜 Redux가 필요한가요? (고민 해결사)
우리가 이전에 배웠던 Props와 State를 생각해 보세요.
State:
컴포넌트 내부에서만 관리되는 데이터였죠.
Props:
부모에서 자식으로 데이터를 전달하는 방식이었고요.
그런데 애플리케이션이 커지면서 다음과 같은 문제가 생길 수 있어요.
Props Drilling (프롭스 드릴링):
멀리 떨어진 자식 컴포넌트에 데이터를 전달하기 위해 중간에 필요 없는 많은 컴포넌트들을 거쳐서 Props를 계속해서 내려줘야 하는 불편함.
컴포넌트 간 데이터 공유의 어려움:
서로 연관 없는 형제 컴포넌트나 아주 다른 위치에 있는 컴포넌트들끼리 데이터를 주고받기가 어려움.
Redux는 이런 문제들을 해결하기 위해 **앱 전체의 모든 중요한 상태(Global State)**를 한 곳(Store)에 모아 관리함으로써, 어떤 컴포넌트든 필요한 데이터에 직접 접근할 수 있게 해줍니다.
Redux의 세 가지 핵심 원칙
Redux는 데이터를 효율적으로 관리하기 위해 세 가지 중요한 규칙을 따릅니다.
하나의 진실된 원천 (Single Source of Truth) - Store
앱 전체의 모든 상태(State)는 단 하나의 거대한 객체 트리 형태로 하나의 Store 안에 저장됩니다.
앱의 '뇌'나 '중앙 통제실'이라고 생각하면 돼요.
상태는 읽기 전용 (State is read-only)
상태를 직접 변경할 수 없습니다.
오직 **액션(Action)**을 통해서만 상태를 변경할 수 있습니다.
액션은 '무슨 일이 일어났는지'를 설명하는 단순한 JavaScript 객체입니다.
순수 함수로만 변경 가능 (Changes are made with pure functions) - Reducer
액션에 의해 상태를 변경하는 것은 **리듀서(Reducer)**라는 순수(Pure) 함수만이 할 수 있습니다.
리듀서는 이전 상태와 액션을 받아서 새로운 상태를 반환합니다.
이 과정은 항상 예측 가능하며, 동일한 입력에 대해 항상 동일한 출력을 보장합니다.
Redux의 주요 구성 요소와 데이터 흐름
여러분에게 하나의 은행 계좌가 있다고 상상해 보세요.
State (상태):
은행 계좌의 현재 잔액입니다. (예: 100 달러)
앱에서 State는 화면에 보여지거나 앱의 특정 기능을 결정하는 현재 데이터를 의미해요.
예를 들어, 게시물 목록, 사용자 로그인 상태, 카운터의 숫자 등이 될 수 있죠.
Action (액션):
잔액을 변경하고 싶을 때 작성하는 **'요청서'**입니다. (예: "50 달러 입금 요청", "20 달러 출금 요청")
Action은 **'무슨 일이 일어났는지'**를 설명하는 간단한 JavaScript 객체입니다.
직접 상태를 바꾸는 게 아니라, 상태를 바꾸기 위한 **'의도'**만 전달하는 거예요.
보통 { type: 'INCREASE', payload: 10 } 와 같은 형태로, type으로 어떤 변화인지, payload로 추가적인 데이터(얼마나 바꿀지)를 전달합니다.
Dispatch (디스패치):
작성한 요청서(Action)를 은행원에게 건네는 행위입니다.
Dispatch는 Action 객체를 Reducer에게 전달하는 함수입니다.
우리가 직접 Reducer를 호출하는 게 아니라, Dispatch 함수를 통해 Action을 '보내기'만 하면 됩니다.
Reducer (리듀서):
요청서(Action)를 받아서 잔액을 실제로 계산하고 업데이트하는 '은행원' 입니다.
은행원은 현재 잔액(State)과 요청서 내용(Action)을 보고, 새로운 잔액을 계산해서 반환합니다.
Reducer는 (현재 State, Action)을 인자로 받아서 **새로운 State를 반환하는 '순수 함수'**입니다.
이 함수 안에서만 상태 변경 로직이 일어납니다.
Store (스토어):
은행의 중앙 전산 시스템입니다.
모든 고객의 계좌 잔액(State)이 이곳에 안전하게 보관되고 관리됩니다.
Store는 애플리케이션의 모든 State를 보관하는 '중앙 저장소' 역할을 합니다.
Redux에서는 이 Store가 하나로 통합되어 앱 전체의 상태를 관리하고, React의 useReducer에서는 해당 컴포넌트 내부의 상태를 관리하는 것이 작은 Store와 유사하게 작동합니다.
관계 및 데이터 흐름
이 모든 것들이 React에서 다음과 같은 흐름으로 서로 연결되어 동작합니다.
사용자 인터랙션:
사용자가 버튼을 클릭하거나(예: "증가" 버튼), 데이터를 입력합니다.
Action 생성:
이 행동에 맞는 Action 객체(예: { type: 'INCREMENT' })를 생성합니다.
Dispatch 호출:
생성된 Action을 dispatch 함수에 넣어 보냅니다. (dispatch({ type: 'INCREMENT' }))
Reducer 실행:
dispatch가 Action을 Reducer에게 전달하면, Reducer는 현재 State와 전달받은 Action을 분석합니다.
새로운 State 반환:
Reducer는 이 Action에 따라 State를 어떻게 변경할지 결정하고, 새로운 State를 계산하여 반환합니다.
Store 업데이트:
반환된 새로운 State로 Store(또는 useReducer의 경우 해당 컴포넌트의 내부 상태)가 업데이트됩니다.
UI 렌더링:
State가 변경되었으므로, React는 변경된 State를 사용하는 컴포넌트들을 자동으로 다시 렌더링하여 화면을 최신 상태로 업데이트합니다.
이러한 흐름은 단방향으로 이루어져, 상태 변화를 예측하기 쉽고 디버깅하기 용이하다는 장점이 있습니다.
React와 Redux 연동 (react-redux 라이브러리)
React와 Redux를 함께 사용하려면 **react-redux**라는 라이브러리를 사용합니다.
Provider:
React 컴포넌트 트리에 Redux Store를 연결하여, 하위 컴포넌트들이 Store에 접근할 수 있게 해줍니다.
useSelector():
Redux Store의 상태(State)에서 필요한 데이터를 가져올 때 사용하는 Hook입니다.
useDispatch():
Redux Store에 액션을 보낼(dispatch) 때 사용하는 Hook입니다.
Redux가 필요한 경우
애플리케이션의 크기가 크고 복잡할 때
전역적으로 관리되어야 하는 상태가 많을 때
상태의 변경 로직이 복잡하고 예측 가능해야 할 때
협업하는 개발자가 많을 때 (일관된 상태 관리 패턴 제공)
Redux는 강력하지만, 작은 프로젝트에는 useState나 useContext만으로도 충분할 수 있습니다.
프로젝트의 규모와 복잡도에 따라 적절히 선택하는 것이 중요합니다.
react-redux install
# If you use npm: npm install redux npm install react-redux
# Or if you use Yarn: yarn add react-redux
Final build
https://stackblitz.com/edit/vitejs-vite-75rbaovk?file=src%2FApp.jsx
egoing - React redux - StackBlitz
Next generation frontend tooling. It's fast!
시뻘건 박스 몇개 만들어 봅니다.
App.css
일단 다 지웁니다.
그리고 간단한 스타일을 제작 합니다.
#container,
#container div {
border: 5px solid red;
margin: 10px;
}
펑션레프트1을 추가 합니다.
export default function App() {
return (
<div id="container">
<h1>Root</h1>
</div>
);
}
function Left1(props) {
return (
<div>
<h1>Left1</h1>
</div>
);
}
펑션앱에 레프트를 추가하고 스타일을 적용합니다.
// 'App'이라는 이름의 함수형 컴포넌트를 정의하고 기본 내보내기로 설정합니다.
// 이 컴포넌트가 애플리케이션의 가장 상위(root) 컴포넌트 역할을 합니다.
export default function App() {
// 컴포넌트가 렌더링할 UI를 정의하는 JSX를 반환합니다.
return (
// 'container'라는 ID를 가진 div 요소입니다.
<div id="container">
{/* "Root"라는 텍스트를 표시하는 제목입니다. */}
<h1>Root</h1>
{/* 'Left1' 컴포넌트를 렌더링합니다. React의 컴포넌트 합성(composition)을 보여줍니다. */}
<Left1></Left1>
</div>
);
}
----------------------------------------------------------------------------------------------------------------------------------------------------------
function Left1(props) {
return (
<div>
<h1>Left1</h1>
<Left2></Left2>
</div>
);
}
function Left2(props) {
return (
<div>
<h1>Left2</h1>
<Left3></Left3>
</div>
);
}
function Left3(props) {
return (
<div>
<h1>Left3</h1>
</div>
);
}
펑션을 몇가지 더 추가합니다.
코드 페이스트 하다가 무한루프에 빠지지 않도록 조심합니다.
// 'Left1'이라는 이름의 함수형 컴포넌트입니다. 'props'를 인자로 받지만, 현재는 사용하고 있지 않습니다.
// 이 컴포넌트는 'App' 컴포넌트에 의해 렌더링됩니다.
function Left1(props) {
return (
// 'Left1' 컴포넌트의 UI를 정의합니다.
<div>
{/* "Left1"이라는 텍스트를 표시하는 제목입니다. */}
<h1>Left1</h1>
{/* 'Left2' 컴포넌트를 렌더링합니다. 이로써 컴포넌트의 중첩 구조가 이어집니다. */}
<Left2></Left2>
</div>
);
}
----------------------------------------------------------------------------------------------------------------------------------------------------------
TEST 진행
벌건 박스를 체크 합니다.

----------------------------------------------------------------------------------------------------------------------------------------------------------
import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
export default function App() {
return (
<div id="container">
<h1>Root</h1>
<Left1></Left1>
</div>
);
}
function Left1(props) {
return (
<div>
<h1>Left1</h1>
<Left2></Left2>
</div>
);
}
function Left2(props) {
return (
<div>
<h1>Left2</h1>
<Left3></Left3>
</div>
);
}
function Left3(props) {
return (
<div>
<h1>Left3</h1>
</div>
);
}
데이터를 프롭스로 최상위 컴포넌트에서 최하위 컴포넌트까지 전달 합니다.
유즈스테이트를 임포트 합니다.
import { useState } from 'react';
펑션앱 아래에 넘버 스테이트를 초기화 합니다.
// 'useState' 훅을 사용하여 'number'라는 상태 변수와 이를 업데이트하는 'setNumber' 함수를 선언합니다.
// 'number'의 초기값은 1로 설정됩니다.
const [number, setNumber] = useState(1);
펑션앱 넘버를 데이터바인딩 합니다.
{/* 'Left1' 컴포넌트를 렌더링합니다.
이때 'number' 상태 값을 'props'로 'Left1' 컴포넌트에 전달합니다.
이러한 데이터 전달 방식을 'prop drilling'이라고 합니다. */}
<Left1 number={number}></Left1>
넘버를 받고 전달 합니다.
{/* "Left1"이라는 텍스트와 함께 부모로부터 받은 'number' 값을 표시합니다.
props.number는 App 컴포넌트의 number 상태 값(1)이 됩니다. */}
<h1>Left1 : {props.number}</h1>
{/* 'Left2' 컴포넌트를 렌더링합니다.
다시 'props.number' 값을 'props'로 'Left2' 컴포넌트에 전달합니다. */}
<Left2 number={props.number}></Left2>
넘버를 받고 전달 합니다.
<h1>Left2 : {props.number}</h1>
<Left3 number={props.number}></Left3>
넘버를 받습니다.
<h1>Left3 : {props.number}</h1>
----------------------------------------------------------------------------------------------------------------------------------------------------------
TEST 진행
데이터가 잘 전달되는지 체크합니다.

----------------------------------------------------------------------------------------------------------------------------------------------------------
import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
export default function App() {
const [number, setNumber] = useState(1);
return (
<div id="container">
<h1>Root</h1>
<Left1 number={number}></Left1>
</div>
);
}
function Left1(props) {
return (
<div>
<h1>Left1 : {props.number}</h1>
<Left2 number={props.number}></Left2>
</div>
);
}
function Left2(props) {
return (
<div>
<h1>Left2 : {props.number}</h1>
<Left3 number={props.number}></Left3>
</div>
);
}
function Left3(props) {
return (
<div>
<h1>Left3 : {props.number}</h1>
</div>
);
}
심심하니까 오른쪽에도 박스를 몇개 쌓아 봅니다.
펑션레프트3 아래에 오른쪽 박스 하나 추가 합니다.
function Right1(props) {
return (
<div>
<h1>Right1</h1>
</div>
);
}
App.css
박스 스타일을 추가 합니다.
#grid {
display: grid;
grid-template-columns: 1fr 1fr;
}
펑션앱 에이치태그에 넘버를 바인딩 합니다.
<h1>Root : {number}</h1>
펑션앱 내부에 레프트원을 그리드 아이디 디브로 래핑 합니다. 오른쪽 박스를 추가 합니다.
<div id="grid">
<Left1 number={number}></Left1>
<Right1></Right1>
</div>
----------------------------------------------------------------------------------------------------------------------------------------------------------
function Right1(props) {
return (
<div>
<h1>Right1</h1>
<Right2></Right2>
</div>
);
}
function Right2(props) {
return (
<div>
<h1>Right2</h1>
<Right3></Right3>
</div>
);
}
function Right3(props) {
return (
<div>
<h1>Right3</h1>
</div>
);
}
오른쪽 박스를 몇개 더 만듭니다.
----------------------------------------------------------------------------------------------------------------------------------------------------------
TEST 진행
박스가 잘 쌓였는지 체크 합니다.

----------------------------------------------------------------------------------------------------------------------------------------------------------
App.css
#container,
#container div {
border: 5px solid red;
margin: 10px;
}
#grid {
display: grid;
grid-template-columns: 1fr 1fr;
}
import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
export default function App() {
const [number, setNumber] = useState(1);
return (
<div id="container">
<h1>Root : {number}</h1>
<div id="grid">
<Left1 number={number}></Left1>
<Right1></Right1>
</div>
</div>
);
}
function Left1(props) {
return (
<div>
<h1>Left1 : {props.number}</h1>
<Left2 number={props.number}></Left2>
</div>
);
}
function Left2(props) {
return (
<div>
<h1>Left2 : {props.number}</h1>
<Left3 number={props.number}></Left3>
</div>
);
}
function Left3(props) {
return (
<div>
<h1>Left3 : {props.number}</h1>
</div>
);
}
function Right1(props) {
return (
<div>
<h1>Right1</h1>
<Right2></Right2>
</div>
);
}
function Right2(props) {
return (
<div>
<h1>Right2</h1>
<Right3></Right3>
</div>
);
}
function Right3(props) {
return (
<div>
<h1>Right3</h1>
</div>
);
}
최하위 컴포넌트 버튼을 탭했을때 최상위 컴포넌트 데이터 변경 합니다.
Right3 버튼 탭하면 Root 데이터를 변경 합니다.
이벤트 코드를 역순으로 추가해 보겠습니다.
롸이트3에 더하기 유아이를 만들고 이벤트를 겁니다.
이 이벤트는 펑션앱 셋넘버 콜 합니다.
셋넘버 메서드는 아직 추가하지 않았습니다.
역순으로 올라가서 추가 합니다.
// Right3 컴포넌트는 제목과 함께 드디어 'number' 값을 증가시킬 버튼을 가지고 있어요!
// 이 버튼을 누르면 Right2, Right1을 거쳐 App 컴포넌트까지 전달된 onIncrease 기능이 실행됩니다.
function Right3(props) {
return (
<div>
<h1>Right3</h1>
<input
type="button"
value="+" // 버튼에 '+'라고 표시됩니다.
onClick={() => {
props.onIncrease(); // 버튼이 클릭되면 Right2에서 받은 onIncrease 기능을 실행합니다.
// 이 onIncrease는 결국 App 컴포넌트의 setNumber(number + 1)을 호출하게 되죠!
}}
></input>
</div>
);
}
이벤트를 전달 합니다.
// Right2 컴포넌트도 제목을 보여주고, Right1에서 받은 onIncrease 기능을 Right3에게 다시 전달합니다.
function Right2(props) {
return (
<div>
<h1>Right2</h1>
{/* Right1에서 받은 onIncrease 기능을 Right3에게 'onIncrease'라는 이름으로 전달합니다. */}
<Right3
onIncrease={() => {
props.onIncrease(); // 받은 onIncrease를 그대로 실행합니다.
}}
></Right3>
</div>
);
}
이벤트를 전달 합니다.
// Right1 컴포넌트는 제목을 보여주고, App에서 받은 onIncrease 기능을 Right2에게 고스란히 전달해 줍니다.
function Right1(props) {
return (
<div>
<h1>Right1</h1>
{/* App에서 받은 onIncrease 기능을 Right2에게 'onIncrease'라는 이름으로 전달합니다. */}
<Right2
onIncrease={() => {
props.onIncrease(); // 받은 onIncrease를 그대로 실행합니다.
}}
></Right2>
</div>
);
}
드디어 메서드를 추가 합니다.
셋넘버를 1 증가 합니다.
{/* Right1 컴포넌트에게는 'onIncrease'라는 특별한 기능을 전달해요!
이 기능을 실행하면 `number` 값을 1 증가시키도록 약속하는 거죠. */}
<Right1
onIncrease={() => {
setNumber(number + 1); // onIncrease가 호출되면 number를 1 증가시켜줍니다!
}}
></Right1>
----------------------------------------------------------------------------------------------------------------------------------------------------------
TEST 진행
플러스버튼유아이를 3번 탭합니다.
우측 유아이가 왼쪽 박스에 어떤 영향를 주는지 체크합니다.

----------------------------------------------------------------------------------------------------------------------------------------------------------
import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
export default function App() {
const [number, setNumber] = useState(1);
return (
<div id="container">
<h1>Root : {number}</h1>
<div id="grid">
<Left1 number={number}></Left1>
<Right1
onIncrease={() => {
setNumber(number + 1);
}}
></Right1>
</div>
</div>
);
}
function Left1(props) {
return (
<div>
<h1>Left1 : {props.number}</h1>
<Left2 number={props.number}></Left2>
</div>
);
}
function Left2(props) {
return (
<div>
<h1>Left2 : {props.number}</h1>
<Left3 number={props.number}></Left3>
</div>
);
}
function Left3(props) {
return (
<div>
<h1>Left3 : {props.number}</h1>
</div>
);
}
function Right1(props) {
return (
<div>
<h1>Right1</h1>
<Right2
onIncrease={() => {
props.onIncrease();
}}
></Right2>
</div>
);
}
function Right2(props) {
return (
<div>
<h1>Right2</h1>
<Right3
onIncrease={() => {
props.onIncrease();
}}
></Right3>
</div>
);
}
function Right3(props) {
return (
<div>
<h1>Right3</h1>
<input
type="button"
value="+"
onClick={() => {
props.onIncrease();
}}
></input>
</div>
);
}
일부 코드 제거하고 리덕스 사용 준비를 합니다.
위 코드는 각각의 박스가 위아래로 아주 단단하게 체결되어 있습니다.
심심하니까 중간에 컴포넌트가 1억개가 있다라고 상상 합니다.
단전 깊숙한 곳에 봉인된 나의 흑염룡이 정수리까지 뚫고 올라옵니다.
하지만 리액트가 잘못한것은 없습니다.
리액트는 원래 그런 애니까요.
잘못한 애를 나무랄게 아니라 해결잘하는 애를 데려옵니다.
이런 빡치는 상황을 무선 연결로 처리할 수 있는 아이가 리덕스입니다.
리덕스를 사용하기 위해 레프트 롸이트에 단단하게 체결되어 있던 고리를 해제 합니다.
롸이트1/2 메소드를 삭제합니다.
롸이트3 온클릭메소드 내부 펑션을 삭제합니다.
// 'Right1' 컴포넌트는 제목을 표시하고 'Right2' 컴포넌트를 렌더링합니다.
function Right1(props) {
return (
<div>
<h1>Right1</h1>
<Right2></Right2>
</div>
);
}
// 'Right2' 컴포넌트는 제목을 표시하고 'Right3' 컴포넌트를 렌더링합니다.
function Right2(props) {
return (
<div>
<h1>Right2</h1>
<Right3></Right3>
</div>
);
}
// 'Right3' 컴포넌트는 Right 계층 구조의 가장 하단에 있으며, 제목과 버튼을 포함합니다.
function Right3(props) {
return (
<div>
<h1>Right3</h1>
<input
type="button" // 버튼 타입의 input 요소입니다.
value="+" // 버튼에 표시될 텍스트입니다.
onClick={() => {}} // 버튼 클릭 시 실행될 함수입니다. 현재는 아무 동작도 하지 않습니다.
></input>
</div>
);
}
레프트원투쓰리 h태그 데이터바인딩을 삭제합니다.
레프트원투 차일드태그 데이터바인딩을 삭제합니다.
// 'Left1' 컴포넌트는 단순히 제목을 표시하고 'Left2' 컴포넌트를 렌더링합니다.
// 이전 코드와 달리, 'App' 컴포넌트의 `number` 값을 전달받지 않습니다.
function Left1(props) {
return (
<div>
<h1>Left1 : </h1>
<Left2></Left2>
</div>
);
}
// 'Left2' 컴포넌트는 제목을 표시하고 'Left3' 컴포넌트를 렌더링합니다.
function Left2(props) {
return (
<div>
<h1>Left2 : </h1>
<Left3></Left3>
</div>
);
}
// 'Left3' 컴포넌트는 Left 계층 구조의 가장 하단에 있으며, 제목만 표시합니다.
function Left3(props) {
return (
<div>
<h1>Left3 : </h1>
</div>
);
}
레프트원/롸이트원 태그 데이터바인딩/메소드를 삭제합니다.
<div id="grid">
{/* 'Left1' 컴포넌트를 렌더링합니다. 이 컴포넌트에는 현재 어떤 props도 전달되지 않습니다. */}
<Left1></Left1>
{/* 'Right1' 컴포넌트를 렌더링합니다. 이 컴포넌트에도 현재 어떤 props도 전달되지 않습니다. */}
<Right1></Right1>
</div>
TEST 진행
연결이 모두 해제되었는지 체크 합니다.

---------------------------------------------------------------------------------------------------------------------------------------------
import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
export default function App() {
const [number, setNumber] = useState(1);
return (
<div id="container">
<h1>Root : {number}</h1>
<div id="grid">
<Left1></Left1>
<Right1></Right1>
</div>
</div>
);
}
function Left1(props) {
return (
<div>
<h1>Left1 : </h1>
<Left2></Left2>
</div>
);
}
function Left2(props) {
return (
<div>
<h1>Left2 : </h1>
<Left3></Left3>
</div>
);
}
function Left3(props) {
return (
<div>
<h1>Left3 : </h1>
</div>
);
}
function Right1(props) {
return (
<div>
<h1>Right1</h1>
<Right2></Right2>
</div>
);
}
function Right2(props) {
return (
<div>
<h1>Right2</h1>
<Right3></Right3>
</div>
);
}
function Right3(props) {
return (
<div>
<h1>Right3</h1>
<input type="button" value="+" onClick={() => {}}></input>
</div>
);
}
리덕스 인스톨 합니다.
https://lshjju.tistory.com/150
React.js - redux - 리액트 리덕스
Redux(리덕스)란 무엇인가요? Redux는 React(뿐만 아니라 다른 JavaScript 라이브러리/프레임워크)에서 **애플리케이션의 상태(State)를 효율적으로 관리하기 위한 예측 가능한 상태 컨테이너(Predictable Stat
lshjju.tistory.com
최상위 데이타와 최하위 데이터를 무선으로 연결합니다.
리덕스에서 제일 먼저 해야 할 일은 스토어를 생성 하는 것입니다.
그러기 위한 스텝원으로 크리에이트스토어 임포트 합니다.
목주름은 레거시란 뜻이니 신경 안 써도 됩니다.
import { createStore } from 'redux'; // Redux 스토어를 생성하는 함수입니다.
스토어는 글로벌 스코프를 가져야 합니다.
그러므로 임포트 아래에 크리에이트스토어로 스토어 변수를 만듭니다.
const store = createStore();
리듀서를 설치 합니다.
리듀서 펑션을 만든 후에 스토어는 리듀서를 파라미터로 사용합니다.
그러므로 스토어 위에 펑션 리듀서를 추가 합니다.
리듀서는 현재스테이트/액션 두가지 파라미터를 사용합니다.
넘버 초기값을 여기서 지정합니다.
기존의 초기값은 나중에 삭제 합니다.
현재 스테이트를 복제하고 새로운 오브젝을 만듭니다.
스테이트는 결국 스토어에서 콘트롤하게 될것입니다.
즉 앞으로 스토어는 리듀서에서 리턴하는 데이터를 사용합니다.
뷰를 보면 에러가 난것 같겠지만 괜찮습니다.
스토어 파라미터를 세팅하면 해결 됩니다.
// Reducer(리듀서) 함수는 애플리케이션의 상태를 변경하는 방법을 정의합니다.
// `currentState`와 `action`을 인자로 받아 새로운 상태를 반환합니다.
function reducer(currentState, action) {
// 스토어가 처음 생성될 때 currentState가 undefined이므로 초기 상태를 설정합니다.
if (currentState === undefined) {
return {
number: 1, // 'number'라는 상태 변수의 초기값을 1로 설정합니다.
};
}
// 현재 상태를 복사하여 새로운 상태 객체를 만듭니다. (불변성을 유지하기 위함)
const newState = { ...currentState };
// 현재는 어떤 액션이 오더라도 상태를 변경하지 않고 현재 상태를 그대로 반환합니다.
return newState;
}
스토어의 파라미터로 리듀서를 사용합니다.
그리고 스토어는 리듀서가 리턴한 스테이트를 업데이트 합니다.
즉, 앞으로 스토어는 리듀서가 제공하는 신선한 스테이트를 공급 받을 것입니다.
// `reducer` 함수를 사용하여 Redux 스토어를 생성합니다.
// 이 스토어가 애플리케이션의 모든 상태를 중앙에서 관리하게 됩니다.
const store = createStore(reducer);
넘버는 이제부터 스토어에서 관리 합니다.
그로므로 펑션앱 넘버 스테이트는 이제 필요 없으니 삭제 합니다.
const [number, setNumber] = useState(1); // 삭제
h1태그 데이터바인딩도 당연히 삭제 합니다.
<h1>Root</h1>
리듀서 필수 포브라더스를 임포트 합니다.
프로바이더는 대문자로 시작 합니다.
connect 는 이 프로젝트에서는 사용하지 않습니다.
콘텍트는 클래스형 전용이고 이 프로젝트는 함수형 프로젝트이기 때문입니다.
하지만, 공식 인증 받은 포브라더스 멤버이므로 그냥 상징적으로 임포트 했습니다.
import { Provider, useSelector, useDispatch, connect } from 'react-redux';
// React 애플리케이션에 Redux를 연결하기 위한 도구들입니다.
// - Provider: Redux 스토어를 React 컴포넌트 트리에 제공합니다.
// - useSelector: Redux 스토어의 상태를 컴포넌트에서 선택하여 가져올 수 있게 합니다.
// - useDispatch: Redux 스토어에 액션을 보내기 위한 함수를 가져올 수 있게 합니다. (현재 코드에서는 사용되지 않습니다.)
// - connect: 클래스형 컴포넌트에서 Redux 스토어에 연결하는 고차 컴포넌트입니다. (현재 코드에서는 사용되지 않습니다.)
내부 컴포넌트가 스토어를 사용해야 합니다.
그러기 위해 펑션앱 내부에 프로바이더로 특정 할 최상위 태그를 래핑해서 스토어에 밸류를 배정 합니다.
이제 프로바이더 내부 컴포넌트는 스토어를 사용할 권리를 얻었습니다.
<div id="grid">
{/* <Provider> 컴포넌트를 사용하여 생성된 'store'를 하위 컴포넌트들에게 제공합니다.
<Provider> 내부에 있는 모든 컴포넌트는 Redux 스토어의 상태에 접근할 수 있습니다. */}
<Provider store={store}>
{/* 'Left1' 컴포넌트와 'Right1' 컴포넌트를 렌더링합니다. */}
<Left1></Left1>
<Right1></Right1>
</Provider>
</div>
이제 넘버벼수를 무선으로 연결해야 합니다.
유즈셀렉터는 이제부터 특정한 값을 무선으로 사용하겠다는 선언으로 이해하면 편합니다.
그러기 위해 펑션레프트3 리턴 위에 넘버를 넘버를 유즈셀렉터로 초기화 합니다.
유즈셀렉터는 펑션을 파라미터로 사용합니다.
그냥은 안되고 펑션이 필요하기 때문입니다.
f는 임시로 사용할 네임입니다.
const number = useSelector(f);
펑션레프트3 넘버변수 아래에 에프펑션을 추가합니다.
스테이트를 파라미터로 사용합니다.
스테이트중 넘버를 리턴 하겠습니다.
function f(state) {
return state.number;
}
펑션레프트3 코드체지방을 줄입니다.
펑션을 유즈셀렉터 파라미터 안에 장착 합니다.
즉, 스테이트중 넘버를 무선으로 사용하겠다는 의미로 받아들이면 편합니다.
// `useSelector` 훅을 사용하여 Redux 스토어의 상태 중 'number' 값을 가져옵니다.
// 이제 `number`는 App 컴포넌트에서 props로 전달받는 대신 Redux 스토어에서 직접 가져오게 됩니다.
const number = useSelector((state) => state.number);
펑션레프트3 데이터 바인딩 합니다.
축하 합니다.
이제 스토어 넘버와 h태그의 넘버가 무선으로 연결되었습니다.
<h1>Left3 : {number}</h1> {/* Redux 스토어에서 가져온 'number' 값을 표시합니다. */}
---------------------------------------------------------------------------------------------------------------------------------------------
TEST 진행
이제 루트와 레프트3 데이터가 무선으로 잘 연결 되는지 체크 합니다.
스토어 현재밸류인 1이 보인다면 성공입니다.

---------------------------------------------------------------------------------------------------------------------------------------------
import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch, connect } from 'react-redux';
function reducer(currentState, action) {
if (currentState === undefined) {
return {
number: 1,
};
}
const newState = { ...currentState };
return newState;
}
const store = createStore(reducer);
export default function App() {
return (
<div id="container">
<h1>Root</h1>
<div id="grid">
<Provider store={store}>
<Left1></Left1>
<Right1></Right1>
</Provider>
</div>
</div>
);
}
function Left1(props) {
return (
<div>
<h1>Left1 : </h1>
<Left2></Left2>
</div>
);
}
function Left2(props) {
return (
<div>
<h1>Left2 : </h1>
<Left3></Left3>
</div>
);
}
function Left3(props) {
const number = useSelector((state) => state.number);
return (
<div>
<h1>Left3 : {number}</h1>
</div>
);
}
function Right1(props) {
return (
<div>
<h1>Right1</h1>
<Right2></Right2>
</div>
);
}
function Right2(props) {
return (
<div>
<h1>Right2</h1>
<Right3></Right3>
</div>
);
}
function Right3(props) {
return (
<div>
<h1>Right3</h1>
<input type="button" value="+" onClick={() => {}}></input>
</div>
);
}
롸잇3 박스 유아이 플러스를 탭하면 레프트3 데이터가 변경 됩니다.
펑션롸이트3 이벤트 액션을 리듀서에 전달해야 합니다.
왜냐하면 이벤트의 목적지는 결국 리듀서이기 때문입니다.
딴일은 안하고 이 일만 열심히 하는 아이가 디스패치입니다.
펑션롸이트3 리턴위에 디스패치를 유즈디스패치로 초기화 합니다.
이제부터 디스패치는 유즈디스패치 기능을 사용할 수 있습니다.
// `useDispatch` 훅을 사용하여 Redux 스토어에 액션을 보낼 수 있는 `dispatch` 함수를 가져옵니다.
const dispatch = useDispatch();
펑션롸이트3 인풋 디스패치 타입을 디파인 합니다.
이벤트로 리듀서에 액션을 전달해야 하기 때문입니다.
액션은 오브젝이니까 기분 좋게 오브젝스타일로 디파인하면 좋겠습니다.
이 플러스를 탭하면 리듀서 콜 합니다.
<input
type="button" // 버튼 타입의 input 요소입니다.
value="+" // 버튼에 표시될 텍스트입니다.
onClick={() => {
// 버튼 클릭 시, `dispatch` 함수를 호출하여 { type: 'PLUS' } 액션을 Redux 스토어에 보냅니다.
// 이 액션은 `reducer` 함수에 의해 처리되어 `number` 상태를 증가시킵니다.
dispatch({ type: 'PLUS' });
}}
></input>
펑션리듀서 리턴 위에 액션타입에 대응하는 이프문을 작성합니다.
결과가 참이라면 증감연산자를 사용해서 데이터를 1증가시킵니다.
결국 뉴스테이트가 리턴 됩니다.
// `action.type`이 'PLUS'일 경우, `number` 상태를 1 증가시킵니다.
if (action.type === 'PLUS') {
newState.number++;
}
---------------------------------------------------------------------------------------------------------------------------------------------
TEST 진행
플러스 유아이를 탭합니다.
탭할 때 레프트3 데이터가 증가하는지 체크 합니다.
즉, 플러스를 탭하면 스테이트가 변경 됩니다.
스테이트가 변경되면 레프트3 데이터가 변경됩니다.

---------------------------------------------------------------------------------------------------------------------------------------------
완성코드
import { useState } from 'react'; // React에서 상태를 관리하는 'useState' 훅을 가져옵니다. (현재 이 코드에서는 직접 사용되지 않습니다.)
import reactLogo from './assets/react.svg'; // React 로고 이미지 파일을 가져옵니다. (코드 내에서 사용되지 않습니다.)
import viteLogo from '/vite.svg'; // Vite 로고 이미지 파일을 가져옵니다. (코드 내에서 사용되지 않습니다.)
import './App.css'; // 'App' 컴포넌트의 스타일을 정의하는 CSS 파일을 가져옵니다.
// Redux(리덕스) 관련 모듈을 가져옵니다.
import { createStore } from 'redux'; // Redux 스토어를 생성하는 함수입니다.
import { Provider, useSelector, useDispatch, connect } from 'react-redux';
// React 애플리케이션과 Redux 스토어를 연결하는 데 필요한 도구들을 가져옵니다.
// - Provider: React 컴포넌트 트리에 Redux 스토어를 제공합니다.
// - useSelector: Redux 스토어의 상태를 컴포넌트 내에서 선택적으로 조회할 수 있도록 합니다.
// - useDispatch: Redux 스토어에 액션(상태 변경 요청)을 보낼 수 있는 함수를 가져옵니다.
// - connect: (이 코드에서는 사용되지 않음) 클래스형 컴포넌트에서 Redux 스토어와 연결할 때 사용되는 고차 컴포넌트입니다.
// `reducer` 함수는 애플리케이션의 상태를 변경하는 방식을 정의합니다.
// `currentState`(현재 상태)와 `action`(발생한 이벤트)을 인자로 받아 새로운 상태를 반환합니다.
function reducer(currentState, action) {
// 스토어가 최초 생성될 때 `currentState`가 `undefined`이므로, 초기 상태를 설정합니다.
if (currentState === undefined) {
return {
number: 1, // `number` 상태 변수의 초기값을 1로 설정합니다.
};
}
// 현재 상태를 복사하여 새로운 상태 객체를 생성합니다. 이는 Redux의 불변성(immutability) 원칙을 따르기 위함입니다.
const newState = { ...currentState };
// `action.type`이 'PLUS'일 경우, `number` 상태를 1 증가시킵니다.
if (action.type === 'PLUS') {
newState.number++;
}
return newState; // 변경된 새로운 상태를 반환합니다.
}
// `reducer` 함수를 사용하여 Redux 스토어를 생성합니다.
// 이 스토어가 애플리케이션의 모든 전역 상태를 관리하게 됩니다.
const store = createStore(reducer);
// 'App' 컴포넌트는 애플리케이션의 최상위 컴포넌트입니다.
// 이 컴포넌트에서는 `useState`를 직접 사용하여 상태를 관리하는 대신, Redux 스토어를 사용합니다.
export default function App() {
return (
<div id="container">
<h1>Root</h1> {/* 'Root' 제목만 표시되며, `number` 값은 하위 컴포넌트에서 Redux를 통해 표시됩니다. */}
<div id="grid">
{/* <Provider> 컴포넌트를 사용하여 생성된 'store'를 하위 컴포넌트 트리에 제공합니다.
<Provider> 내부에 있는 모든 컴포넌트는 이 스토어의 상태에 접근할 수 있게 됩니다. */}
<Provider store={store}>
<Left1></Left1> {/* 'Left1' 컴포넌트를 렌더링합니다. */}
<Right1></Right1> {/* 'Right1' 컴포넌트를 렌더링합니다. */}
</Provider>
</div>
</div>
);
}
// 'Left1' 컴포넌트는 단순히 제목을 표시하고 'Left2' 컴포넌트를 렌더링합니다.
// Redux 스토어의 상태를 직접 사용하거나 props로 전달받지는 않습니다.
function Left1(props) {
return (
<div>
<h1>Left1 : </h1>
<Left2></Left2>
</div>
);
}
// 'Left2' 컴포넌트는 제목을 표시하고 'Left3' 컴포넌트를 렌더링합니다.
function Left2(props) {
return (
<div>
<h1>Left2 : </h1>
<Left3></Left3>
</div>
);
}
// 'Left3' 컴포넌트는 Redux 스토어의 상태를 직접 조회하여 사용합니다.
function Left3(props) {
// `useSelector` 훅을 사용하여 Redux 스토어의 전체 상태(state)에서 `number` 값을 가져옵니다.
const number = useSelector((state) => state.number);
return (
<div>
<h1>Left3 : {number}</h1> {/* Redux 스토어에서 가져온 `number` 값을 표시합니다. */}
</div>
);
}
// 'Right1' 컴포넌트는 제목을 표시하고 'Right2' 컴포넌트를 렌더링합니다.
function Right1(props) {
return (
<div>
<h1>Right1</h1>
<Right2></Right2>
</div>
);
}
// 'Right2' 컴포넌트는 제목을 표시하고 'Right3' 컴포넌트를 렌더링합니다.
function Right2(props) {
return (
<div>
<h1>Right2</h1>
<Right3></Right3>
</div>
);
}
// 'Right3' 컴포넌트는 제목과 버튼을 포함하며, 이 버튼을 통해 Redux 스토어에 액션을 보냅니다.
function Right3(props) {
// `useDispatch` 훅을 사용하여 Redux 스토어에 액션을 보낼 수 있는 `dispatch` 함수를 가져옵니다.
const dispatch = useDispatch();
return (
<div>
<h1>Right3</h1>
<input
type="button" // 버튼 타입의 input 요소입니다.
value="+" // 버튼에 표시될 텍스트입니다.
onClick={() => {
// 버튼 클릭 시, `dispatch` 함수를 호출하여 { type: 'PLUS' } 액션을 Redux 스토어에 보냅니다.
// 이 액션은 `reducer` 함수에 의해 처리되어 `number` 상태를 증가시킵니다.
dispatch({ type: 'PLUS' });
}}
></input>
</div>
);
}
참조링크
https://lshjju.tistory.com/150
egoing - React redux - stackblitz ver
React reduxhttps://lshjju.tistory.com/150 React.js - redux - 리액트 리덕스Redux(리덕스)란 무엇인가요? Redux는 React(뿐만 아니라 다른 JavaScript 라이브러리/프레임워크)에서 **애플리케이션의 상태(State)를 효율적
lshjju.tistory.com
'프로그래밍언어 > 리엑트' 카테고리의 다른 글
| [React] 프로젝트 만들기 part1 (0) | 2026.03.15 |
|---|---|
| [React] 리덕스 툴킷 (0) | 2026.03.08 |
| [React] useReducer (0) | 2026.03.08 |
| [React] Context API (0) | 2026.03.08 |
| [React] 스타일드 컴포넌트 (0) | 2026.03.01 |
