📂 오늘 배운 내용
- state의 종류와 상태관리(Context API, Redux)
- js 함수: `Number.toLocaleString()` 회계 단위 표현
- `ls -a` 숨겨진 파일(.git / .env) 포함 목록 조회 CLI
State 종류
- Local State: 지금까지 사용한 상태들 useState
- Cross-Component State: 지금까지 사용한 상태들. 2개 이상의 컴포넌트에서 props를 통해 상태 전달
- App-wide state: 여러 컴포넌트 또는 앱 전체 영역에서 공유되어야 하는 데이터나 상태
>> corss-component, app-wide 일 때 context API 또는 Redux가 요구됨
전역 상태 관리의 필요성
- 의미없는 행동(props drilling) 을 막기위해 전역 상태관리를 사용하게 됨
- Props를 너무 많이 전달해줄 때 씀
리액트의 상태 관리 방법 :
1. 리액트 기본 기능 contextAPI `useContext`
2. 라이브러리 사용 (설치 필요): redux, recoil, react-query, mobX...
Context API
context란? 컴포넌트 간 특정 값 공유 가능. State를 전역적으로 다룰 수 있음
# 기본 사용법
- context는 Provider라는 컴포넌트를 제공함
- value props로 넘겨준 값들을 하위컴포넌트 모두에서 사용 가능
1. createContext: 만들기 (context 폴더 따로 만들어서 Context 파일 관리)
2. provider: `App.js`에서 컴포넌트 감싸기
(1) `ThemContext` Context를 직접 Provider로 생성하고, valueProps로 전역 데이터 전달
(2) `UserProvider` Provider 컴포넌트를 통해 Context 제공
[App.js]
[components/ Provider/ userProvider.js]
import { useState } from 'react';
import { UserContext } from '../../context/UserContext';
// App.js state관리를 하는 것이 아닌
// 해당 state를 관리할 Provider를 따로 선언
export function UserProvider({ children }) {
const [name, setName] = useState('');
return (
<UserContext.Provider value={(name, setName)}>
{children}
</UserContext.Provider>
);
}
3. useContext: 실제로 사용하기 위해
[components/ Profile.jsx]
import { useContext, useRef } from 'react';
import { AgeContext } from '../context/AgeContext';
import { UserContext } from '../context/UserContext';
export default function Profile() {
const ageContext = useContext(AgeContext); // app.js에서 가져오기
const userContext = useContext(UserContext);
console.log('ageContext', ageContext); //{age: 0, setAge: ƒ}
console.log('userContext', userContext); //{name: '홍길동', setName: ƒ}
const nameRef = useRef();
const ageRef = useRef();
console.log(nameRef.value);
const { name, setName } = userContext;
const { age, setAge } = ageContext;
const changeInfo = () => {
setAge(Number(ageRef.current.value));
setName(nameRef.current.value);
};
return (
<div>
<h3>사용자 프로필</h3>
<p>이름: <context에서 값 가져오기 >: {name}</p>
<p>나이: <context에서 값 가져오기 >: {age}</p>
<input type='text' placeholder='이름 입력' ref={nameRef} />
<input type='number' placeholder='나이 입력' ref={ageRef} />
<br />
<button onClick={changeInfo}>변경</button>
</div>
);
}
# 폴더 구조
26.context-api/
├── node_modules/
├── public/
├── src/
│ ├── components/
│ │ ├── provider/
│ │ ├── Box.jsx
│ │ ├── Profile.jsx
│ ├── context/
│ │ ├── AgeContext.js
│ │ ├── ThemeContext.js
│ │ ├── UserContext.js
│ ├── pages/
│ │ ├── App.js
│ │ ├── index.js
├── .gitignore
├── App-before.js
├── App.js
├── package-lock.json
├── package.json
├── README.md
`Components`에서는 `Context`를 가져오고, `Context` 폴더에서 CreateContext로 만듦
# 주의(Official)
context 보다 컴포넌트 합성이 더 간단한 해결책일 수 있음. context사용하면 재사용이 어려워질 수 있기 때문!
Redux
# 용어 정리
useReducer와 비슷한 흐름으로 작동
1. store
- 전역 상태 관리하는 저장소
- 한 개의 프로젝트는 단 하나의 스토어만
- 스토어 안에는 앱 상태와 리듀서가 들어있음
- 스토어 데이터는 컴포넌트 직접 조작 x → 리듀서 함수로 관리
2. action
// action type 설정: 문자열 상수화
const CREATE = 'todo/CREATE';
const DONE = 'todo/DONE';
// action 생성 함수: 컴포넌트 내부에서 사용
export const create = (payload) => {
return {
type: CREATE,
payload, // action과 함께 store로 전달되는 추가 데이터
};
};
- Action은 컴포넌트에서 Store로 데이터를 운반하는 객체. 데이터를 Store로 전달하기 위한 '택배상자'같은 역할
- `type` : 무엇을 할 지, 리듀서 함수가 수행할 작업 명시
- `payload` : 상태 업데이트 시 필요한 정보를 담는 선택 속성
3. dispatch
- 액션 전달하는 함수
- dispatch(action)과 같은 형태로 액션 객체를 인자로 넣어 호출
4. reducer
- 액션의 타입에 따라 변화를 일으키는 함수 `reducer(현재 상태값, action 값){ 함수 }`
- store에 저장된 State는 reducer 함수만이 변경할 수 있음
- reducer는 기존 state를 변경하는 것이 아니라, 항상 새로운 state 객체 반환
- http 요청, 데이터 저장 금지!!🧐
# 시작하기
모듈 설치
npm i redux react-redux @reduxjs/toolkit
@reduxjs/toolkit란?
- 리덕스 복잡성을 줄이기 위해 만들어진 도구
- 리덕스 관련 기능 효율적 구현(액션 생성, 리듀서, 미들웨어 등)
- `configureStore()` 리덕스 스토어 생성 함수
- `createSlice()` 리듀서 + 액션 생성 함수
# Redux 흐름과 폴더 구조
1. 흐름
2. 폴더 구조
27.react-redux/
├── node_modules/
├── public/
├── src/
│ ├── components/ // React 컴포넌트 모음
│ │ ├── Bank.jsx // 은행 상태 관리 및 기능 구현 컴포넌트
│ ├── store/ // Redux 상태 관리소
│ │ ├── module/ // 상태 관리 로직(리듀서 함수) 폴더
│ │ │ ├── bankReducer.js // 은행 상태 관리 리듀서 함수
│ │ ├── index.js // 모든 리듀서를 통합(rootReducer)
│ ├── App.jsx // 최상위 컴포넌트, 모든 컴포넌트를 포함하는 메인 컴포넌트
│ ├── index.js // Store 설정 및 전달 (App.jsx 렌더링 엔트리 포인트)
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
- store: 상태 관리소
- store/module: 여러 개의 리듀서 함수 모아놓은 곳
- store/ index.js : 여러 Reducer를 통합하는 역할
Redux 예제: 입출금 기능
1. src/ store/ module 개별적인 전역 state 선언 : reducer 생성
⭐️ reducer 선언 = state 만들기
실제로 상태를 관리하는 reducer 함수 만들기
@ src/ store/ module/ bankReducer.js
// action type의 상수화
const WITHDRAW = 'bank/WITHDRAW';
const DEPOSIT = 'bank/DEPOSIT';
// action 리턴 함수
export const deposit = (payload) => ({
type: 'bank/DEPOSIT',
payload: payload,
});
export const withdraw = (payload) => ({
type: 'bank/WITHDRAW',
payload,
});
const initialState = 0;
// [todo 1] src/store/module 개별적인 전역 state 선언 -> reducer 생성
export const bankReducer = (state = initialState, action) => {
console.log('bank action', action);
switch (action.type) {
case DEPOSIT:
return state + action.payload;
case WITHDRAW:
return state - action.payload;
default:
return state;
}
};
2. store/ module에서 만들어준 여러개의 reducer 통합
@ src/ store/ index.js
// [todo2] store/module에서 만들어준 여러 개의 Reducer를 통합
// store/ index.js : 여러 개의 reducer를 통합하는 역할
import { combineReducers } from '@reduxjs/toolkit';
import { isDataReducer } from './module/isDataReducer';
import { counterReducer } from './module/counterReducer';
import { bankReducer } from './module/bankReducer';
const rootReducer = combineReducers({
isData: isDataReducer,
count: counterReducer,
bank: bankReducer,
// 만약 전역 관리 State가 추가되면 이곳에도 추가
// Key값: useSelector를 이용해 가져오는 값
});
// [todo2-2] 내보내기 -> src/ index.js에서 쓰일 예정
export default rootReducer;
3. src/index.js 에서 store 설정하고 <Provider> 컴포넌트로 감싸기
@ src/ index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import rootReducer from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
// [Todo3] store 설정
// module/ index.js에서 통합한 root reducer를 value로 전달
const store = configureStore({ reducer: rootReducer });
// [Todo4] App 컴포넌트의 자식 컴포넌트 모두에서 사용 가능하도록
// store Props로 store 전달
root.render(
<Provider store={store}>
<App />
</Provider>
);
(1) `configureStore` 사용해서 Store 생성:
- rootReducer 기반으로 Redux store 생성
(2) Redux 사용할 때는 Provider로 App을 감싸야함 :
- `Provider` : React 애플리케이션의 최상위 컴포넌트를 Redux store에 연결하는 역할
- `Provider` 컴포넌트는 `store`를 `props`로 받아 애플리케이션 전역에 전달
- App 컴포넌트의 하위 컴포넌트 모두 redux store 접근 가능
4. 컴포넌트에서 사용하기
@ src/ components/ Bank.jsx
import { useDispatch, useSelector } from 'react-redux';
import { deposit, withdraw } from '../store/module/bankReducer';
import { useState } from 'react';
export default function Bank() {
const [inputNumber, setInputNumber] = useState(0);
// [*] useSelector: Store에 저장되어 있는 state 가져오기
const balance = useSelector((state) => state.bank);
console.log('잔액', balance);
// [*] useDispatch: action을 reducer로 전달하기 위한 dispatch 함수 제공
const dispatch = useDispatch();
return (
<div>
<p>잔액: {balance.toLocaleString()}원</p>
<input
type='number'
step={10000}
value={inputNumber}
onChange={(e) => setInputNumber(Number(e.target.value))}
/>
<button onClick={() => dispatch(deposit(inputNumber))}>입금</button>
<button onClick={() => dispatch(withdraw(inputNumber))}>출금</button>
</div>
);
}
- 상태 가져오기 `useSelector( )`
- 상태 변경하기 `useDispatch( )`
- dispatch의 action 타입에 따라 로직을 바꿔줄 수 있음
@ src/ App.jsx
import Bank from './components/Bank';
function App() {
return (
<>
<h2>여러 개의 전역 state 사용하기</h2>
<Bank />
</>
);
}
export default App;
useReducer vs ContextAPI vs Redux
차이점
구분 | useReducer | ContextAPI | Redux |
용도 | 컴포넌트 내부 상태 관리 | 간단한 전역 상태관리, Props Drilling 해결 |
전역 상태 관리 도구 |
범위 | 컴포넌트 단위 | 애플리케이션 전역 | 애플리케이션 전역 |
구조 | - React Hook의 하나 - `useReducer(reducer, initial State)` 형태로 사용 |
- `Context.Provider`를 통해 상태를 전달하고, 'useContext`로 소비 | - 상태를 관리하기 위해 `store`, `action`, `reducer` 등 설정해야함 |
데이터 흐름 | 상태와 로직이 컴포넌트에 국한됨 | Provider를 통해 트리 구조의 하위 컴포넌트로 데이터 전달 | 상태와 로직이 전역적으로 적용 (상태가 중앙 저장소 store에 저장됨) |
설치 여부 | 별도 설치 필요x (React 내장 hook) |
별도 설치 필요x (React 내장 API) |
`reudx`, `react-redux`등 패키지 설치 필요 |
미들웨어 | x | x | 사용 가능: 비동기 작업 처리 (Redux-thunk, redux-saga 등) |
디버깅 | console.log | console.log | Redux DevTools 도구 |
특징 | 상태가 컴포넌트 내에 한정되어 있어 전역적 공유 어려움 | Context 값을 많이 소비하면(빈번한 상태 업데이트 시) 렌더링 성능 저하 발생 가능 | - 구조적으로 전역 상태를 효율적으로 관리하므로 대규모 애플리케이션에 적합 - 설정 및 초기 구성이 복잡함 |
💡 회고
1. Redux와 상태 흐름:
node.js sequelize 때 데이터 흐름을 따라가지 못했던 기분을 또 다시 느끼는 Redux 🤢
상태도 데이터라서 폴더와 파일들을 분리하고 관리해야 하는 점이 어렵게 느껴진다.
2. useReducer 과제 <to do list>를 구현할 때의 고민:
JSX가 복잡해지는 게 싫어서 처음에는 '할 일 추가, 삭제, 완료 표시 기능'을 모두 개별 이벤트 핸들러로 만들어서 onClick에 전달했었다. 작성하면서 reducer 함수와 ToDoApp 내의 이벤트 역할이 모호하게 느껴졌었다. Redux를 배우면서, useReducer에서 문자열 상수를 따로 관리했던 것처럼, Redux에서도 액션과 리듀서를 따로 관리할 수 있다는 걸 알게되었다!!!
근데 아직도 잘 모르겠다! 🤷♀️
3. HTML/EJS/JSX 태그와 가독성 문제:
ejs 때도 극혐이었지만 개인적으로 HTML/EJS/ JSX 태그에 뭔가 덕지 덕지(?) 붙어서 길어지는 게 싫다 🙃 특히 JSX에서는 태그가 복잡해질 수록 가독성이 떨어지는 것 같아서 뭔가...좀 더 깔끔해질 수 있는 방법이 없는지 궁금하다. 컴포넌트를 어떻게 나눠야할 지 모르겠음.
🌐 참고
서울시 청년취업사관학교 x 코딩온 웹 개발자 풀스택 과정 2기
26.context-api
- 상자 (themeContext)
- 사용자 프로필 정보 변경 기능
27.react-redux
- 상자들
- 은행 예금, 출금 기능
'Today I Learned > SeSAC 웹 2기' 카테고리의 다른 글
TypeScript와 React | 13주차(3) (0) | 2025.01.30 |
---|---|
Typescript 입문 🥳 | 13주차(2) (0) | 2025.01.24 |
React Router | 13주차(1)상 (1) | 2025.01.23 |
React Hooks, useForm | 12주차 (3) (0) | 2025.01.17 |
React Style 적용 방법 | 12주차 (2) (0) | 2025.01.14 |